diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 7269fa92644e..3aa14155bf85 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -1,7 +1,12 @@ version: 2 updates: - package-ecosystem: "gradle" - directory: "/" + directory: "/hedera-dependency-versions" + schedule: + interval: "daily" + open-pull-requests-limit: 10 + - package-ecosystem: "gradle" + directory: "/gradle/plugins" schedule: interval: "daily" open-pull-requests-limit: 10 diff --git a/.github/workflows/config/node-release.yaml b/.github/workflows/config/node-release.yaml index 029a80e4f33d..439ff9240c77 100644 --- a/.github/workflows/config/node-release.yaml +++ b/.github/workflows/config/node-release.yaml @@ -1,11 +1,11 @@ release: branching: execution: - time: "20:00:00" + time: "22:00:00" schedule: - - on: "2023-02-27" - name: release/0.48 + - on: "2024-05-06" + name: release/0.50 initial-tag: - create: false - name: v0.45.0-alpha.2 + create: true + name: v0.50.0-alpha.0 diff --git a/.github/workflows/node-flow-build-application.yaml b/.github/workflows/node-flow-build-application.yaml index 020e39b42936..01a3b54f4c1c 100644 --- a/.github/workflows/node-flow-build-application.yaml +++ b/.github/workflows/node-flow-build-application.yaml @@ -94,3 +94,4 @@ jobs: codacy-project-token: ${{ secrets.CODACY_PROJECT_TOKEN }} gradle-cache-username: ${{ secrets.GRADLE_CACHE_USERNAME }} gradle-cache-password: ${{ secrets.GRADLE_CACHE_PASSWORD }} + codecov-token: ${{ secrets.CODECOV_TOKEN }} diff --git a/.github/workflows/node-flow-deploy-adhoc-artifact.yaml b/.github/workflows/node-flow-deploy-adhoc-artifact.yaml index b2bcb95fa4c1..2160fee3da9b 100644 --- a/.github/workflows/node-flow-deploy-adhoc-artifact.yaml +++ b/.github/workflows/node-flow-deploy-adhoc-artifact.yaml @@ -51,7 +51,7 @@ jobs: with: version-policy: branch-commit trigger-env-deploy: none - sdk-release-profile: AdhocCommit + release-profile: AdhocCommit dry-run-enabled: ${{ github.event.inputs.dry-run-enabled == 'true' }} java-version: ${{ github.event.inputs.java-version || '21.0.1' }} java-distribution: ${{ github.event.inputs.java-distribution || 'temurin' }} diff --git a/.github/workflows/node-flow-deploy-release-artifact.yaml b/.github/workflows/node-flow-deploy-release-artifact.yaml index 963eea885158..fa3fc7fc329b 100644 --- a/.github/workflows/node-flow-deploy-release-artifact.yaml +++ b/.github/workflows/node-flow-deploy-release-artifact.yaml @@ -70,7 +70,7 @@ jobs: version-policy: specified new-version: ${{ needs.prepare-tag-release.outputs.version }} trigger-env-deploy: none - sdk-release-profile: ${{ needs.prepare-tag-release.outputs.prerelease == 'true' && 'PrereleaseChannel' || 'MavenCentral' }} + release-profile: ${{ needs.prepare-tag-release.outputs.prerelease == 'true' && 'PrereleaseChannel' || 'MavenCentral' }} secrets: access-token: ${{ secrets.GITHUB_TOKEN }} bucket-name: ${{ secrets.RELEASE_ARTIFACT_BUCKET_NAME }} @@ -97,7 +97,7 @@ jobs: with: version-policy: branch-commit trigger-env-deploy: integration - sdk-release-profile: DevelopCommit + release-profile: DevelopCommit secrets: access-token: ${{ secrets.GITHUB_TOKEN }} bucket-name: ${{ secrets.RELEASE_ARTIFACT_BUCKET_NAME }} diff --git a/.github/workflows/node-flow-pull-request-checks.yaml b/.github/workflows/node-flow-pull-request-checks.yaml index b446c4f56a8b..0c33169c3a15 100644 --- a/.github/workflows/node-flow-pull-request-checks.yaml +++ b/.github/workflows/node-flow-pull-request-checks.yaml @@ -87,6 +87,7 @@ jobs: gradle-cache-username: ${{ secrets.GRADLE_CACHE_USERNAME }} gradle-cache-password: ${{ secrets.GRADLE_CACHE_PASSWORD }} codacy-project-token: ${{ secrets.CODACY_PROJECT_TOKEN }} + codecov-token: ${{ secrets.CODECOV_TOKEN }} eet-tests: name: E2E Tests diff --git a/.github/workflows/node-zxc-build-release-artifact.yaml b/.github/workflows/node-zxc-build-release-artifact.yaml index a94a78530355..00c9944b23b5 100644 --- a/.github/workflows/node-zxc-build-release-artifact.yaml +++ b/.github/workflows/node-zxc-build-release-artifact.yaml @@ -29,15 +29,15 @@ on: type: string required: true default: "none" - # Valid SDK release profiles are as follows: + # Valid release profiles are as follows: # - none # - AdhocCommit # - DevelopCommit # - DevelopDailySnapshot # - DevelopSnapshot # - PrereleaseChannel - sdk-release-profile: - description: "SDK Release Profile:" + release-profile: + description: "Release Profile:" type: string required: true default: "none" @@ -687,103 +687,12 @@ jobs: name: Production Image Manifests path: ${{ env.DOCKER_MANIFEST_PATH }} - evm-mc-publish: - name: Publish EVM to Maven Central + publish: + name: Publish to ${{ inputs.version-policy == 'specified' && 'Maven Central' || 'GCP Registry' }} runs-on: [ self-hosted, Linux, large, ephemeral ] needs: - validate - steps: - - name: Checkout Code - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 - - - name: Install GnuPG Tools - if: ${{ inputs.dry-run-enabled != true }} - run: | - if ! command -v gpg2 >/dev/null 2>&1; then - echo "::group::Updating APT Repository Indices" - sudo apt update - echo "::endgroup::" - echo "::group::Installing GnuPG Tools" - sudo apt install -y gnupg2 - echo "::endgroup::" - fi - - - name: Import GPG key - id: gpg_key - uses: crazy-max/ghaction-import-gpg@01dd5d3ca463c7f10f7f4f7b4f177225ac661ee4 # v6.1.0 - if: ${{ inputs.dry-run-enabled != true && !cancelled() && !failure() }} - with: - gpg_private_key: ${{ secrets.svcs-gpg-key-contents }} - passphrase: ${{ secrets.svcs-gpg-key-passphrase }} - git_config_global: true - git_user_signingkey: true - git_commit_gpgsign: true - git_tag_gpgsign: true - - - name: Setup Java - uses: actions/setup-java@387ac29b308b003ca37ba93a6cab5eb57c8f5f93 # v4.0.0 - with: - distribution: ${{ inputs.java-distribution }} - java-version: ${{ inputs.java-version }} - - - name: Setup Gradle - uses: gradle/gradle-build-action@29c0906b64b8fc82467890bfb7a0a7ef34bda89e # v3.1.0 - with: - gradle-version: ${{ inputs.gradle-version }} - - - name: Restore Build Version - uses: actions/cache@ab5e6d0c87105b4c9c2047343972218f562e4319 # v4.0.1 - with: - fail-on-cache-miss: true - path: version.txt - key: node-build-version-${{ needs.validate.outputs.version }}-${{ github.sha }} - - - name: Gradle Update Version (Snapshot) - uses: gradle/gradle-build-action@29c0906b64b8fc82467890bfb7a0a7ef34bda89e # v3.1.0 - if: ${{ inputs.version-policy != 'specified' && !cancelled() && !failure() }} - with: - gradle-version: ${{ inputs.gradle-version }} - arguments: versionAsSnapshot --scan - - - name: Gradle Assemble - id: gradle-build - uses: gradle/gradle-build-action@29c0906b64b8fc82467890bfb7a0a7ef34bda89e # v3.1.0 - with: - gradle-version: ${{ inputs.gradle-version }} - arguments: assemble --scan - - - name: Gradle Version Summary - uses: gradle/gradle-build-action@29c0906b64b8fc82467890bfb7a0a7ef34bda89e # v3.1.0 - with: - gradle-version: ${{ inputs.gradle-version }} - arguments: githubVersionSummary --scan - - - name: Gradle Maven Central Release - uses: gradle/gradle-build-action@29c0906b64b8fc82467890bfb7a0a7ef34bda89e # v3.1.0 - if: ${{ inputs.dry-run-enabled != true && inputs.version-policy == 'specified' && !cancelled() && !failure() }} - env: - OSSRH_USERNAME: ${{ secrets.svcs-ossrh-username }} - OSSRH_PASSWORD: ${{ secrets.svcs-ossrh-password }} - with: - gradle-version: ${{ inputs.gradle-version }} - arguments: "releaseEvmMavenCentral --scan -PpublishSigningEnabled=true --no-configuration-cache --no-parallel" - - - name: Gradle Maven Central Snapshot - uses: gradle/gradle-build-action@29c0906b64b8fc82467890bfb7a0a7ef34bda89e # v3.1.0 - if: ${{ inputs.dry-run-enabled != true && inputs.version-policy != 'specified' && !cancelled() && !failure() }} - env: - OSSRH_USERNAME: ${{ secrets.svcs-ossrh-username }} - OSSRH_PASSWORD: ${{ secrets.svcs-ossrh-password }} - with: - gradle-version: ${{ inputs.gradle-version }} - arguments: "releaseEvmMavenCentralSnapshot --scan -PpublishSigningEnabled=true --no-configuration-cache --no-parallel" - - sdk-publish: - name: Publish Platform to ${{ inputs.version-policy == 'specified' && 'Maven Central' || 'GCP Registry' }} - runs-on: [ self-hosted, Linux, large, ephemeral ] - needs: - - validate - if: ${{ inputs.sdk-release-profile != 'none' }} + if: ${{ inputs.release-profile != 'none' }} steps: - name: Checkout Code uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 @@ -924,15 +833,15 @@ jobs: gpg --output "${PUBLIC_ARCHIVE_FILE}.sha256.asc" --detach-sig "${PUBLIC_ARCHIVE_FILE}.sha256" echo "::endgroup::" - - name: Gradle Publish to ${{ inputs.version-policy == 'specified' && 'Maven Central' || 'Google Artifact Registry' }} (${{ inputs.sdk-release-profile }}) + - name: Gradle Publish to ${{ inputs.version-policy == 'specified' && 'Maven Central' || 'Google Artifact Registry' }} (${{ inputs.release-profile }}) uses: gradle/gradle-build-action@29c0906b64b8fc82467890bfb7a0a7ef34bda89e # v3.1.0 - if: ${{ inputs.dry-run-enabled != true && inputs.sdk-release-profile != 'none' && !cancelled() && !failure() }} + if: ${{ inputs.dry-run-enabled != true && inputs.release-profile != 'none' && !cancelled() && !failure() }} env: OSSRH_USERNAME: ${{ secrets.sdk-ossrh-username }} OSSRH_PASSWORD: ${{ secrets.sdk-ossrh-password }} with: gradle-version: ${{ inputs.gradle-version }} - arguments: "release${{ inputs.sdk-release-profile }} --scan -PpublishSigningEnabled=true --no-configuration-cache --no-parallel" + arguments: "release${{ inputs.release-profile }} --scan -PpublishSigningEnabled=true --no-configuration-cache" - name: Upload SDK Release Archives if: ${{ inputs.dry-run-enabled != true && inputs.version-policy == 'specified' && !cancelled() && !failure() }} @@ -949,8 +858,7 @@ jobs: - validate - local-node-images - validate-production-image - - evm-mc-publish - - sdk-publish + - publish if: ${{ inputs.dry-run-enabled != true && inputs.version-policy == 'specified' && !cancelled() && !failure() }} steps: - name: Determine Notification Parameters @@ -960,7 +868,7 @@ jobs: ARTIFACT_LINK_NAME="MC Availability Check" ARTIFACT_REGISTRY="Maven Central" - if [[ "${{ inputs.sdk-release-profile }}" == "PrereleaseChannel" ]]; then + if [[ "${{ inputs.release-profile }}" == "PrereleaseChannel" ]]; then ARTIFACT_URL="https://console.cloud.google.com/artifacts/maven/swirlds-registry/us/maven-prerelease-channel/com.swirlds:swirlds-platform-core/${{ needs.validate.outputs.version }}?project=swirlds-registry" ARTIFACT_LINK_NAME="GCP Registry" ARTIFACT_REGISTRY="GCP Artifact Registry" diff --git a/.github/workflows/node-zxc-compile-application-code.yaml b/.github/workflows/node-zxc-compile-application-code.yaml index bedd75a0da74..4815ea54f2f9 100644 --- a/.github/workflows/node-zxc-compile-application-code.yaml +++ b/.github/workflows/node-zxc-compile-application-code.yaml @@ -125,6 +125,9 @@ on: codacy-project-token: description: "The Codacy project token used to report code coverage." required: false + codecov-token: + description: "The Codecov token used to report code coverage." + required: false defaults: run: @@ -224,7 +227,7 @@ jobs: - name: Unit Testing id: gradle-test if: ${{ inputs.enable-unit-tests && steps.gradle-build.conclusion == 'success' && !cancelled() }} - run: ${GRADLE_EXEC} test timingSensitive jacocoTestReport --continue --scan + run: ${GRADLE_EXEC} :reports:testCodeCoverageReport --continue --scan - name: Publish Unit Test Report uses: EnricoMi/publish-unit-test-result-action@30eadd5010312f995f0d3b3cff7fe2984f69409e # v2.16.1 @@ -298,14 +301,14 @@ jobs: check_name: 'Node: HAPI Test (Misc) Results' check_run_disabled: false json_thousands_separator: ',' - junit_files: "**/build/test-results/hapiTestMisc/TEST-*.xml" + junit_files: "**/test-clients/build/test-results/test/TEST-*.xml" - name: Upload HAPI Test (Misc) Report Artifacts uses: actions/upload-artifact@5d5d22a31266ced268874388b861e4b58bb5c2f3 # v4.3.1 if: ${{ inputs.enable-hapi-tests-misc && steps.gradle-build.conclusion == 'success' && !cancelled() }} with: name: HAPI Test (Misc) Reports - path: "**/build/test-results/hapiTestMisc/TEST-*.xml" + path: "**/test-clients/build/test-results/test/TEST-*.xml" retention-days: 7 - name: Upload HAPI Test (Misc) Network Logs @@ -332,14 +335,14 @@ jobs: check_name: 'Node: HAPI Test (Crypto) Results' check_run_disabled: false json_thousands_separator: ',' - junit_files: "**/build/test-results/hapiTestCrypto/TEST-*.xml" + junit_files: "**/test-clients/build/test-results/test/TEST-*.xml" - name: Upload HAPI Test (Crypto) Report Artifacts uses: actions/upload-artifact@5d5d22a31266ced268874388b861e4b58bb5c2f3 # v4.3.1 if: ${{ inputs.enable-hapi-tests-crypto && steps.gradle-build.conclusion == 'success' && !cancelled() }} with: name: HAPI Test (Crypto) Report - path: "**/build/test-results/hapiTestCrypto/TEST-*.xml" + path: "**/test-clients/build/test-results/test/TEST-*.xml" retention-days: 7 - name: Upload HAPI Test (crypto) Network Logs @@ -366,14 +369,14 @@ jobs: check_name: 'Node: HAPI Test (Token) Results' check_run_disabled: false json_thousands_separator: ',' - junit_files: "**/build/test-results/hapiTestToken/TEST-*.xml" + junit_files: "**/test-clients/build/test-results/test/TEST-*.xml" - name: Upload HAPI Test (Token) Report Artifacts uses: actions/upload-artifact@5d5d22a31266ced268874388b861e4b58bb5c2f3 # v4.3.1 if: ${{ inputs.enable-hapi-tests-token && steps.gradle-build.conclusion == 'success' && !cancelled() }} with: name: HAPI Test (Token) Report - path: "**/build/test-results/hapiTestToken/TEST-*.xml" + path: "**/test-clients/build/test-results/test/TEST-*.xml" retention-days: 7 - name: Upload HAPI Test (Token) Network Logs @@ -400,14 +403,14 @@ jobs: check_name: 'Node: HAPI Test (Smart Contract) Results' check_run_disabled: false json_thousands_separator: ',' - junit_files: "**/build/test-results/hapiTestSmartContract/TEST-*.xml" + junit_files: "**/test-clients/build/test-results/test/TEST-*.xml" - name: Upload HAPI Test (Smart Contract) Report Artifacts uses: actions/upload-artifact@5d5d22a31266ced268874388b861e4b58bb5c2f3 # v4.3.1 if: ${{ inputs.enable-hapi-tests-smart-contract && steps.gradle-build.conclusion == 'success' && !cancelled() }} with: name: HAPI Test (Smart Contract) Report - path: "**/build/test-results/hapiTestSmartContract/TEST-*.xml" + path: "**/test-clients/build/test-results/test/TEST-*.xml" retention-days: 7 - name: Upload HAPI Test (Smart Contract) Network Logs @@ -434,14 +437,14 @@ jobs: check_name: 'Node: HAPI Test (Time Consuming) Results' check_run_disabled: false json_thousands_separator: ',' - junit_files: "**/build/test-results/hapiTestTimeConsuming/TEST-*.xml" + junit_files: "**/test-clients/build/test-results/test/TEST-*.xml" - name: Upload HAPI Test (Time Consuming) Report Artifacts uses: actions/upload-artifact@5d5d22a31266ced268874388b861e4b58bb5c2f3 # v4.3.1 if: ${{ inputs.enable-hapi-tests-time-consuming && steps.gradle-build.conclusion == 'success' && !cancelled() }} with: name: HAPI Test (Time Consuming) Report - path: "**/build/test-results/hapiTestTimeConsuming/TEST-*.xml" + path: "**/test-clients/build/test-results/test/TEST-*.xml" retention-days: 7 - name: Upload HAPI Test (Time Consuming) Network Logs @@ -468,14 +471,14 @@ jobs: check_name: 'Node: HAPI Test (Restart) Results' check_run_disabled: false json_thousands_separator: ',' - junit_files: "**/build/test-results/hapiTestRestart/TEST-*.xml" + junit_files: "**/test-clients/build/test-results/test/TEST-*.xml" - name: Upload HAPI Test (Restart) Report Artifacts uses: actions/upload-artifact@5d5d22a31266ced268874388b861e4b58bb5c2f3 # v4.3.1 if: ${{ inputs.enable-hapi-tests-restart && steps.gradle-build.conclusion == 'success' && !cancelled() }} with: name: HAPI Test (Restart) Report - path: "**/build/test-results/hapiTestRestart/TEST-*.xml" + path: "**/test-clients/build/test-results/test/TEST-*.xml" retention-days: 7 - name: Upload HAPI Test (Restart) Network Logs @@ -503,14 +506,14 @@ jobs: check_name: 'Node: HAPI Test (Node Death Reconnect) Results' check_run_disabled: false json_thousands_separator: ',' - junit_files: "**/build/test-results/hapiTestNDReconnect/TEST-*.xml" + junit_files: "**/test-clients/build/test-results/test/TEST-*.xml" - name: Upload HAPI Test (Node Death Reconnect) Report Artifacts uses: actions/upload-artifact@5d5d22a31266ced268874388b861e4b58bb5c2f3 # v4.3.1 if: ${{ inputs.enable-hapi-tests-nd-reconnect && steps.gradle-build.conclusion == 'failure' && !cancelled() }} with: name: HAPI Test (Node Death Reconnect) Report - path: "**/build/test-results/hapiTestNDReconnect/TEST-*.xml" + path: "**/test-clients/build/test-results/test/TEST-*.xml" retention-days: 7 - name: Upload HAPI Test (Node Death Reconnect) Network Logs @@ -557,12 +560,16 @@ jobs: - name: Publish To Codecov if: ${{ inputs.enable-unit-tests && !cancelled() }} uses: codecov/codecov-action@54bcd8715eee62d40e33596ef5e8f0f48dbbccab # v4.1.0 + env: + CODECOV_TOKEN: ${{ secrets.codecov-token }} + with: + files: gradle/reports/build/reports/jacoco/testCodeCoverageReport/testCodeCoverageReport.xml - name: Publish to Codacy env: CODACY_PROJECT_TOKEN: ${{ secrets.codacy-project-token }} if: ${{ inputs.enable-unit-tests && !cancelled() }} - run: bash <(curl -Ls https://coverage.codacy.com/get.sh) report -l Java $(find . -name 'jacoco*.xml' -printf '-r %p ') + run: bash <(curl -Ls https://coverage.codacy.com/get.sh) report -l Java -r gradle/reports/build/reports/jacoco/testCodeCoverageReport/testCodeCoverageReport.xml - name: Upload Test Reports uses: actions/upload-artifact@5d5d22a31266ced268874388b861e4b58bb5c2f3 # v4.3.1 diff --git a/block-node/blocknode-core-spi/build.gradle.kts b/block-node/blocknode-core-spi/build.gradle.kts index 5a79033d1c36..217616d40490 100644 --- a/block-node/blocknode-core-spi/build.gradle.kts +++ b/block-node/blocknode-core-spi/build.gradle.kts @@ -14,4 +14,7 @@ * limitations under the License. */ -plugins { id("com.hedera.hashgraph.blocknode.conventions") } +plugins { + id("com.hedera.gradle.blocknode") + id("com.hedera.gradle.blocknode-publish") +} diff --git a/block-node/blocknode-core/build.gradle.kts b/block-node/blocknode-core/build.gradle.kts index 5a79033d1c36..217616d40490 100644 --- a/block-node/blocknode-core/build.gradle.kts +++ b/block-node/blocknode-core/build.gradle.kts @@ -14,4 +14,7 @@ * limitations under the License. */ -plugins { id("com.hedera.hashgraph.blocknode.conventions") } +plugins { + id("com.hedera.gradle.blocknode") + id("com.hedera.gradle.blocknode-publish") +} diff --git a/block-node/blocknode-filesystem-api/build.gradle.kts b/block-node/blocknode-filesystem-api/build.gradle.kts index 5a79033d1c36..217616d40490 100644 --- a/block-node/blocknode-filesystem-api/build.gradle.kts +++ b/block-node/blocknode-filesystem-api/build.gradle.kts @@ -14,4 +14,7 @@ * limitations under the License. */ -plugins { id("com.hedera.hashgraph.blocknode.conventions") } +plugins { + id("com.hedera.gradle.blocknode") + id("com.hedera.gradle.blocknode-publish") +} diff --git a/block-node/blocknode-filesystem-local/build.gradle.kts b/block-node/blocknode-filesystem-local/build.gradle.kts index 5a79033d1c36..217616d40490 100644 --- a/block-node/blocknode-filesystem-local/build.gradle.kts +++ b/block-node/blocknode-filesystem-local/build.gradle.kts @@ -14,4 +14,7 @@ * limitations under the License. */ -plugins { id("com.hedera.hashgraph.blocknode.conventions") } +plugins { + id("com.hedera.gradle.blocknode") + id("com.hedera.gradle.blocknode-publish") +} diff --git a/block-node/blocknode-filesystem-s3/build.gradle.kts b/block-node/blocknode-filesystem-s3/build.gradle.kts index 5a79033d1c36..217616d40490 100644 --- a/block-node/blocknode-filesystem-s3/build.gradle.kts +++ b/block-node/blocknode-filesystem-s3/build.gradle.kts @@ -14,4 +14,7 @@ * limitations under the License. */ -plugins { id("com.hedera.hashgraph.blocknode.conventions") } +plugins { + id("com.hedera.gradle.blocknode") + id("com.hedera.gradle.blocknode-publish") +} diff --git a/block-node/blocknode-grpc-api/build.gradle.kts b/block-node/blocknode-grpc-api/build.gradle.kts index 5a79033d1c36..217616d40490 100644 --- a/block-node/blocknode-grpc-api/build.gradle.kts +++ b/block-node/blocknode-grpc-api/build.gradle.kts @@ -14,4 +14,7 @@ * limitations under the License. */ -plugins { id("com.hedera.hashgraph.blocknode.conventions") } +plugins { + id("com.hedera.gradle.blocknode") + id("com.hedera.gradle.blocknode-publish") +} diff --git a/block-node/blocknode-state/build.gradle.kts b/block-node/blocknode-state/build.gradle.kts index 5a79033d1c36..217616d40490 100644 --- a/block-node/blocknode-state/build.gradle.kts +++ b/block-node/blocknode-state/build.gradle.kts @@ -14,4 +14,7 @@ * limitations under the License. */ -plugins { id("com.hedera.hashgraph.blocknode.conventions") } +plugins { + id("com.hedera.gradle.blocknode") + id("com.hedera.gradle.blocknode-publish") +} diff --git a/build-logic/project-plugins/src/main/kotlin/Utils.kt b/build-logic/project-plugins/src/main/kotlin/Utils.kt deleted file mode 100644 index 71b7927b8293..000000000000 --- a/build-logic/project-plugins/src/main/kotlin/Utils.kt +++ /dev/null @@ -1,84 +0,0 @@ -/* - * Copyright (C) 2022-2023 Hedera Hashgraph, LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import org.gradle.api.Task -import org.gradle.api.file.Directory -import org.gradle.api.file.RegularFile -import org.gradle.api.tasks.testing.TestDescriptor -import org.gradle.api.tasks.testing.TestListener -import org.gradle.api.tasks.testing.TestResult -import java.io.OutputStream -import java.io.PrintStream -import java.text.SimpleDateFormat -import java.util.Date - - -class Utils { - companion object { - - // Find the version.txt in the root of the repository, independent of - // which build is started from where. - @JvmStatic - fun Directory.versionTxt(): RegularFile = - file("version.txt").let { if (it.asFile.exists()) it else this.dir("..").versionTxt() } - - @JvmStatic - fun generateProjectVersionReport(version: String, ostream: OutputStream) { - val writer = PrintStream(ostream, false, Charsets.UTF_8) - - ostream.use { - writer.use { - // Writer headers - writer.println("### Deployed Version Information") - writer.println() - writer.println("| Artifact Name | Version Number |") - writer.println("| --- | --- |") - // Write table rows - writer.printf("| %s | %s |\n", "hedera-node", version) - writer.printf("| %s | %s |\n", "platform-sdk", version) - writer.flush() - ostream.flush() - } - } - } - - @JvmStatic - fun Task.testLogger() = object : TestListener { - override fun beforeSuite(suite: TestDescriptor) { - logger.lifecycle( - "=====> Starting Suite: " + suite.displayName + " <=====" - ) - } - - override fun beforeTest(testDescriptor: TestDescriptor) {} - - override fun afterTest( - testDescriptor: TestDescriptor, - result: TestResult - ) { - logger.lifecycle( - SimpleDateFormat.getDateTimeInstance().format(Date()) + - ": " + - testDescriptor.displayName + - " " + - result.resultType.name - ) - } - - override fun afterSuite(suite: TestDescriptor, result: TestResult) {} - } - } -} diff --git a/build-logic/project-plugins/src/main/kotlin/com.hedera.hashgraph.evm-maven-publish.gradle.kts b/build-logic/project-plugins/src/main/kotlin/com.hedera.hashgraph.evm-maven-publish.gradle.kts deleted file mode 100644 index 81307718223c..000000000000 --- a/build-logic/project-plugins/src/main/kotlin/com.hedera.hashgraph.evm-maven-publish.gradle.kts +++ /dev/null @@ -1,87 +0,0 @@ -/* - * Copyright (C) 2023-2024 Hedera Hashgraph, LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -plugins { - id("java") - id("com.hedera.hashgraph.maven-publish") -} - -publishing { - publications { - named("maven") { - pom.developers { - developer { - name.set("Hedera Base Team") - email.set("hedera-base@swirldslabs.com") - organization.set("Hedera Hashgraph") - organizationUrl.set("https://www.hedera.com") - } - developer { - name.set("Hedera Services Team") - email.set("hedera-services@swirldslabs.com") - organization.set("Hedera Hashgraph") - organizationUrl.set("https://www.hedera.com") - } - developer { - name.set("Hedera Smart Contracts Team") - email.set("hedera-smart-contracts@swirldslabs.com") - organization.set("Hedera Hashgraph") - organizationUrl.set("https://www.hedera.com") - } - developer { - name.set("Release Engineering Team") - email.set("release-engineering@swirldslabs.com") - organization.set("Hedera Hashgraph") - organizationUrl.set("https://www.hedera.com") - } - } - - repositories { - maven { - name = "sonatype" - url = uri("https://oss.sonatype.org/service/local/staging/deploy/maven2/") - credentials { - username = System.getenv("OSSRH_USERNAME") - password = System.getenv("OSSRH_PASSWORD") - } - } - maven { - name = "sonatypeSnapshot" - url = uri("https://oss.sonatype.org/content/repositories/snapshots/") - credentials { - username = System.getenv("OSSRH_USERNAME") - password = System.getenv("OSSRH_PASSWORD") - } - } - } - } - } -} - -tasks.register("releaseEvmMavenCentral") { - group = "release" - dependsOn(tasks.named("publishMavenPublicationToSonatypeRepository")) -} - -tasks.register("releaseEvmMavenCentralSnapshot") { - group = "release" - dependsOn( - project(":swirlds-common") - .tasks - .named("publishMavenPublicationToSonatypeSnapshotRepository") - ) - dependsOn(tasks.named("publishMavenPublicationToSonatypeSnapshotRepository")) -} diff --git a/build-logic/project-plugins/src/main/kotlin/com.hedera.hashgraph.platform-maven-publish.gradle.kts b/build-logic/project-plugins/src/main/kotlin/com.hedera.hashgraph.platform-maven-publish.gradle.kts deleted file mode 100644 index 5daa03a1a697..000000000000 --- a/build-logic/project-plugins/src/main/kotlin/com.hedera.hashgraph.platform-maven-publish.gradle.kts +++ /dev/null @@ -1,160 +0,0 @@ -/* - * Copyright (C) 2023-2024 Hedera Hashgraph, LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import org.gradle.kotlin.dsl.support.serviceOf - -plugins { - id("java") - id("com.hedera.hashgraph.maven-publish") -} - -@Suppress("UnstableApiUsage") -if (!serviceOf().configurationCache.active.get()) { - // plugin to support 'artifactregistry' repositories that currently only works without - // configuration cache - // https://github.com/GoogleCloudPlatform/artifact-registry-maven-tools/issues/85 - apply(plugin = "com.google.cloud.artifactregistry.gradle-plugin") -} - -publishing { - publications { - named("maven") { - pom.description.set( - "Swirlds is a software platform designed to build fully-distributed " + - "applications that harness the power of the cloud without servers. " + - "Now you can develop applications with fairness in decision making, " + - "speed, trust and reliability, at a fraction of the cost of " + - "traditional server-based platforms." - ) - - pom.developers { - developer { - name.set("Platform Base Team") - email.set("platform-base@swirldslabs.com") - organization.set("Hedera Hashgraph") - organizationUrl.set("https://www.hedera.com") - } - developer { - name.set("Platform Hashgraph Team") - email.set("platform-hashgraph@swirldslabs.com") - organization.set("Hedera Hashgraph") - organizationUrl.set("https://www.hedera.com") - } - developer { - name.set("Platform Data Team") - email.set("platform-data@swirldslabs.com") - organization.set("Hedera Hashgraph") - organizationUrl.set("https://www.hedera.com") - } - developer { - name.set("Release Engineering Team") - email.set("release-engineering@swirldslabs.com") - organization.set("Hedera Hashgraph") - organizationUrl.set("https://www.hedera.com") - } - } - - repositories { - maven { - name = "prereleaseChannel" - url = - uri( - "artifactregistry://us-maven.pkg.dev/swirlds-registry/maven-prerelease-channel" - ) - } - maven { - name = "developSnapshot" - url = - uri( - "artifactregistry://us-maven.pkg.dev/swirlds-registry/maven-develop-snapshots" - ) - } - maven { - name = "developDailySnapshot" - url = - uri( - "artifactregistry://us-maven.pkg.dev/swirlds-registry/maven-develop-daily-snapshots" - ) - } - maven { - name = "developCommit" - url = - uri( - "artifactregistry://us-maven.pkg.dev/swirlds-registry/maven-develop-commits" - ) - } - maven { - name = "adhocCommit" - url = - uri( - "artifactregistry://us-maven.pkg.dev/swirlds-registry/maven-adhoc-commits" - ) - } - maven { - name = "sonatype" - url = uri("https://s01.oss.sonatype.org/service/local/staging/deploy/maven2/") - credentials { - username = System.getenv("OSSRH_USERNAME") - password = System.getenv("OSSRH_PASSWORD") - } - } - maven { - name = "sonatypeSnapshot" - url = uri("https://s01.oss.sonatype.org/content/repositories/snapshots/") - credentials { - username = System.getenv("OSSRH_USERNAME") - password = System.getenv("OSSRH_PASSWORD") - } - } - } - } - } -} - -tasks.register("releaseMavenCentral") { - group = "release" - dependsOn(tasks.named("publishMavenPublicationToSonatypeRepository")) -} - -tasks.register("releaseMavenCentralSnapshot") { - group = "release" - dependsOn(tasks.named("publishMavenPublicationToSonatypeSnapshotRepository")) -} - -tasks.register("releaseDevelopSnapshot") { - group = "release" - dependsOn(tasks.named("publishMavenPublicationToDevelopSnapshotRepository")) -} - -tasks.register("releaseDevelopDailySnapshot") { - group = "release" - dependsOn(tasks.named("publishMavenPublicationToDevelopDailySnapshotRepository")) -} - -tasks.register("releaseDevelopCommit") { - group = "release" - dependsOn(tasks.named("publishMavenPublicationToDevelopCommitRepository")) -} - -tasks.register("releaseAdhocCommit") { - group = "release" - dependsOn(tasks.named("publishMavenPublicationToAdhocCommitRepository")) -} - -tasks.register("releasePrereleaseChannel") { - group = "release" - dependsOn(tasks.named("publishMavenPublicationToPrereleaseChannelRepository")) -} diff --git a/build-logic/project-plugins/src/main/kotlin/com/hedera/hashgraph/gradlebuild/services/TaskLockService.kt b/build-logic/project-plugins/src/main/kotlin/com/hedera/hashgraph/gradlebuild/services/TaskLockService.kt deleted file mode 100644 index 02a673bb75ca..000000000000 --- a/build-logic/project-plugins/src/main/kotlin/com/hedera/hashgraph/gradlebuild/services/TaskLockService.kt +++ /dev/null @@ -1,6 +0,0 @@ -package com.hedera.hashgraph.gradlebuild.services - -import org.gradle.api.services.BuildService -import org.gradle.api.services.BuildServiceParameters - -abstract class TaskLockService : BuildService \ No newline at end of file diff --git a/build-logic/project-plugins/src/main/kotlin/com/hedera/hashgraph/gradlebuild/tasks/GitClone.kt b/build-logic/project-plugins/src/main/kotlin/com/hedera/hashgraph/gradlebuild/tasks/GitClone.kt deleted file mode 100644 index c0c857eea6de..000000000000 --- a/build-logic/project-plugins/src/main/kotlin/com/hedera/hashgraph/gradlebuild/tasks/GitClone.kt +++ /dev/null @@ -1,57 +0,0 @@ -package com.hedera.hashgraph.gradlebuild.tasks - -import org.gradle.api.DefaultTask -import org.gradle.api.file.DirectoryProperty -import org.gradle.api.provider.Property -import org.gradle.api.tasks.Input -import org.gradle.api.tasks.OutputDirectory -import org.gradle.api.tasks.TaskAction -import org.gradle.process.ExecOperations -import javax.inject.Inject - -abstract class GitClone : DefaultTask() { - - @get:Input - abstract val url: Property - - @get:Input - abstract val branchOrTag: Property - - @get:Input - abstract val offline: Property - - @get:OutputDirectory - abstract val localCloneDirectory: DirectoryProperty - - @get:Inject - protected abstract val exec: ExecOperations - - @TaskAction - fun cloneOrUpdate() { - val localClone = localCloneDirectory.get() - if (!offline.get()) { - exec.exec { - if (!localClone.dir(".git").asFile.exists()) { - workingDir = localClone.asFile.parentFile - commandLine( - "git", - "clone", - "https://github.com/hashgraph/hedera-protobufs.git", - "-q" - ) - } else { - workingDir = localClone.asFile - commandLine("git", "fetch", "-q") - } - } - } - exec.exec { - workingDir = localClone.asFile - commandLine("git", "checkout", branchOrTag.get(), "-q") - } - exec.exec { - workingDir = localClone.asFile - commandLine("git", "reset", "--hard", "origin/${branchOrTag.get()}", "-q") - } - } -} \ No newline at end of file diff --git a/build-logic/settings.gradle.kts b/build-logic/settings.gradle.kts deleted file mode 100644 index f9f0c9a072ae..000000000000 --- a/build-logic/settings.gradle.kts +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright (C) 2022-2024 Hedera Hashgraph, LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -dependencyResolutionManagement { repositories { gradlePluginPortal() } } - -// There are two projects for convention plugins to have different dependencies to external plugins -// for project and settings plugins. Otherwise, 'com.diffplug.spotless' and 'me.champeau.includegit' -// clash because they transitively depend on different JGit versions. - -include("project-plugins") - -include("settings-plugins") diff --git a/build.gradle.kts b/build.gradle.kts index 2b8f9792dce4..9c998aff3795 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -14,4 +14,4 @@ * limitations under the License. */ -plugins { id("com.hedera.hashgraph.root") } +plugins { id("com.hedera.gradle.root") } diff --git a/example-apps/swirlds-platform-base-example/build.gradle.kts b/example-apps/swirlds-platform-base-example/build.gradle.kts index bb8a7a724e51..7f3e5f02a809 100644 --- a/example-apps/swirlds-platform-base-example/build.gradle.kts +++ b/example-apps/swirlds-platform-base-example/build.gradle.kts @@ -16,7 +16,7 @@ plugins { id("application") - id("com.hedera.hashgraph.sdk.conventions") + id("com.hedera.gradle.platform") } mainModuleInfo { diff --git a/build-logic/project-plugins/build.gradle.kts b/gradle/plugins/build.gradle.kts similarity index 79% rename from build-logic/project-plugins/build.gradle.kts rename to gradle/plugins/build.gradle.kts index 3864f1d4be7b..13d7b5790041 100644 --- a/build-logic/project-plugins/build.gradle.kts +++ b/gradle/plugins/build.gradle.kts @@ -20,19 +20,23 @@ plugins { `kotlin-dsl` } +repositories { gradlePluginPortal() } + dependencies { implementation("com.adarshr:gradle-test-logger-plugin:4.0.0") - implementation("com.autonomousapps:dependency-analysis-gradle-plugin:1.29.0") + implementation("com.autonomousapps:dependency-analysis-gradle-plugin:1.31.0") implementation("com.diffplug.spotless:spotless-plugin-gradle:6.25.0") implementation("com.github.johnrengelman:shadow:8.1.1") implementation("com.google.protobuf:protobuf-gradle-plugin:0.9.4") + implementation("com.gradle:develocity-gradle-plugin:3.17.2") implementation( "gradle.plugin.com.google.cloud.artifactregistry:artifactregistry-gradle-plugin:2.2.1" ) + implementation("io.github.gradle-nexus:publish-plugin:1.3.0") implementation("me.champeau.jmh:jmh-gradle-plugin:0.7.2") implementation("net.swiftzer.semver:semver:1.3.0") implementation("org.gradlex:extra-java-module-info:1.8") - implementation("org.gradlex:java-ecosystem-capabilities:1.5.1") - implementation("org.gradlex:java-module-dependencies:1.6.2") - implementation("org.owasp:dependency-check-gradle:9.0.9") + implementation("org.gradlex:jvm-dependency-conflict-resolution:2.0") + implementation("org.gradlex:java-module-dependencies:1.6.5") + implementation("org.owasp:dependency-check-gradle:9.1.0") } diff --git a/build-logic/project-plugins/src/main/kotlin/com.hedera.hashgraph.aggregate-reports.gradle.kts b/gradle/plugins/src/main/kotlin/com.hedera.gradle.aggregate-reports.gradle.kts similarity index 100% rename from build-logic/project-plugins/src/main/kotlin/com.hedera.hashgraph.aggregate-reports.gradle.kts rename to gradle/plugins/src/main/kotlin/com.hedera.gradle.aggregate-reports.gradle.kts diff --git a/build-logic/project-plugins/src/main/kotlin/com.hedera.hashgraph.application.gradle.kts b/gradle/plugins/src/main/kotlin/com.hedera.gradle.application.gradle.kts similarity index 98% rename from build-logic/project-plugins/src/main/kotlin/com.hedera.hashgraph.application.gradle.kts rename to gradle/plugins/src/main/kotlin/com.hedera.gradle.application.gradle.kts index 661a560677eb..29820edd87ab 100644 --- a/build-logic/project-plugins/src/main/kotlin/com.hedera.hashgraph.application.gradle.kts +++ b/gradle/plugins/src/main/kotlin/com.hedera.gradle.application.gradle.kts @@ -16,7 +16,7 @@ plugins { id("application") - id("com.hedera.hashgraph.java") + id("com.hedera.gradle.java") } group = "com.swirlds" diff --git a/build-logic/project-plugins/src/main/kotlin/com.hedera.hashgraph.benchmark-conventions.gradle.kts b/gradle/plugins/src/main/kotlin/com.hedera.gradle.benchmark.gradle.kts similarity index 100% rename from build-logic/project-plugins/src/main/kotlin/com.hedera.hashgraph.benchmark-conventions.gradle.kts rename to gradle/plugins/src/main/kotlin/com.hedera.gradle.benchmark.gradle.kts diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/signed/PcesFileRenamed.java b/gradle/plugins/src/main/kotlin/com.hedera.gradle.blocknode-publish.gradle.kts similarity index 56% rename from platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/signed/PcesFileRenamed.java rename to gradle/plugins/src/main/kotlin/com.hedera.gradle.blocknode-publish.gradle.kts index e3a6e9df1c4d..365c87032c21 100644 --- a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/signed/PcesFileRenamed.java +++ b/gradle/plugins/src/main/kotlin/com.hedera.gradle.blocknode-publish.gradle.kts @@ -14,21 +14,22 @@ * limitations under the License. */ -package com.swirlds.platform.state.signed; - -import edu.umd.cs.findbugs.annotations.NonNull; - -/** - * This exception is thrown when a preconsensus event file is renamed, which prevents a preconsensus event file from - * being copied. - */ -public class PcesFileRenamed extends RuntimeException { +plugins { + id("java") + id("com.hedera.gradle.maven-publish") +} - /** - * Constructor. - * @param cause the cause - */ - public PcesFileRenamed(@NonNull final Throwable cause) { - super(cause); +publishing { + publications { + named("maven") { + pom.developers { + developer { + name = "Release Engineering Team" + email = "release-engineering@swirldslabs.com" + organization = "Hedera Hashgraph" + organizationUrl = "https://www.hedera.com" + } + } + } } } diff --git a/build-logic/project-plugins/src/main/kotlin/com.hedera.hashgraph.blocknode.conventions.gradle.kts b/gradle/plugins/src/main/kotlin/com.hedera.gradle.blocknode.gradle.kts similarity index 95% rename from build-logic/project-plugins/src/main/kotlin/com.hedera.hashgraph.blocknode.conventions.gradle.kts rename to gradle/plugins/src/main/kotlin/com.hedera.gradle.blocknode.gradle.kts index 1b8ff644778c..97d2db967e4d 100644 --- a/build-logic/project-plugins/src/main/kotlin/com.hedera.hashgraph.blocknode.conventions.gradle.kts +++ b/gradle/plugins/src/main/kotlin/com.hedera.gradle.blocknode.gradle.kts @@ -16,7 +16,7 @@ plugins { id("java-library") - id("com.hedera.hashgraph.java") + id("com.hedera.gradle.java") } group = "com.hedera.storage" diff --git a/gradle/plugins/src/main/kotlin/com.hedera.gradle.evm-publish.gradle.kts b/gradle/plugins/src/main/kotlin/com.hedera.gradle.evm-publish.gradle.kts new file mode 100644 index 000000000000..43890a0282c5 --- /dev/null +++ b/gradle/plugins/src/main/kotlin/com.hedera.gradle.evm-publish.gradle.kts @@ -0,0 +1,54 @@ +/* + * Copyright (C) 2024 Hedera Hashgraph, LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +plugins { + id("java") + id("com.hedera.gradle.maven-publish") +} + +publishing { + publications { + named("maven") { + artifactId = "hedera-evm" + pom.developers { + developer { + name = "Hedera Base Team" + email = "hedera-base@swirldslabs.com" + organization = "Hedera Hashgraph" + organizationUrl = "https://www.hedera.com" + } + developer { + name = "Hedera Services Team" + email = "hedera-services@swirldslabs.com" + organization = "Hedera Hashgraph" + organizationUrl = "https://www.hedera.com" + } + developer { + name = "Hedera Smart Contracts Team" + email = "hedera-smart-contracts@swirldslabs.com" + organization = "Hedera Hashgraph" + organizationUrl = "https://www.hedera.com" + } + developer { + name = "Release Engineering Team" + email = "release-engineering@swirldslabs.com" + organization = "Hedera Hashgraph" + organizationUrl = "https://www.hedera.com" + } + } + } + } +} diff --git a/build-logic/project-plugins/src/main/kotlin/com.hedera.hashgraph.dependency-analysis.gradle.kts b/gradle/plugins/src/main/kotlin/com.hedera.gradle.evm.gradle.kts similarity index 79% rename from build-logic/project-plugins/src/main/kotlin/com.hedera.hashgraph.dependency-analysis.gradle.kts rename to gradle/plugins/src/main/kotlin/com.hedera.gradle.evm.gradle.kts index e8e5e87d0993..d9bf18df24b4 100644 --- a/build-logic/project-plugins/src/main/kotlin/com.hedera.hashgraph.dependency-analysis.gradle.kts +++ b/gradle/plugins/src/main/kotlin/com.hedera.gradle.evm.gradle.kts @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023-2024 Hedera Hashgraph, LLC + * Copyright (C) 2022-2024 Hedera Hashgraph, LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,4 +14,9 @@ * limitations under the License. */ -plugins { id("com.autonomousapps.dependency-analysis") } +plugins { + id("java-library") + id("com.hedera.gradle.java") +} + +group = "com.hedera.evm" diff --git a/build-logic/project-plugins/src/main/kotlin/com.hedera.hashgraph.java-test-fixtures.gradle.kts b/gradle/plugins/src/main/kotlin/com.hedera.gradle.java-test-fixtures.gradle.kts similarity index 100% rename from build-logic/project-plugins/src/main/kotlin/com.hedera.hashgraph.java-test-fixtures.gradle.kts rename to gradle/plugins/src/main/kotlin/com.hedera.gradle.java-test-fixtures.gradle.kts diff --git a/build-logic/project-plugins/src/main/kotlin/com.hedera.hashgraph.java.gradle.kts b/gradle/plugins/src/main/kotlin/com.hedera.gradle.java.gradle.kts similarity index 84% rename from build-logic/project-plugins/src/main/kotlin/com.hedera.hashgraph.java.gradle.kts rename to gradle/plugins/src/main/kotlin/com.hedera.gradle.java.gradle.kts index 46fc6d6623f2..3214bcda5e96 100644 --- a/build-logic/project-plugins/src/main/kotlin/com.hedera.hashgraph.java.gradle.kts +++ b/gradle/plugins/src/main/kotlin/com.hedera.gradle.java.gradle.kts @@ -14,57 +14,53 @@ * limitations under the License. */ -import Utils.Companion.testLogger -import Utils.Companion.versionTxt import com.adarshr.gradle.testlogger.theme.ThemeType import com.autonomousapps.AbstractExtension import com.autonomousapps.DependencyAnalysisSubExtension -import com.hedera.hashgraph.gradlebuild.services.TaskLockService +import com.hedera.gradle.services.TaskLockService +import com.hedera.gradle.utils.Utils.versionTxt plugins { id("java") id("jacoco") id("checkstyle") id("com.adarshr.test-logger") - id("com.hedera.hashgraph.lifecycle") - id("com.hedera.hashgraph.jpms-modules") - id("com.hedera.hashgraph.jpms-module-dependencies") - id("com.hedera.hashgraph.repositories") - id("com.hedera.hashgraph.spotless-conventions") - id("com.hedera.hashgraph.spotless-java-conventions") - id("com.hedera.hashgraph.spotless-kotlin-conventions") + id("com.hedera.gradle.lifecycle") + id("com.hedera.gradle.jpms-modules") + id("com.hedera.gradle.jpms-module-dependencies") + id("com.hedera.gradle.repositories") + id("com.hedera.gradle.spotless-java") + id("com.hedera.gradle.spotless-kotlin") } version = providers.fileContents(rootProject.layout.projectDirectory.versionTxt()).asText.get().trim() -// Fail the build if Gradle is started with an old version of Java -val MIN_JAVA_VERSION = JavaVersion.VERSION_21 -val CUR_JAVA_VERSION = JavaVersion.current() - -if (CUR_JAVA_VERSION.ordinal < MIN_JAVA_VERSION.ordinal) { - throw StopExecutionException( - "ERROR: Gradle is started with Java " + - CUR_JAVA_VERSION + - ". This project requires running Gradle with Java " + - MIN_JAVA_VERSION + - " or above." + - " Please check your JAVA_HOME and/or PATH and configure the default JDK to use Java version " + - MIN_JAVA_VERSION + - " or above." - ) -} +val javaVersionMajor = JavaVersion.VERSION_21 +val javaVersionPatch = "0.1" -java { - sourceCompatibility = MIN_JAVA_VERSION - targetCompatibility = MIN_JAVA_VERSION +val currentJavaVersionMajor = JavaVersion.current() +val currentJavaVersion = providers.systemProperty("java.version").get() +val expectedJavaVersion = "$javaVersionMajor.$javaVersionPatch" + +if (currentJavaVersion != expectedJavaVersion) { + val message = + "Gradle runs with Java $currentJavaVersion. This project works best running with Java $expectedJavaVersion. " + + "\n - From commandline: change JAVA_HOME and/or PATH to point at Java $expectedJavaVersion installation." + + "\n - From IntelliJ: change 'Gradle JVM' in 'Gradle Settings' to point at Java $expectedJavaVersion installation." - toolchain { - languageVersion = JavaLanguageVersion.of(21) - vendor = JvmVendorSpec.ADOPTIUM + if (currentJavaVersionMajor.ordinal < javaVersionMajor.ordinal) { // fail if version is too old + throw (RuntimeException(message)) + } else { + logger.lifecycle("WARN: $message") } } +java { + sourceCompatibility = javaVersionMajor + targetCompatibility = javaVersionMajor +} + configurations.all { // In case published versions of a module are also available, always prefer the local one resolutionStrategy.preferProjectModules() @@ -88,7 +84,11 @@ sourceSets.all { // Remove 'classes' tasks from 'build' group to keep it cleaned up tasks.named(classesTaskName) { group = null } - configurations.getByName(compileClasspathConfigurationName) { extendsFrom(internal.get()) } + configurations.getByName(compileClasspathConfigurationName) { + extendsFrom(internal.get()) + @Suppress("UnstableApiUsage") + shouldResolveConsistentlyWith(configurations.getByName(runtimeClasspathConfigurationName)) + } configurations.getByName(runtimeClasspathConfigurationName) { extendsFrom(internal.get()) } dependencies { @@ -130,11 +130,14 @@ val writeGitProperties = tasks.processResources { from(writeGitProperties) } +// ignore the content of 'git.properties' when using a classpath as task input +normalization.runtimeClasspath { ignore("git.properties") } + tasks.withType().configureEach { isPreserveFileTimestamps = false isReproducibleFileOrder = true - fileMode = 436 // octal: 0664 - dirMode = 509 // octal: 0775 + filePermissions { unix("0664") } + dirPermissions { unix("0775") } } tasks.jar { exclude("**/classpath.index") } @@ -144,9 +147,10 @@ tasks.withType().configureEach { // By default, Gradle only tracks the major version as defined in the toolchain (e.g. 17). // Since the full version is encoded in 'module-info.class' files, it should be tracked as // it otherwise leads to wrong build cache hits. - inputs.property("fullJavaVersion", providers.systemProperty("java.version")) + inputs.property("fullJavaVersion", currentJavaVersion) options.encoding = "UTF-8" + options.isFork = true // run compiler in separate JVM process (independent of toolchain setup) doLast { // Make sure consistent line ending are used in files generated by annotation processors by @@ -234,7 +238,6 @@ testing { group = "build" shouldRunAfter(tasks.test) maxHeapSize = "8g" - addTestListener(testLogger()) } } } diff --git a/build-logic/project-plugins/src/main/kotlin/com.hedera.hashgraph.jpms-module-dependencies.gradle.kts b/gradle/plugins/src/main/kotlin/com.hedera.gradle.jpms-module-dependencies.gradle.kts similarity index 89% rename from build-logic/project-plugins/src/main/kotlin/com.hedera.hashgraph.jpms-module-dependencies.gradle.kts rename to gradle/plugins/src/main/kotlin/com.hedera.gradle.jpms-module-dependencies.gradle.kts index 820d2f956976..776f05ffa328 100644 --- a/build-logic/project-plugins/src/main/kotlin/com.hedera.hashgraph.jpms-module-dependencies.gradle.kts +++ b/gradle/plugins/src/main/kotlin/com.hedera.gradle.jpms-module-dependencies.gradle.kts @@ -26,4 +26,6 @@ javaModuleDependencies { moduleNamePrefixToGroup.put("com.", "com.swirlds") moduleNamePrefixToGroup.put("com.hedera.node.", "com.hedera.hashgraph") moduleNamePrefixToGroup.put("com.hedera.storage.", "com.hedera.storage.blocknode") + moduleNameToGA.put("com.hedera.evm", "com.hedera.evm:hedera-evm") + moduleNameToGA.put("com.hedera.evm.impl", "com.hedera:hedera-evm-impl") } diff --git a/build-logic/project-plugins/src/main/kotlin/com.hedera.hashgraph.jpms-modules.gradle.kts b/gradle/plugins/src/main/kotlin/com.hedera.gradle.jpms-modules.gradle.kts similarity index 71% rename from build-logic/project-plugins/src/main/kotlin/com.hedera.hashgraph.jpms-modules.gradle.kts rename to gradle/plugins/src/main/kotlin/com.hedera.gradle.jpms-modules.gradle.kts index f4f52720cf88..ca9f3b18e37d 100644 --- a/build-logic/project-plugins/src/main/kotlin/com.hedera.hashgraph.jpms-modules.gradle.kts +++ b/gradle/plugins/src/main/kotlin/com.hedera.gradle.jpms-modules.gradle.kts @@ -14,26 +14,18 @@ * limitations under the License. */ -import org.gradlex.javaecosystem.capabilities.customrules.AddDependenciesMetadataRule -import org.gradlex.javaecosystem.capabilities.customrules.AddFeaturesMetadataRule -import org.gradlex.javaecosystem.capabilities.customrules.RemoveDependenciesMetadataRule - plugins { - id("org.gradlex.java-ecosystem-capabilities") + id("org.gradlex.jvm-dependency-conflict-resolution") id("org.gradlex.extra-java-module-info") } // Fix or enhance the metadata of third-party Modules. This is about the metadata in the // repositories: '*.pom' and '*.module' files. -dependencies.components { - withModule("io.netty:netty-transport-native-epoll") { - params(listOf("linux-x86_64", "linux-aarch_64")) - } - +jvmDependencyConflicts.patch { + val grpcModule = "io.helidon.grpc:io.grpc" // The following 'io.grpc' libraries are replaced with a singe dependency to // 'io.helidon.grpc:io.grpc', which is a re-packaged Modular Jar of all the 'grpc' libraries. val grpcComponents = listOf("io.grpc:grpc-api", "io.grpc:grpc-context", "io.grpc:grpc-core") - val grpcModule = listOf("io.helidon.grpc:io.grpc") // These compile time annotation libraries are not of interest in our setup and are thus removed // from the dependencies of all components that bring them in. @@ -50,86 +42,77 @@ dependencies.components { "org.codehaus.mojo:animal-sniffer-annotations" ) - withModule("io.grpc:grpc-netty") { - params(grpcComponents + annotationLibraries) - } - withModule("io.grpc:grpc-netty") { params(grpcModule) } - withModule("io.grpc:grpc-protobuf") { - params(grpcComponents + annotationLibraries) - } - withModule("io.grpc:grpc-protobuf") { params(grpcModule) } - withModule("io.grpc:grpc-protobuf-lite") { - params( - grpcComponents + annotationLibraries + listOf("com.google.protobuf:protobuf-javalite") - ) + module("io.netty:netty-transport-native-epoll") { + addFeature("linux-x86_64") + addFeature("linux-aarch_64") } - withModule("io.grpc:grpc-protobuf-lite") { - params(grpcModule + listOf("com.google.protobuf:protobuf-java")) + module("io.grpc:grpc-netty") { + annotationLibraries.forEach { removeDependency(it) } + grpcComponents.forEach { removeDependency(it) } + addApiDependency(grpcModule) } - withModule("io.grpc:grpc-services") { - params(grpcComponents + annotationLibraries) + module("io.grpc:grpc-protobuf") { + annotationLibraries.forEach { removeDependency(it) } + grpcComponents.forEach { removeDependency(it) } + addApiDependency(grpcModule) } - withModule("io.grpc:grpc-services") { params(grpcModule) } - withModule("io.grpc:grpc-stub") { - params(grpcComponents + annotationLibraries) + module("io.grpc:grpc-protobuf-lite") { + annotationLibraries.forEach { removeDependency(it) } + grpcComponents.forEach { removeDependency(it) } + addApiDependency(grpcModule) + removeDependency("com.google.protobuf:protobuf-javalite") + addApiDependency("com.google.protobuf:protobuf-java") } - withModule("io.grpc:grpc-stub") { params(grpcModule) } - withModule("io.grpc:grpc-testing") { - params(grpcComponents + annotationLibraries) + module("io.grpc:grpc-services") { + annotationLibraries.forEach { removeDependency(it) } + grpcComponents.forEach { removeDependency(it) } + addApiDependency(grpcModule) } - withModule("io.grpc:grpc-testing") { params(grpcModule) } - - withModule("com.github.ben-manes.caffeine:caffeine") { - params(annotationLibraries) - } - withModule("com.github.spotbugs:spotbugs-annotations") { - params(annotationLibraries) - } - withModule("com.google.dagger:dagger-compiler") { - params(annotationLibraries) + module("io.grpc:grpc-stub") { + annotationLibraries.forEach { removeDependency(it) } + grpcComponents.forEach { removeDependency(it) } + addApiDependency(grpcModule) } - withModule("com.google.dagger:dagger-producers") { - params(annotationLibraries) + module("io.grpc:grpc-testing") { + annotationLibraries.forEach { removeDependency(it) } + grpcComponents.forEach { removeDependency(it) } + addApiDependency(grpcModule) } - withModule("com.google.dagger:dagger-spi") { - params(annotationLibraries) + module("com.github.ben-manes.caffeine:caffeine") { + annotationLibraries.forEach { removeDependency(it) } } - withModule("com.google.guava:guava") { - params(annotationLibraries) + module("com.github.spotbugs:spotbugs-annotations") { + removeDependency("com.google.code.findbugs:jsr305") } - withModule("com.google.protobuf:protobuf-java-util") { - params(annotationLibraries) + module("com.google.dagger:dagger-compiler") { + annotationLibraries.forEach { removeDependency(it) } } - withModule("io.helidon.grpc:io.grpc") { - params(annotationLibraries) + module("com.google.dagger:dagger-producers") { + annotationLibraries.forEach { removeDependency(it) } } - withModule("org.apache.tuweni:tuweni-bytes") { - params(annotationLibraries) - } - withModule("org.apache.tuweni:tuweni-units") { - params(annotationLibraries) - } - withModule("io.prometheus:simpleclient") { - params( - listOf( - "io.prometheus:simpleclient_tracer_otel", - "io.prometheus:simpleclient_tracer_otel_agent" - ) - ) + module("com.google.dagger:dagger-spi") { annotationLibraries.forEach { removeDependency(it) } } + module("com.google.guava:guava") { annotationLibraries.forEach { removeDependency(it) } } + module("com.google.protobuf:protobuf-java-util") { + annotationLibraries.forEach { removeDependency(it) } } - withModule("org.jetbrains.kotlin:kotlin-stdlib") { - params(listOf("org.jetbrains.kotlin:kotlin-stdlib-common")) + module("io.helidon.grpc:io.grpc") { annotationLibraries.forEach { removeDependency(it) } } + module("org.apache.tuweni:tuweni-bytes") { removeDependency("com.google.code.findbugs:jsr305") } + module("org.apache.tuweni:tuweni-units") { removeDependency("com.google.code.findbugs:jsr305") } + module("io.prometheus:simpleclient") { + removeDependency("io.prometheus:simpleclient_tracer_otel") + removeDependency("io.prometheus:simpleclient_tracer_otel_agent") } - withModule("junit:junit") { - params(listOf("org.hamcrest:hamcrest-core")) + module("org.jetbrains.kotlin:kotlin-stdlib") { + removeDependency("org.jetbrains.kotlin:kotlin-stdlib-common") } + module("junit:junit") { removeDependency("org.hamcrest:hamcrest-core") } } // Fix or enhance the 'module-info.class' of third-party Modules. This is about the // 'module-info.class' inside the Jar files. In our full Java Modules setup every // Jar needs to have this file. If it is missing, it is added by what is configured here. extraJavaModuleInfo { - failOnAutomaticModules.set(true) // Only allow Jars with 'module-info' on all module paths + failOnAutomaticModules = true // Only allow Jars with 'module-info' on all module paths module("io.grpc:grpc-netty", "grpc.netty") { exportAllPackages() @@ -162,9 +145,7 @@ extraJavaModuleInfo { module("com.google.dagger:dagger", "dagger") module("io.perfmark:perfmark-api", "io.perfmark") module("javax.inject:javax.inject", "javax.inject") - // module("org.apache.commons:commons-lang3", "org.apache.commons.lang3") module("commons-codec:commons-codec", "org.apache.commons.codec") - // module("commons-io:commons-io", "org.apache.commons.io") module("org.apache.commons:commons-math3", "org.apache.commons.math3") module("org.apache.commons:commons-collections4", "org.apache.commons.collections4") module("com.esaulpaugh:headlong", "headlong") @@ -276,7 +257,6 @@ extraJavaModuleInfo { requires("java.compiler") } module("junit:junit", "junit") - // module("org.apache.commons:commons-compress", "org.apache.commons.compress") module("org.hamcrest:hamcrest", "org.hamcrest") module("org.json:json", "org.json") module("org.mockito:mockito-core", "org.mockito") diff --git a/build-logic/project-plugins/src/main/kotlin/com.hedera.hashgraph.lifecycle.gradle.kts b/gradle/plugins/src/main/kotlin/com.hedera.gradle.lifecycle.gradle.kts similarity index 96% rename from build-logic/project-plugins/src/main/kotlin/com.hedera.hashgraph.lifecycle.gradle.kts rename to gradle/plugins/src/main/kotlin/com.hedera.gradle.lifecycle.gradle.kts index ef8cee2c5083..c0e1aa4fd83c 100644 --- a/build-logic/project-plugins/src/main/kotlin/com.hedera.hashgraph.lifecycle.gradle.kts +++ b/gradle/plugins/src/main/kotlin/com.hedera.gradle.lifecycle.gradle.kts @@ -35,3 +35,5 @@ tasks.register("qualityGate") { dependsOn(tasks.spotlessApply) dependsOn(tasks.assemble) } + +tasks.register("releaseMavenCentral") diff --git a/build-logic/project-plugins/src/main/kotlin/com.hedera.hashgraph.maven-publish.gradle.kts b/gradle/plugins/src/main/kotlin/com.hedera.gradle.maven-publish.gradle.kts similarity index 95% rename from build-logic/project-plugins/src/main/kotlin/com.hedera.hashgraph.maven-publish.gradle.kts rename to gradle/plugins/src/main/kotlin/com.hedera.gradle.maven-publish.gradle.kts index ba45f82a7cfb..fbc9dd85100f 100644 --- a/build-logic/project-plugins/src/main/kotlin/com.hedera.hashgraph.maven-publish.gradle.kts +++ b/gradle/plugins/src/main/kotlin/com.hedera.gradle.maven-publish.gradle.kts @@ -27,6 +27,11 @@ java { tasks.withType().configureEach { setGroup(null) } +tasks.named("releaseMavenCentral") { + group = "release" + dependsOn(tasks.named("publishToSonatype")) +} + val maven = publishing.publications.create("maven") { from(components["java"]) diff --git a/gradle/plugins/src/main/kotlin/com.hedera.gradle.platform-publish.gradle.kts b/gradle/plugins/src/main/kotlin/com.hedera.gradle.platform-publish.gradle.kts new file mode 100644 index 000000000000..b1675913d022 --- /dev/null +++ b/gradle/plugins/src/main/kotlin/com.hedera.gradle.platform-publish.gradle.kts @@ -0,0 +1,95 @@ +/* + * Copyright (C) 2023-2024 Hedera Hashgraph, LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +plugins { + id("java") + id("com.hedera.gradle.maven-publish") +} + +if ( + gradle.startParameter.taskNames.any { it.startsWith("release") || it.contains("MavenCentral") } +) { + // We apply the 'artifactregistry' plugin conditionally, as there are two issues: + // 1. It does not support configuration cache. + // https://github.com/GoogleCloudPlatform/artifact-registry-maven-tools/issues/85 + // 2. It does not interact well with the 'gradle-nexus.publish-plugin' plugin, causing: + // 'No staging repository with name sonatype created' during IDE import + publishing.repositories.remove(publishing.repositories.getByName("sonatype")) + apply(plugin = "com.google.cloud.artifactregistry.gradle-plugin") +} + +publishing.publications.named("maven") { + pom.description = + "Swirlds is a software platform designed to build fully-distributed " + + "applications that harness the power of the cloud without servers. " + + "Now you can develop applications with fairness in decision making, " + + "speed, trust and reliability, at a fraction of the cost of " + + "traditional server-based platforms." + + pom.developers { + developer { + name = "Platform Base Team" + email = "platform-base@swirldslabs.com" + organization = "Hedera Hashgraph" + organizationUrl = "https://www.hedera.com" + } + developer { + name = "Platform Hashgraph Team" + email = "platform-hashgraph@swirldslabs.com" + organization = "Hedera Hashgraph" + organizationUrl = "https://www.hedera.com" + } + developer { + name = "Platform Data Team" + email = "platform-data@swirldslabs.com" + organization = "Hedera Hashgraph" + organizationUrl = "https://www.hedera.com" + } + developer { + name = "Release Engineering Team" + email = "release-engineering@swirldslabs.com" + organization = "Hedera Hashgraph" + organizationUrl = "https://www.hedera.com" + } + } +} + +publishing.repositories { + maven("artifactregistry://us-maven.pkg.dev/swirlds-registry/maven-prerelease-channel") { + name = "prereleaseChannel" + } + maven("artifactregistry://us-maven.pkg.dev/swirlds-registry/maven-develop-snapshots") { + name = "developSnapshot" + } + maven("artifactregistry://us-maven.pkg.dev/swirlds-registry/maven-develop-daily-snapshots") { + name = "developDailySnapshot" + } + maven("artifactregistry://us-maven.pkg.dev/swirlds-registry/maven-develop-commits") { + name = "developCommit" + } + maven("artifactregistry://us-maven.pkg.dev/swirlds-registry/maven-adhoc-commits") { + name = "adhocCommit" + } +} + +// Register one 'release*' task for each publishing repository +publishing.repositories.all { + val ucName = name.replaceFirstChar { it.titlecase() } + tasks.register("release$ucName") { + group = "release" + dependsOn(tasks.named("publishMavenPublicationTo${ucName}Repository")) + } +} diff --git a/build-logic/project-plugins/src/main/kotlin/com.hedera.hashgraph.sdk.conventions.gradle.kts b/gradle/plugins/src/main/kotlin/com.hedera.gradle.platform.gradle.kts similarity index 86% rename from build-logic/project-plugins/src/main/kotlin/com.hedera.hashgraph.sdk.conventions.gradle.kts rename to gradle/plugins/src/main/kotlin/com.hedera.gradle.platform.gradle.kts index 0daa91c5b955..95ad1b5a011a 100644 --- a/build-logic/project-plugins/src/main/kotlin/com.hedera.hashgraph.sdk.conventions.gradle.kts +++ b/gradle/plugins/src/main/kotlin/com.hedera.gradle.platform.gradle.kts @@ -16,7 +16,7 @@ plugins { id("java-library") - id("com.hedera.hashgraph.java") + id("com.hedera.gradle.java") } group = "com.swirlds" @@ -45,7 +45,7 @@ val timingSensitive = usesService( gradle.sharedServices.registerIfAbsent( "lock", - com.hedera.hashgraph.gradlebuild.services.TaskLockService::class + com.hedera.gradle.services.TaskLockService::class ) { maxParallelUsages = 1 } @@ -60,4 +60,12 @@ val timingSensitive = maxHeapSize = "4g" } +configurations.coverageDataElementsForTest { + outgoing.artifact( + timingSensitive.map { it.extensions.getByType().destinationFile!! } + ) { + type = "binary" + } +} + tasks.check { dependsOn(timingSensitive) } diff --git a/build-logic/project-plugins/src/main/kotlin/com.hedera.hashgraph.hapi.gradle.kts b/gradle/plugins/src/main/kotlin/com.hedera.gradle.protobuf.gradle.kts similarity index 96% rename from build-logic/project-plugins/src/main/kotlin/com.hedera.hashgraph.hapi.gradle.kts rename to gradle/plugins/src/main/kotlin/com.hedera.gradle.protobuf.gradle.kts index 60948f59d941..3d0269c2d28c 100644 --- a/build-logic/project-plugins/src/main/kotlin/com.hedera.hashgraph.hapi.gradle.kts +++ b/gradle/plugins/src/main/kotlin/com.hedera.gradle.protobuf.gradle.kts @@ -15,10 +15,10 @@ */ import com.google.protobuf.gradle.id -import com.hedera.hashgraph.gradlebuild.tasks.GitClone +import com.hedera.gradle.tasks.GitClone plugins { - id("com.hedera.hashgraph.conventions") + id("com.hedera.gradle.services") id("com.google.protobuf") } diff --git a/gradle/plugins/src/main/kotlin/com.hedera.gradle.reports.gradle.kts b/gradle/plugins/src/main/kotlin/com.hedera.gradle.reports.gradle.kts new file mode 100644 index 000000000000..674a18b4c803 --- /dev/null +++ b/gradle/plugins/src/main/kotlin/com.hedera.gradle.reports.gradle.kts @@ -0,0 +1,49 @@ +/* + * Copyright (C) 2023-2024 Hedera Hashgraph, LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +plugins { + id("jvm-ecosystem") + id("jacoco-report-aggregation") + id("com.hedera.gradle.repositories") + id("com.hedera.gradle.jpms-modules") + id("com.hedera.gradle.jpms-module-dependencies") +} + +dependencies { + rootProject.subprojects + // exclude the 'reports' project itself + .filter { prj -> prj != project } + // exclude 'test-clients' as it contains test sources in 'main' + // see also 'codecov.yml' + .filter { prj -> prj.name != "test-clients" } + .forEach { + if (it.name == "hedera-dependency-versions") { + jacocoAggregation(platform(project(it.path))) + } else { + jacocoAggregation(project(it.path)) + } + } +} + +// Use Gradle's 'jacoco-report-aggregation' plugin to create an aggregated report independent of the +// platform (Codecov, Codacy, ...) that picks it up later on. +// See: +// https://docs.gradle.org/current/samples/sample_jvm_multi_project_with_code_coverage_standalone.html +reporting { + reports.create("testCodeCoverageReport") { + testType = TestSuiteType.UNIT_TEST + } +} diff --git a/build-logic/project-plugins/src/main/kotlin/com.hedera.hashgraph.repositories.gradle.kts b/gradle/plugins/src/main/kotlin/com.hedera.gradle.repositories.gradle.kts similarity index 100% rename from build-logic/project-plugins/src/main/kotlin/com.hedera.hashgraph.repositories.gradle.kts rename to gradle/plugins/src/main/kotlin/com.hedera.gradle.repositories.gradle.kts diff --git a/build-logic/project-plugins/src/main/kotlin/com.hedera.hashgraph.root.gradle.kts b/gradle/plugins/src/main/kotlin/com.hedera.gradle.root.gradle.kts similarity index 88% rename from build-logic/project-plugins/src/main/kotlin/com.hedera.hashgraph.root.gradle.kts rename to gradle/plugins/src/main/kotlin/com.hedera.gradle.root.gradle.kts index 67e50ec2fbac..64f7b7806802 100644 --- a/build-logic/project-plugins/src/main/kotlin/com.hedera.hashgraph.root.gradle.kts +++ b/gradle/plugins/src/main/kotlin/com.hedera.gradle.root.gradle.kts @@ -14,19 +14,20 @@ * limitations under the License. */ -import Utils.Companion.versionTxt +import com.hedera.gradle.utils.Utils.generateProjectVersionReport +import com.hedera.gradle.utils.Utils.versionTxt import net.swiftzer.semver.SemVer plugins { - id("com.hedera.hashgraph.lifecycle") - id("com.hedera.hashgraph.repositories") - id("com.hedera.hashgraph.aggregate-reports") - id("com.hedera.hashgraph.spotless-conventions") - id("com.hedera.hashgraph.spotless-kotlin-conventions") - id("com.hedera.hashgraph.dependency-analysis") + id("com.hedera.gradle.lifecycle") + id("com.hedera.gradle.repositories") + id("com.hedera.hashgraph.nexus-publish") + id("com.hedera.gradle.aggregate-reports") + id("com.hedera.gradle.spotless-kotlin") + id("com.autonomousapps.dependency-analysis") } -spotless { kotlinGradle { target("build-logic/**/*.gradle.kts") } } +spotless { kotlinGradle { target("gradle/plugins/**/*.gradle.kts") } } val productVersion = layout.projectDirectory.versionTxt().asFile.readText().trim() @@ -48,7 +49,7 @@ tasks.register("githubVersionSummary") { ) doLast { - Utils.generateProjectVersionReport( + generateProjectVersionReport( inputs.properties["version"] as String, outputs.files.singleFile.outputStream().buffered() ) diff --git a/gradle/plugins/src/main/kotlin/com.hedera.gradle.services-publish.gradle.kts b/gradle/plugins/src/main/kotlin/com.hedera.gradle.services-publish.gradle.kts new file mode 100644 index 000000000000..c5ea8dd91531 --- /dev/null +++ b/gradle/plugins/src/main/kotlin/com.hedera.gradle.services-publish.gradle.kts @@ -0,0 +1,53 @@ +/* + * Copyright (C) 2023-2024 Hedera Hashgraph, LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +plugins { + id("java") + id("com.hedera.gradle.maven-publish") +} + +publishing { + publications { + named("maven") { + pom.developers { + developer { + name = "Hedera Base Team" + email = "hedera-base@swirldslabs.com" + organization = "Hedera Hashgraph" + organizationUrl = "https://www.hedera.com" + } + developer { + name = "Hedera Services Team" + email = "hedera-services@swirldslabs.com" + organization = "Hedera Hashgraph" + organizationUrl = "https://www.hedera.com" + } + developer { + name = "Hedera Smart Contracts Team" + email = "hedera-smart-contracts@swirldslabs.com" + organization = "Hedera Hashgraph" + organizationUrl = "https://www.hedera.com" + } + developer { + name = "Release Engineering Team" + email = "release-engineering@swirldslabs.com" + organization = "Hedera Hashgraph" + organizationUrl = "https://www.hedera.com" + } + } + } + } +} diff --git a/build-logic/project-plugins/src/main/kotlin/com.hedera.hashgraph.conventions.gradle.kts b/gradle/plugins/src/main/kotlin/com.hedera.gradle.services.gradle.kts similarity index 94% rename from build-logic/project-plugins/src/main/kotlin/com.hedera.hashgraph.conventions.gradle.kts rename to gradle/plugins/src/main/kotlin/com.hedera.gradle.services.gradle.kts index 37d24ef59588..be80cfac1c39 100644 --- a/build-logic/project-plugins/src/main/kotlin/com.hedera.hashgraph.conventions.gradle.kts +++ b/gradle/plugins/src/main/kotlin/com.hedera.gradle.services.gradle.kts @@ -16,7 +16,7 @@ plugins { id("java-library") - id("com.hedera.hashgraph.java") + id("com.hedera.gradle.java") } group = "com.hedera.hashgraph" diff --git a/build-logic/settings-plugins/src/main/kotlin/com.hedera.hashgraph.settings.settings.gradle.kts b/gradle/plugins/src/main/kotlin/com.hedera.gradle.settings.settings.gradle.kts similarity index 89% rename from build-logic/settings-plugins/src/main/kotlin/com.hedera.hashgraph.settings.settings.gradle.kts rename to gradle/plugins/src/main/kotlin/com.hedera.gradle.settings.settings.gradle.kts index 51c95581c1f4..d41ca0b3de18 100644 --- a/build-logic/settings-plugins/src/main/kotlin/com.hedera.hashgraph.settings.settings.gradle.kts +++ b/gradle/plugins/src/main/kotlin/com.hedera.gradle.settings.settings.gradle.kts @@ -22,16 +22,14 @@ pluginManagement { } } -plugins { - id("com.gradle.enterprise") - id("org.gradle.toolchains.foojay-resolver-convention") -} +plugins { id("com.gradle.develocity") } // Enable Gradle Build Scan -gradleEnterprise { +develocity { buildScan { - termsOfServiceUrl = "https://gradle.com/terms-of-service" - termsOfServiceAgree = "yes" + termsOfUseUrl = "https://gradle.com/help/legal-terms-of-use" + termsOfUseAgree = "yes" + publishing.onlyIf { false } // only publish with explicit '--scan' } } diff --git a/build-logic/project-plugins/src/main/kotlin/com.hedera.hashgraph.shadow-jar.gradle.kts b/gradle/plugins/src/main/kotlin/com.hedera.gradle.shadow-jar.gradle.kts similarity index 100% rename from build-logic/project-plugins/src/main/kotlin/com.hedera.hashgraph.shadow-jar.gradle.kts rename to gradle/plugins/src/main/kotlin/com.hedera.gradle.shadow-jar.gradle.kts diff --git a/build-logic/project-plugins/src/main/kotlin/com.hedera.hashgraph.spotless-java-conventions.gradle.kts b/gradle/plugins/src/main/kotlin/com.hedera.gradle.spotless-java.gradle.kts similarity index 93% rename from build-logic/project-plugins/src/main/kotlin/com.hedera.hashgraph.spotless-java-conventions.gradle.kts rename to gradle/plugins/src/main/kotlin/com.hedera.gradle.spotless-java.gradle.kts index f4b8bb6262cb..030d31049379 100644 --- a/build-logic/project-plugins/src/main/kotlin/com.hedera.hashgraph.spotless-java-conventions.gradle.kts +++ b/gradle/plugins/src/main/kotlin/com.hedera.gradle.spotless-java.gradle.kts @@ -14,7 +14,10 @@ * limitations under the License. */ -plugins { id("com.diffplug.spotless") } +import com.hedera.gradle.spotless.RepairDashedCommentsFormatterStep +import com.hedera.gradle.spotless.StripOldLicenseFormatterStep + +plugins { id("com.hedera.gradle.spotless") } spotless { java { diff --git a/build-logic/project-plugins/src/main/kotlin/com.hedera.hashgraph.spotless-kotlin-conventions.gradle.kts b/gradle/plugins/src/main/kotlin/com.hedera.gradle.spotless-kotlin.gradle.kts similarity index 97% rename from build-logic/project-plugins/src/main/kotlin/com.hedera.hashgraph.spotless-kotlin-conventions.gradle.kts rename to gradle/plugins/src/main/kotlin/com.hedera.gradle.spotless-kotlin.gradle.kts index 77a3694f344f..68fcb3923015 100644 --- a/build-logic/project-plugins/src/main/kotlin/com.hedera.hashgraph.spotless-kotlin-conventions.gradle.kts +++ b/gradle/plugins/src/main/kotlin/com.hedera.gradle.spotless-kotlin.gradle.kts @@ -14,7 +14,7 @@ * limitations under the License. */ -plugins { id("com.diffplug.spotless") } +plugins { id("com.hedera.gradle.spotless") } spotless { kotlinGradle { diff --git a/build-logic/project-plugins/src/main/kotlin/com.hedera.hashgraph.spotless-conventions.gradle.kts b/gradle/plugins/src/main/kotlin/com.hedera.gradle.spotless.gradle.kts similarity index 100% rename from build-logic/project-plugins/src/main/kotlin/com.hedera.hashgraph.spotless-conventions.gradle.kts rename to gradle/plugins/src/main/kotlin/com.hedera.gradle.spotless.gradle.kts diff --git a/build-logic/project-plugins/src/main/kotlin/com.hedera.hashgraph.versions.gradle.kts b/gradle/plugins/src/main/kotlin/com.hedera.gradle.versions.gradle.kts similarity index 85% rename from build-logic/project-plugins/src/main/kotlin/com.hedera.hashgraph.versions.gradle.kts rename to gradle/plugins/src/main/kotlin/com.hedera.gradle.versions.gradle.kts index 96bd0daef428..02fffa7ac885 100644 --- a/build-logic/project-plugins/src/main/kotlin/com.hedera.hashgraph.versions.gradle.kts +++ b/gradle/plugins/src/main/kotlin/com.hedera.gradle.versions.gradle.kts @@ -16,11 +16,13 @@ plugins { id("java-platform") - id("com.hedera.hashgraph.jpms-modules") - id("com.hedera.hashgraph.jpms-module-dependencies") + id("com.hedera.gradle.jpms-modules") + id("com.hedera.gradle.jpms-module-dependencies") id("org.gradlex.java-module-versions") } group = "com.hedera.hashgraph" javaPlatform { allowDependencies() } + +tasks.register("releaseMavenCentral") diff --git a/gradle/plugins/src/main/kotlin/com.hedera.hashgraph.nexus-publish.gradle.kts b/gradle/plugins/src/main/kotlin/com.hedera.hashgraph.nexus-publish.gradle.kts new file mode 100644 index 000000000000..508fc7d48dc1 --- /dev/null +++ b/gradle/plugins/src/main/kotlin/com.hedera.hashgraph.nexus-publish.gradle.kts @@ -0,0 +1,45 @@ +/* + * Copyright (C) 2023-2024 Hedera Hashgraph, LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +plugins { + id("com.hedera.gradle.lifecycle") + id("io.github.gradle-nexus.publish-plugin") +} + +nexusPublishing { + repositories { + sonatype { + username = System.getenv("OSSRH_USERNAME") + password = System.getenv("OSSRH_PASSWORD") + } + } +} + +tasks.named("closeSonatypeStagingRepository") { + // The publishing of all components to Maven Central is automatically done before close (which + // is done before release). + dependsOn(subprojects.map { ":${it.name}:releaseMavenCentral" }) +} + +tasks.named("releaseMavenCentral") { + group = "release" + dependsOn(tasks.closeAndReleaseStagingRepository) +} + +tasks.register("releaseMavenCentralSnapshot") { + group = "release" + dependsOn(subprojects.map { ":${it.name}:releaseMavenCentral" }) +} diff --git a/gradle/plugins/src/main/kotlin/com/hedera/gradle/services/TaskLockService.kt b/gradle/plugins/src/main/kotlin/com/hedera/gradle/services/TaskLockService.kt new file mode 100644 index 000000000000..212fe41ffd26 --- /dev/null +++ b/gradle/plugins/src/main/kotlin/com/hedera/gradle/services/TaskLockService.kt @@ -0,0 +1,22 @@ +/* + * Copyright (C) 2022-2023 Hedera Hashgraph, LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.hedera.gradle.services + +import org.gradle.api.services.BuildService +import org.gradle.api.services.BuildServiceParameters + +abstract class TaskLockService : BuildService diff --git a/build-logic/project-plugins/src/main/kotlin/RepairDashedCommentsFormatterStep.kt b/gradle/plugins/src/main/kotlin/com/hedera/gradle/spotless/RepairDashedCommentsFormatterStep.kt similarity index 98% rename from build-logic/project-plugins/src/main/kotlin/RepairDashedCommentsFormatterStep.kt rename to gradle/plugins/src/main/kotlin/com/hedera/gradle/spotless/RepairDashedCommentsFormatterStep.kt index 1822e0da3be4..39b9770b4f7f 100644 --- a/build-logic/project-plugins/src/main/kotlin/RepairDashedCommentsFormatterStep.kt +++ b/gradle/plugins/src/main/kotlin/com/hedera/gradle/spotless/RepairDashedCommentsFormatterStep.kt @@ -14,6 +14,8 @@ * limitations under the License. */ +package com.hedera.gradle.spotless + import com.diffplug.spotless.FormatterFunc import com.diffplug.spotless.FormatterStep diff --git a/build-logic/project-plugins/src/main/kotlin/StripOldLicenseFormatterStep.kt b/gradle/plugins/src/main/kotlin/com/hedera/gradle/spotless/StripOldLicenseFormatterStep.kt similarity index 98% rename from build-logic/project-plugins/src/main/kotlin/StripOldLicenseFormatterStep.kt rename to gradle/plugins/src/main/kotlin/com/hedera/gradle/spotless/StripOldLicenseFormatterStep.kt index db544f5ed342..c4b435d47542 100644 --- a/build-logic/project-plugins/src/main/kotlin/StripOldLicenseFormatterStep.kt +++ b/gradle/plugins/src/main/kotlin/com/hedera/gradle/spotless/StripOldLicenseFormatterStep.kt @@ -14,6 +14,8 @@ * limitations under the License. */ +package com.hedera.gradle.spotless + import com.diffplug.spotless.FormatterFunc import com.diffplug.spotless.FormatterStep diff --git a/gradle/plugins/src/main/kotlin/com/hedera/gradle/tasks/GitClone.kt b/gradle/plugins/src/main/kotlin/com/hedera/gradle/tasks/GitClone.kt new file mode 100644 index 000000000000..d4356938f738 --- /dev/null +++ b/gradle/plugins/src/main/kotlin/com/hedera/gradle/tasks/GitClone.kt @@ -0,0 +1,99 @@ +/* + * Copyright (C) 2022-2023 Hedera Hashgraph, LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.hedera.gradle.tasks + +import org.gradle.api.DefaultTask +import org.gradle.api.file.DirectoryProperty +import org.gradle.api.provider.Property +import org.gradle.api.tasks.Input +import org.gradle.api.tasks.Optional +import org.gradle.api.tasks.OutputDirectory +import org.gradle.api.tasks.TaskAction +import org.gradle.process.ExecOperations +import javax.inject.Inject + +abstract class GitClone : DefaultTask() { + + @get:Input + abstract val url: Property + + @get:Input + @get:Optional + abstract val tag: Property + + @get:Input + @get:Optional + abstract val branch: Property + + @get:Input + abstract val offline: Property + + @get:OutputDirectory + abstract val localCloneDirectory: DirectoryProperty + + @get:Inject + protected abstract val exec: ExecOperations + + init { + // If a 'branch' is configured, the task is never up-to-date as it may change + outputs.upToDateWhen { !branch.isPresent } + } + + @TaskAction + fun cloneOrUpdate() { + if (!tag.isPresent && !branch.isPresent || tag.isPresent && branch.isPresent) { + throw RuntimeException("Define either 'tag' or 'branch'") + } + + val localClone = localCloneDirectory.get() + if (!offline.get()) { + exec.exec { + if (!localClone.dir(".git").asFile.exists()) { + workingDir = localClone.asFile.parentFile + commandLine( + "git", + "clone", + "https://github.com/hashgraph/hedera-protobufs.git", + "-q" + ) + } else { + workingDir = localClone.asFile + commandLine("git", "fetch", "-q") + } + } + } + if (tag.isPresent) { + exec.exec { + workingDir = localClone.asFile + commandLine("git", "checkout", tag.get(), "-q") + } + exec.exec { + workingDir = localClone.asFile + commandLine("git", "reset", "--hard", tag.get(), "-q") + } + } else { + exec.exec { + workingDir = localClone.asFile + commandLine("git", "checkout", branch.get(), "-q") + } + exec.exec { + workingDir = localClone.asFile + commandLine("git", "reset", "--hard", "origin/${branch.get()}", "-q") + } + } + } +} \ No newline at end of file diff --git a/gradle/plugins/src/main/kotlin/com/hedera/gradle/utils/Utils.kt b/gradle/plugins/src/main/kotlin/com/hedera/gradle/utils/Utils.kt new file mode 100644 index 000000000000..94bba28d8947 --- /dev/null +++ b/gradle/plugins/src/main/kotlin/com/hedera/gradle/utils/Utils.kt @@ -0,0 +1,56 @@ +/* + * Copyright (C) 2022-2023 Hedera Hashgraph, LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.hedera.gradle.utils + +import org.gradle.api.Task +import org.gradle.api.file.Directory +import org.gradle.api.file.RegularFile +import org.gradle.api.tasks.testing.TestDescriptor +import org.gradle.api.tasks.testing.TestListener +import org.gradle.api.tasks.testing.TestResult +import java.io.OutputStream +import java.io.PrintStream +import java.text.SimpleDateFormat +import java.util.Date + +object Utils { + // Find the version.txt in the root of the repository, independent of + // which build is started from where. + @JvmStatic + fun Directory.versionTxt(): RegularFile = + file("version.txt").let { if (it.asFile.exists()) it else this.dir("..").versionTxt() } + + @JvmStatic + fun generateProjectVersionReport(version: String, ostream: OutputStream) { + val writer = PrintStream(ostream, false, Charsets.UTF_8) + + ostream.use { + writer.use { + // Writer headers + writer.println("### Deployed Version Information") + writer.println() + writer.println("| Artifact Name | Version Number |") + writer.println("| --- | --- |") + // Write table rows + writer.printf("| %s | %s |\n", "hedera-node", version) + writer.printf("| %s | %s |\n", "platform-sdk", version) + writer.flush() + ostream.flush() + } + } + } +} diff --git a/gradle/reports/build.gradle.kts b/gradle/reports/build.gradle.kts new file mode 100644 index 000000000000..c4ea91df8da6 --- /dev/null +++ b/gradle/reports/build.gradle.kts @@ -0,0 +1,3 @@ +plugins { + id("com.hedera.gradle.reports") +} diff --git a/build-logic/scripts/clear-gradle-cache.sh b/gradle/scripts/clear-gradle-cache.sh similarity index 100% rename from build-logic/scripts/clear-gradle-cache.sh rename to gradle/scripts/clear-gradle-cache.sh diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index d64cd4917707..e6441136f3d4 100644 Binary files a/gradle/wrapper/gradle-wrapper.jar and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/hapi/build.gradle.kts b/hapi/build.gradle.kts index c54e134972ce..a03b9db648cd 100644 --- a/hapi/build.gradle.kts +++ b/hapi/build.gradle.kts @@ -15,19 +15,20 @@ */ plugins { - id("com.hedera.hashgraph.hapi") - id("com.hedera.hashgraph.evm-maven-publish") - @Suppress("DSL_SCOPE_VIOLATION") alias(libs.plugins.pbj) - id("com.hedera.hashgraph.java-test-fixtures") + id("com.hedera.gradle.protobuf") + id("com.hedera.gradle.services-publish") + id("com.hedera.gradle.java-test-fixtures") + alias(libs.plugins.pbj) } description = "Hedera API" // Add downloaded HAPI repo protobuf files into build directory and add to sources to build them tasks.cloneHederaProtobufs { - branchOrTag = "v0.50.0-release" - // As long as the 'branchOrTag' above is not stable, run always: - outputs.upToDateWhen { false } + // uncomment below to use a specific tag + // tag = "v0.50.0-release" + // uncomment below to use a specific branch + branch = "main" } sourceSets { diff --git a/hapi/src/main/java/module-info.java b/hapi/src/main/java/module-info.java index 9d8d979e54fd..3f1bed1862af 100644 --- a/hapi/src/main/java/module-info.java +++ b/hapi/src/main/java/module-info.java @@ -32,6 +32,8 @@ exports com.hedera.hapi.streams; exports com.hedera.hapi.streams.codec; exports com.hedera.hapi.streams.schema; + exports com.hedera.hapi.node.state.addressbook.codec; + exports com.hedera.hapi.node.state.addressbook; exports com.hedera.hapi.node.state.consensus.codec; exports com.hedera.hapi.node.state.consensus; exports com.hedera.hapi.node.state.token; diff --git a/hedera-dependency-versions/build.gradle.kts b/hedera-dependency-versions/build.gradle.kts index 507c015bb91f..2901407469a1 100644 --- a/hedera-dependency-versions/build.gradle.kts +++ b/hedera-dependency-versions/build.gradle.kts @@ -15,103 +15,216 @@ */ plugins { - id("com.hedera.hashgraph.versions") + id("com.hedera.gradle.versions") } -val besuNativeVersion = "0.8.2" -val besuVersion = "24.3.3" -val bouncycastleVersion = "1.78" -val daggerVersion = "2.42" -val eclipseCollectionsVersion = "10.4.0" -val grpcVersion = "1.54.1" -val helidonVersion = "3.2.1" -val jacksonVersion = "2.16.0" -val log4jVersion = "2.21.1" -val mockitoVersion = "5.8.0" -val nettyVersion = "4.1.87.Final" -val prometheusVersion = "0.16.0" -val protobufVersion = "3.21.7" -val systemStubsVersion = "2.1.5" -val testContainersVersion = "1.17.2" -val tuweniVersion = "2.4.2" - dependencies { - api(enforcedPlatform("io.netty:netty-bom:$nettyVersion")) + api(enforcedPlatform("io.netty:netty-bom:4.1.87.Final")) // Force commons compress version to close a security vulnerability api(javaModuleDependencies.gav("org.apache.commons.compress")) // forward logging from modules using SLF4J (e.g. 'org.hyperledger.besu.evm') to Log4J - runtime(javaModuleDependencies.gav("org.apache.logging.log4j.slf4j")) + runtime(javaModuleDependencies.gav("org.apache.logging.log4j.slf4j2.impl")) } -moduleInfo { - version("awaitility", "4.2.0") - version("com.fasterxml.jackson.core", jacksonVersion) - version("com.fasterxml.jackson.databind", jacksonVersion) - version("com.github.benmanes.caffeine", "3.1.1") - version("com.github.docker.java.api", "3.2.13") - version("com.github.spotbugs.annotations", "4.7.3") - version("com.google.auto.service", "1.1.1") - version("com.google.auto.service.processor", "1.1.1") - version("com.google.common", "31.1-jre") - version("com.google.jimfs", "1.2") - version("com.google.protobuf", protobufVersion) - version("com.google.protobuf.util", protobufVersion) - version("com.hedera.pbj.runtime", "0.8.7") - version("com.squareup.javapoet", "1.13.0") - version("com.sun.jna", "5.12.1") - version("dagger", daggerVersion) - version("dagger.compiler", daggerVersion) - version("grpc.netty", grpcVersion) - version("grpc.protobuf", grpcVersion) - version("grpc.stub", grpcVersion) - version("headlong", "6.1.1") - version("info.picocli", "4.6.3") - version("io.github.classgraph", "4.8.65") - version("io.grpc", helidonVersion) - version("io.netty.handler", nettyVersion) - version("io.netty.transport", nettyVersion) - version("io.netty.transport.classes.epoll", nettyVersion) - version("io.perfmark", "0.25.0") - version("io.prometheus.simpleclient", prometheusVersion) - version("io.prometheus.simpleclient.httpserver", prometheusVersion) - version("jakarta.inject", "2.0.1") - version("java.annotation", "1.3.2") - version("javax.inject", "1") - version("lazysodium.java", "5.1.1") - version("net.i2p.crypto.eddsa", "0.3.0") - version("org.antlr.antlr4.runtime", "4.11.1") - version("org.apache.commons.codec", "1.15") - version("org.apache.commons.collections4", "4.4") - version("org.apache.commons.io", "2.15.1") - version("org.apache.commons.lang3", "3.14.0") - version("org.apache.commons.math3", "3.2") - version("org.apache.commons.compress", "1.26.0") - version("org.apache.logging.log4j", log4jVersion) - version("org.apache.logging.log4j.core", log4jVersion) - version("org.apache.logging.log4j.slf4j", log4jVersion) - version("org.assertj.core", "3.23.1") - version("org.bouncycastle.pkix", bouncycastleVersion) - version("org.bouncycastle.provider", bouncycastleVersion) - version("org.eclipse.collections.api", eclipseCollectionsVersion) - version("org.eclipse.collections.impl", eclipseCollectionsVersion) - version("org.hamcrest", "2.2") - version("org.hyperledger.besu.datatypes", besuVersion) - version("org.hyperledger.besu.evm", besuVersion) - version("org.hyperledger.besu.nativelib.secp256k1", besuNativeVersion) - version("org.json", "20231013") - version("org.junit.jupiter.api", "5.9.1") - version("org.junit.platform.engine", "1.9.1") - version("org.junitpioneer", "2.0.1") - version("org.mockito", mockitoVersion) - version("org.mockito.junit.jupiter", mockitoVersion) - version("org.opentest4j", "1.2.0") - version("org.testcontainers", testContainersVersion) - version("org.testcontainers.junit.jupiter", testContainersVersion) - version("org.yaml.snakeyaml", "2.2") - version("tuweni.bytes", tuweniVersion) - version("tuweni.units", tuweniVersion) - version("uk.org.webcompere.systemstubs.core", systemStubsVersion) - version("uk.org.webcompere.systemstubs.jupiter", systemStubsVersion) +dependencies.constraints { + api("org.awaitility:awaitility:4.2.0") { + because("awaitility") + } + api("com.fasterxml.jackson.core:jackson-core:2.16.0") { + because("com.fasterxml.jackson.core") + } + api("com.fasterxml.jackson.core:jackson-databind:2.16.0") { + because("com.fasterxml.jackson.databind") + } + api("com.github.ben-manes.caffeine:caffeine:3.1.1") { + because("com.github.benmanes.caffeine") + } + api("com.github.docker-java:docker-java-api:3.2.13") { + because("com.github.docker.java.api") + } + api("com.github.spotbugs:spotbugs-annotations:4.7.3") { + because("com.github.spotbugs.annotations") + } + api("com.google.auto.service:auto-service-annotations:1.1.1") { + because("com.google.auto.service") + } + api("com.google.auto.service:auto-service:1.1.1") { + because("com.google.auto.service.processor") + } + api("com.google.guava:guava:31.1-jre") { + because("com.google.common") + } + api("com.google.jimfs:jimfs:1.2") { + because("com.google.jimfs") + } + api("com.google.protobuf:protobuf-java:3.21.7") { + because("com.google.protobuf") + } + api("com.google.protobuf:protobuf-java-util:3.21.7") { + because("com.google.protobuf.util") + } + api("com.hedera.pbj:pbj-runtime:0.8.9") { + because("com.hedera.pbj.runtime") + } + api("com.squareup:javapoet:1.13.0") { + because("com.squareup.javapoet") + } + api("net.java.dev.jna:jna:5.12.1") { + because("com.sun.jna") + } + api("com.google.dagger:dagger:2.42") { + because("dagger") + } + api("com.google.dagger:dagger-compiler:2.42") { + because("dagger.compiler") + } + api("io.grpc:grpc-netty:1.54.1") { + because("grpc.netty") + } + api("io.grpc:grpc-protobuf:1.54.1") { + because("grpc.protobuf") + } + api("io.grpc:grpc-stub:1.54.1") { + because("grpc.stub") + } + api("com.esaulpaugh:headlong:6.1.1") { + because("headlong") + } + api("info.picocli:picocli:4.6.3") { + because("info.picocli") + } + api("io.github.classgraph:classgraph:4.8.65") { + because("io.github.classgraph") + } + api("io.helidon.grpc:io.grpc:3.2.1") { + because("io.grpc") + } + api("io.netty:netty-handler:4.1.87.Final") { + because("io.netty.handler") + } + api("io.netty:netty-transport:4.1.87.Final") { + because("io.netty.transport") + } + api("io.netty:netty-transport-classes-epoll:4.1.87.Final") { + because("io.netty.transport.classes.epoll") + } + api("io.perfmark:perfmark-api:0.25.0") { + because("io.perfmark") + } + api("io.prometheus:simpleclient:0.16.0") { + because("io.prometheus.simpleclient") + } + api("io.prometheus:simpleclient_httpserver:0.16.0") { + because("io.prometheus.simpleclient.httpserver") + } + api("jakarta.inject:jakarta.inject-api:2.0.1") { + because("jakarta.inject") + } + api("javax.annotation:javax.annotation-api:1.3.2") { + because("java.annotation") + } + api("javax.inject:javax.inject:1") { + because("javax.inject") + } + api("com.goterl:lazysodium-java:5.1.1") { + because("lazysodium.java") + } + api("net.i2p.crypto:eddsa:0.3.0") { + because("net.i2p.crypto.eddsa") + } + api("org.antlr:antlr4-runtime:4.13.1") { + because("org.antlr.antlr4.runtime") + } + api("commons-codec:commons-codec:1.15") { + because("org.apache.commons.codec") + } + api("org.apache.commons:commons-collections4:4.4") { + because("org.apache.commons.collections4") + } + api("commons-io:commons-io:2.15.1") { + because("org.apache.commons.io") + } + api("org.apache.commons:commons-lang3:3.14.0") { + because("org.apache.commons.lang3") + } + api("org.apache.commons:commons-compress:1.26.0") { + because("org.apache.commons.compress") + } + api("org.apache.logging.log4j:log4j-api:2.21.1") { + because("org.apache.logging.log4j") + } + api("org.apache.logging.log4j:log4j-core:2.21.1") { + because("org.apache.logging.log4j.core") + } + api("org.apache.logging.log4j:log4j-slf4j2-impl:2.21.1") { + because("org.apache.logging.log4j.slf4j2.impl") + } + api("org.assertj:assertj-core:3.23.1") { + because("org.assertj.core") + } + api("org.bouncycastle:bcpkix-jdk18on:1.78") { + because("org.bouncycastle.pkix") + } + api("org.bouncycastle:bcprov-jdk18on:1.78") { + because("org.bouncycastle.provider") + } + api("org.eclipse.collections:eclipse-collections-api:10.4.0") { + because("org.eclipse.collections.api") + } + api("org.eclipse.collections:eclipse-collections:10.4.0") { + because("org.eclipse.collections.impl") + } + api("org.hamcrest:hamcrest:2.2") { + because("org.hamcrest") + } + api("org.hyperledger.besu:besu-datatypes:24.3.3") { + because("org.hyperledger.besu.datatypes") + } + api("org.hyperledger.besu:evm:24.3.3") { + because("org.hyperledger.besu.evm") + } + api("org.hyperledger.besu:secp256k1:0.8.2") { + because("org.hyperledger.besu.nativelib.secp256k1") + } + api("org.json:json:20231013") { + because("org.json") + } + api("org.junit.jupiter:junit-jupiter-api:5.10.2") { + because("org.junit.jupiter.api") + } + api("org.junit-pioneer:junit-pioneer:2.0.1") { + because("org.junitpioneer") + } + api("org.mockito:mockito-core:5.8.0") { + because("org.mockito") + } + api("org.mockito:mockito-junit-jupiter:5.8.0") { + because("org.mockito.junit.jupiter") + } + api("org.opentest4j:opentest4j:1.2.0") { + because("org.opentest4j") + } + api("org.testcontainers:testcontainers:1.17.2") { + because("org.testcontainers") + } + api("org.testcontainers:junit-jupiter:1.17.2") { + because("org.testcontainers.junit.jupiter") + } + api("org.yaml:snakeyaml:2.2") { + because("org.yaml.snakeyaml") + } + api("io.tmio:tuweni-bytes:2.4.2") { + because("tuweni.bytes") + } + api("io.tmio:tuweni-units:2.4.2") { + because("tuweni.units") + } + api("uk.org.webcompere:system-stubs-core:2.1.5") { + because("uk.org.webcompere.systemstubs.core") + } + api("uk.org.webcompere:system-stubs-jupiter:2.1.5") { + because("uk.org.webcompere.systemstubs.jupiter") + } } diff --git a/hedera-node/cli-clients/build.gradle.kts b/hedera-node/cli-clients/build.gradle.kts index c373687b9af6..b916cb667fae 100644 --- a/hedera-node/cli-clients/build.gradle.kts +++ b/hedera-node/cli-clients/build.gradle.kts @@ -15,8 +15,8 @@ */ plugins { - id("com.hedera.hashgraph.conventions") - id("com.hedera.hashgraph.shadow-jar") + id("com.hedera.gradle.services") + id("com.hedera.gradle.shadow-jar") } description = "Hedera Services Command-Line Clients" diff --git a/hedera-node/cli-clients/src/main/java/com/hedera/services/cli/contracts/assembly/Editor.java b/hedera-node/cli-clients/src/main/java/com/hedera/services/cli/contracts/assembly/Editor.java index 96a2ce14eaec..a24713bc9b7c 100644 --- a/hedera-node/cli-clients/src/main/java/com/hedera/services/cli/contracts/assembly/Editor.java +++ b/hedera-node/cli-clients/src/main/java/com/hedera/services/cli/contracts/assembly/Editor.java @@ -118,7 +118,7 @@ public String toString() { } } - /** Merge a List with a bunch of Edits, producing a new assembly List */ + /** Merge a {@link List} with a bunch of Edits, producing a new assembly {@link List} */ public @NonNull List merge() { var merged = new ArrayList(baseLines); final Consumer inserter = edit -> merged.addAll(edit.lineRange().from(), edit.newLines()); diff --git a/hedera-node/cli-clients/src/main/java/com/hedera/services/cli/sign/AccountBalanceSigningUtils.java b/hedera-node/cli-clients/src/main/java/com/hedera/services/cli/sign/AccountBalanceSigningUtils.java index 68dfe8e14cb9..481f26974827 100644 --- a/hedera-node/cli-clients/src/main/java/com/hedera/services/cli/sign/AccountBalanceSigningUtils.java +++ b/hedera-node/cli-clients/src/main/java/com/hedera/services/cli/sign/AccountBalanceSigningUtils.java @@ -67,7 +67,7 @@ public static boolean signAccountBalanceFile( try { final Hash entireHash = computeEntireHash(streamFileToSign.toFile()); - final byte[] fileHashByte = entireHash.getValue(); + final byte[] fileHashByte = entireHash.copyToByteArray(); final byte[] signature = signData(fileHashByte, keyPair); generateSigBalanceFile(signatureFileDestination.toFile(), signature, fileHashByte); diff --git a/hedera-node/cli-clients/src/main/java/com/hedera/services/cli/signedstate/DumpContractStoresSubcommand.java b/hedera-node/cli-clients/src/main/java/com/hedera/services/cli/signedstate/DumpContractStoresSubcommand.java index fb0a7604fff5..9d2dac1a2c28 100644 --- a/hedera-node/cli-clients/src/main/java/com/hedera/services/cli/signedstate/DumpContractStoresSubcommand.java +++ b/hedera-node/cli-clients/src/main/java/com/hedera/services/cli/signedstate/DumpContractStoresSubcommand.java @@ -29,18 +29,18 @@ import com.hedera.node.app.service.mono.state.virtual.ContractKey; import com.hedera.node.app.service.mono.utils.NonAtomicReference; import com.hedera.node.app.spi.state.StateDefinition; -import com.hedera.node.app.spi.state.WritableKVState; -import com.hedera.node.app.spi.state.WritableKVStateBase; import com.hedera.node.app.state.merkle.StateMetadata; -import com.hedera.node.app.state.merkle.memory.InMemoryKey; -import com.hedera.node.app.state.merkle.memory.InMemoryValue; -import com.hedera.node.app.state.merkle.memory.InMemoryWritableKVState; import com.hedera.services.cli.signedstate.DumpStateCommand.EmitSummary; import com.hedera.services.cli.signedstate.DumpStateCommand.WithMigration; import com.hedera.services.cli.signedstate.DumpStateCommand.WithSlots; import com.hedera.services.cli.signedstate.DumpStateCommand.WithValidation; import com.hedera.services.cli.signedstate.SignedStateCommand.Verbosity; import com.swirlds.merkle.map.MerkleMap; +import com.swirlds.platform.state.merkle.memory.InMemoryKey; +import com.swirlds.platform.state.merkle.memory.InMemoryValue; +import com.swirlds.platform.state.merkle.memory.InMemoryWritableKVState; +import com.swirlds.platform.state.spi.WritableKVStateBase; +import com.swirlds.state.spi.WritableKVState; import edu.umd.cs.findbugs.annotations.NonNull; import edu.umd.cs.findbugs.annotations.Nullable; import java.io.FileNotFoundException; @@ -272,8 +272,12 @@ WritableKVState getMigratedContractStore() { final var contractMerkleMap = new NonAtomicReference, InMemoryValue>>( new MerkleMap<>(expectedNumberOfSlots)); - final var toStore = new NonAtomicReference>( - new InMemoryWritableKVState<>(contractStoreSchemaMetadata, contractMerkleMap.get())); + final var toStore = new NonAtomicReference>(new InMemoryWritableKVState<>( + contractStoreStateDefinition.stateKey(), + contractStoreSchemaMetadata.inMemoryValueClassId(), + contractStoreStateDefinition.keyCodec(), + contractStoreStateDefinition.valueCodec(), + contractMerkleMap.get())); final var flushCounter = new AtomicInteger(); @@ -283,7 +287,12 @@ WritableKVState getMigratedContractStore() { // Copy the underlying map, which does the flush contractMerkleMap.set(contractMerkleMap.get().copy()); // Create a new store to go on with - toStore.set(new InMemoryWritableKVState<>(contractStoreSchemaMetadata, contractMerkleMap.get())); + toStore.set(new InMemoryWritableKVState<>( + contractStoreStateDefinition.stateKey(), + contractStoreSchemaMetadata.inMemoryValueClassId(), + contractStoreStateDefinition.keyCodec(), + contractStoreStateDefinition.valueCodec(), + contractMerkleMap.get())); flushCounter.incrementAndGet(); diff --git a/hedera-node/cli-clients/src/main/java/com/hedera/services/cli/signedstate/SignedStateHolder.java b/hedera-node/cli-clients/src/main/java/com/hedera/services/cli/signedstate/SignedStateHolder.java index a783c1684d1a..6949640305d4 100644 --- a/hedera-node/cli-clients/src/main/java/com/hedera/services/cli/signedstate/SignedStateHolder.java +++ b/hedera-node/cli-clients/src/main/java/com/hedera/services/cli/signedstate/SignedStateHolder.java @@ -36,19 +36,16 @@ import com.hedera.node.app.service.mono.state.virtual.VirtualBlobValue; import com.hedera.node.app.service.mono.stream.RecordsRunningHashLeaf; import com.hedera.node.app.service.mono.utils.EntityNum; -import com.swirlds.base.time.Time; import com.swirlds.common.AutoCloseableNonThrowing; import com.swirlds.common.config.singleton.ConfigurationHolder; import com.swirlds.common.constructable.ConstructableRegistry; import com.swirlds.common.constructable.ConstructableRegistryException; -import com.swirlds.common.context.DefaultPlatformContext; -import com.swirlds.common.crypto.CryptographyHolder; -import com.swirlds.common.metrics.noop.NoOpMetrics; +import com.swirlds.common.context.PlatformContext; import com.swirlds.config.api.Configuration; import com.swirlds.config.api.ConfigurationBuilder; import com.swirlds.config.extensions.sources.LegacyFileConfigSource; import com.swirlds.platform.state.signed.ReservedSignedState; -import com.swirlds.platform.state.signed.SignedStateFileReader; +import com.swirlds.platform.state.snapshot.SignedStateFileReader; import com.swirlds.platform.system.StaticSoftwareVersion; import edu.umd.cs.findbugs.annotations.NonNull; import edu.umd.cs.findbugs.annotations.Nullable; @@ -355,8 +352,7 @@ private Pair dehydrate(@NonNull final List

A range of integers specified by inclusive index at the low end, exclusive at the high end, * thus an empty range is from == to. (There is no check _on construction_ that this range is - * "valid", i.e., 0 <= from, from <= to.) + * "valid", i.e., 0 <= from, from <= to.) * * @param from - inclusive * @param to - exclusive diff --git a/hedera-node/cli-clients/src/main/java/com/hedera/services/cli/utils/ThingsToStrings.java b/hedera-node/cli-clients/src/main/java/com/hedera/services/cli/utils/ThingsToStrings.java index e7fb9592b4b3..311c9581a329 100644 --- a/hedera-node/cli-clients/src/main/java/com/hedera/services/cli/utils/ThingsToStrings.java +++ b/hedera-node/cli-clients/src/main/java/com/hedera/services/cli/utils/ThingsToStrings.java @@ -199,7 +199,7 @@ public static String toStringOfJKey(@NonNull final JKey jkey) { if (jkey.isEmpty()) return ""; try { final var ser = jkey.serialize(); - final var hash = CryptographyHolder.get().digestSync(ser).getValue(); + final var hash = CryptographyHolder.get().digestBytesSync(ser); return toStringOfByteArray(hash); } catch (final IOException ex) { @@ -216,7 +216,7 @@ public static boolean toStringOfJKey(@NonNull final StringBuilder sb, @Nullable try { final var ser = jkey.serialize(); - final var hash = CryptographyHolder.get().digestSync(ser).getValue(); + final var hash = CryptographyHolder.get().digestBytesSync(ser); toStringOfByteArray(sb, hash); } catch (final IOException ex) { sb.append("**EXCEPTION SERIALIZING JKEY**"); diff --git a/hedera-node/cli-clients/src/main/java/module-info.java b/hedera-node/cli-clients/src/main/java/module-info.java index aa03aeb82eda..452e77df3506 100644 --- a/hedera-node/cli-clients/src/main/java/module-info.java +++ b/hedera-node/cli-clients/src/main/java/module-info.java @@ -10,19 +10,19 @@ requires com.hedera.node.app.hapi.utils; requires com.hedera.node.app.service.contract.impl; requires com.hedera.node.app.service.contract; - requires com.hedera.node.app.service.evm; requires com.hedera.node.app.spi; requires com.hedera.node.app; requires com.hedera.node.hapi; requires com.google.common; requires com.google.protobuf; + requires com.hedera.evm; requires com.hedera.pbj.runtime; requires com.swirlds.base; requires com.swirlds.config.api; requires com.swirlds.config.extensions; requires com.swirlds.fchashmap; requires com.swirlds.merkle; - requires com.swirlds.metrics.api; + requires com.swirlds.state.api; requires com.swirlds.virtualmap; requires io.github.classgraph; requires org.apache.commons.lang3; diff --git a/hedera-node/configuration/compose/log4j2.xml b/hedera-node/configuration/compose/log4j2.xml index 4cf87407c7b4..34b70dd60740 100644 --- a/hedera-node/configuration/compose/log4j2.xml +++ b/hedera-node/configuration/compose/log4j2.xml @@ -7,101 +7,77 @@ + filePattern="output/hgcaa-%i.log"> %d{yyyy-MM-dd HH:mm:ss.SSS} %-5p %-4L %c{1} - %m{nolookups}%n - - - - - - - - + + filePattern="output/queries-%i.log"> %d{yyyy-MM-dd HH:mm:ss.SSS} %-5p %-4L %c{1} - %m{nolookups}%n - - - - - - - - + + filePattern="output/swirlds-sdk-%i.log"> %d{yyyy-MM-dd HH:mm:ss.SSS} %-8sn %-5p %-16marker <%t> %c{1}: %msg{nolookups}%n - - - - - - - - + + filePattern="output/swirlds-vmap-%i.log"> %d{yyyy-MM-dd HH:mm:ss.SSS} %-8sn %-5p %-16marker <%t> %c{1}: %msg{nolookups}%n - - - - - - - - + + filePattern="output/swirlds-hashstream/swirlds-hashstream-%i.log"> %d{yyyy-MM-dd HH:mm:ss.SSS} %-8sn %-5p %-16marker <%t> %c{1}: %msg{nolookups}%n - - - - - - - - + + filePattern="output/transaction-state/state-changes-%i.log"> + + %d{yyyy-MM-dd HH:mm:ss.SSS} - %m{nolookups}%n + + + + + + + %d{yyyy-MM-dd HH:mm:ss.SSS} - %m{nolookups}%n @@ -242,6 +218,11 @@ + + + + + diff --git a/hedera-node/configuration/dev/log4j2.xml b/hedera-node/configuration/dev/log4j2.xml index 4cf87407c7b4..2e4b79cb7315 100644 --- a/hedera-node/configuration/dev/log4j2.xml +++ b/hedera-node/configuration/dev/log4j2.xml @@ -7,101 +7,76 @@ + filePattern="output/hgcaa-%i.log"> %d{yyyy-MM-dd HH:mm:ss.SSS} %-5p %-4L %c{1} - %m{nolookups}%n - - - - - - - - + + filePattern="output/queries-%i.log"> %d{yyyy-MM-dd HH:mm:ss.SSS} %-5p %-4L %c{1} - %m{nolookups}%n - - - - - - - - + + filePattern="output/swirlds-sdk-%i.log"> %d{yyyy-MM-dd HH:mm:ss.SSS} %-8sn %-5p %-16marker <%t> %c{1}: %msg{nolookups}%n - - - - - - - - + + filePattern="output/swirlds-vmap-%i.log"> %d{yyyy-MM-dd HH:mm:ss.SSS} %-8sn %-5p %-16marker <%t> %c{1}: %msg{nolookups}%n - - - - - - - - + + filePattern="output/swirlds-hashstream/swirlds-hashstream-%i.log"> %d{yyyy-MM-dd HH:mm:ss.SSS} %-8sn %-5p %-16marker <%t> %c{1}: %msg{nolookups}%n - - - - - - - - + + filePattern="output/transaction-state/state-changes-%i.log"> + + %d{yyyy-MM-dd HH:mm:ss.SSS} - %m{nolookups}%n + + + + + + %d{yyyy-MM-dd HH:mm:ss.SSS} - %m{nolookups}%n @@ -242,6 +217,11 @@ + + + + + diff --git a/hedera-node/configuration/mainnet/log4j2.xml b/hedera-node/configuration/mainnet/log4j2.xml index 4cf87407c7b4..6427fdf529cb 100644 --- a/hedera-node/configuration/mainnet/log4j2.xml +++ b/hedera-node/configuration/mainnet/log4j2.xml @@ -7,101 +7,76 @@ + filePattern="output/hgcaa-%i.log"> %d{yyyy-MM-dd HH:mm:ss.SSS} %-5p %-4L %c{1} - %m{nolookups}%n - - - - - - - - + + filePattern="output/queries-%i.log"> %d{yyyy-MM-dd HH:mm:ss.SSS} %-5p %-4L %c{1} - %m{nolookups}%n - - - - - - - - + + filePattern="output/swirlds-sdk-%i.log"> %d{yyyy-MM-dd HH:mm:ss.SSS} %-8sn %-5p %-16marker <%t> %c{1}: %msg{nolookups}%n - - - - - - - - + + filePattern="output/swirlds-vmap-%i.log"> %d{yyyy-MM-dd HH:mm:ss.SSS} %-8sn %-5p %-16marker <%t> %c{1}: %msg{nolookups}%n - - - - - - - - + + filePattern="output/swirlds-hashstream/swirlds-hashstream-%i.log"> %d{yyyy-MM-dd HH:mm:ss.SSS} %-8sn %-5p %-16marker <%t> %c{1}: %msg{nolookups}%n - - - - - - - - + + filePattern="output/transaction-state/state-changes-%i.log"> + + %d{yyyy-MM-dd HH:mm:ss.SSS} - %m{nolookups}%n + + + + + + %d{yyyy-MM-dd HH:mm:ss.SSS} - %m{nolookups}%n @@ -242,6 +217,12 @@ + + + + + + diff --git a/hedera-node/configuration/preprod/log4j2.xml b/hedera-node/configuration/preprod/log4j2.xml index 4cf87407c7b4..9420c13ac930 100644 --- a/hedera-node/configuration/preprod/log4j2.xml +++ b/hedera-node/configuration/preprod/log4j2.xml @@ -7,101 +7,76 @@ + filePattern="output/hgcaa-%i.log"> %d{yyyy-MM-dd HH:mm:ss.SSS} %-5p %-4L %c{1} - %m{nolookups}%n - - - - - - - - + + filePattern="output/queries-%i.log"> %d{yyyy-MM-dd HH:mm:ss.SSS} %-5p %-4L %c{1} - %m{nolookups}%n - - - - - - - - + + filePattern="output/swirlds-sdk-%i.log"> %d{yyyy-MM-dd HH:mm:ss.SSS} %-8sn %-5p %-16marker <%t> %c{1}: %msg{nolookups}%n - - - - - - - - + + filePattern="output/swirlds-vmap-%i.log"> %d{yyyy-MM-dd HH:mm:ss.SSS} %-8sn %-5p %-16marker <%t> %c{1}: %msg{nolookups}%n - - - - - - - - + + filePattern="output/swirlds-hashstream/swirlds-hashstream-%i.log"> %d{yyyy-MM-dd HH:mm:ss.SSS} %-8sn %-5p %-16marker <%t> %c{1}: %msg{nolookups}%n - - - - - - - - + + filePattern="output/transaction-state/state-changes-%d{yyyy-MM-dd--HH-mm-ss}-%i.log"> + + %d{yyyy-MM-dd HH:mm:ss.SSS} - %m{nolookups}%n + + + + + + %d{yyyy-MM-dd HH:mm:ss.SSS} - %m{nolookups}%n @@ -242,6 +217,11 @@ + + + + + diff --git a/hedera-node/configuration/previewnet/log4j2.xml b/hedera-node/configuration/previewnet/log4j2.xml index 4cf87407c7b4..065272f50525 100644 --- a/hedera-node/configuration/previewnet/log4j2.xml +++ b/hedera-node/configuration/previewnet/log4j2.xml @@ -7,101 +7,77 @@ + filePattern="output/hgcaa-%i.log"> %d{yyyy-MM-dd HH:mm:ss.SSS} %-5p %-4L %c{1} - %m{nolookups}%n - - - - - - - - + + filePattern="output/queries-%i.log"> %d{yyyy-MM-dd HH:mm:ss.SSS} %-5p %-4L %c{1} - %m{nolookups}%n - - - - - - - - + + filePattern="output/swirlds-sdk-%i.log"> %d{yyyy-MM-dd HH:mm:ss.SSS} %-8sn %-5p %-16marker <%t> %c{1}: %msg{nolookups}%n - - - - - - - - + + filePattern="output/swirlds-vmap-%i.log"> %d{yyyy-MM-dd HH:mm:ss.SSS} %-8sn %-5p %-16marker <%t> %c{1}: %msg{nolookups}%n - - - - - - - - + + filePattern="output/swirlds-hashstream/swirlds-hashstream-%i.log"> %d{yyyy-MM-dd HH:mm:ss.SSS} %-8sn %-5p %-16marker <%t> %c{1}: %msg{nolookups}%n - - - - - - - - + + fileName="output/transaction-state/state-changes.log" + filePattern="output/transaction-state/state-changes-%d{yyyy-MM-dd--HH-mm-ss}-%i.log"> + + %d{yyyy-MM-dd HH:mm:ss.SSS} - %m{nolookups}%n + + + + + + + %d{yyyy-MM-dd HH:mm:ss.SSS} - %m{nolookups}%n @@ -242,6 +218,12 @@ + + + + + + @@ -294,7 +276,7 @@ - + diff --git a/hedera-node/configuration/testnet/log4j2.xml b/hedera-node/configuration/testnet/log4j2.xml index 4cf87407c7b4..35e76b0b5c69 100644 --- a/hedera-node/configuration/testnet/log4j2.xml +++ b/hedera-node/configuration/testnet/log4j2.xml @@ -7,101 +7,77 @@ + filePattern="output/hgcaa-%i.log"> %d{yyyy-MM-dd HH:mm:ss.SSS} %-5p %-4L %c{1} - %m{nolookups}%n - - - - - - - - + + filePattern="output/queries-%i.log"> %d{yyyy-MM-dd HH:mm:ss.SSS} %-5p %-4L %c{1} - %m{nolookups}%n - - - - - - - - + + filePattern="output/swirlds-sdk-%i.log"> %d{yyyy-MM-dd HH:mm:ss.SSS} %-8sn %-5p %-16marker <%t> %c{1}: %msg{nolookups}%n - - - - - - - - + + filePattern="output/swirlds-vmap-%i.log"> %d{yyyy-MM-dd HH:mm:ss.SSS} %-8sn %-5p %-16marker <%t> %c{1}: %msg{nolookups}%n - - - - - - - - + + filePattern="output/swirlds-hashstream/swirlds-hashstream-%i.log"> %d{yyyy-MM-dd HH:mm:ss.SSS} %-8sn %-5p %-16marker <%t> %c{1}: %msg{nolookups}%n - - - - - - - - + + fileName="output/transaction-state/state-changes.log" + filePattern="output/transaction-state/state-changes-%d{yyyy-MM-dd--HH-mm-ss}-%i.log"> + + %d{yyyy-MM-dd HH:mm:ss.SSS} - %m{nolookups}%n + + + + + + + %d{yyyy-MM-dd HH:mm:ss.SSS} - %m{nolookups}%n @@ -242,6 +218,11 @@ + + + + + diff --git a/hedera-node/configuration/testnet/upgrade/throttles.json b/hedera-node/configuration/testnet/upgrade/throttles.json index d7c8fadb3fc0..0f37bf762256 100644 --- a/hedera-node/configuration/testnet/upgrade/throttles.json +++ b/hedera-node/configuration/testnet/upgrade/throttles.json @@ -61,7 +61,7 @@ }, { "opsPerSec": 0, - "milliOpsPerSec": 1250000, + "milliOpsPerSec": 125000, "operations": [ "TokenMint" ] @@ -140,14 +140,14 @@ "throttleGroups": [ { "opsPerSec": 0, - "milliOpsPerSec": 200000, + "milliOpsPerSec": 2000, "operations": [ "CryptoCreate" ] }, { "opsPerSec": 0, - "milliOpsPerSec": 50000, + "milliOpsPerSec": 5000, "operations": [ "ConsensusCreateTopic" ] @@ -179,4 +179,4 @@ ] } ] -} +} \ No newline at end of file diff --git a/hedera-node/docs/design/services/smart-contract-service/cancun-fork-support.md b/hedera-node/docs/design/services/smart-contract-service/cancun-fork-support.md index c4e73d6a8f6a..ddb829613f3a 100644 --- a/hedera-node/docs/design/services/smart-contract-service/cancun-fork-support.md +++ b/hedera-node/docs/design/services/smart-contract-service/cancun-fork-support.md @@ -5,6 +5,7 @@ The Ethereum "Cancun" hardfork introduces a number of changes to the EVM that will need to be implemented to maintain EVM Equivalence. There are four different HIPs covering this feature (tracked in epic [#11697](https://github.com/hashgraph/hedera-services/issues/11697): + * [HIP-865](https://hips.hedera.com/hip/hip-865): Add EVM Support for transient storage and memory copy Cancun opcodes (issue [#11699](https://github.com/hashgraph/hedera-services/issues/11699)) * [HIP-866](https://hips.hedera.com/hip/hip-866): Add EVM compatibility for non-supported Cancun blob features @@ -78,12 +79,14 @@ following features: ### Upgrade to latest Besu `GasCalculator` Update `CustomGasCalculator` to inherit from Besu's `CancunGasCalculator`. + * (This needs to be part of the regular EVM module upgrade - it was last updated for the London release, wasn't done for Shanghai.) ### KZG precompile initialization The KZG precompiles needs to be set up properly: + * A native library loaded * The trusted setup ([big file of community generated constants](https://github.com/ethereum/c-kzg-4844/blob/main/src/trusted_setup.txt)) loaded from a file and processed @@ -113,6 +116,7 @@ sufficient. The current Hedera override class, `CustomSelfDestructOperation`, will be updated so that it registers, with the frame, the executing contract for deletion if either: + * pre-Cancun semantics, or * post-Cancun semantics and the contract was created in the same frame * the latter information is available in the frame itself diff --git a/hedera-node/docs/design/services/smart-contract-service/evm-versioning.md b/hedera-node/docs/design/services/smart-contract-service/evm-versioning.md index 01df69154986..9da0093fc9ef 100644 --- a/hedera-node/docs/design/services/smart-contract-service/evm-versioning.md +++ b/hedera-node/docs/design/services/smart-contract-service/evm-versioning.md @@ -56,6 +56,7 @@ document which major hardfork corresponds to each internal version. | `v0.38` | [Shanghai](https://github.com/ethereum/execution-specs/blob/master/network-upgrades/mainnet-upgrades/shanghai.md) | Adds `PUSH0` opcode needed for solidity compatibility | | `v0.46` | Shanghai | Change to non-existing call behavior for EVM Equivalence | | `v0.50` | [Cancun](https://github.com/ethereum/execution-specs/blob/master/network-upgrades/mainnet-upgrades/cancun.md) | Adds opcodes `TSTORE`, `TLOAD`, and `MCOPY` opcodes, non-implementation of blobs, KZG precompile; new `SELFDESTRUCT` semantics (HIPS-865 through -868) | +| `v0.51` | Cancun | Adds Hedera Account Service System Contract at address `0x16a`. | ## Open Questions diff --git a/hedera-node/docs/design/services/token-service/airdrops/token-claim-airdrop.md b/hedera-node/docs/design/services/token-service/airdrops/token-claim-airdrop.md new file mode 100644 index 000000000000..80d7f3b3e45a --- /dev/null +++ b/hedera-node/docs/design/services/token-service/airdrops/token-claim-airdrop.md @@ -0,0 +1,165 @@ +# Introduce TokenClaimAirdrop transaction +## Purpose + +We need to add a new functionality that would make it possible for an airdrop receiver to accept a pending airdrop transfer. This would be the only way for a receiver, which hasn't been associated to a given token to accept an airdropped token that has been in pending airdrops state. + +## Goals + +1. Define new `TokenClaimAirdrop` HAPI transaction +2. Implement token claim airdrop transaction handler logic + +## Non-Goals + +- Implement token claim airdrop in system contract functions + +## Architecture + +The implementation related to the new `TokenClaimAirdrop` transaction will be gated behind a `tokens.airdrops.claim.enabled` feature flag. + +### HAPI updates + +Create new transaction type as defined in the HIP: + +```protobuf +/** + * Token claim airdrop
+ * Complete one or more pending transfers on behalf of the recipient(s) for each airdrop.
+ * The sender MUST have sufficient balance to fulfill the airdrop at the time of claim. If the + * sender does not have sufficient balance, the claim SHALL fail. + * + * Each pending airdrop successfully claimed SHALL be removed from state and SHALL NOT be available + * to claim again. + * + * Each claim SHALL be represented in the transaction body and SHALL NOT be restated + * in the record file.
+ * All claims MUST succeed for this transaction to succeed. + */ +message TokenClaimAirdropTransactionBody { + /** + * A list of one or more pending airdrop identifiers.
+ * This transaction MUST be signed by the account referenced in the `receiver_id` for + * each entry in this list. + *

+ * This list MUST contain between 1 and 10 entries, inclusive.
+ * This list MUST NOT have any duplicate entries. + */ + repeated PendingAirdropId pending_airdrops = 1; +} + +/** + * A unique, composite, identifier for a pending airdrop. + * + * Each pending airdrop SHALL be uniquely identified by a PendingAirdropId. + * A PendingAirdropId SHALL be recorded when created and MUST be provided in any transaction + * that would modify that pending airdrop (such as a `claimAirdrop` or `cancelAirdrop`). + */ +message PendingAirdropId { + /** + * A sending account.
+ * This is the account that initiated, and SHALL fund, this pending airdrop.
+ * This field is REQUIRED. + */ + AccountID sender_id = 1; + + /** + * A receiving account.
+ * This is the ID of the account that SHALL receive the airdrop.
+ * This field is REQUIRED. + */ + AccountID receiver_id = 2; + + oneof token_reference { + /** + * A token ID.
+ * This is the type of token for a fungible/common token airdrop.
+ * This field is REQUIRED for a fungible/common token and MUST NOT be used for a + * non-fungible/unique token. + */ + TokenID fungible_token_type = 3; + + /** + * The id of a single NFT, consisting of a Token ID and serial number.
+ * This is the type of token for a non-fungible/unique token airdrop.
+ * This field is REQUIRED for a non-fungible/unique token and MUST NOT be used for a + * fungible/common token. + */ + NftID non_fungible_token = 4; + } +} +``` + +Add new RPC to `TokenService` : + +```protobuf +service TokenService { + +// ... + + /** + * Claim one or more pending airdrops.
+ * This transaction MUST be signed by _each_ account *receiving* an airdrop to be claimed.
+ * If a Sender lacks sufficient balance to fulfill the airdrop at the time the claim is made, + * that claim SHALL fail. + */ + rpc claimAirdrop (Transaction) returns (TransactionResponse); +} +``` + +### Fees + +The basic `TokenClaimAirdrop` fee should be proportional to the number of airdrops being claimed in the transaction. + +An update into the `feeSchedule` file would be needed to specify that. + +### Services updates + +- Update `TokenServiceDefinition` class to include the new RPC method definition for claiming airdrops +- Implement new `TokenClaimAirdropHandler` class which should be invoked when the gRPC server handles `TokenClaimAirdrop` transactions. The class should be responsible for: + - Pure checks: validation logic based only on the transaction body itself in order to verify if the transaction is valid one + - Verify that the pending airdrops list contains between 1 and 10 entries, inclusive + - Verify that the pending airdrops list does not have any duplicate entries + - Pre-handle: + - The transaction must be signed by the account referenced by a `receiver_id` for each entry in the pending airdrops list + - Confirm that for the given pending airdrops ids in the transaction there are corresponding pending transfers existing in state + - Check if the sender has sufficient balance to fulfill the airdrop + - Handle: + - Any additional validation depending on config or state i.e. semantics checks + - The business logic for claiming pending airdrops + - We need to create a token association between each `receiver_id` and `token_reference`, future rents for token association slot should be paid by `receiver_id` + - Since we would have the signature of the receiver, even if it's an account with `receiver_sig_required=true`, the claim would implicitly work properly + - Then we should transfer the claimed tokens to each `receiver_id` + - We can dispatch synthetic crypto transfer for this, but we must skip the assessment of custom fees + - In case of a fungible token claim + - Create a synthetic `CryptoTransfer` with the corresponding `sender`, `receiver`, `token` and `amount` based on the `PendingAirdropId` and the corresponding `PendingAirdropValue` + - Then delegate it to the `CryptoTransfer.handler()` to execute the transfer + - In case of an NFT claim + - Create a synthetic `CryptoTransfer` with the corresponding `sender`, `receiver`, `token` and `serial number` based on the based on the `PendingAirdropId` + - Then delegate it to `CryptoTransfer.handler()` to execute the transfer + - Token transfers and associations should be externalized using the `tokenTransferLists` and `automatic_token_associations` fields in the transaction record + - Fees calculation + +### Zero-Balance accounts + +An account with no open auto-association slots can receive airdrops but must send a `TokenClaimAirdrop` transaction, which requires a payer. If the account has zero hbars, then it can still claim the transfer if someone else is willing to pay for that transaction. For example, a Dapp could be the payer on the transaction. Both the Dapp and the account must sign the transaction. + +### Hollow accounts + +Any existing hollow accounts that were created before [HIP-904](https://hips.hedera.com/hip/hip-904) will have no or limited number of `maxAutoAssociations` depending on if they were created with HBAR or token transfer respectively. That means an airdrop of unassociated tokens to such accounts will result in a pending transfer. +Performing `TokenClaimAirdrop` for such hollow account will also complete the account by setting its key which will obtained from the required transaction signature. Completing the hollow account should not modify the `maxAutoAssociations` on the account. That should always be an explicit step by a user. + +## Acceptance Tests + +All of the expected behaviour described below should be present only if the new `TokenClaimAirdrop` feature flag is enabled. + +- Given existing pending airdrop in state when valid `TokenClaimAirdrop` transaction containing entry for the same pending airdrop is performed then the `TokenClaimAirdrop` should succeed resulting in: + - the tokens being claimed should be automatically associated with the `receiver_id` account + - the tokens being claimed should be transferred to the `receiver_id` account + - the pending airdrop should be removed from state +- Given a successful `TokenClaimAirdrop` transaction having a hollow account as `receiver_id` should also complete the account without modifying its `maxAutoAssociations` value +- Given successful `TokenClaimAirdrop` when another `TokenClaimAirdrop` for the same airdrop is performed then the second `TokenClaimAirdrop` should fail +- `TokenClaimAirdrop` transaction with no pending airdrops entries should fail +- `TokenClaimAirdrop` transaction with more than 10 pending airdrops entries should fail +- `TokenClaimAirdrop` transaction containing duplicate entries should fail +- `TokenClaimAirdrop` transaction containing pending airdrops entries which do not exist in state should fail +- `TokenClaimAirdrop` transaction not signed by the account referenced by a `receiver_id` for each entry in the pending airdrops list should fail +- `TokenClaimAirdrop` transaction with a `sender_id` account that does not have sufficient balance of the claimed token should fail diff --git a/hedera-node/docs/design/services/token-service/airdrops/token-reject.md b/hedera-node/docs/design/services/token-service/airdrops/token-reject.md new file mode 100644 index 000000000000..03020c91bcad --- /dev/null +++ b/hedera-node/docs/design/services/token-service/airdrops/token-reject.md @@ -0,0 +1,155 @@ +# Introduce TokenReject operation + +## Purpose + +As part of [HIP-904](https://hips.hedera.com/hip/hip-904), we would like to have the option of rejecting a token that has become part of an account's balance (no matter how e.g. via regular crypto transfer or airdrop). + +This would mean transferring the token (the whole amount if fungible) or the NFT (concrete serial number from a unique token type) back to its treasury account, without assessing custom fees. Using token reject an account could get rid of potential “ransom†tokens which have very expensive custom fees and only pay for the cheaper token reject operation. + +An added advantage for token reject is that we can transfer back tokens to the treasury even if they are paused or frozen (which is not the case with regular crypto transfer). + +Token reject transaction can be performed only for tokens which are part of the persistent state of the account. No tokens from the pending state that is introduced with HIP-904 could be rejected. This remains only a right for the owner, who can cancel a pending airdrop. + +## Goals + +1. Define new `TokenReject` HAPI transaction +2. Implement token reject transaction handler logic + +## Non-Goals + +- Implement token rejection in system contract functions + +## Architecture + +The implementation related to the new `TokenReject` transaction will be gated behind a `tokens.reject.enabled` feature flag. + +### HAPI updates + +Create new transaction type as defined in the HIP: + +```protobuf +/** + * Reject undesired token(s).
+ * Transfer one or more token balances held by the requesting account to the treasury for each + * token type.
+ * Each transfer SHALL be one of the following + * - A single non-fungible/unique token. + * - The full balance held for a fungible/common token type. + * + * A single tokenReject transaction SHALL support a maximum of 10 transfers. + */ +message TokenRejectTransactionBody { + /** + * An account holding the tokens to be rejected. + */ + AccountID owner = 1; + + /** + * A list of one or more token rejections.
+ * On success each rejected token serial number or balance SHALL be transferred from + * the requesting account to the treasury account for that token type. + */ + repeated TokenReference rejections = 2; +} + +/** + * A union token identifier.
+ * Identify a fungible/common token type, or a single non-fungible/unique token serial. + */ +message TokenReference { + oneof token_identifier { + /** + * A fungible/common token type. + */ + TokenID fungible_token = 1; + + /** + * A single specific serialized non-fungible/unique token. + */ + NftID nft = 2; + } +} +``` + +Add new RPC to `TokenService` : + +```protobuf +service TokenService { + +// ... + + /** + * Reject one or more tokens.
+ * This transaction SHALL transfer the full balance of one or more tokens from the requesting + * account to the treasury for each token. This transfer SHALL NOT charge any custom fee or + * royalty defined for the token(s) to be rejected.
+ *

Effects on success

+ *
    + *
  • If the rejected token is fungible/common, the requesting account SHALL have a balance + * of 0 for the rejected token. The treasury balance SHALL increase by the amount that + * the requesting account decreased.
  • + *
  • If the rejected token is non-fungible/unique the requesting account SHALL NOT hold + * the specific serialized token that is rejected. The treasury account SHALL hold each + * specific serialized token that was rejected.
  • + * + */ + rpc rejectToken (Transaction) returns (TransactionResponse); +} +``` + +### Fees + +In essence token rejection is very special case of one-way token transfer and the fees paid for it should be the same as for basic crypto transfer without any custom fees. + +An update into the `feeSchedule` file would be needed to specify that. + +### Services updates + +- Update `TokenServiceDefinition` class to include the new RPC method definition for token rejection. +- Implement new `TokenRejectHandler` class which should be invoked when the gRPC server handles `TokenReject` transactions. The class should be responsible for: + - Pure checks: validation logic based only on the transaction body itself in order to verify if the transaction is valid one + - Pre-handle: additional validation that will that can be done using a read-only underlying state + - We should have a signature verification logic validating that we have the signature of the payer of the transaction + - We should have a signature verification logic validating that we have the signature of the sender/owner of a given token + - Does the account have a positive balance (in case of FT) of the token or is the account owner of the token (in case of NFT) they try to reject + - Handle: + - Any additional validation depending on config or state i.e. semantics checks + - The business logic for token reject + - We should not decrement `used_auto_associations` field + - We should not dissociate the token from the account after token reject is performed + - In case `receiver_sig_required` is set on a treasury account it should be ignored and the token reject should succeed + - In case the token is frozen or paused the token reject should succeed + - Update state + - Fees calculation + - Custom fees are not applied and no tokens are deducted from the sender balance + - Token dissociate fees are not applied + +### Token allowances +An owner can provide a token allowance for non-fungible and fungible tokens. The owner is the account that owns the tokens and grants the allowance to the spender. The spender is the account that spends tokens authorized by the owner from the owners account. + +If `TokenReject` is executed on the owner of a token allowance, then the allowance for that token should be canceled. + +If `TokenReject` is executed on the spender of a token allowance, then the allowance should not be affected. It's the responsibility of the owner to clean up any unnecessary allowances (or continue to pay rent for them). +That would mean that after performing `TokenReject` on a spender account, then the spender can still perform `TokenAirdrop` for a given allowance even though the token was rejected. + +## Acceptance Tests + +All of the expected behaviour described below should be present only if the new `TokenReject` feature flag is enabled. No custom fees should be assessed and the tokens should not be dissociated from the account after token reject is performed. + +- Given account with some fungible token in its balance when `TokenReject` for the same fungible token is performed then the `TokenReject` should succeed and the whole amount of the fungible token from the account should be transferred to the token treasury +- Given account with some NFT in its balance when `TokenReject` for the same NFT is performed then the `TokenReject` should succeed, the NFT should be transferred from the account to the token treasury and any other NFTs from the same collection should be left in the account +- Given account with enough fungible tokens and NFTs in its balance when `TokenReject` for up to 10 transfers of fungible or NFTs is performed then the `TokenReject` should succeed and the whole amount of the fungible tokens, the specified NFTs from the account should should be transferred to the token treasury and any other NFTs from the same collection should be left in the account +- Given account with some token in its balance and treasury account with `receiver_sig_required` enabled when `TokenReject` for the same token is performed then the `TokenReject` should succeed +- Given account with some fungible token in its balance that is frozen when `TokenReject` for the same token is performed then the `TokenReject` should succeed +- Given account with some fungible token in its balance that is paused when `TokenReject` for the same token is performed then the `TokenReject` should succeed +- Given account with some NFT in its balance that is frozen when `TokenReject` for the same NFT is performed then the `TokenReject` should succeed and any other NFTs from the same collection should be left in the account +- Given account with some NFT in its balance that is paused when `TokenReject` for the same NFT is performed then the `TokenReject` should succeed and any other NFTs from the same collection should be left in the account +- Given token allowance from owner to sender account when `TokenReject` for the same token is performed on the owner account then the token allowance should be canceled +- Given token allowance from owner to sender account when `TokenReject` for the same token is performed on the sender account then the token allowance should not be affected +- Given token allowance from owner to sender account when `TokenReject` for the same token is performed on the sender account then the sender account should be able to perform `TokenAirdrop` for the same token within the existing allowance +- Given `TokenReject` reject transaction that has payer different from the sender/owner of a given token, and the transaction has both signatures of the payer and the sender/owner account then the `TokenReject` should succeed +- Given `TokenReject` reject transaction that does not have the signature of the sender/owner of a given token then the `TokenReject` should fail +- Given account with no fungible token in its balance when `TokenReject` for the same fungible token is performed then the `TokenReject` should fail +- Given account with no NFT in its balance when `TokenReject` for the same NFT is performed then the `TokenReject` should fail +- Given account with enough fungible tokens and NFTs in its balance when `TokenReject` for more than 10 transfers of fungible or NFTs is performed then the `TokenReject` should fail +- Given treasury account with its token in balance then `TokenReject` for the same token is performed from the treasury account then the `TokenReject` should fail diff --git a/hedera-node/docs/intellij-quickstart.md b/hedera-node/docs/intellij-quickstart.md index 7c479910017f..922149e4c1d6 100644 --- a/hedera-node/docs/intellij-quickstart.md +++ b/hedera-node/docs/intellij-quickstart.md @@ -95,9 +95,7 @@ Private: 91132178e72057a1d7528025956fe39b0b847f200ab59b2fdd367017f3087137 The _test-clients/_ directory in this repo contains a large number of end-to-end tests that Hedera engineering uses to validate the behavior of Hedera Services. Many of these tests are written in the style of a BDD -specification. For example, browse to -`com.hedera.services.bdd.suites.crypto.HelloWorldSpec`, which makes some minimal -assertions about the effects of a crypto transfer. +specification. Run `HelloWorldSpec#main` with an IntelliJ configuration whose working directory is the _test-clients/_ directory of your clone of this repo: diff --git a/hedera-node/docs/system-accounts-operations.md b/hedera-node/docs/system-accounts-operations.md index 4576253d860e..513844759d7d 100644 --- a/hedera-node/docs/system-accounts-operations.md +++ b/hedera-node/docs/system-accounts-operations.md @@ -107,7 +107,9 @@ Legend of the symbols used in the tables below: | 0x3E8 → â™¾ï¸ | ✅ | ✅ - if account exists and has receiverSigRequired = false
    ✅ - if (account exists) and (account has receiverSigRequired = true) and (account is sender)
    ⌠- some failing scenarios described above | | beneficiary same as contract address | ✅ burns the eth and destructs the contract | ⌠- fails with SELF_DESTRUCT_TO_SELF | -### Call operations (CallOp, DelegateCallOp, CallCodeOp, StaticCallOp) +### Call operations that do not transfer value (CallOp, DelegateCallOp, CallCodeOp, StaticCallOp) + +_Please note that the expected behavior described in this section is valid if there is no `value` passed. If there is `value`, this falls in the next section ("Transfer and send operations")._ 1. For address 0x0: - **Ethereum:** success with no op. There is no contract on this address. It is often associated with token burn & mint/genesis events and used as a generic null address. @@ -132,9 +134,7 @@ Legend of the symbols used in the tables below: - success, if the address is a contract, and we are using the correct ABI; - success with no op, if there is no contract; - fail, if the address is a contract, and we are **not** using the correct ABI. - - **Hedera:** success with no op. - -_Please note that the expected behavior above is valid considering there is no `value` passed. If there is `value`, this falls in the next section ("Transfer and send operations")._ + - **Hedera:** success with no op. (with all gas consumed) | Address | Calls in Ethereum (ABI) | Calls in Hedera (ABI) | |------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|--------------------------------------------------------------------------------------------------------------| diff --git a/hedera-node/hapi-fees/build.gradle.kts b/hedera-node/hapi-fees/build.gradle.kts index 2213020415a7..05a8a548c047 100644 --- a/hedera-node/hapi-fees/build.gradle.kts +++ b/hedera-node/hapi-fees/build.gradle.kts @@ -14,7 +14,10 @@ * limitations under the License. */ -plugins { id("com.hedera.hashgraph.conventions") } +plugins { + id("com.hedera.gradle.services") + id("com.hedera.gradle.services-publish") +} description = "Hedera Services API Fees" diff --git a/hedera-node/hapi-utils/build.gradle.kts b/hedera-node/hapi-utils/build.gradle.kts index 5ad76de313b8..8efcebff1949 100644 --- a/hedera-node/hapi-utils/build.gradle.kts +++ b/hedera-node/hapi-utils/build.gradle.kts @@ -14,7 +14,10 @@ * limitations under the License. */ -plugins { id("com.hedera.hashgraph.conventions") } +plugins { + id("com.hedera.gradle.services") + id("com.hedera.gradle.services-publish") +} description = "Hedera Services API Utilities" diff --git a/hedera-node/hapi-utils/src/main/java/com/hedera/node/app/hapi/utils/CommonUtils.java b/hedera-node/hapi-utils/src/main/java/com/hedera/node/app/hapi/utils/CommonUtils.java index 8542f47d7684..931f7c1e377e 100644 --- a/hedera-node/hapi-utils/src/main/java/com/hedera/node/app/hapi/utils/CommonUtils.java +++ b/hedera-node/hapi-utils/src/main/java/com/hedera/node/app/hapi/utils/CommonUtils.java @@ -59,6 +59,7 @@ import static com.hederahashgraph.api.proto.java.HederaFunctionality.TokenUnfreezeAccount; import static com.hederahashgraph.api.proto.java.HederaFunctionality.TokenUnpause; import static com.hederahashgraph.api.proto.java.HederaFunctionality.TokenUpdate; +import static com.hederahashgraph.api.proto.java.HederaFunctionality.TokenUpdateNfts; import static com.hederahashgraph.api.proto.java.HederaFunctionality.UncheckedSubmit; import static com.hederahashgraph.api.proto.java.HederaFunctionality.UtilPrng; import static java.lang.System.arraycopy; @@ -200,6 +201,7 @@ public static HederaFunctionality functionOf(@NonNull final TransactionBody txn) case SCHEDULEDELETE -> ScheduleDelete; case SCHEDULESIGN -> ScheduleSign; case UTIL_PRNG -> UtilPrng; + case TOKEN_UPDATE_NFTS -> TokenUpdateNfts; default -> throw new UnknownHederaFunctionality("Unknown HederaFunctionality for " + txn); }; } diff --git a/hedera-node/hapi-utils/src/main/java/com/hedera/node/app/hapi/utils/ethereum/EthTxData.java b/hedera-node/hapi-utils/src/main/java/com/hedera/node/app/hapi/utils/ethereum/EthTxData.java index c470d759256f..ff5559bae96d 100644 --- a/hedera-node/hapi-utils/src/main/java/com/hedera/node/app/hapi/utils/ethereum/EthTxData.java +++ b/hedera-node/hapi-utils/src/main/java/com/hedera/node/app/hapi/utils/ethereum/EthTxData.java @@ -78,6 +78,7 @@ public static EthTxData populateEthTxData(byte[] data) { return switch (rlpItem.asByte()) { case 1 -> populateEip2390EthTxData(decoder.next(), data); case 2 -> populateEip1559EthTxData(decoder.next(), data); + case 3 -> null; // We don't currently support Cancun "blob" transactions default -> null; }; @@ -158,7 +159,7 @@ public byte[] encodeTx() { gasPrice, Integers.toBytes(gasLimit), to, - Integers.toBytesUnsigned(value), + value.toByteArray(), callData, v, r, @@ -171,7 +172,7 @@ public byte[] encodeTx() { gasPrice, Integers.toBytes(gasLimit), to, - Integers.toBytesUnsigned(value), + value.toByteArray(), callData, List.of(/*accessList*/ ), Integers.toBytes(recId), @@ -186,7 +187,7 @@ public byte[] encodeTx() { maxGas, Integers.toBytes(gasLimit), to, - Integers.toBytesUnsigned(value), + value.toByteArray(), callData, List.of(/*accessList*/ ), Integers.toBytes(recId), @@ -390,7 +391,7 @@ private static EthTxData populateLegacyEthTxData(RLPItem rlpItem, byte[] rawTx) null, // maxGas rlpList.get(2).asLong(), // gasLimit rlpList.get(3).data(), // to - rlpList.get(4).asBigInt(), // value + toValue(rlpList.get(4).data()), // value rlpList.get(5).data(), // callData null, // accessList recId, @@ -425,7 +426,7 @@ private static EthTxData populateEip1559EthTxData(RLPItem rlpItem, byte[] rawTx) rlpList.get(3).data(), // maxGas rlpList.get(4).asLong(), // gasLimit rlpList.get(5).data(), // to - rlpList.get(6).asBigInt(), // value + toValue(rlpList.get(6).data()), // value rlpList.get(7).data(), // callData rlpList.get(8).data(), // accessList rlpList.get(9).asByte(), // recId @@ -460,7 +461,7 @@ private static EthTxData populateEip2390EthTxData(RLPItem rlpItem, byte[] rawTx) null, // maxGas rlpList.get(3).asLong(), // gasLimit rlpList.get(4).data(), // to - rlpList.get(5).asBigInt(), // value + toValue(rlpList.get(5).data()), // value rlpList.get(6).data(), // callData rlpList.get(7).data(), // accessList rlpList.get(8).asByte(), // recId @@ -470,6 +471,12 @@ private static EthTxData populateEip2390EthTxData(RLPItem rlpItem, byte[] rawTx) ); } + // Wrapping the bytes in a BigInteger to handle when there is a negative value. If we use the RLPItem.asBigInt() + // method it doesn't handle two's complement correctly. + private static BigInteger toValue(final byte[] value) { + return value.length == 0 ? BigInteger.ZERO : new BigInteger(value); + } + // before EIP155 the value of v in // (unprotected) ethereum transactions is either 27 or 28 private static boolean isLegacyUnprotectedEtx(@NonNull BigInteger vBI) { diff --git a/hedera-node/hapi-utils/src/main/java/module-info.java b/hedera-node/hapi-utils/src/main/java/module-info.java index 4c974005c632..eb99fb9fd321 100644 --- a/hedera-node/hapi-utils/src/main/java/module-info.java +++ b/hedera-node/hapi-utils/src/main/java/module-info.java @@ -24,9 +24,9 @@ requires transitive javax.inject; requires transitive net.i2p.crypto.eddsa; requires transitive org.apache.commons.lang3; - requires com.hedera.node.app.service.evm; requires com.fasterxml.jackson.databind; requires com.google.common; + requires com.hedera.evm; requires com.sun.jna; requires com.swirlds.base; requires org.apache.commons.codec; diff --git a/hedera-node/hapi-utils/src/test/java/com/hedera/node/app/hapi/utils/ethereum/EthTxDataTest.java b/hedera-node/hapi-utils/src/test/java/com/hedera/node/app/hapi/utils/ethereum/EthTxDataTest.java index 45bcf705e0f2..fa892140ab9f 100644 --- a/hedera-node/hapi-utils/src/test/java/com/hedera/node/app/hapi/utils/ethereum/EthTxDataTest.java +++ b/hedera-node/hapi-utils/src/test/java/com/hedera/node/app/hapi/utils/ethereum/EthTxDataTest.java @@ -37,13 +37,15 @@ import java.util.List; import org.bouncycastle.util.encoders.Hex; import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.EnumSource; class EthTxDataTest { static final String SIGNATURE_ADDRESS = "a94f5374fce5edbc8e2a8697c15331677e6ebf0b"; static final String SIGNATURE_PUBKEY = "033a514176466fa815ed481ffad09110a2d344f6c9b78c1d14afc351c3a51be33d"; static final String RAW_TX_TYPE_0 = - "f864012f83018000947e3a9eaf9bcc39e2ffa38eb30bf7a93feacbc18180827653820277a0f9fbff985d374be4a55f296915002eec11ac96f1ce2df183adf992baa9390b2fa00c1e867cc960d9c74ec2e6a662b7908ec4c8cc9f3091e886bcefbeb2290fb792"; + "f864012f83018000947e3a9eaf9bcc39e2ffa38eb30bf7a93feacbc18100827653820277a0f9fbff985d374be4a55f296915002eec11ac96f1ce2df183adf992baa9390b2fa00c1e867cc960d9c74ec2e6a662b7908ec4c8cc9f3091e886bcefbeb2290fb792"; static final String RAW_TX_TYPE_0_TRIMMED_LAST_BYTES = "f864012f83018000947e3a9eaf9bcc39e2ffa38eb30bf7a93feacbc18180827653820277a0f9fbff985d374be4a55f296915002eec11ac96f1ce2df183adf992baa9390b2fa00c1e867cc960d9c74ec2e6a662b7908ec4c8cc9f3091e886bcefbeb2290000"; // { @@ -71,13 +73,16 @@ class EthTxDataTest { static final String RAW_TX_TYPE_2 = "02f87082012a022f2f83018000947e3a9eaf9bcc39e2ffa38eb30bf7a93feacbc181880de0b6b3a764000083123456c001a0df48f2efd10421811de2bfb125ab75b2d3c44139c4642837fb1fccce911fd479a01aaf7ae92bee896651dfc9d99ae422a296bf5d9f1ca49b2d96d82b79eb112d66"; + // https://etherscan.io/tx/0x2ea19986a6866b6efd2ac292fa8132b0bbf1fcc478560525ce43d6c300323652 + static final String RAW_TX_TYPE_3 = + "03f9049f01830837e4843b9aca0085213f9eed7283036a2b941c479675ad559dc151f6ec7ed3fbf8cee79582b680b8a43e5aa082000000000000000000000000000000000000000000000000000000000008fff2000000000000000000000000000000000000000000000000000000000016a443000000000000000000000000e64a54e2533fd126c2e452c5fab544d80e2e4eb5000000000000000000000000000000000000000000000000000000000aafdc87000000000000000000000000000000000000000000000000000000000aafde27f902c0f8dd941c479675ad559dc151f6ec7ed3fbf8cee79582b6f8c6a00000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000001a0000000000000000000000000000000000000000000000000000000000000000aa0b53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103a0360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbca0a10aa54071443520884ed767b0684edf43acec528b7da83ab38ce60126562660f90141948315177ab297ba92a06054ce80a67ed4dbd7ed3af90129a00000000000000000000000000000000000000000000000000000000000000006a00000000000000000000000000000000000000000000000000000000000000007a00000000000000000000000000000000000000000000000000000000000000009a0000000000000000000000000000000000000000000000000000000000000000aa0b53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103a0360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbca0a66cc928b5edb82af9bd49922954155ab7b0942694bea4ce44661d9a873fc679a0a66cc928b5edb82af9bd49922954155ab7b0942694bea4ce44661d9a873fc67aa0f652222313e28459528d920b65115c16c04f3efc82aaedc97be59f3f3792b181f89b94e64a54e2533fd126c2e452c5fab544d80e2e4eb5f884a00000000000000000000000000000000000000000000000000000000000000004a00000000000000000000000000000000000000000000000000000000000000005a0e85fd79f89ff278fc57d40aecb7947873df9f0beac531c8f71a98f630e1eab62a07686888b19bb7b75e46bb1aa328b65150743f4899443d722f0adf8e252ccda410af8c6a0014527d555d949b3afcfa246e16eb0e0aef9e9da60b7a0266f1da43b3fd8e8cfa0016d80efa350ab1fc156b505ab619bee3f6245b8f7d4d60bf11c9d8b0105b02fa00176b14180ebfaa132142ff163eb2aaf2985af7da011d195e39fe8b0faf1e960a00134da09304a6a66b691bc48d351b976203cd419778d142f19e68e904f07a5aea00181b4581a9fc316eadc58e4d6d362e316e259643913339a3e46b7c9d742ac30a00112fa6c9dfaceaff1868ef19d01c4a1da99e6e02162fe7dacf94ec441da697701a04dfd139f20fdefc834fbdce2e120ca8ed1a4688d8843df8fc2de1df8c6d0a0f3a06ec282ea1c2c55e467425c380c17f0f8ef664d8e2de8a39b18d6c83b8d6a9afa"; static final String EIP_155_DEMO_ADDRESS = "9d8a62f656a8d1615c1294fd71e9cfb3e4855a4f"; static final String EIP_155_DEMO_PUBKEY = "024bc2a31265153f07e70e0bab08724e6b85e217f8cd628ceb62974247bb493382"; static final String EIP155_DEMO = "f86c098504a817c800825208943535353535353535353535353535353535353535880de0b6b3a76400008025a028ef61340bd939bc2195fe537567866003e1a15d3c71ff63e1590620aa636276a067cbe9d8997f761aecb703304b3800ccf555c9f3dc64214b297fb1966a3b6d83"; // v = 37 - static final String EIP155_UNPROTECTED = - "f8a58085174876e800830186a08080b853604580600e600039806000f350fe7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe03601600081602082378035828234f58015156039578182fd5b8082525050506014600cf31ba02222222222222222222222222222222222222222222222222222222222222222a02222222222222222222222222222222222222222222222222222222222222222"; + static final String EIP155_NO_CHAIN_ID = + "f8a58085174876e800830186a08000b853604580600e600039806000f350fe7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe03601600081602082378035828234f58015156039578182fd5b8082525050506014600cf31ba02222222222222222222222222222222222222222222222222222222222222222a02222222222222222222222222222222222222222222222222222222222222222"; // v = 27 private static final long TINYBAR_GAS_PRICE = 100L; @@ -154,7 +159,7 @@ void extractFrontierSignature() { assertEquals( "0c1e867cc960d9c74ec2e6a662b7908ec4c8cc9f3091e886bcefbeb2290fb792", Hex.toHexString(frontierTx.s())); assertEquals( - "9ffbd69c44cf643ed8d1e756b505e545e3b5dd3a6b5ef9da1d8eca6679706594", + "30dfc91e5df4eee9640b82fa6451fc6496c9eb439d055c6809795bd4b5fcadac", Hex.toHexString(frontierTx.getEthereumHash())); final var frontierSigs = EthTxSigs.extractSignatures(frontierTx); @@ -264,8 +269,10 @@ void roundTrip155() { } @Test + // EIP-155 adds chainId in order to prevent replay attacks. This test checks if the encoding works without the + // chainId void roundTrip155UnprotectedTx() { - final var expected = Hex.decode(EIP155_UNPROTECTED); + final var expected = Hex.decode(EIP155_NO_CHAIN_ID); final var tx155 = EthTxData.populateEthTxData(expected); assertNotNull(tx155); @@ -317,7 +324,14 @@ void whiteBoxDecodingErrors() { // type 2 TX with not Type RLP Item assertNull( EthTxData.populateEthTxData(RLPEncoder.encodeSequentially(new byte[] {2}, sequentiallyEncodeOneByte))); - // Unsupported Transaciton Type + // type 3 TX (blobs) are rejected (just one test case suffices) + assertNull(EthTxData.populateEthTxData(RLPEncoder.encodeSequentially(new byte[] {3}, size_13))); + { + final var rawTx3 = Hex.decode(RAW_TX_TYPE_3); + rawTx3[1] += 1; // now total length is wrong, thus invalid RLP encoding + assertNull(EthTxData.populateEthTxData(rawTx3)); + } + // Unsupported Transaction Type assertNull(EthTxData.populateEthTxData(RLPEncoder.encodeSequentially(new byte[] {127}, size_13))); // Trimmed End Bytes assertNull(EthTxData.populateEthTxData(Hex.decode(RAW_TX_TYPE_0_TRIMMED_LAST_BYTES))); @@ -572,4 +586,50 @@ void maxGasForDeterministicDeployerIsAsExpected() { .multiply(WEIBARS_TO_TINYBARS)) == 0); } + + @ParameterizedTest + @EnumSource(EthTransactionType.class) + void negativeValueWithDifferentTypes(EthTransactionType type) { + final var negativeValue = BigInteger.valueOf(-1000); + + final var oneByte = new byte[] {1}; + final EthTxData ethTxData = new EthTxData( + oneByte, + type, + oneByte, + 1, + oneByte, + oneByte, + oneByte, + 1, + oneByte, + negativeValue, + oneByte, + null, + 1, + oneByte, + oneByte, + oneByte); + final var encoded = ethTxData.encodeTx(); + + final var populateEthTxData = EthTxData.populateEthTxData(encoded); + + assertEquals(negativeValue, populateEthTxData.value()); + } + + @ParameterizedTest + @EnumSource(EthTransactionType.class) + void bigPositiveValueWithDifferentTypes(EthTransactionType type) { + final var bigValue = BigInteger.valueOf(Long.MAX_VALUE); + + final var oneByte = new byte[] {1}; + final EthTxData ethTxData = new EthTxData( + oneByte, type, oneByte, 1, oneByte, oneByte, oneByte, 1, oneByte, bigValue, oneByte, null, 1, oneByte, + oneByte, oneByte); + final var encoded = ethTxData.encodeTx(); + + final var populateEthTxData = EthTxData.populateEthTxData(encoded); + + assertEquals(bigValue, populateEthTxData.value()); + } } diff --git a/build-logic/settings-plugins/build.gradle.kts b/hedera-node/hedera-addressbook-service/build.gradle.kts similarity index 71% rename from build-logic/settings-plugins/build.gradle.kts rename to hedera-node/hedera-addressbook-service/build.gradle.kts index 01de7ec67381..33944bba9fea 100644 --- a/build-logic/settings-plugins/build.gradle.kts +++ b/hedera-node/hedera-addressbook-service/build.gradle.kts @@ -1,5 +1,5 @@ /* - * Copyright (C) 2022-2024 Hedera Hashgraph, LLC + * Copyright (C) 2024 Hedera Hashgraph, LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,9 +14,10 @@ * limitations under the License. */ -plugins { `kotlin-dsl` } - -dependencies { - implementation("org.gradle.toolchains:foojay-resolver:0.7.0") - implementation("com.gradle:gradle-enterprise-gradle-plugin:3.15.1") +plugins { + id("com.hedera.gradle.services") + id("com.hedera.gradle.services-publish") + id("com.hedera.gradle.java-test-fixtures") } + +description = "Hedera AddressBook Service API" diff --git a/hedera-node/hedera-addressbook-service/src/main/java/com/hedera/node/app/service/addressbook/AddressBookService.java b/hedera-node/hedera-addressbook-service/src/main/java/com/hedera/node/app/service/addressbook/AddressBookService.java new file mode 100644 index 000000000000..37cd3f946933 --- /dev/null +++ b/hedera-node/hedera-addressbook-service/src/main/java/com/hedera/node/app/service/addressbook/AddressBookService.java @@ -0,0 +1,55 @@ +/* + * Copyright (C) 2024 Hedera Hashgraph, LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.hedera.node.app.service.addressbook; + +import com.hedera.node.app.spi.Service; +import com.hedera.node.app.spi.ServiceFactory; +import com.hedera.pbj.runtime.RpcServiceDefinition; +import edu.umd.cs.findbugs.annotations.NonNull; +import java.util.ServiceLoader; +import java.util.Set; + +/** + * Implements the HAPI Address Book Service. + */ +public interface AddressBookService extends Service { + + String NAME = "AddressBookService"; + + @NonNull + @Override + default String getServiceName() { + return NAME; + } + + @NonNull + @Override + default Set rpcDefinitions() { + return Set.of(AddressBookServiceDefinition.INSTANCE); + } + + /** + * Returns the concrete implementation instance of the service + * + * @return the implementation instance + */ + @NonNull + static AddressBookService getInstance() { + return ServiceFactory.loadService(AddressBookService.class, ServiceLoader.load(AddressBookService.class)); + } +} diff --git a/hedera-node/hedera-addressbook-service/src/main/java/com/hedera/node/app/service/addressbook/AddressBookServiceDefinition.java b/hedera-node/hedera-addressbook-service/src/main/java/com/hedera/node/app/service/addressbook/AddressBookServiceDefinition.java new file mode 100644 index 000000000000..2c7e6b245d42 --- /dev/null +++ b/hedera-node/hedera-addressbook-service/src/main/java/com/hedera/node/app/service/addressbook/AddressBookServiceDefinition.java @@ -0,0 +1,78 @@ +/* + * Copyright (C) 2024 Hedera Hashgraph, LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.hedera.node.app.service.addressbook; + +import com.hedera.hapi.node.base.Transaction; +import com.hedera.hapi.node.transaction.Query; +import com.hedera.hapi.node.transaction.Response; +import com.hedera.hapi.node.transaction.TransactionResponse; +import com.hedera.pbj.runtime.RpcMethodDefinition; +import com.hedera.pbj.runtime.RpcServiceDefinition; +import edu.umd.cs.findbugs.annotations.NonNull; +import java.util.Set; + +/** + * The Addressbook Service provides the ability for Hedera Hashgraph to provide facilitate changes to the nodes used across the Hedera network. + */ +@SuppressWarnings("java:S6548") +public final class AddressBookServiceDefinition implements RpcServiceDefinition { + public static final AddressBookServiceDefinition INSTANCE = new AddressBookServiceDefinition(); + + private static final Set> methods = Set.of( + + // Prepare to add a new node to the network. + // When a valid council member initiates a HAPI transaction to add a new node, + // then the network should acknowledge the transaction and update the network’s Address Book within 24 + // hours. + // The added node will not be active until the network is upgradede + // Request is [NodeCreateTransactionBody](#proto.NodeCreateTransactionBody) + // + new RpcMethodDefinition<>("createNode", Transaction.class, TransactionResponse.class), + // Prepare to update the node to the network. + // The node will not be updated until the network is upgraded. + // Request is [NodeUpdateTransactionBody](#proto.NodeUpdateTransactionBody) + // + new RpcMethodDefinition<>("updateNode", Transaction.class, TransactionResponse.class), + // Prepare to delete the node to the network. + // The deleted node will not be deleted until the network is upgraded. + // Such a deleted node can never be reused. + // Request is [NodeDeleteTransactionBody](#proto.NodeDeleteTransactionBody) + // + new RpcMethodDefinition<>("deleteNode", Transaction.class, TransactionResponse.class), + + // Retrieves the node information by node Id. + // Request is [NodeGetInfoQuery](#proto.NodeGetInfoQuery) + // Response is [NodeGetInfoResponse](#proto.NodeGetInfoResponse) + // + new RpcMethodDefinition<>("getNodeInfo", Query.class, Response.class)); + + private AddressBookServiceDefinition() { + // Forbid instantiation + } + + @Override + @NonNull + public String basePath() { + return "proto.AddressbookService"; + } + + @Override + @NonNull + public Set> methods() { + return methods; + } +} diff --git a/hedera-node/hedera-addressbook-service/src/main/java/com/hedera/node/app/service/addressbook/ReadableNodeStore.java b/hedera-node/hedera-addressbook-service/src/main/java/com/hedera/node/app/service/addressbook/ReadableNodeStore.java new file mode 100644 index 000000000000..94417a1f1707 --- /dev/null +++ b/hedera-node/hedera-addressbook-service/src/main/java/com/hedera/node/app/service/addressbook/ReadableNodeStore.java @@ -0,0 +1,54 @@ +/* + * Copyright (C) 2024 Hedera Hashgraph, LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.hedera.node.app.service.addressbook; + +import com.hedera.hapi.node.state.addressbook.Node; +import edu.umd.cs.findbugs.annotations.Nullable; + +/** + * Provides read-only methods for interacting with the underlying data storage mechanisms for + * working with Nodes. + * + *

    This class is not exported from the module. It is an internal implementation detail. + */ +public interface ReadableNodeStore { + + /** + * Returns the node needed. If the node doesn't exist returns failureReason. If the + * node exists , the failure reason will be null. + * + * @param nodeId node id being looked up + * @return node's metadata + */ + @Nullable + Node get(final long nodeId); + + /** + * Returns the number of nodes in the state. + * @return the number of nodes in the state. + */ + long sizeOfState(); + + /** + * Warms the system by preloading a node into memory + * + *

    The default implementation is empty because preloading data into memory is only used for some implementations. + * + * @param nodeId the node id + */ + default void warm(final long nodeId) {} +} diff --git a/hedera-node/hedera-addressbook-service/src/main/java/com/hedera/node/app/service/addressbook/package-info.java b/hedera-node/hedera-addressbook-service/src/main/java/com/hedera/node/app/service/addressbook/package-info.java new file mode 100644 index 000000000000..c4f909c3cf9b --- /dev/null +++ b/hedera-node/hedera-addressbook-service/src/main/java/com/hedera/node/app/service/addressbook/package-info.java @@ -0,0 +1,5 @@ +/** + * This package is the base package of the address book service API. This module must not contain any + * sources outside this base package + */ +package com.hedera.node.app.service.addressbook; diff --git a/hedera-node/hedera-addressbook-service/src/main/java/module-info.java b/hedera-node/hedera-addressbook-service/src/main/java/module-info.java new file mode 100644 index 000000000000..0d3591ace542 --- /dev/null +++ b/hedera-node/hedera-addressbook-service/src/main/java/module-info.java @@ -0,0 +1,10 @@ +module com.hedera.node.app.service.addressbook { + exports com.hedera.node.app.service.addressbook; + + uses com.hedera.node.app.service.addressbook.AddressBookService; + + requires transitive com.hedera.node.app.spi; + requires transitive com.hedera.node.hapi; + requires transitive com.hedera.pbj.runtime; + requires static com.github.spotbugs.annotations; +} diff --git a/hedera-node/hedera-app-spi/build.gradle.kts b/hedera-node/hedera-app-spi/build.gradle.kts index 6509307a50d7..ceba8d43b6be 100644 --- a/hedera-node/hedera-app-spi/build.gradle.kts +++ b/hedera-node/hedera-app-spi/build.gradle.kts @@ -15,14 +15,16 @@ */ plugins { - id("com.hedera.hashgraph.conventions") - id("com.hedera.hashgraph.java-test-fixtures") + id("com.hedera.gradle.services") + id("com.hedera.gradle.services-publish") + id("com.hedera.gradle.java-test-fixtures") } description = "Hedera Application - SPI" testModuleInfo { requires("com.hedera.node.app.spi") + requires("com.swirlds.platform.core.test.fixtures") requires("org.apache.commons.lang3") requires("org.assertj.core") requires("org.junit.jupiter.api") diff --git a/hedera-node/hedera-app-spi/src/main/java/com/hedera/node/app/spi/HapiUtils.java b/hedera-node/hedera-app-spi/src/main/java/com/hedera/node/app/spi/HapiUtils.java index d9df43092225..d22341eb9746 100644 --- a/hedera-node/hedera-app-spi/src/main/java/com/hedera/node/app/spi/HapiUtils.java +++ b/hedera-node/hedera-app-spi/src/main/java/com/hedera/node/app/spi/HapiUtils.java @@ -187,7 +187,8 @@ public static int countOfCryptographicKeys(@NonNull final Key key) { HederaFunctionality.TOKEN_GET_NFT_INFOS, HederaFunctionality.TOKEN_GET_ACCOUNT_NFT_INFOS, HederaFunctionality.NETWORK_GET_EXECUTION_TIME, - HederaFunctionality.GET_ACCOUNT_DETAILS); + HederaFunctionality.GET_ACCOUNT_DETAILS, + HederaFunctionality.NODE_GET_INFO); public static HederaFunctionality functionOf(final TransactionBody txn) throws UnknownHederaFunctionality { return switch (txn.data().kind()) { @@ -237,6 +238,9 @@ public static HederaFunctionality functionOf(final TransactionBody txn) throws U case TOKEN_WIPE -> HederaFunctionality.TOKEN_ACCOUNT_WIPE; case UTIL_PRNG -> HederaFunctionality.UTIL_PRNG; case UNCHECKED_SUBMIT -> HederaFunctionality.UNCHECKED_SUBMIT; + case NODE_CREATE -> HederaFunctionality.NODE_CREATE; + case NODE_UPDATE -> HederaFunctionality.NODE_UPDATE; + case NODE_DELETE -> HederaFunctionality.NODE_DELETE; case UNSET -> throw new UnknownHederaFunctionality(); }; } @@ -268,6 +272,7 @@ public static HederaFunctionality functionOf(final Query txn) throws UnknownHede case TRANSACTION_GET_RECEIPT -> HederaFunctionality.TRANSACTION_GET_RECEIPT; case TRANSACTION_GET_RECORD -> HederaFunctionality.TRANSACTION_GET_RECORD; case TRANSACTION_GET_FAST_RECORD -> HederaFunctionality.TRANSACTION_GET_FAST_RECORD; + case NODE_GET_INFO -> HederaFunctionality.NODE_GET_INFO; case UNSET -> throw new UnknownHederaFunctionality(); }; } diff --git a/hedera-node/hedera-app-spi/src/main/java/com/hedera/node/app/spi/api/ServiceApiProvider.java b/hedera-node/hedera-app-spi/src/main/java/com/hedera/node/app/spi/api/ServiceApiProvider.java index eabaf060aaca..851e210093b8 100644 --- a/hedera-node/hedera-app-spi/src/main/java/com/hedera/node/app/spi/api/ServiceApiProvider.java +++ b/hedera-node/hedera-app-spi/src/main/java/com/hedera/node/app/spi/api/ServiceApiProvider.java @@ -17,8 +17,8 @@ package com.hedera.node.app.spi.api; import com.hedera.node.app.spi.metrics.StoreMetricsService; -import com.hedera.node.app.spi.state.WritableStates; import com.swirlds.config.api.Configuration; +import com.swirlds.state.spi.WritableStates; import edu.umd.cs.findbugs.annotations.NonNull; /** diff --git a/hedera-node/hedera-app-spi/src/main/java/com/hedera/node/app/spi/fees/Fees.java b/hedera-node/hedera-app-spi/src/main/java/com/hedera/node/app/spi/fees/Fees.java index 7edc89ec414c..24133de95f1f 100644 --- a/hedera-node/hedera-app-spi/src/main/java/com/hedera/node/app/spi/fees/Fees.java +++ b/hedera-node/hedera-app-spi/src/main/java/com/hedera/node/app/spi/fees/Fees.java @@ -16,6 +16,9 @@ package com.hedera.node.app.spi.fees; +import com.hederahashgraph.api.proto.java.FeeComponents; +import com.hederahashgraph.api.proto.java.FeeData; + /** * Represents the combination of node, network, and service fees. * @@ -36,6 +39,15 @@ public record Fees(long nodeFee, long networkFee, long serviceFee) { /** A constant representing zero fees. */ public static final Fees FREE = new Fees(0, 0, 0); + /** + * A constant representing fees of 1 constant resource usage for each of the node, network, and service components. + * This is useful when a fee is required, but the entity is not present in state to determine the actual fee. + */ + public static final FeeData CONSTANT_FEE_DATA = FeeData.newBuilder() + .setNodedata(FeeComponents.newBuilder().setConstant(1).build()) + .setNetworkdata(FeeComponents.newBuilder().setConstant(1).build()) + .setServicedata(FeeComponents.newBuilder().setConstant(1).build()) + .build(); public Fees { // Validate the fee components are never negative. diff --git a/hedera-node/hedera-app-spi/src/main/java/com/hedera/node/app/spi/key/KeyUtils.java b/hedera-node/hedera-app-spi/src/main/java/com/hedera/node/app/spi/key/KeyUtils.java index b5ce039dab7a..ced465753a72 100644 --- a/hedera-node/hedera-app-spi/src/main/java/com/hedera/node/app/spi/key/KeyUtils.java +++ b/hedera-node/hedera-app-spi/src/main/java/com/hedera/node/app/spi/key/KeyUtils.java @@ -33,6 +33,7 @@ public class KeyUtils { public static final Key IMMUTABILITY_SENTINEL_KEY = Key.newBuilder().keyList(KeyList.DEFAULT).build(); + public static final int EVM_ADDRESS_BYTE_LENGTH = 20; public static final int ED25519_BYTE_LENGTH = 32; private static final byte ODD_PARITY = (byte) 0x03; @@ -43,10 +44,6 @@ private KeyUtils() { throw new UnsupportedOperationException("Utility Class"); } - public static boolean isEmptyAndNotImmutable(@Nullable final Key pbjKey) { - return isEmptyInternal(pbjKey, true); - } - /** * Checks if the given key is empty. * For a KeyList type checks if the list is empty. diff --git a/hedera-node/hedera-app-spi/src/main/java/com/hedera/node/app/spi/metrics/StoreMetricsService.java b/hedera-node/hedera-app-spi/src/main/java/com/hedera/node/app/spi/metrics/StoreMetricsService.java index 1053b79a32f0..4e1b546733fb 100644 --- a/hedera-node/hedera-app-spi/src/main/java/com/hedera/node/app/spi/metrics/StoreMetricsService.java +++ b/hedera-node/hedera-app-spi/src/main/java/com/hedera/node/app/spi/metrics/StoreMetricsService.java @@ -16,6 +16,7 @@ package com.hedera.node.app.spi.metrics; +import com.swirlds.state.spi.metrics.StoreMetrics; import edu.umd.cs.findbugs.annotations.NonNull; public interface StoreMetricsService { diff --git a/hedera-node/hedera-app-spi/src/main/java/com/hedera/node/app/spi/state/CommittableWritableStates.java b/hedera-node/hedera-app-spi/src/main/java/com/hedera/node/app/spi/state/CommittableWritableStates.java index c8c685529eec..6949b53a661a 100644 --- a/hedera-node/hedera-app-spi/src/main/java/com/hedera/node/app/spi/state/CommittableWritableStates.java +++ b/hedera-node/hedera-app-spi/src/main/java/com/hedera/node/app/spi/state/CommittableWritableStates.java @@ -17,6 +17,7 @@ package com.hedera.node.app.spi.state; import com.hedera.node.app.spi.workflows.HandleContext.SavepointStack; +import com.swirlds.state.spi.WritableStates; /** * A {@link WritableStates} implementation at the "bottom" of a {@link SavepointStack}; i.e., an diff --git a/hedera-node/hedera-app-spi/src/main/java/com/hedera/node/app/spi/state/EmptyReadableStates.java b/hedera-node/hedera-app-spi/src/main/java/com/hedera/node/app/spi/state/EmptyReadableStates.java index eee5f926d4a6..d2b8f6c039f2 100644 --- a/hedera-node/hedera-app-spi/src/main/java/com/hedera/node/app/spi/state/EmptyReadableStates.java +++ b/hedera-node/hedera-app-spi/src/main/java/com/hedera/node/app/spi/state/EmptyReadableStates.java @@ -16,6 +16,10 @@ package com.hedera.node.app.spi.state; +import com.swirlds.state.spi.ReadableKVState; +import com.swirlds.state.spi.ReadableQueueState; +import com.swirlds.state.spi.ReadableSingletonState; +import com.swirlds.state.spi.ReadableStates; import edu.umd.cs.findbugs.annotations.NonNull; import java.util.Collections; import java.util.Objects; diff --git a/hedera-node/hedera-app-spi/src/main/java/com/hedera/node/app/spi/state/EmptyWritableStates.java b/hedera-node/hedera-app-spi/src/main/java/com/hedera/node/app/spi/state/EmptyWritableStates.java index bd7ad2122a09..a069e0becde9 100644 --- a/hedera-node/hedera-app-spi/src/main/java/com/hedera/node/app/spi/state/EmptyWritableStates.java +++ b/hedera-node/hedera-app-spi/src/main/java/com/hedera/node/app/spi/state/EmptyWritableStates.java @@ -16,6 +16,10 @@ package com.hedera.node.app.spi.state; +import com.swirlds.state.spi.WritableKVState; +import com.swirlds.state.spi.WritableQueueState; +import com.swirlds.state.spi.WritableSingletonState; +import com.swirlds.state.spi.WritableStates; import edu.umd.cs.findbugs.annotations.NonNull; import java.util.Collections; import java.util.Objects; diff --git a/hedera-node/hedera-app-spi/src/main/java/com/hedera/node/app/spi/state/FilteredReadableStates.java b/hedera-node/hedera-app-spi/src/main/java/com/hedera/node/app/spi/state/FilteredReadableStates.java index 76c56f8153c5..564d20d585df 100644 --- a/hedera-node/hedera-app-spi/src/main/java/com/hedera/node/app/spi/state/FilteredReadableStates.java +++ b/hedera-node/hedera-app-spi/src/main/java/com/hedera/node/app/spi/state/FilteredReadableStates.java @@ -16,6 +16,10 @@ package com.hedera.node.app.spi.state; +import com.swirlds.state.spi.ReadableKVState; +import com.swirlds.state.spi.ReadableQueueState; +import com.swirlds.state.spi.ReadableSingletonState; +import com.swirlds.state.spi.ReadableStates; import edu.umd.cs.findbugs.annotations.NonNull; import java.util.Objects; import java.util.Set; diff --git a/hedera-node/hedera-app-spi/src/main/java/com/hedera/node/app/spi/state/FilteredWritableStates.java b/hedera-node/hedera-app-spi/src/main/java/com/hedera/node/app/spi/state/FilteredWritableStates.java index bc129c118ead..a732cc2993ff 100644 --- a/hedera-node/hedera-app-spi/src/main/java/com/hedera/node/app/spi/state/FilteredWritableStates.java +++ b/hedera-node/hedera-app-spi/src/main/java/com/hedera/node/app/spi/state/FilteredWritableStates.java @@ -16,6 +16,10 @@ package com.hedera.node.app.spi.state; +import com.swirlds.state.spi.WritableKVState; +import com.swirlds.state.spi.WritableQueueState; +import com.swirlds.state.spi.WritableSingletonState; +import com.swirlds.state.spi.WritableStates; import edu.umd.cs.findbugs.annotations.NonNull; import java.util.Objects; import java.util.Set; diff --git a/hedera-node/hedera-app-spi/src/main/java/com/hedera/node/app/spi/state/MigrationContext.java b/hedera-node/hedera-app-spi/src/main/java/com/hedera/node/app/spi/state/MigrationContext.java index 9fc02bcca894..01517dc5a3d5 100644 --- a/hedera-node/hedera-app-spi/src/main/java/com/hedera/node/app/spi/state/MigrationContext.java +++ b/hedera-node/hedera-app-spi/src/main/java/com/hedera/node/app/spi/state/MigrationContext.java @@ -20,6 +20,8 @@ import com.hedera.node.app.spi.info.NetworkInfo; import com.hedera.node.app.spi.workflows.record.GenesisRecordsBuilder; import com.swirlds.config.api.Configuration; +import com.swirlds.state.spi.ReadableStates; +import com.swirlds.state.spi.WritableStates; import edu.umd.cs.findbugs.annotations.NonNull; import edu.umd.cs.findbugs.annotations.Nullable; diff --git a/hedera-node/hedera-app-spi/src/main/java/com/hedera/node/app/spi/state/Schema.java b/hedera-node/hedera-app-spi/src/main/java/com/hedera/node/app/spi/state/Schema.java index a0b5f9db2bd1..59237d985f03 100644 --- a/hedera-node/hedera-app-spi/src/main/java/com/hedera/node/app/spi/state/Schema.java +++ b/hedera-node/hedera-app-spi/src/main/java/com/hedera/node/app/spi/state/Schema.java @@ -20,6 +20,8 @@ import com.hedera.hapi.node.base.SemanticVersion; import com.hedera.pbj.runtime.Codec; +import com.swirlds.state.spi.ReadableKVState; +import com.swirlds.state.spi.ReadableStates; import edu.umd.cs.findbugs.annotations.NonNull; import java.util.Collections; import java.util.Objects; diff --git a/hedera-node/hedera-app-spi/src/main/java/com/hedera/node/app/spi/state/SchemaRegistry.java b/hedera-node/hedera-app-spi/src/main/java/com/hedera/node/app/spi/state/SchemaRegistry.java index e6a215ba4bd7..3d179453a783 100644 --- a/hedera-node/hedera-app-spi/src/main/java/com/hedera/node/app/spi/state/SchemaRegistry.java +++ b/hedera-node/hedera-app-spi/src/main/java/com/hedera/node/app/spi/state/SchemaRegistry.java @@ -25,14 +25,6 @@ * {@link Service} to register all of its {@link Schema}s. */ public interface SchemaRegistry { - /** - * Register the given {@link Schema}. {@link Schema}s do not need to be registered in order. - * - * @param schema The {@link Schema} to register. - * @return a reference to this registry instance - */ - SchemaRegistry register(@NonNull Schema schema); - /** * Register the given array of {@link Schema}s. * @@ -47,6 +39,14 @@ default SchemaRegistry registerAll(@NonNull Schema... schemas) { return this; } + /** + * Register the given {@link Schema}. {@link Schema}s do not need to be registered in order. + * + * @param schema The {@link Schema} to register. + * @return a reference to this registry instance + */ + SchemaRegistry register(@NonNull Schema schema); + /** * Register the given array of {@link Schema}s. * diff --git a/hedera-node/hedera-app-spi/src/main/java/com/hedera/node/app/spi/state/StateDefinition.java b/hedera-node/hedera-app-spi/src/main/java/com/hedera/node/app/spi/state/StateDefinition.java index 14cf7d66211f..dafeaad88f2e 100644 --- a/hedera-node/hedera-app-spi/src/main/java/com/hedera/node/app/spi/state/StateDefinition.java +++ b/hedera-node/hedera-app-spi/src/main/java/com/hedera/node/app/spi/state/StateDefinition.java @@ -17,6 +17,7 @@ package com.hedera.node.app.spi.state; import com.hedera.pbj.runtime.Codec; +import com.swirlds.state.spi.ReadableKVState; import edu.umd.cs.findbugs.annotations.NonNull; import edu.umd.cs.findbugs.annotations.Nullable; diff --git a/hedera-node/hedera-app-spi/src/main/java/com/hedera/node/app/spi/state/WrappedReadableKVState.java b/hedera-node/hedera-app-spi/src/main/java/com/hedera/node/app/spi/state/WrappedReadableKVState.java index 5858fbdc6b15..ece01a21d87a 100644 --- a/hedera-node/hedera-app-spi/src/main/java/com/hedera/node/app/spi/state/WrappedReadableKVState.java +++ b/hedera-node/hedera-app-spi/src/main/java/com/hedera/node/app/spi/state/WrappedReadableKVState.java @@ -16,6 +16,8 @@ package com.hedera.node.app.spi.state; +import com.swirlds.platform.state.spi.ReadableKVStateBase; +import com.swirlds.state.spi.ReadableKVState; import edu.umd.cs.findbugs.annotations.NonNull; import java.util.Iterator; diff --git a/hedera-node/hedera-app-spi/src/main/java/com/hedera/node/app/spi/state/WrappedWritableKVState.java b/hedera-node/hedera-app-spi/src/main/java/com/hedera/node/app/spi/state/WrappedWritableKVState.java index 04a6f9d2bcdc..6d6f3c8258a2 100644 --- a/hedera-node/hedera-app-spi/src/main/java/com/hedera/node/app/spi/state/WrappedWritableKVState.java +++ b/hedera-node/hedera-app-spi/src/main/java/com/hedera/node/app/spi/state/WrappedWritableKVState.java @@ -16,7 +16,9 @@ package com.hedera.node.app.spi.state; -import com.hedera.node.app.spi.metrics.StoreMetrics; +import com.swirlds.platform.state.spi.WritableKVStateBase; +import com.swirlds.state.spi.WritableKVState; +import com.swirlds.state.spi.metrics.StoreMetrics; import edu.umd.cs.findbugs.annotations.NonNull; import java.util.Iterator; import java.util.Objects; diff --git a/hedera-node/hedera-app-spi/src/main/java/com/hedera/node/app/spi/state/WrappedWritableQueueState.java b/hedera-node/hedera-app-spi/src/main/java/com/hedera/node/app/spi/state/WrappedWritableQueueState.java index ff7f5de4cbec..0a53d6ccff7b 100644 --- a/hedera-node/hedera-app-spi/src/main/java/com/hedera/node/app/spi/state/WrappedWritableQueueState.java +++ b/hedera-node/hedera-app-spi/src/main/java/com/hedera/node/app/spi/state/WrappedWritableQueueState.java @@ -16,6 +16,8 @@ package com.hedera.node.app.spi.state; +import com.swirlds.platform.state.spi.WritableQueueStateBase; +import com.swirlds.state.spi.WritableQueueState; import edu.umd.cs.findbugs.annotations.NonNull; import java.util.Iterator; diff --git a/hedera-node/hedera-app-spi/src/main/java/com/hedera/node/app/spi/state/WrappedWritableSingletonState.java b/hedera-node/hedera-app-spi/src/main/java/com/hedera/node/app/spi/state/WrappedWritableSingletonState.java index 74f70b7e7a5a..4aa0036328b5 100644 --- a/hedera-node/hedera-app-spi/src/main/java/com/hedera/node/app/spi/state/WrappedWritableSingletonState.java +++ b/hedera-node/hedera-app-spi/src/main/java/com/hedera/node/app/spi/state/WrappedWritableSingletonState.java @@ -16,6 +16,8 @@ package com.hedera.node.app.spi.state; +import com.swirlds.platform.state.spi.WritableSingletonStateBase; +import com.swirlds.state.spi.WritableSingletonState; import edu.umd.cs.findbugs.annotations.NonNull; /** diff --git a/hedera-node/hedera-app-spi/src/main/java/com/hedera/node/app/spi/workflows/HandleContext.java b/hedera-node/hedera-app-spi/src/main/java/com/hedera/node/app/spi/workflows/HandleContext.java index 2795101ec55f..139c22eb54c5 100644 --- a/hedera-node/hedera-app-spi/src/main/java/com/hedera/node/app/spi/workflows/HandleContext.java +++ b/hedera-node/hedera-app-spi/src/main/java/com/hedera/node/app/spi/workflows/HandleContext.java @@ -270,7 +270,7 @@ TransactionKeys allKeysForTransaction(@NonNull TransactionBody nestedTxn, @NonNu /** * Checks whether the payer of the current transaction refers to a superuser. * - * @return {@code true} if the payer is a superuser, otherwise {@code false + * @return {@code true} if the payer is a superuser, otherwise {@code false} */ boolean isSuperUser(); diff --git a/hedera-node/hedera-app-spi/src/main/java/module-info.java b/hedera-node/hedera-app-spi/src/main/java/module-info.java index a163e88e4ce2..9bed5c5ba71c 100644 --- a/hedera-node/hedera-app-spi/src/main/java/module-info.java +++ b/hedera-node/hedera-app-spi/src/main/java/module-info.java @@ -3,13 +3,14 @@ requires transitive com.hedera.node.hapi; requires transitive com.hedera.pbj.runtime; requires transitive com.swirlds.config.api; + requires transitive com.swirlds.platform.core; + requires transitive com.swirlds.state.api; requires static com.github.spotbugs.annotations; exports com.hedera.node.app.spi; exports com.hedera.node.app.spi.fees; exports com.hedera.node.app.spi.api; exports com.hedera.node.app.spi.info; - exports com.hedera.node.app.spi.state; exports com.hedera.node.app.spi.key; exports com.hedera.node.app.spi.numbers; exports com.hedera.node.app.spi.workflows; @@ -18,5 +19,6 @@ exports com.hedera.node.app.spi.validation; exports com.hedera.node.app.spi.workflows.record; exports com.hedera.node.app.spi.authorization; + exports com.hedera.node.app.spi.state; exports com.hedera.node.app.spi.metrics; } diff --git a/hedera-node/hedera-app-spi/src/test/java/com/hedera/node/app/spi/HapiUtilsTest.java b/hedera-node/hedera-app-spi/src/test/java/com/hedera/node/app/spi/HapiUtilsTest.java index c85d9983c6d3..8a2c197ce6ca 100644 --- a/hedera-node/hedera-app-spi/src/test/java/com/hedera/node/app/spi/HapiUtilsTest.java +++ b/hedera-node/hedera-app-spi/src/test/java/com/hedera/node/app/spi/HapiUtilsTest.java @@ -25,8 +25,8 @@ import com.hedera.hapi.node.base.KeyList; import com.hedera.hapi.node.base.ThresholdKey; import com.hedera.hapi.node.base.Timestamp; -import com.hedera.node.app.spi.fixtures.TestBase; import com.hedera.pbj.runtime.io.buffer.Bytes; +import com.swirlds.platform.test.fixtures.state.TestBase; import edu.umd.cs.findbugs.annotations.NonNull; import java.time.Instant; import java.util.stream.Stream; diff --git a/hedera-node/hedera-app-spi/src/test/java/com/hedera/node/app/spi/state/EmptyReadableStatesTest.java b/hedera-node/hedera-app-spi/src/test/java/com/hedera/node/app/spi/state/EmptyReadableStatesTest.java index 7a9d5e31417f..144359de7358 100644 --- a/hedera-node/hedera-app-spi/src/test/java/com/hedera/node/app/spi/state/EmptyReadableStatesTest.java +++ b/hedera-node/hedera-app-spi/src/test/java/com/hedera/node/app/spi/state/EmptyReadableStatesTest.java @@ -19,6 +19,7 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.AssertionsForClassTypes.assertThatThrownBy; +import com.swirlds.platform.test.fixtures.state.StateTestBase; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; diff --git a/hedera-node/hedera-app-spi/src/test/java/com/hedera/node/app/spi/state/EmptyWritableStatesTest.java b/hedera-node/hedera-app-spi/src/test/java/com/hedera/node/app/spi/state/EmptyWritableStatesTest.java index 809b42775fbc..88dfd4f9908d 100644 --- a/hedera-node/hedera-app-spi/src/test/java/com/hedera/node/app/spi/state/EmptyWritableStatesTest.java +++ b/hedera-node/hedera-app-spi/src/test/java/com/hedera/node/app/spi/state/EmptyWritableStatesTest.java @@ -19,6 +19,7 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.AssertionsForClassTypes.assertThatThrownBy; +import com.swirlds.platform.test.fixtures.state.StateTestBase; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; diff --git a/hedera-node/hedera-app-spi/src/test/java/com/hedera/node/app/spi/state/FilteredReadableStatesTest.java b/hedera-node/hedera-app-spi/src/test/java/com/hedera/node/app/spi/state/FilteredReadableStatesTest.java index f56986fcc54e..01b105184876 100644 --- a/hedera-node/hedera-app-spi/src/test/java/com/hedera/node/app/spi/state/FilteredReadableStatesTest.java +++ b/hedera-node/hedera-app-spi/src/test/java/com/hedera/node/app/spi/state/FilteredReadableStatesTest.java @@ -19,7 +19,10 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.AssertionsForClassTypes.assertThatThrownBy; -import com.hedera.node.app.spi.fixtures.state.MapReadableStates; +import com.swirlds.platform.test.fixtures.state.MapReadableStates; +import com.swirlds.platform.test.fixtures.state.StateTestBase; +import com.swirlds.state.spi.ReadableStates; +import edu.umd.cs.findbugs.annotations.NonNull; import java.util.Collections; import java.util.HashSet; import java.util.Set; @@ -136,6 +139,17 @@ void nonNullSingletonKey() { void nonNullQueueKey() { assertThatThrownBy(() -> states.getQueue(UNKNOWN_KEY)).isInstanceOf(IllegalArgumentException.class); } + + @NonNull + protected MapReadableStates allReadableStates() { + return MapReadableStates.builder() + .state(readableFruitState()) + .state(readableCountryState()) + .state(readableAnimalState()) + .state(readableSTEAMState()) + .state(readableSpaceState()) + .build(); + } } @Nested diff --git a/hedera-node/hedera-app-spi/src/test/java/com/hedera/node/app/spi/state/FilteredWritableStatesTest.java b/hedera-node/hedera-app-spi/src/test/java/com/hedera/node/app/spi/state/FilteredWritableStatesTest.java index 1ccae745ae3f..6497ee0375d8 100644 --- a/hedera-node/hedera-app-spi/src/test/java/com/hedera/node/app/spi/state/FilteredWritableStatesTest.java +++ b/hedera-node/hedera-app-spi/src/test/java/com/hedera/node/app/spi/state/FilteredWritableStatesTest.java @@ -20,6 +20,9 @@ import static org.assertj.core.api.AssertionsForClassTypes.assertThatThrownBy; import com.hedera.node.app.spi.fixtures.state.MapWritableStates; +import com.swirlds.platform.test.fixtures.state.StateTestBase; +import com.swirlds.state.spi.WritableStates; +import edu.umd.cs.findbugs.annotations.NonNull; import java.util.Collections; import java.util.HashSet; import java.util.Set; @@ -124,6 +127,17 @@ void nonNullKey() { void nonNullSingletonKey() { assertThatThrownBy(() -> states.getSingleton(UNKNOWN_KEY)).isInstanceOf(IllegalArgumentException.class); } + + @NonNull + protected MapWritableStates allWritableStates() { + return MapWritableStates.builder() + .state(writableAnimalState()) + .state(writableCountryState()) + .state(writableAnimalState()) + .state(writableSTEAMState()) + .state(writableSpaceState()) + .build(); + } } @Nested diff --git a/hedera-node/hedera-app-spi/src/test/java/com/hedera/node/app/spi/state/SchemaTest.java b/hedera-node/hedera-app-spi/src/test/java/com/hedera/node/app/spi/state/SchemaTest.java index 522fd31b3753..1918786d15ca 100644 --- a/hedera-node/hedera-app-spi/src/test/java/com/hedera/node/app/spi/state/SchemaTest.java +++ b/hedera-node/hedera-app-spi/src/test/java/com/hedera/node/app/spi/state/SchemaTest.java @@ -22,6 +22,7 @@ import com.hedera.hapi.node.base.SemanticVersion; import com.hedera.node.app.spi.fixtures.state.MapWritableStates; import com.hedera.node.app.spi.fixtures.state.TestSchema; +import com.swirlds.platform.test.fixtures.state.StateTestBase; import java.util.ArrayList; import java.util.Collections; import org.junit.jupiter.api.Disabled; diff --git a/hedera-node/hedera-app-spi/src/test/java/com/hedera/node/app/spi/state/WrappedReadableKVStateTest.java b/hedera-node/hedera-app-spi/src/test/java/com/hedera/node/app/spi/state/WrappedReadableKVStateTest.java index de4ed83f76a7..17d2ab2f796d 100644 --- a/hedera-node/hedera-app-spi/src/test/java/com/hedera/node/app/spi/state/WrappedReadableKVStateTest.java +++ b/hedera-node/hedera-app-spi/src/test/java/com/hedera/node/app/spi/state/WrappedReadableKVStateTest.java @@ -16,6 +16,8 @@ package com.hedera.node.app.spi.state; +import com.swirlds.platform.state.spi.ReadableKVStateBase; +import com.swirlds.platform.state.spi.ReadableKVStateBaseTest; import java.util.Map; /** diff --git a/hedera-node/hedera-app-spi/src/test/java/com/hedera/node/app/spi/state/WrappedWritableKVStateTest.java b/hedera-node/hedera-app-spi/src/test/java/com/hedera/node/app/spi/state/WrappedWritableKVStateTest.java index 1381c6efb683..f33ec3fd2618 100644 --- a/hedera-node/hedera-app-spi/src/test/java/com/hedera/node/app/spi/state/WrappedWritableKVStateTest.java +++ b/hedera-node/hedera-app-spi/src/test/java/com/hedera/node/app/spi/state/WrappedWritableKVStateTest.java @@ -20,7 +20,9 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.mockito.ArgumentMatchers.anyString; -import com.hedera.node.app.spi.fixtures.state.MapWritableKVState; +import com.swirlds.platform.state.spi.WritableKVStateBase; +import com.swirlds.platform.state.spi.WritableKVStateBaseTest; +import com.swirlds.platform.test.fixtures.state.MapWritableKVState; import edu.umd.cs.findbugs.annotations.NonNull; import java.util.Map; import org.junit.jupiter.api.DisplayName; @@ -88,8 +90,10 @@ void putNew() { // Instead, modifications on delegate have increased. // Since modifications are increased, size of delegate also increases. state.commit(); - Mockito.verify(state, Mockito.times(1)).putIntoDataSource(C_KEY, CHERRY); - Mockito.verify(state, Mockito.never()).removeFromDataSource(anyString()); + Mockito.verify((WrappedWritableKVState) state, Mockito.times(1)) + .putIntoDataSource(C_KEY, CHERRY); + Mockito.verify((WrappedWritableKVState) state, Mockito.never()) + .removeFromDataSource(anyString()); assertEquals(3, state.size()); assertEquals(3, delegate.size()); assertEquals(1, delegate.modifiedKeys().size()); @@ -120,8 +124,10 @@ void removeExisting() { // Commit should cause change in modifications on delegate. // So the size of the delegate also decreases by 1. state.commit(); - Mockito.verify(state, Mockito.never()).putIntoDataSource(anyString(), anyString()); - Mockito.verify(state, Mockito.times(1)).removeFromDataSource(A_KEY); + Mockito.verify((WrappedWritableKVState) state, Mockito.never()) + .putIntoDataSource(anyString(), anyString()); + Mockito.verify((WrappedWritableKVState) state, Mockito.times(1)) + .removeFromDataSource(A_KEY); assertEquals(1, state.size()); assertEquals(1, delegate.size()); assertEquals(1, delegate.modifiedKeys().size()); diff --git a/hedera-node/hedera-app-spi/src/test/java/com/hedera/node/app/spi/state/WrappedWritableSingletonTest.java b/hedera-node/hedera-app-spi/src/test/java/com/hedera/node/app/spi/state/WrappedWritableSingletonTest.java index 0d84ebd58405..27e6511095ff 100644 --- a/hedera-node/hedera-app-spi/src/test/java/com/hedera/node/app/spi/state/WrappedWritableSingletonTest.java +++ b/hedera-node/hedera-app-spi/src/test/java/com/hedera/node/app/spi/state/WrappedWritableSingletonTest.java @@ -18,6 +18,10 @@ import static org.assertj.core.api.Assertions.assertThat; +import com.swirlds.platform.state.spi.WritableKVStateBaseTest; +import com.swirlds.platform.state.spi.WritableSingletonStateBase; +import com.swirlds.platform.state.spi.WritableSingletonStateBaseTest; +import com.swirlds.state.spi.WritableSingletonState; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; diff --git a/hedera-node/hedera-app-spi/src/test/java/com/swirlds/platform/state/spi/ReadableKVStateBaseTest.java b/hedera-node/hedera-app-spi/src/test/java/com/swirlds/platform/state/spi/ReadableKVStateBaseTest.java new file mode 100644 index 000000000000..55a68cce03fe --- /dev/null +++ b/hedera-node/hedera-app-spi/src/test/java/com/swirlds/platform/state/spi/ReadableKVStateBaseTest.java @@ -0,0 +1,173 @@ +/* + * Copyright (C) 2024 Hedera Hashgraph, LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.swirlds.platform.state.spi; + +/* + * Copyright (C) 2023-2024 Hedera Hashgraph, LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +import com.swirlds.platform.test.fixtures.state.MapReadableKVState; +import java.util.HashMap; +import java.util.Map; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +/** + * Tests for the base class for all states. Most of the methods in this class are final, and can be + * tested in isolation of specific subclasses. Those that cannot be (such as {@link + * ReadableKVStateBase#reset()}) will be covered by other tests in addition to this one. + */ +public class ReadableKVStateBaseTest extends com.swirlds.platform.test.fixtures.state.StateTestBase { + private ReadableKVStateBase state; + protected Map backingMap; + + @BeforeEach + void setUp() { + this.backingMap = createBackingMap(); + this.state = createFruitState(this.backingMap); + } + + protected Map createBackingMap() { + final var map = new HashMap(); + map.put(A_KEY, APPLE); + map.put(B_KEY, BANANA); + map.put(C_KEY, CHERRY); + map.put(D_KEY, DATE); + map.put(E_KEY, EGGPLANT); + map.put(F_KEY, FIG); + map.put(G_KEY, GRAPE); + return map; + } + + protected ReadableKVStateBase createFruitState(Map backingMap) { + return new MapReadableKVState<>(FRUIT_STATE_KEY, backingMap); + } + + /** Make sure the constructor is holding onto the state key properly */ + @Test + @DisplayName("The state key must match what was provided in the constructor") + void testStateKey() { + assertThat(state.getStateKey()).isEqualTo(FRUIT_STATE_KEY); + } + + /** + * When we are asked to get an unknown item (something not in the backing store), the {@link + * ReadableKVStateBase} still needs to remember this key and include it in the set of read keys, + * because the fact that the key was missing might be an important piece of information when + * working in pre-handle. So we keep track in readKeys of ALL keys read, not just those that had + * values. + */ + @Test + @DisplayName("`get` of an unknown item returns an empty optional") + void testNonExistingGet() { + assertThat(state.readKeys()).isEmpty(); + assertThat(state.get(UNKNOWN_KEY)).isNull(); + assertThat(state.readKeys()).contains(UNKNOWN_KEY); + } + + @Test + @DisplayName("The set of readKeys must be unmodifiable") + void testReadKeysIsUnmodifiable() { + state.get(A_KEY); + final var readKeys = state.readKeys(); + assertThatThrownBy(() -> readKeys.add(B_KEY)).isInstanceOf(UnsupportedOperationException.class); + assertThatThrownBy(() -> readKeys.remove(A_KEY)).isInstanceOf(UnsupportedOperationException.class); + } + + /** + * When asked to get a known item, not only is it returned, but we also record this in the + * "readKeys". + */ + @Test + @DisplayName("`get` of a known item returns that item") + void testExistingGet() { + assertThat(state.readKeys()).isEmpty(); + assertThat(state.get(A_KEY)).isNotNull(); + assertThat(state.get(A_KEY)).isEqualTo(APPLE); + assertThat(state.readKeys()).contains(A_KEY); + } + + /** Similar to get, but for "contains". We must record this in "readKeys". */ + @Test + @DisplayName("`contains` of an unknown item returns false") + void testNonExistingContains() { + assertThat(state.readKeys()).isEmpty(); + assertThat(state.contains(UNKNOWN_KEY)).isFalse(); + assertThat(state.readKeys()).contains(UNKNOWN_KEY); + } + + /** Similar to get, but for contains. */ + @Test + @DisplayName("`contains` of a known item returns true") + void testExistingContains() { + assertThat(state.readKeys()).isEmpty(); + assertThat(state.contains(B_KEY)).isTrue(); + assertThat(state.readKeys()).contains(B_KEY); + } + + /** + * Read some states, which will populate the "readKeys". Then clear the state, which must clear + * the "readKeys". + */ + @Test + @DisplayName("`reset` clears the readKeys cache") + void testReset() { + // Populate "readKeys" by reading values + assertThat(state.get(A_KEY)).isNotNull(); + assertThat(state.contains(B_KEY)).isTrue(); + assertThat(state.readKeys()).hasSize(2); + + // Reset and verify the cache is empty + state.reset(); + assertThat(state.readKeys()).isEmpty(); + + // Repopulate the cache by reading again, proving it is still working after reset + assertThat(state.get(A_KEY)).isNotNull(); + assertThat(state.contains(B_KEY)).isTrue(); + assertThat(state.readKeys()).hasSize(2); + } + + @Test + @DisplayName("Can iterate over all fruit") + void testIteration() { + assertThat(state.keys()) + .toIterable() + .containsExactlyInAnyOrder(backingMap.keySet().toArray(new String[0])); + } + + @Test + @DisplayName("Size returns backing map size") + void testSize() { + assertThat(state.size()).isEqualTo(backingMap.size()); + } +} diff --git a/hedera-node/hedera-app-spi/src/test/java/com/hedera/node/app/spi/state/SingletonStateTestBase.java b/hedera-node/hedera-app-spi/src/test/java/com/swirlds/platform/state/spi/SingletonStateTestBase.java similarity index 97% rename from hedera-node/hedera-app-spi/src/test/java/com/hedera/node/app/spi/state/SingletonStateTestBase.java rename to hedera-node/hedera-app-spi/src/test/java/com/swirlds/platform/state/spi/SingletonStateTestBase.java index b293a13e5869..39ba74e7793e 100644 --- a/hedera-node/hedera-app-spi/src/test/java/com/hedera/node/app/spi/state/SingletonStateTestBase.java +++ b/hedera-node/hedera-app-spi/src/test/java/com/swirlds/platform/state/spi/SingletonStateTestBase.java @@ -14,10 +14,11 @@ * limitations under the License. */ -package com.hedera.node.app.spi.state; +package com.swirlds.platform.state.spi; import static org.assertj.core.api.Assertions.assertThat; +import com.swirlds.platform.test.fixtures.state.StateTestBase; import java.util.concurrent.atomic.AtomicReference; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; diff --git a/hedera-node/hedera-app-spi/src/test/java/com/swirlds/platform/state/spi/WritableKVStateBaseTest.java b/hedera-node/hedera-app-spi/src/test/java/com/swirlds/platform/state/spi/WritableKVStateBaseTest.java new file mode 100644 index 000000000000..c23df0e6c95c --- /dev/null +++ b/hedera-node/hedera-app-spi/src/test/java/com/swirlds/platform/state/spi/WritableKVStateBaseTest.java @@ -0,0 +1,1011 @@ +/* + * Copyright (C) 2024 Hedera Hashgraph, LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.swirlds.platform.state.spi; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.AssertionsForClassTypes.assertThatThrownBy; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.fail; +import static org.mockito.ArgumentMatchers.anyString; + +import com.swirlds.platform.test.fixtures.state.MapWritableKVState; +import edu.umd.cs.findbugs.annotations.NonNull; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.NoSuchElementException; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.Executors; +import java.util.concurrent.TimeUnit; +import java.util.function.Consumer; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.Test; +import org.mockito.Mockito; + +/** + * Tests for the base class for all mutable states. All non-abstract methods in this class are + * final, so we can test here very thoroughly with a dummy {@link WritableKVStateBase}. + * + *

    In this test, we create a backing store with only {(A=APPLE),(B=BANANA)}. We then have a + * series of tests that will replace the values for A, B, or remove them, or add new values. + */ +public class WritableKVStateBaseTest extends ReadableKVStateBaseTest { + private static final String NUM_ITERATIONS_ARG = "WritableKVStateBaseTest.DeterministicUpdates.numIterations"; + protected WritableKVStateBase state; + + @Override + protected Map createBackingMap() { + final var map = new HashMap(); + this.backingMap = Mockito.spy(map); + backingMap.put(A_KEY, APPLE); + backingMap.put(B_KEY, BANANA); + + // This method call is really unexpected. I tried to add both keys to the map before + // creating the spy, and it seems to me that should have worked, but it doesn't, the + // spy doesn't know anything about the values put into the map. Instead, I had to + // add them afterward. But then I need to reset the spy, so all my verify methods are + // right. Kind of bogus, honestly. + //noinspection unchecked + Mockito.reset(this.backingMap); + + return backingMap; + } + + protected WritableKVStateBase createFruitState(@NonNull final Map map) { + this.state = Mockito.spy(new MapWritableKVState<>(FRUIT_STATE_KEY, map)); + return state; + } + + @Nested + @DisplayName("getForModify") + final class GetForModifyTest { + + /** + * Using getForModify on an unknown value MUST return null, and it MUST record the key in + * "readKeys", since nothing was modified as a result of this call, but it was read. When + * committed, nothing should change on the backing store. + */ + @Test + @DisplayName("Call on an unknown value returns null and is a read key") + void getForModifyUnknownValue() { + // Before running getForModify, readKeys and modifiedKeys must be empty + assertThat(state.readKeys()).isEmpty(); + assertThat(state.modifiedKeys()).isEmpty(); + + // Verify the value is null + final var value = state.getForModify(UNKNOWN_KEY); + assertThat(value).isNull(); + assertThat(state.readKeys()).contains(UNKNOWN_KEY); + assertThat(state.readKeys()).contains(UNKNOWN_KEY); + + // Note, it is NOT a modified key, because it didn't exist in the underlying data store + assertThat(state.modifiedKeys()).doesNotContain(UNKNOWN_KEY); + + // Commit should cause no changes to the backing store + state.commit(); + Mockito.verify(state, Mockito.never()).putIntoDataSource(anyString(), anyString()); + Mockito.verify(state, Mockito.never()).removeFromDataSource(anyString()); + } + + /** + * Using getForModify on an unknown value twice MUST only hit the backend data source once. + */ + @Test + @DisplayName("Call on an unknown value twice hits the backend only once") + void getForModifyUnknownValueTwice() { + // Call getForModify twice and verify the spied on backingMap was called only once. + state.getForModify(A_KEY); + state.getForModify(A_KEY); + Mockito.verify(backingMap, Mockito.times(1)).get(A_KEY); + } + + /** + * Using getForModify on an existing key MUST return the associated value, and it MUST + * record the key in "readKeys". When committed, nothing is changed on the backing store, + * since getForModify only signals to the backend that something should be prepared to be + * modified, but the actual modification is done through "put". + */ + @Test + @DisplayName("Call on an existing value gets it and marks it as read and modified") + void getForModifyKnownValue() { + // Before running getForModify, readKeys and modifiedKeys must be empty + assertThat(state.readKeys()).isEmpty(); + assertThat(state.modifiedKeys()).isEmpty(); + + // Verify the value is what we expected + final var value = state.getForModify(A_KEY); + assertThat(value).isEqualTo(APPLE); + + // Verify the key used in lookup is now in readKeys but NOT modifiedKeys + assertThat(state.readKeys()).contains(A_KEY); + assertThat(state.modifiedKeys()).isEmpty(); + + // Commit should cause no changes to the backing store + state.commit(); + Mockito.verify(state, Mockito.never()).putIntoDataSource(anyString(), anyString()); + Mockito.verify(state, Mockito.never()).removeFromDataSource(anyString()); + } + + /** + * Using getForModify on an existing key more than once MUST return the associated value, + * and it MUST record the key in "readKeys" where it is present only once. + */ + @Test + @DisplayName("Called twice on an existing value") + void getForModifyKnownValueTwice() { + // Before running getForModify, readKeys and modifiedKeys must be empty + assertThat(state.readKeys()).isEmpty(); + assertThat(state.modifiedKeys()).isEmpty(); + + // Verify the value is what we expected + for (int i = 0; i < 2; i++) { + final var value = state.getForModify(A_KEY); + assertThat(value).isEqualTo(APPLE); + + // Verify the key used in lookup is now in readKeys and NOT in modifiedKeys + assertThat(state.readKeys()).contains(A_KEY); + assertThat(state.modifiedKeys()).doesNotContain(A_KEY); + } + } + + /** + * Using getForModify on a key that has already been "put" MUST return the associated value + * that was previously put. The key MUST NOT be in "readKeys", and MUST be in + * "modifiedKeys". + * + *

    Given transaction T1 that modifies the value for some key K, and transaction T2 that + * *never* reads the value of K but always does a {@code put(K, V)}, and then subsequently + * does a {@code getForModify}, it doesn't matter at all what T1 did with the value + * for K. T2 is just going to overwrite it. So in this flow, it should not register K as a + * "readKey" because it is never actually read from the backing data source. + * + *

    When committed, the most current "put" value is sent to the backing store. + */ + @Test + @DisplayName("A key that was put and did not exist before") + void getForModifyAfterPut() { + // Put some value + state.put(C_KEY, CHERRY); + + // Before running getForModify, readKeys is empty and modified keys contains C_KEY + assertThat(state.readKeys()).isEmpty(); + assertThat(state.modifiedKeys()).hasSize(1); + assertThat(state.modifiedKeys()).contains(C_KEY); + + // Verify the value is what we expected + final var value = state.getForModify(C_KEY); + assertThat(value).isEqualTo(CHERRY); + + // Verify the key used in lookup is not in readKeys and is in modifiedKeys + assertThat(state.readKeys()).isEmpty(); + assertThat(state.modifiedKeys()).contains(C_KEY); + assertThat(state.modifiedKeys()).hasSize(1); + + // Commit should cause the most recent put value to be saved + state.commit(); + Mockito.verify(state, Mockito.times(1)).putIntoDataSource(anyString(), anyString()); + Mockito.verify(state, Mockito.times(1)).putIntoDataSource(C_KEY, CHERRY); + Mockito.verify(state, Mockito.never()).removeFromDataSource(anyString()); + } + + /** + * Using getForModify on a key that has already been "put", where that key DID exist in the + * backend already, MUST return the associated value that was previously put. The key MUST + * NOT be in "readKeys", and MUST be in "modifiedKeys". The reasoning here is the same as + * with {@link #getForModifyAfterPut()}. + * + *

    When committed, the most current "put" value is sent to the backing store. + */ + @Test + @DisplayName("A key that was put (and did exist before) first") + void getForModifyAfterPutOnExistingKey() { + // Put some value + state.put(B_KEY, BLACKBERRY); + + // Before running getForModify, readKeys is empty and modified keys contains B_KEY + assertThat(state.readKeys()).isEmpty(); + assertThat(state.modifiedKeys()).hasSize(1); + assertThat(state.modifiedKeys()).contains(B_KEY); + + // Verify the value is what we expected + final var value = state.getForModify(B_KEY); + assertThat(value).isEqualTo(BLACKBERRY); + + // Verify the key used in lookup is not in readKeys and is in modifiedKeys + assertThat(state.readKeys()).isEmpty(); + assertThat(state.modifiedKeys()).contains(B_KEY); + assertThat(state.modifiedKeys()).hasSize(1); + + // Commit should cause the most recent put value to be saved + state.commit(); + Mockito.verify(state, Mockito.times(1)).putIntoDataSource(anyString(), anyString()); + Mockito.verify(state, Mockito.times(1)).putIntoDataSource(B_KEY, BLACKBERRY); + Mockito.verify(state, Mockito.never()).removeFromDataSource(anyString()); + } + + /** + * Using getForModify on a key that has already been "removed" MUST return null. The key + * MUST be in "modifiedKeys", but must not be in "readKeys". When committed, the key will be + * removed. + */ + @Test + @DisplayName("A key that has been previously removed will return null for the value") + void getForModifyAfterRemove() { + // Remove some value + state.remove(A_KEY); + + // Before running getForModify, readKeys is empty and modified keys contains A_KEY + assertThat(state.readKeys()).isEmpty(); + assertThat(state.modifiedKeys()).hasSize(1); + assertThat(state.modifiedKeys()).contains(A_KEY); + + // Verify the value is null + final var value = state.getForModify(A_KEY); + assertThat(value).isNull(); + + // Verify the key used in lookup is not in readKeys and is in modifiedKeys + assertThat(state.readKeys()).isEmpty(); + assertThat(state.modifiedKeys()).contains(A_KEY); + assertThat(state.modifiedKeys()).hasSize(1); + + // Commit should cause the value to be removed + state.commit(); + Mockito.verify(state, Mockito.never()).putIntoDataSource(anyString(), anyString()); + Mockito.verify(state, Mockito.times(1)).removeFromDataSource(anyString()); + Mockito.verify(state, Mockito.times(1)).removeFromDataSource(A_KEY); + } + } + + /** + * Gives size of backing store plus modifications (additions or removals). + * If a new key is added by calling {@code put()}, then size increases, as new key is added to modifications map for addition. + * If an existing key is removed by calling {@code remove()}, then size decreases, as new key is added to modifications map for removal. + */ + @Nested + @DisplayName("size") + final class SizeTest { + @Test + @DisplayName("Adding a key that does not already exist in the backing store impacts size") + void putNew() { + assertThat(state.readKeys()).isEmpty(); + assertThat(state.modifiedKeys()).isEmpty(); + + // Before doing put, the size should be 2 (setup of the test adds 2 keys) + assertEquals(2, state.size()); + state.put(C_KEY, CHERRY); + + // After put, size includes modifications as well. So the size should be 3. + assertEquals(3, state.size()); + + // Commit should keep the size, as the modifications are considered in size. + state.commit(); + Mockito.verify(state, Mockito.times(1)).putIntoDataSource(anyString(), anyString()); + Mockito.verify(state, Mockito.times(1)).putIntoDataSource(C_KEY, CHERRY); + Mockito.verify(state, Mockito.never()).removeFromDataSource(anyString()); + assertEquals(3, state.size()); + } + + @Test + @DisplayName("Removing a key that exists in the backing store impacts size") + void removeExisting() { + assertThat(state.readKeys()).isEmpty(); + assertThat(state.modifiedKeys()).isEmpty(); + + // Before remove, the size should be 2 (setup of the test adds 2 keys) + assertEquals(2, state.size()); + + state.remove(A_KEY); + // After remove, size includes modifications as well. So the size should be 1. + assertEquals(1, state.size()); + + // Commit should not cause any change in size, as the modifications were considered + state.commit(); + Mockito.verify(state, Mockito.never()).putIntoDataSource(anyString(), anyString()); + Mockito.verify(state, Mockito.times(1)).removeFromDataSource(A_KEY); + assertEquals(1, state.size()); + } + + @Test + @DisplayName("Getting a key from the backing store doesn't affect size") + void getDoesntAffect() { + assertThat(state.readKeys()).isEmpty(); + assertThat(state.modifiedKeys()).isEmpty(); + + state.get(A_KEY); + // Before commit, the size should be 2 (setup of the test adds 2 keys) + assertEquals(2, state.size()); + + // Commit should not have any effect on size + state.commit(); + Mockito.verify(state, Mockito.never()).putIntoDataSource(anyString(), anyString()); + Mockito.verify(state, Mockito.never()).removeFromDataSource(anyString()); + assertEquals(2, state.size()); + } + + @Test + @DisplayName("Doing a getForModify on a key existing in the backing store doesn't affect size") + void getForModifyDoesntAffect() { + assertThat(state.readKeys()).isEmpty(); + assertThat(state.modifiedKeys()).isEmpty(); + + state.getForModify(A_KEY); + // Before commit, the size should be 2 (setup of the test adds 2 keys) + assertEquals(2, state.size()); + + // Commit should not have any effect on size + state.commit(); + Mockito.verify(state, Mockito.never()).putIntoDataSource(anyString(), anyString()); + Mockito.verify(state, Mockito.never()).removeFromDataSource(anyString()); + assertEquals(2, state.size()); + } + } + + @Nested + @DisplayName("put") + final class PutTest { + /** + * If the key is unknown in the backing store, then it will now be recorded as a + * modification. It will not be listed as a "read" key. When committed, the new value should + * be saved. + */ + @Test + @DisplayName("Put a key that does not already exist in the backing store") + void putNew() { + assertThat(state.readKeys()).isEmpty(); + assertThat(state.modifiedKeys()).isEmpty(); + + state.put(C_KEY, CHERRY); + assertThat(state.readKeys()).isEmpty(); + assertThat(state.modifiedKeys()).hasSize(1); + assertThat(state.modifiedKeys()).contains(C_KEY); + + // We should be able to "get" the modification + assertThat(state.get(C_KEY)).isEqualTo(CHERRY); + assertThat(state.readKeys()).isEmpty(); + + // The original value should still not exist + assertThat(state.getOriginalValue(C_KEY)).isNull(); + + // Commit should cause the value to be added + state.commit(); + Mockito.verify(state, Mockito.times(1)).putIntoDataSource(Mockito.anyString(), Mockito.anyString()); + Mockito.verify(state, Mockito.times(1)).putIntoDataSource(C_KEY, CHERRY); + Mockito.verify(state, Mockito.never()).removeFromDataSource(Mockito.anyString()); + + // After a commit, the original value should have been added + assertThat(state.getOriginalValue(C_KEY)).isEqualTo(CHERRY); + } + + /** + * Even if the key is known already in the backing store, a "put" will record the + * modification with no respect to what is in the backing store already. It will not be a + * "read" key. + */ + @Test + @DisplayName("Put a key that already exists in the backing store") + void putExisting() { + assertThat(state.readKeys()).isEmpty(); + assertThat(state.modifiedKeys()).isEmpty(); + + state.put(A_KEY, ACAI); + assertThat(state.readKeys()).isEmpty(); + assertThat(state.modifiedKeys()).hasSize(1); + assertThat(state.modifiedKeys()).contains(A_KEY); + + // The original value should not have changed + assertThat(state.getOriginalValue(A_KEY)).isEqualTo(APPLE); + + // Commit should cause the value to be updated + state.commit(); + Mockito.verify(state, Mockito.times(1)).putIntoDataSource(Mockito.anyString(), Mockito.anyString()); + Mockito.verify(state, Mockito.times(1)).putIntoDataSource(A_KEY, ACAI); + Mockito.verify(state, Mockito.never()).removeFromDataSource(Mockito.anyString()); + + // After a commit, the original value should have changed + assertThat(state.getOriginalValue(A_KEY)).isEqualTo(ACAI); + } + + /** + * If the key has been previously part of "getForModify", and then we "put", then the key + * will still be listed as a "read" key, and will also be a modified key. + */ + @Test + @DisplayName("Put a key that was previously 'getForModify'") + void putAfterGetForModify() { + state.getForModify(B_KEY); + assertThat(state.readKeys()).isNotEmpty(); + assertThat(state.modifiedKeys()).isEmpty(); + assertThat(state.readKeys()).contains(B_KEY); + + state.put(B_KEY, BLACKBERRY); + assertThat(state.readKeys()).contains(B_KEY); + assertThat(state.modifiedKeys()).contains(B_KEY); + + // Commit should cause the value to be updated + state.commit(); + Mockito.verify(state, Mockito.times(1)).putIntoDataSource(anyString(), anyString()); + Mockito.verify(state, Mockito.times(1)).putIntoDataSource(B_KEY, BLACKBERRY); + Mockito.verify(state, Mockito.never()).removeFromDataSource(anyString()); + } + + /** Calling put twice is idempotent. */ + @Test + @DisplayName("Put a key twice") + void putTwice() { + state.put(B_KEY, BLACKBERRY); + assertThat(state.readKeys()).isEmpty(); + assertThat(state.modifiedKeys()).isNotEmpty(); + assertThat(state.modifiedKeys()).contains(B_KEY); + + state.put(B_KEY, BLUEBERRY); + assertThat(state.readKeys()).isEmpty(); + assertThat(state.modifiedKeys()).contains(B_KEY); + + // The original value should not have changed + assertThat(state.getOriginalValue(B_KEY)).isEqualTo(BANANA); + + // Commit should cause the value to be updated to the latest value + state.commit(); + Mockito.verify(state, Mockito.times(1)).putIntoDataSource(anyString(), anyString()); + Mockito.verify(state, Mockito.times(1)).putIntoDataSource(B_KEY, BLUEBERRY); + Mockito.verify(state, Mockito.never()).removeFromDataSource(anyString()); + + // After a commit, the original value should have changed to the latest value + assertThat(state.getOriginalValue(B_KEY)).isEqualTo(BLUEBERRY); + } + + /** + * If a key has been removed, and then is "put" back, it should ultimately be "put" and not + * removed. + */ + @Test + @DisplayName("Put a key after having removed it") + void putAfterRemove() { + state.remove(B_KEY); + assertThat(state.readKeys()).isEmpty(); + assertThat(state.modifiedKeys()).isNotEmpty(); + assertThat(state.modifiedKeys()).contains(B_KEY); + + state.put(B_KEY, BLACKBERRY); + assertThat(state.readKeys()).isEmpty(); + assertThat(state.modifiedKeys()).contains(B_KEY); + + // The original value should not have changed + assertThat(state.getOriginalValue(B_KEY)).isEqualTo(BANANA); + + // Commit should cause the value to be updated to the latest value + state.commit(); + Mockito.verify(state, Mockito.times(1)).putIntoDataSource(anyString(), anyString()); + Mockito.verify(state, Mockito.times(1)).putIntoDataSource(B_KEY, BLACKBERRY); + Mockito.verify(state, Mockito.never()).removeFromDataSource(anyString()); + + // After a commit, the original value should have changed + assertThat(state.getOriginalValue(B_KEY)).isEqualTo(BLACKBERRY); + } + } + + @Nested + @DisplayName("remove") + final class RemoveTest { + /** + * If the key is unknown in the backing store, and is removed, it should be a no-op, but + * MUST be listed in the set of modified keys. This is because it may be in pre-handle at + * the time "remove" is called that there is no underlying data, but then later during + * handle there actually is, and if we didn't record this as a modification, we wouldn't + * have removed the value in the end! + * + *

    On commit, the remove method MUST be called on the backing store. + */ + @Test + @DisplayName("Remove an unknown key") + void removeUnknown() { + // Initially everything is clean + assertThat(state.readKeys()).isEmpty(); + assertThat(state.modifiedKeys()).isEmpty(); + + // Remove an unknown key + state.remove(C_KEY); + + // "readKeys" is still empty, but "modifiedKeys" has the new key + assertThat(state.readKeys()).isEmpty(); + assertThat(state.modifiedKeys()).isNotEmpty(); + assertThat(state.modifiedKeys()).hasSize(1); + assertThat(state.modifiedKeys()).contains(C_KEY); + + // The original value should not exist + assertThat(state.getOriginalValue(C_KEY)).isNull(); + + // Commit should cause the value to be removed (even though it doesn't actually exist in + // the backend) + state.commit(); + Mockito.verify(state, Mockito.never()).putIntoDataSource(anyString(), anyString()); + Mockito.verify(state, Mockito.times(1)).removeFromDataSource(anyString()); + Mockito.verify(state, Mockito.times(1)).removeFromDataSource(C_KEY); + + // After a commit, the original value should still not exist + assertThat(state.getOriginalValue(C_KEY)).isNull(); + } + + /** + * If the key is known in the backing store, and is removed, it MUST be listed in the set of + * modified keys. + * + *

    On commit, the remove method MUST be called on the backing store. + */ + @Test + @DisplayName("Remove a known key") + void removeKnown() { + // Initially everything is clean + assertThat(state.readKeys()).isEmpty(); + assertThat(state.modifiedKeys()).isEmpty(); + + // Remove a known key + state.remove(A_KEY); + + // "readKeys" is still empty, but "modifiedKeys" has the key + assertThat(state.readKeys()).isEmpty(); + assertThat(state.modifiedKeys()).isNotEmpty(); + assertThat(state.modifiedKeys()).hasSize(1); + assertThat(state.modifiedKeys()).contains(A_KEY); + + // The original value should not have changed + assertThat(state.getOriginalValue(A_KEY)).isEqualTo(APPLE); + + // Commit should cause the value to be removed + state.commit(); + Mockito.verify(state, Mockito.never()).putIntoDataSource(anyString(), anyString()); + Mockito.verify(state, Mockito.times(1)).removeFromDataSource(anyString()); + Mockito.verify(state, Mockito.times(1)).removeFromDataSource(A_KEY); + + // After a commit, the original value should have been removed + assertThat(state.getOriginalValue(A_KEY)).isNull(); + } + + /** + * If the key is removed twice, the remove call is idempotent. + * + *

    On commit, the remove method MUST be called on the backing store just once. + */ + @Test + @DisplayName("Remove a known key twice") + void removeTwice() { + // Initially everything is clean + assertThat(state.readKeys()).isEmpty(); + assertThat(state.modifiedKeys()).isEmpty(); + + // Remove a known key + for (int i = 0; i < 2; i++) { + state.remove(B_KEY); + + // "readKeys" is still empty, but "modifiedKeys" has the key + assertThat(state.readKeys()).isEmpty(); + assertThat(state.modifiedKeys()).isNotEmpty(); + assertThat(state.modifiedKeys()).hasSize(1); + assertThat(state.modifiedKeys()).contains(B_KEY); + } + + // The original value should not have changed + assertThat(state.getOriginalValue(B_KEY)).isEqualTo(BANANA); + + // Commit should cause the value to be removed + state.commit(); + Mockito.verify(state, Mockito.never()).putIntoDataSource(anyString(), anyString()); + Mockito.verify(state, Mockito.times(1)).removeFromDataSource(anyString()); + Mockito.verify(state, Mockito.times(1)).removeFromDataSource(B_KEY); + + // After a commit, the original value should have been removed + assertThat(state.getOriginalValue(B_KEY)).isNull(); + } + + /** + * If the key was previously "get" and is then removed, it MUST be present in both the + * "readKeys" and "modified" keys. + * + *

    On commit, the remove method MUST be called on the backing store. + */ + @Test + @DisplayName("Remove a known key after get") + void removeAfterGet() { + // Initially everything is clean + assertThat(state.readKeys()).isEmpty(); + assertThat(state.modifiedKeys()).isEmpty(); + + // Remove a known key after getting it + assertThat(state.get(A_KEY)).isEqualTo(APPLE); + state.remove(A_KEY); + + // "readKeys" is now populated, and "modifiedKeys" has the key + assertThat(state.readKeys()).hasSize(1); + assertThat(state.modifiedKeys()).hasSize(1); + assertThat(state.readKeys()).contains(A_KEY); + assertThat(state.modifiedKeys()).contains(A_KEY); + + // Commit should cause the value to be removed but not "put" + state.commit(); + Mockito.verify(state, Mockito.never()).putIntoDataSource(anyString(), anyString()); + Mockito.verify(state, Mockito.times(1)).removeFromDataSource(anyString()); + Mockito.verify(state, Mockito.times(1)).removeFromDataSource(A_KEY); + } + + /** + * If the key was previously "get" and is then removed, and was unknown, it MUST be present + * in both the "readKeys" and "modified" keys. + * + *

    On commit, the remove method MUST be called on the backing store. + */ + @Test + @DisplayName("Remove a unknown key after get") + void removeAfterGetUnknown() { + // Initially everything is clean + assertThat(state.readKeys()).isEmpty(); + assertThat(state.modifiedKeys()).isEmpty(); + + // Remove a known key after getting it + assertThat(state.get(C_KEY)).isNull(); + state.remove(C_KEY); + + // "readKeys" is now populated, and "modifiedKeys" has the key + assertThat(state.readKeys()).hasSize(1); + assertThat(state.modifiedKeys()).hasSize(1); + assertThat(state.readKeys()).contains(C_KEY); + assertThat(state.modifiedKeys()).contains(C_KEY); + + // Commit should cause the value to be removed but not "put" + state.commit(); + Mockito.verify(state, Mockito.never()).putIntoDataSource(anyString(), anyString()); + Mockito.verify(state, Mockito.times(1)).removeFromDataSource(anyString()); + Mockito.verify(state, Mockito.times(1)).removeFromDataSource(C_KEY); + } + + /** + * If the key was previously "getForModify" and is then removed, it MUST be present in both + * the "readKeys" and "modified" keys. + * + *

    On commit, the remove method MUST be called on the backing store. + */ + @Test + @DisplayName("Remove a known key after getForModify") + void removeAfterGetForModify() { + // Initially everything is clean + assertThat(state.readKeys()).isEmpty(); + assertThat(state.modifiedKeys()).isEmpty(); + + // Remove a known key after getting it + assertThat(state.get(A_KEY)).isEqualTo(APPLE); + state.remove(A_KEY); + + // "readKeys" is now populated, and "modifiedKeys" has the key + assertThat(state.readKeys()).hasSize(1); + assertThat(state.modifiedKeys()).hasSize(1); + assertThat(state.readKeys()).contains(A_KEY); + assertThat(state.modifiedKeys()).contains(A_KEY); + + // Commit should cause the value to be removed but not "put" + state.commit(); + Mockito.verify(state, Mockito.never()).putIntoDataSource(anyString(), anyString()); + Mockito.verify(state, Mockito.times(1)).removeFromDataSource(anyString()); + Mockito.verify(state, Mockito.times(1)).removeFromDataSource(A_KEY); + } + + /** + * If the key was previously "getForModify" and is then removed, and the key was not in the + * backing store, it MUST be present in both the "readKeys" and "modified" keys. + * + *

    On commit, the remove method MUST be called on the backing store. + */ + @Test + @DisplayName("Remove a unknown key after getForModify") + void removeAfterGetForModifyUnknown() { + // Initially everything is clean + assertThat(state.readKeys()).isEmpty(); + assertThat(state.modifiedKeys()).isEmpty(); + + // Remove a known key after getting it + assertThat(state.getForModify(C_KEY)).isNull(); + state.remove(C_KEY); + + // "readKeys" is now populated, and "modifiedKeys" has the key + assertThat(state.readKeys()).hasSize(1); + assertThat(state.modifiedKeys()).hasSize(1); + assertThat(state.readKeys()).contains(C_KEY); + assertThat(state.modifiedKeys()).contains(C_KEY); + + // Commit should cause the value to be removed but not "put" + state.commit(); + Mockito.verify(state, Mockito.never()).putIntoDataSource(anyString(), anyString()); + Mockito.verify(state, Mockito.times(1)).removeFromDataSource(anyString()); + Mockito.verify(state, Mockito.times(1)).removeFromDataSource(C_KEY); + } + + /** + * If the key was previously "put" and is then removed, it MUST be present in only + * "modified" keys. + * + *

    On commit, the remove method MUST be called on the backing store and not the put + * method. + */ + @Test + @DisplayName("Remove a known key after put") + void removeAfterPut() { + // Initially everything is clean + assertThat(state.readKeys()).isEmpty(); + assertThat(state.modifiedKeys()).isEmpty(); + + // Remove a known key after putting it + state.put(A_KEY, ACAI); + state.remove(A_KEY); + + // "readKeys" is not populated, and "modifiedKeys" has the key + assertThat(state.readKeys()).isEmpty(); + assertThat(state.modifiedKeys()).hasSize(1); + assertThat(state.modifiedKeys()).contains(A_KEY); + + // The original value should not have changed + assertThat(state.getOriginalValue(A_KEY)).isEqualTo(APPLE); + + // Commit should cause the value to be removed but not "put" + state.commit(); + Mockito.verify(state, Mockito.never()).putIntoDataSource(anyString(), anyString()); + Mockito.verify(state, Mockito.times(1)).removeFromDataSource(anyString()); + Mockito.verify(state, Mockito.times(1)).removeFromDataSource(A_KEY); + + // After a commit, the original value should have been removed + assertThat(state.getOriginalValue(A_KEY)).isNull(); + } + } + + @Nested + @DisplayName("iterator") + final class IteratorTest { + @Test + @DisplayName("Iterator on an empty state returns false to `hasNext`") + void hasNextFalseOnEmptyState() { + // Empty out the state first + state.remove(A_KEY); + state.remove(B_KEY); + state.commit(); + state.reset(); + + // OK, we have an empty state with an empty backend. So we can test this. + final var itr = state.keys(); + assertThat(itr.hasNext()).isFalse(); + } + + @Test + @DisplayName("Iterator on an empty state throws exception on `next`") + void nextThrowsOnEmptyState() { + // Empty out the state first + state.remove(A_KEY); + state.remove(B_KEY); + state.commit(); + state.reset(); + + // OK, we have an empty state with an empty backend. So we can test this. + final var itr = state.keys(); + assertThatThrownBy(itr::next).isInstanceOf(NoSuchElementException.class); + } + + @Test + @DisplayName("Iteration after `remove` and before `commit` includes modifications") + void testIterationAfterRemove() { + state.remove(B_KEY); + assertThat(state.keys()).toIterable().containsExactlyInAnyOrder(A_KEY); + } + + @Test + @DisplayName("Iteration after `remove` and `commit` includes modifications") + void testIterationAfterRemoveAndCommit() { + state.remove(B_KEY); + state.commit(); + assertThat(state.keys()).toIterable().contains(A_KEY); + } + + @Test + @DisplayName("Iteration after `put` and before `commit` includes modifications") + void testIterationAfterPut() { + state.put(C_KEY, CHERRY); + assertThat(state.keys()).toIterable().contains(A_KEY, B_KEY, C_KEY); + } + + @Test + @DisplayName("Iteration after `put` and `commit` includes modifications") + void testIterationAfterPutAndCommit() { + state.put(C_KEY, CHERRY); + state.commit(); + assertThat(state.keys()).toIterable().contains(A_KEY, B_KEY, C_KEY); + } + + @Test + @DisplayName("Iteration over keys with all modifications until the iteration concludes") + void iterateOverAllChanges() { + state.put(C_KEY, CHERRY); + state.put(D_KEY, DATE); + state.put(E_KEY, EGGPLANT); + state.put(F_KEY, FIG); + state.remove(A_KEY); + state.remove(E_KEY); + state.put(E_KEY, ELDERBERRY); + state.remove(F_KEY); + + final var itr = state.keys(); + final var foundKeys = new HashSet(); + for (int i = 0; i < 4; i++) { + assertThat(itr.hasNext()).isTrue(); + foundKeys.add(itr.next()); + } + + assertThat(itr.hasNext()).isFalse(); + assertThat(foundKeys).containsExactlyInAnyOrder(B_KEY, C_KEY, D_KEY, E_KEY); + } + + @Test + @DisplayName("Changes made after the iterator is created are not considered") + void iterateAfterChangesAreMade() { + final var itr = state.keys(); + state.put(C_KEY, CHERRY); + state.remove(A_KEY); + assertThat(itr).toIterable().containsExactlyInAnyOrder(A_KEY, B_KEY); + } + } + + @Test + @DisplayName("After making many modifications and reads, reset the state") + // Suppress the warning that we have too many asserts + @SuppressWarnings("java:S5961") + void reset() { + assertThat(state.get(C_KEY)).isNull(); + assertThat(state.get(A_KEY)).isEqualTo(APPLE); + assertThat(state.get(D_KEY)).isNull(); + assertThat(state.get(B_KEY)).isEqualTo(BANANA); + state.put(A_KEY, ACAI); + state.put(E_KEY, ELDERBERRY); + state.remove(B_KEY); + state.remove(F_KEY); + + assertThat(state.readKeys()).hasSize(4); + assertThat(state.readKeys()).contains(A_KEY); + assertThat(state.readKeys()).contains(B_KEY); + assertThat(state.readKeys()).contains(C_KEY); + assertThat(state.readKeys()).contains(D_KEY); + + assertThat(state.modifiedKeys()).hasSize(4); + assertThat(state.modifiedKeys()).contains(A_KEY); + assertThat(state.modifiedKeys()).contains(B_KEY); + assertThat(state.modifiedKeys()).contains(E_KEY); + assertThat(state.modifiedKeys()).contains(F_KEY); + + // The original values should not have changed + assertThat(state.getOriginalValue(A_KEY)).isEqualTo(APPLE); + assertThat(state.getOriginalValue(B_KEY)).isEqualTo(BANANA); + assertThat(state.getOriginalValue(C_KEY)).isNull(); + assertThat(state.getOriginalValue(D_KEY)).isNull(); + assertThat(state.getOriginalValue(E_KEY)).isNull(); + assertThat(state.getOriginalValue(F_KEY)).isNull(); + + state.reset(); + assertThat(state.readKeys()).isEmpty(); + assertThat(state.modifiedKeys()).isEmpty(); + + // After a reset, the original value should not have changed + assertThat(state.getOriginalValue(A_KEY)).isEqualTo(APPLE); + assertThat(state.getOriginalValue(B_KEY)).isEqualTo(BANANA); + assertThat(state.getOriginalValue(C_KEY)).isNull(); + assertThat(state.getOriginalValue(D_KEY)).isNull(); + assertThat(state.getOriginalValue(E_KEY)).isNull(); + assertThat(state.getOriginalValue(F_KEY)).isNull(); + } + + /** + * It is essential that EVERY node causes the exact same mutations on the backend data store in + * the exact same order. If we have a map containing all modifications, it may be that iterating + * the map on different nodes produces mutations in a different order, since maps do not + * guarantee deterministic ordering (unless you use some kind of sorted map). + * + *

    This kind of error is very hard to test directly, so we use a "hammer" test to just do + * many iterations on many threads concurrently to try to cause it to fail. If the test + * succeeds, it does not mean there is no bug, but if the test fails, it means there is + * DEFINITELY a bug. This test may be flaky, but if it ever fails, you must investigate! + */ + @Test + @DisplayName("Updates to the underlying data source are deterministic") + @Tag("Hammer") + void deterministicUpdates() { + // We will execute 'numIterations' iterations. This is parameterized with a system property + // so that we may dial the number up or down for certain types of tests (for example, + // nightly vs. continuous). Each iteration will construct a list of modifications (add, + // remove, modify). We will then create 39 threads each with their own WritableKVStateBase. + // Each will apply the modifications and commit them. We will then join back and verify + // that the order in which modifications were applied is identical. The hope is that if + // there is any non-determinism, we will tease it out by hammering it in this way. + final int numIterations = Integer.getInteger(NUM_ITERATIONS_ARG, 100); + final var numMutations = 20; + final var numThreads = 39; + final var executors = Executors.newFixedThreadPool(numThreads); + for (int i = 0; i < numIterations; i++) { + // Contains lambdas that will mutate the map in some way, and then populate the + // mutation list with random mutations (puts and removes) + List>> mutations = new ArrayList<>(); + for (int j = 0; j < numMutations; j++) { + final var key = random().nextInt(100); + if (random().nextInt(10) < 8) { + final var randomSuffix = randomString(10); + mutations.add(state -> state.put(key, randomSuffix)); + } else { + mutations.add(state -> state.remove(key)); + } + } + + // Now, spin up the threads. Each one will have its own unique state. The state + // is special in that it keeps track of the order in which remove and add calls + // were made to it, so we can compare those later. + final var latch = new CountDownLatch(numThreads); + final var mutationOrders = new ArrayList>(); + for (int t = 0; t < numThreads; t++) { + final var state = new MapWritableKVState(FRUIT_STATE_KEY) { + private final List keys = new ArrayList<>(); + + @Override + protected void putIntoDataSource(@NonNull Integer key, @NonNull String value) { + keys.add(key); + super.putIntoDataSource(key, value); + } + + @Override + protected void removeFromDataSource(@NonNull Integer key) { + keys.add(key); + super.removeFromDataSource(key); + } + }; + + // Add a bunch of random junk to the state and then reset it. + // If the backend data structures are not able to maintain deterministic + // behavior in the face of this (like a HashMap), then the test will fail. + for (int j = 0; j < random().nextInt(100, 1000); j++) { + state.put(random().nextInt(), randomString(100)); + } + state.reset(); + + mutationOrders.add(state.keys); + executors.execute(() -> { + for (var mutator : mutations) { + mutator.accept(state); + } + state.commit(); + latch.countDown(); + }); + } + + try { + if (!latch.await(1, TimeUnit.SECONDS)) { + fail("Failed to complete task in a reasonable time"); + } + } catch (InterruptedException e) { + fail("Test was interrupted"); + } + + // Now we verify that every single "mutationOrder" array is the same + List mutationOrder = mutationOrders.get(0); + for (final var mo : mutationOrders) { + assertThat(mo).containsExactlyElementsOf(mutationOrder); + } + } + } +} diff --git a/hedera-node/hedera-app-spi/src/test/java/com/hedera/node/app/spi/state/WritableSingletonStateBaseTest.java b/hedera-node/hedera-app-spi/src/test/java/com/swirlds/platform/state/spi/WritableSingletonStateBaseTest.java similarity index 98% rename from hedera-node/hedera-app-spi/src/test/java/com/hedera/node/app/spi/state/WritableSingletonStateBaseTest.java rename to hedera-node/hedera-app-spi/src/test/java/com/swirlds/platform/state/spi/WritableSingletonStateBaseTest.java index 182d2d5c8da6..770f2d3f465e 100644 --- a/hedera-node/hedera-app-spi/src/test/java/com/hedera/node/app/spi/state/WritableSingletonStateBaseTest.java +++ b/hedera-node/hedera-app-spi/src/test/java/com/swirlds/platform/state/spi/WritableSingletonStateBaseTest.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.hedera.node.app.spi.state; +package com.swirlds.platform.state.spi; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; @@ -23,7 +23,7 @@ import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; -class WritableSingletonStateBaseTest extends SingletonStateTestBase { +public class WritableSingletonStateBaseTest extends SingletonStateTestBase { @Override protected WritableSingletonStateBase createState() { diff --git a/hedera-node/hedera-app-spi/src/testFixtures/java/com/hedera/node/app/spi/fixtures/Scenarios.java b/hedera-node/hedera-app-spi/src/testFixtures/java/com/hedera/node/app/spi/fixtures/Scenarios.java index aeecac33b63e..570e823580bf 100644 --- a/hedera-node/hedera-app-spi/src/testFixtures/java/com/hedera/node/app/spi/fixtures/Scenarios.java +++ b/hedera-node/hedera-app-spi/src/testFixtures/java/com/hedera/node/app/spi/fixtures/Scenarios.java @@ -21,10 +21,10 @@ import com.hedera.hapi.node.base.KeyList; import com.hedera.hapi.node.state.primitives.ProtoBytes; import com.hedera.hapi.node.state.token.Account; -import com.hedera.node.app.spi.fixtures.state.MapReadableKVState; -import com.hedera.node.app.spi.fixtures.state.MapReadableStates; -import com.hedera.node.app.spi.state.ReadableStates; import com.hedera.pbj.runtime.io.buffer.Bytes; +import com.swirlds.platform.test.fixtures.state.MapReadableKVState; +import com.swirlds.platform.test.fixtures.state.MapReadableStates; +import com.swirlds.state.spi.ReadableStates; import java.util.Map; /** diff --git a/hedera-node/hedera-app-spi/src/testFixtures/java/com/hedera/node/app/spi/fixtures/state/MapWritableStates.java b/hedera-node/hedera-app-spi/src/testFixtures/java/com/hedera/node/app/spi/fixtures/state/MapWritableStates.java index af2746d4195d..aa2e5df68209 100644 --- a/hedera-node/hedera-app-spi/src/testFixtures/java/com/hedera/node/app/spi/fixtures/state/MapWritableStates.java +++ b/hedera-node/hedera-app-spi/src/testFixtures/java/com/hedera/node/app/spi/fixtures/state/MapWritableStates.java @@ -19,13 +19,15 @@ import static java.util.Objects.requireNonNull; import com.hedera.node.app.spi.state.CommittableWritableStates; -import com.hedera.node.app.spi.state.WritableKVState; -import com.hedera.node.app.spi.state.WritableKVStateBase; -import com.hedera.node.app.spi.state.WritableQueueState; -import com.hedera.node.app.spi.state.WritableQueueStateBase; -import com.hedera.node.app.spi.state.WritableSingletonState; -import com.hedera.node.app.spi.state.WritableSingletonStateBase; -import com.hedera.node.app.spi.state.WritableStates; +import com.swirlds.platform.state.spi.WritableKVStateBase; +import com.swirlds.platform.state.spi.WritableQueueStateBase; +import com.swirlds.platform.state.spi.WritableSingletonStateBase; +import com.swirlds.platform.test.fixtures.state.MapReadableStates; +import com.swirlds.platform.test.fixtures.state.MapWritableKVState; +import com.swirlds.state.spi.WritableKVState; +import com.swirlds.state.spi.WritableQueueState; +import com.swirlds.state.spi.WritableSingletonState; +import com.swirlds.state.spi.WritableStates; import edu.umd.cs.findbugs.annotations.NonNull; import java.util.Collections; import java.util.HashMap; diff --git a/hedera-node/hedera-app-spi/src/testFixtures/java/com/hedera/node/app/spi/fixtures/state/StateTestBase.java b/hedera-node/hedera-app-spi/src/testFixtures/java/com/hedera/node/app/spi/fixtures/state/StateTestBase.java deleted file mode 100644 index 4b9987a3c277..000000000000 --- a/hedera-node/hedera-app-spi/src/testFixtures/java/com/hedera/node/app/spi/fixtures/state/StateTestBase.java +++ /dev/null @@ -1,85 +0,0 @@ -/* - * Copyright (C) 2023-2024 Hedera Hashgraph, LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.hedera.node.app.spi.fixtures.state; - -import com.hedera.node.app.spi.fixtures.TestBase; - -public class StateTestBase extends TestBase { - protected static final String UNKNOWN_STATE_KEY = "BOGUS_STATE_KEY"; - protected static final String UNKNOWN_KEY = "BOGUS_KEY"; - - protected static final String FRUIT_STATE_KEY = "FRUIT"; - protected static final String ANIMAL_STATE_KEY = "ANIMAL"; - protected static final String SPACE_STATE_KEY = "SPACE"; - protected static final String STEAM_STATE_KEY = "STEAM"; - protected static final String COUNTRY_STATE_KEY = "COUNTRY"; - - protected static final String A_KEY = "A"; - protected static final String B_KEY = "B"; - protected static final String C_KEY = "C"; - protected static final String D_KEY = "D"; - protected static final String E_KEY = "E"; - protected static final String F_KEY = "F"; - protected static final String G_KEY = "G"; - - protected static final String APPLE = "Apple"; - protected static final String ACAI = "Acai"; - protected static final String BANANA = "Banana"; - protected static final String BLACKBERRY = "BlackBerry"; - protected static final String BLUEBERRY = "BlueBerry"; - protected static final String CHERRY = "Cherry"; - protected static final String CRANBERRY = "Cranberry"; - protected static final String DATE = "Date"; - protected static final String DRAGONFRUIT = "DragonFruit"; - protected static final String EGGPLANT = "Eggplant"; - protected static final String ELDERBERRY = "ElderBerry"; - protected static final String FIG = "Fig"; - protected static final String FEIJOA = "Feijoa"; - protected static final String GRAPE = "Grape"; - - protected static final String AARDVARK = "Aardvark"; - protected static final String BEAR = "Bear"; - protected static final String CUTTLEFISH = "Cuttlefish"; - protected static final String DOG = "Dog"; - protected static final String EMU = "Emu"; - protected static final String FOX = "Fox"; - protected static final String GOOSE = "Goose"; - - protected static final String ASTRONAUT = "Astronaut"; - protected static final String BLASTOFF = "Blastoff"; - protected static final String COMET = "Comet"; - protected static final String DRACO = "Draco"; - protected static final String EXOPLANET = "Exoplanet"; - protected static final String FORCE = "Force"; - protected static final String GRAVITY = "Gravity"; - - protected static final String ART = "Art"; - protected static final String BIOLOGY = "Biology"; - protected static final String CHEMISTRY = "Chemistry"; - protected static final String DISCIPLINE = "Discipline"; - protected static final String ECOLOGY = "Ecology"; - protected static final String FIELDS = "Fields"; - protected static final String GEOMETRY = "Geometry"; - - protected static final String AUSTRALIA = "Australia"; - protected static final String BRAZIL = "Brazil"; - protected static final String CHAD = "Chad"; - protected static final String DENMARK = "Denmark"; - protected static final String ESTONIA = "Estonia"; - protected static final String FRANCE = "France"; - protected static final String GHANA = "Ghana"; -} diff --git a/hedera-node/hedera-app-spi/src/testFixtures/java/module-info.java b/hedera-node/hedera-app-spi/src/testFixtures/java/module-info.java index 5224d6c7991d..7943e0ddfa64 100644 --- a/hedera-node/hedera-app-spi/src/testFixtures/java/module-info.java +++ b/hedera-node/hedera-app-spi/src/testFixtures/java/module-info.java @@ -8,11 +8,14 @@ requires transitive com.hedera.node.hapi; requires transitive com.hedera.pbj.runtime; requires transitive com.swirlds.config.api; + requires transitive com.swirlds.platform.core.test.fixtures; + requires transitive com.swirlds.state.api; requires transitive org.apache.logging.log4j; requires transitive org.assertj.core; requires transitive org.junit.jupiter.api; requires com.hedera.node.app.hapi.utils; requires com.swirlds.common; + requires com.swirlds.platform.core; requires org.apache.logging.log4j.core; // Temporarily needed until FakePreHandleContext can be removed diff --git a/hedera-node/hedera-app/build.gradle.kts b/hedera-node/hedera-app/build.gradle.kts index 2a652605bf3f..fd3bd6bf384b 100644 --- a/hedera-node/hedera-app/build.gradle.kts +++ b/hedera-node/hedera-app/build.gradle.kts @@ -15,9 +15,10 @@ */ plugins { - id("com.hedera.hashgraph.conventions") - id("com.hedera.hashgraph.benchmark-conventions") - id("com.hedera.hashgraph.java-test-fixtures") + id("com.hedera.gradle.services") + id("com.hedera.gradle.services-publish") + id("com.hedera.gradle.benchmark") + id("com.hedera.gradle.java-test-fixtures") } description = "Hedera Application - Implementation" @@ -38,7 +39,7 @@ testModuleInfo { requires("com.hedera.node.config.test.fixtures") requires("com.google.jimfs") requires("com.swirlds.config.extensions.test.fixtures") - requires("io.github.classgraph") + requires("com.swirlds.platform.core.test.fixtures") requires("org.assertj.core") requires("org.hamcrest") requires("org.junit.jupiter.api") @@ -56,11 +57,12 @@ itestModuleInfo { requires("com.hedera.node.app.spi.test.fixtures") requires("com.hedera.node.config") requires("com.hedera.node.config.test.fixtures") - requires("com.hedera.node.hapi") requires("com.github.spotbugs.annotations") requires("com.hedera.pbj.runtime") requires("com.swirlds.common") requires("com.swirlds.config.api") + requires("com.swirlds.platform.core.test.fixtures") + requires("com.hedera.node.hapi") requires("com.swirlds.metrics.api") requires("grpc.netty") requires("grpc.stub") @@ -102,6 +104,7 @@ xtestModuleInfo { requires("com.swirlds.config.extensions.test.fixtures") requires("com.swirlds.metrics.api") requires("com.swirlds.platform.core") + requires("com.swirlds.state.api") requires("dagger") requires("headlong") requires("javax.inject") diff --git a/hedera-node/hedera-app/src/itest/java/grpc/GrpcTestBase.java b/hedera-node/hedera-app/src/itest/java/grpc/GrpcTestBase.java index 8823364aa965..ebc593354cee 100644 --- a/hedera-node/hedera-app/src/itest/java/grpc/GrpcTestBase.java +++ b/hedera-node/hedera-app/src/itest/java/grpc/GrpcTestBase.java @@ -23,7 +23,6 @@ import com.hedera.node.app.grpc.impl.netty.NettyGrpcServerManager; import com.hedera.node.app.services.ServicesRegistry; import com.hedera.node.app.spi.Service; -import com.hedera.node.app.spi.fixtures.TestBase; import com.hedera.node.app.spi.fixtures.state.NoOpGenesisRecordsBuilder; import com.hedera.node.app.state.merkle.MerkleSchemaRegistry; import com.hedera.node.app.workflows.ingest.IngestWorkflow; @@ -44,6 +43,7 @@ import com.swirlds.config.api.ConfigurationBuilder; import com.swirlds.config.api.source.ConfigSource; import com.swirlds.metrics.api.Metrics; +import com.swirlds.platform.test.fixtures.state.TestBase; import edu.umd.cs.findbugs.annotations.NonNull; import io.grpc.CallOptions; import io.grpc.Channel; diff --git a/hedera-node/hedera-app/src/jmh/java/com/hedera/node/app/signature/impl/DoNothingCryptoEngine.java b/hedera-node/hedera-app/src/jmh/java/com/hedera/node/app/signature/impl/DoNothingCryptoEngine.java index c15192f91bb5..89ad5cc9cd7f 100644 --- a/hedera-node/hedera-app/src/jmh/java/com/hedera/node/app/signature/impl/DoNothingCryptoEngine.java +++ b/hedera-node/hedera-app/src/jmh/java/com/hedera/node/app/signature/impl/DoNothingCryptoEngine.java @@ -42,6 +42,11 @@ public Hash digestSync(SerializableHashable serializableHashable, DigestType dig return null; } + @Override + public byte[] digestBytesSync(final byte[] message, final DigestType digestType) { + return null; + } + @Override public Hash getNullHash(DigestType digestType) { return null; diff --git a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/Hedera.java b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/Hedera.java index c9b8a0e37850..d51294893f72 100644 --- a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/Hedera.java +++ b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/Hedera.java @@ -92,10 +92,8 @@ import com.hedera.node.app.service.util.impl.UtilServiceImpl; import com.hedera.node.app.services.ServicesRegistryImpl; import com.hedera.node.app.spi.HapiUtils; -import com.hedera.node.app.spi.state.WritableSingletonStateBase; import com.hedera.node.app.spi.workflows.record.GenesisRecordsBuilder; import com.hedera.node.app.state.HederaLifecyclesImpl; -import com.hedera.node.app.state.HederaState; import com.hedera.node.app.state.merkle.MerkleHederaState; import com.hedera.node.app.state.recordcache.RecordCacheService; import com.hedera.node.app.throttle.CongestionThrottleService; @@ -110,6 +108,7 @@ import com.swirlds.common.constructable.ClassConstructorPair; import com.swirlds.common.constructable.ConstructableRegistry; import com.swirlds.common.constructable.ConstructableRegistryException; +import com.swirlds.common.constructable.RuntimeConstructable; import com.swirlds.common.crypto.CryptographyHolder; import com.swirlds.common.platform.NodeId; import com.swirlds.fcqueue.FCQueue; @@ -119,6 +118,7 @@ import com.swirlds.platform.listeners.ReconnectCompleteListener; import com.swirlds.platform.listeners.StateWriteToDiskCompleteListener; import com.swirlds.platform.state.PlatformState; +import com.swirlds.platform.state.spi.WritableSingletonStateBase; import com.swirlds.platform.system.InitTrigger; import com.swirlds.platform.system.Platform; import com.swirlds.platform.system.Round; @@ -128,6 +128,7 @@ import com.swirlds.platform.system.events.Event; import com.swirlds.platform.system.status.PlatformStatus; import com.swirlds.platform.system.transaction.Transaction; +import com.swirlds.state.HederaState; import com.swirlds.virtualmap.VirtualMap; import edu.umd.cs.findbugs.annotations.NonNull; import edu.umd.cs.findbugs.annotations.Nullable; @@ -245,9 +246,13 @@ public final class Hedera implements SwirldMain { =================================================================================================================*/ /** - * Create a new Hedera instance. + * Creates a Hedera node that will register its own and its services' {@link RuntimeConstructable} factories + * with the given {@link ConstructableRegistry}. * - * @param constructableRegistry The registry to use during the deserialization process + *

    This constructor instantiates several infrastructure components that would be better injected to + * improve testability and reuse. (For example, the {@link BootstrapConfigProviderImpl}.) + * + * @param constructableRegistry the registry to register {@link RuntimeConstructable} factories with */ public Hedera(@NonNull final ConstructableRegistry constructableRegistry) { requireNonNull(constructableRegistry); @@ -288,6 +293,11 @@ public Hedera(@NonNull final ConstructableRegistry constructableRegistry) { // Create all the service implementations logger.info("Registering services"); + // These services are being stored as static fields ONLY to support the mono-to-mod + // migration. That is, these Service instances own schemas that need access to mono + // state children that aren't available until the platform calls init() on the saved + // mono state; so we make these Services instances available to our onStateInitialized() + // method by storing them as static fields. ENTITY_SERVICE = new EntityIdService(); CONSENSUS_SERVICE = new ConsensusServiceImpl(); FILE_SERVICE = new FileServiceImpl(bootstrapConfigProvider); @@ -319,7 +329,7 @@ public Hedera(@NonNull final ConstructableRegistry constructableRegistry) { FEE_SERVICE, CONGESTION_THROTTLE_SERVICE, new NetworkServiceImpl()) - .forEach(service -> servicesRegistry.register(service, version)); + .forEach(servicesRegistry::register); // Register MerkleHederaState with the ConstructableRegistry, so we can use a constructor OTHER THAN the default // constructor to make sure it has the config and other info it needs to be created correctly. @@ -333,13 +343,6 @@ public Hedera(@NonNull final ConstructableRegistry constructableRegistry) { } } - /** - * Gets the port the gRPC server is listening on, or {@code -1} if there is no server listening. - */ - public int getGrpcPort() { - return daggerApp.grpcServerManager().port(); - } - /** * Indicates whether this node is UP and ready for business. * @@ -389,10 +392,11 @@ public SoftwareVersion getSoftwareVersion() { /** * {@inheritDoc} * - *

    Called by the platform ONLY during genesis (that is, if there is no saved state). However, it is also - * called indirectly by {@link ConstructableRegistry} due to registration in this class' constructor. + *

    Called by the platform to either build a genesis state, or to deserialize a Merkle node in a saved state + * with the stable class id of the Services Merkle tree root during genesis (c.f. our constructor, in which + * we register this method as the factory for the {@literal 0x8e300b0dfdafbb1a} class id). * - * @return A new {@link SwirldState} instance. + * @return a Services state object */ @Override @NonNull @@ -726,10 +730,7 @@ private void unmarkMigrationRecordsStreamed(HederaState state) { final var nextBlockInfo = currentBlockInfo.copyBuilder().migrationRecordsStreamed(false).build(); blockInfoState.put(nextBlockInfo); - logger.info( - "Unmarked migration records streamed with block info {} with hash {}", - nextBlockInfo, - blockInfoState.hashCode()); + logger.info("Unmarked migration records streamed"); ((WritableSingletonStateBase) blockInfoState).commit(); } diff --git a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/ServicesMain.java b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/ServicesMain.java index 17c47fa75950..8d1275026b11 100644 --- a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/ServicesMain.java +++ b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/ServicesMain.java @@ -29,8 +29,10 @@ import static java.util.Objects.requireNonNull; import com.hedera.node.app.config.ConfigProviderImpl; +import com.hedera.node.app.state.merkle.MerkleHederaState; import com.hedera.node.config.data.HederaConfig; import com.swirlds.common.constructable.ConstructableRegistry; +import com.swirlds.common.constructable.RuntimeConstructable; import com.swirlds.common.context.PlatformContext; import com.swirlds.common.io.utility.FileUtils; import com.swirlds.common.platform.NodeId; @@ -42,6 +44,8 @@ import com.swirlds.platform.config.legacy.ConfigurationException; import com.swirlds.platform.config.legacy.LegacyConfigProperties; import com.swirlds.platform.config.legacy.LegacyConfigPropertiesLoader; +import com.swirlds.platform.state.PlatformState; +import com.swirlds.platform.system.InitTrigger; import com.swirlds.platform.system.Platform; import com.swirlds.platform.system.SoftwareVersion; import com.swirlds.platform.system.SwirldMain; @@ -117,9 +121,37 @@ public void run() { } /** - * Launches the application. + * Launches Services directly, without use of the "app browser" from + * {@link com.swirlds.platform.Browser}. The approximate startup sequence is: + *

      + *
    1. Scan the classpath for {@link RuntimeConstructable} classes, + * registering their no-op constructors as the default factories for their + * class ids.
    2. + *
    3. Create the application's {@link Hedera} singleton, which overrides + * the default factory for the stable {@literal 0x8e300b0dfdafbb1a} class + * id of the Services Merkle tree root with a reference to its + * {@link Hedera#newState()} method.
    4. + *
    5. Determine this node's self id by searching the config.txt + * in the working directory for any address book entries with IP addresses + * local to this machine; if there is there is more than one such entry, + * fail unless the command line args include a {@literal -local N} arg.
    6. + *
    7. Build a {@link Platform} instance from Services application metadata + * and the working directory settings.txt, providing the same + * {@link Hedera#newState()} method reference as the genesis state factory. + * (IMPORTANT: This step instantiates and invokes + * {@link SwirldState#init(Platform, PlatformState, InitTrigger, SoftwareVersion)} + * on a {@link MerkleHederaState} instance that delegates the call back to our + * Hedera instance.)
    8. + *
    9. Call {@link Hedera#init(Platform, NodeId)} to complete startup phase + * validation and register notification listeners on the platform.
    10. + *
    11. Invoke {@link Platform#start()}.
    12. + *
    * - * @param args First arg, if specified, will be the node ID + *

    Please see the startup-phase-lifecycle.png in this directory to visualize + * the sequence of events in the startup phase and the centrality of the {@link Hedera} + * singleton. + * + * @param args optionally, what node id to run; required if the address book is ambiguous */ public static void main(final String... args) throws Exception { BootstrapUtils.setupConstructableRegistry(); @@ -166,6 +198,29 @@ public static void main(final String... args) throws Exception { builder.withPreviousSoftwareVersionClassId(0x6f2b1bc2df8cbd0bL /* SerializableSemVers.CLASS_ID */); builder.withPlatformContext(platformContext); + // IMPORTANT: A surface-level reading of this method will undersell the centrality + // of the Hedera instance. It is actually omnipresent throughout both the startup + // and runtime phases of the application. + // + // Let's see why. When we build the platform, the builder will either: + // (1) Create a genesis state; or, + // (2) Deserialize a saved state. + // In both cases the state object will be created by the hedera::newState method + // reference bound to our Hedera instance. Because, + // (1) We provided this method as the genesis state factory right above; and, + // (2) Our Hedera instance's constructor registered its newState() method with the + // ConstructableRegistry as the factory for the Services Merkle tree class id. + // + // Now, note that hedera::newState returns MerkleHederaState instances that delegate + // their lifecycle methods to an injected instance of HederaLifecycles---and + // hedera::newState injects an instance of HederaLifecyclesImpl which primarily + // delegates these calls back to the Hedera instance itself. + // + // Thus, the Hedera instance centralizes nearly all the setup and runtime logic for the + // application. It implements this logic by instantiating a Dagger2 @Singleton component + // whose object graph roots include the Ingest, PreHandle, Handle, and Query workflows; + // as well as other infrastructure components that need to be initialized or accessed + // at specific points in the Swirlds application lifecycle. final Platform platform = builder.build(); hedera.init(platform, selfId); platform.start(); @@ -175,7 +230,7 @@ public static void main(final String... args) throws Exception { /** * Selects the node to run locally from either the command line arguments or the address book. * - * @param nodesToRun the list of nodes configured to run based on the address book. + * @param nodesToRun the list of nodes configured to run based on the address book. * @param localNodesToStart the node ids specified on the command line. * @return the node which should be run locally. * @throws ConfigurationException if more than one node would be started or the requested node is not configured. diff --git a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/fees/ChildFeeContextImpl.java b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/fees/ChildFeeContextImpl.java index 1fdf7b1929e5..9664dbe52b0c 100644 --- a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/fees/ChildFeeContextImpl.java +++ b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/fees/ChildFeeContextImpl.java @@ -26,10 +26,10 @@ import com.hedera.node.app.spi.authorization.Authorizer; import com.hedera.node.app.spi.fees.FeeCalculator; import com.hedera.node.app.spi.fees.FeeContext; -import com.hedera.node.app.state.HederaState; import com.hedera.node.app.workflows.dispatcher.ReadableStoreFactory; import com.hedera.node.app.workflows.handle.HandleContextImpl; import com.swirlds.config.api.Configuration; +import com.swirlds.state.HederaState; import edu.umd.cs.findbugs.annotations.NonNull; import edu.umd.cs.findbugs.annotations.Nullable; import java.util.Objects; diff --git a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/fees/ExchangeRateManager.java b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/fees/ExchangeRateManager.java index bdcb1fac9b2f..f72378fcb21b 100644 --- a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/fees/ExchangeRateManager.java +++ b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/fees/ExchangeRateManager.java @@ -26,7 +26,6 @@ import com.hedera.hapi.node.transaction.ExchangeRateSet; import com.hedera.node.app.spi.fees.ExchangeRateInfo; import com.hedera.node.app.spi.workflows.HandleException; -import com.hedera.node.app.state.HederaState; import com.hedera.node.app.util.FileUtilities; import com.hedera.node.config.ConfigProvider; import com.hedera.node.config.data.AccountsConfig; @@ -34,8 +33,8 @@ import com.hedera.node.config.data.HederaConfig; import com.hedera.node.config.data.RatesConfig; import com.hedera.pbj.runtime.ParseException; -import com.hedera.pbj.runtime.UncheckedParseException; import com.hedera.pbj.runtime.io.buffer.Bytes; +import com.swirlds.state.HederaState; import edu.umd.cs.findbugs.annotations.NonNull; import edu.umd.cs.findbugs.annotations.Nullable; import java.math.BigInteger; @@ -43,6 +42,8 @@ import java.util.stream.LongStream; import javax.inject.Inject; import javax.inject.Singleton; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; /** * Parses the exchange rate information and makes it available to the workflows. @@ -63,6 +64,7 @@ */ @Singleton public final class ExchangeRateManager { + private static final Logger log = LogManager.getLogger(ExchangeRateManager.class); private static final BigInteger ONE_HUNDRED = BigInteger.valueOf(100); @@ -80,31 +82,29 @@ public void init(@NonNull final HederaState state, @NonNull final Bytes bytes) { requireNonNull(state, "state must not be null"); requireNonNull(bytes, "bytes must not be null"); - // First we try to read midnightRates from state + // Re-use the same code path to set the active rates as does the SystemFileUpdateFacility + // IMPORTANT - if we first initialized the midnight rates, we couldn't reuse this code path + // because it overwrites the midnight rates with the current rates + systemUpdate(bytes); + + // Now fix the midnight rates to what is in state (note that all post-initialization + // Services states must have a non-null midnight rates set, even at genesis, as + // FeeService schema migrate() creates them in that case) midnightRates = state.getReadableStates(FeeService.NAME) .getSingleton(FeeService.MIDNIGHT_RATES_STATE_KEY) .get(); - if (midnightRates != ExchangeRateSet.DEFAULT) { - // midnightRates were found in state, a regular update is sufficient - // - systemUpdate(bytes); - } - - // If midnightRates were not found in state, we initialize them from the file - try { - midnightRates = ExchangeRateSet.PROTOBUF.parse(bytes.toReadableSequentialData()); - } catch (ParseException e) { - // an error here is fatal and needs to be handled by the general initialization code - throw new UncheckedParseException(e); - } - this.currentExchangeRateInfo = new ExchangeRateInfoImpl(midnightRates); + requireNonNull(midnightRates, "an initialized state must have a midnight rates set"); + log.info( + "Initializing exchange rates with midnight rates {} and active rates {}", + midnightRates, + currentExchangeRateInfo.exchangeRates()); } /** * Updates the exchange rate information. MUST BE CALLED on the handle thread! * - * @param bytes The protobuf encoded {@link ExchangeRateSet}. - * @param payerId The payer of the transaction that triggered this update. + * @param bytes The protobuf encoded {@link ExchangeRateSet}. + * @param payerId The payer of the transaction that triggered this update. */ public void update(@NonNull final Bytes bytes, @NonNull AccountID payerId) { requireNonNull(payerId, "payerId must not be null"); @@ -147,7 +147,6 @@ private void internalUpdate(@NonNull final Bytes bytes, @Nullable AccountID paye // Update the current ExchangeRateInfo and eventually the midnightRates this.currentExchangeRateInfo = new ExchangeRateInfoImpl(proposedRates); - // TODO: save the mignightRates in state only if (isAdminUser(payerId, accountsConfig)) { midnightRates = proposedRates; } @@ -167,15 +166,22 @@ private boolean isAdminUser(@NonNull final AccountID accountID, AccountsConfig a return num == accountsConfig.systemAdmin(); } + /** + * Updates the midnight rates to the current exchange rates, both internally and in the given state. + * + * @param state the {@link HederaState} to update the midnight rates in + */ public void updateMidnightRates(@NonNull final HederaState state) { midnightRates = currentExchangeRateInfo.exchangeRates(); final var singleton = state.getWritableStates(FeeService.NAME) .getSingleton(FeeService.MIDNIGHT_RATES_STATE_KEY); singleton.put(midnightRates); + log.info("Updated midnight rates to {}", midnightRates); } /** * Gets the current {@link ExchangeRateSet}. MUST BE CALLED ON THE HANDLE THREAD!! + * * @return The current {@link ExchangeRateSet}. */ @NonNull @@ -188,7 +194,7 @@ public ExchangeRateSet exchangeRates() { * THREAD!! * * @param consensusTime The consensus time. If after the expiration time of the current rate, the next rate will - * be returned. Otherwise, the current rate will be returned. + * be returned. Otherwise, the current rate will be returned. * @return The {@link ExchangeRate} that should be used as of the given consensus time. */ @NonNull diff --git a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/fees/FeeManager.java b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/fees/FeeManager.java index aed5c7647e60..298aba3d53f0 100644 --- a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/fees/FeeManager.java +++ b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/fees/FeeManager.java @@ -16,6 +16,7 @@ package com.hedera.node.app.fees; +import static com.hedera.hapi.node.base.HederaFunctionality.GET_ACCOUNT_DETAILS; import static com.hedera.hapi.node.base.ResponseCodeEnum.SUCCESS; import static java.util.Objects.requireNonNull; @@ -220,7 +221,11 @@ public FeeData getFeeData( // Now, lookup the fee data for the transaction type. final var result = feeDataMap.get(new Entry(functionality, subType)); if (result == null) { - logger.warn("Using default usage prices to calculate fees for {}!", functionality); + // There is no point in adding a fee schedule entry for a privileged query type, + // since privileged queries are not charged fees in the first place + if (functionality != GET_ACCOUNT_DETAILS) { + logger.warn("Using default usage prices to calculate fees for {}!", functionality); + } return DEFAULT_FEE_DATA; } return result; diff --git a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/fees/FeeService.java b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/fees/FeeService.java index 444c7cce3d85..6c1e5891e8a6 100644 --- a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/fees/FeeService.java +++ b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/fees/FeeService.java @@ -26,8 +26,8 @@ import com.hedera.node.app.spi.state.Schema; import com.hedera.node.app.spi.state.SchemaRegistry; import com.hedera.node.app.spi.state.StateDefinition; -import com.hedera.node.app.spi.state.WritableSingletonStateBase; import com.hedera.node.config.data.BootstrapConfig; +import com.swirlds.platform.state.spi.WritableSingletonStateBase; import edu.umd.cs.findbugs.annotations.NonNull; import edu.umd.cs.findbugs.annotations.Nullable; import java.util.Set; diff --git a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/grpc/impl/MethodBase.java b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/grpc/impl/MethodBase.java index 7779e77cc402..0fc324e5c511 100644 --- a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/grpc/impl/MethodBase.java +++ b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/grpc/impl/MethodBase.java @@ -109,17 +109,20 @@ public abstract class MethodBase implements ServerCalls.UnaryMethod responseObserver) { - try { - // Track the number of times this method has been called - callsReceivedCounter.increment(); - callsReceivedSpeedometer.cycle(); + // Track the number of times this method has been called + callsReceivedCounter.increment(); + callsReceivedSpeedometer.cycle(); - // Fail-fast if the request is too large (Note that the request buffer is sized to allow exactly - // 1 more byte than MAX_MESSAGE_SIZE, so we can detect this case). - if (requestBuffer.length() > MAX_MESSAGE_SIZE) { - throw new RuntimeException("More than " + MAX_MESSAGE_SIZE + " received"); - } + // Fail-fast if the request is too large (Note that the request buffer is sized to allow exactly + // 1 more byte than MAX_MESSAGE_SIZE, so we can detect this case). + if (requestBuffer.length() > MAX_MESSAGE_SIZE) { + callsFailedCounter.increment(); + final var exception = new RuntimeException("More than " + MAX_MESSAGE_SIZE + " received"); + responseObserver.onError(exception); + return; + } + try { // Prepare the response buffer final var responseBuffer = BUFFER_THREAD_LOCAL.get(); responseBuffer.reset(); diff --git a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/ids/EntityIdService.java b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/ids/EntityIdService.java index dd000ffc39fd..385148d19bfc 100644 --- a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/ids/EntityIdService.java +++ b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/ids/EntityIdService.java @@ -20,11 +20,11 @@ import com.hedera.hapi.node.state.common.EntityNumber; import com.hedera.node.app.spi.Service; import com.hedera.node.app.spi.state.MigrationContext; -import com.hedera.node.app.spi.state.ReadableStates; import com.hedera.node.app.spi.state.Schema; import com.hedera.node.app.spi.state.SchemaRegistry; import com.hedera.node.app.spi.state.StateDefinition; import com.hedera.node.config.data.HederaConfig; +import com.swirlds.state.spi.ReadableStates; import edu.umd.cs.findbugs.annotations.NonNull; import java.util.Set; import org.apache.logging.log4j.LogManager; diff --git a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/ids/WritableEntityIdStore.java b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/ids/WritableEntityIdStore.java index 3f09ed760acc..958f59933f30 100644 --- a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/ids/WritableEntityIdStore.java +++ b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/ids/WritableEntityIdStore.java @@ -19,8 +19,8 @@ import static java.util.Objects.requireNonNull; import com.hedera.hapi.node.state.common.EntityNumber; -import com.hedera.node.app.spi.state.WritableSingletonState; -import com.hedera.node.app.spi.state.WritableStates; +import com.swirlds.state.spi.WritableSingletonState; +import com.swirlds.state.spi.WritableStates; import edu.umd.cs.findbugs.annotations.NonNull; /** diff --git a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/info/NetworkInfoImpl.java b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/info/NetworkInfoImpl.java index d3f4a78b3934..b8b9dd13e374 100644 --- a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/info/NetworkInfoImpl.java +++ b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/info/NetworkInfoImpl.java @@ -105,8 +105,8 @@ private NodeInfo nodeInfo(@NonNull final NodeId nodeId) { final var address = platformAddressBook.getAddress(nodeId); return NodeInfoImpl.fromAddress(address); } catch (NoSuchElementException e) { - // The node ID is not in the address book - logger.warn("Unable to find node with id {} in the platform address book", nodeId, e); + // The node ID is not in the address book; this is a normal condition + // if user error leads to a request for a non-existent node return null; } catch (IllegalArgumentException e) { logger.warn("Unable to parse memo of node with id {} in the platform address book", nodeId, e); diff --git a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/meta/StoreModule.java b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/meta/StoreModule.java index e3fd75194dec..c49c716e7666 100644 --- a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/meta/StoreModule.java +++ b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/meta/StoreModule.java @@ -16,9 +16,9 @@ package com.hedera.node.app.meta; -import com.hedera.node.app.state.HederaState; import com.hedera.node.app.workflows.dispatcher.ReadableStoreFactory; import com.swirlds.common.utility.AutoCloseableWrapper; +import com.swirlds.state.HederaState; import dagger.Module; import dagger.Provides; import java.util.function.Supplier; diff --git a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/metrics/StoreMetricsImpl.java b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/metrics/StoreMetricsImpl.java index a93d887c614d..1aa7744b1669 100644 --- a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/metrics/StoreMetricsImpl.java +++ b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/metrics/StoreMetricsImpl.java @@ -18,9 +18,9 @@ import static java.util.Objects.requireNonNull; -import com.hedera.node.app.spi.metrics.StoreMetrics; import com.swirlds.common.metrics.FunctionGauge.Config; import com.swirlds.metrics.api.Metrics; +import com.swirlds.state.spi.metrics.StoreMetrics; import edu.umd.cs.findbugs.annotations.NonNull; import java.util.concurrent.atomic.AtomicLong; diff --git a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/metrics/StoreMetricsServiceImpl.java b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/metrics/StoreMetricsServiceImpl.java index 9164373d8872..96b1e5d753d2 100644 --- a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/metrics/StoreMetricsServiceImpl.java +++ b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/metrics/StoreMetricsServiceImpl.java @@ -16,9 +16,9 @@ package com.hedera.node.app.metrics; -import com.hedera.node.app.spi.metrics.StoreMetrics; import com.hedera.node.app.spi.metrics.StoreMetricsService; import com.swirlds.metrics.api.Metrics; +import com.swirlds.state.spi.metrics.StoreMetrics; import edu.umd.cs.findbugs.annotations.NonNull; import java.util.EnumMap; import java.util.Map; diff --git a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/platform/PlatformModule.java b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/platform/PlatformModule.java index 09c8e0a9fc80..e44df50b6958 100644 --- a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/platform/PlatformModule.java +++ b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/platform/PlatformModule.java @@ -45,7 +45,7 @@ static NodeId selfId(@NonNull final Platform platform) { @Provides @Singleton static Signer signer(@NonNull final Platform platform) { - return platform; + return platform::sign; } @Provides diff --git a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/records/BlockRecordManager.java b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/records/BlockRecordManager.java index 626c3da45e22..ce977f27b38b 100644 --- a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/records/BlockRecordManager.java +++ b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/records/BlockRecordManager.java @@ -18,9 +18,9 @@ import com.hedera.hapi.node.state.blockrecords.BlockInfo; import com.hedera.node.app.spi.records.BlockRecordInfo; -import com.hedera.node.app.state.HederaState; import com.hedera.node.app.state.SingleTransactionRecord; import com.swirlds.platform.state.PlatformState; +import com.swirlds.state.HederaState; import edu.umd.cs.findbugs.annotations.NonNull; import java.time.Instant; import java.util.stream.Stream; @@ -89,7 +89,7 @@ boolean startUserTransaction( * be called after the user transaction has been committed to state and is 100% done. It must include the record of * the user transaction along with all preceding child transactions and any child or transactions after. System * transactions are treated as though they were user transactions, calling - * {@link #startUserTransaction(Instant, HederaState)} and {@link #endUserTransaction(Stream, HederaState)}. + * {@link #startUserTransaction(Instant, HederaState, PlatformState)} and this method. * * @param recordStreamItems Stream of records produced while handling the user transaction * @param state The state to read {@link BlockInfo} from diff --git a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/records/BlockRecordService.java b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/records/BlockRecordService.java index 35a0f1e20f43..9f4832d24cdc 100644 --- a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/records/BlockRecordService.java +++ b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/records/BlockRecordService.java @@ -26,8 +26,12 @@ import com.hedera.node.app.service.mono.state.merkle.MerkleNetworkContext; import com.hedera.node.app.service.mono.stream.RecordsRunningHashLeaf; import com.hedera.node.app.spi.Service; -import com.hedera.node.app.spi.state.*; +import com.hedera.node.app.spi.state.MigrationContext; +import com.hedera.node.app.spi.state.Schema; +import com.hedera.node.app.spi.state.SchemaRegistry; +import com.hedera.node.app.spi.state.StateDefinition; import com.hedera.pbj.runtime.io.buffer.Bytes; +import com.swirlds.platform.state.spi.WritableSingletonStateBase; import edu.umd.cs.findbugs.annotations.NonNull; import edu.umd.cs.findbugs.annotations.Nullable; import java.io.IOException; diff --git a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/records/ReadableBlockRecordStore.java b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/records/ReadableBlockRecordStore.java index efcacb029452..84a19b6f73bc 100644 --- a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/records/ReadableBlockRecordStore.java +++ b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/records/ReadableBlockRecordStore.java @@ -19,8 +19,8 @@ import static java.util.Objects.requireNonNull; import com.hedera.hapi.node.state.blockrecords.BlockInfo; -import com.hedera.node.app.spi.state.ReadableSingletonState; -import com.hedera.node.app.spi.state.ReadableStates; +import com.swirlds.state.spi.ReadableSingletonState; +import com.swirlds.state.spi.ReadableStates; import edu.umd.cs.findbugs.annotations.NonNull; public class ReadableBlockRecordStore { diff --git a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/records/impl/BlockRecordManagerImpl.java b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/records/impl/BlockRecordManagerImpl.java index c2d6c0620e80..8236e1f37a6d 100644 --- a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/records/impl/BlockRecordManagerImpl.java +++ b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/records/impl/BlockRecordManagerImpl.java @@ -26,8 +26,6 @@ import com.hedera.hapi.node.state.blockrecords.RunningHashes; import com.hedera.node.app.records.BlockRecordManager; import com.hedera.node.app.records.BlockRecordService; -import com.hedera.node.app.spi.state.WritableSingletonStateBase; -import com.hedera.node.app.state.HederaState; import com.hedera.node.app.state.SingleTransactionRecord; import com.hedera.node.config.ConfigProvider; import com.hedera.node.config.data.BlockRecordStreamConfig; @@ -36,6 +34,8 @@ import com.swirlds.common.crypto.Hash; import com.swirlds.common.stream.LinkedObjectStreamUtilities; import com.swirlds.platform.state.PlatformState; +import com.swirlds.platform.state.spi.WritableSingletonStateBase; +import com.swirlds.state.HederaState; import edu.umd.cs.findbugs.annotations.NonNull; import edu.umd.cs.findbugs.annotations.Nullable; import java.time.Instant; diff --git a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/records/impl/codec/RunningHashesTranslator.java b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/records/impl/codec/RunningHashesTranslator.java index f7e33534f57b..a2aa27f2fe86 100644 --- a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/records/impl/codec/RunningHashesTranslator.java +++ b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/records/impl/codec/RunningHashesTranslator.java @@ -19,7 +19,6 @@ import static java.util.Objects.requireNonNull; import com.hedera.hapi.node.state.blockrecords.RunningHashes; -import com.hedera.pbj.runtime.io.buffer.Bytes; import edu.umd.cs.findbugs.annotations.NonNull; public final class RunningHashesTranslator { @@ -37,14 +36,13 @@ public static RunningHashes runningHashesFromRecordsRunningHashLeaf( @NonNull final com.hedera.node.app.service.mono.stream.RecordsRunningHashLeaf recordsRunningHashLeaf) { requireNonNull(recordsRunningHashLeaf); return new RunningHashes.Builder() - .runningHash(Bytes.wrap( - recordsRunningHashLeaf.getRunningHash().getHash().getValue())) - .nMinus1RunningHash(Bytes.wrap( - recordsRunningHashLeaf.getNMinus1RunningHash().getHash().getValue())) - .nMinus2RunningHash(Bytes.wrap( - recordsRunningHashLeaf.getNMinus2RunningHash().getHash().getValue())) - .nMinus3RunningHash(Bytes.wrap( - recordsRunningHashLeaf.getNMinus3RunningHash().getHash().getValue())) + .runningHash(recordsRunningHashLeaf.getRunningHash().getHash().getBytes()) + .nMinus1RunningHash( + recordsRunningHashLeaf.getNMinus1RunningHash().getHash().getBytes()) + .nMinus2RunningHash( + recordsRunningHashLeaf.getNMinus2RunningHash().getHash().getBytes()) + .nMinus3RunningHash( + recordsRunningHashLeaf.getNMinus3RunningHash().getHash().getBytes()) .build(); } } diff --git a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/services/ServicesRegistryImpl.java b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/services/ServicesRegistryImpl.java index ab420fa2690a..6eaf28f1614a 100644 --- a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/services/ServicesRegistryImpl.java +++ b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/services/ServicesRegistryImpl.java @@ -22,7 +22,6 @@ import com.hedera.node.app.spi.Service; import com.hedera.node.app.spi.workflows.record.GenesisRecordsBuilder; import com.hedera.node.app.state.merkle.MerkleSchemaRegistry; -import com.hedera.node.app.version.HederaSoftwareVersion; import com.swirlds.common.constructable.ConstructableRegistry; import edu.umd.cs.findbugs.annotations.NonNull; import java.util.Collections; @@ -67,9 +66,8 @@ public ServicesRegistryImpl( * Register the given service. * * @param service The service to register - * @param version */ - public void register(@NonNull final Service service, final HederaSoftwareVersion version) { + public void register(@NonNull final Service service) { final var serviceName = service.getServiceName(); logger.debug("Registering schemas for service {}", serviceName); diff --git a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/signature/MonoSignaturePreparer.java b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/signature/MonoSignaturePreparer.java index 639d6a4e985f..28aad65640bf 100644 --- a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/signature/MonoSignaturePreparer.java +++ b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/signature/MonoSignaturePreparer.java @@ -37,9 +37,9 @@ import com.hedera.node.app.signature.impl.SignatureVerifierImpl; import com.hedera.node.app.spi.key.HederaKey; import com.hedera.node.app.spi.workflows.PreCheckException; -import com.hedera.node.app.state.HederaState; import com.hedera.pbj.runtime.io.buffer.Bytes; import com.swirlds.common.crypto.TransactionSignature; +import com.swirlds.state.HederaState; import edu.umd.cs.findbugs.annotations.NonNull; import java.util.ArrayList; import java.util.List; diff --git a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/signature/SignaturePreparer.java b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/signature/SignaturePreparer.java index c443c0cd21da..8384a4a58b48 100644 --- a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/signature/SignaturePreparer.java +++ b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/signature/SignaturePreparer.java @@ -22,9 +22,9 @@ import com.hedera.node.app.service.mono.sigs.PlatformSigsCreationResult; import com.hedera.node.app.spi.key.HederaKey; import com.hedera.node.app.spi.workflows.PreCheckException; -import com.hedera.node.app.state.HederaState; import com.hedera.pbj.runtime.io.buffer.Bytes; import com.swirlds.common.crypto.TransactionSignature; +import com.swirlds.state.HederaState; import edu.umd.cs.findbugs.annotations.NonNull; import java.util.List; import java.util.Map; diff --git a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/startup-phase-lifecycle.png b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/startup-phase-lifecycle.png new file mode 100644 index 000000000000..0285b5e18155 Binary files /dev/null and b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/startup-phase-lifecycle.png differ diff --git a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/state/HandleConsensusRoundListener.java b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/state/HandleConsensusRoundListener.java index c6e0565adc12..4c6ee39c176c 100644 --- a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/state/HandleConsensusRoundListener.java +++ b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/state/HandleConsensusRoundListener.java @@ -18,6 +18,7 @@ import com.swirlds.platform.state.PlatformState; import com.swirlds.platform.system.Round; +import com.swirlds.state.HederaState; import edu.umd.cs.findbugs.annotations.NonNull; /** Listener invoked for each consensus round that occurs. */ diff --git a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/state/HederaLifecyclesImpl.java b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/state/HederaLifecyclesImpl.java index cf2fe71510aa..aad877b1d0bf 100644 --- a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/state/HederaLifecyclesImpl.java +++ b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/state/HederaLifecyclesImpl.java @@ -29,20 +29,21 @@ import com.hedera.node.app.service.token.TokenService; import com.hedera.node.app.state.merkle.HederaLifecycles; import com.hedera.node.app.state.merkle.MerkleHederaState; -import com.hedera.node.app.state.merkle.disk.OnDiskKey; -import com.hedera.node.app.state.merkle.disk.OnDiskValue; import com.swirlds.common.context.PlatformContext; import com.swirlds.common.platform.NodeId; import com.swirlds.common.threading.manager.AdHocThreadManager; import com.swirlds.config.api.Configuration; import com.swirlds.merkle.map.MerkleMap; import com.swirlds.platform.state.PlatformState; +import com.swirlds.platform.state.merkle.disk.OnDiskKey; +import com.swirlds.platform.state.merkle.disk.OnDiskValue; import com.swirlds.platform.system.InitTrigger; import com.swirlds.platform.system.Platform; import com.swirlds.platform.system.Round; import com.swirlds.platform.system.SoftwareVersion; import com.swirlds.platform.system.address.AddressBook; import com.swirlds.platform.system.events.Event; +import com.swirlds.state.HederaState; import com.swirlds.virtualmap.VirtualMap; import com.swirlds.virtualmap.VirtualMapMigration; import edu.umd.cs.findbugs.annotations.NonNull; diff --git a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/state/LedgerValidator.java b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/state/LedgerValidator.java index 7507d02356ce..44f59579e1be 100644 --- a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/state/LedgerValidator.java +++ b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/state/LedgerValidator.java @@ -16,6 +16,7 @@ package com.hedera.node.app.state; +import com.swirlds.state.HederaState; import edu.umd.cs.findbugs.annotations.NonNull; /** diff --git a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/state/LedgerValidatorImpl.java b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/state/LedgerValidatorImpl.java index f30f77b4384a..fab398260d9e 100644 --- a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/state/LedgerValidatorImpl.java +++ b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/state/LedgerValidatorImpl.java @@ -23,9 +23,10 @@ import com.hedera.node.app.service.token.TokenService; import com.hedera.node.app.service.token.impl.TokenServiceImpl; import com.hedera.node.app.spi.HapiUtils; -import com.hedera.node.app.spi.state.ReadableKVState; import com.hedera.node.config.ConfigProvider; import com.hedera.node.config.data.LedgerConfig; +import com.swirlds.state.HederaState; +import com.swirlds.state.spi.ReadableKVState; import edu.umd.cs.findbugs.annotations.NonNull; import java.util.concurrent.atomic.AtomicLong; import javax.inject.Inject; diff --git a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/state/PreHandleListener.java b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/state/PreHandleListener.java index 3823e0f60a66..af033bb89fc3 100644 --- a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/state/PreHandleListener.java +++ b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/state/PreHandleListener.java @@ -17,6 +17,7 @@ package com.hedera.node.app.state; import com.swirlds.platform.system.events.Event; +import com.swirlds.state.HederaState; import edu.umd.cs.findbugs.annotations.NonNull; /** Listener invoked whenever an event is ready for pre-handle */ diff --git a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/state/ReadonlyKVStateWrapper.java b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/state/ReadonlyKVStateWrapper.java index 2a11bcc6afa8..0a37af22dc96 100644 --- a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/state/ReadonlyKVStateWrapper.java +++ b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/state/ReadonlyKVStateWrapper.java @@ -18,8 +18,8 @@ import static java.util.Objects.requireNonNull; -import com.hedera.node.app.spi.state.ReadableKVState; -import com.hedera.node.app.spi.state.WritableKVState; +import com.swirlds.state.spi.ReadableKVState; +import com.swirlds.state.spi.WritableKVState; import edu.umd.cs.findbugs.annotations.NonNull; import edu.umd.cs.findbugs.annotations.Nullable; import java.util.Iterator; diff --git a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/state/ReadonlyQueueStateWrapper.java b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/state/ReadonlyQueueStateWrapper.java index 98944c78258a..68be8ddb3a83 100644 --- a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/state/ReadonlyQueueStateWrapper.java +++ b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/state/ReadonlyQueueStateWrapper.java @@ -18,8 +18,8 @@ import static java.util.Objects.requireNonNull; -import com.hedera.node.app.spi.state.ReadableQueueState; -import com.hedera.node.app.spi.state.WritableQueueState; +import com.swirlds.state.spi.ReadableQueueState; +import com.swirlds.state.spi.WritableQueueState; import edu.umd.cs.findbugs.annotations.NonNull; import edu.umd.cs.findbugs.annotations.Nullable; import java.util.Iterator; diff --git a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/state/ReadonlySingletonStateWrapper.java b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/state/ReadonlySingletonStateWrapper.java index fa4f85411931..16b099f4bde9 100644 --- a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/state/ReadonlySingletonStateWrapper.java +++ b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/state/ReadonlySingletonStateWrapper.java @@ -18,8 +18,8 @@ import static java.util.Objects.requireNonNull; -import com.hedera.node.app.spi.state.ReadableSingletonState; -import com.hedera.node.app.spi.state.WritableSingletonState; +import com.swirlds.state.spi.ReadableSingletonState; +import com.swirlds.state.spi.WritableSingletonState; import edu.umd.cs.findbugs.annotations.NonNull; import edu.umd.cs.findbugs.annotations.Nullable; diff --git a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/state/ReadonlyStatesWrapper.java b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/state/ReadonlyStatesWrapper.java index b6276b6d8199..3c4a520cef2c 100644 --- a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/state/ReadonlyStatesWrapper.java +++ b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/state/ReadonlyStatesWrapper.java @@ -16,11 +16,11 @@ package com.hedera.node.app.state; -import com.hedera.node.app.spi.state.ReadableKVState; -import com.hedera.node.app.spi.state.ReadableQueueState; -import com.hedera.node.app.spi.state.ReadableSingletonState; -import com.hedera.node.app.spi.state.ReadableStates; -import com.hedera.node.app.spi.state.WritableStates; +import com.swirlds.state.spi.ReadableKVState; +import com.swirlds.state.spi.ReadableQueueState; +import com.swirlds.state.spi.ReadableSingletonState; +import com.swirlds.state.spi.ReadableStates; +import com.swirlds.state.spi.WritableStates; import edu.umd.cs.findbugs.annotations.NonNull; import java.util.Set; diff --git a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/state/WorkingStateAccessor.java b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/state/WorkingStateAccessor.java index 12479eebdff6..970b13afe2cd 100644 --- a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/state/WorkingStateAccessor.java +++ b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/state/WorkingStateAccessor.java @@ -18,6 +18,7 @@ import static java.util.Objects.requireNonNull; +import com.swirlds.state.HederaState; import edu.umd.cs.findbugs.annotations.Nullable; import javax.inject.Inject; import javax.inject.Singleton; diff --git a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/state/WrappedHederaState.java b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/state/WrappedHederaState.java index 6ebb9d8eebdc..b6156867dabf 100644 --- a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/state/WrappedHederaState.java +++ b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/state/WrappedHederaState.java @@ -18,8 +18,9 @@ import static java.util.Objects.requireNonNull; -import com.hedera.node.app.spi.state.ReadableStates; -import com.hedera.node.app.spi.state.WritableStates; +import com.swirlds.state.HederaState; +import com.swirlds.state.spi.ReadableStates; +import com.swirlds.state.spi.WritableStates; import edu.umd.cs.findbugs.annotations.NonNull; import java.util.HashMap; import java.util.Map; diff --git a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/state/WrappedWritableStates.java b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/state/WrappedWritableStates.java index 320749c6558f..6d5ebb29579c 100644 --- a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/state/WrappedWritableStates.java +++ b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/state/WrappedWritableStates.java @@ -22,10 +22,10 @@ import com.hedera.node.app.spi.state.WrappedWritableKVState; import com.hedera.node.app.spi.state.WrappedWritableQueueState; import com.hedera.node.app.spi.state.WrappedWritableSingletonState; -import com.hedera.node.app.spi.state.WritableKVState; -import com.hedera.node.app.spi.state.WritableQueueState; -import com.hedera.node.app.spi.state.WritableSingletonState; -import com.hedera.node.app.spi.state.WritableStates; +import com.swirlds.state.spi.WritableKVState; +import com.swirlds.state.spi.WritableQueueState; +import com.swirlds.state.spi.WritableSingletonState; +import com.swirlds.state.spi.WritableStates; import edu.umd.cs.findbugs.annotations.NonNull; import java.util.HashMap; import java.util.Map; diff --git a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/state/listeners/ReconnectListener.java b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/state/listeners/ReconnectListener.java index d566755dfa52..52e99c530294 100644 --- a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/state/listeners/ReconnectListener.java +++ b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/state/listeners/ReconnectListener.java @@ -19,7 +19,6 @@ import com.hedera.node.app.service.file.ReadableUpgradeFileStore; import com.hedera.node.app.service.networkadmin.ReadableFreezeStore; import com.hedera.node.app.service.networkadmin.impl.handlers.ReadableFreezeUpgradeActions; -import com.hedera.node.app.state.HederaState; import com.hedera.node.app.state.PlatformStateAccessor; import com.hedera.node.app.workflows.dispatcher.ReadableStoreFactory; import com.hedera.node.config.ConfigProvider; @@ -27,6 +26,7 @@ import com.swirlds.common.utility.AutoCloseableWrapper; import com.swirlds.platform.listeners.ReconnectCompleteListener; import com.swirlds.platform.listeners.ReconnectCompleteNotification; +import com.swirlds.state.HederaState; import edu.umd.cs.findbugs.annotations.NonNull; import java.util.concurrent.Executor; import java.util.function.Supplier; diff --git a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/state/listeners/WriteStateToDiskListener.java b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/state/listeners/WriteStateToDiskListener.java index 234cb2a6ef63..bc5c763c3a08 100644 --- a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/state/listeners/WriteStateToDiskListener.java +++ b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/state/listeners/WriteStateToDiskListener.java @@ -21,13 +21,13 @@ import com.hedera.node.app.service.file.ReadableUpgradeFileStore; import com.hedera.node.app.service.networkadmin.ReadableFreezeStore; import com.hedera.node.app.service.networkadmin.impl.handlers.ReadableFreezeUpgradeActions; -import com.hedera.node.app.state.HederaState; import com.hedera.node.app.workflows.dispatcher.ReadableStoreFactory; import com.hedera.node.config.ConfigProvider; import com.hedera.node.config.data.NetworkAdminConfig; import com.swirlds.common.utility.AutoCloseableWrapper; import com.swirlds.platform.listeners.StateWriteToDiskCompleteListener; import com.swirlds.platform.listeners.StateWriteToDiskCompleteNotification; +import com.swirlds.state.HederaState; import edu.umd.cs.findbugs.annotations.NonNull; import java.util.concurrent.Executor; import java.util.function.Supplier; diff --git a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/state/logging/TransactionStateLogger.java b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/state/logging/TransactionStateLogger.java index 48d52b5874b0..6315e61dbfd0 100644 --- a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/state/logging/TransactionStateLogger.java +++ b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/state/logging/TransactionStateLogger.java @@ -27,13 +27,13 @@ import com.hedera.hapi.node.transaction.TransactionBody; import com.hedera.hapi.node.transaction.TransactionRecord; import com.hedera.node.app.spi.info.NodeInfo; -import com.hedera.node.app.state.merkle.disk.OnDiskKey; -import com.hedera.node.app.state.merkle.disk.OnDiskValue; -import com.hedera.node.app.state.merkle.memory.InMemoryKey; -import com.hedera.node.app.state.merkle.memory.InMemoryValue; -import com.hedera.node.app.state.merkle.singleton.ValueLeaf; import com.hedera.node.app.workflows.prehandle.PreHandleResult; import com.swirlds.fcqueue.FCQueue; +import com.swirlds.platform.state.merkle.disk.OnDiskKey; +import com.swirlds.platform.state.merkle.disk.OnDiskValue; +import com.swirlds.platform.state.merkle.memory.InMemoryKey; +import com.swirlds.platform.state.merkle.memory.InMemoryValue; +import com.swirlds.platform.state.merkle.singleton.ValueLeaf; import com.swirlds.platform.system.Round; import com.swirlds.platform.system.events.ConsensusEvent; import com.swirlds.platform.system.transaction.ConsensusTransaction; diff --git a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/state/merkle/HederaLifecycles.java b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/state/merkle/HederaLifecycles.java index 4459d40c1bad..12d8670db214 100644 --- a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/state/merkle/HederaLifecycles.java +++ b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/state/merkle/HederaLifecycles.java @@ -16,7 +16,6 @@ package com.hedera.node.app.state.merkle; -import com.hedera.node.app.state.HederaState; import com.swirlds.common.context.PlatformContext; import com.swirlds.platform.state.PlatformState; import com.swirlds.platform.system.InitTrigger; @@ -25,6 +24,7 @@ import com.swirlds.platform.system.SoftwareVersion; import com.swirlds.platform.system.address.AddressBook; import com.swirlds.platform.system.events.Event; +import com.swirlds.state.HederaState; import edu.umd.cs.findbugs.annotations.NonNull; import edu.umd.cs.findbugs.annotations.Nullable; diff --git a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/state/merkle/MerkleHederaState.java b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/state/merkle/MerkleHederaState.java index 550f291981fe..ecdcd0f1ec25 100644 --- a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/state/merkle/MerkleHederaState.java +++ b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/state/merkle/MerkleHederaState.java @@ -22,28 +22,6 @@ import com.hedera.node.app.Hedera; import com.hedera.node.app.spi.state.CommittableWritableStates; import com.hedera.node.app.spi.state.EmptyReadableStates; -import com.hedera.node.app.spi.state.ReadableKVState; -import com.hedera.node.app.spi.state.ReadableQueueState; -import com.hedera.node.app.spi.state.ReadableSingletonState; -import com.hedera.node.app.spi.state.ReadableStates; -import com.hedera.node.app.spi.state.WritableKVState; -import com.hedera.node.app.spi.state.WritableKVStateBase; -import com.hedera.node.app.spi.state.WritableQueueState; -import com.hedera.node.app.spi.state.WritableQueueStateBase; -import com.hedera.node.app.spi.state.WritableSingletonState; -import com.hedera.node.app.spi.state.WritableSingletonStateBase; -import com.hedera.node.app.spi.state.WritableStates; -import com.hedera.node.app.state.HederaState; -import com.hedera.node.app.state.merkle.disk.OnDiskReadableKVState; -import com.hedera.node.app.state.merkle.disk.OnDiskWritableKVState; -import com.hedera.node.app.state.merkle.memory.InMemoryReadableKVState; -import com.hedera.node.app.state.merkle.memory.InMemoryWritableKVState; -import com.hedera.node.app.state.merkle.queue.QueueNode; -import com.hedera.node.app.state.merkle.queue.ReadableQueueStateImpl; -import com.hedera.node.app.state.merkle.queue.WritableQueueStateImpl; -import com.hedera.node.app.state.merkle.singleton.ReadableSingletonStateImpl; -import com.hedera.node.app.state.merkle.singleton.SingletonNode; -import com.hedera.node.app.state.merkle.singleton.WritableSingletonStateImpl; import com.swirlds.common.constructable.ConstructableRegistry; import com.swirlds.common.context.PlatformContext; import com.swirlds.common.merkle.MerkleInternal; @@ -53,6 +31,20 @@ import com.swirlds.merkle.map.MerkleMap; import com.swirlds.metrics.api.Metrics; import com.swirlds.platform.state.PlatformState; +import com.swirlds.platform.state.merkle.StateUtils; +import com.swirlds.platform.state.merkle.disk.OnDiskReadableKVState; +import com.swirlds.platform.state.merkle.disk.OnDiskWritableKVState; +import com.swirlds.platform.state.merkle.memory.InMemoryReadableKVState; +import com.swirlds.platform.state.merkle.memory.InMemoryWritableKVState; +import com.swirlds.platform.state.merkle.queue.QueueNode; +import com.swirlds.platform.state.merkle.queue.ReadableQueueStateImpl; +import com.swirlds.platform.state.merkle.queue.WritableQueueStateImpl; +import com.swirlds.platform.state.merkle.singleton.ReadableSingletonStateImpl; +import com.swirlds.platform.state.merkle.singleton.SingletonNode; +import com.swirlds.platform.state.merkle.singleton.WritableSingletonStateImpl; +import com.swirlds.platform.state.spi.WritableKVStateBase; +import com.swirlds.platform.state.spi.WritableQueueStateBase; +import com.swirlds.platform.state.spi.WritableSingletonStateBase; import com.swirlds.platform.system.InitTrigger; import com.swirlds.platform.system.Platform; import com.swirlds.platform.system.Round; @@ -62,6 +54,15 @@ import com.swirlds.platform.system.address.AddressBook; import com.swirlds.platform.system.events.Event; import com.swirlds.platform.system.state.notifications.NewRecoveredStateListener; +import com.swirlds.state.HederaState; +import com.swirlds.state.spi.ReadableKVState; +import com.swirlds.state.spi.ReadableQueueState; +import com.swirlds.state.spi.ReadableSingletonState; +import com.swirlds.state.spi.ReadableStates; +import com.swirlds.state.spi.WritableKVState; +import com.swirlds.state.spi.WritableQueueState; +import com.swirlds.state.spi.WritableSingletonState; +import com.swirlds.state.spi.WritableStates; import com.swirlds.virtualmap.VirtualMap; import edu.umd.cs.findbugs.annotations.NonNull; import java.io.IOException; @@ -267,8 +268,7 @@ public void close() { logger.info("Closing MerkleHederaState"); for (final var svc : services.values()) { for (final var md : svc.values()) { - final var index = - findNodeIndex(md.serviceName(), md.stateDefinition().stateKey()); + final var index = findNodeIndex(md.serviceName(), extractStateKey(md)); if (index >= 0) { final var node = getChild(index); if (node instanceof VirtualMap virtualMap) { @@ -600,12 +600,11 @@ protected abstract ReadableQueueState createReadableQueueState( */ @NonNull MerkleNode findNode(@NonNull final StateMetadata md) { - final var index = - findNodeIndex(md.serviceName(), md.stateDefinition().stateKey()); + final var index = findNodeIndex(md.serviceName(), extractStateKey(md)); if (index == -1) { // This can only happen if there WAS a node here, and it was removed! throw new IllegalStateException("State '" - + md.stateDefinition().stateKey() + + extractStateKey(md) + "' for service '" + md.serviceName() + "' is missing from the merkle tree!"); @@ -633,27 +632,31 @@ public final class MerkleReadableStates extends MerkleStates { @NonNull protected ReadableKVState createReadableKVState( @NonNull final StateMetadata md, @NonNull final VirtualMap v) { - return new OnDiskReadableKVState<>(md, v); + return new OnDiskReadableKVState<>( + extractStateKey(md), + md.onDiskKeyClassId(), + md.stateDefinition().keyCodec(), + v); } @Override @NonNull protected ReadableKVState createReadableKVState( @NonNull final StateMetadata md, @NonNull final MerkleMap m) { - return new InMemoryReadableKVState<>(md, m); + return new InMemoryReadableKVState<>(extractStateKey(md), m); } @Override @NonNull protected ReadableSingletonState createReadableSingletonState( @NonNull final StateMetadata md, @NonNull final SingletonNode s) { - return new ReadableSingletonStateImpl<>(md, s); + return new ReadableSingletonStateImpl<>(extractStateKey(md), s); } @NonNull @Override protected ReadableQueueState createReadableQueueState(@NonNull StateMetadata md, @NonNull QueueNode q) { - return new ReadableQueueStateImpl(md, q); + return new ReadableQueueStateImpl(extractStateKey(md), q); } } @@ -715,28 +718,39 @@ public WritableQueueState getQueue(@NonNull String stateKey) { @NonNull protected WritableKVState createReadableKVState( @NonNull final StateMetadata md, @NonNull final VirtualMap v) { - return new OnDiskWritableKVState<>(md, v); + return new OnDiskWritableKVState<>( + extractStateKey(md), + md.onDiskKeyClassId(), + md.stateDefinition().keyCodec(), + md.onDiskValueClassId(), + md.stateDefinition().valueCodec(), + v); } @Override @NonNull protected WritableKVState createReadableKVState( @NonNull final StateMetadata md, @NonNull final MerkleMap m) { - return new InMemoryWritableKVState<>(md, m); + return new InMemoryWritableKVState<>( + extractStateKey(md), + md.inMemoryValueClassId(), + md.stateDefinition().keyCodec(), + md.stateDefinition().valueCodec(), + m); } @Override @NonNull protected WritableSingletonState createReadableSingletonState( @NonNull final StateMetadata md, @NonNull final SingletonNode s) { - return new WritableSingletonStateImpl<>(md, s); + return new WritableSingletonStateImpl<>(extractStateKey(md), s); } @NonNull @Override protected WritableQueueState createReadableQueueState( @NonNull final StateMetadata md, @NonNull final QueueNode q) { - return new WritableQueueStateImpl<>(md, q); + return new WritableQueueStateImpl<>(extractStateKey(md), q); } @Override @@ -766,4 +780,9 @@ public void remove(String stateKey) { queueInstances.remove(stateKey); } } + + @NonNull + private static String extractStateKey(@NonNull final StateMetadata md) { + return md.stateDefinition().stateKey(); + } } diff --git a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/state/merkle/MerkleSchemaRegistry.java b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/state/merkle/MerkleSchemaRegistry.java index a0718d03be3b..46c44d34f061 100644 --- a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/state/merkle/MerkleSchemaRegistry.java +++ b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/state/merkle/MerkleSchemaRegistry.java @@ -27,21 +27,10 @@ import com.hedera.node.app.spi.state.FilteredReadableStates; import com.hedera.node.app.spi.state.FilteredWritableStates; import com.hedera.node.app.spi.state.MigrationContext; -import com.hedera.node.app.spi.state.ReadableStates; import com.hedera.node.app.spi.state.Schema; import com.hedera.node.app.spi.state.SchemaRegistry; import com.hedera.node.app.spi.state.StateDefinition; import com.hedera.node.app.spi.workflows.record.GenesisRecordsBuilder; -import com.hedera.node.app.state.merkle.disk.OnDiskKey; -import com.hedera.node.app.state.merkle.disk.OnDiskKeySerializer; -import com.hedera.node.app.state.merkle.disk.OnDiskValue; -import com.hedera.node.app.state.merkle.disk.OnDiskValueSerializer; -import com.hedera.node.app.state.merkle.memory.InMemoryValue; -import com.hedera.node.app.state.merkle.memory.InMemoryWritableKVState; -import com.hedera.node.app.state.merkle.queue.QueueNode; -import com.hedera.node.app.state.merkle.singleton.SingletonNode; -import com.hedera.node.app.state.merkle.singleton.StringLeaf; -import com.hedera.node.app.state.merkle.singleton.ValueLeaf; import com.hedera.node.app.workflows.handle.record.MigrationContextImpl; import com.swirlds.common.constructable.ClassConstructorPair; import com.swirlds.common.constructable.ConstructableRegistry; @@ -52,6 +41,18 @@ import com.swirlds.merkledb.MerkleDbDataSourceBuilder; import com.swirlds.merkledb.MerkleDbTableConfig; import com.swirlds.metrics.api.Metrics; +import com.swirlds.platform.state.merkle.StateUtils; +import com.swirlds.platform.state.merkle.disk.OnDiskKey; +import com.swirlds.platform.state.merkle.disk.OnDiskKeySerializer; +import com.swirlds.platform.state.merkle.disk.OnDiskValue; +import com.swirlds.platform.state.merkle.disk.OnDiskValueSerializer; +import com.swirlds.platform.state.merkle.memory.InMemoryValue; +import com.swirlds.platform.state.merkle.memory.InMemoryWritableKVState; +import com.swirlds.platform.state.merkle.queue.QueueNode; +import com.swirlds.platform.state.merkle.singleton.SingletonNode; +import com.swirlds.platform.state.merkle.singleton.StringLeaf; +import com.swirlds.platform.state.merkle.singleton.ValueLeaf; +import com.swirlds.state.spi.ReadableStates; import com.swirlds.virtualmap.VirtualMap; import edu.umd.cs.findbugs.annotations.NonNull; import edu.umd.cs.findbugs.annotations.Nullable; @@ -72,7 +73,7 @@ * then registers each and every {@link Schema} that it has. Each {@link Schema} is associated with * a {@link SemanticVersion}. * - *

    The Hedera application then calls {@link #migrate(MerkleHederaState, SemanticVersion, SemanticVersion, Configuration, NetworkInfo, WritableEntityIdStore)} on each {@link MerkleSchemaRegistry} instance, supplying it the + *

    The Hedera application then calls {@link com.hedera.node.app.Hedera#onMigrate(MerkleHederaState, HederaSoftwareVersion, InitTrigger, Metrics)} on each {@link MerkleSchemaRegistry} instance, supplying it the * application version number and the newly created (or deserialized) but not yet hashed copy of the {@link * MerkleHederaState}. The registry determines which {@link Schema}s to apply, possibly taking multiple migration steps, * to transition the merkle tree from its current version to the final version. @@ -210,9 +211,23 @@ public void migrate( logger.info(" Ensuring {} has state {}", serviceName, stateKey); final var md = new StateMetadata<>(serviceName, schema, def); if (def.singleton()) { - hederaState.putServiceStateIfAbsent(md, () -> new SingletonNode<>(md, null)); + hederaState.putServiceStateIfAbsent( + md, + () -> new SingletonNode<>( + md.serviceName(), + md.stateDefinition().stateKey(), + md.singletonClassId(), + md.stateDefinition().valueCodec(), + null)); } else if (def.queue()) { - hederaState.putServiceStateIfAbsent(md, () -> new QueueNode<>(md)); + hederaState.putServiceStateIfAbsent( + md, + () -> new QueueNode<>( + md.serviceName(), + md.stateDefinition().stateKey(), + md.queueNodeClassId(), + md.singletonClassId(), + md.stateDefinition().valueCodec())); } else if (!def.onDisk()) { hederaState.putServiceStateIfAbsent(md, () -> { final var map = new MerkleMap<>(); @@ -220,6 +235,7 @@ public void migrate( return map; }); } else { + hederaState.putServiceStateIfAbsent(md, () -> { // MAX_IN_MEMORY_HASHES (ramToDiskThreshold) = 8388608 // PREFER_DISK_BASED_INDICES = false @@ -227,9 +243,15 @@ public void migrate( (short) 1, DigestType.SHA_384, (short) 1, - new OnDiskKeySerializer<>(md), + new OnDiskKeySerializer<>( + md.onDiskKeySerializerClassId(), + md.onDiskKeyClassId(), + md.stateDefinition().keyCodec()), (short) 1, - new OnDiskValueSerializer<>(md)) + new OnDiskValueSerializer<>( + md.onDiskValueSerializerClassId(), + md.onDiskValueClassId(), + md.stateDefinition().valueCodec())) .maxNumberOfKeys(def.maxKeysHint()); final var label = StateUtils.computeLabel(serviceName, stateKey); final var dsBuilder = new MerkleDbDataSourceBuilder<>(tableConfig); @@ -430,23 +452,53 @@ private void registerWithSystem(@NonNull final StateMetadata md) { // various delegate writers and parsers, and so can parse/write different types of data // based on the id. try { - constructableRegistry.registerConstructable( - new ClassConstructorPair(InMemoryValue.class, () -> new InMemoryValue(md))); - constructableRegistry.registerConstructable( - new ClassConstructorPair(OnDiskKey.class, () -> new OnDiskKey<>(md))); - constructableRegistry.registerConstructable( - new ClassConstructorPair(OnDiskKeySerializer.class, () -> new OnDiskKeySerializer<>(md))); - constructableRegistry.registerConstructable( - new ClassConstructorPair(OnDiskValue.class, () -> new OnDiskValue<>(md))); - constructableRegistry.registerConstructable( - new ClassConstructorPair(OnDiskValueSerializer.class, () -> new OnDiskValueSerializer<>(md))); - constructableRegistry.registerConstructable( - new ClassConstructorPair(SingletonNode.class, () -> new SingletonNode<>(md, null))); - constructableRegistry.registerConstructable( - new ClassConstructorPair(QueueNode.class, () -> new QueueNode<>(md))); + constructableRegistry.registerConstructable(new ClassConstructorPair( + InMemoryValue.class, + () -> new InMemoryValue( + md.inMemoryValueClassId(), + md.stateDefinition().keyCodec(), + md.stateDefinition().valueCodec()))); + constructableRegistry.registerConstructable(new ClassConstructorPair( + OnDiskKey.class, + () -> new OnDiskKey<>( + md.onDiskKeyClassId(), md.stateDefinition().keyCodec()))); + constructableRegistry.registerConstructable(new ClassConstructorPair( + OnDiskKeySerializer.class, + () -> new OnDiskKeySerializer<>( + md.onDiskKeySerializerClassId(), + md.onDiskKeyClassId(), + md.stateDefinition().keyCodec()))); + constructableRegistry.registerConstructable(new ClassConstructorPair( + OnDiskValue.class, + () -> new OnDiskValue<>( + md.onDiskValueClassId(), md.stateDefinition().valueCodec()))); + constructableRegistry.registerConstructable(new ClassConstructorPair( + OnDiskValueSerializer.class, + () -> new OnDiskValueSerializer<>( + md.onDiskValueSerializerClassId(), + md.onDiskValueClassId(), + md.stateDefinition().valueCodec()))); + constructableRegistry.registerConstructable(new ClassConstructorPair( + SingletonNode.class, + () -> new SingletonNode<>( + md.serviceName(), + md.stateDefinition().stateKey(), + md.singletonClassId(), + md.stateDefinition().valueCodec(), + null))); + constructableRegistry.registerConstructable(new ClassConstructorPair( + QueueNode.class, + () -> new QueueNode<>( + md.serviceName(), + md.stateDefinition().stateKey(), + md.queueNodeClassId(), + md.singletonClassId(), + md.stateDefinition().valueCodec()))); constructableRegistry.registerConstructable(new ClassConstructorPair(StringLeaf.class, StringLeaf::new)); - constructableRegistry.registerConstructable( - new ClassConstructorPair(ValueLeaf.class, () -> new ValueLeaf<>(md))); + constructableRegistry.registerConstructable(new ClassConstructorPair( + ValueLeaf.class, + () -> new ValueLeaf<>( + md.singletonClassId(), md.stateDefinition().valueCodec()))); } catch (ConstructableRegistryException e) { // This is a fatal error. throw new RuntimeException( diff --git a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/state/merkle/StateMetadata.java b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/state/merkle/StateMetadata.java index 98667e218c2d..340ff3aeab13 100644 --- a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/state/merkle/StateMetadata.java +++ b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/state/merkle/StateMetadata.java @@ -18,6 +18,7 @@ import com.hedera.node.app.spi.state.Schema; import com.hedera.node.app.spi.state.StateDefinition; +import com.swirlds.platform.state.merkle.StateUtils; import edu.umd.cs.findbugs.annotations.NonNull; /** diff --git a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/state/recordcache/RecordCacheImpl.java b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/state/recordcache/RecordCacheImpl.java index 824dfdebbfd4..3c654bfe0a2a 100644 --- a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/state/recordcache/RecordCacheImpl.java +++ b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/state/recordcache/RecordCacheImpl.java @@ -30,9 +30,6 @@ import com.hedera.hapi.node.state.recordcache.TransactionRecordEntry; import com.hedera.hapi.node.transaction.TransactionRecord; import com.hedera.node.app.spi.state.CommittableWritableStates; -import com.hedera.node.app.spi.state.ReadableQueueState; -import com.hedera.node.app.spi.state.WritableQueueState; -import com.hedera.node.app.spi.state.WritableStates; import com.hedera.node.app.spi.validation.TruePredicate; import com.hedera.node.app.state.DeduplicationCache; import com.hedera.node.app.state.HederaRecordCache; @@ -41,6 +38,9 @@ import com.hedera.node.config.ConfigProvider; import com.hedera.node.config.data.HederaConfig; import com.hedera.node.config.data.LedgerConfig; +import com.swirlds.state.spi.ReadableQueueState; +import com.swirlds.state.spi.WritableQueueState; +import com.swirlds.state.spi.WritableStates; import edu.umd.cs.findbugs.annotations.NonNull; import edu.umd.cs.findbugs.annotations.Nullable; import java.util.ArrayList; diff --git a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/state/recordcache/RecordCacheService.java b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/state/recordcache/RecordCacheService.java index a80311f06bb4..0e2cb677af26 100644 --- a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/state/recordcache/RecordCacheService.java +++ b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/state/recordcache/RecordCacheService.java @@ -29,7 +29,7 @@ import com.hedera.node.app.spi.state.Schema; import com.hedera.node.app.spi.state.SchemaRegistry; import com.hedera.node.app.spi.state.StateDefinition; -import com.hedera.node.app.spi.state.WritableQueueStateBase; +import com.swirlds.platform.state.spi.WritableQueueStateBase; import edu.umd.cs.findbugs.annotations.NonNull; import edu.umd.cs.findbugs.annotations.Nullable; import java.util.List; diff --git a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/statedumpers/StateDumper.java b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/statedumpers/StateDumper.java index 47210ee36fa5..99ec49afa66d 100644 --- a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/statedumpers/StateDumper.java +++ b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/statedumpers/StateDumper.java @@ -85,15 +85,15 @@ import com.hedera.node.app.service.token.TokenService; import com.hedera.node.app.state.merkle.MerkleHederaState; import com.hedera.node.app.state.merkle.StateMetadata; -import com.hedera.node.app.state.merkle.disk.OnDiskKey; -import com.hedera.node.app.state.merkle.disk.OnDiskValue; -import com.hedera.node.app.state.merkle.memory.InMemoryKey; -import com.hedera.node.app.state.merkle.memory.InMemoryValue; -import com.hedera.node.app.state.merkle.queue.QueueNode; -import com.hedera.node.app.state.merkle.singleton.SingletonNode; import com.hedera.node.app.state.recordcache.RecordCacheService; import com.hedera.node.app.throttle.CongestionThrottleService; import com.swirlds.merkle.map.MerkleMap; +import com.swirlds.platform.state.merkle.disk.OnDiskKey; +import com.swirlds.platform.state.merkle.disk.OnDiskValue; +import com.swirlds.platform.state.merkle.memory.InMemoryKey; +import com.swirlds.platform.state.merkle.memory.InMemoryValue; +import com.swirlds.platform.state.merkle.queue.QueueNode; +import com.swirlds.platform.state.merkle.singleton.SingletonNode; import com.swirlds.virtualmap.VirtualMap; import edu.umd.cs.findbugs.annotations.NonNull; import edu.umd.cs.findbugs.annotations.Nullable; diff --git a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/statedumpers/accounts/AccountDumpUtils.java b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/statedumpers/accounts/AccountDumpUtils.java index 576c6f1affa5..2fa63138b506 100644 --- a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/statedumpers/accounts/AccountDumpUtils.java +++ b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/statedumpers/accounts/AccountDumpUtils.java @@ -25,8 +25,8 @@ import com.hedera.node.app.service.mono.statedumpers.DumpCheckpoint; import com.hedera.node.app.service.mono.statedumpers.accounts.BBMHederaAccount; import com.hedera.node.app.service.mono.statedumpers.utils.Writer; -import com.hedera.node.app.state.merkle.disk.OnDiskKey; -import com.hedera.node.app.state.merkle.disk.OnDiskValue; +import com.swirlds.platform.state.merkle.disk.OnDiskKey; +import com.swirlds.platform.state.merkle.disk.OnDiskValue; import com.swirlds.virtualmap.VirtualMap; import edu.umd.cs.findbugs.annotations.NonNull; import java.nio.file.Path; diff --git a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/statedumpers/associations/TokenAssociationsDumpUtils.java b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/statedumpers/associations/TokenAssociationsDumpUtils.java index c52e5b4e5370..cfaedc163def 100644 --- a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/statedumpers/associations/TokenAssociationsDumpUtils.java +++ b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/statedumpers/associations/TokenAssociationsDumpUtils.java @@ -27,9 +27,9 @@ import com.hedera.node.app.service.mono.statedumpers.associations.BBMTokenAssociation; import com.hedera.node.app.service.mono.statedumpers.associations.BBMTokenAssociationId; import com.hedera.node.app.service.mono.statedumpers.utils.Writer; -import com.hedera.node.app.state.merkle.disk.OnDiskKey; -import com.hedera.node.app.state.merkle.disk.OnDiskValue; import com.swirlds.base.utility.Pair; +import com.swirlds.platform.state.merkle.disk.OnDiskKey; +import com.swirlds.platform.state.merkle.disk.OnDiskValue; import com.swirlds.virtualmap.VirtualMap; import edu.umd.cs.findbugs.annotations.NonNull; import edu.umd.cs.findbugs.annotations.Nullable; diff --git a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/statedumpers/contracts/ContractBytecodesDumpUtils.java b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/statedumpers/contracts/ContractBytecodesDumpUtils.java index e981a9a2da90..bb4d9945c696 100644 --- a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/statedumpers/contracts/ContractBytecodesDumpUtils.java +++ b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/statedumpers/contracts/ContractBytecodesDumpUtils.java @@ -30,8 +30,8 @@ import com.hedera.node.app.service.mono.statedumpers.contracts.Validity; import com.hedera.node.app.service.mono.statedumpers.utils.Writer; import com.hedera.node.app.state.merkle.StateMetadata; -import com.hedera.node.app.state.merkle.disk.OnDiskKey; -import com.hedera.node.app.state.merkle.disk.OnDiskValue; +import com.swirlds.platform.state.merkle.disk.OnDiskKey; +import com.swirlds.platform.state.merkle.disk.OnDiskValue; import com.swirlds.virtualmap.VirtualMap; import edu.umd.cs.findbugs.annotations.NonNull; import java.nio.file.Path; @@ -100,7 +100,8 @@ public static BBMContract fromMod( final VirtualMap, OnDiskValue> accounts, final StateMetadata stateMetadata) { final var isDeleted = accounts.get(new OnDiskKey<>( - stateMetadata, + stateMetadata.onDiskKeyClassId(), + stateMetadata.stateDefinition().keyCodec(), AccountID.newBuilder() .accountNum(id.getKey().contractNum()) .build())) diff --git a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/statedumpers/files/FilesDumpUtils.java b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/statedumpers/files/FilesDumpUtils.java index e10bb3bb3d15..0d273536ef79 100644 --- a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/statedumpers/files/FilesDumpUtils.java +++ b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/statedumpers/files/FilesDumpUtils.java @@ -32,9 +32,9 @@ import com.hedera.node.app.service.mono.statedumpers.files.FileStore; import com.hedera.node.app.service.mono.statedumpers.files.SystemFileType; import com.hedera.node.app.service.mono.statedumpers.utils.Writer; -import com.hedera.node.app.state.merkle.disk.OnDiskKey; -import com.hedera.node.app.state.merkle.disk.OnDiskValue; import com.swirlds.base.utility.Pair; +import com.swirlds.platform.state.merkle.disk.OnDiskKey; +import com.swirlds.platform.state.merkle.disk.OnDiskValue; import com.swirlds.virtualmap.VirtualMap; import edu.umd.cs.findbugs.annotations.NonNull; import java.nio.file.Path; diff --git a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/statedumpers/nfts/UniqueTokenDumpUtils.java b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/statedumpers/nfts/UniqueTokenDumpUtils.java index 638f8e7dde4d..c8543a4fa64d 100644 --- a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/statedumpers/nfts/UniqueTokenDumpUtils.java +++ b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/statedumpers/nfts/UniqueTokenDumpUtils.java @@ -30,9 +30,9 @@ import com.hedera.node.app.service.mono.statedumpers.nfts.BBMUniqueTokenId; import com.hedera.node.app.service.mono.statedumpers.utils.Writer; import com.hedera.node.app.service.mono.utils.NftNumPair; -import com.hedera.node.app.state.merkle.disk.OnDiskKey; -import com.hedera.node.app.state.merkle.disk.OnDiskValue; import com.swirlds.base.utility.Pair; +import com.swirlds.platform.state.merkle.disk.OnDiskKey; +import com.swirlds.platform.state.merkle.disk.OnDiskValue; import com.swirlds.virtualmap.VirtualMap; import edu.umd.cs.findbugs.annotations.NonNull; import edu.umd.cs.findbugs.annotations.Nullable; diff --git a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/statedumpers/scheduledtransactions/ScheduledTransactionsDumpUtils.java b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/statedumpers/scheduledtransactions/ScheduledTransactionsDumpUtils.java index 8b74f11996c0..b9e7787e5ba0 100644 --- a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/statedumpers/scheduledtransactions/ScheduledTransactionsDumpUtils.java +++ b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/statedumpers/scheduledtransactions/ScheduledTransactionsDumpUtils.java @@ -38,9 +38,9 @@ import com.hedera.node.app.service.mono.statedumpers.scheduledtransactions.BBMScheduledSecondValue; import com.hedera.node.app.service.mono.statedumpers.scheduledtransactions.BBMScheduledTransaction; import com.hedera.node.app.service.mono.statedumpers.utils.Writer; -import com.hedera.node.app.state.merkle.disk.OnDiskKey; -import com.hedera.node.app.state.merkle.disk.OnDiskValue; import com.swirlds.base.utility.Pair; +import com.swirlds.platform.state.merkle.disk.OnDiskKey; +import com.swirlds.platform.state.merkle.disk.OnDiskValue; import com.swirlds.virtualmap.VirtualMap; import edu.umd.cs.findbugs.annotations.NonNull; import java.nio.file.Path; diff --git a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/statedumpers/singleton/PayerRecordsDumpUtils.java b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/statedumpers/singleton/PayerRecordsDumpUtils.java index ab15191467b2..793641f6ad47 100644 --- a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/statedumpers/singleton/PayerRecordsDumpUtils.java +++ b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/statedumpers/singleton/PayerRecordsDumpUtils.java @@ -25,7 +25,7 @@ import com.hedera.node.app.service.mono.statedumpers.DumpCheckpoint; import com.hedera.node.app.service.mono.statedumpers.singleton.BBMPayerRecord; import com.hedera.node.app.service.mono.statedumpers.utils.Writer; -import com.hedera.node.app.state.merkle.queue.QueueNode; +import com.swirlds.platform.state.merkle.queue.QueueNode; import edu.umd.cs.findbugs.annotations.NonNull; import java.nio.file.Path; import java.util.ArrayList; diff --git a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/statedumpers/singleton/StakingInfoDumpUtils.java b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/statedumpers/singleton/StakingInfoDumpUtils.java index 001dd37e801e..fa681da71211 100644 --- a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/statedumpers/singleton/StakingInfoDumpUtils.java +++ b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/statedumpers/singleton/StakingInfoDumpUtils.java @@ -24,9 +24,9 @@ import com.hedera.node.app.service.mono.statedumpers.DumpCheckpoint; import com.hedera.node.app.service.mono.statedumpers.singleton.BBMStakingInfo; import com.hedera.node.app.service.mono.statedumpers.utils.Writer; -import com.hedera.node.app.state.merkle.memory.InMemoryKey; -import com.hedera.node.app.state.merkle.memory.InMemoryValue; import com.swirlds.merkle.map.MerkleMap; +import com.swirlds.platform.state.merkle.memory.InMemoryKey; +import com.swirlds.platform.state.merkle.memory.InMemoryValue; import edu.umd.cs.findbugs.annotations.NonNull; import java.nio.file.Path; import java.util.HashMap; diff --git a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/statedumpers/tokentypes/TokenTypesDumpUtils.java b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/statedumpers/tokentypes/TokenTypesDumpUtils.java index 82f85a6650e1..57aae2a2b16c 100644 --- a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/statedumpers/tokentypes/TokenTypesDumpUtils.java +++ b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/statedumpers/tokentypes/TokenTypesDumpUtils.java @@ -33,9 +33,9 @@ import com.hedera.node.app.service.mono.statedumpers.DumpCheckpoint; import com.hedera.node.app.service.mono.statedumpers.tokentypes.BBMToken; import com.hedera.node.app.service.mono.statedumpers.utils.Writer; -import com.hedera.node.app.state.merkle.disk.OnDiskKey; -import com.hedera.node.app.state.merkle.disk.OnDiskValue; import com.swirlds.base.utility.Pair; +import com.swirlds.platform.state.merkle.disk.OnDiskKey; +import com.swirlds.platform.state.merkle.disk.OnDiskValue; import com.swirlds.virtualmap.VirtualMap; import edu.umd.cs.findbugs.annotations.NonNull; import edu.umd.cs.findbugs.annotations.Nullable; diff --git a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/statedumpers/topics/TopicDumpUtils.java b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/statedumpers/topics/TopicDumpUtils.java index cf97e8451c06..cdde724002e7 100644 --- a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/statedumpers/topics/TopicDumpUtils.java +++ b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/statedumpers/topics/TopicDumpUtils.java @@ -28,9 +28,9 @@ import com.hedera.node.app.service.mono.statedumpers.DumpCheckpoint; import com.hedera.node.app.service.mono.statedumpers.topics.BBMTopic; import com.hedera.node.app.service.mono.statedumpers.utils.Writer; -import com.hedera.node.app.state.merkle.disk.OnDiskKey; -import com.hedera.node.app.state.merkle.disk.OnDiskValue; import com.swirlds.base.utility.Pair; +import com.swirlds.platform.state.merkle.disk.OnDiskKey; +import com.swirlds.platform.state.merkle.disk.OnDiskValue; import com.swirlds.virtualmap.VirtualMap; import edu.umd.cs.findbugs.annotations.NonNull; import java.nio.file.Path; diff --git a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/throttle/NetworkUtilizationManager.java b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/throttle/NetworkUtilizationManager.java index c29799472e6b..7b42281bb59a 100644 --- a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/throttle/NetworkUtilizationManager.java +++ b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/throttle/NetworkUtilizationManager.java @@ -18,8 +18,8 @@ import com.hedera.hapi.node.base.HederaFunctionality; import com.hedera.node.app.hapi.utils.throttles.DeterministicThrottle; -import com.hedera.node.app.state.HederaState; import com.hedera.node.app.workflows.TransactionInfo; +import com.swirlds.state.HederaState; import edu.umd.cs.findbugs.annotations.NonNull; import java.time.Instant; import java.util.List; @@ -45,7 +45,6 @@ void trackTxn( /** * Updates the throttle usage and congestion pricing for cases where the transaction is not valid, but we want to track the fee payments related to it. * - * @param payer - the payer of the transaction. * @param consensusNow - the consensus time of the transaction. * @param state - the state of the node. */ diff --git a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/throttle/NetworkUtilizationManagerImpl.java b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/throttle/NetworkUtilizationManagerImpl.java index 6d8a9af54adf..6710030aa475 100644 --- a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/throttle/NetworkUtilizationManagerImpl.java +++ b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/throttle/NetworkUtilizationManagerImpl.java @@ -27,10 +27,10 @@ import com.hedera.hapi.node.transaction.TransactionBody; import com.hedera.node.app.fees.congestion.CongestionMultipliers; import com.hedera.node.app.hapi.utils.throttles.DeterministicThrottle; -import com.hedera.node.app.state.HederaState; import com.hedera.node.app.throttle.annotations.BackendThrottle; import com.hedera.node.app.workflows.TransactionInfo; import com.hedera.pbj.runtime.io.buffer.Bytes; +import com.swirlds.state.HederaState; import edu.umd.cs.findbugs.annotations.NonNull; import java.time.Instant; import java.util.List; diff --git a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/throttle/SynchronizedThrottleAccumulator.java b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/throttle/SynchronizedThrottleAccumulator.java index 62388f5d1f49..aae26350c251 100644 --- a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/throttle/SynchronizedThrottleAccumulator.java +++ b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/throttle/SynchronizedThrottleAccumulator.java @@ -21,9 +21,9 @@ import com.hedera.hapi.node.base.AccountID; import com.hedera.hapi.node.base.HederaFunctionality; import com.hedera.hapi.node.transaction.Query; -import com.hedera.node.app.state.HederaState; import com.hedera.node.app.throttle.annotations.IngestThrottle; import com.hedera.node.app.workflows.TransactionInfo; +import com.swirlds.state.HederaState; import edu.umd.cs.findbugs.annotations.NonNull; import edu.umd.cs.findbugs.annotations.Nullable; import java.time.Instant; diff --git a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/throttle/ThrottleAccumulator.java b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/throttle/ThrottleAccumulator.java index 147f3c96a07a..0c91fc04e5ab 100644 --- a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/throttle/ThrottleAccumulator.java +++ b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/throttle/ThrottleAccumulator.java @@ -59,7 +59,6 @@ import com.hedera.node.app.service.token.ReadableAccountStore; import com.hedera.node.app.spi.UnknownHederaFunctionality; import com.hedera.node.app.spi.workflows.HandleException; -import com.hedera.node.app.state.HederaState; import com.hedera.node.app.workflows.TransactionInfo; import com.hedera.node.app.workflows.dispatcher.ReadableStoreFactory; import com.hedera.node.config.ConfigProvider; @@ -71,6 +70,7 @@ import com.hedera.node.config.data.TokensConfig; import com.hedera.pbj.runtime.io.buffer.Bytes; import com.swirlds.config.api.Configuration; +import com.swirlds.state.HederaState; import edu.umd.cs.findbugs.annotations.NonNull; import edu.umd.cs.findbugs.annotations.Nullable; import java.math.BigInteger; diff --git a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/throttle/ThrottleServiceManager.java b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/throttle/ThrottleServiceManager.java index 305920df6a74..9bcb7e45014e 100644 --- a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/throttle/ThrottleServiceManager.java +++ b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/throttle/ThrottleServiceManager.java @@ -36,16 +36,16 @@ import com.hedera.node.app.hapi.utils.throttles.DeterministicThrottle; import com.hedera.node.app.service.mono.pbj.PbjConverter; import com.hedera.node.app.service.token.ReadableAccountStore; -import com.hedera.node.app.spi.state.ReadableSingletonState; -import com.hedera.node.app.spi.state.ReadableStates; -import com.hedera.node.app.spi.state.WritableSingletonState; -import com.hedera.node.app.spi.state.WritableStates; -import com.hedera.node.app.state.HederaState; import com.hedera.node.app.throttle.annotations.BackendThrottle; import com.hedera.node.app.throttle.annotations.IngestThrottle; import com.hedera.node.config.ConfigProvider; import com.hedera.node.config.data.FilesConfig; import com.hedera.pbj.runtime.io.buffer.Bytes; +import com.swirlds.state.HederaState; +import com.swirlds.state.spi.ReadableSingletonState; +import com.swirlds.state.spi.ReadableStates; +import com.swirlds.state.spi.WritableSingletonState; +import com.swirlds.state.spi.WritableStates; import edu.umd.cs.findbugs.annotations.NonNull; import edu.umd.cs.findbugs.annotations.Nullable; import java.time.Instant; diff --git a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/util/FileUtilities.java b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/util/FileUtilities.java index f4a052ab619b..bef81bdfa565 100644 --- a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/util/FileUtilities.java +++ b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/util/FileUtilities.java @@ -22,11 +22,11 @@ import com.hedera.hapi.node.base.FileID; import com.hedera.hapi.node.state.file.File; import com.hedera.node.app.service.file.FileService; -import com.hedera.node.app.state.HederaState; import com.hedera.node.config.data.FilesConfig; import com.hedera.node.config.data.HederaConfig; import com.hedera.pbj.runtime.io.buffer.Bytes; import com.swirlds.config.api.Configuration; +import com.swirlds.state.HederaState; import edu.umd.cs.findbugs.annotations.NonNull; public class FileUtilities { diff --git a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/workflows/dispatcher/ReadableStoreFactory.java b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/workflows/dispatcher/ReadableStoreFactory.java index 570076dc14bd..58124fac96a0 100644 --- a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/workflows/dispatcher/ReadableStoreFactory.java +++ b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/workflows/dispatcher/ReadableStoreFactory.java @@ -50,8 +50,8 @@ import com.hedera.node.app.service.token.impl.ReadableStakingInfoStoreImpl; import com.hedera.node.app.service.token.impl.ReadableTokenRelationStoreImpl; import com.hedera.node.app.service.token.impl.ReadableTokenStoreImpl; -import com.hedera.node.app.spi.state.ReadableStates; -import com.hedera.node.app.state.HederaState; +import com.swirlds.state.HederaState; +import com.swirlds.state.spi.ReadableStates; import edu.umd.cs.findbugs.annotations.NonNull; import java.util.Collections; import java.util.HashMap; diff --git a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/workflows/dispatcher/WritableStoreFactory.java b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/workflows/dispatcher/WritableStoreFactory.java index c9956df83328..38eacd00fc5a 100644 --- a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/workflows/dispatcher/WritableStoreFactory.java +++ b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/workflows/dispatcher/WritableStoreFactory.java @@ -40,9 +40,9 @@ import com.hedera.node.app.service.token.impl.WritableTokenRelationStore; import com.hedera.node.app.service.token.impl.WritableTokenStore; import com.hedera.node.app.spi.metrics.StoreMetricsService; -import com.hedera.node.app.spi.state.WritableStates; -import com.hedera.node.app.state.HederaState; import com.swirlds.config.api.Configuration; +import com.swirlds.state.HederaState; +import com.swirlds.state.spi.WritableStates; import edu.umd.cs.findbugs.annotations.NonNull; import java.util.Collections; import java.util.HashMap; diff --git a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/workflows/handle/CacheWarmer.java b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/workflows/handle/CacheWarmer.java index e52913a9b9c5..7b70e1f0c9c1 100644 --- a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/workflows/handle/CacheWarmer.java +++ b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/workflows/handle/CacheWarmer.java @@ -24,7 +24,6 @@ import com.hedera.node.app.service.token.ReadableAccountStore; import com.hedera.node.app.spi.workflows.PreCheckException; import com.hedera.node.app.spi.workflows.TransactionHandler; -import com.hedera.node.app.state.HederaState; import com.hedera.node.app.workflows.TransactionChecker; import com.hedera.node.app.workflows.dispatcher.ReadableStoreFactory; import com.hedera.node.app.workflows.dispatcher.TransactionDispatcher; @@ -35,6 +34,7 @@ import com.swirlds.platform.system.Round; import com.swirlds.platform.system.events.ConsensusEvent; import com.swirlds.platform.system.transaction.Transaction; +import com.swirlds.state.HederaState; import edu.umd.cs.findbugs.annotations.NonNull; import edu.umd.cs.findbugs.annotations.Nullable; import java.util.concurrent.Executor; @@ -107,7 +107,7 @@ private TransactionBody extractTransactionBody(@NonNull final Transaction platfo // We can potentially optimize this by limiting the code to the bare minimum needed // or keeping the result for later. try { - final Bytes buffer = Bytes.wrap(platformTransaction.getContents()); + final Bytes buffer = platformTransaction.getApplicationPayload(); return checker.parseAndCheck(buffer).txBody(); } catch (PreCheckException ex) { return null; diff --git a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/workflows/handle/HandleWorkflow.java b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/workflows/handle/HandleWorkflow.java index a0db557a98cf..3f9698fe3590 100644 --- a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/workflows/handle/HandleWorkflow.java +++ b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/workflows/handle/HandleWorkflow.java @@ -96,7 +96,6 @@ import com.hedera.node.app.spi.workflows.InsufficientServiceFeeException; import com.hedera.node.app.spi.workflows.PreCheckException; import com.hedera.node.app.state.HederaRecordCache; -import com.hedera.node.app.state.HederaState; import com.hedera.node.app.throttle.NetworkUtilizationManager; import com.hedera.node.app.throttle.SynchronizedThrottleAccumulator; import com.hedera.node.app.throttle.ThrottleServiceManager; @@ -125,6 +124,7 @@ import com.swirlds.platform.system.Round; import com.swirlds.platform.system.events.ConsensusEvent; import com.swirlds.platform.system.transaction.ConsensusTransaction; +import com.swirlds.state.HederaState; import edu.umd.cs.findbugs.annotations.NonNull; import edu.umd.cs.findbugs.annotations.Nullable; import java.time.Instant; @@ -317,8 +317,8 @@ private void handleUserTransaction( @NonNull final ConsensusEvent platformEvent, @NonNull final NodeInfo creator, @NonNull final ConsensusTransaction platformTxn) { - // Determine if this is the first transaction after startup. This needs to be determined BEFORE starting the - // user transaction + // (FUTURE) We actually want consider exporting synthetic transactions on every + // first post-upgrade transaction, not just the first transaction after genesis. final var consTimeOfLastHandledTxn = blockRecordManager.consTimeOfLastHandledTxn(); final var isFirstTransaction = !consTimeOfLastHandledTxn.isAfter(Instant.EPOCH); @@ -610,7 +610,7 @@ private void handleUserTransaction( } catch (final Exception e) { logger.error("Possibly CATASTROPHIC failure while handling a user transaction", e); if (transactionInfo == null) { - final var baseData = extractTransactionBaseData(platformTxn.getContents()); + final var baseData = extractTransactionBaseData(platformTxn.getApplicationPayload()); if (baseData.transaction() == null) { // FUTURE: Charge node generic penalty, set values in record builder, and remove log statement logger.error("Failed to parse transaction from creator: {}", creator); @@ -826,13 +826,19 @@ private static long getGasLimitForContractTx(final TransactionBody txnBody, fina return switch (function) { case CONTRACT_CREATE -> txnBody.contractCreateInstance().gas(); case CONTRACT_CALL -> txnBody.contractCall().gas(); - case ETHEREUM_TRANSACTION -> EthTxData.populateEthTxData( - txnBody.ethereumTransaction().ethereumData().toByteArray()) - .gasLimit(); + case ETHEREUM_TRANSACTION -> getGasLimitFromEthTxData(txnBody); default -> 0L; }; } + private static long getGasLimitFromEthTxData(final TransactionBody txn) { + final var ethTxBody = txn.ethereumTransaction(); + if (ethTxBody == null) return 0L; + final var ethTxData = + EthTxData.populateEthTxData(ethTxBody.ethereumData().toByteArray()); + return ethTxData != null ? ethTxData.gasLimit() : 0L; + } + private ValidationResult validate( @NonNull final Instant consensusNow, @NonNull final KeyVerifier verifier, @@ -1028,15 +1034,15 @@ private record TransactionBaseData( @Nullable TransactionBody txBody, @Nullable AccountID payer) {} - private TransactionBaseData extractTransactionBaseData(@Nullable final byte[] contents) { + private TransactionBaseData extractTransactionBaseData(@NonNull final Bytes content) { // This method is only called if something fatal happened. We do a best effort approach to extract the // type of the transaction, the TransactionBody and the payer if not known. - if (contents == null || contents.length == 0) { + if (content.length() == 0) { return new TransactionBaseData(NONE, Bytes.EMPTY, null, null, null); } HederaFunctionality function = NONE; - Bytes transactionBytes = Bytes.wrap(contents); + Bytes transactionBytes = content; Transaction transaction = null; TransactionBody txBody = null; AccountID payer = null; diff --git a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/workflows/handle/HandleWorkflowInjectionModule.java b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/workflows/handle/HandleWorkflowInjectionModule.java index 8631cb63a9c9..b8d07497d96c 100644 --- a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/workflows/handle/HandleWorkflowInjectionModule.java +++ b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/workflows/handle/HandleWorkflowInjectionModule.java @@ -25,12 +25,12 @@ import com.hedera.node.app.service.mono.utils.accessors.TxnAccessor; import com.hedera.node.app.spi.validation.AttributeValidator; import com.hedera.node.app.spi.validation.ExpiryValidator; -import com.hedera.node.app.state.HederaState; import com.hedera.node.app.state.WorkingStateAccessor; import com.hedera.node.app.workflows.handle.validation.MonoExpiryValidator; import com.hedera.node.app.workflows.handle.validation.StandardizedAttributeValidator; import com.swirlds.common.utility.AutoCloseableWrapper; import com.swirlds.platform.system.Platform; +import com.swirlds.state.HederaState; import dagger.Binds; import dagger.Module; import dagger.Provides; diff --git a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/workflows/handle/PlatformStateUpdateFacility.java b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/workflows/handle/PlatformStateUpdateFacility.java index 447988ecb5f6..56c24446ba1c 100644 --- a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/workflows/handle/PlatformStateUpdateFacility.java +++ b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/workflows/handle/PlatformStateUpdateFacility.java @@ -26,10 +26,10 @@ import com.hedera.hapi.node.transaction.TransactionBody; import com.hedera.node.app.service.networkadmin.FreezeService; import com.hedera.node.app.service.networkadmin.impl.FreezeServiceImpl; -import com.hedera.node.app.spi.state.ReadableSingletonState; -import com.hedera.node.app.spi.state.ReadableStates; -import com.hedera.node.app.state.HederaState; import com.swirlds.platform.state.PlatformState; +import com.swirlds.state.HederaState; +import com.swirlds.state.spi.ReadableSingletonState; +import com.swirlds.state.spi.ReadableStates; import edu.umd.cs.findbugs.annotations.NonNull; import java.time.Instant; import javax.inject.Inject; diff --git a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/workflows/handle/SystemFileUpdateFacility.java b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/workflows/handle/SystemFileUpdateFacility.java index 09b06ea0e90a..bada881daa54 100644 --- a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/workflows/handle/SystemFileUpdateFacility.java +++ b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/workflows/handle/SystemFileUpdateFacility.java @@ -28,7 +28,6 @@ import com.hedera.node.app.config.ConfigProviderImpl; import com.hedera.node.app.fees.ExchangeRateManager; import com.hedera.node.app.fees.FeeManager; -import com.hedera.node.app.state.HederaState; import com.hedera.node.app.throttle.ThrottleServiceManager; import com.hedera.node.app.util.FileUtilities; import com.hedera.node.config.data.FilesConfig; @@ -36,6 +35,7 @@ import com.hedera.pbj.runtime.ParseException; import com.hedera.pbj.runtime.io.buffer.Bytes; import com.swirlds.config.api.Configuration; +import com.swirlds.state.HederaState; import edu.umd.cs.findbugs.annotations.NonNull; import javax.inject.Inject; import javax.inject.Singleton; diff --git a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/workflows/handle/TokenContextImpl.java b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/workflows/handle/TokenContextImpl.java index 0d94749cf9e5..62d2cc0f756e 100644 --- a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/workflows/handle/TokenContextImpl.java +++ b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/workflows/handle/TokenContextImpl.java @@ -26,13 +26,13 @@ import com.hedera.node.app.service.token.records.FinalizeContext; import com.hedera.node.app.service.token.records.TokenContext; import com.hedera.node.app.spi.metrics.StoreMetricsService; -import com.hedera.node.app.state.HederaState; import com.hedera.node.app.workflows.dispatcher.ReadableStoreFactory; import com.hedera.node.app.workflows.dispatcher.WritableStoreFactory; import com.hedera.node.app.workflows.handle.record.RecordListBuilder; import com.hedera.node.app.workflows.handle.record.SingleTransactionRecordBuilderImpl; import com.hedera.node.app.workflows.handle.stack.SavepointStackImpl; import com.swirlds.config.api.Configuration; +import com.swirlds.state.HederaState; import edu.umd.cs.findbugs.annotations.NonNull; import java.time.Instant; import java.util.Set; diff --git a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/workflows/handle/record/GenesisRecordsConsensusHook.java b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/workflows/handle/record/GenesisRecordsConsensusHook.java index c3e39b70ddb1..0b31cea95687 100644 --- a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/workflows/handle/record/GenesisRecordsConsensusHook.java +++ b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/workflows/handle/record/GenesisRecordsConsensusHook.java @@ -73,7 +73,9 @@ public void process(@NonNull final TokenContext context) { final var blockStore = context.readableStore(ReadableBlockRecordStore.class); // This process should only run ONCE, when a node receives its first transaction after startup - if (!shouldStreamRecords(blockStore, context)) return; + if (!shouldStreamRecords(blockStore, context)) { + return; + } final var consensusTime = context.consensusTime(); boolean recordsStreamed = false; diff --git a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/workflows/handle/record/MigrationContextImpl.java b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/workflows/handle/record/MigrationContextImpl.java index 159e6164ee25..d7e7a0c51d55 100644 --- a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/workflows/handle/record/MigrationContextImpl.java +++ b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/workflows/handle/record/MigrationContextImpl.java @@ -23,11 +23,11 @@ import com.hedera.node.app.spi.info.NetworkInfo; import com.hedera.node.app.spi.state.FilteredWritableStates; import com.hedera.node.app.spi.state.MigrationContext; -import com.hedera.node.app.spi.state.ReadableStates; -import com.hedera.node.app.spi.state.WritableStates; import com.hedera.node.app.spi.workflows.record.GenesisRecordsBuilder; import com.hedera.node.app.state.merkle.MerkleHederaState; import com.swirlds.config.api.Configuration; +import com.swirlds.state.spi.ReadableStates; +import com.swirlds.state.spi.WritableStates; import edu.umd.cs.findbugs.annotations.NonNull; import edu.umd.cs.findbugs.annotations.Nullable; diff --git a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/workflows/handle/stack/SavepointStackImpl.java b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/workflows/handle/stack/SavepointStackImpl.java index 6806b8d6f9f9..b9557ec24984 100644 --- a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/workflows/handle/stack/SavepointStackImpl.java +++ b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/workflows/handle/stack/SavepointStackImpl.java @@ -18,12 +18,12 @@ import static java.util.Objects.requireNonNull; -import com.hedera.node.app.spi.state.ReadableStates; -import com.hedera.node.app.spi.state.WritableStates; import com.hedera.node.app.spi.workflows.HandleContext.SavepointStack; -import com.hedera.node.app.state.HederaState; import com.hedera.node.app.state.ReadonlyStatesWrapper; import com.hedera.node.app.state.WrappedHederaState; +import com.swirlds.state.HederaState; +import com.swirlds.state.spi.ReadableStates; +import com.swirlds.state.spi.WritableStates; import edu.umd.cs.findbugs.annotations.NonNull; import java.util.ArrayDeque; import java.util.Deque; diff --git a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/workflows/handle/stack/WritableKVStateStack.java b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/workflows/handle/stack/WritableKVStateStack.java index f9db56a49f71..e2f9e907aea7 100644 --- a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/workflows/handle/stack/WritableKVStateStack.java +++ b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/workflows/handle/stack/WritableKVStateStack.java @@ -18,8 +18,8 @@ import static java.util.Objects.requireNonNull; -import com.hedera.node.app.spi.metrics.StoreMetrics; -import com.hedera.node.app.spi.state.WritableKVState; +import com.swirlds.state.spi.WritableKVState; +import com.swirlds.state.spi.metrics.StoreMetrics; import edu.umd.cs.findbugs.annotations.NonNull; import edu.umd.cs.findbugs.annotations.Nullable; import java.util.Iterator; @@ -48,7 +48,7 @@ public class WritableKVStateStack implements WritableKVState { /** * Constructs a {@link WritableKVStateStack} that delegates to the current {@link WritableKVState} in * the given {@link WritableStatesStack} for the given state key. A {@link WritableStatesStack} is an implementation - * of {@link com.hedera.node.app.spi.state.WritableStates} that delegates to the most recent version in a + * of {@link com.swirlds.state.spi.WritableStates} that delegates to the most recent version in a * {@link com.hedera.node.app.spi.workflows.HandleContext.SavepointStack} * * @param writableStatesStack the {@link WritableStatesStack} diff --git a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/workflows/handle/stack/WritableQueueStateStack.java b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/workflows/handle/stack/WritableQueueStateStack.java index b26e75c3363b..13eb5714d9a8 100644 --- a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/workflows/handle/stack/WritableQueueStateStack.java +++ b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/workflows/handle/stack/WritableQueueStateStack.java @@ -18,8 +18,9 @@ import static java.util.Objects.requireNonNull; -import com.hedera.node.app.spi.state.WritableQueueState; -import com.hedera.node.app.spi.state.WritableSingletonState; +import com.swirlds.state.spi.WritableQueueState; +import com.swirlds.state.spi.WritableSingletonState; +import com.swirlds.state.spi.WritableStates; import edu.umd.cs.findbugs.annotations.NonNull; import edu.umd.cs.findbugs.annotations.Nullable; import java.util.Iterator; @@ -47,7 +48,7 @@ public class WritableQueueStateStack implements WritableQueueState { /** * Constructs a {@link WritableQueueStateStack} that delegates to the current {@link WritableQueueState} in * the given {@link WritableStatesStack} for the given state key. A {@link WritableStatesStack} is an implementation - * of {@link com.hedera.node.app.spi.state.WritableStates} that delegates to the most recent version in a + * of {@link WritableStates} that delegates to the most recent version in a * {@link com.hedera.node.app.spi.workflows.HandleContext.SavepointStack} * * @param writableStatesStack the {@link WritableStatesStack} diff --git a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/workflows/handle/stack/WritableSingletonStateStack.java b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/workflows/handle/stack/WritableSingletonStateStack.java index c4a747b039a4..09907792c62e 100644 --- a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/workflows/handle/stack/WritableSingletonStateStack.java +++ b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/workflows/handle/stack/WritableSingletonStateStack.java @@ -18,7 +18,8 @@ import static java.util.Objects.requireNonNull; -import com.hedera.node.app.spi.state.WritableSingletonState; +import com.swirlds.state.spi.WritableSingletonState; +import com.swirlds.state.spi.WritableStates; import edu.umd.cs.findbugs.annotations.NonNull; import edu.umd.cs.findbugs.annotations.Nullable; @@ -44,7 +45,7 @@ public class WritableSingletonStateStack implements WritableSingletonState /** * Constructs a {@link WritableSingletonStateStack} that delegates to the current {@link WritableSingletonState} in * the given {@link WritableStatesStack} for the given state key. A {@link WritableStatesStack} is an implementation - * of {@link com.hedera.node.app.spi.state.WritableStates} that delegates to the most recent version in a + * of {@link WritableStates} that delegates to the most recent version in a * {@link com.hedera.node.app.spi.workflows.HandleContext.SavepointStack} * * @param writableStatesStack the {@link WritableStatesStack} diff --git a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/workflows/handle/stack/WritableStatesStack.java b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/workflows/handle/stack/WritableStatesStack.java index f8529a2a8fd4..e6234728a119 100644 --- a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/workflows/handle/stack/WritableStatesStack.java +++ b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/workflows/handle/stack/WritableStatesStack.java @@ -18,11 +18,11 @@ import static java.util.Objects.requireNonNull; -import com.hedera.node.app.spi.state.ReadableStates; -import com.hedera.node.app.spi.state.WritableKVState; -import com.hedera.node.app.spi.state.WritableQueueState; -import com.hedera.node.app.spi.state.WritableSingletonState; -import com.hedera.node.app.spi.state.WritableStates; +import com.swirlds.state.spi.ReadableStates; +import com.swirlds.state.spi.WritableKVState; +import com.swirlds.state.spi.WritableQueueState; +import com.swirlds.state.spi.WritableSingletonState; +import com.swirlds.state.spi.WritableStates; import edu.umd.cs.findbugs.annotations.NonNull; import java.util.Set; diff --git a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/workflows/handle/validation/AttributeValidatorImpl.java b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/workflows/handle/validation/AttributeValidatorImpl.java index fe790e8dec36..64c272c578e3 100644 --- a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/workflows/handle/validation/AttributeValidatorImpl.java +++ b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/workflows/handle/validation/AttributeValidatorImpl.java @@ -23,6 +23,7 @@ import static com.hedera.hapi.node.base.ResponseCodeEnum.INVALID_ZERO_BYTE_IN_STRING; import static com.hedera.hapi.node.base.ResponseCodeEnum.MEMO_TOO_LONG; import static com.hedera.node.app.spi.key.KeyUtils.isValid; +import static com.hedera.node.app.spi.validation.ExpiryMeta.NA; import static com.hedera.node.app.spi.workflows.HandleException.validateTrue; import static java.util.Objects.requireNonNull; @@ -91,7 +92,8 @@ public void validateExpiry(long expiry) { context.configuration().getConfigData(EntitiesConfig.class).maxLifetime(); final var now = context.consensusNow().getEpochSecond(); final var expiryGivenMaxLifetime = now + maxEntityLifetime; - validateTrue(expiry > now && expiry < expiryGivenMaxLifetime, INVALID_EXPIRATION_TIME); + final var impliedExpiry = expiry == NA ? expiryGivenMaxLifetime : expiry; + validateTrue(impliedExpiry > now && impliedExpiry <= expiryGivenMaxLifetime, INVALID_EXPIRATION_TIME); } /** diff --git a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/workflows/ingest/IngestChecker.java b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/workflows/ingest/IngestChecker.java index 5ddec151c126..41d69bec0f9c 100644 --- a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/workflows/ingest/IngestChecker.java +++ b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/workflows/ingest/IngestChecker.java @@ -26,7 +26,6 @@ import static com.hedera.hapi.node.base.ResponseCodeEnum.BUSY; import static com.hedera.hapi.node.base.ResponseCodeEnum.DUPLICATE_TRANSACTION; import static com.hedera.hapi.node.base.ResponseCodeEnum.INSUFFICIENT_GAS; -import static com.hedera.hapi.node.base.ResponseCodeEnum.INSUFFICIENT_TX_FEE; import static com.hedera.hapi.node.base.ResponseCodeEnum.INVALID_ETHEREUM_TRANSACTION; import static com.hedera.hapi.node.base.ResponseCodeEnum.INVALID_NODE_ACCOUNT; import static com.hedera.hapi.node.base.ResponseCodeEnum.INVALID_SIGNATURE; @@ -62,7 +61,6 @@ import com.hedera.node.app.spi.signatures.SignatureVerification; import com.hedera.node.app.spi.workflows.PreCheckException; import com.hedera.node.app.state.DeduplicationCache; -import com.hedera.node.app.state.HederaState; import com.hedera.node.app.throttle.SynchronizedThrottleAccumulator; import com.hedera.node.app.workflows.SolvencyPreCheck; import com.hedera.node.app.workflows.TransactionChecker; @@ -74,6 +72,7 @@ import com.hedera.node.config.data.LazyCreationConfig; import com.hedera.pbj.runtime.io.buffer.Bytes; import com.swirlds.config.api.Configuration; +import com.swirlds.state.HederaState; import edu.umd.cs.findbugs.annotations.NonNull; import java.math.BigInteger; import java.time.Instant; @@ -189,15 +188,15 @@ public TransactionInfo runAllChecks( // If the fee offered does not cover the gas cost of the transaction, then // we would again end up with uncompensated work - final var gasCost = solvencyPreCheck.estimateAdditionalCosts(txBody, CONTRACT_CALL, consensusTime) - - txBody.contractCallOrThrow().amount(); - validateTruePreCheck(txBody.transactionFee() >= gasCost, INSUFFICIENT_TX_FEE); + // final var gasCost = solvencyPreCheck.estimateAdditionalCosts(txBody, CONTRACT_CALL, consensusTime) + // - txBody.contractCallOrThrow().amount(); + // validateTruePreCheck(txBody.transactionFee() >= gasCost, INSUFFICIENT_TX_FEE); } else if (functionality == ETHEREUM_TRANSACTION) { final var ethTxData = populateEthTxData( requireNonNull(txBody.ethereumTransactionOrThrow().ethereumData()) .toByteArray()); validateTruePreCheck(nonNull(ethTxData), INVALID_ETHEREUM_TRANSACTION); - validateTruePreCheck(requireNonNull(ethTxData.gasLimit()) >= INTRINSIC_GAS_LOWER_BOUND, INSUFFICIENT_GAS); + validateTruePreCheck(ethTxData.gasLimit() >= INTRINSIC_GAS_LOWER_BOUND, INSUFFICIENT_GAS); // Do not allow sending HBars to Burn Address if (ethTxData.value().compareTo(BigInteger.ZERO) > 0) { validateFalsePreCheck(Arrays.equals(ethTxData.to(), new byte[20]), INVALID_SOLIDITY_ADDRESS); diff --git a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/workflows/ingest/IngestWorkflowImpl.java b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/workflows/ingest/IngestWorkflowImpl.java index 0ec2635052a9..44333cea79fd 100644 --- a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/workflows/ingest/IngestWorkflowImpl.java +++ b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/workflows/ingest/IngestWorkflowImpl.java @@ -23,12 +23,12 @@ import com.hedera.node.app.spi.workflows.HandleException; import com.hedera.node.app.spi.workflows.InsufficientBalanceException; import com.hedera.node.app.spi.workflows.PreCheckException; -import com.hedera.node.app.state.HederaState; import com.hedera.node.app.workflows.TransactionChecker; import com.hedera.node.config.ConfigProvider; import com.hedera.pbj.runtime.io.buffer.BufferedData; import com.hedera.pbj.runtime.io.buffer.Bytes; import com.swirlds.common.utility.AutoCloseableWrapper; +import com.swirlds.state.HederaState; import edu.umd.cs.findbugs.annotations.NonNull; import java.io.IOException; import java.io.UncheckedIOException; diff --git a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/workflows/prehandle/AdaptedMonoEventExpansion.java b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/workflows/prehandle/AdaptedMonoEventExpansion.java index e895f183e344..b01c4b9846c6 100644 --- a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/workflows/prehandle/AdaptedMonoEventExpansion.java +++ b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/workflows/prehandle/AdaptedMonoEventExpansion.java @@ -24,10 +24,10 @@ import com.hedera.node.app.service.mono.sigs.EventExpansion; import com.hedera.node.app.service.mono.sigs.order.SigReqsManager; import com.hedera.node.app.service.mono.utils.accessors.SignedTxnAccessor; -import com.hedera.node.app.state.HederaState; import com.hedera.node.app.workflows.dispatcher.ReadableStoreFactory; import com.swirlds.platform.system.events.Event; import com.swirlds.platform.system.transaction.Transaction; +import com.swirlds.state.HederaState; import edu.umd.cs.findbugs.annotations.NonNull; import java.util.ArrayList; import java.util.List; @@ -63,7 +63,8 @@ public void expand(final Event event, final HederaState state, final NodeInfo no final List forWorkflows = new ArrayList<>(); event.forEachTransaction(txn -> { try { - final var accessor = SignedTxnAccessor.from(txn.getContents()); + final var accessor = + SignedTxnAccessor.from(txn.getApplicationPayload().toByteArray()); if (typesForWorkflows.contains(accessor.getFunction())) { forWorkflows.add(txn); } else { diff --git a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/workflows/prehandle/PreHandleWorkflowImpl.java b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/workflows/prehandle/PreHandleWorkflowImpl.java index 732f6c95551a..134d336504e3 100644 --- a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/workflows/prehandle/PreHandleWorkflowImpl.java +++ b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/workflows/prehandle/PreHandleWorkflowImpl.java @@ -47,7 +47,6 @@ import com.hedera.node.app.workflows.dispatcher.TransactionDispatcher; import com.hedera.node.config.ConfigProvider; import com.hedera.node.config.VersionedConfiguration; -import com.hedera.pbj.runtime.io.buffer.Bytes; import com.swirlds.platform.system.events.Event; import com.swirlds.platform.system.transaction.Transaction; import edu.umd.cs.findbugs.annotations.NonNull; @@ -176,7 +175,7 @@ public PreHandleResult preHandleTransaction( // Transaction info is a pure function of the transaction, so we can // always reuse it from a prior result txInfo = previousResult == null - ? transactionChecker.parseAndCheck(Bytes.wrap(platformTx.getContents())) + ? transactionChecker.parseAndCheck(platformTx.getApplicationPayload()) : previousResult.txInfo(); if (txInfo == null) { // In particular, a null transaction info means we already know the transaction's final failure status diff --git a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/workflows/query/QueryContextImpl.java b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/workflows/query/QueryContextImpl.java index c34f39a8f098..757dd208baee 100644 --- a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/workflows/query/QueryContextImpl.java +++ b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/workflows/query/QueryContextImpl.java @@ -30,9 +30,9 @@ import com.hedera.node.app.spi.records.BlockRecordInfo; import com.hedera.node.app.spi.records.RecordCache; import com.hedera.node.app.spi.workflows.QueryContext; -import com.hedera.node.app.state.HederaState; import com.hedera.node.app.workflows.dispatcher.ReadableStoreFactory; import com.swirlds.config.api.Configuration; +import com.swirlds.state.HederaState; import edu.umd.cs.findbugs.annotations.NonNull; import edu.umd.cs.findbugs.annotations.Nullable; diff --git a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/workflows/query/QueryDispatcher.java b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/workflows/query/QueryDispatcher.java index 3a94fa1d68e2..4b7919f9e4e4 100644 --- a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/workflows/query/QueryDispatcher.java +++ b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/workflows/query/QueryDispatcher.java @@ -87,6 +87,7 @@ public QueryHandler getHandler(@NonNull final Query query) { case TOKEN_GET_NFT_INFO -> handlers.tokenGetNftInfoHandler(); case TOKEN_GET_NFT_INFOS -> handlers.tokenGetNftInfosHandler(); + case NODE_GET_INFO -> throw new UnsupportedOperationException("Not implemented yet"); case UNSET -> throw new UnsupportedOperationException(QUERY_NOT_SET); }; } diff --git a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/workflows/query/QueryWorkflowImpl.java b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/workflows/query/QueryWorkflowImpl.java index eb582465867f..92e0cb9b0f8a 100644 --- a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/workflows/query/QueryWorkflowImpl.java +++ b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/workflows/query/QueryWorkflowImpl.java @@ -51,7 +51,6 @@ import com.hedera.node.app.spi.workflows.PreCheckException; import com.hedera.node.app.spi.workflows.QueryContext; import com.hedera.node.app.spi.workflows.QueryHandler; -import com.hedera.node.app.state.HederaState; import com.hedera.node.app.throttle.SynchronizedThrottleAccumulator; import com.hedera.node.app.workflows.dispatcher.ReadableStoreFactory; import com.hedera.node.app.workflows.ingest.IngestChecker; @@ -64,6 +63,7 @@ import com.hedera.pbj.runtime.io.buffer.BufferedData; import com.hedera.pbj.runtime.io.buffer.Bytes; import com.swirlds.common.utility.AutoCloseableWrapper; +import com.swirlds.state.HederaState; import edu.umd.cs.findbugs.annotations.NonNull; import io.grpc.Status; import io.grpc.StatusRuntimeException; diff --git a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/workflows/query/QueryWorkflowInjectionModule.java b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/workflows/query/QueryWorkflowInjectionModule.java index 18c611c1c04f..11853f667980 100644 --- a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/workflows/query/QueryWorkflowInjectionModule.java +++ b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/workflows/query/QueryWorkflowInjectionModule.java @@ -25,10 +25,10 @@ import com.hedera.node.app.service.networkadmin.impl.handlers.NetworkAdminHandlers; import com.hedera.node.app.service.schedule.impl.handlers.ScheduleHandlers; import com.hedera.node.app.service.token.impl.handlers.TokenHandlers; -import com.hedera.node.app.state.HederaState; import com.hedera.node.app.state.WorkingStateAccessor; import com.hedera.pbj.runtime.Codec; import com.swirlds.common.utility.AutoCloseableWrapper; +import com.swirlds.state.HederaState; import dagger.Binds; import dagger.Module; import dagger.Provides; diff --git a/hedera-node/hedera-app/src/main/java/module-info.java b/hedera-node/hedera-app/src/main/java/module-info.java index 2fba07c5cba7..eab0303a5b01 100644 --- a/hedera-node/hedera-app/src/main/java/module-info.java +++ b/hedera-node/hedera-app/src/main/java/module-info.java @@ -19,27 +19,28 @@ requires transitive com.hedera.pbj.runtime; requires transitive com.swirlds.common; requires transitive com.swirlds.config.api; - requires transitive com.swirlds.merkle; - requires transitive com.swirlds.merkledb; requires transitive com.swirlds.metrics.api; requires transitive com.swirlds.platform.core; - requires transitive com.swirlds.virtualmap; + requires transitive com.swirlds.state.api; requires transitive dagger; requires transitive grpc.stub; requires transitive javax.inject; requires com.hedera.node.app.hapi.fees; requires com.hedera.node.app.service.consensus; requires com.hedera.node.app.service.contract; - requires com.hedera.node.app.service.evm; requires com.hedera.node.app.service.file; requires com.hedera.node.app.service.network.admin; requires com.hedera.node.app.service.util; requires com.google.common; requires com.google.protobuf; + requires com.hedera.evm; requires com.swirlds.base; requires com.swirlds.config.extensions; requires com.swirlds.fcqueue; requires com.swirlds.logging; + requires com.swirlds.merkle; + requires com.swirlds.merkledb; + requires com.swirlds.virtualmap; requires grpc.netty; requires io.grpc; requires io.netty.handler; @@ -59,10 +60,6 @@ com.hedera.node.app.test.fixtures; exports com.hedera.node.app.state.merkle to com.hedera.node.services.cli; - exports com.hedera.node.app.state.merkle.disk to - com.hedera.node.services.cli; - exports com.hedera.node.app.state.merkle.memory to - com.hedera.node.services.cli; exports com.hedera.node.app.workflows.dispatcher; exports com.hedera.node.app.config; exports com.hedera.node.app.workflows.handle.validation; @@ -74,8 +71,6 @@ com.hedera.node.app.test.fixtures; exports com.hedera.node.app.workflows.handle.record to com.hedera.node.app.test.fixtures; - exports com.hedera.node.app.state.merkle.queue to - com.swirlds.platform; exports com.hedera.node.app.version to com.hedera.node.app.test.fixtures, com.swirlds.platform; diff --git a/hedera-node/hedera-app/src/test/java/com/hedera/node/app/ids/WritableEntityIdStoreTest.java b/hedera-node/hedera-app/src/test/java/com/hedera/node/app/ids/WritableEntityIdStoreTest.java index 1be1c06818e2..bd4c436aaab7 100644 --- a/hedera-node/hedera-app/src/test/java/com/hedera/node/app/ids/WritableEntityIdStoreTest.java +++ b/hedera-node/hedera-app/src/test/java/com/hedera/node/app/ids/WritableEntityIdStoreTest.java @@ -21,9 +21,9 @@ import com.hedera.hapi.node.state.common.EntityNumber; import com.hedera.node.app.spi.fixtures.state.MapWritableStates; -import com.hedera.node.app.spi.state.WritableSingletonState; -import com.hedera.node.app.spi.state.WritableSingletonStateBase; -import com.hedera.node.app.spi.state.WritableStates; +import com.swirlds.platform.state.spi.WritableSingletonStateBase; +import com.swirlds.state.spi.WritableSingletonState; +import com.swirlds.state.spi.WritableStates; import java.util.Map; import java.util.concurrent.atomic.AtomicReference; import org.junit.jupiter.api.Test; diff --git a/hedera-node/hedera-app/src/test/java/com/hedera/node/app/platform/event/EventMigrationTest.java b/hedera-node/hedera-app/src/test/java/com/hedera/node/app/platform/event/EventMigrationTest.java index 0e9fdf2d5691..dcb49ed6cce7 100644 --- a/hedera-node/hedera-app/src/test/java/com/hedera/node/app/platform/event/EventMigrationTest.java +++ b/hedera-node/hedera-app/src/test/java/com/hedera/node/app/platform/event/EventMigrationTest.java @@ -69,7 +69,8 @@ public void migration() throws URISyntaxException, IOException { .toPath(), false)) { while (iterator.hasNext()) { - final BaseEventHashedData hashedData = iterator.next().getBaseEventHashedData(); + final BaseEventHashedData hashedData = + iterator.next().getGossipEvent().getHashedData(); numEvents++; CryptographyHolder.get().digestSync(hashedData); eventHashes.add(hashedData.getHash()); diff --git a/hedera-node/hedera-app/src/test/java/com/hedera/node/app/records/ReadableBlockRecordStoreTest.java b/hedera-node/hedera-app/src/test/java/com/hedera/node/app/records/ReadableBlockRecordStoreTest.java index 676b949a3450..d9f0eb9b6ecd 100644 --- a/hedera-node/hedera-app/src/test/java/com/hedera/node/app/records/ReadableBlockRecordStoreTest.java +++ b/hedera-node/hedera-app/src/test/java/com/hedera/node/app/records/ReadableBlockRecordStoreTest.java @@ -20,9 +20,9 @@ import static org.assertj.core.api.Assertions.assertThat; import com.hedera.hapi.node.state.blockrecords.BlockInfo; -import com.hedera.node.app.spi.fixtures.state.MapReadableStates; -import com.hedera.node.app.spi.state.ReadableSingletonStateBase; import com.hedera.pbj.runtime.io.buffer.Bytes; +import com.swirlds.platform.state.spi.ReadableSingletonStateBase; +import com.swirlds.platform.test.fixtures.state.MapReadableStates; import java.util.Map; import org.assertj.core.api.Assertions; import org.junit.jupiter.api.Test; diff --git a/hedera-node/hedera-app/src/test/java/com/hedera/node/app/services/ServicesRegistryImplTest.java b/hedera-node/hedera-app/src/test/java/com/hedera/node/app/services/ServicesRegistryImplTest.java index acd5a5da2cfc..6b6a239baa42 100644 --- a/hedera-node/hedera-app/src/test/java/com/hedera/node/app/services/ServicesRegistryImplTest.java +++ b/hedera-node/hedera-app/src/test/java/com/hedera/node/app/services/ServicesRegistryImplTest.java @@ -16,7 +16,6 @@ package com.hedera.node.app.services; -import static com.hedera.node.app.spi.fixtures.state.TestSchema.CURRENT_VERSION; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.mockito.ArgumentMatchers.any; @@ -28,7 +27,6 @@ import com.hedera.node.app.spi.fixtures.state.TestSchema; import com.hedera.node.app.spi.state.StateDefinition; import com.hedera.node.app.spi.workflows.record.GenesisRecordsBuilder; -import com.hedera.node.app.version.HederaSoftwareVersion; import com.swirlds.common.constructable.ConstructableRegistry; import com.swirlds.common.constructable.ConstructableRegistryException; import org.junit.jupiter.api.DisplayName; @@ -64,15 +62,13 @@ void nullGenesisRecordsThrows() { @Test void registerCallsTheConstructableRegistry() throws ConstructableRegistryException { final var registry = new ServicesRegistryImpl(cr, genesisRecords); - registry.register( - TestService.newBuilder() - .name("registerCallsTheConstructableRegistryTest") - .schema(TestSchema.newBuilder() - .minorVersion(1) - .stateToCreate(StateDefinition.singleton("Singleton", Timestamp.JSON)) - .build()) - .build(), - new HederaSoftwareVersion(CURRENT_VERSION, CURRENT_VERSION, 0)); + registry.register(TestService.newBuilder() + .name("registerCallsTheConstructableRegistryTest") + .schema(TestSchema.newBuilder() + .minorVersion(1) + .stateToCreate(StateDefinition.singleton("Singleton", Timestamp.JSON)) + .build()) + .build()); //noinspection removal verify(cr, atLeastOnce()).registerConstructable(any()); } @@ -80,15 +76,9 @@ void registerCallsTheConstructableRegistry() throws ConstructableRegistryExcepti @Test void registrationsAreSortedByName() { final var registry = new ServicesRegistryImpl(cr, genesisRecords); - registry.register( - TestService.newBuilder().name("B").build(), - new HederaSoftwareVersion(CURRENT_VERSION, CURRENT_VERSION, 0)); - registry.register( - TestService.newBuilder().name("C").build(), - new HederaSoftwareVersion(CURRENT_VERSION, CURRENT_VERSION, 0)); - registry.register( - TestService.newBuilder().name("A").build(), - new HederaSoftwareVersion(CURRENT_VERSION, CURRENT_VERSION, 0)); + registry.register(TestService.newBuilder().name("B").build()); + registry.register(TestService.newBuilder().name("C").build()); + registry.register(TestService.newBuilder().name("A").build()); final var registrations = registry.registrations(); assertThat(registrations.stream().map(r -> r.service().getServiceName())) diff --git a/hedera-node/hedera-app/src/test/java/com/hedera/node/app/state/HederaLifecyclesImplTest.java b/hedera-node/hedera-app/src/test/java/com/hedera/node/app/state/HederaLifecyclesImplTest.java index 41e803b5f311..fa094f49d433 100644 --- a/hedera-node/hedera-app/src/test/java/com/hedera/node/app/state/HederaLifecyclesImplTest.java +++ b/hedera-node/hedera-app/src/test/java/com/hedera/node/app/state/HederaLifecyclesImplTest.java @@ -17,7 +17,7 @@ package com.hedera.node.app.state; import static com.hedera.node.app.service.token.impl.TokenServiceImpl.STAKING_INFO_KEY; -import static com.hedera.node.app.state.merkle.AddresBookUtils.createPretendBookFrom; +import static com.swirlds.platform.test.fixtures.addressbook.AddresBookUtils.createPretendBookFrom; import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertThrows; @@ -30,21 +30,21 @@ import com.hedera.hapi.node.state.token.StakingNodeInfo; import com.hedera.node.app.Hedera; import com.hedera.node.app.service.token.TokenService; -import com.hedera.node.app.spi.fixtures.state.MapReadableKVState; -import com.hedera.node.app.spi.fixtures.state.MapReadableStates; -import com.hedera.node.app.spi.fixtures.state.MapWritableKVState; import com.hedera.node.app.spi.fixtures.state.MapWritableStates; import com.hedera.node.app.state.merkle.MerkleHederaState; import com.hedera.node.app.state.merkle.MerkleTestBase; -import com.hedera.node.app.state.merkle.disk.OnDiskKey; -import com.hedera.node.app.state.merkle.disk.OnDiskValue; import com.swirlds.common.context.PlatformContext; import com.swirlds.common.platform.NodeId; import com.swirlds.platform.state.PlatformState; +import com.swirlds.platform.state.merkle.disk.OnDiskKey; +import com.swirlds.platform.state.merkle.disk.OnDiskValue; import com.swirlds.platform.system.InitTrigger; import com.swirlds.platform.system.Platform; import com.swirlds.platform.system.Round; import com.swirlds.platform.system.events.Event; +import com.swirlds.platform.test.fixtures.state.MapReadableKVState; +import com.swirlds.platform.test.fixtures.state.MapReadableStates; +import com.swirlds.platform.test.fixtures.state.MapWritableKVState; import com.swirlds.virtualmap.VirtualMap; import java.util.Iterator; import java.util.NoSuchElementException; diff --git a/hedera-node/hedera-app/src/test/java/com/hedera/node/app/state/merkle/DependencyMigrationTest.java b/hedera-node/hedera-app/src/test/java/com/hedera/node/app/state/merkle/DependencyMigrationTest.java index 6120193e3a21..a17ff8854359 100644 --- a/hedera-node/hedera-app/src/test/java/com/hedera/node/app/state/merkle/DependencyMigrationTest.java +++ b/hedera-node/hedera-app/src/test/java/com/hedera/node/app/state/merkle/DependencyMigrationTest.java @@ -32,13 +32,13 @@ import com.hedera.node.app.spi.state.Schema; import com.hedera.node.app.spi.state.SchemaRegistry; import com.hedera.node.app.spi.state.StateDefinition; -import com.hedera.node.app.spi.state.WritableStates; import com.hedera.node.app.throttle.ThrottleAccumulator; import com.hedera.node.app.version.HederaSoftwareVersion; import com.hedera.node.config.VersionedConfigImpl; import com.hedera.node.config.testfixtures.HederaTestConfigBuilder; import com.swirlds.common.constructable.ConstructableRegistry; import com.swirlds.metrics.api.Metrics; +import com.swirlds.state.spi.WritableStates; import edu.umd.cs.findbugs.annotations.NonNull; import java.util.LinkedList; import java.util.List; @@ -160,7 +160,7 @@ public void migrate(@NonNull MigrationContext ctx) { } }; final DependentService dsService = new DependentService(); - Set.of(entityService, dsService).forEach(service -> servicesRegistry.register(service, VERSION)); + Set.of(entityService, dsService).forEach(service -> servicesRegistry.register(service)); // When: the migrations are run final var subject = new OrderedServiceMigrator(servicesRegistry); @@ -264,8 +264,7 @@ public void migrate(@NonNull MigrationContext ctx) { } }; // Intentionally register the services in a different order than the expected migration order - List.of(dsService, serviceA, entityIdService, serviceB) - .forEach(service -> servicesRegistry.register(service, VERSION)); + List.of(dsService, serviceA, entityIdService, serviceB).forEach(service -> servicesRegistry.register(service)); // When: the migrations are run final var subject = new OrderedServiceMigrator(servicesRegistry); diff --git a/hedera-node/hedera-app/src/test/java/com/hedera/node/app/state/merkle/MerkleHederaStateTest.java b/hedera-node/hedera-app/src/test/java/com/hedera/node/app/state/merkle/MerkleHederaStateTest.java index 4563caad2fc6..97a0480bdfc3 100644 --- a/hedera-node/hedera-app/src/test/java/com/hedera/node/app/state/merkle/MerkleHederaStateTest.java +++ b/hedera-node/hedera-app/src/test/java/com/hedera/node/app/state/merkle/MerkleHederaStateTest.java @@ -21,25 +21,26 @@ import static org.junit.jupiter.api.Assertions.assertThrows; import com.hedera.node.app.spi.fixtures.state.TestSchema; -import com.hedera.node.app.spi.state.ReadableKVState; -import com.hedera.node.app.spi.state.ReadableQueueState; -import com.hedera.node.app.spi.state.ReadableSingletonState; import com.hedera.node.app.spi.state.StateDefinition; -import com.hedera.node.app.spi.state.WritableKVState; -import com.hedera.node.app.spi.state.WritableQueueState; -import com.hedera.node.app.spi.state.WritableSingletonState; -import com.hedera.node.app.state.HederaState; import com.swirlds.base.state.MutabilityException; import com.swirlds.common.context.PlatformContext; import com.swirlds.common.merkle.MerkleNode; import com.swirlds.merkle.map.MerkleMap; import com.swirlds.platform.state.PlatformState; +import com.swirlds.platform.state.merkle.StateUtils; import com.swirlds.platform.system.InitTrigger; import com.swirlds.platform.system.Platform; import com.swirlds.platform.system.Round; import com.swirlds.platform.system.SoftwareVersion; import com.swirlds.platform.system.address.AddressBook; import com.swirlds.platform.system.events.Event; +import com.swirlds.state.HederaState; +import com.swirlds.state.spi.ReadableKVState; +import com.swirlds.state.spi.ReadableQueueState; +import com.swirlds.state.spi.ReadableSingletonState; +import com.swirlds.state.spi.WritableKVState; +import com.swirlds.state.spi.WritableQueueState; +import com.swirlds.state.spi.WritableSingletonState; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; diff --git a/hedera-node/hedera-app/src/test/java/com/hedera/node/app/state/merkle/MerkleSchemaRegistryTest.java b/hedera-node/hedera-app/src/test/java/com/hedera/node/app/state/merkle/MerkleSchemaRegistryTest.java index 494f840fe127..40bf5db264e1 100644 --- a/hedera-node/hedera-app/src/test/java/com/hedera/node/app/state/merkle/MerkleSchemaRegistryTest.java +++ b/hedera-node/hedera-app/src/test/java/com/hedera/node/app/state/merkle/MerkleSchemaRegistryTest.java @@ -28,12 +28,8 @@ import com.hedera.node.app.spi.fixtures.state.TestSchema; import com.hedera.node.app.spi.info.NetworkInfo; import com.hedera.node.app.spi.state.MigrationContext; -import com.hedera.node.app.spi.state.ReadableKVState; -import com.hedera.node.app.spi.state.ReadableSingletonState; import com.hedera.node.app.spi.state.Schema; import com.hedera.node.app.spi.state.StateDefinition; -import com.hedera.node.app.spi.state.WritableKVState; -import com.hedera.node.app.spi.state.WritableSingletonState; import com.hedera.node.app.spi.workflows.record.GenesisRecordsBuilder; import com.hedera.node.config.data.HederaConfig; import com.swirlds.common.constructable.ConstructableRegistry; @@ -41,6 +37,10 @@ import com.swirlds.config.api.Configuration; import com.swirlds.merkledb.MerkleDb; import com.swirlds.metrics.api.Metrics; +import com.swirlds.state.spi.ReadableKVState; +import com.swirlds.state.spi.ReadableSingletonState; +import com.swirlds.state.spi.WritableKVState; +import com.swirlds.state.spi.WritableSingletonState; import edu.umd.cs.findbugs.annotations.NonNull; import java.util.LinkedList; import java.util.Set; diff --git a/hedera-node/hedera-app/src/test/java/com/hedera/node/app/state/merkle/MerkleTestBase.java b/hedera-node/hedera-app/src/test/java/com/hedera/node/app/state/merkle/MerkleTestBase.java index d6613f8539a7..4be63ce53d80 100644 --- a/hedera-node/hedera-app/src/test/java/com/hedera/node/app/state/merkle/MerkleTestBase.java +++ b/hedera-node/hedera-app/src/test/java/com/hedera/node/app/state/merkle/MerkleTestBase.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023-2024 Hedera Hashgraph, LLC + * Copyright (C) 2024 Hedera Hashgraph, LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,40 +16,20 @@ package com.hedera.node.app.state.merkle; -import com.hedera.hapi.node.base.SemanticVersion; -import com.hedera.node.app.spi.fixtures.state.StateTestBase; import com.hedera.node.app.spi.fixtures.state.TestSchema; import com.hedera.node.app.spi.state.StateDefinition; -import com.hedera.node.app.state.merkle.disk.OnDiskKey; -import com.hedera.node.app.state.merkle.disk.OnDiskKeySerializer; -import com.hedera.node.app.state.merkle.disk.OnDiskValue; -import com.hedera.node.app.state.merkle.disk.OnDiskValueSerializer; -import com.hedera.node.app.state.merkle.memory.InMemoryKey; -import com.hedera.node.app.state.merkle.memory.InMemoryValue; -import com.hedera.node.app.state.merkle.queue.QueueNode; -import com.hedera.node.app.state.merkle.singleton.SingletonNode; import com.hedera.pbj.runtime.Codec; -import com.swirlds.common.constructable.ConstructableRegistry; -import com.swirlds.common.constructable.ConstructableRegistryException; -import com.swirlds.common.crypto.DigestType; -import com.swirlds.common.io.streams.MerkleDataInputStream; -import com.swirlds.common.io.streams.MerkleDataOutputStream; import com.swirlds.common.merkle.MerkleNode; -import com.swirlds.common.merkle.crypto.MerkleCryptoFactory; -import com.swirlds.common.merkle.crypto.MerkleCryptography; import com.swirlds.common.utility.Labeled; import com.swirlds.merkle.map.MerkleMap; import com.swirlds.merkledb.MerkleDb; -import com.swirlds.merkledb.MerkleDbDataSourceBuilder; -import com.swirlds.merkledb.MerkleDbTableConfig; +import com.swirlds.platform.state.merkle.disk.OnDiskKey; +import com.swirlds.platform.state.merkle.disk.OnDiskValue; +import com.swirlds.platform.state.merkle.memory.InMemoryKey; +import com.swirlds.platform.state.merkle.memory.InMemoryValue; +import com.swirlds.platform.test.fixtures.state.StateTestBase; import com.swirlds.virtualmap.VirtualMap; -import edu.umd.cs.findbugs.annotations.NonNull; -import java.io.ByteArrayInputStream; -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.nio.file.Path; import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.io.TempDir; /** * This base class provides helpful methods and defaults for simplifying the other merkle related @@ -73,72 +53,19 @@ * complexity, and also requires a storage directory, so rather than creating these for every test * even if they don't need it, I just use it for virtual map specific tests). */ -public class MerkleTestBase extends StateTestBase { - public static final String FIRST_SERVICE = "First-Service"; - public static final String SECOND_SERVICE = "Second-Service"; - public static final String UNKNOWN_SERVICE = "Bogus-Service"; +public class MerkleTestBase extends com.swirlds.platform.test.fixtures.state.merkle.MerkleTestBase { - /** A TEST ONLY {@link Codec} to be used with String data types */ - public static final Codec STRING_CODEC = TestStringCodec.SINGLETON; - /** A TEST ONLY {@link Codec} to be used with Long data types */ - public static final Codec LONG_CODEC = TestLongCodec.SINGLETON; - - /** Used by some tests that need to hash */ - protected static final MerkleCryptography CRYPTO = MerkleCryptoFactory.getInstance(); - - // These longs are used with the "space" k/v state - public static final long A_LONG_KEY = 0L; - public static final long B_LONG_KEY = 1L; - public static final long C_LONG_KEY = 2L; - public static final long D_LONG_KEY = 3L; - public static final long E_LONG_KEY = 4L; - public static final long F_LONG_KEY = 5L; - public static final long G_LONG_KEY = 6L; - - /** - * This {@link ConstructableRegistry} is required for serialization tests. It is expensive to - * configure it, so it is null unless {@link #setupConstructableRegistry()} has been called by - * the test code. - */ - protected ConstructableRegistry registry; - - @TempDir - private Path virtualDbPath; - - // The "FRUIT" Map is part of FIRST_SERVICE - protected String fruitLabel; protected StateMetadata fruitMetadata; - protected MerkleMap, InMemoryValue> fruitMerkleMap; - - // An alternative "FRUIT" Map that is also part of FIRST_SERVICE, but based on VirtualMap - protected String fruitVirtualLabel; protected StateMetadata fruitVirtualMetadata; - protected VirtualMap, OnDiskValue> fruitVirtualMap; - - // The "ANIMAL" map is part of FIRST_SERVICE - protected String animalLabel; protected StateMetadata animalMetadata; - protected MerkleMap, InMemoryValue> animalMerkleMap; - - // The "SPACE" map is part of SECOND_SERVICE and uses the long-based keys - protected String spaceLabel; protected StateMetadata spaceMetadata; - protected MerkleMap, InMemoryValue> spaceMerkleMap; - - // The "STEAM" queue is part of FIRST_SERVICE - protected String steamLabel; protected StateMetadata steamMetadata; - protected QueueNode steamQueue; - - // The "COUNTRY" singleton is part of FIRST_SERVICE - protected String countryLabel; protected StateMetadata countryMetadata; - protected SingletonNode countrySingleton; /** Sets up the "Fruit" merkle map, label, and metadata. */ + @Override protected void setupFruitMerkleMap() { - fruitLabel = StateUtils.computeLabel(FIRST_SERVICE, FRUIT_STATE_KEY); - fruitMerkleMap = createMerkleMap(fruitLabel); + super.setupFruitMerkleMap(); fruitMetadata = new StateMetadata<>( FIRST_SERVICE, new TestSchema(1), @@ -146,19 +73,19 @@ protected void setupFruitMerkleMap() { } /** Sets up the "Fruit" virtual map, label, and metadata. */ + @Override protected void setupFruitVirtualMap() { - fruitVirtualLabel = StateUtils.computeLabel(FIRST_SERVICE, FRUIT_STATE_KEY); + super.setupFruitVirtualMap(); fruitVirtualMetadata = new StateMetadata<>( FIRST_SERVICE, new TestSchema(1), StateDefinition.onDisk(FRUIT_STATE_KEY, STRING_CODEC, STRING_CODEC, 100)); - fruitVirtualMap = createVirtualMap(fruitVirtualLabel, fruitVirtualMetadata); } /** Sets up the "Animal" merkle map, label, and metadata. */ + @Override protected void setupAnimalMerkleMap() { - animalLabel = StateUtils.computeLabel(FIRST_SERVICE, ANIMAL_STATE_KEY); - animalMerkleMap = createMerkleMap(animalLabel); + super.setupAnimalMerkleMap(); animalMetadata = new StateMetadata<>( FIRST_SERVICE, new TestSchema(1), @@ -166,74 +93,39 @@ protected void setupAnimalMerkleMap() { } /** Sets up the "Space" merkle map, label, and metadata. */ + @Override protected void setupSpaceMerkleMap() { - spaceLabel = StateUtils.computeLabel(SECOND_SERVICE, SPACE_STATE_KEY); - spaceMerkleMap = createMerkleMap(spaceLabel); + super.setupSpaceMerkleMap(); spaceMetadata = new StateMetadata<>( SECOND_SERVICE, new TestSchema(1), StateDefinition.inMemory(SPACE_STATE_KEY, LONG_CODEC, STRING_CODEC)); } + @Override protected void setupSingletonCountry() { - countryLabel = StateUtils.computeLabel(FIRST_SERVICE, COUNTRY_STATE_KEY); + super.setupSingletonCountry(); countryMetadata = new StateMetadata<>( FIRST_SERVICE, new TestSchema(1), StateDefinition.singleton(COUNTRY_STATE_KEY, STRING_CODEC)); - countrySingleton = new SingletonNode<>(countryMetadata, AUSTRALIA); } + @Override protected void setupSteamQueue() { - steamLabel = StateUtils.computeLabel(FIRST_SERVICE, STEAM_STATE_KEY); + super.setupSteamQueue(); steamMetadata = new StateMetadata<>( FIRST_SERVICE, new TestSchema(1), StateDefinition.queue(STEAM_STATE_KEY, STRING_CODEC)); - steamQueue = new QueueNode<>(steamMetadata); - } - - /** Sets up the {@link #registry}, ready to be used for serialization tests */ - protected void setupConstructableRegistry() { - // Unfortunately, we need to configure the ConstructableRegistry for serialization tests and - // even for basic usage of the MerkleMap (it uses it internally to make copies of internal - // nodes). - try { - registry = ConstructableRegistry.getInstance(); - - // It may have been configured during some other test, so we reset it - registry.reset(); - registry.registerConstructables("com.swirlds.merklemap"); - registry.registerConstructables("com.swirlds.merkledb"); - registry.registerConstructables("com.swirlds.fcqueue"); - registry.registerConstructables("com.swirlds.virtualmap"); - registry.registerConstructables("com.swirlds.common.merkle"); - registry.registerConstructables("com.swirlds.common"); - registry.registerConstructables("com.swirlds.merkle"); - registry.registerConstructables("com.swirlds.merkle.tree"); - } catch (ConstructableRegistryException ex) { - throw new AssertionError(ex); - } - } - - /** Creates a new arbitrary merkle map with the given label. */ - protected , V> MerkleMap, InMemoryValue> createMerkleMap( - String label) { - final var map = new MerkleMap, InMemoryValue>(); - map.setLabel(label); - return map; } /** Creates a new arbitrary virtual map with the given label, storageDir, and metadata */ @SuppressWarnings("unchecked") protected VirtualMap, OnDiskValue> createVirtualMap( String label, StateMetadata md) { - final var merkleDbTableConfig = new MerkleDbTableConfig<>( - (short) 1, - DigestType.SHA_384, - (short) 1, - new OnDiskKeySerializer<>(md), - (short) 1, - new OnDiskValueSerializer<>(md)); - merkleDbTableConfig.hashesRamToDiskThreshold(0); - merkleDbTableConfig.maxNumberOfKeys(100); - merkleDbTableConfig.preferDiskIndices(true); - final var builder = new MerkleDbDataSourceBuilder<>(virtualDbPath, merkleDbTableConfig); - return new VirtualMap<>(label, builder); + return createVirtualMap( + label, + md.onDiskKeySerializerClassId(), + md.onDiskKeyClassId(), + md.stateDefinition().keyCodec(), + md.onDiskValueSerializerClassId(), + md.onDiskValueClassId(), + md.stateDefinition().valueCodec()); } /** @@ -254,15 +146,6 @@ protected MerkleNode getNodeForLabel(MerkleHederaState hederaMerkle, String labe return null; } - /** A convenience method for creating {@link SemanticVersion}. */ - protected SemanticVersion version(int major, int minor, int patch) { - return SemanticVersion.newBuilder() - .major(major) - .minor(minor) - .patch(patch) - .build(); - } - /** A convenience method for adding a k/v pair to a merkle map */ protected void add( MerkleMap, InMemoryValue> map, @@ -270,8 +153,7 @@ protected void add( String key, String value) { final var def = md.stateDefinition(); - final var k = new InMemoryKey<>(key); - map.put(k, new InMemoryValue<>(md, k, value)); + super.add(map, md.inMemoryValueClassId(), def.keyCodec(), def.valueCodec(), key, value); } /** A convenience method for adding a k/v pair to a virtual map */ @@ -280,28 +162,14 @@ protected void add( StateMetadata md, String key, String value) { - final var k = new OnDiskKey<>(md, key); - map.put(k, new OnDiskValue<>(md, value)); - } - - /** A convenience method used to serialize a merkle tree */ - protected byte[] writeTree(@NonNull final MerkleNode tree, @NonNull final Path tempDir) throws IOException { - final var byteOutputStream = new ByteArrayOutputStream(); - try (final var out = new MerkleDataOutputStream(byteOutputStream)) { - out.writeMerkleTree(tempDir, tree); - } - return byteOutputStream.toByteArray(); - } - - /** A convenience method used to deserialize a merkle tree */ - protected T parseTree(@NonNull final byte[] state, @NonNull final Path tempDir) - throws IOException { - // Restore to a fresh MerkleDb instance - MerkleDb.resetDefaultInstancePath(); - final var byteInputStream = new ByteArrayInputStream(state); - try (final var in = new MerkleDataInputStream(byteInputStream)) { - return in.readMerkleTree(tempDir, 100); - } + super.add( + map, + md.onDiskKeyClassId(), + md.stateDefinition().keyCodec(), + md.onDiskValueClassId(), + md.stateDefinition().valueCodec(), + key, + value); } @AfterEach diff --git a/hedera-node/hedera-app/src/test/java/com/hedera/node/app/state/merkle/SerializationTest.java b/hedera-node/hedera-app/src/test/java/com/hedera/node/app/state/merkle/SerializationTest.java index a6d2764610bc..f284f1fa4571 100644 --- a/hedera-node/hedera-app/src/test/java/com/hedera/node/app/state/merkle/SerializationTest.java +++ b/hedera-node/hedera-app/src/test/java/com/hedera/node/app/state/merkle/SerializationTest.java @@ -17,21 +17,14 @@ package com.hedera.node.app.state.merkle; import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.Mockito.lenient; import static org.mockito.Mockito.mock; import com.hedera.node.app.ids.WritableEntityIdStore; import com.hedera.node.app.spi.fixtures.state.TestSchema; import com.hedera.node.app.spi.info.NetworkInfo; import com.hedera.node.app.spi.state.MigrationContext; -import com.hedera.node.app.spi.state.ReadableKVState; -import com.hedera.node.app.spi.state.ReadableQueueState; -import com.hedera.node.app.spi.state.ReadableSingletonState; import com.hedera.node.app.spi.state.Schema; import com.hedera.node.app.spi.state.StateDefinition; -import com.hedera.node.app.spi.state.WritableKVState; -import com.hedera.node.app.spi.state.WritableQueueState; -import com.hedera.node.app.spi.state.WritableSingletonState; import com.hedera.node.app.spi.workflows.record.GenesisRecordsBuilder; import com.hedera.node.config.data.HederaConfig; import com.swirlds.common.constructable.ClassConstructorPair; @@ -39,15 +32,27 @@ import com.swirlds.common.constructable.RuntimeConstructable; import com.swirlds.common.io.utility.LegacyTemporaryFileBuilder; import com.swirlds.config.api.Configuration; +import com.swirlds.config.extensions.sources.SimpleConfigSource; +import com.swirlds.config.extensions.test.fixtures.TestConfigBuilder; import com.swirlds.metrics.api.Metrics; +import com.swirlds.platform.state.merkle.disk.OnDiskReadableKVState; +import com.swirlds.platform.state.merkle.disk.OnDiskWritableKVState; +import com.swirlds.state.spi.*; +import com.swirlds.virtualmap.VirtualMap; +import com.swirlds.virtualmap.config.VirtualMapConfig; +import com.swirlds.virtualmap.config.VirtualMapConfig_; +import com.swirlds.virtualmap.internal.merkle.VirtualRootNode; import edu.umd.cs.findbugs.annotations.NonNull; import java.io.IOException; +import java.lang.reflect.Field; import java.nio.file.Path; import java.util.Set; import java.util.function.Supplier; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; @@ -65,10 +70,14 @@ void setUp() throws IOException { setupConstructableRegistry(); this.dir = LegacyTemporaryFileBuilder.buildTemporaryDirectory(); - this.config = mock(Configuration.class); + this.config = new TestConfigBuilder() + .withSource(new SimpleConfigSource() + .withValue(VirtualMapConfig_.FLUSH_INTERVAL, 1 + "") + .withValue(VirtualMapConfig_.COPY_FLUSH_THRESHOLD, 1 + "")) + .withConfigDataType(VirtualMapConfig.class) + .withConfigDataType(HederaConfig.class) + .getOrCreateConfig(); this.networkInfo = mock(NetworkInfo.class); - final var hederaConfig = mock(HederaConfig.class); - lenient().when(config.getConfigData(HederaConfig.class)).thenReturn(hederaConfig); } Schema createV1Schema() { @@ -96,7 +105,8 @@ public void migrate(@NonNull final MigrationContext ctx) { fruit.put(F_KEY, FIG); fruit.put(G_KEY, GRAPE); - final WritableKVState animals = newStates.get(ANIMAL_STATE_KEY); + final OnDiskWritableKVState animals = + (OnDiskWritableKVState) (OnDiskWritableKVState) newStates.get(ANIMAL_STATE_KEY); animals.put(A_KEY, AARDVARK); animals.put(B_KEY, BEAR); animals.put(C_KEY, CUTTLEFISH); @@ -104,6 +114,7 @@ public void migrate(@NonNull final MigrationContext ctx) { animals.put(E_KEY, EMU); animals.put(F_KEY, FOX); animals.put(G_KEY, GOOSE); + animals.commit(); final WritableSingletonState country = newStates.getSingleton(COUNTRY_STATE_KEY); country.put(CHAD); @@ -120,26 +131,98 @@ public void migrate(@NonNull final MigrationContext ctx) { }; } + private void forceFlush(ReadableKVState state) { + if (state instanceof OnDiskReadableKVState) { + try { + Field vmField = OnDiskReadableKVState.class.getDeclaredField("virtualMap"); + vmField.setAccessible(true); + VirtualMap vm = (VirtualMap) vmField.get(state); + + final VirtualRootNode root = vm.getRight(); + if (!vm.isEmpty()) { + root.enableFlush(); + vm.release(); + root.waitUntilFlushed(); + } + } catch (IllegalAccessException | NoSuchFieldException | InterruptedException e) { + throw new RuntimeException(e); + } + } + } + /** * In this test scenario, we have a genesis setup where we create FRUIT and ANIMALS and COUNTRY, * save them to disk, and then load them back in, and verify everything was loaded correctly. + *
    + * This tests has two modes: one where we force flush the VMs to disk and one where we don't. + * When it forces disk flush, it makes sure that the data gets to the datasource from cache and persisted in the table. + * When the flush is not forced, the data remains in cache which has its own serialization mechanism */ - @Test - void simpleReadAndWrite() throws IOException, ConstructableRegistryException { - // Given a merkle tree with some fruit and animals and country - final var v1 = version(1, 0, 0); - final var originalTree = new MerkleHederaState(lifecycles); - final var originalRegistry = - new MerkleSchemaRegistry(registry, FIRST_SERVICE, mock(GenesisRecordsBuilder.class)); + @ParameterizedTest + @ValueSource(booleans = {true, false}) + void simpleReadAndWrite(boolean forceFlush) throws IOException, ConstructableRegistryException { final var schemaV1 = createV1Schema(); - originalRegistry.register(schemaV1); - originalRegistry.migrate( - originalTree, null, v1, config, networkInfo, mock(Metrics.class), mock(WritableEntityIdStore.class)); + final var originalTree = createMerkleHederaState(schemaV1); // When we serialize it to bytes and deserialize it back into a tree - originalTree.copy(); // make a fast copy because we can only write to disk an immutable copy - CRYPTO.digestTreeSync(originalTree); - final var serializedBytes = writeTree(originalTree, dir); + MerkleHederaState copy = originalTree.copy(); // make a copy to make VM flushable + final byte[] serializedBytes; + if (forceFlush) { + // Force flush the VMs to disk to test serialization and deserialization + forceFlush(originalTree.getReadableStates(FIRST_SERVICE).get(ANIMAL_STATE_KEY)); + copy.copy(); // make a fast copy because we can only write to disk an immutable copy + CRYPTO.digestTreeSync(copy); + serializedBytes = writeTree(copy, dir); + } else { + CRYPTO.digestTreeSync(copy); + serializedBytes = writeTree(originalTree, dir); + } + + final MerkleHederaState loadedTree = loadeMerkleTree(schemaV1, serializedBytes); + + assertTree(loadedTree); + } + + /** + * This test scenario is trickier, and it's designed to reproduce #13335: OnDiskKeySerializer uses wrong classId for OnDiskKey. + * This issue can be reproduced only if at first it gets flushed to disk, then it gets loaded back in, and this time it remains in cache. + * After it gets saved to disk again, and then loaded back in, it results in ClassCastException due to incorrect classId. + */ + @Test + void dualReadAndWrite() throws IOException, ConstructableRegistryException { + final var schemaV1 = createV1Schema(); + final var originalTree = createMerkleHederaState(schemaV1); + + MerkleHederaState copy = originalTree.copy(); // make a copy to make VM flushable + ; + + forceFlush(originalTree.getReadableStates(FIRST_SERVICE).get(ANIMAL_STATE_KEY)); + copy.copy(); // make a fast copy because we can only write to disk an immutable copy + CRYPTO.digestTreeSync(copy); + final byte[] serializedBytes = writeTree(copy, dir); + + MerkleHederaState loadedTree = loadeMerkleTree(schemaV1, serializedBytes); + ((OnDiskReadableKVState) originalTree.getReadableStates(FIRST_SERVICE).get(ANIMAL_STATE_KEY)).reset(); + populateVmCache(loadedTree); + + loadedTree.copy(); // make a copy to store it to disk + + CRYPTO.digestTreeSync(loadedTree); + // refreshing the dir + dir = LegacyTemporaryFileBuilder.buildTemporaryDirectory(); + final byte[] serializedBytesWithCache = writeTree(loadedTree, dir); + + // let's load it again and see if it works + MerkleHederaState loadedTreeWithCache = loadeMerkleTree(schemaV1, serializedBytesWithCache); + ((OnDiskReadableKVState) + loadedTreeWithCache.getReadableStates(FIRST_SERVICE).get(ANIMAL_STATE_KEY)) + .reset(); + + assertTree(loadedTreeWithCache); + } + + private MerkleHederaState loadeMerkleTree(Schema schemaV1, byte[] serializedBytes) + throws ConstructableRegistryException, IOException { final var newRegistry = new MerkleSchemaRegistry(registry, FIRST_SERVICE, mock(GenesisRecordsBuilder.class)); newRegistry.register(schemaV1); @@ -160,7 +243,33 @@ void simpleReadAndWrite() throws IOException, ConstructableRegistryException { mock(WritableEntityIdStore.class)); loadedTree.migrate(1); - // Then, we should be able to see all our original states again + return loadedTree; + } + + private MerkleHederaState createMerkleHederaState(Schema schemaV1) { + final var v1 = version(1, 0, 0); + final var originalTree = new MerkleHederaState(lifecycles); + final var originalRegistry = + new MerkleSchemaRegistry(registry, FIRST_SERVICE, mock(GenesisRecordsBuilder.class)); + originalRegistry.register(schemaV1); + originalRegistry.migrate( + originalTree, null, v1, config, networkInfo, mock(Metrics.class), mock(WritableEntityIdStore.class)); + return originalTree; + } + + private static void populateVmCache(MerkleHederaState loadedTree) { + final var states = loadedTree.getWritableStates(FIRST_SERVICE); + final WritableKVState animalState = states.get(ANIMAL_STATE_KEY); + assertThat(animalState.getForModify(A_KEY)).isEqualTo(AARDVARK); + assertThat(animalState.getForModify(B_KEY)).isEqualTo(BEAR); + assertThat(animalState.getForModify(C_KEY)).isEqualTo(CUTTLEFISH); + assertThat(animalState.getForModify(D_KEY)).isEqualTo(DOG); + assertThat(animalState.getForModify(E_KEY)).isEqualTo(EMU); + assertThat(animalState.getForModify(F_KEY)).isEqualTo(FOX); + assertThat(animalState.getForModify(G_KEY)).isEqualTo(GOOSE); + } + + private static void assertTree(MerkleHederaState loadedTree) { final var states = loadedTree.getReadableStates(FIRST_SERVICE); final ReadableKVState fruitState = states.get(FRUIT_STATE_KEY); assertThat(fruitState.get(A_KEY)).isEqualTo(APPLE); diff --git a/hedera-node/hedera-app/src/test/java/com/hedera/node/app/state/merkle/StateMetadataTest.java b/hedera-node/hedera-app/src/test/java/com/hedera/node/app/state/merkle/StateMetadataTest.java index e7ca1dd18e43..aaae57bf53a7 100644 --- a/hedera-node/hedera-app/src/test/java/com/hedera/node/app/state/merkle/StateMetadataTest.java +++ b/hedera-node/hedera-app/src/test/java/com/hedera/node/app/state/merkle/StateMetadataTest.java @@ -22,12 +22,11 @@ import com.hedera.node.app.spi.fixtures.state.TestSchema; import com.hedera.node.app.spi.state.Schema; import com.hedera.node.app.spi.state.StateDefinition; -import java.util.stream.Stream; +import com.swirlds.platform.state.merkle.StateUtils; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.MethodSource; class StateMetadataTest extends MerkleTestBase { @@ -35,14 +34,6 @@ class StateMetadataTest extends MerkleTestBase { private Schema schema; private StateDefinition def; - public static Stream illegalServiceNames() { - return StateUtilsTest.illegalIdentifiers(); - } - - public static Stream legalServiceNames() { - return StateUtilsTest.legalIdentifiers(); - } - @BeforeEach void setUp() { setupSpaceMerkleMap(); @@ -93,7 +84,8 @@ void validStateKey(final String serviceName) { @DisplayName("inMemoryValueClassId is as expected") void inMemoryValueClassId() { final var md = new StateMetadata<>(FIRST_SERVICE, schema, def); - final var expected = StateUtils.computeClassId(md, "InMemoryValue"); + final var expected = computeClassId(md, "InMemoryValue"); + assertThat(expected).isEqualTo(md.inMemoryValueClassId()); } @@ -101,7 +93,7 @@ void inMemoryValueClassId() { @DisplayName("onDiskKeyClassId is as expected") void onDiskKeyClassId() { final var md = new StateMetadata<>(FIRST_SERVICE, schema, def); - final var expected = StateUtils.computeClassId(md, "OnDiskKey"); + final var expected = computeClassId(md, "OnDiskKey"); assertThat(expected).isEqualTo(md.onDiskKeyClassId()); } @@ -109,7 +101,7 @@ void onDiskKeyClassId() { @DisplayName("onDiskValueClassId is as expected") void onDiskValueClassId() { final var md = new StateMetadata<>(FIRST_SERVICE, schema, def); - final var expected = StateUtils.computeClassId(md, "OnDiskValue"); + final var expected = computeClassId(md, "OnDiskValue"); assertThat(expected).isEqualTo(md.onDiskValueClassId()); } @@ -117,7 +109,7 @@ void onDiskValueClassId() { @DisplayName("onDiskKeySerializerClassId is as expected") void onDiskKeySerializerClassId() { final var md = new StateMetadata<>(FIRST_SERVICE, schema, def); - final var expected = StateUtils.computeClassId(md, "OnDiskKeySerializer"); + final var expected = computeClassId(md, "OnDiskKeySerializer"); assertThat(expected).isEqualTo(md.onDiskKeySerializerClassId()); } @@ -125,7 +117,12 @@ void onDiskKeySerializerClassId() { @DisplayName("onDiskValueSerializerClassId is as expected") void onDiskValueSerializerClassId() { final var md = new StateMetadata<>(FIRST_SERVICE, schema, def); - final var expected = StateUtils.computeClassId(md, "OnDiskValueSerializer"); + final var expected = computeClassId(md, "OnDiskValueSerializer"); assertThat(expected).isEqualTo(md.onDiskValueSerializerClassId()); } + + private long computeClassId(StateMetadata md, String suffix) { + return StateUtils.computeClassId( + md.serviceName(), md.stateDefinition().stateKey(), md.schema().getVersion(), suffix); + } } diff --git a/hedera-node/hedera-app/src/test/java/com/hedera/node/app/state/merkle/disk/OnDiskTest.java b/hedera-node/hedera-app/src/test/java/com/hedera/node/app/state/merkle/disk/OnDiskTest.java index 481675630214..bc0a62d6fb1c 100644 --- a/hedera-node/hedera-app/src/test/java/com/hedera/node/app/state/merkle/disk/OnDiskTest.java +++ b/hedera-node/hedera-app/src/test/java/com/hedera/node/app/state/merkle/disk/OnDiskTest.java @@ -27,14 +27,19 @@ import com.hedera.node.app.spi.workflows.record.GenesisRecordsBuilder; import com.hedera.node.app.state.merkle.MerkleSchemaRegistry; import com.hedera.node.app.state.merkle.MerkleTestBase; -import com.hedera.node.app.state.merkle.StateMetadata; -import com.hedera.node.app.state.merkle.StateUtils; import com.hedera.node.config.data.HederaConfig; import com.swirlds.common.crypto.DigestType; import com.swirlds.common.io.utility.LegacyTemporaryFileBuilder; import com.swirlds.config.api.Configuration; import com.swirlds.merkledb.MerkleDbDataSourceBuilder; import com.swirlds.merkledb.MerkleDbTableConfig; +import com.swirlds.platform.state.merkle.StateUtils; +import com.swirlds.platform.state.merkle.disk.OnDiskKey; +import com.swirlds.platform.state.merkle.disk.OnDiskKeySerializer; +import com.swirlds.platform.state.merkle.disk.OnDiskReadableKVState; +import com.swirlds.platform.state.merkle.disk.OnDiskValue; +import com.swirlds.platform.state.merkle.disk.OnDiskValueSerializer; +import com.swirlds.platform.state.merkle.disk.OnDiskWritableKVState; import com.swirlds.virtualmap.VirtualMap; import com.swirlds.virtualmap.internal.merkle.VirtualRootNode; import edu.umd.cs.findbugs.annotations.NonNull; @@ -56,9 +61,7 @@ class OnDiskTest extends MerkleTestBase { private Schema schema; private StateDefinition def; - private StateMetadata md; private VirtualMap, OnDiskValue> virtualMap; - private Configuration config; @BeforeEach void setUp() throws IOException { @@ -76,15 +79,19 @@ public Set statesToCreate() { } }; - md = new StateMetadata<>(SERVICE_NAME, schema, def); - final var tableConfig = new MerkleDbTableConfig<>( (short) 1, DigestType.SHA_384, (short) 1, - new OnDiskKeySerializer<>(md), + new OnDiskKeySerializer<>( + onDiskKeySerializerClassId(SERVICE_NAME, ACCOUNT_STATE_KEY), + onDiskKeyClassId(SERVICE_NAME, ACCOUNT_STATE_KEY), + AccountID.PROTOBUF), (short) 1, - new OnDiskValueSerializer<>(md)); + new OnDiskValueSerializer<>( + onDiskValueSerializerClassId(SERVICE_NAME, ACCOUNT_STATE_KEY), + onDiskValueClassId(SERVICE_NAME, ACCOUNT_STATE_KEY), + Account.PROTOBUF)); // Force all hashes to disk, to make sure we're going through all the // serialization paths we can tableConfig.hashesRamToDiskThreshold(0); @@ -94,7 +101,7 @@ public Set statesToCreate() { final var builder = new MerkleDbDataSourceBuilder<>(storageDir, tableConfig); virtualMap = new VirtualMap<>(StateUtils.computeLabel(SERVICE_NAME, ACCOUNT_STATE_KEY), builder); - this.config = mock(Configuration.class); + Configuration config = mock(Configuration.class); final var hederaConfig = mock(HederaConfig.class); lenient().when(config.getConfigData(HederaConfig.class)).thenReturn(hederaConfig); } @@ -124,7 +131,13 @@ VirtualMap, OnDiskValue> copyHashAndFlush(VirtualMap(md, virtualMap); + final var ws = new OnDiskWritableKVState<>( + ACCOUNT_STATE_KEY, + onDiskKeyClassId(SERVICE_NAME, ACCOUNT_STATE_KEY), + AccountID.PROTOBUF, + onDiskValueClassId(SERVICE_NAME, ACCOUNT_STATE_KEY), + Account.PROTOBUF, + virtualMap); for (int i = 0; i < 10; i++) { final var id = AccountID.newBuilder().accountNum(i).build(); final var acct = Account.newBuilder() @@ -154,7 +167,8 @@ void populateTheMapAndFlushToDiskAndReadBack() throws IOException { // read it back now as our map and validate the data come back fine virtualMap = parseTree(serializedBytes, snapshotDir); - final var rs = new OnDiskReadableKVState<>(md, virtualMap); + final var rs = new OnDiskReadableKVState<>( + ACCOUNT_STATE_KEY, onDiskKeyClassId(SERVICE_NAME, ACCOUNT_STATE_KEY), AccountID.PROTOBUF, virtualMap); for (int i = 0; i < 10; i++) { final var id = AccountID.newBuilder().accountNum(i).build(); final var acct = rs.get(id); @@ -167,7 +181,13 @@ void populateTheMapAndFlushToDiskAndReadBack() throws IOException { @Test void populateFlushToDisk() { - final var ws = new OnDiskWritableKVState<>(md, virtualMap); + final var ws = new OnDiskWritableKVState<>( + ACCOUNT_STATE_KEY, + onDiskKeyClassId(SERVICE_NAME, ACCOUNT_STATE_KEY), + AccountID.PROTOBUF, + onDiskValueClassId(SERVICE_NAME, ACCOUNT_STATE_KEY), + Account.PROTOBUF, + virtualMap); for (int i = 1; i < 10; i++) { final var id = AccountID.newBuilder().accountNum(i).build(); final var acct = Account.newBuilder() @@ -180,7 +200,8 @@ void populateFlushToDisk() { ws.commit(); virtualMap = copyHashAndFlush(virtualMap); - final var rs = new OnDiskReadableKVState<>(md, virtualMap); + final var rs = new OnDiskReadableKVState<>( + ACCOUNT_STATE_KEY, onDiskKeyClassId(SERVICE_NAME, ACCOUNT_STATE_KEY), AccountID.PROTOBUF, virtualMap); for (int i = 1; i < 10; i++) { final var id = AccountID.newBuilder().accountNum(i).build(); final var acct = rs.get(id); @@ -193,7 +214,7 @@ void populateFlushToDisk() { @Test void toStringWorks() { - final var key = new OnDiskKey<>(md); + final var key = new OnDiskKey<>(onDiskKeyClassId(SERVICE_NAME, ACCOUNT_STATE_KEY), AccountID.PROTOBUF); final var string = key.toString(); assertThat(string).isEqualTo("OnDiskKey{key=null}"); } diff --git a/hedera-node/hedera-app/src/test/java/com/hedera/node/app/state/recordcache/RecordCacheImplTest.java b/hedera-node/hedera-app/src/test/java/com/hedera/node/app/state/recordcache/RecordCacheImplTest.java index cc8902b8f782..2375575f5e28 100644 --- a/hedera-node/hedera-app/src/test/java/com/hedera/node/app/state/recordcache/RecordCacheImplTest.java +++ b/hedera-node/hedera-app/src/test/java/com/hedera/node/app/state/recordcache/RecordCacheImplTest.java @@ -40,11 +40,9 @@ import com.hedera.node.app.fixtures.AppTestBase; import com.hedera.node.app.fixtures.state.FakeHederaState; import com.hedera.node.app.fixtures.state.FakeSchemaRegistry; -import com.hedera.node.app.spi.fixtures.state.ListWritableQueueState; import com.hedera.node.app.spi.fixtures.state.TestSchema; import com.hedera.node.app.spi.info.NetworkInfo; import com.hedera.node.app.spi.records.RecordCache; -import com.hedera.node.app.spi.state.WritableQueueState; import com.hedera.node.app.state.DeduplicationCache; import com.hedera.node.app.state.SingleTransactionRecord; import com.hedera.node.app.state.SingleTransactionRecord.TransactionOutputs; @@ -53,6 +51,8 @@ import com.hedera.node.config.VersionedConfiguration; import com.hedera.node.config.data.HederaConfig; import com.hedera.node.config.data.LedgerConfig; +import com.swirlds.platform.test.fixtures.state.ListWritableQueueState; +import com.swirlds.state.spi.WritableQueueState; import edu.umd.cs.findbugs.annotations.NonNull; import java.time.Instant; import java.time.temporal.ChronoUnit; diff --git a/hedera-node/hedera-app/src/test/java/com/hedera/node/app/throttle/SynchronizedThrottleAccumulatorTest.java b/hedera-node/hedera-app/src/test/java/com/hedera/node/app/throttle/SynchronizedThrottleAccumulatorTest.java index e057ad4c8538..948ae177a14b 100644 --- a/hedera-node/hedera-app/src/test/java/com/hedera/node/app/throttle/SynchronizedThrottleAccumulatorTest.java +++ b/hedera-node/hedera-app/src/test/java/com/hedera/node/app/throttle/SynchronizedThrottleAccumulatorTest.java @@ -25,8 +25,8 @@ import com.hedera.hapi.node.base.AccountID; import com.hedera.hapi.node.base.HederaFunctionality; import com.hedera.hapi.node.transaction.Query; -import com.hedera.node.app.state.HederaState; import com.hedera.node.app.workflows.TransactionInfo; +import com.swirlds.state.HederaState; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; diff --git a/hedera-node/hedera-app/src/test/java/com/hedera/node/app/throttle/ThrottleAccumulatorTest.java b/hedera-node/hedera-app/src/test/java/com/hedera/node/app/throttle/ThrottleAccumulatorTest.java index 572cc67a67ea..27183a60303a 100644 --- a/hedera-node/hedera-app/src/test/java/com/hedera/node/app/throttle/ThrottleAccumulatorTest.java +++ b/hedera-node/hedera-app/src/test/java/com/hedera/node/app/throttle/ThrottleAccumulatorTest.java @@ -71,9 +71,6 @@ import com.hedera.node.app.spi.fixtures.util.LogCaptureExtension; import com.hedera.node.app.spi.fixtures.util.LoggingSubject; import com.hedera.node.app.spi.fixtures.util.LoggingTarget; -import com.hedera.node.app.spi.state.ReadableKVState; -import com.hedera.node.app.spi.state.ReadableStates; -import com.hedera.node.app.state.HederaState; import com.hedera.node.app.workflows.TransactionInfo; import com.hedera.node.config.ConfigProvider; import com.hedera.node.config.VersionedConfiguration; @@ -85,6 +82,9 @@ import com.hedera.node.config.data.TokensConfig; import com.hedera.pbj.runtime.ParseException; import com.hedera.pbj.runtime.io.buffer.Bytes; +import com.swirlds.state.HederaState; +import com.swirlds.state.spi.ReadableKVState; +import com.swirlds.state.spi.ReadableStates; import java.io.IOException; import java.io.InputStream; import java.time.Instant; diff --git a/hedera-node/hedera-app/src/test/java/com/hedera/node/app/throttle/ThrottleServiceManagerTest.java b/hedera-node/hedera-app/src/test/java/com/hedera/node/app/throttle/ThrottleServiceManagerTest.java index 84e62e5b0045..ec85d9860212 100644 --- a/hedera-node/hedera-app/src/test/java/com/hedera/node/app/throttle/ThrottleServiceManagerTest.java +++ b/hedera-node/hedera-app/src/test/java/com/hedera/node/app/throttle/ThrottleServiceManagerTest.java @@ -38,12 +38,6 @@ import com.hedera.node.app.hapi.utils.throttles.DeterministicThrottle; import com.hedera.node.app.hapi.utils.throttles.GasLimitDeterministicThrottle; import com.hedera.node.app.service.file.FileService; -import com.hedera.node.app.spi.state.ReadableKVState; -import com.hedera.node.app.spi.state.ReadableSingletonState; -import com.hedera.node.app.spi.state.ReadableStates; -import com.hedera.node.app.spi.state.WritableSingletonState; -import com.hedera.node.app.spi.state.WritableStates; -import com.hedera.node.app.state.HederaState; import com.hedera.node.app.util.FileUtilities; import com.hedera.node.config.ConfigProvider; import com.hedera.node.config.VersionedConfigImpl; @@ -51,6 +45,12 @@ import com.hedera.node.config.testfixtures.HederaTestConfigBuilder; import com.hedera.pbj.runtime.io.buffer.Bytes; import com.swirlds.config.api.Configuration; +import com.swirlds.state.HederaState; +import com.swirlds.state.spi.ReadableKVState; +import com.swirlds.state.spi.ReadableSingletonState; +import com.swirlds.state.spi.ReadableStates; +import com.swirlds.state.spi.WritableSingletonState; +import com.swirlds.state.spi.WritableStates; import java.time.Instant; import java.util.List; import org.junit.jupiter.api.BeforeEach; diff --git a/hedera-node/hedera-app/src/test/java/com/hedera/node/app/throttle/impl/NetworkUtilizationManagerImplTest.java b/hedera-node/hedera-app/src/test/java/com/hedera/node/app/throttle/impl/NetworkUtilizationManagerImplTest.java index 37c5fa7edb1b..f04d93b394e3 100644 --- a/hedera-node/hedera-app/src/test/java/com/hedera/node/app/throttle/impl/NetworkUtilizationManagerImplTest.java +++ b/hedera-node/hedera-app/src/test/java/com/hedera/node/app/throttle/impl/NetworkUtilizationManagerImplTest.java @@ -31,15 +31,15 @@ import com.hedera.node.app.spi.fixtures.util.LogCaptureExtension; import com.hedera.node.app.spi.fixtures.util.LoggingSubject; import com.hedera.node.app.spi.fixtures.util.LoggingTarget; -import com.hedera.node.app.spi.state.ReadableSingletonState; -import com.hedera.node.app.spi.state.ReadableStates; -import com.hedera.node.app.spi.state.WritableSingletonState; -import com.hedera.node.app.spi.state.WritableStates; -import com.hedera.node.app.state.HederaState; import com.hedera.node.app.throttle.NetworkUtilizationManagerImpl; import com.hedera.node.app.throttle.ThrottleAccumulator; import com.hedera.node.app.workflows.TransactionInfo; import com.hedera.pbj.runtime.io.buffer.Bytes; +import com.swirlds.state.HederaState; +import com.swirlds.state.spi.ReadableSingletonState; +import com.swirlds.state.spi.ReadableStates; +import com.swirlds.state.spi.WritableSingletonState; +import com.swirlds.state.spi.WritableStates; import java.time.Instant; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; diff --git a/hedera-node/hedera-app/src/test/java/com/hedera/node/app/workflows/dispatcher/ReadableStoreFactoryTest.java b/hedera-node/hedera-app/src/test/java/com/hedera/node/app/workflows/dispatcher/ReadableStoreFactoryTest.java index b0d1bad9f013..7e4ec783638c 100644 --- a/hedera-node/hedera-app/src/test/java/com/hedera/node/app/workflows/dispatcher/ReadableStoreFactoryTest.java +++ b/hedera-node/hedera-app/src/test/java/com/hedera/node/app/workflows/dispatcher/ReadableStoreFactoryTest.java @@ -29,9 +29,9 @@ import com.hedera.node.app.service.token.ReadableStakingInfoStore; import com.hedera.node.app.service.token.ReadableTokenRelationStore; import com.hedera.node.app.service.token.ReadableTokenStore; -import com.hedera.node.app.spi.state.ReadableKVState; -import com.hedera.node.app.spi.state.ReadableStates; -import com.hedera.node.app.state.HederaState; +import com.swirlds.state.HederaState; +import com.swirlds.state.spi.ReadableKVState; +import com.swirlds.state.spi.ReadableStates; import org.junit.jupiter.api.extension.ExtendWith; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.ValueSource; diff --git a/hedera-node/hedera-app/src/test/java/com/hedera/node/app/workflows/dispatcher/ServiceApiFactoryTest.java b/hedera-node/hedera-app/src/test/java/com/hedera/node/app/workflows/dispatcher/ServiceApiFactoryTest.java index 1f448990a22e..32eff4550d18 100644 --- a/hedera-node/hedera-app/src/test/java/com/hedera/node/app/workflows/dispatcher/ServiceApiFactoryTest.java +++ b/hedera-node/hedera-app/src/test/java/com/hedera/node/app/workflows/dispatcher/ServiceApiFactoryTest.java @@ -24,12 +24,12 @@ import com.hedera.node.app.service.token.TokenService; import com.hedera.node.app.service.token.api.TokenServiceApi; -import com.hedera.node.app.spi.fixtures.state.MapWritableKVState; import com.hedera.node.app.spi.metrics.StoreMetricsService; -import com.hedera.node.app.spi.state.WritableStates; import com.hedera.node.app.workflows.handle.stack.SavepointStackImpl; import com.hedera.node.config.testfixtures.HederaTestConfigBuilder; import com.swirlds.config.api.Configuration; +import com.swirlds.platform.test.fixtures.state.MapWritableKVState; +import com.swirlds.state.spi.WritableStates; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; diff --git a/hedera-node/hedera-app/src/test/java/com/hedera/node/app/workflows/dispatcher/WritableStoreFactoryImplTest.java b/hedera-node/hedera-app/src/test/java/com/hedera/node/app/workflows/dispatcher/WritableStoreFactoryImplTest.java index 3a093d524077..62f6faf95df6 100644 --- a/hedera-node/hedera-app/src/test/java/com/hedera/node/app/workflows/dispatcher/WritableStoreFactoryImplTest.java +++ b/hedera-node/hedera-app/src/test/java/com/hedera/node/app/workflows/dispatcher/WritableStoreFactoryImplTest.java @@ -35,10 +35,10 @@ import com.hedera.node.app.service.token.impl.WritableTokenRelationStore; import com.hedera.node.app.service.token.impl.WritableTokenStore; import com.hedera.node.app.spi.metrics.StoreMetricsService; -import com.hedera.node.app.spi.state.WritableKVState; -import com.hedera.node.app.spi.state.WritableStates; import com.hedera.node.app.workflows.handle.stack.SavepointStackImpl; import com.hedera.node.config.testfixtures.HederaTestConfigBuilder; +import com.swirlds.state.spi.WritableKVState; +import com.swirlds.state.spi.WritableStates; import java.util.stream.Stream; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.extension.ExtendWith; diff --git a/hedera-node/hedera-app/src/test/java/com/hedera/node/app/workflows/handle/HandleContextImplTest.java b/hedera-node/hedera-app/src/test/java/com/hedera/node/app/workflows/handle/HandleContextImplTest.java index 669aa6238095..ca7d99499f62 100644 --- a/hedera-node/hedera-app/src/test/java/com/hedera/node/app/workflows/handle/HandleContextImplTest.java +++ b/hedera-node/hedera-app/src/test/java/com/hedera/node/app/workflows/handle/HandleContextImplTest.java @@ -71,17 +71,12 @@ import com.hedera.node.app.spi.fees.FeeContext; import com.hedera.node.app.spi.fees.Fees; import com.hedera.node.app.spi.fixtures.Scenarios; -import com.hedera.node.app.spi.fixtures.state.MapWritableKVState; import com.hedera.node.app.spi.fixtures.state.MapWritableStates; -import com.hedera.node.app.spi.fixtures.state.StateTestBase; import com.hedera.node.app.spi.info.NetworkInfo; import com.hedera.node.app.spi.info.SelfNodeInfo; import com.hedera.node.app.spi.metrics.StoreMetricsService; import com.hedera.node.app.spi.records.BlockRecordInfo; import com.hedera.node.app.spi.signatures.SignatureVerification; -import com.hedera.node.app.spi.state.ReadableStates; -import com.hedera.node.app.spi.state.WritableSingletonState; -import com.hedera.node.app.spi.state.WritableStates; import com.hedera.node.app.spi.workflows.ComputeDispatchFeesAsTopLevel; import com.hedera.node.app.spi.workflows.HandleContext; import com.hedera.node.app.spi.workflows.HandleContext.TransactionCategory; @@ -91,7 +86,6 @@ import com.hedera.node.app.spi.workflows.record.RecordListCheckPoint; import com.hedera.node.app.spi.workflows.record.SingleTransactionRecordBuilder; import com.hedera.node.app.state.HederaRecordCache; -import com.hedera.node.app.state.HederaState; import com.hedera.node.app.throttle.NetworkUtilizationManager; import com.hedera.node.app.throttle.SynchronizedThrottleAccumulator; import com.hedera.node.app.workflows.SolvencyPreCheck; @@ -104,6 +98,12 @@ import com.hedera.pbj.runtime.io.buffer.Bytes; import com.swirlds.config.api.Configuration; import com.swirlds.platform.state.PlatformState; +import com.swirlds.platform.test.fixtures.state.MapWritableKVState; +import com.swirlds.platform.test.fixtures.state.StateTestBase; +import com.swirlds.state.HederaState; +import com.swirlds.state.spi.ReadableStates; +import com.swirlds.state.spi.WritableSingletonState; +import com.swirlds.state.spi.WritableStates; import java.lang.reflect.InvocationTargetException; import java.time.Instant; import java.util.Arrays; diff --git a/hedera-node/hedera-app/src/test/java/com/hedera/node/app/workflows/handle/HandleWorkflowTest.java b/hedera-node/hedera-app/src/test/java/com/hedera/node/app/workflows/handle/HandleWorkflowTest.java index 3c2b87da3c93..ccb35c504488 100644 --- a/hedera-node/hedera-app/src/test/java/com/hedera/node/app/workflows/handle/HandleWorkflowTest.java +++ b/hedera-node/hedera-app/src/test/java/com/hedera/node/app/workflows/handle/HandleWorkflowTest.java @@ -1163,7 +1163,7 @@ void testPreHandleCausesUnknownFailure() { void testPreHandleCausesUnexpectedException() { final var transactionBytes = Transaction.PROTOBUF.toBytes( new TransactionScenarioBuilder().txInfo().transaction()); - when(platformTxn.getContents()).thenReturn(transactionBytes.toByteArray()); + when(platformTxn.getApplicationPayload()).thenReturn(transactionBytes); when(platformTxn.getMetadata()).thenReturn(null); when(preHandleWorkflow.preHandleTransaction(any(), any(), any(), eq(platformTxn), eq(null))) .thenThrow(NullPointerException.class); diff --git a/hedera-node/hedera-app/src/test/java/com/hedera/node/app/workflows/handle/PlatformStateUpdateFacilityTest.java b/hedera-node/hedera-app/src/test/java/com/hedera/node/app/workflows/handle/PlatformStateUpdateFacilityTest.java index 413ced777f3e..dc0776d7650f 100644 --- a/hedera-node/hedera-app/src/test/java/com/hedera/node/app/workflows/handle/PlatformStateUpdateFacilityTest.java +++ b/hedera-node/hedera-app/src/test/java/com/hedera/node/app/workflows/handle/PlatformStateUpdateFacilityTest.java @@ -32,9 +32,9 @@ import com.hedera.node.app.fixtures.state.FakeHederaState; import com.hedera.node.app.service.networkadmin.FreezeService; import com.hedera.node.app.spi.fixtures.TransactionFactory; -import com.hedera.node.app.spi.state.WritableSingletonStateBase; -import com.hedera.node.app.spi.state.WritableStates; import com.swirlds.platform.state.PlatformState; +import com.swirlds.platform.state.spi.WritableSingletonStateBase; +import com.swirlds.state.spi.WritableStates; import java.time.Instant; import java.util.Map; import java.util.concurrent.atomic.AtomicReference; diff --git a/hedera-node/hedera-app/src/test/java/com/hedera/node/app/workflows/handle/record/BlockRecordManagerTest.java b/hedera-node/hedera-app/src/test/java/com/hedera/node/app/workflows/handle/record/BlockRecordManagerTest.java index ad4ecd0e6c06..b4b3ba40cf75 100644 --- a/hedera-node/hedera-app/src/test/java/com/hedera/node/app/workflows/handle/record/BlockRecordManagerTest.java +++ b/hedera-node/hedera-app/src/test/java/com/hedera/node/app/workflows/handle/record/BlockRecordManagerTest.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.hedera.node.app.records; +package com.hedera.node.app.workflows.handle.record; import static com.hedera.node.app.records.BlockRecordService.BLOCK_INFO_STATE_KEY; import static com.hedera.node.app.records.BlockRecordService.EPOCH; @@ -36,6 +36,7 @@ import com.hedera.hapi.node.state.blockrecords.BlockInfo; import com.hedera.hapi.node.state.blockrecords.RunningHashes; import com.hedera.node.app.fixtures.AppTestBase; +import com.hedera.node.app.records.BlockRecordService; import com.hedera.node.app.records.impl.BlockRecordManagerImpl; import com.hedera.node.app.records.impl.BlockRecordStreamProducer; import com.hedera.node.app.records.impl.producers.BlockRecordFormat; @@ -44,14 +45,14 @@ import com.hedera.node.app.records.impl.producers.StreamFileProducerSingleThreaded; import com.hedera.node.app.records.impl.producers.formats.BlockRecordWriterFactoryImpl; import com.hedera.node.app.records.impl.producers.formats.v6.BlockRecordFormatV6; -import com.hedera.node.app.spi.fixtures.state.MapReadableStates; -import com.hedera.node.app.spi.state.ReadableSingletonStateBase; -import com.hedera.node.app.spi.state.ReadableStates; -import com.hedera.node.app.spi.state.WritableStates; -import com.hedera.node.app.state.HederaState; import com.hedera.node.config.data.BlockRecordStreamConfig; import com.hedera.pbj.runtime.io.buffer.Bytes; import com.swirlds.platform.state.PlatformState; +import com.swirlds.platform.state.spi.ReadableSingletonStateBase; +import com.swirlds.platform.test.fixtures.state.MapReadableStates; +import com.swirlds.state.HederaState; +import com.swirlds.state.spi.ReadableStates; +import com.swirlds.state.spi.WritableStates; import edu.umd.cs.findbugs.annotations.NonNull; import java.nio.file.FileSystem; import java.nio.file.Files; diff --git a/hedera-node/hedera-app/src/test/java/com/hedera/node/app/workflows/handle/record/BlockRecordServiceTest.java b/hedera-node/hedera-app/src/test/java/com/hedera/node/app/workflows/handle/record/BlockRecordServiceTest.java index 3873d82b8b13..7520d278d679 100644 --- a/hedera-node/hedera-app/src/test/java/com/hedera/node/app/workflows/handle/record/BlockRecordServiceTest.java +++ b/hedera-node/hedera-app/src/test/java/com/hedera/node/app/workflows/handle/record/BlockRecordServiceTest.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.hedera.node.app.records; +package com.hedera.node.app.workflows.handle.record; import static com.hedera.node.app.records.BlockRecordService.BLOCK_INFO_STATE_KEY; import static com.hedera.node.app.records.BlockRecordService.EPOCH; @@ -28,8 +28,14 @@ import com.hedera.hapi.node.state.blockrecords.BlockInfo; import com.hedera.hapi.node.state.blockrecords.RunningHashes; -import com.hedera.node.app.spi.state.*; +import com.hedera.node.app.records.BlockRecordService; +import com.hedera.node.app.spi.state.MigrationContext; +import com.hedera.node.app.spi.state.Schema; +import com.hedera.node.app.spi.state.SchemaRegistry; +import com.hedera.node.app.spi.state.StateDefinition; import com.hedera.pbj.runtime.io.buffer.Bytes; +import com.swirlds.state.spi.WritableSingletonState; +import com.swirlds.state.spi.WritableStates; import java.util.Set; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; diff --git a/hedera-node/hedera-app/src/test/java/com/hedera/node/app/workflows/handle/record/impl/producers/StreamFileProducerConcurrentTest.java b/hedera-node/hedera-app/src/test/java/com/hedera/node/app/workflows/handle/record/impl/producers/StreamFileProducerConcurrentTest.java index 16247e435f68..92680606bb64 100644 --- a/hedera-node/hedera-app/src/test/java/com/hedera/node/app/workflows/handle/record/impl/producers/StreamFileProducerConcurrentTest.java +++ b/hedera-node/hedera-app/src/test/java/com/hedera/node/app/workflows/handle/record/impl/producers/StreamFileProducerConcurrentTest.java @@ -14,9 +14,11 @@ * limitations under the License. */ -package com.hedera.node.app.records.impl.producers; +package com.hedera.node.app.workflows.handle.record.impl.producers; import com.hedera.node.app.records.impl.BlockRecordStreamProducer; +import com.hedera.node.app.records.impl.producers.BlockRecordWriterFactory; +import com.hedera.node.app.records.impl.producers.StreamFileProducerConcurrent; import com.hedera.node.app.records.impl.producers.formats.v6.BlockRecordFormatV6; import edu.umd.cs.findbugs.annotations.NonNull; import java.util.concurrent.ForkJoinPool; diff --git a/hedera-node/hedera-app/src/test/java/com/hedera/node/app/workflows/handle/record/impl/producers/StreamFileProducerSingleThreadedTest.java b/hedera-node/hedera-app/src/test/java/com/hedera/node/app/workflows/handle/record/impl/producers/StreamFileProducerSingleThreadedTest.java index c3167cbe4331..edf626283375 100644 --- a/hedera-node/hedera-app/src/test/java/com/hedera/node/app/workflows/handle/record/impl/producers/StreamFileProducerSingleThreadedTest.java +++ b/hedera-node/hedera-app/src/test/java/com/hedera/node/app/workflows/handle/record/impl/producers/StreamFileProducerSingleThreadedTest.java @@ -14,9 +14,11 @@ * limitations under the License. */ -package com.hedera.node.app.records.impl.producers; +package com.hedera.node.app.workflows.handle.record.impl.producers; import com.hedera.node.app.records.impl.BlockRecordStreamProducer; +import com.hedera.node.app.records.impl.producers.BlockRecordWriterFactory; +import com.hedera.node.app.records.impl.producers.StreamFileProducerSingleThreaded; import com.hedera.node.app.records.impl.producers.formats.v6.BlockRecordFormatV6; import edu.umd.cs.findbugs.annotations.NonNull; diff --git a/hedera-node/hedera-app/src/test/java/com/hedera/node/app/workflows/handle/record/impl/producers/StreamFileProducerTest.java b/hedera-node/hedera-app/src/test/java/com/hedera/node/app/workflows/handle/record/impl/producers/StreamFileProducerTest.java index 944f7007deb5..29964d9fbc42 100644 --- a/hedera-node/hedera-app/src/test/java/com/hedera/node/app/workflows/handle/record/impl/producers/StreamFileProducerTest.java +++ b/hedera-node/hedera-app/src/test/java/com/hedera/node/app/workflows/handle/record/impl/producers/StreamFileProducerTest.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.hedera.node.app.records.impl.producers; +package com.hedera.node.app.workflows.handle.record.impl.producers; import static com.hedera.node.app.records.RecordTestData.STARTING_RUNNING_HASH_OBJ; import static com.hedera.node.app.records.RecordTestData.TEST_BLOCKS; @@ -28,6 +28,9 @@ import com.hedera.hapi.streams.HashObject; import com.hedera.node.app.fixtures.AppTestBase; import com.hedera.node.app.records.impl.BlockRecordStreamProducer; +import com.hedera.node.app.records.impl.producers.BlockRecordWriter; +import com.hedera.node.app.records.impl.producers.BlockRecordWriterFactory; +import com.hedera.node.app.records.impl.producers.SerializedSingleTransactionRecord; import com.hedera.node.app.records.impl.producers.formats.v6.BlockRecordFormatV6; import com.hedera.node.app.state.SingleTransactionRecord; import com.hedera.pbj.runtime.io.buffer.Bytes; diff --git a/hedera-node/hedera-app/src/test/java/com/hedera/node/app/workflows/handle/stack/SavepointStackImplTest.java b/hedera-node/hedera-app/src/test/java/com/hedera/node/app/workflows/handle/stack/SavepointStackImplTest.java index 1c288004b648..193a7aaeee98 100644 --- a/hedera-node/hedera-app/src/test/java/com/hedera/node/app/workflows/handle/stack/SavepointStackImplTest.java +++ b/hedera-node/hedera-app/src/test/java/com/hedera/node/app/workflows/handle/stack/SavepointStackImplTest.java @@ -22,11 +22,11 @@ import static org.mockito.Mock.Strictness.LENIENT; import static org.mockito.Mockito.when; -import com.hedera.node.app.spi.fixtures.state.MapWritableKVState; import com.hedera.node.app.spi.fixtures.state.MapWritableStates; -import com.hedera.node.app.spi.fixtures.state.StateTestBase; -import com.hedera.node.app.spi.state.ReadableStates; -import com.hedera.node.app.state.HederaState; +import com.swirlds.platform.test.fixtures.state.MapWritableKVState; +import com.swirlds.platform.test.fixtures.state.StateTestBase; +import com.swirlds.state.HederaState; +import com.swirlds.state.spi.ReadableStates; import java.util.HashMap; import java.util.Map; import java.util.Objects; diff --git a/hedera-node/hedera-app/src/test/java/com/hedera/node/app/workflows/ingest/IngestWorkflowImplTest.java b/hedera-node/hedera-app/src/test/java/com/hedera/node/app/workflows/ingest/IngestWorkflowImplTest.java index 4aef0cdbc7af..ca11bff2a599 100644 --- a/hedera-node/hedera-app/src/test/java/com/hedera/node/app/workflows/ingest/IngestWorkflowImplTest.java +++ b/hedera-node/hedera-app/src/test/java/com/hedera/node/app/workflows/ingest/IngestWorkflowImplTest.java @@ -45,7 +45,6 @@ import com.hedera.hapi.node.transaction.TransactionResponse; import com.hedera.node.app.fixtures.AppTestBase; import com.hedera.node.app.spi.workflows.PreCheckException; -import com.hedera.node.app.state.HederaState; import com.hedera.node.app.workflows.TransactionChecker; import com.hedera.node.app.workflows.TransactionInfo; import com.hedera.node.config.ConfigProvider; @@ -57,6 +56,7 @@ import com.hedera.pbj.runtime.io.buffer.Bytes; import com.swirlds.common.utility.AutoCloseableWrapper; import com.swirlds.platform.system.status.PlatformStatus; +import com.swirlds.state.HederaState; import edu.umd.cs.findbugs.annotations.NonNull; import java.util.function.Supplier; import java.util.stream.Stream; diff --git a/hedera-node/hedera-app/src/test/java/com/hedera/node/app/workflows/prehandle/AdaptedMonoEventExpansionTest.java b/hedera-node/hedera-app/src/test/java/com/hedera/node/app/workflows/prehandle/AdaptedMonoEventExpansionTest.java index a8bff7f212da..3c8e148c7492 100644 --- a/hedera-node/hedera-app/src/test/java/com/hedera/node/app/workflows/prehandle/AdaptedMonoEventExpansionTest.java +++ b/hedera-node/hedera-app/src/test/java/com/hedera/node/app/workflows/prehandle/AdaptedMonoEventExpansionTest.java @@ -28,6 +28,7 @@ import com.hedera.node.app.service.mono.sigs.EventExpansion; import com.hedera.node.app.service.mono.sigs.order.SigReqsManager; import com.hedera.node.app.state.merkle.MerkleHederaState; +import com.hedera.pbj.runtime.io.buffer.Bytes; import com.swirlds.platform.system.events.Event; import com.swirlds.platform.system.transaction.ConsensusTransactionImpl; import com.swirlds.platform.system.transaction.Transaction; @@ -108,7 +109,7 @@ void routesWorkflowEnabledTxnsDifferently() { void worksAroundUnparseableTxn() { final var workflows = Set.of(ConsensusCreateTopic); given(staticProperties.workflowsEnabled()).willReturn(workflows); - given(nonsenseTxn.getContents()).willReturn("NONSENSE".getBytes()); + given(nonsenseTxn.getApplicationPayload()).willReturn(Bytes.wrap("NONSENSE")); willAnswer(invocation -> { final Consumer consumer = invocation.getArgument(0); consumer.accept(nonsenseTxn); diff --git a/hedera-node/hedera-app/src/test/java/com/hedera/node/app/workflows/query/QueryWorkflowImplTest.java b/hedera-node/hedera-app/src/test/java/com/hedera/node/app/workflows/query/QueryWorkflowImplTest.java index 61dae9b01563..fe0e477eb76d 100644 --- a/hedera-node/hedera-app/src/test/java/com/hedera/node/app/workflows/query/QueryWorkflowImplTest.java +++ b/hedera-node/hedera-app/src/test/java/com/hedera/node/app/workflows/query/QueryWorkflowImplTest.java @@ -69,7 +69,6 @@ import com.hedera.node.app.spi.workflows.InsufficientBalanceException; import com.hedera.node.app.spi.workflows.PreCheckException; import com.hedera.node.app.spi.workflows.QueryContext; -import com.hedera.node.app.state.HederaState; import com.hedera.node.app.throttle.SynchronizedThrottleAccumulator; import com.hedera.node.app.workflows.TransactionInfo; import com.hedera.node.app.workflows.ingest.IngestChecker; @@ -84,6 +83,7 @@ import com.hedera.pbj.runtime.io.buffer.BufferedData; import com.hedera.pbj.runtime.io.buffer.Bytes; import com.swirlds.common.utility.AutoCloseableWrapper; +import com.swirlds.state.HederaState; import io.grpc.Status; import io.grpc.StatusRuntimeException; import java.util.function.Function; diff --git a/hedera-node/hedera-app/src/testFixtures/java/com/hedera/node/app/fixtures/AppTestBase.java b/hedera-node/hedera-app/src/testFixtures/java/com/hedera/node/app/fixtures/AppTestBase.java index 84586369c7c8..370b2bad01dc 100644 --- a/hedera-node/hedera-app/src/testFixtures/java/com/hedera/node/app/fixtures/AppTestBase.java +++ b/hedera-node/hedera-app/src/testFixtures/java/com/hedera/node/app/fixtures/AppTestBase.java @@ -30,16 +30,11 @@ import com.hedera.node.app.service.token.TokenService; import com.hedera.node.app.spi.Service; import com.hedera.node.app.spi.fixtures.Scenarios; -import com.hedera.node.app.spi.fixtures.TestBase; import com.hedera.node.app.spi.fixtures.TransactionFactory; -import com.hedera.node.app.spi.fixtures.state.MapWritableKVState; import com.hedera.node.app.spi.fixtures.state.MapWritableStates; import com.hedera.node.app.spi.info.NetworkInfo; import com.hedera.node.app.spi.info.NodeInfo; import com.hedera.node.app.spi.info.SelfNodeInfo; -import com.hedera.node.app.spi.state.ReadableStates; -import com.hedera.node.app.spi.state.WritableStates; -import com.hedera.node.app.state.HederaState; import com.hedera.node.app.state.WorkingStateAccessor; import com.hedera.node.app.version.HederaSoftwareVersion; import com.hedera.node.config.ConfigProvider; @@ -64,6 +59,11 @@ import com.swirlds.platform.system.SwirldState; import com.swirlds.platform.system.address.Address; import com.swirlds.platform.system.address.AddressBook; +import com.swirlds.platform.test.fixtures.state.MapWritableKVState; +import com.swirlds.platform.test.fixtures.state.TestBase; +import com.swirlds.state.HederaState; +import com.swirlds.state.spi.ReadableStates; +import com.swirlds.state.spi.WritableStates; import edu.umd.cs.findbugs.annotations.NonNull; import edu.umd.cs.findbugs.annotations.Nullable; import java.util.LinkedHashSet; diff --git a/hedera-node/hedera-app/src/testFixtures/java/com/hedera/node/app/fixtures/state/FakeHederaState.java b/hedera-node/hedera-app/src/testFixtures/java/com/hedera/node/app/fixtures/state/FakeHederaState.java index 56227967cb01..bd8884b6b47a 100644 --- a/hedera-node/hedera-app/src/testFixtures/java/com/hedera/node/app/fixtures/state/FakeHederaState.java +++ b/hedera-node/hedera-app/src/testFixtures/java/com/hedera/node/app/fixtures/state/FakeHederaState.java @@ -16,18 +16,18 @@ package com.hedera.node.app.fixtures.state; -import com.hedera.node.app.spi.fixtures.state.ListReadableQueueState; -import com.hedera.node.app.spi.fixtures.state.ListWritableQueueState; -import com.hedera.node.app.spi.fixtures.state.MapReadableKVState; -import com.hedera.node.app.spi.fixtures.state.MapReadableStates; -import com.hedera.node.app.spi.fixtures.state.MapWritableKVState; import com.hedera.node.app.spi.fixtures.state.MapWritableStates; -import com.hedera.node.app.spi.state.ReadableKVState; -import com.hedera.node.app.spi.state.ReadableSingletonStateBase; -import com.hedera.node.app.spi.state.ReadableStates; -import com.hedera.node.app.spi.state.WritableSingletonStateBase; -import com.hedera.node.app.spi.state.WritableStates; -import com.hedera.node.app.state.HederaState; +import com.swirlds.platform.state.spi.ReadableSingletonStateBase; +import com.swirlds.platform.state.spi.WritableSingletonStateBase; +import com.swirlds.platform.test.fixtures.state.ListReadableQueueState; +import com.swirlds.platform.test.fixtures.state.ListWritableQueueState; +import com.swirlds.platform.test.fixtures.state.MapReadableKVState; +import com.swirlds.platform.test.fixtures.state.MapReadableStates; +import com.swirlds.platform.test.fixtures.state.MapWritableKVState; +import com.swirlds.state.HederaState; +import com.swirlds.state.spi.ReadableKVState; +import com.swirlds.state.spi.ReadableStates; +import com.swirlds.state.spi.WritableStates; import edu.umd.cs.findbugs.annotations.NonNull; import java.util.HashMap; import java.util.Map; diff --git a/hedera-node/hedera-app/src/testFixtures/java/com/hedera/node/app/fixtures/state/FakeSchemaRegistry.java b/hedera-node/hedera-app/src/testFixtures/java/com/hedera/node/app/fixtures/state/FakeSchemaRegistry.java index e536baa41865..e47e1b5901e5 100644 --- a/hedera-node/hedera-app/src/testFixtures/java/com/hedera/node/app/fixtures/state/FakeSchemaRegistry.java +++ b/hedera-node/hedera-app/src/testFixtures/java/com/hedera/node/app/fixtures/state/FakeSchemaRegistry.java @@ -19,21 +19,21 @@ import static com.hedera.node.app.spi.fixtures.state.TestSchema.CURRENT_VERSION; import com.hedera.hapi.node.base.SemanticVersion; -import com.hedera.node.app.spi.fixtures.state.ListWritableQueueState; -import com.hedera.node.app.spi.fixtures.state.MapWritableKVState; import com.hedera.node.app.spi.fixtures.state.MapWritableStates; import com.hedera.node.app.spi.fixtures.state.NoOpGenesisRecordsBuilder; import com.hedera.node.app.spi.info.NetworkInfo; import com.hedera.node.app.spi.state.EmptyReadableStates; import com.hedera.node.app.spi.state.MigrationContext; -import com.hedera.node.app.spi.state.ReadableStates; import com.hedera.node.app.spi.state.Schema; import com.hedera.node.app.spi.state.SchemaRegistry; -import com.hedera.node.app.spi.state.WritableSingletonStateBase; -import com.hedera.node.app.spi.state.WritableStates; import com.hedera.node.app.spi.workflows.record.GenesisRecordsBuilder; import com.swirlds.config.api.Configuration; import com.swirlds.config.api.ConfigurationBuilder; +import com.swirlds.platform.state.spi.WritableSingletonStateBase; +import com.swirlds.platform.test.fixtures.state.ListWritableQueueState; +import com.swirlds.platform.test.fixtures.state.MapWritableKVState; +import com.swirlds.state.spi.ReadableStates; +import com.swirlds.state.spi.WritableStates; import edu.umd.cs.findbugs.annotations.NonNull; import java.util.HashMap; import java.util.LinkedList; diff --git a/hedera-node/hedera-app/src/testFixtures/java/module-info.java b/hedera-node/hedera-app/src/testFixtures/java/module-info.java index 0f62d62b514a..bf88b90f573d 100644 --- a/hedera-node/hedera-app/src/testFixtures/java/module-info.java +++ b/hedera-node/hedera-app/src/testFixtures/java/module-info.java @@ -4,6 +4,8 @@ requires transitive com.hedera.node.app.spi.test.fixtures; requires transitive com.hedera.node.app.spi; requires transitive com.hedera.node.app; + requires transitive com.swirlds.platform.core.test.fixtures; + requires transitive com.swirlds.state.api; requires com.hedera.node.app.service.mono; requires com.hedera.node.app.service.token; requires com.hedera.node.config.test.fixtures; diff --git a/hedera-node/hedera-app/src/xtest/java/common/AbstractXTest.java b/hedera-node/hedera-app/src/xtest/java/common/AbstractXTest.java index 8b08b4cced74..6a99d2549a00 100644 --- a/hedera-node/hedera-app/src/xtest/java/common/AbstractXTest.java +++ b/hedera-node/hedera-app/src/xtest/java/common/AbstractXTest.java @@ -55,16 +55,17 @@ import com.hedera.node.app.service.token.TokenService; import com.hedera.node.app.service.token.impl.TokenServiceImpl; import com.hedera.node.app.spi.metrics.StoreMetricsService; -import com.hedera.node.app.spi.state.ReadableKVState; import com.hedera.node.app.spi.workflows.HandleException; +import com.hedera.node.app.spi.workflows.PreCheckException; import com.hedera.node.app.spi.workflows.QueryHandler; import com.hedera.node.app.spi.workflows.TransactionHandler; import com.hedera.node.app.spi.workflows.record.SingleTransactionRecordBuilder; -import com.hedera.node.app.state.HederaState; import com.hedera.node.app.workflows.handle.record.SingleTransactionRecordBuilderImpl; import com.hedera.node.app.workflows.handle.stack.SavepointStackImpl; import com.hedera.pbj.runtime.io.buffer.Bytes; import com.swirlds.metrics.api.Metrics; +import com.swirlds.state.HederaState; +import com.swirlds.state.spi.ReadableKVState; import contract.AbstractContractXTest; import edu.umd.cs.findbugs.annotations.NonNull; import java.io.IOException; @@ -127,6 +128,7 @@ void scenarioPasses() { doScenarioOperations(); + assertExpectedTokens(finalTokens()); assertExpectedNfts(finalNfts()); assertExpectedAliases(finalAliases()); assertExpectedAccounts(finalAccounts()); @@ -159,13 +161,18 @@ protected SingleTransactionRecordBuilderImpl handleAndCommitSingleTransaction( @NonNull final TransactionBody txn, @NonNull final ResponseCodeEnum expectedStatus) { final var context = component().txnContextFactory().apply(txn); + final var preContext = component().txnPreHandleContextFactory().apply(txn); var impliedStatus = OK; try { + handler.preHandle(preContext); handler.handle(context); ((SavepointStackImpl) context.savepointStack()).commitFullStack(); } catch (HandleException e) { impliedStatus = e.getStatus(); ((SavepointStackImpl) context.savepointStack()).rollbackFullStack(); + } catch (PreCheckException e) { + impliedStatus = e.responseCode(); + ((SavepointStackImpl) context.savepointStack()).rollbackFullStack(); } assertEquals(expectedStatus, impliedStatus); return (SingleTransactionRecordBuilderImpl) context.recordBuilder(SingleTransactionRecordBuilder.class); @@ -326,6 +333,13 @@ protected void assertExpectedBytecodes(@NonNull ReadableKVState tokens) {} + private ReadableKVState finalTokens() { + return component() + .hederaState() + .getReadableStates(TokenServiceImpl.NAME) + .get(TokenServiceImpl.TOKENS_KEY); + } + private ReadableKVState finalNfts() { return component() .hederaState() diff --git a/hedera-node/hedera-app/src/xtest/java/common/BaseScaffoldingComponent.java b/hedera-node/hedera-app/src/xtest/java/common/BaseScaffoldingComponent.java index 3a1720b6b5ce..6a8a00a8408a 100644 --- a/hedera-node/hedera-app/src/xtest/java/common/BaseScaffoldingComponent.java +++ b/hedera-node/hedera-app/src/xtest/java/common/BaseScaffoldingComponent.java @@ -22,10 +22,11 @@ import com.hedera.node.app.fees.ExchangeRateManager; import com.hedera.node.app.fees.FeeManager; import com.hedera.node.app.spi.workflows.HandleContext; +import com.hedera.node.app.spi.workflows.PreHandleContext; import com.hedera.node.app.spi.workflows.QueryContext; -import com.hedera.node.app.state.HederaState; import com.hedera.node.app.state.WorkingStateAccessor; import com.swirlds.config.api.Configuration; +import com.swirlds.state.HederaState; import java.util.function.BiFunction; import java.util.function.Function; @@ -41,6 +42,8 @@ public interface BaseScaffoldingComponent { Function txnContextFactory(); + Function txnPreHandleContextFactory(); + BiFunction queryContextFactory(); FeeManager feeManager(); diff --git a/hedera-node/hedera-app/src/xtest/java/common/BaseScaffoldingModule.java b/hedera-node/hedera-app/src/xtest/java/common/BaseScaffoldingModule.java index 70566dde43f7..c5630d1dc298 100644 --- a/hedera-node/hedera-app/src/xtest/java/common/BaseScaffoldingModule.java +++ b/hedera-node/hedera-app/src/xtest/java/common/BaseScaffoldingModule.java @@ -66,12 +66,9 @@ import com.hedera.node.app.spi.info.SelfNodeInfo; import com.hedera.node.app.spi.metrics.StoreMetricsService; import com.hedera.node.app.spi.records.RecordCache; -import com.hedera.node.app.spi.workflows.HandleContext; -import com.hedera.node.app.spi.workflows.PreHandleDispatcher; -import com.hedera.node.app.spi.workflows.QueryContext; +import com.hedera.node.app.spi.workflows.*; import com.hedera.node.app.state.DeduplicationCache; import com.hedera.node.app.state.HederaRecordCache; -import com.hedera.node.app.state.HederaState; import com.hedera.node.app.state.PlatformStateAccessor; import com.hedera.node.app.state.recordcache.DeduplicationCacheImpl; import com.hedera.node.app.state.recordcache.RecordCacheImpl; @@ -90,6 +87,7 @@ import com.hedera.node.app.workflows.handle.record.RecordListBuilder; import com.hedera.node.app.workflows.handle.stack.SavepointStackImpl; import com.hedera.node.app.workflows.prehandle.DummyPreHandleDispatcher; +import com.hedera.node.app.workflows.prehandle.PreHandleContextImpl; import com.hedera.node.app.workflows.query.QueryContextImpl; import com.hedera.node.config.ConfigProvider; import com.hedera.node.config.VersionedConfigImpl; @@ -100,6 +98,7 @@ import com.swirlds.config.api.Configuration; import com.swirlds.metrics.api.Metrics; import com.swirlds.platform.state.PlatformState; +import com.swirlds.state.HederaState; import contract.ContractScaffoldingComponent; import dagger.Binds; import dagger.Module; @@ -318,6 +317,23 @@ static Function provideHandleContextCreator( }; } + @Provides + @Singleton + static Function providePreHandleContextCreator( + @NonNull final Configuration configuration, + @NonNull final TransactionDispatcher dispatcher, + @NonNull final HederaState state, + @NonNull final PlatformStateAccessor platformState) { + platformState.setPlatformState(new PlatformState()); + return body -> { + try { + return new PreHandleContextImpl(new ReadableStoreFactory(state), body, configuration, dispatcher); + } catch (PreCheckException e) { + throw new RuntimeException(e); + } + }; + } + @Provides @Singleton static CongestionMultipliers createCongestionMultipliers( diff --git a/hedera-node/hedera-app/src/xtest/java/contract/AbstractContractXTest.java b/hedera-node/hedera-app/src/xtest/java/contract/AbstractContractXTest.java index 5f295eee94dd..a557e76b0130 100644 --- a/hedera-node/hedera-app/src/xtest/java/contract/AbstractContractXTest.java +++ b/hedera-node/hedera-app/src/xtest/java/contract/AbstractContractXTest.java @@ -18,7 +18,7 @@ import static com.hedera.node.app.service.contract.impl.ContractServiceImpl.CONTRACT_SERVICE; import static com.hedera.node.app.service.contract.impl.exec.utils.FrameUtils.CONFIG_CONTEXT_VARIABLE; -import static com.hedera.node.app.service.contract.impl.exec.utils.FrameUtils.CallType.DIRECT_OR_TOKEN_REDIRECT; +import static com.hedera.node.app.service.contract.impl.exec.utils.FrameUtils.CallType.DIRECT_OR_PROXY_REDIRECT; import static com.hedera.node.app.service.contract.impl.exec.utils.FrameUtils.SYSTEM_CONTRACT_GAS_CALCULATOR_CONTEXT_VARIABLE; import static com.hedera.node.app.service.contract.impl.utils.ConversionUtils.asLongZeroAddress; import static contract.XTestConstants.PLACEHOLDER_CALL_BODY; @@ -55,8 +55,8 @@ import com.hedera.node.app.service.contract.impl.exec.scope.HandleHederaOperations; import com.hedera.node.app.service.contract.impl.exec.scope.HandleSystemContractOperations; import com.hedera.node.app.service.contract.impl.exec.scope.VerificationStrategies; -import com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.HtsCall; -import com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.HtsCallAddressChecks; +import com.hedera.node.app.service.contract.impl.exec.systemcontracts.common.Call; +import com.hedera.node.app.service.contract.impl.exec.systemcontracts.common.CallAddressChecks; import com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.HtsCallAttempt; import com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.HtsCallFactory; import com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.SyntheticIds; @@ -112,7 +112,7 @@ public abstract class AbstractContractXTest extends AbstractXTest { private ProxyWorldUpdater proxyUpdater; @Mock - private HtsCallAddressChecks addressChecks; + private CallAddressChecks addressChecks; private HtsCallFactory callAttemptFactory; @@ -122,7 +122,7 @@ public abstract class AbstractContractXTest extends AbstractXTest { void setUp() { component = DaggerContractScaffoldingComponent.factory().create(metrics, configuration(), storeMetricsService); callAttemptFactory = new HtsCallFactory( - LIVE_SYNTHETIC_IDS, addressChecks, LIVE_VERIFICATION_STRATEGIES, component.callTranslators()); + LIVE_SYNTHETIC_IDS, addressChecks, LIVE_VERIFICATION_STRATEGIES, component.callHtsTranslators()); } protected Configuration configuration() { @@ -252,7 +252,7 @@ private void runHtsCallAndExpect( final boolean requiresDelegatePermission, @NonNull final org.hyperledger.besu.datatypes.Address sender, @NonNull final org.apache.tuweni.bytes.Bytes input, - @NonNull final Consumer resultAssertions) { + @NonNull final Consumer resultAssertions) { final var context = component.txnContextFactory().apply(PLACEHOLDER_CALL_BODY); final var tinybarValues = TinybarValues.forTransactionWith( context.exchangeRateInfo().activeRate(Instant.now()), @@ -273,8 +273,8 @@ private void runHtsCallAndExpect( component.config().getConfigData(HederaConfig.class), HederaFunctionality.CONTRACT_CALL, new PendingCreationMetadataRef()), - new HandleHederaNativeOperations(context), - new HandleSystemContractOperations(context)); + new HandleHederaNativeOperations(context, null), + new HandleSystemContractOperations(context, null)); given(proxyUpdater.enhancement()).willReturn(enhancement); given(frame.getWorldUpdater()).willReturn(proxyUpdater); given(frame.getSenderAddress()).willReturn(sender); @@ -288,7 +288,7 @@ private void runHtsCallAndExpect( given(addressChecks.hasParentDelegateCall(frame)).willReturn(requiresDelegatePermission); Mockito.lenient().when(frame.getValue()).thenReturn(Wei.MAX_WEI); - final var attempt = callAttemptFactory.createCallAttemptFrom(input, DIRECT_OR_TOKEN_REDIRECT, frame); + final var attempt = callAttemptFactory.createCallAttemptFrom(input, DIRECT_OR_PROXY_REDIRECT, frame); final var call = attempt.asExecutableCall(); final var pricedResult = requireNonNull(call).execute(frame); @@ -342,7 +342,7 @@ protected Consumer assertingCallLocalResultIsBuffer( }; } - private Consumer resultOnlyAssertion( + private Consumer resultOnlyAssertion( @NonNull final Consumer resultAssertion) { return pricedResult -> { final var fullResult = pricedResult.fullResult(); diff --git a/hedera-node/hedera-app/src/xtest/java/contract/AssociationsXTest.java b/hedera-node/hedera-app/src/xtest/java/contract/AssociationsXTest.java index 546e6cc3462c..6b9f0fa89693 100644 --- a/hedera-node/hedera-app/src/xtest/java/contract/AssociationsXTest.java +++ b/hedera-node/hedera-app/src/xtest/java/contract/AssociationsXTest.java @@ -49,7 +49,7 @@ import com.hedera.hapi.node.state.token.TokenRelation; import com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.ReturnTypes; import com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.associations.AssociationsTranslator; -import com.hedera.node.app.spi.state.ReadableKVState; +import com.swirlds.state.spi.ReadableKVState; import java.math.BigInteger; import java.util.HashMap; import java.util.Map; diff --git a/hedera-node/hedera-app/src/xtest/java/contract/AssortedOpsXTest.java b/hedera-node/hedera-app/src/xtest/java/contract/AssortedOpsXTest.java index cafbd11ddef0..7cf6d830722c 100644 --- a/hedera-node/hedera-app/src/xtest/java/contract/AssortedOpsXTest.java +++ b/hedera-node/hedera-app/src/xtest/java/contract/AssortedOpsXTest.java @@ -64,8 +64,8 @@ import com.hedera.hapi.node.state.primitives.ProtoBytes; import com.hedera.hapi.node.state.token.Account; import com.hedera.hapi.node.transaction.TransactionBody; -import com.hedera.node.app.spi.state.ReadableKVState; import com.hedera.pbj.runtime.io.buffer.Bytes; +import com.swirlds.state.spi.ReadableKVState; import edu.umd.cs.findbugs.annotations.NonNull; import java.util.HashMap; import java.util.Map; diff --git a/hedera-node/hedera-app/src/xtest/java/contract/BurnsXTest.java b/hedera-node/hedera-app/src/xtest/java/contract/BurnsXTest.java index 4a39d4aa4acd..a69b5efa611e 100644 --- a/hedera-node/hedera-app/src/xtest/java/contract/BurnsXTest.java +++ b/hedera-node/hedera-app/src/xtest/java/contract/BurnsXTest.java @@ -65,7 +65,7 @@ import com.hedera.hapi.node.state.token.TokenRelation; import com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.burn.BurnTranslator; import com.hedera.node.app.spi.fixtures.Scenarios; -import com.hedera.node.app.spi.state.ReadableKVState; +import com.swirlds.state.spi.ReadableKVState; import java.math.BigInteger; import java.util.HashMap; import java.util.Map; diff --git a/hedera-node/hedera-app/src/xtest/java/contract/ContractScaffoldingComponent.java b/hedera-node/hedera-app/src/xtest/java/contract/ContractScaffoldingComponent.java index c1428fd676ba..aa6fba6dd656 100644 --- a/hedera-node/hedera-app/src/xtest/java/contract/ContractScaffoldingComponent.java +++ b/hedera-node/hedera-app/src/xtest/java/contract/ContractScaffoldingComponent.java @@ -16,8 +16,9 @@ package contract; +import com.hedera.node.app.service.contract.impl.exec.processors.HasTranslatorsModule; import com.hedera.node.app.service.contract.impl.exec.processors.HtsTranslatorsModule; -import com.hedera.node.app.service.contract.impl.exec.systemcontracts.HtsCallTranslator; +import com.hedera.node.app.service.contract.impl.exec.systemcontracts.common.CallTranslator; import com.hedera.node.app.spi.metrics.StoreMetricsService; import com.hedera.node.app.workflows.handle.HandlersInjectionModule; import com.swirlds.config.api.Configuration; @@ -27,6 +28,7 @@ import dagger.BindsInstance; import dagger.Component; import java.util.List; +import javax.inject.Named; import javax.inject.Singleton; /** @@ -34,7 +36,13 @@ * {@link com.hedera.node.app.service.contract.ContractService} handlers. */ @Singleton -@Component(modules = {HandlersInjectionModule.class, BaseScaffoldingModule.class, HtsTranslatorsModule.class}) +@Component( + modules = { + HandlersInjectionModule.class, + BaseScaffoldingModule.class, + HtsTranslatorsModule.class, + HasTranslatorsModule.class + }) public interface ContractScaffoldingComponent extends BaseScaffoldingComponent { @Component.Factory interface Factory { @@ -44,5 +52,9 @@ ContractScaffoldingComponent create( @BindsInstance StoreMetricsService storeMetricsService); } - List callTranslators(); + @Named("HtsTranslators") + List callHtsTranslators(); + + @Named("HasTranslators") + List callHasTranslators(); } diff --git a/hedera-node/hedera-app/src/xtest/java/contract/DeleteXTest.java b/hedera-node/hedera-app/src/xtest/java/contract/DeleteXTest.java index 8b9de4e0f3de..7ec6ca949ee0 100644 --- a/hedera-node/hedera-app/src/xtest/java/contract/DeleteXTest.java +++ b/hedera-node/hedera-app/src/xtest/java/contract/DeleteXTest.java @@ -20,7 +20,6 @@ import static com.hedera.hapi.node.base.ResponseCodeEnum.INVALID_TOKEN_ID; import static com.hedera.hapi.node.base.ResponseCodeEnum.TOKEN_IS_IMMUTABLE; import static com.hedera.hapi.node.base.ResponseCodeEnum.TOKEN_WAS_DELETED; -import static com.hedera.node.app.service.contract.impl.utils.ConversionUtils.asHeadlongAddress; import static contract.AssociationsXTestConstants.A_TOKEN_ADDRESS; import static contract.AssociationsXTestConstants.A_TOKEN_ID; import static contract.AssociationsXTestConstants.B_TOKEN_ADDRESS; @@ -55,7 +54,7 @@ import com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.ReturnTypes; import com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.delete.DeleteTranslator; import com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.freeze.FreezeUnfreezeTranslator; -import com.hedera.node.app.spi.state.ReadableKVState; +import com.swirlds.state.spi.ReadableKVState; import edu.umd.cs.findbugs.annotations.NonNull; import java.util.HashMap; import java.util.Map; diff --git a/hedera-node/hedera-app/src/xtest/java/contract/Erc721XTest.java b/hedera-node/hedera-app/src/xtest/java/contract/Erc721XTest.java index b4cea3fe7917..9a278a8f91e8 100644 --- a/hedera-node/hedera-app/src/xtest/java/contract/Erc721XTest.java +++ b/hedera-node/hedera-app/src/xtest/java/contract/Erc721XTest.java @@ -50,8 +50,8 @@ import com.hedera.hapi.node.state.primitives.ProtoBytes; import com.hedera.hapi.node.state.token.Account; import com.hedera.hapi.node.transaction.TransactionBody; -import com.hedera.node.app.spi.state.ReadableKVState; import com.hedera.pbj.runtime.io.buffer.Bytes; +import com.swirlds.state.spi.ReadableKVState; import edu.umd.cs.findbugs.annotations.NonNull; import java.math.BigInteger; import java.nio.ByteBuffer; diff --git a/hedera-node/hedera-app/src/xtest/java/contract/FreezeUnfreezeXTest.java b/hedera-node/hedera-app/src/xtest/java/contract/FreezeUnfreezeXTest.java index 7f0e81e7ccf1..8ef0f9946f88 100644 --- a/hedera-node/hedera-app/src/xtest/java/contract/FreezeUnfreezeXTest.java +++ b/hedera-node/hedera-app/src/xtest/java/contract/FreezeUnfreezeXTest.java @@ -53,7 +53,7 @@ import com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.associations.AssociationsTranslator; import com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.freeze.FreezeUnfreezeTranslator; import com.hedera.node.app.spi.fixtures.Scenarios; -import com.hedera.node.app.spi.state.ReadableKVState; +import com.swirlds.state.spi.ReadableKVState; import java.math.BigInteger; import java.util.HashMap; import java.util.Map; diff --git a/hedera-node/hedera-app/src/xtest/java/contract/HtsErc20TransfersXTest.java b/hedera-node/hedera-app/src/xtest/java/contract/HtsErc20TransfersXTest.java index 976be33e0679..ed045b69ac2e 100644 --- a/hedera-node/hedera-app/src/xtest/java/contract/HtsErc20TransfersXTest.java +++ b/hedera-node/hedera-app/src/xtest/java/contract/HtsErc20TransfersXTest.java @@ -44,7 +44,7 @@ import com.hedera.hapi.node.state.token.AccountFungibleTokenAllowance; import com.hedera.hapi.node.state.token.Token; import com.hedera.hapi.node.state.token.TokenRelation; -import com.hedera.node.app.spi.state.ReadableKVState; +import com.swirlds.state.spi.ReadableKVState; import java.math.BigInteger; import java.util.HashMap; import java.util.List; diff --git a/hedera-node/hedera-app/src/xtest/java/contract/HtsErc721TransferFromXTest.java b/hedera-node/hedera-app/src/xtest/java/contract/HtsErc721TransferFromXTest.java index 132e546faf80..38608d3ff164 100644 --- a/hedera-node/hedera-app/src/xtest/java/contract/HtsErc721TransferFromXTest.java +++ b/hedera-node/hedera-app/src/xtest/java/contract/HtsErc721TransferFromXTest.java @@ -54,7 +54,7 @@ import com.hedera.hapi.node.state.token.Nft; import com.hedera.hapi.node.state.token.Token; import com.hedera.hapi.node.state.token.TokenRelation; -import com.hedera.node.app.spi.state.ReadableKVState; +import com.swirlds.state.spi.ReadableKVState; import edu.umd.cs.findbugs.annotations.NonNull; import java.math.BigInteger; import java.util.HashMap; diff --git a/hedera-node/hedera-app/src/xtest/java/contract/MintsXTest.java b/hedera-node/hedera-app/src/xtest/java/contract/MintsXTest.java index 59a77299c53d..f792c99062e6 100644 --- a/hedera-node/hedera-app/src/xtest/java/contract/MintsXTest.java +++ b/hedera-node/hedera-app/src/xtest/java/contract/MintsXTest.java @@ -61,7 +61,7 @@ import com.hedera.hapi.node.state.token.Token; import com.hedera.hapi.node.state.token.TokenRelation; import com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.mint.MintTranslator; -import com.hedera.node.app.spi.state.ReadableKVState; +import com.swirlds.state.spi.ReadableKVState; import java.math.BigInteger; import java.util.HashMap; import java.util.Map; diff --git a/hedera-node/hedera-app/src/xtest/java/contract/MiscClassicTransfersXTest.java b/hedera-node/hedera-app/src/xtest/java/contract/MiscClassicTransfersXTest.java index a9b888c009dc..2ef049fd9a25 100644 --- a/hedera-node/hedera-app/src/xtest/java/contract/MiscClassicTransfersXTest.java +++ b/hedera-node/hedera-app/src/xtest/java/contract/MiscClassicTransfersXTest.java @@ -62,7 +62,7 @@ import com.hedera.hapi.node.state.token.TokenRelation; import com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.ReturnTypes; import com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.transfer.ClassicTransfersTranslator; -import com.hedera.node.app.spi.state.ReadableKVState; +import com.swirlds.state.spi.ReadableKVState; import edu.umd.cs.findbugs.annotations.NonNull; import java.util.HashMap; import java.util.Map; diff --git a/hedera-node/hedera-app/src/xtest/java/contract/SetApprovalForAllXTest.java b/hedera-node/hedera-app/src/xtest/java/contract/SetApprovalForAllXTest.java index 3706fce7298a..929cead18b43 100644 --- a/hedera-node/hedera-app/src/xtest/java/contract/SetApprovalForAllXTest.java +++ b/hedera-node/hedera-app/src/xtest/java/contract/SetApprovalForAllXTest.java @@ -60,7 +60,7 @@ import com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.ReturnTypes; import com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.setapproval.SetApprovalForAllTranslator; import com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.transfer.ClassicTransfersTranslator; -import com.hedera.node.app.spi.state.ReadableKVState; +import com.swirlds.state.spi.ReadableKVState; import java.util.HashMap; import java.util.Map; import org.apache.tuweni.bytes.Bytes; diff --git a/hedera-node/hedera-app/src/xtest/java/contract/UpdatesExpiryXTest.java b/hedera-node/hedera-app/src/xtest/java/contract/UpdatesExpiryXTest.java index 333ce02984c2..afc180882f18 100644 --- a/hedera-node/hedera-app/src/xtest/java/contract/UpdatesExpiryXTest.java +++ b/hedera-node/hedera-app/src/xtest/java/contract/UpdatesExpiryXTest.java @@ -51,7 +51,7 @@ import com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.ReturnTypes; import com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.update.UpdateExpiryTranslator; import com.hedera.node.app.spi.fixtures.Scenarios; -import com.hedera.node.app.spi.state.ReadableKVState; +import com.swirlds.state.spi.ReadableKVState; import edu.umd.cs.findbugs.annotations.NonNull; import java.time.Instant; import java.util.HashMap; diff --git a/hedera-node/hedera-app/src/xtest/java/contract/UpdatesXTest.java b/hedera-node/hedera-app/src/xtest/java/contract/UpdatesXTest.java index c00e29fbb77f..0f46daeb2cd5 100644 --- a/hedera-node/hedera-app/src/xtest/java/contract/UpdatesXTest.java +++ b/hedera-node/hedera-app/src/xtest/java/contract/UpdatesXTest.java @@ -53,7 +53,7 @@ import com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.ReturnTypes; import com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.update.UpdateTranslator; import com.hedera.node.app.spi.fixtures.Scenarios; -import com.hedera.node.app.spi.state.ReadableKVState; +import com.swirlds.state.spi.ReadableKVState; import edu.umd.cs.findbugs.annotations.NonNull; import java.util.HashMap; import java.util.Map; diff --git a/hedera-node/hedera-app/src/xtest/java/contract/WipeXTest.java b/hedera-node/hedera-app/src/xtest/java/contract/WipeXTest.java index d5859ed92f98..60fb4dd2948d 100644 --- a/hedera-node/hedera-app/src/xtest/java/contract/WipeXTest.java +++ b/hedera-node/hedera-app/src/xtest/java/contract/WipeXTest.java @@ -63,7 +63,7 @@ import com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.ReturnTypes; import com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.wipe.WipeTranslator; import com.hedera.node.app.spi.fixtures.Scenarios; -import com.hedera.node.app.spi.state.ReadableKVState; +import com.swirlds.state.spi.ReadableKVState; import java.util.HashMap; import java.util.Map; import org.apache.tuweni.bytes.Bytes; diff --git a/hedera-node/hedera-app/src/xtest/java/token/AbstractTokenXTest.java b/hedera-node/hedera-app/src/xtest/java/token/AbstractTokenXTest.java index a8f2860d5599..fe8321b0291b 100644 --- a/hedera-node/hedera-app/src/xtest/java/token/AbstractTokenXTest.java +++ b/hedera-node/hedera-app/src/xtest/java/token/AbstractTokenXTest.java @@ -16,19 +16,14 @@ package token; -import com.hedera.hapi.node.base.AccountAmount; -import com.hedera.hapi.node.base.AccountID; -import com.hedera.hapi.node.base.Fraction; -import com.hedera.hapi.node.base.NftTransfer; -import com.hedera.hapi.node.base.TokenID; -import com.hedera.hapi.node.base.TokenTransferList; -import com.hedera.hapi.node.base.TransactionID; +import static contract.XTestConstants.AN_ED25519_KEY; + +import com.hedera.hapi.node.base.*; import com.hedera.hapi.node.state.token.Account; import com.hedera.hapi.node.token.CryptoTransferTransactionBody; import com.hedera.hapi.node.token.TokenMintTransactionBody; -import com.hedera.hapi.node.transaction.CustomFee; -import com.hedera.hapi.node.transaction.RoyaltyFee; -import com.hedera.hapi.node.transaction.TransactionBody; +import com.hedera.hapi.node.token.TokenUpdateTransactionBody; +import com.hedera.hapi.node.transaction.*; import com.hedera.node.config.testfixtures.HederaTestConfigBuilder; import com.hedera.pbj.runtime.io.buffer.Bytes; import com.swirlds.config.api.Configuration; @@ -122,6 +117,19 @@ protected final TransactionBody transfer(Consumer> methods) { + final var b = TokenUpdateTransactionBody.newBuilder(); + b.token(tokenID); + for (final var method : methods) { + method.accept(b); + } + return TransactionBody.newBuilder() + .transactionID(TransactionID.newBuilder().accountID(DEFAULT_PAYER_ID)) + .tokenUpdate(b) + .build(); + } + protected final TransactionBody nftMint(@NonNull final Bytes metadata, @NonNull final String token) { return nftMint(metadata, idOfNamedToken(token)); } @@ -143,6 +151,7 @@ protected Map initialAccounts() { Account.newBuilder() .accountId(DEFAULT_PAYER_ID) .tinybarBalance(Long.MAX_VALUE / 2) + .key(AN_ED25519_KEY) .build()); return accounts; } diff --git a/hedera-node/hedera-app/src/xtest/java/token/RoyaltyCollectorAutoAssociationXTest.java b/hedera-node/hedera-app/src/xtest/java/token/RoyaltyCollectorAutoAssociationXTest.java index b563c78948b3..6acfb8f23b83 100644 --- a/hedera-node/hedera-app/src/xtest/java/token/RoyaltyCollectorAutoAssociationXTest.java +++ b/hedera-node/hedera-app/src/xtest/java/token/RoyaltyCollectorAutoAssociationXTest.java @@ -24,8 +24,8 @@ import com.hedera.hapi.node.state.token.Nft; import com.hedera.hapi.node.state.token.Token; import com.hedera.hapi.node.state.token.TokenRelation; -import com.hedera.node.app.spi.state.ReadableKVState; import com.hedera.pbj.runtime.io.buffer.Bytes; +import com.swirlds.state.spi.ReadableKVState; import edu.umd.cs.findbugs.annotations.NonNull; import java.util.Map; diff --git a/hedera-node/hedera-app/src/xtest/java/token/TokenScaffoldingComponent.java b/hedera-node/hedera-app/src/xtest/java/token/TokenScaffoldingComponent.java index 552ce7c0536e..585a9475fd10 100644 --- a/hedera-node/hedera-app/src/xtest/java/token/TokenScaffoldingComponent.java +++ b/hedera-node/hedera-app/src/xtest/java/token/TokenScaffoldingComponent.java @@ -18,6 +18,7 @@ import com.hedera.node.app.service.token.impl.handlers.CryptoTransferHandler; import com.hedera.node.app.service.token.impl.handlers.TokenMintHandler; +import com.hedera.node.app.service.token.impl.handlers.TokenUpdateHandler; import com.hedera.node.app.spi.metrics.StoreMetricsService; import com.hedera.node.app.workflows.handle.HandlersInjectionModule; import com.swirlds.config.api.Configuration; @@ -46,4 +47,6 @@ TokenScaffoldingComponent create( CryptoTransferHandler cryptoTransferHandler(); TokenMintHandler tokenMintHandler(); + + TokenUpdateHandler tokenUpdateHandler(); } diff --git a/hedera-node/hedera-app/src/xtest/java/token/update/TokenUpdateRemoveKeysXTest.java b/hedera-node/hedera-app/src/xtest/java/token/update/TokenUpdateRemoveKeysXTest.java new file mode 100644 index 000000000000..60bfaf54589d --- /dev/null +++ b/hedera-node/hedera-app/src/xtest/java/token/update/TokenUpdateRemoveKeysXTest.java @@ -0,0 +1,134 @@ +/* + * Copyright (C) 2023-2024 Hedera Hashgraph, LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package token.update; + +import static com.hedera.hapi.node.base.ResponseCodeEnum.OK; +import static com.hedera.node.app.spi.key.KeyUtils.IMMUTABILITY_SENTINEL_KEY; + +import com.hedera.hapi.node.base.AccountID; +import com.hedera.hapi.node.base.Key; +import com.hedera.hapi.node.base.TokenID; +import com.hedera.hapi.node.state.token.Account; +import com.hedera.hapi.node.state.token.Token; +import com.hedera.pbj.runtime.io.buffer.Bytes; +import java.util.List; +import java.util.Map; +import token.AbstractTokenXTest; + +public class TokenUpdateRemoveKeysXTest extends AbstractTokenXTest { + private final Key TOKEN_TREASURY_KEY = Key.newBuilder() + .ed25519(Bytes.fromHex("00aaa00aaa00aaa00aaa00aaa00aaaab00aaa00aaa00aaa00aaa00aaa00aaaad")) + .build(); + protected static final String TOKEN_REMOVE_KEYS_ID = "tokenRemoveKeysId"; + protected static final String TOKEN_CHANGE_KEYS_ID = "tokenChangeKeysId"; + private static final String TOKEN_TREASURY = "tokenTreasury"; + private static final String FIRST_ROYALTY_COLLECTOR = "firstRoyaltyCollector"; + private static final int PLENTY_OF_SLOTS = 10; + + private static final Key DEFAULT_KEY = Key.newBuilder() + .ed25519(Bytes.wrap("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa".getBytes())) + .build(); + + @Override + protected void doScenarioOperations() { + // remove freeze key + handleAndCommitSingleTransaction( + component.tokenUpdateHandler(), + tokenUpdate(idOfNamedToken(TOKEN_REMOVE_KEYS_ID), List.of(b -> b.freezeKey(IMMUTABILITY_SENTINEL_KEY))), + OK); + // remove kyc key + handleAndCommitSingleTransaction( + component.tokenUpdateHandler(), + tokenUpdate(idOfNamedToken(TOKEN_REMOVE_KEYS_ID), List.of(b -> b.kycKey(IMMUTABILITY_SENTINEL_KEY))), + OK); + // remove wipe key + handleAndCommitSingleTransaction( + component.tokenUpdateHandler(), + tokenUpdate(idOfNamedToken(TOKEN_REMOVE_KEYS_ID), List.of(b -> b.wipeKey(IMMUTABILITY_SENTINEL_KEY))), + OK); + // remove supply key + handleAndCommitSingleTransaction( + component.tokenUpdateHandler(), + tokenUpdate(idOfNamedToken(TOKEN_REMOVE_KEYS_ID), List.of(b -> b.supplyKey(IMMUTABILITY_SENTINEL_KEY))), + OK); + // remove fee schedule key + handleAndCommitSingleTransaction( + component.tokenUpdateHandler(), + tokenUpdate( + idOfNamedToken(TOKEN_REMOVE_KEYS_ID), + List.of(b -> b.feeScheduleKey(IMMUTABILITY_SENTINEL_KEY))), + OK); + // remove pause key + handleAndCommitSingleTransaction( + component.tokenUpdateHandler(), + tokenUpdate(idOfNamedToken(TOKEN_REMOVE_KEYS_ID), List.of(b -> b.pauseKey(IMMUTABILITY_SENTINEL_KEY))), + OK); + handleAndCommitSingleTransaction( + component.tokenUpdateHandler(), + tokenUpdate( + idOfNamedToken(TOKEN_REMOVE_KEYS_ID), List.of(b -> b.metadataKey(IMMUTABILITY_SENTINEL_KEY))), + OK); + // remove admin key + handleAndCommitSingleTransaction( + component.tokenUpdateHandler(), + tokenUpdate(idOfNamedToken(TOKEN_REMOVE_KEYS_ID), List.of(b -> b.adminKey(IMMUTABILITY_SENTINEL_KEY))), + OK); + } + + @Override + protected Map initialAccounts() { + final var accounts = super.initialAccounts(); + addNamedAccount( + TOKEN_TREASURY, b -> b.maxAutoAssociations(PLENTY_OF_SLOTS).key(TOKEN_TREASURY_KEY), accounts); + addNamedAccount(FIRST_ROYALTY_COLLECTOR, b -> b.maxAutoAssociations(PLENTY_OF_SLOTS), accounts); + return accounts; + } + + @Override + protected Map initialTokens() { + /** + * Initial token should be initialized with all keys to test removal. Set DEFAULT_KEY as default value for all keys. + */ + final var tokens = super.initialTokens(); + addNamedFungibleToken( + TOKEN_REMOVE_KEYS_ID, + b -> b.treasuryAccountId(idOfNamedAccount(TOKEN_TREASURY)) + .adminKey(DEFAULT_KEY) + .freezeKey(DEFAULT_KEY) + .supplyKey(DEFAULT_KEY) + .kycKey(DEFAULT_KEY) + .feeScheduleKey(DEFAULT_KEY) + .metadataKey(DEFAULT_KEY) + .wipeKey(DEFAULT_KEY) + .pauseKey(DEFAULT_KEY), + tokens); + addNamedFungibleToken( + TOKEN_CHANGE_KEYS_ID, + b -> b.treasuryAccountId(idOfNamedAccount(TOKEN_TREASURY)) + .adminKey(DEFAULT_KEY) + .freezeKey(DEFAULT_KEY) + .supplyKey(DEFAULT_KEY) + .kycKey(DEFAULT_KEY) + .feeScheduleKey(DEFAULT_KEY) + .metadataKey(DEFAULT_KEY) + .wipeKey(DEFAULT_KEY) + .pauseKey(DEFAULT_KEY), + tokens); + + return tokens; + } +} diff --git a/hedera-node/hedera-app/src/xtest/java/token/update/TokenUpdateReplaceKeysXTest.java b/hedera-node/hedera-app/src/xtest/java/token/update/TokenUpdateReplaceKeysXTest.java new file mode 100644 index 000000000000..22295ae5ef54 --- /dev/null +++ b/hedera-node/hedera-app/src/xtest/java/token/update/TokenUpdateReplaceKeysXTest.java @@ -0,0 +1,217 @@ +/* + * Copyright (C) 2023-2024 Hedera Hashgraph, LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package token.update; + +import static com.hedera.hapi.node.base.ResponseCodeEnum.OK; +import static org.junit.jupiter.api.Assertions.assertEquals; + +import com.hedera.hapi.node.base.AccountID; +import com.hedera.hapi.node.base.Key; +import com.hedera.hapi.node.base.TokenID; +import com.hedera.hapi.node.base.TokenKeyValidation; +import com.hedera.hapi.node.state.common.EntityIDPair; +import com.hedera.hapi.node.state.token.Account; +import com.hedera.hapi.node.state.token.Token; +import com.hedera.hapi.node.state.token.TokenRelation; +import com.hedera.pbj.runtime.io.buffer.Bytes; +import com.swirlds.platform.state.spi.ReadableKVStateBase; +import com.swirlds.state.spi.ReadableKVState; +import edu.umd.cs.findbugs.annotations.NonNull; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import token.AbstractTokenXTest; + +public class TokenUpdateReplaceKeysXTest extends AbstractTokenXTest { + // This key is structurally valid, but effectively unusable because there is no + // known way to invert the SHA-512 hash of its associated curve point + public static final Key UNUSABLE_ZEROS_KEY = Key.newBuilder() + .ed25519(Bytes.fromHex("0000000000000000000000000000000000000000000000000000000000000000")) + .build(); + private Map accountsCurrent; + private final Key TOKEN_TREASURY_KEY = Key.newBuilder() + .ed25519(Bytes.fromHex("00aaa00aaa00aaa00aaa00aaa00aaaab00aaa00aaa00aaa00aaa00aaa00aaaad")) + .build(); + private final Key INITIAL_KEY = Key.newBuilder() + .ed25519(Bytes.fromHex("00aaa00aaa00aaa00aaa00aaa00aaaab00aaa00aaa00aaa00aaa00aaa00aaaac")) + .build(); + private final Key FINAL_KEY = Key.newBuilder() + .ed25519(Bytes.fromHex("00aaa00aaa00aaa00aaa00aaa00aaaab00aaa00aaa00aaa00aaa00aaa00aaaab")) + .build(); + private final Key ADMIN_KEY = Key.newBuilder() + .ed25519(Bytes.fromHex("00aaa00aaa00aaa00aaa00aaa00aaaaa00aaa00aaa00aaa00aaa00aaa00aaaaa")) + .build(); + protected static final String TOKEN_UPDATE_KEYS_ID = "tokenUpdateKeysId"; + protected static final String TOKEN_INVALID_KEYS_ID = "tokenInvalidKeysId"; + private static final String TOKEN_TREASURY = "tokenTreasury"; + private static final String FIRST_ROYALTY_COLLECTOR = "firstRoyaltyCollector"; + private static final int PLENTY_OF_SLOTS = 10; + private static final long INITIAL_SUPPLY = 123456789; + + @Override + protected void doScenarioOperations() { + // update freeze key + accountsCurrent.get(idOfNamedAccount(TOKEN_TREASURY)); + handleAndCommitSingleTransaction( + component.tokenUpdateHandler(), + tokenUpdate(idOfNamedToken(TOKEN_UPDATE_KEYS_ID), List.of(b -> b.freezeKey(FINAL_KEY))), + OK); + // update feeScheduleKey key + accountsCurrent.get(idOfNamedAccount(TOKEN_TREASURY)); + handleAndCommitSingleTransaction( + component.tokenUpdateHandler(), + tokenUpdate(idOfNamedToken(TOKEN_UPDATE_KEYS_ID), List.of(b -> b.feeScheduleKey(FINAL_KEY))), + OK); + // update kycKey key + accountsCurrent.get(idOfNamedAccount(TOKEN_TREASURY)); + handleAndCommitSingleTransaction( + component.tokenUpdateHandler(), + tokenUpdate(idOfNamedToken(TOKEN_UPDATE_KEYS_ID), List.of(b -> b.kycKey(FINAL_KEY))), + OK); + // update pauseKey key + accountsCurrent.get(idOfNamedAccount(TOKEN_TREASURY)); + handleAndCommitSingleTransaction( + component.tokenUpdateHandler(), + tokenUpdate(idOfNamedToken(TOKEN_UPDATE_KEYS_ID), List.of(b -> b.pauseKey(FINAL_KEY))), + OK); + // update supplyKey key + accountsCurrent.get(idOfNamedAccount(TOKEN_TREASURY)); + handleAndCommitSingleTransaction( + component.tokenUpdateHandler(), + tokenUpdate(idOfNamedToken(TOKEN_UPDATE_KEYS_ID), List.of(b -> b.supplyKey(FINAL_KEY))), + OK); + // update wipeKey key + accountsCurrent.get(idOfNamedAccount(TOKEN_TREASURY)); + handleAndCommitSingleTransaction( + component.tokenUpdateHandler(), + tokenUpdate(idOfNamedToken(TOKEN_UPDATE_KEYS_ID), List.of(b -> b.wipeKey(FINAL_KEY))), + OK); + // update metadataKey key + accountsCurrent.get(idOfNamedAccount(TOKEN_TREASURY)); + handleAndCommitSingleTransaction( + component.tokenUpdateHandler(), + tokenUpdate(idOfNamedToken(TOKEN_UPDATE_KEYS_ID), List.of(b -> b.metadataKey(FINAL_KEY))), + OK); + + // change keys to an "invalid" key i.e `0x0000000000000000000000000000000000000000` + // change freeze key to an invalid + handleAndCommitSingleTransaction( + component.tokenUpdateHandler(), + tokenUpdate(idOfNamedToken(TOKEN_INVALID_KEYS_ID), List.of(b -> b.freezeKey(UNUSABLE_ZEROS_KEY) + .keyVerificationMode(TokenKeyValidation.NO_VALIDATION))), + OK); + // change kyc key to an invalid + handleAndCommitSingleTransaction( + component.tokenUpdateHandler(), + tokenUpdate(idOfNamedToken(TOKEN_INVALID_KEYS_ID), List.of(b -> b.kycKey(UNUSABLE_ZEROS_KEY) + .keyVerificationMode(TokenKeyValidation.NO_VALIDATION))), + OK); + // change wipe key to an invalid + handleAndCommitSingleTransaction( + component.tokenUpdateHandler(), + tokenUpdate(idOfNamedToken(TOKEN_INVALID_KEYS_ID), List.of(b -> b.wipeKey(UNUSABLE_ZEROS_KEY) + .keyVerificationMode(TokenKeyValidation.NO_VALIDATION))), + OK); + // change supply key to an invalid + handleAndCommitSingleTransaction( + component.tokenUpdateHandler(), + tokenUpdate(idOfNamedToken(TOKEN_INVALID_KEYS_ID), List.of(b -> b.supplyKey(UNUSABLE_ZEROS_KEY) + .keyVerificationMode(TokenKeyValidation.NO_VALIDATION))), + OK); + // change fee schedule key to an invalid + handleAndCommitSingleTransaction( + component.tokenUpdateHandler(), + tokenUpdate(idOfNamedToken(TOKEN_INVALID_KEYS_ID), List.of(b -> b.feeScheduleKey(UNUSABLE_ZEROS_KEY) + .keyVerificationMode(TokenKeyValidation.NO_VALIDATION))), + OK); + // change pause key to an invalid + handleAndCommitSingleTransaction( + component.tokenUpdateHandler(), + tokenUpdate(idOfNamedToken(TOKEN_INVALID_KEYS_ID), List.of(b -> b.pauseKey(UNUSABLE_ZEROS_KEY) + .keyVerificationMode(TokenKeyValidation.NO_VALIDATION))), + OK); + // change metadata key to an invalid + handleAndCommitSingleTransaction( + component.tokenUpdateHandler(), + tokenUpdate(idOfNamedToken(TOKEN_INVALID_KEYS_ID), List.of(b -> b.metadataKey(UNUSABLE_ZEROS_KEY) + .keyVerificationMode(TokenKeyValidation.NO_VALIDATION))), + OK); + // change admin key to an invalid + handleAndCommitSingleTransaction( + component.tokenUpdateHandler(), + tokenUpdate(idOfNamedToken(TOKEN_INVALID_KEYS_ID), List.of(b -> b.adminKey(UNUSABLE_ZEROS_KEY) + .keyVerificationMode(TokenKeyValidation.NO_VALIDATION))), + OK); + } + + @Override + protected void assertExpectedTokens(@NonNull ReadableKVState tokens) { + ((ReadableKVStateBase) tokens).reset(); + final var token = Objects.requireNonNull(tokens.get(idOfNamedToken(TOKEN_UPDATE_KEYS_ID))); + assertEquals(FINAL_KEY, token.freezeKey()); + assertEquals(FINAL_KEY, token.feeScheduleKey()); + } + + @Override + protected Map initialAccounts() { + final var accounts = super.initialAccounts(); + addNamedAccount( + TOKEN_TREASURY, b -> b.maxAutoAssociations(PLENTY_OF_SLOTS).key(TOKEN_TREASURY_KEY), accounts); + addNamedAccount(FIRST_ROYALTY_COLLECTOR, b -> b.maxAutoAssociations(PLENTY_OF_SLOTS), accounts); + accountsCurrent = accounts; + return accounts; + } + + @Override + protected Map initialTokens() { + final var tokens = super.initialTokens(); + addNamedFungibleToken( + TOKEN_UPDATE_KEYS_ID, + b -> b.treasuryAccountId(idOfNamedAccount(TOKEN_TREASURY)) + .totalSupply(INITIAL_SUPPLY) + .adminKey(ADMIN_KEY) + .freezeKey(INITIAL_KEY) + .feeScheduleKey(INITIAL_KEY) + .pauseKey(INITIAL_KEY) + .supplyKey(INITIAL_KEY) + .wipeKey(INITIAL_KEY) + .metadataKey(INITIAL_KEY) + .kycKey(INITIAL_KEY), + tokens); + addNamedFungibleToken( + TOKEN_INVALID_KEYS_ID, + b -> b.treasuryAccountId(idOfNamedAccount(TOKEN_TREASURY)) + .totalSupply(INITIAL_SUPPLY) + .adminKey(ADMIN_KEY) + .freezeKey(INITIAL_KEY) + .feeScheduleKey(INITIAL_KEY) + .pauseKey(INITIAL_KEY) + .supplyKey(INITIAL_KEY) + .wipeKey(INITIAL_KEY) + .metadataKey(INITIAL_KEY) + .kycKey(INITIAL_KEY), + tokens); + return tokens; + } + + @Override + protected Map initialTokenRelationships() { + final var tokenRels = super.initialTokenRelationships(); + addNewRelation(TOKEN_TREASURY, TOKEN_UPDATE_KEYS_ID, b -> b.balance(INITIAL_SUPPLY), tokenRels); + return tokenRels; + } +} diff --git a/hedera-node/hedera-config/build.gradle.kts b/hedera-node/hedera-config/build.gradle.kts index 98e8f1ec3abd..17f0f3eac4d9 100644 --- a/hedera-node/hedera-config/build.gradle.kts +++ b/hedera-node/hedera-config/build.gradle.kts @@ -15,8 +15,9 @@ */ plugins { - id("com.hedera.hashgraph.conventions") - id("com.hedera.hashgraph.java-test-fixtures") + id("com.hedera.gradle.services") + id("com.hedera.gradle.services-publish") + id("com.hedera.gradle.java-test-fixtures") } description = "Hedera Configuration" diff --git a/hedera-node/hedera-config/src/main/java/com/hedera/node/config/data/ApiPermissionConfig.java b/hedera-node/hedera-config/src/main/java/com/hedera/node/config/data/ApiPermissionConfig.java index ece69aa0abfb..ea0cfbffd0fd 100644 --- a/hedera-node/hedera-config/src/main/java/com/hedera/node/config/data/ApiPermissionConfig.java +++ b/hedera-node/hedera-config/src/main/java/com/hedera/node/config/data/ApiPermissionConfig.java @@ -49,6 +49,10 @@ import static com.hedera.hapi.node.base.HederaFunctionality.GET_ACCOUNT_DETAILS; import static com.hedera.hapi.node.base.HederaFunctionality.GET_VERSION_INFO; import static com.hedera.hapi.node.base.HederaFunctionality.NETWORK_GET_EXECUTION_TIME; +import static com.hedera.hapi.node.base.HederaFunctionality.NODE_CREATE; +import static com.hedera.hapi.node.base.HederaFunctionality.NODE_DELETE; +import static com.hedera.hapi.node.base.HederaFunctionality.NODE_GET_INFO; +import static com.hedera.hapi.node.base.HederaFunctionality.NODE_UPDATE; import static com.hedera.hapi.node.base.HederaFunctionality.SCHEDULE_CREATE; import static com.hedera.hapi.node.base.HederaFunctionality.SCHEDULE_DELETE; import static com.hedera.hapi.node.base.HederaFunctionality.SCHEDULE_GET_INFO; @@ -172,6 +176,11 @@ * @param freeze the permission for {@link HederaFunctionality#FREEZE} functionality * @param getAccountDetails the permission for {@link HederaFunctionality#GET_ACCOUNT_DETAILS} functionality * @param tokenUpdateNfts the permission for {@link HederaFunctionality#TOKEN_UPDATE_NFTS} functionality + * + * @param createNode the permission for {@link HederaFunctionality#NODE_CREATE} functionality + * @param updateNode the permission for {@link HederaFunctionality#NODE_UPDATE} functionality + * @param deleteNode the permission for {@link HederaFunctionality#NODE_DELETE} functionality + * @param getNodeInfo the permission for {@link HederaFunctionality#NODE_GET_INFO} functionality */ @ConfigData public record ApiPermissionConfig( @@ -237,7 +246,11 @@ public record ApiPermissionConfig( @ConfigProperty(defaultValue = "2-60") PermissionedAccountsRange systemUndelete, @ConfigProperty(defaultValue = "2-58") PermissionedAccountsRange freeze, @ConfigProperty(defaultValue = "2-50") PermissionedAccountsRange getAccountDetails, - @ConfigProperty(defaultValue = "0-*") PermissionedAccountsRange tokenUpdateNfts) { + @ConfigProperty(defaultValue = "0-*") PermissionedAccountsRange tokenUpdateNfts, + @ConfigProperty(defaultValue = "0-*") PermissionedAccountsRange createNode, + @ConfigProperty(defaultValue = "0-*") PermissionedAccountsRange updateNode, + @ConfigProperty(defaultValue = "0-*") PermissionedAccountsRange deleteNode, + @ConfigProperty(defaultValue = "0-*") PermissionedAccountsRange getNodeInfo) { private static final EnumMap> permissionKeys = new EnumMap<>(HederaFunctionality.class); @@ -308,6 +321,10 @@ public record ApiPermissionConfig( permissionKeys.put(TOKEN_GET_ACCOUNT_NFT_INFOS, c -> c.tokenGetAccountNftInfos); permissionKeys.put(TOKEN_FEE_SCHEDULE_UPDATE, c -> c.tokenFeeScheduleUpdate); permissionKeys.put(UTIL_PRNG, c -> c.utilPrng); + permissionKeys.put(NODE_CREATE, c -> c.createNode); + permissionKeys.put(NODE_UPDATE, c -> c.updateNode); + permissionKeys.put(NODE_DELETE, c -> c.deleteNode); + permissionKeys.put(NODE_GET_INFO, c -> c.getNodeInfo); } /** diff --git a/hedera-node/hedera-config/src/main/java/com/hedera/node/config/data/ContractsConfig.java b/hedera-node/hedera-config/src/main/java/com/hedera/node/config/data/ContractsConfig.java index 904b30bc62a2..31f4831a74ef 100644 --- a/hedera-node/hedera-config/src/main/java/com/hedera/node/config/data/ContractsConfig.java +++ b/hedera-node/hedera-config/src/main/java/com/hedera/node/config/data/ContractsConfig.java @@ -75,6 +75,8 @@ public record ContractsConfig( boolean precompileAtomicCryptoTransferEnabled, @ConfigProperty(value = "precompile.hrcFacade.associate.enabled", defaultValue = "true") @NetworkProperty boolean precompileHrcFacadeAssociateEnabled, + @ConfigProperty(value = "systemContract.accountService.enabled", defaultValue = "false") @NetworkProperty + boolean systemContractAccountServiceEnabled, @ConfigProperty(value = "evm.version.dynamic", defaultValue = "false") @NetworkProperty boolean evmVersionDynamic, @ConfigProperty(value = "evm.allowCallsToNonContractAccounts", defaultValue = "true") @NetworkProperty diff --git a/hedera-node/hedera-config/src/main/java/com/hedera/node/config/data/NetworkAdminConfig.java b/hedera-node/hedera-config/src/main/java/com/hedera/node/config/data/NetworkAdminConfig.java index 622d8ecd3613..7e6d6d6afff0 100644 --- a/hedera-node/hedera-config/src/main/java/com/hedera/node/config/data/NetworkAdminConfig.java +++ b/hedera-node/hedera-config/src/main/java/com/hedera/node/config/data/NetworkAdminConfig.java @@ -28,5 +28,4 @@ */ @ConfigData("networkAdmin") public record NetworkAdminConfig( - @ConfigProperty(defaultValue = "/opt/hgcapp/services-hedera/HapiApp2.0/data/upgrade/current") @NodeProperty - String upgradeArtifactsPath) {} + @ConfigProperty(defaultValue = "data/upgrade/current") @NodeProperty String upgradeArtifactsPath) {} diff --git a/hedera-node/hedera-consensus-service-impl/build.gradle.kts b/hedera-node/hedera-consensus-service-impl/build.gradle.kts index f93506803f9b..6d779c8b1bab 100644 --- a/hedera-node/hedera-consensus-service-impl/build.gradle.kts +++ b/hedera-node/hedera-consensus-service-impl/build.gradle.kts @@ -14,7 +14,10 @@ * limitations under the License. */ -plugins { id("com.hedera.hashgraph.conventions") } +plugins { + id("com.hedera.gradle.services") + id("com.hedera.gradle.services-publish") +} description = "Default Hedera Consensus Service Implementation" @@ -24,6 +27,7 @@ testModuleInfo { requires("com.hedera.node.app") requires("com.hedera.node.app.service.consensus.impl") requires("com.hedera.node.app.service.mono.test.fixtures") + requires("com.swirlds.platform.core.test.fixtures") requires("com.hedera.node.app.service.token") requires("com.hedera.node.app.service.token.impl") requires("com.hedera.node.app.spi.test.fixtures") diff --git a/hedera-node/hedera-consensus-service-impl/src/main/java/com/hedera/node/app/service/consensus/impl/ConsensusServiceImpl.java b/hedera-node/hedera-consensus-service-impl/src/main/java/com/hedera/node/app/service/consensus/impl/ConsensusServiceImpl.java index 6bc595d7c545..ba318ee8071d 100644 --- a/hedera-node/hedera-consensus-service-impl/src/main/java/com/hedera/node/app/service/consensus/impl/ConsensusServiceImpl.java +++ b/hedera-node/hedera-consensus-service-impl/src/main/java/com/hedera/node/app/service/consensus/impl/ConsensusServiceImpl.java @@ -21,13 +21,14 @@ import com.hedera.node.app.service.consensus.impl.schemas.InitialModServiceConsensusSchema; import com.hedera.node.app.service.mono.state.merkle.MerkleTopic; import com.hedera.node.app.service.mono.utils.EntityNum; +import com.hedera.node.app.spi.Service; import com.hedera.node.app.spi.state.SchemaRegistry; import com.swirlds.merkle.map.MerkleMap; import edu.umd.cs.findbugs.annotations.NonNull; import edu.umd.cs.findbugs.annotations.Nullable; /** - * Standard implementation of the {@link ConsensusService} {@link com.hedera.node.app.spi.Service}. + * Standard implementation of the {@link ConsensusService} {@link Service}. */ public final class ConsensusServiceImpl implements ConsensusService { public static final long RUNNING_HASH_VERSION = 3L; diff --git a/hedera-node/hedera-consensus-service-impl/src/main/java/com/hedera/node/app/service/consensus/impl/ReadableTopicStoreImpl.java b/hedera-node/hedera-consensus-service-impl/src/main/java/com/hedera/node/app/service/consensus/impl/ReadableTopicStoreImpl.java index c9695d6a8f17..8f6ff2f9175c 100644 --- a/hedera-node/hedera-consensus-service-impl/src/main/java/com/hedera/node/app/service/consensus/impl/ReadableTopicStoreImpl.java +++ b/hedera-node/hedera-consensus-service-impl/src/main/java/com/hedera/node/app/service/consensus/impl/ReadableTopicStoreImpl.java @@ -22,8 +22,8 @@ import com.hedera.hapi.node.base.TopicID; import com.hedera.hapi.node.state.consensus.Topic; import com.hedera.node.app.service.consensus.ReadableTopicStore; -import com.hedera.node.app.spi.state.ReadableKVState; -import com.hedera.node.app.spi.state.ReadableStates; +import com.swirlds.state.spi.ReadableKVState; +import com.swirlds.state.spi.ReadableStates; import edu.umd.cs.findbugs.annotations.NonNull; import edu.umd.cs.findbugs.annotations.Nullable; diff --git a/hedera-node/hedera-consensus-service-impl/src/main/java/com/hedera/node/app/service/consensus/impl/WritableTopicStore.java b/hedera-node/hedera-consensus-service-impl/src/main/java/com/hedera/node/app/service/consensus/impl/WritableTopicStore.java index 1365d4a406e9..0d055103765a 100644 --- a/hedera-node/hedera-consensus-service-impl/src/main/java/com/hedera/node/app/service/consensus/impl/WritableTopicStore.java +++ b/hedera-node/hedera-consensus-service-impl/src/main/java/com/hedera/node/app/service/consensus/impl/WritableTopicStore.java @@ -23,10 +23,10 @@ import com.hedera.node.app.service.mono.state.merkle.MerkleTopic; import com.hedera.node.app.spi.metrics.StoreMetricsService; import com.hedera.node.app.spi.metrics.StoreMetricsService.StoreType; -import com.hedera.node.app.spi.state.WritableKVState; -import com.hedera.node.app.spi.state.WritableStates; import com.hedera.node.config.data.TopicsConfig; import com.swirlds.config.api.Configuration; +import com.swirlds.state.spi.WritableKVState; +import com.swirlds.state.spi.WritableStates; import edu.umd.cs.findbugs.annotations.NonNull; import java.util.Set; diff --git a/hedera-node/hedera-consensus-service-impl/src/main/java/com/hedera/node/app/service/consensus/impl/codecs/ConsensusServiceStateTranslator.java b/hedera-node/hedera-consensus-service-impl/src/main/java/com/hedera/node/app/service/consensus/impl/codecs/ConsensusServiceStateTranslator.java index 51aab755ae88..acf9c39c2c58 100644 --- a/hedera-node/hedera-consensus-service-impl/src/main/java/com/hedera/node/app/service/consensus/impl/codecs/ConsensusServiceStateTranslator.java +++ b/hedera-node/hedera-consensus-service-impl/src/main/java/com/hedera/node/app/service/consensus/impl/codecs/ConsensusServiceStateTranslator.java @@ -25,8 +25,8 @@ import com.hedera.node.app.service.mono.legacy.core.jproto.JKey; import com.hedera.node.app.service.mono.pbj.PbjConverter; import com.hedera.node.app.service.mono.utils.EntityNum; -import com.hedera.node.app.spi.state.WritableKVState; import com.hedera.pbj.runtime.io.buffer.Bytes; +import com.swirlds.state.spi.WritableKVState; import edu.umd.cs.findbugs.annotations.NonNull; import java.util.function.BiConsumer; diff --git a/hedera-node/hedera-consensus-service-impl/src/main/java/com/hedera/node/app/service/consensus/impl/schemas/InitialModServiceConsensusSchema.java b/hedera-node/hedera-consensus-service-impl/src/main/java/com/hedera/node/app/service/consensus/impl/schemas/InitialModServiceConsensusSchema.java index be4503c83614..02629a4ea8a4 100644 --- a/hedera-node/hedera-consensus-service-impl/src/main/java/com/hedera/node/app/service/consensus/impl/schemas/InitialModServiceConsensusSchema.java +++ b/hedera-node/hedera-consensus-service-impl/src/main/java/com/hedera/node/app/service/consensus/impl/schemas/InitialModServiceConsensusSchema.java @@ -28,8 +28,8 @@ import com.hedera.node.app.spi.state.MigrationContext; import com.hedera.node.app.spi.state.Schema; import com.hedera.node.app.spi.state.StateDefinition; -import com.hedera.node.app.spi.state.WritableKVStateBase; import com.swirlds.merkle.map.MerkleMap; +import com.swirlds.platform.state.spi.WritableKVStateBase; import edu.umd.cs.findbugs.annotations.NonNull; import edu.umd.cs.findbugs.annotations.Nullable; import java.util.Set; diff --git a/hedera-node/hedera-consensus-service-impl/src/main/java/module-info.java b/hedera-node/hedera-consensus-service-impl/src/main/java/module-info.java index 56f6e7282f56..4e46270a4d94 100644 --- a/hedera-node/hedera-consensus-service-impl/src/main/java/module-info.java +++ b/hedera-node/hedera-consensus-service-impl/src/main/java/module-info.java @@ -8,10 +8,12 @@ requires transitive com.hedera.pbj.runtime; requires transitive com.swirlds.config.api; requires transitive com.swirlds.merkle; + requires transitive com.swirlds.state.api; requires transitive dagger; requires transitive javax.inject; requires com.hedera.node.app.hapi.utils; requires com.hedera.node.config; + requires com.swirlds.platform.core; requires org.apache.logging.log4j; requires static com.github.spotbugs.annotations; requires static java.compiler; // javax.annotation.processing.Generated diff --git a/hedera-node/hedera-consensus-service-impl/src/test/java/com/hedera/node/app/service/consensus/impl/test/ConsensusServiceImplTest.java b/hedera-node/hedera-consensus-service-impl/src/test/java/com/hedera/node/app/service/consensus/impl/test/ConsensusServiceImplTest.java new file mode 100644 index 000000000000..4cdc513df08d --- /dev/null +++ b/hedera-node/hedera-consensus-service-impl/src/test/java/com/hedera/node/app/service/consensus/impl/test/ConsensusServiceImplTest.java @@ -0,0 +1,77 @@ +/* + * Copyright (C) 2024 Hedera Hashgraph, LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.hedera.node.app.service.consensus.impl.test; + +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.mockito.ArgumentMatchers.notNull; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; + +import com.hedera.hapi.node.base.SemanticVersion; +import com.hedera.node.app.service.consensus.impl.ConsensusServiceImpl; +import com.hedera.node.app.spi.state.SchemaRegistry; +import com.swirlds.merkle.map.MerkleMap; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +public class ConsensusServiceImplTest { + private ConsensusServiceImpl subject; + + @BeforeEach + void setUp() { + subject = new ConsensusServiceImpl(); + } + + @SuppressWarnings("DataFlowIssue") + @Test + void registerSchemasNullArgsThrow() { + assertThatThrownBy(() -> subject.registerSchemas(null, SemanticVersion.DEFAULT)) + .isInstanceOf(NullPointerException.class); + + assertThatThrownBy(() -> subject.registerSchemas(mock(SchemaRegistry.class), null)) + .isInstanceOf(NullPointerException.class); + } + + @Test + void registerSchemasRegistersTopicSchema() { + final var schemaRegistry = mock(SchemaRegistry.class); + + subject.registerSchemas(schemaRegistry, SemanticVersion.DEFAULT); + verify(schemaRegistry).register(notNull()); + } + + @SuppressWarnings("unchecked") + @Test + void triesToSetStateWithoutRegisteredTopicSchema() { + final var mMap = mock(MerkleMap.class); + + assertThatThrownBy(() -> subject.setFromState(mMap)).isInstanceOf(NullPointerException.class); + } + + @SuppressWarnings("unchecked") + @Test + void stateSetterDontThrow() { + final var registry = mock(SchemaRegistry.class); + // registerSchemas(...) is required to instantiate the token schema + subject.registerSchemas(registry, SemanticVersion.DEFAULT); + + final var mMap = mock(MerkleMap.class); + + subject.setFromState(null); + subject.setFromState(mMap); + } +} diff --git a/hedera-node/hedera-consensus-service-impl/src/test/java/com/hedera/node/app/service/consensus/impl/test/ReadableTopicStoreImplTest.java b/hedera-node/hedera-consensus-service-impl/src/test/java/com/hedera/node/app/service/consensus/impl/test/ReadableTopicStoreImplTest.java index 8724cb2522a8..af51024cf60a 100644 --- a/hedera-node/hedera-consensus-service-impl/src/test/java/com/hedera/node/app/service/consensus/impl/test/ReadableTopicStoreImplTest.java +++ b/hedera-node/hedera-consensus-service-impl/src/test/java/com/hedera/node/app/service/consensus/impl/test/ReadableTopicStoreImplTest.java @@ -32,7 +32,7 @@ import com.hedera.node.app.service.consensus.ReadableTopicStore; import com.hedera.node.app.service.consensus.impl.ReadableTopicStoreImpl; import com.hedera.node.app.service.consensus.impl.test.handlers.ConsensusTestBase; -import com.hedera.node.app.spi.fixtures.state.MapReadableKVState; +import com.swirlds.platform.test.fixtures.state.MapReadableKVState; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; diff --git a/hedera-node/hedera-consensus-service-impl/src/test/java/com/hedera/node/app/service/consensus/impl/test/codecs/ConsensusServiceStateTranslatorTest.java b/hedera-node/hedera-consensus-service-impl/src/test/java/com/hedera/node/app/service/consensus/impl/test/codecs/ConsensusServiceStateTranslatorTest.java index 7b68ffe41fba..5c84081e968b 100644 --- a/hedera-node/hedera-consensus-service-impl/src/test/java/com/hedera/node/app/service/consensus/impl/test/codecs/ConsensusServiceStateTranslatorTest.java +++ b/hedera-node/hedera-consensus-service-impl/src/test/java/com/hedera/node/app/service/consensus/impl/test/codecs/ConsensusServiceStateTranslatorTest.java @@ -27,9 +27,9 @@ import com.hedera.node.app.service.consensus.impl.test.handlers.ConsensusTestBase; import com.hedera.node.app.service.mono.state.merkle.MerkleTopic; import com.hedera.node.app.service.mono.utils.EntityNum; -import com.hedera.node.app.spi.fixtures.state.MapWritableKVState; -import com.hedera.node.app.spi.state.WritableKVState; import com.swirlds.merkle.map.MerkleMap; +import com.swirlds.platform.test.fixtures.state.MapWritableKVState; +import com.swirlds.state.spi.WritableKVState; import edu.umd.cs.findbugs.annotations.NonNull; import java.util.ArrayList; import java.util.List; diff --git a/hedera-node/hedera-consensus-service-impl/src/test/java/com/hedera/node/app/service/consensus/impl/test/handlers/AdapterUtils.java b/hedera-node/hedera-consensus-service-impl/src/test/java/com/hedera/node/app/service/consensus/impl/test/handlers/AdapterUtils.java index a07fcfde331a..ee506828e20a 100644 --- a/hedera-node/hedera-consensus-service-impl/src/test/java/com/hedera/node/app/service/consensus/impl/test/handlers/AdapterUtils.java +++ b/hedera-node/hedera-consensus-service-impl/src/test/java/com/hedera/node/app/service/consensus/impl/test/handlers/AdapterUtils.java @@ -76,12 +76,12 @@ import com.hedera.hapi.node.state.token.AccountCryptoAllowance; import com.hedera.hapi.node.state.token.AccountFungibleTokenAllowance; import com.hedera.node.app.service.token.ReadableAccountStore; -import com.hedera.node.app.spi.fixtures.state.MapReadableKVState; -import com.hedera.node.app.spi.state.ReadableKVState; -import com.hedera.node.app.spi.state.ReadableStates; import com.hedera.pbj.runtime.OneOf; import com.hedera.pbj.runtime.io.buffer.Bytes; import com.hedera.test.utils.TestFixturesKeyLookup; +import com.swirlds.platform.test.fixtures.state.MapReadableKVState; +import com.swirlds.state.spi.ReadableKVState; +import com.swirlds.state.spi.ReadableStates; import java.util.HashMap; import java.util.List; import java.util.Map; diff --git a/hedera-node/hedera-consensus-service-impl/src/test/java/com/hedera/node/app/service/consensus/impl/test/handlers/ConsensusGetTopicInfoTest.java b/hedera-node/hedera-consensus-service-impl/src/test/java/com/hedera/node/app/service/consensus/impl/test/handlers/ConsensusGetTopicInfoTest.java index 82ad132be918..8eaa9ac257fd 100644 --- a/hedera-node/hedera-consensus-service-impl/src/test/java/com/hedera/node/app/service/consensus/impl/test/handlers/ConsensusGetTopicInfoTest.java +++ b/hedera-node/hedera-consensus-service-impl/src/test/java/com/hedera/node/app/service/consensus/impl/test/handlers/ConsensusGetTopicInfoTest.java @@ -44,12 +44,12 @@ import com.hedera.node.app.service.consensus.ReadableTopicStore; import com.hedera.node.app.service.consensus.impl.ReadableTopicStoreImpl; import com.hedera.node.app.service.consensus.impl.handlers.ConsensusGetTopicInfoHandler; -import com.hedera.node.app.spi.fixtures.state.MapReadableKVState; import com.hedera.node.app.spi.workflows.PreCheckException; import com.hedera.node.app.spi.workflows.QueryContext; import com.hedera.node.config.converter.BytesConverter; import com.hedera.node.config.testfixtures.HederaTestConfigBuilder; import com.hedera.pbj.runtime.io.buffer.Bytes; +import com.swirlds.platform.test.fixtures.state.MapReadableKVState; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; diff --git a/hedera-node/hedera-consensus-service-impl/src/test/java/com/hedera/node/app/service/consensus/impl/test/handlers/ConsensusHandlersTest.java b/hedera-node/hedera-consensus-service-impl/src/test/java/com/hedera/node/app/service/consensus/impl/test/handlers/ConsensusHandlersTest.java new file mode 100644 index 000000000000..33e7ca5250a8 --- /dev/null +++ b/hedera-node/hedera-consensus-service-impl/src/test/java/com/hedera/node/app/service/consensus/impl/test/handlers/ConsensusHandlersTest.java @@ -0,0 +1,95 @@ +/* + * Copyright (C) 2024 Hedera Hashgraph, LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.hedera.node.app.service.consensus.impl.test.handlers; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.mockito.Mockito.mock; + +import com.hedera.node.app.service.consensus.impl.handlers.ConsensusCreateTopicHandler; +import com.hedera.node.app.service.consensus.impl.handlers.ConsensusDeleteTopicHandler; +import com.hedera.node.app.service.consensus.impl.handlers.ConsensusGetTopicInfoHandler; +import com.hedera.node.app.service.consensus.impl.handlers.ConsensusHandlers; +import com.hedera.node.app.service.consensus.impl.handlers.ConsensusSubmitMessageHandler; +import com.hedera.node.app.service.consensus.impl.handlers.ConsensusUpdateTopicHandler; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +class ConsensusHandlersTest { + private ConsensusCreateTopicHandler consensusCreateTopicHandler; + private ConsensusDeleteTopicHandler consensusDeleteTopicHandler; + private ConsensusGetTopicInfoHandler consensusGetTopicInfoHandler; + private ConsensusSubmitMessageHandler consensusSubmitMessageHandler; + private ConsensusUpdateTopicHandler consensusUpdateTopicHandler; + + private ConsensusHandlers consensusHandlers; + + @BeforeEach + public void setUp() { + consensusCreateTopicHandler = mock(ConsensusCreateTopicHandler.class); + consensusDeleteTopicHandler = mock(ConsensusDeleteTopicHandler.class); + consensusGetTopicInfoHandler = mock(ConsensusGetTopicInfoHandler.class); + consensusSubmitMessageHandler = mock(ConsensusSubmitMessageHandler.class); + consensusUpdateTopicHandler = mock(ConsensusUpdateTopicHandler.class); + + consensusHandlers = new ConsensusHandlers( + consensusCreateTopicHandler, + consensusDeleteTopicHandler, + consensusGetTopicInfoHandler, + consensusSubmitMessageHandler, + consensusUpdateTopicHandler); + } + + @Test + void consensusCreateTopicHandlerReturnsCorrectInstance() { + assertEquals( + consensusCreateTopicHandler, + consensusHandlers.consensusCreateTopicHandler(), + "consensusCreateTopicHandler does not return correct instance"); + } + + @Test + void consensusDeleteTopicHandlerReturnsCorrectInstance() { + assertEquals( + consensusDeleteTopicHandler, + consensusHandlers.consensusDeleteTopicHandler(), + "consensusDeleteTopicHandler does not return correct instance"); + } + + @Test + void consensusGetTopicInfoHandlerReturnsCorrectInstance() { + assertEquals( + consensusGetTopicInfoHandler, + consensusHandlers.consensusGetTopicInfoHandler(), + "consensusGetTopicInfoHandler does not return correct instance"); + } + + @Test + void consensusSubmitMessageHandlerReturnsCorrectInstance() { + assertEquals( + consensusSubmitMessageHandler, + consensusHandlers.consensusSubmitMessageHandler(), + "consensusSubmitMessageHandler does not return correct instance"); + } + + @Test + void consensusUpdateTopicHandlerReturnsCorrectInstance() { + assertEquals( + consensusUpdateTopicHandler, + consensusHandlers.consensusUpdateTopicHandler(), + "consensusUpdateTopicHandler does not return correct instance"); + } +} diff --git a/hedera-node/hedera-consensus-service-impl/src/test/java/com/hedera/node/app/service/consensus/impl/test/handlers/ConsensusSubmitMessageTest.java b/hedera-node/hedera-consensus-service-impl/src/test/java/com/hedera/node/app/service/consensus/impl/test/handlers/ConsensusSubmitMessageTest.java index 50acf4b0ac2b..73e12bf828f0 100644 --- a/hedera-node/hedera-consensus-service-impl/src/test/java/com/hedera/node/app/service/consensus/impl/test/handlers/ConsensusSubmitMessageTest.java +++ b/hedera-node/hedera-consensus-service-impl/src/test/java/com/hedera/node/app/service/consensus/impl/test/handlers/ConsensusSubmitMessageTest.java @@ -32,6 +32,8 @@ import static org.junit.jupiter.api.Assertions.assertThrows; import static org.mockito.Answers.RETURNS_SELF; import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyLong; +import static org.mockito.ArgumentMatchers.notNull; import static org.mockito.BDDMockito.given; import static org.mockito.Mock.Strictness.LENIENT; import static org.mockito.Mockito.lenient; @@ -57,6 +59,7 @@ import com.hedera.node.app.service.token.ReadableAccountStore; import com.hedera.node.app.spi.fees.FeeAccumulator; import com.hedera.node.app.spi.fees.FeeCalculator; +import com.hedera.node.app.spi.fees.FeeContext; import com.hedera.node.app.spi.fees.Fees; import com.hedera.node.app.spi.fixtures.workflows.FakePreHandleContext; import com.hedera.node.app.spi.metrics.StoreMetricsService; @@ -66,6 +69,9 @@ import com.hedera.node.config.testfixtures.HederaTestConfigBuilder; import com.hedera.pbj.runtime.io.buffer.Bytes; import com.hedera.test.utils.TxnUtils; +import edu.umd.cs.findbugs.annotations.NonNull; +import edu.umd.cs.findbugs.annotations.Nullable; +import java.io.IOException; import java.time.Instant; import java.util.Arrays; import org.junit.jupiter.api.BeforeEach; @@ -136,6 +142,14 @@ void failsIfMessageIsEmpty() { assertThrowsPreCheck(() -> subject.pureChecks(txn), INVALID_TOPIC_MESSAGE); } + @Test + @DisplayName("pureChecks works as expected") + void pureCheckWorksAsExpexcted() { + givenValidTopic(); + final var txn = newDefaultSubmitMessageTxn(topicEntityNum); + assertDoesNotThrow(() -> subject.pureChecks(txn)); + } + @Test @DisplayName("Topic submission key sig required") void submissionKeySigRequired() throws PreCheckException { @@ -200,6 +214,28 @@ void handleWorksAsExpected() { expectedTopic.runningHash().toString()); } + @Test + @DisplayName("Handle throws IOException") + void handleThrowsIOException() { + givenValidTopic(); + subject = new ConsensusSubmitMessageHandler() { + @Override + public Topic updateRunningHashAndSequenceNumber( + @NonNull final TransactionBody txn, @NonNull final Topic topic, @Nullable Instant consensusNow) + throws IOException { + throw new IOException(); + } + }; + + final var txn = newSubmitMessageTxn(topicEntityNum, ""); + given(handleContext.body()).willReturn(txn); + + given(handleContext.consensusNow()).willReturn(consensusTimestamp); + + final var msg = assertThrows(HandleException.class, () -> subject.handle(handleContext)); + assertThat(msg.getStatus()).isEqualTo(ResponseCodeEnum.INVALID_TRANSACTION); + } + @Test @DisplayName("Handle works as expected if Consensus time is null") void handleWorksAsExpectedIfConsensusTimeIsNull() { @@ -304,6 +340,30 @@ void failsIfChunkTxnPayerIsNotInitialID() { assertEquals(ResponseCodeEnum.INVALID_CHUNK_TRANSACTION_ID, msg.getStatus()); } + @Test + void calculateFeesHappyPath() { + givenValidTopic(); + final var chunkTxnId = + TransactionID.newBuilder().accountID(anotherPayer).build(); + final var txn = newSubmitMessageTxnWithChunksAndPayer(topicEntityNum, 1, 2, chunkTxnId); + final var feeCtx = mock(FeeContext.class); + readableStore = mock(ReadableTopicStore.class); + given(feeCtx.body()).willReturn(txn); + + final var feeCalc = mock(FeeCalculator.class); + given(feeCtx.feeCalculator(notNull())).willReturn(feeCalc); + given(feeCalc.addBytesPerTransaction(anyLong())).willReturn(feeCalc); + given(feeCalc.addNetworkRamByteSeconds(anyLong())).willReturn(feeCalc); + // The fees wouldn't be free in this scenario, but we don't care about the actual return + // value here since we're using a mock calculator + given(feeCalc.calculate()).willReturn(Fees.FREE); + + subject.calculateFees(feeCtx); + + verify(feeCalc).addBytesPerTransaction(28); + verify(feeCalc).addNetworkRamByteSeconds(10080); + } + /* ----------------- Helper Methods ------------------- */ private Key mockPayerLookup() { diff --git a/hedera-node/hedera-consensus-service-impl/src/test/java/com/hedera/node/app/service/consensus/impl/test/handlers/ConsensusTestBase.java b/hedera-node/hedera-consensus-service-impl/src/test/java/com/hedera/node/app/service/consensus/impl/test/handlers/ConsensusTestBase.java index 14c22ce26cdd..ff55a0307420 100644 --- a/hedera-node/hedera-consensus-service-impl/src/test/java/com/hedera/node/app/service/consensus/impl/test/handlers/ConsensusTestBase.java +++ b/hedera-node/hedera-consensus-service-impl/src/test/java/com/hedera/node/app/service/consensus/impl/test/handlers/ConsensusTestBase.java @@ -34,14 +34,14 @@ import com.hedera.node.app.service.consensus.impl.ReadableTopicStoreImpl; import com.hedera.node.app.service.consensus.impl.WritableTopicStore; import com.hedera.node.app.service.mono.utils.EntityNum; -import com.hedera.node.app.spi.fixtures.state.MapReadableKVState; -import com.hedera.node.app.spi.fixtures.state.MapWritableKVState; import com.hedera.node.app.spi.metrics.StoreMetricsService; -import com.hedera.node.app.spi.state.ReadableStates; -import com.hedera.node.app.spi.state.WritableStates; import com.hedera.node.app.spi.workflows.HandleContext; import com.hedera.node.config.testfixtures.HederaTestConfigBuilder; import com.hedera.pbj.runtime.io.buffer.Bytes; +import com.swirlds.platform.test.fixtures.state.MapReadableKVState; +import com.swirlds.platform.test.fixtures.state.MapWritableKVState; +import com.swirlds.state.spi.ReadableStates; +import com.swirlds.state.spi.WritableStates; import edu.umd.cs.findbugs.annotations.NonNull; import java.time.Instant; import org.junit.jupiter.api.BeforeEach; diff --git a/hedera-node/hedera-consensus-service/build.gradle.kts b/hedera-node/hedera-consensus-service/build.gradle.kts index 3433551ff572..130bad6fb160 100644 --- a/hedera-node/hedera-consensus-service/build.gradle.kts +++ b/hedera-node/hedera-consensus-service/build.gradle.kts @@ -14,6 +14,9 @@ * limitations under the License. */ -plugins { id("com.hedera.hashgraph.conventions") } +plugins { + id("com.hedera.gradle.services") + id("com.hedera.gradle.services-publish") +} description = "Hedera Consensus Service API" diff --git a/hedera-node/hedera-evm-impl/build.gradle.kts b/hedera-node/hedera-evm-impl/build.gradle.kts index abe3ac6d1b47..4da1f77ee132 100644 --- a/hedera-node/hedera-evm-impl/build.gradle.kts +++ b/hedera-node/hedera-evm-impl/build.gradle.kts @@ -14,8 +14,9 @@ * limitations under the License. */ -plugins { id("com.hedera.hashgraph.conventions") } - -group = "com.hedera.evm" +plugins { + id("com.hedera.gradle.evm") + id("com.hedera.gradle.evm-publish") +} description = "Hedera EVM - Implementation" diff --git a/hedera-node/hedera-evm-impl/src/main/java/com/hedera/node/app/service/evm/impl/PlaceholderImplementation.java b/hedera-node/hedera-evm-impl/src/main/java/com/hedera/node/app/service/evm/impl/PlaceholderImplementation.java index 406132901559..9c6275742f47 100644 --- a/hedera-node/hedera-evm-impl/src/main/java/com/hedera/node/app/service/evm/impl/PlaceholderImplementation.java +++ b/hedera-node/hedera-evm-impl/src/main/java/com/hedera/node/app/service/evm/impl/PlaceholderImplementation.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.hedera.node.app.service.evm.impl; +package com.hedera.evm.impl; import com.hedera.node.app.service.evm.PlaceholderApi; diff --git a/hedera-node/hedera-evm-impl/src/main/java/module-info.java b/hedera-node/hedera-evm-impl/src/main/java/module-info.java index c3b709ef70b6..1f2772585daf 100644 --- a/hedera-node/hedera-evm-impl/src/main/java/module-info.java +++ b/hedera-node/hedera-evm-impl/src/main/java/module-info.java @@ -1,3 +1,3 @@ -module com.hedera.node.app.service.evm.impl { - requires transitive com.hedera.node.app.service.evm; +module com.hedera.evm.impl { + requires transitive com.hedera.evm; } diff --git a/hedera-node/hedera-evm/build.gradle.kts b/hedera-node/hedera-evm/build.gradle.kts index 8ddb81cac216..a18962a9afa4 100644 --- a/hedera-node/hedera-evm/build.gradle.kts +++ b/hedera-node/hedera-evm/build.gradle.kts @@ -15,12 +15,10 @@ */ plugins { - id("com.hedera.hashgraph.conventions") - id("com.hedera.hashgraph.evm-maven-publish") + id("com.hedera.gradle.evm") + id("com.hedera.gradle.evm-publish") } -group = "com.hedera.evm" - description = "Hedera EVM - API" mainModuleInfo { annotationProcessor("dagger.compiler") } @@ -30,12 +28,3 @@ testModuleInfo { requires("org.mockito") requires("org.mockito.junit.jupiter") } - -publishing { - publications { - named("maven") { - groupId = "com.hedera.evm" - artifactId = "hedera-evm" - } - } -} diff --git a/hedera-node/hedera-evm/src/main/java/com/hedera/node/app/service/evm/contracts/execution/BlockMetaSource.java b/hedera-node/hedera-evm/src/main/java/com/hedera/node/app/service/evm/contracts/execution/BlockMetaSource.java index 6d9f5fa8e7f4..b23070a35ab3 100644 --- a/hedera-node/hedera-evm/src/main/java/com/hedera/node/app/service/evm/contracts/execution/BlockMetaSource.java +++ b/hedera-node/hedera-evm/src/main/java/com/hedera/node/app/service/evm/contracts/execution/BlockMetaSource.java @@ -16,15 +16,13 @@ package com.hedera.node.app.service.evm.contracts.execution; -import com.swirlds.common.crypto.DigestType; -import com.swirlds.common.crypto.ImmutableHash; import org.apache.tuweni.bytes.Bytes32; import org.hyperledger.besu.datatypes.Hash; import org.hyperledger.besu.evm.frame.BlockValues; /** Provides block information to a {@link HederaEvmTxProcessor}. */ public interface BlockMetaSource { - Hash UNAVAILABLE_BLOCK_HASH = ethHashFrom(new ImmutableHash(new byte[DigestType.SHA_384.digestLength()])); + Hash UNAVAILABLE_BLOCK_HASH = org.hyperledger.besu.datatypes.Hash.wrap(Bytes32.wrap(new byte[32])); /** * Returns the hash of the given block number, or {@link BlockMetaSource#UNAVAILABLE_BLOCK_HASH} @@ -42,11 +40,4 @@ public interface BlockMetaSource { * @return the scoped block values */ BlockValues computeBlockValues(long gasLimit); - - static org.hyperledger.besu.datatypes.Hash ethHashFrom(final com.swirlds.common.crypto.Hash hash) { - final byte[] hashBytesToConvert = hash.getValue(); - final byte[] prefixBytes = new byte[32]; - System.arraycopy(hashBytesToConvert, 0, prefixBytes, 0, 32); - return org.hyperledger.besu.datatypes.Hash.wrap(Bytes32.wrap(prefixBytes)); - } } diff --git a/hedera-node/hedera-evm/src/main/java/com/hedera/node/app/service/evm/contracts/execution/HederaEvmTxProcessor.java b/hedera-node/hedera-evm/src/main/java/com/hedera/node/app/service/evm/contracts/execution/HederaEvmTxProcessor.java index b704410d1ba1..24c3173eb3e5 100644 --- a/hedera-node/hedera-evm/src/main/java/com/hedera/node/app/service/evm/contracts/execution/HederaEvmTxProcessor.java +++ b/hedera-node/hedera-evm/src/main/java/com/hedera/node/app/service/evm/contracts/execution/HederaEvmTxProcessor.java @@ -23,7 +23,9 @@ import com.hedera.node.app.service.evm.store.models.HederaEvmAccount; import com.hederahashgraph.api.proto.java.HederaFunctionality; import java.time.Instant; +import java.util.List; import java.util.Map; +import java.util.Optional; import javax.inject.Provider; import org.apache.tuweni.bytes.Bytes; import org.hyperledger.besu.datatypes.Address; @@ -136,6 +138,7 @@ public HederaEvmTransactionProcessingResult execute( .initialGas(gasAvailable) .originator(senderEvmAddress) .gasPrice(Wei.of(gasPrice)) + .blobGasPrice((Wei.ONE)) .sender(senderEvmAddress) .value(valueAsWei) .apparentValue(valueAsWei) @@ -144,6 +147,7 @@ public HederaEvmTransactionProcessingResult execute( .isStatic(isStatic) .miningBeneficiary(coinbase) .blockHashLookup(blockMetaSource::getBlockHash) + .versionedHashes(Optional.of(List.of())) .contextVariables(Map.of("HederaFunctionality", getFunctionType())); this.initialFrame = buildInitialFrame(commonInitialFrame, receiver, payload, value); @@ -218,7 +222,7 @@ protected void process(final MessageFrame frame, final OperationTracer operation executor.process(frame, operationTracer); } - private AbstractMessageProcessor getMessageProcessor(final MessageFrame.Type type) { + protected AbstractMessageProcessor getMessageProcessor(final MessageFrame.Type type) { return switch (type) { case MESSAGE_CALL -> messageCallProcessor; case CONTRACT_CREATION -> contractCreationProcessor; diff --git a/hedera-node/hedera-evm/src/main/java/module-info.java b/hedera-node/hedera-evm/src/main/java/module-info.java index 957f3263f537..841df004abef 100644 --- a/hedera-node/hedera-evm/src/main/java/module-info.java +++ b/hedera-node/hedera-evm/src/main/java/module-info.java @@ -1,10 +1,9 @@ /** Provides the core interfaces for the Hedera EVM implementation. */ -module com.hedera.node.app.service.evm { - requires transitive com.hedera.node.hapi; +module com.hedera.evm { requires transitive com.github.benmanes.caffeine; requires transitive com.google.protobuf; + requires transitive com.hedera.node.hapi; requires transitive com.hedera.pbj.runtime; - requires transitive com.swirlds.common; requires transitive dagger; requires transitive headlong; requires transitive javax.inject; @@ -16,6 +15,7 @@ requires transitive tuweni.units; requires com.google.common; requires com.sun.jna; + requires com.swirlds.common; requires org.bouncycastle.provider; requires static com.github.spotbugs.annotations; diff --git a/hedera-node/hedera-evm/src/test/java/com/hedera/node/app/service/evm/contracts/execution/HederaEvmTxProcessorTest.java b/hedera-node/hedera-evm/src/test/java/com/hedera/node/app/service/evm/contracts/execution/HederaEvmTxProcessorTest.java index f5d983131a54..bc519af7e3a9 100644 --- a/hedera-node/hedera-evm/src/test/java/com/hedera/node/app/service/evm/contracts/execution/HederaEvmTxProcessorTest.java +++ b/hedera-node/hedera-evm/src/test/java/com/hedera/node/app/service/evm/contracts/execution/HederaEvmTxProcessorTest.java @@ -36,7 +36,6 @@ import java.math.BigInteger; import java.util.List; import java.util.Map; -import java.util.Set; import javax.inject.Provider; import org.apache.tuweni.bytes.Bytes; import org.hyperledger.besu.datatypes.Address; @@ -50,7 +49,6 @@ import org.hyperledger.besu.evm.frame.MessageFrame; import org.hyperledger.besu.evm.gascalculator.GasCalculator; import org.hyperledger.besu.evm.internal.EvmConfiguration; -import org.hyperledger.besu.evm.operation.Operation; import org.hyperledger.besu.evm.operation.OperationRegistry; import org.hyperledger.besu.evm.precompile.PrecompileContractRegistry; import org.hyperledger.besu.evm.processor.ContractCreationProcessor; @@ -67,6 +65,7 @@ class HederaEvmTxProcessorTest { private static final int MAX_STACK_SIZE = 1024; private final String EVM_VERSION_0_30 = "v0.30"; private final String EVM_VERSION_0_34 = "v0.34"; + private final String EVM_VERSION_0_50 = "v0.50"; @Mock private PricesAndFeesProvider livePricesSource; @@ -80,9 +79,6 @@ class HederaEvmTxProcessorTest { @Mock private GasCalculator gasCalculator; - @Mock - private Set operations; - @Mock private HederaEvmWorldUpdater updater; @@ -111,19 +107,23 @@ class HederaEvmTxProcessorTest { private final long GAS_LIMIT = 300_000L; private final String INSUFFICIENT_GAS = "INSUFFICIENT_GAS"; - private MockHederaEvmTxProcessor evmTxProcessor; + private SpyHederaEvmTxProcessor evmTxProcessor; + private MessageCallProcessor msgCallProcessor050; private String mcpVersion; private String ccpVersion; @BeforeEach void setup() { + final var testChainId = BigInteger.ZERO; final var operationRegistry = new OperationRegistry(); - MainnetEVMs.registerLondonOperations(operationRegistry, gasCalculator, BigInteger.ZERO); - operations.forEach(operationRegistry::put); + final var operationRegistry50 = new OperationRegistry(); + MainnetEVMs.registerLondonOperations(operationRegistry, gasCalculator, testChainId); + MainnetEVMs.registerCancunOperations(operationRegistry, gasCalculator, testChainId); when(globalDynamicProperties.evmVersion()).thenReturn(EVM_VERSION_0_30); when(evmConfiguration.getJumpDestCacheWeightBytes()) .thenReturn(EvmConfiguration.DEFAULT.getJumpDestCacheWeightBytes()); final var evm30 = new EVM(operationRegistry, gasCalculator, evmConfiguration, EvmSpecVersion.LONDON); + final var evm50 = new EVM(operationRegistry, gasCalculator, evmConfiguration, EvmSpecVersion.CANCUN); final Map> mcps = Map.of( EVM_VERSION_0_30, () -> { @@ -134,6 +134,11 @@ void setup() { () -> { mcpVersion = EVM_VERSION_0_34; return new MessageCallProcessor(evm30, new PrecompileContractRegistry()); + }, + EVM_VERSION_0_50, + () -> { + mcpVersion = EVM_VERSION_0_50; + return msgCallProcessor050 = new MessageCallProcessor(evm50, new PrecompileContractRegistry()); }); final Map> ccps = Map.of( EVM_VERSION_0_30, @@ -146,9 +151,14 @@ void setup() { () -> { ccpVersion = EVM_VERSION_0_34; return new ContractCreationProcessor(gasCalculator, evm30, true, List.of(), 1); + }, + EVM_VERSION_0_50, + () -> { + ccpVersion = EVM_VERSION_0_50; + return new ContractCreationProcessor(gasCalculator, evm50, true, List.of(), 1); }); - evmTxProcessor = new MockHederaEvmTxProcessor( + evmTxProcessor = new SpyHederaEvmTxProcessor( worldState, livePricesSource, globalDynamicProperties, gasCalculator, mcps, ccps, blockMetaSource); final var hederaEvmOperationTracer = new DefaultHederaTracer(); @@ -166,6 +176,34 @@ void assertSuccessExecution() { assertTrue(result.isSuccessful()); } + @Test + void assertSuccessExecution050() { + // Force EVM 0.50 + when(globalDynamicProperties.dynamicEvmVersion()).thenReturn(true); + when(globalDynamicProperties.evmVersion()).thenReturn(EVM_VERSION_0_50); + givenValidMock(0L); + given(globalDynamicProperties.fundingAccountAddress()).willReturn(fundingAccount); + + final var spyOpTracer = new DefaultHederaTracer() { + public MessageFrame frame; + + @Override + public void init(final MessageFrame initialFrame) { + frame = initialFrame; + } + }; + evmTxProcessor.setOperationTracer(spyOpTracer); + evmTxProcessor.setupFields(Bytes.EMPTY, false); + final var result = + evmTxProcessor.execute(sender, receiver, 33_333L, 1234L, 1L, Bytes.EMPTY, true, mirrorReceiver); + assertTrue(result.isSuccessful()); + assertEquals(msgCallProcessor050, evmTxProcessor.getMessageCallProcessor(), "Confirming using EVM 0.50"); + + // Given EVM 0.50, check Cancun semantics + assertTrue(() -> spyOpTracer.frame.getVersionedHashes().orElseThrow().isEmpty()); + assertEquals(Wei.ONE, spyOpTracer.frame.getBlobGasPrice()); + } + @Test void missingCodeBecomesEmptyInInitialFrame() { final MessageFrame.Builder protoFrame = MessageFrame.builder() diff --git a/hedera-node/hedera-evm/src/test/java/com/hedera/node/app/service/evm/contracts/execution/MockHederaEvmTxProcessor.java b/hedera-node/hedera-evm/src/test/java/com/hedera/node/app/service/evm/contracts/execution/SpyHederaEvmTxProcessor.java similarity index 89% rename from hedera-node/hedera-evm/src/test/java/com/hedera/node/app/service/evm/contracts/execution/MockHederaEvmTxProcessor.java rename to hedera-node/hedera-evm/src/test/java/com/hedera/node/app/service/evm/contracts/execution/SpyHederaEvmTxProcessor.java index 6bc366151232..909d8b9f8546 100644 --- a/hedera-node/hedera-evm/src/test/java/com/hedera/node/app/service/evm/contracts/execution/MockHederaEvmTxProcessor.java +++ b/hedera-node/hedera-evm/src/test/java/com/hedera/node/app/service/evm/contracts/execution/SpyHederaEvmTxProcessor.java @@ -26,12 +26,13 @@ import org.hyperledger.besu.evm.frame.MessageFrame; import org.hyperledger.besu.evm.frame.MessageFrame.Builder; import org.hyperledger.besu.evm.gascalculator.GasCalculator; +import org.hyperledger.besu.evm.processor.AbstractMessageProcessor; import org.hyperledger.besu.evm.processor.ContractCreationProcessor; import org.hyperledger.besu.evm.processor.MessageCallProcessor; -public class MockHederaEvmTxProcessor extends HederaEvmTxProcessor { +public class SpyHederaEvmTxProcessor extends HederaEvmTxProcessor { - protected MockHederaEvmTxProcessor( + protected SpyHederaEvmTxProcessor( final HederaEvmMutableWorldState worldState, final PricesAndFeesProvider livePricesSource, final EvmProperties dynamicProperties, @@ -58,4 +59,8 @@ public MessageFrame buildInitialFrame( .code(CodeV0.EMPTY_CODE) .build(); } + + public AbstractMessageProcessor getMessageCallProcessor() { + return messageCallProcessor; + } } diff --git a/hedera-node/hedera-file-service-impl/build.gradle.kts b/hedera-node/hedera-file-service-impl/build.gradle.kts index 1b56cdeb5286..6015c3df22d6 100644 --- a/hedera-node/hedera-file-service-impl/build.gradle.kts +++ b/hedera-node/hedera-file-service-impl/build.gradle.kts @@ -14,7 +14,10 @@ * limitations under the License. */ -plugins { id("com.hedera.hashgraph.conventions") } +plugins { + id("com.hedera.gradle.services") + id("com.hedera.gradle.services-publish") +} description = "Default Hedera File Service Implementation" @@ -25,6 +28,7 @@ testModuleInfo { requires("com.hedera.node.app.service.file.impl") requires("com.hedera.node.app.service.mono.test.fixtures") requires("com.hedera.node.app.service.token") + requires("com.swirlds.platform.core.test.fixtures") requires("com.hedera.node.app.spi.test.fixtures") requires("com.hedera.node.config.test.fixtures") requires("com.google.protobuf") diff --git a/hedera-node/hedera-file-service-impl/src/main/java/com/hedera/node/app/service/file/impl/FileServiceImpl.java b/hedera-node/hedera-file-service-impl/src/main/java/com/hedera/node/app/service/file/impl/FileServiceImpl.java index 1c0dc0d5002a..cb462b6e5d70 100644 --- a/hedera-node/hedera-file-service-impl/src/main/java/com/hedera/node/app/service/file/impl/FileServiceImpl.java +++ b/hedera-node/hedera-file-service-impl/src/main/java/com/hedera/node/app/service/file/impl/FileServiceImpl.java @@ -22,6 +22,7 @@ import com.hedera.node.app.service.mono.state.adapters.VirtualMapLike; import com.hedera.node.app.service.mono.state.virtual.VirtualBlobKey; import com.hedera.node.app.service.mono.state.virtual.VirtualBlobValue; +import com.hedera.node.app.spi.Service; import com.hedera.node.app.spi.state.SchemaRegistry; import com.hedera.node.config.ConfigProvider; import edu.umd.cs.findbugs.annotations.NonNull; @@ -29,7 +30,7 @@ import java.util.function.Supplier; import javax.inject.Inject; -/** Standard implementation of the {@link FileService} {@link com.hedera.node.app.spi.Service}. */ +/** Standard implementation of the {@link FileService} {@link Service}. */ public final class FileServiceImpl implements FileService { public static final String BLOBS_KEY = "FILES"; public static final String UPGRADE_FILE_KEY = "UPGRADE_FILE"; @@ -37,11 +38,19 @@ public final class FileServiceImpl implements FileService { private final ConfigProvider configProvider; private InitialModFileGenesisSchema initialFileSchema; + /** + * Constructs a {@link FileServiceImpl} with the given {@link ConfigProvider}. + * @param configProvider the configuration provider + */ @Inject public FileServiceImpl(@NonNull final ConfigProvider configProvider) { this.configProvider = configProvider; } + /** + * Sets the {@link Supplier} for the {@link VirtualMapLike} that will be used to store file data. + * @param fss the file data storage supplier + */ public void setFs(@Nullable final Supplier> fss) { initialFileSchema.setFs(fss); } diff --git a/hedera-node/hedera-file-service-impl/src/main/java/com/hedera/node/app/service/file/impl/ReadableFileStoreImpl.java b/hedera-node/hedera-file-service-impl/src/main/java/com/hedera/node/app/service/file/impl/ReadableFileStoreImpl.java index 8fdb717ebc8e..80ca3f0a97d3 100644 --- a/hedera-node/hedera-file-service-impl/src/main/java/com/hedera/node/app/service/file/impl/ReadableFileStoreImpl.java +++ b/hedera-node/hedera-file-service-impl/src/main/java/com/hedera/node/app/service/file/impl/ReadableFileStoreImpl.java @@ -22,8 +22,8 @@ import com.hedera.hapi.node.state.file.File; import com.hedera.node.app.service.file.FileMetadata; import com.hedera.node.app.service.file.ReadableFileStore; -import com.hedera.node.app.spi.state.ReadableKVState; -import com.hedera.node.app.spi.state.ReadableStates; +import com.swirlds.state.spi.ReadableKVState; +import com.swirlds.state.spi.ReadableStates; import edu.umd.cs.findbugs.annotations.NonNull; import edu.umd.cs.findbugs.annotations.Nullable; import java.util.Objects; @@ -59,6 +59,12 @@ public ReadableFileStoreImpl(@NonNull final ReadableStates states) { return file == null ? null : FileStore.fileMetaFrom(file); } + /** + * Returns the file leaf for the given file id. + * + * @param id the file id + * @return the file for the given file id + */ public @Nullable File getFileLeaf(@NonNull FileID id) { return fileState.get(id); } diff --git a/hedera-node/hedera-file-service-impl/src/main/java/com/hedera/node/app/service/file/impl/ReadableUpgradeFileStoreImpl.java b/hedera-node/hedera-file-service-impl/src/main/java/com/hedera/node/app/service/file/impl/ReadableUpgradeFileStoreImpl.java index c85a8cbdd304..e17d868ab596 100644 --- a/hedera-node/hedera-file-service-impl/src/main/java/com/hedera/node/app/service/file/impl/ReadableUpgradeFileStoreImpl.java +++ b/hedera-node/hedera-file-service-impl/src/main/java/com/hedera/node/app/service/file/impl/ReadableUpgradeFileStoreImpl.java @@ -24,10 +24,10 @@ import com.hedera.hapi.node.state.file.File; import com.hedera.hapi.node.state.primitives.ProtoBytes; import com.hedera.node.app.service.file.ReadableUpgradeFileStore; -import com.hedera.node.app.spi.state.ReadableKVState; -import com.hedera.node.app.spi.state.ReadableQueueState; -import com.hedera.node.app.spi.state.ReadableStates; import com.hedera.pbj.runtime.io.buffer.Bytes; +import com.swirlds.state.spi.ReadableKVState; +import com.swirlds.state.spi.ReadableQueueState; +import com.swirlds.state.spi.ReadableStates; import edu.umd.cs.findbugs.annotations.NonNull; import edu.umd.cs.findbugs.annotations.Nullable; import java.io.ByteArrayOutputStream; diff --git a/hedera-node/hedera-file-service-impl/src/main/java/com/hedera/node/app/service/file/impl/WritableFileStore.java b/hedera-node/hedera-file-service-impl/src/main/java/com/hedera/node/app/service/file/impl/WritableFileStore.java index 4bb385c9c450..f1c137d17f45 100644 --- a/hedera-node/hedera-file-service-impl/src/main/java/com/hedera/node/app/service/file/impl/WritableFileStore.java +++ b/hedera-node/hedera-file-service-impl/src/main/java/com/hedera/node/app/service/file/impl/WritableFileStore.java @@ -24,10 +24,10 @@ import com.hedera.node.app.service.mono.state.merkle.MerkleTopic; import com.hedera.node.app.spi.metrics.StoreMetricsService; import com.hedera.node.app.spi.metrics.StoreMetricsService.StoreType; -import com.hedera.node.app.spi.state.WritableKVState; -import com.hedera.node.app.spi.state.WritableStates; import com.hedera.node.config.data.FilesConfig; import com.swirlds.config.api.Configuration; +import com.swirlds.state.spi.WritableKVState; +import com.swirlds.state.spi.WritableStates; import edu.umd.cs.findbugs.annotations.NonNull; import java.util.Optional; import java.util.Set; diff --git a/hedera-node/hedera-file-service-impl/src/main/java/com/hedera/node/app/service/file/impl/WritableUpgradeFileStore.java b/hedera-node/hedera-file-service-impl/src/main/java/com/hedera/node/app/service/file/impl/WritableUpgradeFileStore.java index 505f3026f0e3..2039df808d27 100644 --- a/hedera-node/hedera-file-service-impl/src/main/java/com/hedera/node/app/service/file/impl/WritableUpgradeFileStore.java +++ b/hedera-node/hedera-file-service-impl/src/main/java/com/hedera/node/app/service/file/impl/WritableUpgradeFileStore.java @@ -23,10 +23,10 @@ import com.hedera.hapi.node.base.FileID; import com.hedera.hapi.node.state.file.File; import com.hedera.hapi.node.state.primitives.ProtoBytes; -import com.hedera.node.app.spi.state.WritableKVState; -import com.hedera.node.app.spi.state.WritableQueueState; -import com.hedera.node.app.spi.state.WritableStates; import com.hedera.pbj.runtime.io.buffer.Bytes; +import com.swirlds.state.spi.WritableKVState; +import com.swirlds.state.spi.WritableQueueState; +import com.swirlds.state.spi.WritableStates; import edu.umd.cs.findbugs.annotations.NonNull; import java.util.Objects; import java.util.function.Predicate; @@ -56,16 +56,33 @@ public WritableUpgradeFileStore(@NonNull final WritableStates states) { writableUpgradeFileState = requireNonNull(states.get(BLOBS_KEY)); } + /** + * Adds a file to the store. + * + * @param file the file to add + */ public void add(@NonNull final File file) { requireNonNull(file); writableUpgradeFileState.put(file.fileIdOrThrow(), file); } + /** + * Adds upgrade file to the store. + * + * @param fileID the file id + * @param content content file to add + */ public void addUpgradeContent(@NonNull final FileID fileID, Bytes content) { final WritableQueueState upgradeState = getUpgradeState(fileID); upgradeState.add(new ProtoBytes(content)); } + /** + * Appends bytes to the file. + * + * @param bytes the bytes to append + * @param fileID the file id + */ public void append(@NonNull final Bytes bytes, @NonNull final FileID fileID) { requireNonNull(bytes); requireNonNull(fileID); @@ -73,6 +90,11 @@ public void append(@NonNull final Bytes bytes, @NonNull final FileID fileID) { upgradeState.add(new ProtoBytes(bytes)); } + /** + * Resets the file contents. + * + * @param fileID the file id + */ @SuppressWarnings({"StatementWithEmptyBody"}) public void resetFileContents(@NonNull final FileID fileID) { final WritableQueueState upgradeState = getUpgradeState(fileID); diff --git a/hedera-node/hedera-file-service-impl/src/main/java/com/hedera/node/app/service/file/impl/codec/FileServiceStateTranslator.java b/hedera-node/hedera-file-service-impl/src/main/java/com/hedera/node/app/service/file/impl/codec/FileServiceStateTranslator.java index a9b19ff0bdc4..c4657efa2e71 100644 --- a/hedera-node/hedera-file-service-impl/src/main/java/com/hedera/node/app/service/file/impl/codec/FileServiceStateTranslator.java +++ b/hedera-node/hedera-file-service-impl/src/main/java/com/hedera/node/app/service/file/impl/codec/FileServiceStateTranslator.java @@ -116,6 +116,11 @@ public static FileMetadataAndContent pbjToState(@NonNull File file) throws Inval return new FileMetadataAndContent(data, hFileMeta); } + /*** + * Provides a record for the file metadata and content + * @param data bytes of file + * @param metadata all the metadata of the file + */ @SuppressWarnings("java:S6218") public record FileMetadataAndContent( @Nullable byte[] data, @NonNull com.hedera.node.app.service.mono.files.HFileMeta metadata) {} diff --git a/hedera-node/hedera-file-service-impl/src/main/java/com/hedera/node/app/service/file/impl/handlers/FileAppendHandler.java b/hedera-node/hedera-file-service-impl/src/main/java/com/hedera/node/app/service/file/impl/handlers/FileAppendHandler.java index 64fbc1061df1..8d9fc0cf0ca9 100644 --- a/hedera-node/hedera-file-service-impl/src/main/java/com/hedera/node/app/service/file/impl/handlers/FileAppendHandler.java +++ b/hedera-node/hedera-file-service-impl/src/main/java/com/hedera/node/app/service/file/impl/handlers/FileAppendHandler.java @@ -61,6 +61,9 @@ public class FileAppendHandler implements TransactionHandler { private static final Logger logger = LogManager.getLogger(FileAppendHandler.class); + /** + * Default constructor for injection. + */ @Inject public FileAppendHandler() { // Exists for injection diff --git a/hedera-node/hedera-file-service-impl/src/main/java/com/hedera/node/app/service/file/impl/handlers/FileCreateHandler.java b/hedera-node/hedera-file-service-impl/src/main/java/com/hedera/node/app/service/file/impl/handlers/FileCreateHandler.java index 2ba9286cd30e..aeb9f1965f59 100644 --- a/hedera-node/hedera-file-service-impl/src/main/java/com/hedera/node/app/service/file/impl/handlers/FileCreateHandler.java +++ b/hedera-node/hedera-file-service-impl/src/main/java/com/hedera/node/app/service/file/impl/handlers/FileCreateHandler.java @@ -58,6 +58,10 @@ public class FileCreateHandler implements TransactionHandler { private final FileOpsUsage fileOpsUsage; + /** + * Constructs a {@link FileCreateHandler} with the given {@link FileOpsUsage}. + * @param fileOpsUsage the file operation usage calculator + */ @Inject public FileCreateHandler(final FileOpsUsage fileOpsUsage) { this.fileOpsUsage = fileOpsUsage; diff --git a/hedera-node/hedera-file-service-impl/src/main/java/com/hedera/node/app/service/file/impl/handlers/FileDeleteHandler.java b/hedera-node/hedera-file-service-impl/src/main/java/com/hedera/node/app/service/file/impl/handlers/FileDeleteHandler.java index a0d139f794c5..f1978c438a92 100644 --- a/hedera-node/hedera-file-service-impl/src/main/java/com/hedera/node/app/service/file/impl/handlers/FileDeleteHandler.java +++ b/hedera-node/hedera-file-service-impl/src/main/java/com/hedera/node/app/service/file/impl/handlers/FileDeleteHandler.java @@ -55,6 +55,10 @@ public class FileDeleteHandler implements TransactionHandler { private final FileFeeBuilder usageEstimator; + /** + * Constructs a {@link FileDeleteHandler} with the given {@link FileFeeBuilder}. + * @param usageEstimator the file fee builder to be used for fee calculation + */ @Inject public FileDeleteHandler(final FileFeeBuilder usageEstimator) { this.usageEstimator = usageEstimator; diff --git a/hedera-node/hedera-file-service-impl/src/main/java/com/hedera/node/app/service/file/impl/handlers/FileGetContentsHandler.java b/hedera-node/hedera-file-service-impl/src/main/java/com/hedera/node/app/service/file/impl/handlers/FileGetContentsHandler.java index 24a56fed356c..d7d7f6c45521 100644 --- a/hedera-node/hedera-file-service-impl/src/main/java/com/hedera/node/app/service/file/impl/handlers/FileGetContentsHandler.java +++ b/hedera-node/hedera-file-service-impl/src/main/java/com/hedera/node/app/service/file/impl/handlers/FileGetContentsHandler.java @@ -50,6 +50,10 @@ public class FileGetContentsHandler extends FileQueryBase { private final FileFeeBuilder usageEstimator; + /** + * Constructs a {@link FileGetContentsHandler} with the given {@link FileFeeBuilder}. + * @param usageEstimator the file fee builder to be used for fee calculation + */ @Inject public FileGetContentsHandler(final FileFeeBuilder usageEstimator) { this.usageEstimator = usageEstimator; @@ -82,7 +86,7 @@ public void validate(@NonNull final QueryContext context) throws PreCheckExcepti final var query = queryContext.query(); final var fileStore = queryContext.createStore(ReadableFileStore.class); final var op = query.fileGetContentsOrThrow(); - final var fileId = op.fileIDOrThrow(); + final var fileId = op.fileIDOrElse(FileID.DEFAULT); final var responseType = op.headerOrElse(QueryHeader.DEFAULT).responseType(); final FileContents fileContents = contentFile(fileId, fileStore); return queryContext.feeCalculator().legacyCalculate(sigValueObj -> new GetFileContentsResourceUsage( diff --git a/hedera-node/hedera-file-service-impl/src/main/java/com/hedera/node/app/service/file/impl/handlers/FileGetInfoHandler.java b/hedera-node/hedera-file-service-impl/src/main/java/com/hedera/node/app/service/file/impl/handlers/FileGetInfoHandler.java index 0c936b4b3841..d39d5836dfec 100644 --- a/hedera-node/hedera-file-service-impl/src/main/java/com/hedera/node/app/service/file/impl/handlers/FileGetInfoHandler.java +++ b/hedera-node/hedera-file-service-impl/src/main/java/com/hedera/node/app/service/file/impl/handlers/FileGetInfoHandler.java @@ -61,6 +61,10 @@ public class FileGetInfoHandler extends FileQueryBase { private final FileOpsUsage fileOpsUsage; + /** + * Constructs a {@link FileGetInfoHandler} with the given {@link FileOpsUsage}. + * @param fileOpsUsage the file operations usage to be used for fee calculation + */ @Inject public FileGetInfoHandler(final FileOpsUsage fileOpsUsage) { this.fileOpsUsage = fileOpsUsage; @@ -93,7 +97,7 @@ public void validate(@NonNull final QueryContext context) throws PreCheckExcepti final var query = queryContext.query(); final var fileStore = queryContext.createStore(ReadableFileStore.class); final var op = query.fileGetInfoOrThrow(); - final var fileId = op.fileIDOrThrow(); + final var fileId = op.fileIDOrElse(FileID.DEFAULT); final File file = fileStore.getFileLeaf(fileId); return queryContext.feeCalculator().legacyCalculate(sigValueObj -> new GetFileInfoResourceUsage(fileOpsUsage) @@ -138,7 +142,7 @@ public void validate(@NonNull final QueryContext context) throws PreCheckExcepti * Provides information about a file. * @param fileID the file to get information about * @param fileStore the file store - * @param ledgerConfig + * @param ledgerConfig Ledger configuration properties * @return the information about the file */ @SuppressWarnings("java:S5738") // Suppress the warning that we are using deprecated class(CryptographyHolder) @@ -161,8 +165,7 @@ private Optional infoForFile( // The "memo" of a special upgrade file is its hexed SHA-384 hash for DevOps convenience final var contents = upgradeFileStore.getFull(fileID).toByteArray(); contentSize = contents.length; - final var upgradeHash = - hex(CryptographyHolder.get().digestSync(contents).getValue()); + final var upgradeHash = hex(CryptographyHolder.get().digestBytesSync(contents)); meta = new FileMetadata( file.fileId(), Timestamp.newBuilder().seconds(file.expirationSecond()).build(), diff --git a/hedera-node/hedera-file-service-impl/src/main/java/com/hedera/node/app/service/file/impl/handlers/FileHandlers.java b/hedera-node/hedera-file-service-impl/src/main/java/com/hedera/node/app/service/file/impl/handlers/FileHandlers.java index 503fefee4346..16b5ca795ccc 100644 --- a/hedera-node/hedera-file-service-impl/src/main/java/com/hedera/node/app/service/file/impl/handlers/FileHandlers.java +++ b/hedera-node/hedera-file-service-impl/src/main/java/com/hedera/node/app/service/file/impl/handlers/FileHandlers.java @@ -41,6 +41,18 @@ public class FileHandlers { private final FileUpdateHandler fileUpdateHandler; + /** + * Constructor for the FileHandlers. + * + * @param fileAppendHandler the file append handler + * @param fileCreateHandler the file create handler + * @param fileDeleteHandler the file delete handler + * @param fileGetContentsHandler the file get contents handler + * @param fileGetInfoHandler the file get info handler + * @param fileSystemDeleteHandler the file system delete handler + * @param fileSystemUndeleteHandler the file system undelete handler + * @param fileUpdateHandler the file update handler + */ @Inject public FileHandlers( @NonNull final FileAppendHandler fileAppendHandler, @@ -63,34 +75,74 @@ public FileHandlers( this.fileUpdateHandler = requireNonNull(fileUpdateHandler, "fileUpdateHandler must not be null"); } + /** + * Gets the fileAppendHandler. + * + * @return the fileAppendHandler + */ public FileAppendHandler fileAppendHandler() { return fileAppendHandler; } + /** + * Gets the fileCreateHandler. + * + * @return the fileCreateHandler + */ public FileCreateHandler fileCreateHandler() { return fileCreateHandler; } + /** + * Gets the fileDeleteHandler. + * + * @return the fileDeleteHandler + */ public FileDeleteHandler fileDeleteHandler() { return fileDeleteHandler; } + /** + * Gets the fileGetContentsHandler. + * + * @return the fileGetContentsHandler + */ public FileGetContentsHandler fileGetContentsHandler() { return fileGetContentsHandler; } + /** + * Gets the fileGetInfoHandler. + * + * @return the fileGetInfoHandler + */ public FileGetInfoHandler fileGetInfoHandler() { return fileGetInfoHandler; } + /** + * Gets the fileSystemDeleteHandler. + * + * @return the fileSystemDeleteHandler + */ public FileSystemDeleteHandler fileSystemDeleteHandler() { return fileSystemDeleteHandler; } + /** + * Gets the fileSystemUndeleteHandler. + * + * @return the fileSystemUndeleteHandler + */ public FileSystemUndeleteHandler fileSystemUndeleteHandler() { return fileSystemUndeleteHandler; } + /** + * Gets the fileUpdateHandler. + * + * @return the fileUpdateHandler + */ public FileUpdateHandler fileUpdateHandler() { return fileUpdateHandler; } diff --git a/hedera-node/hedera-file-service-impl/src/main/java/com/hedera/node/app/service/file/impl/handlers/FileSignatureWaiversImpl.java b/hedera-node/hedera-file-service-impl/src/main/java/com/hedera/node/app/service/file/impl/handlers/FileSignatureWaiversImpl.java index f1538472bcbb..02a4d7326a3a 100644 --- a/hedera-node/hedera-file-service-impl/src/main/java/com/hedera/node/app/service/file/impl/handlers/FileSignatureWaiversImpl.java +++ b/hedera-node/hedera-file-service-impl/src/main/java/com/hedera/node/app/service/file/impl/handlers/FileSignatureWaiversImpl.java @@ -35,6 +35,10 @@ public class FileSignatureWaiversImpl implements FileSignatureWaivers { private final Authorizer authorizer; + /** + * Constructs a {@link FileSignatureWaiversImpl} with the given {@link Authorizer}. + * @param authorizer account is authorized to perform a specific function + */ @Inject public FileSignatureWaiversImpl(@NonNull final Authorizer authorizer) { this.authorizer = requireNonNull(authorizer); diff --git a/hedera-node/hedera-file-service-impl/src/main/java/com/hedera/node/app/service/file/impl/handlers/FileSystemDeleteHandler.java b/hedera-node/hedera-file-service-impl/src/main/java/com/hedera/node/app/service/file/impl/handlers/FileSystemDeleteHandler.java index 3f120a1d81ac..564ace86a12e 100644 --- a/hedera-node/hedera-file-service-impl/src/main/java/com/hedera/node/app/service/file/impl/handlers/FileSystemDeleteHandler.java +++ b/hedera-node/hedera-file-service-impl/src/main/java/com/hedera/node/app/service/file/impl/handlers/FileSystemDeleteHandler.java @@ -51,6 +51,10 @@ public class FileSystemDeleteHandler implements TransactionHandler { private final FileFeeBuilder usageEstimator; + /** + * Constructs a {@link FileSystemDeleteHandler} with the given {@link FileFeeBuilder}. + * @param usageEstimator the file fee builder to be used for fee calculation + */ @Inject public FileSystemDeleteHandler(final FileFeeBuilder usageEstimator) { this.usageEstimator = usageEstimator; diff --git a/hedera-node/hedera-file-service-impl/src/main/java/com/hedera/node/app/service/file/impl/handlers/FileSystemUndeleteHandler.java b/hedera-node/hedera-file-service-impl/src/main/java/com/hedera/node/app/service/file/impl/handlers/FileSystemUndeleteHandler.java index a4fdb6c57e9b..e2ae3b9c1830 100644 --- a/hedera-node/hedera-file-service-impl/src/main/java/com/hedera/node/app/service/file/impl/handlers/FileSystemUndeleteHandler.java +++ b/hedera-node/hedera-file-service-impl/src/main/java/com/hedera/node/app/service/file/impl/handlers/FileSystemUndeleteHandler.java @@ -50,6 +50,10 @@ public class FileSystemUndeleteHandler implements TransactionHandler { private final FileFeeBuilder usageEstimator; + /** + * Constructs a {@link FileSystemUndeleteHandler} with the given {@link FileFeeBuilder}. + * @param usageEstimator the file fee builder to be used for fee calculation + */ @Inject public FileSystemUndeleteHandler(final FileFeeBuilder usageEstimator) { this.usageEstimator = usageEstimator; diff --git a/hedera-node/hedera-file-service-impl/src/main/java/com/hedera/node/app/service/file/impl/handlers/FileUpdateHandler.java b/hedera-node/hedera-file-service-impl/src/main/java/com/hedera/node/app/service/file/impl/handlers/FileUpdateHandler.java index 74882ca80d31..2c2c6ffb6903 100644 --- a/hedera-node/hedera-file-service-impl/src/main/java/com/hedera/node/app/service/file/impl/handlers/FileUpdateHandler.java +++ b/hedera-node/hedera-file-service-impl/src/main/java/com/hedera/node/app/service/file/impl/handlers/FileUpdateHandler.java @@ -68,10 +68,15 @@ public class FileUpdateHandler implements TransactionHandler { private final FileOpsUsage fileOpsUsage; private final FileSignatureWaivers fileSignatureWaivers; + /** + * Constructs a {@link FileUpdateHandler} with the given {@link FileOpsUsage} and {@link FileSignatureWaivers}. + * @param fileOpsUsage the file operation usage calculator + * @param fileSignatureWaivers the file signature waivers + */ @Inject - public FileUpdateHandler(final FileOpsUsage fileOpsUsage, final FileSignatureWaivers fileSignatureWaivers1) { + public FileUpdateHandler(final FileOpsUsage fileOpsUsage, final FileSignatureWaivers fileSignatureWaivers) { this.fileOpsUsage = fileOpsUsage; - this.fileSignatureWaivers = fileSignatureWaivers1; + this.fileSignatureWaivers = fileSignatureWaivers; } /** @@ -259,6 +264,12 @@ private void validateAutoRenew(FileUpdateTransactionBody op, HandleContext handl } } + /** + * Determines if the update operation wants to mutate non-expiry fields. + * + * @param op the update operation transaction body + * @return {@code true} if the operation wants to mutate non-expiry fields, {@code false} otherwise + */ public static boolean wantsToMutateNonExpiryField(@NonNull final FileUpdateTransactionBody op) { return op.hasMemo() || op.hasKeys() || op.contents().length() > 0; } diff --git a/hedera-node/hedera-file-service-impl/src/main/java/com/hedera/node/app/service/file/impl/schemas/InitialModFileGenesisSchema.java b/hedera-node/hedera-file-service-impl/src/main/java/com/hedera/node/app/service/file/impl/schemas/InitialModFileGenesisSchema.java index 9cc21ae71197..11927109c7b2 100644 --- a/hedera-node/hedera-file-service-impl/src/main/java/com/hedera/node/app/service/file/impl/schemas/InitialModFileGenesisSchema.java +++ b/hedera-node/hedera-file-service-impl/src/main/java/com/hedera/node/app/service/file/impl/schemas/InitialModFileGenesisSchema.java @@ -56,8 +56,6 @@ import com.hedera.node.app.spi.state.MigrationContext; import com.hedera.node.app.spi.state.Schema; import com.hedera.node.app.spi.state.StateDefinition; -import com.hedera.node.app.spi.state.WritableKVState; -import com.hedera.node.app.spi.state.WritableKVStateBase; import com.hedera.node.config.ConfigProvider; import com.hedera.node.config.data.BootstrapConfig; import com.hedera.node.config.data.FilesConfig; @@ -66,6 +64,8 @@ import com.hedera.pbj.runtime.io.buffer.Bytes; import com.swirlds.common.threading.manager.AdHocThreadManager; import com.swirlds.config.api.Configuration; +import com.swirlds.platform.state.spi.WritableKVStateBase; +import com.swirlds.state.spi.WritableKVState; import edu.umd.cs.findbugs.annotations.NonNull; import edu.umd.cs.findbugs.annotations.Nullable; import java.io.IOException; @@ -102,7 +102,12 @@ public class InitialModFileGenesisSchema extends Schema { private Map fileContents; private Map fileAttrs; - /** Create a new instance */ + /** + * Constructs a new {@link InitialModFileGenesisSchema} instance with the given {@link SemanticVersion} and {@link ConfigProvider}. + * + * @param version the version of the schema + * @param configProvider the configuration provider + */ public InitialModFileGenesisSchema( @NonNull final SemanticVersion version, @NonNull final ConfigProvider configProvider) { super(version); @@ -579,6 +584,12 @@ private void createGenesisThrottleDefinitions( .build()); } + /** + * Load the throttle definitions from the bootstrap configuration. + * + * @param bootstrapConfig the bootstrap configuration + * @return the throttle definitions proto as a byte array + */ public static byte[] loadBootstrapThrottleDefinitions(@NonNull BootstrapConfig bootstrapConfig) { // Get the path to the throttles permissions file final var throttleDefinitionsResource = bootstrapConfig.throttleDefsJsonResource(); diff --git a/hedera-node/hedera-file-service-impl/src/main/java/module-info.java b/hedera-node/hedera-file-service-impl/src/main/java/module-info.java index 2f347c65f7b5..023f76406c3f 100644 --- a/hedera-node/hedera-file-service-impl/src/main/java/module-info.java +++ b/hedera-node/hedera-file-service-impl/src/main/java/module-info.java @@ -2,6 +2,7 @@ requires com.fasterxml.jackson.databind; requires com.swirlds.base; requires com.swirlds.common; + requires com.swirlds.platform.core; requires org.apache.commons.lang3; requires org.apache.logging.log4j; requires transitive com.hedera.node.app.hapi.fees; @@ -13,6 +14,7 @@ requires transitive com.hedera.node.hapi; requires transitive com.hedera.pbj.runtime; requires transitive com.swirlds.config.api; + requires transitive com.swirlds.state.api; requires transitive dagger; requires transitive javax.inject; requires static com.github.spotbugs.annotations; diff --git a/hedera-node/hedera-file-service-impl/src/test/java/com/hedera/node/app/service/file/impl/test/FileServiceImplTest.java b/hedera-node/hedera-file-service-impl/src/test/java/com/hedera/node/app/service/file/impl/test/FileServiceImplTest.java index 59efdbc3c609..d0d05b044496 100644 --- a/hedera-node/hedera-file-service-impl/src/test/java/com/hedera/node/app/service/file/impl/test/FileServiceImplTest.java +++ b/hedera-node/hedera-file-service-impl/src/test/java/com/hedera/node/app/service/file/impl/test/FileServiceImplTest.java @@ -16,17 +16,24 @@ package com.hedera.node.app.service.file.impl.test; -import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.assertj.core.api.AssertionsForClassTypes.assertThat; +import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; import com.hedera.node.app.config.BootstrapConfigProviderImpl; import com.hedera.node.app.service.file.FileService; import com.hedera.node.app.service.file.impl.FileServiceImpl; +import com.hedera.node.app.service.file.impl.schemas.InitialModFileGenesisSchema; +import com.hedera.node.app.service.mono.state.adapters.VirtualMapLike; +import com.hedera.node.app.service.mono.state.virtual.VirtualBlobKey; +import com.hedera.node.app.service.mono.state.virtual.VirtualBlobValue; import com.hedera.node.app.spi.fixtures.state.TestSchema; import com.hedera.node.app.spi.state.Schema; import com.hedera.node.app.spi.state.SchemaRegistry; import com.hedera.node.app.spi.state.StateDefinition; import com.hedera.node.config.ConfigProvider; +import java.lang.reflect.Field; +import java.util.function.Supplier; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; @@ -57,10 +64,26 @@ void registersExpectedSchema() { final var schema = schemaCaptor.getValue(); final var statesToCreate = schema.statesToCreate(); - assertEquals(11, statesToCreate.size()); + assertThat(11).isEqualTo(statesToCreate.size()); final var iter = statesToCreate.stream().map(StateDefinition::stateKey).sorted().iterator(); - assertEquals(FileServiceImpl.BLOBS_KEY, iter.next()); + assertThat(FileServiceImpl.BLOBS_KEY).isEqualTo(iter.next()); + } + + @Test + void testSetFs() throws NoSuchFieldException, IllegalAccessException { + FileServiceImpl fileService = new FileServiceImpl(configProvider); + fileService.registerSchemas(registry, TestSchema.CURRENT_VERSION); + Supplier> mockSupplier = mock(Supplier.class); + InitialModFileGenesisSchema mockInitialFileSchema = mock(InitialModFileGenesisSchema.class); + + Field field = FileServiceImpl.class.getDeclaredField("initialFileSchema"); + field.setAccessible(true); + field.set(fileService, mockInitialFileSchema); + + fileService.setFs(mockSupplier); + + verify(mockInitialFileSchema).setFs(mockSupplier); } private FileService subject() { diff --git a/hedera-node/hedera-file-service-impl/src/test/java/com/hedera/node/app/service/file/impl/test/FileTestBase.java b/hedera-node/hedera-file-service-impl/src/test/java/com/hedera/node/app/service/file/impl/test/FileTestBase.java index 7658ccfa58a3..04e007686b30 100644 --- a/hedera-node/hedera-file-service-impl/src/test/java/com/hedera/node/app/service/file/impl/test/FileTestBase.java +++ b/hedera-node/hedera-file-service-impl/src/test/java/com/hedera/node/app/service/file/impl/test/FileTestBase.java @@ -39,19 +39,19 @@ import com.hedera.node.app.service.file.impl.WritableUpgradeFileStore; import com.hedera.node.app.service.mono.legacy.core.jproto.JEd25519Key; import com.hedera.node.app.service.mono.legacy.core.jproto.JKeyList; -import com.hedera.node.app.spi.fixtures.state.ListReadableQueueState; -import com.hedera.node.app.spi.fixtures.state.ListWritableQueueState; -import com.hedera.node.app.spi.fixtures.state.MapReadableKVState; -import com.hedera.node.app.spi.fixtures.state.MapWritableKVState; import com.hedera.node.app.spi.metrics.StoreMetricsService; import com.hedera.node.app.spi.signatures.SignatureVerification; import com.hedera.node.app.spi.state.FilteredReadableStates; import com.hedera.node.app.spi.state.FilteredWritableStates; -import com.hedera.node.app.spi.state.ReadableStates; -import com.hedera.node.app.spi.state.WritableStates; import com.hedera.node.app.spi.workflows.HandleContext; import com.hedera.node.config.testfixtures.HederaTestConfigBuilder; import com.hedera.pbj.runtime.io.buffer.Bytes; +import com.swirlds.platform.test.fixtures.state.ListReadableQueueState; +import com.swirlds.platform.test.fixtures.state.ListWritableQueueState; +import com.swirlds.platform.test.fixtures.state.MapReadableKVState; +import com.swirlds.platform.test.fixtures.state.MapWritableKVState; +import com.swirlds.state.spi.ReadableStates; +import com.swirlds.state.spi.WritableStates; import edu.umd.cs.findbugs.annotations.NonNull; import java.util.List; import org.junit.jupiter.api.BeforeEach; diff --git a/hedera-node/hedera-file-service-impl/src/test/java/com/hedera/node/app/service/file/impl/test/ReadableFileStoreImplTest.java b/hedera-node/hedera-file-service-impl/src/test/java/com/hedera/node/app/service/file/impl/test/ReadableFileStoreImplTest.java index b0befd8a71b8..335bd6657dff 100644 --- a/hedera-node/hedera-file-service-impl/src/test/java/com/hedera/node/app/service/file/impl/test/ReadableFileStoreImplTest.java +++ b/hedera-node/hedera-file-service-impl/src/test/java/com/hedera/node/app/service/file/impl/test/ReadableFileStoreImplTest.java @@ -23,8 +23,8 @@ import com.hedera.hapi.node.state.file.File; import com.hedera.node.app.service.file.impl.ReadableFileStoreImpl; -import com.hedera.node.app.spi.fixtures.state.MapReadableKVState; import com.hedera.pbj.runtime.io.buffer.Bytes; +import com.swirlds.platform.test.fixtures.state.MapReadableKVState; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; diff --git a/hedera-node/hedera-file-service-impl/src/test/java/com/hedera/node/app/service/file/impl/test/ReadableUpgradeFileStoreImplTest.java b/hedera-node/hedera-file-service-impl/src/test/java/com/hedera/node/app/service/file/impl/test/ReadableUpgradeFileStoreImplTest.java index 770a50802de9..2ca4e87c9b88 100644 --- a/hedera-node/hedera-file-service-impl/src/test/java/com/hedera/node/app/service/file/impl/test/ReadableUpgradeFileStoreImplTest.java +++ b/hedera-node/hedera-file-service-impl/src/test/java/com/hedera/node/app/service/file/impl/test/ReadableUpgradeFileStoreImplTest.java @@ -28,9 +28,9 @@ import com.hedera.hapi.node.state.file.File; import com.hedera.hapi.node.state.primitives.ProtoBytes; import com.hedera.node.app.service.file.impl.ReadableUpgradeFileStoreImpl; -import com.hedera.node.app.spi.fixtures.state.ListReadableQueueState; -import com.hedera.node.app.spi.fixtures.state.MapWritableKVState; import com.hedera.pbj.runtime.io.buffer.Bytes; +import com.swirlds.platform.test.fixtures.state.ListReadableQueueState; +import com.swirlds.platform.test.fixtures.state.MapWritableKVState; import java.io.IOException; import java.util.Iterator; import java.util.List; diff --git a/hedera-node/hedera-file-service-impl/src/test/java/com/hedera/node/app/service/file/impl/test/handlers/FileAppendTest.java b/hedera-node/hedera-file-service-impl/src/test/java/com/hedera/node/app/service/file/impl/test/handlers/FileAppendHandlerTest.java similarity index 86% rename from hedera-node/hedera-file-service-impl/src/test/java/com/hedera/node/app/service/file/impl/test/handlers/FileAppendTest.java rename to hedera-node/hedera-file-service-impl/src/test/java/com/hedera/node/app/service/file/impl/test/handlers/FileAppendHandlerTest.java index b79fa875f8c7..9ff77e037353 100644 --- a/hedera-node/hedera-file-service-impl/src/test/java/com/hedera/node/app/service/file/impl/test/handlers/FileAppendTest.java +++ b/hedera-node/hedera-file-service-impl/src/test/java/com/hedera/node/app/service/file/impl/test/handlers/FileAppendHandlerTest.java @@ -18,12 +18,17 @@ import static com.hedera.hapi.node.base.ResponseCodeEnum.FILE_DELETED; import static com.hedera.hapi.node.base.ResponseCodeEnum.INVALID_FILE_ID; +import static com.hedera.node.app.spi.fixtures.workflows.ExceptionConditions.responseCode; import static com.hedera.test.utils.KeyUtils.A_COMPLEX_KEY; +import static org.assertj.core.api.Assertions.assertThatNoException; +import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertThrows; -import static org.junit.jupiter.api.Assertions.assertTrue; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyLong; +import static org.mockito.ArgumentMatchers.notNull; import static org.mockito.BDDMockito.given; import static org.mockito.Mock.Strictness.LENIENT; import static org.mockito.Mockito.mock; @@ -48,6 +53,7 @@ import com.hedera.node.app.service.token.ReadableAccountStore; import com.hedera.node.app.spi.fees.FeeAccumulator; import com.hedera.node.app.spi.fees.FeeCalculator; +import com.hedera.node.app.spi.fees.FeeContext; import com.hedera.node.app.spi.fees.Fees; import com.hedera.node.app.spi.metrics.StoreMetricsService; import com.hedera.node.app.spi.workflows.HandleException; @@ -70,7 +76,7 @@ import org.mockito.junit.jupiter.MockitoExtension; @ExtendWith(MockitoExtension.class) -class FileAppendTest extends FileTestBase { +class FileAppendHandlerTest extends FileTestBase { private final FileAppendTransactionBody.Builder OP_BUILDER = FileAppendTransactionBody.newBuilder(); @Mock @@ -113,7 +119,7 @@ void setUp() { } @Test - void rejectsMissingFile() { + void handleRejectsMissingFile() { final var txBody = TransactionBody.newBuilder().fileAppend(OP_BUILDER).build(); when(handleContext.body()).thenReturn(txBody); @@ -121,6 +127,25 @@ void rejectsMissingFile() { assertFailsWith(INVALID_FILE_ID, () -> subject.handle(handleContext)); } + @Test + void pureChecksFailWhenMissingFile() { + final var txBody = TransactionBody.newBuilder().fileAppend(OP_BUILDER).build(); + assertThatThrownBy(() -> subject.pureChecks(txBody)) + .isInstanceOf(PreCheckException.class) + .has(responseCode(INVALID_FILE_ID)); + } + + @Test + void pureChecksPassForValidTxn() { + final var txnId = TransactionID.newBuilder().accountID(payerId).build(); + final var txBody = TransactionBody.newBuilder() + .fileAppend(OP_BUILDER.fileID(wellKnownId())) + .transactionID(txnId) + .build(); + + assertThatNoException().isThrownBy(() -> subject.pureChecks(txBody)); + } + @Test @DisplayName("Pre handle works as expected") void preHandleWorksAsExpected() throws PreCheckException { @@ -143,7 +168,7 @@ void preHandleWorksAsExpected() throws PreCheckException { subject.preHandle(realPreContext); - assertTrue(realPreContext.requiredNonPayerKeys().size() > 0); + assertFalse(realPreContext.requiredNonPayerKeys().isEmpty()); assertEquals(3, realPreContext.requiredNonPayerKeys().size()); } @@ -352,6 +377,33 @@ void handleThrowsIfKeysSignatureFailed() { assertThrows(HandleException.class, () -> subject.handle(handleContext)); } + @Test + void calculateFeesHappyPath() { + final var txnId = TransactionID.newBuilder() + .accountID(payerId) + .transactionValidStart(Timestamp.newBuilder().seconds(111111).build()) + .build(); + final var txBody = TransactionBody.newBuilder() + .fileAppend(OP_BUILDER.fileID(wellKnownId())) + .transactionID(txnId) + .build(); + + final var feeCtx = mock(FeeContext.class); + given(feeCtx.body()).willReturn(txBody); + + final var feeCalc = mock(FeeCalculator.class); + given(feeCtx.feeCalculator(notNull())).willReturn(feeCalc); + given(feeCtx.configuration()).willReturn(testConfig); + given(feeCtx.readableStore(ReadableFileStore.class)).willReturn(readableStore); + given(feeCalc.addBytesPerTransaction(anyLong())).willReturn(feeCalc); + given(feeCalc.addStorageBytesSeconds(anyLong())).willReturn(feeCalc); + // The fees wouldn't be free in this scenario, but we don't care about the actual return + // value here since we're using a mock calculator + given(feeCalc.calculate()).willReturn(Fees.FREE); + + assertNotNull(subject.calculateFees(feeCtx)); + } + private FileID wellKnownId() { return fileId; } diff --git a/hedera-node/hedera-file-service-impl/src/test/java/com/hedera/node/app/service/file/impl/test/handlers/FileDeleteTest.java b/hedera-node/hedera-file-service-impl/src/test/java/com/hedera/node/app/service/file/impl/test/handlers/FileDeleteTest.java index dc6c93539591..b2f10a2489c8 100644 --- a/hedera-node/hedera-file-service-impl/src/test/java/com/hedera/node/app/service/file/impl/test/handlers/FileDeleteTest.java +++ b/hedera-node/hedera-file-service-impl/src/test/java/com/hedera/node/app/service/file/impl/test/handlers/FileDeleteTest.java @@ -20,13 +20,14 @@ import static com.hedera.node.app.spi.fixtures.Assertions.assertThrowsPreCheck; import static com.hedera.test.utils.KeyUtils.A_COMPLEX_KEY; import static org.assertj.core.api.Assertions.assertThat; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertThrows; -import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.assertj.core.api.Assertions.assertThatCode; +import static org.assertj.core.api.AssertionsForClassTypes.assertThatThrownBy; +import static org.assertj.core.api.AssertionsForClassTypes.catchThrowable; +import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.notNull; import static org.mockito.BDDMockito.given; import static org.mockito.Mock.Strictness.LENIENT; +import static org.mockito.Mockito.inOrder; import static org.mockito.Mockito.lenient; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; @@ -34,6 +35,7 @@ import com.hedera.hapi.node.base.FileID; import com.hedera.hapi.node.base.Key; import com.hedera.hapi.node.base.ResponseCodeEnum; +import com.hedera.hapi.node.base.SubType; import com.hedera.hapi.node.base.TransactionID; import com.hedera.hapi.node.file.FileDeleteTransactionBody; import com.hedera.hapi.node.state.file.File; @@ -46,6 +48,8 @@ import com.hedera.node.app.service.file.impl.handlers.FileDeleteHandler; import com.hedera.node.app.service.file.impl.test.FileTestBase; import com.hedera.node.app.service.token.ReadableAccountStore; +import com.hedera.node.app.spi.fees.FeeCalculator; +import com.hedera.node.app.spi.fees.FeeContext; import com.hedera.node.app.spi.metrics.StoreMetricsService; import com.hedera.node.app.spi.workflows.HandleContext; import com.hedera.node.app.spi.workflows.HandleException; @@ -62,6 +66,7 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.BDDMockito; +import org.mockito.InOrder; import org.mockito.Mock; import org.mockito.Mock.Strictness; import org.mockito.junit.jupiter.MockitoExtension; @@ -116,6 +121,41 @@ void setUp() { when(mockStoreFactory.getStore(ReadableAccountStore.class)).thenReturn(accountStore); } + @Test + @DisplayName("pureChecks throws exception when file id is null") + public void testPureChecksThrowsExceptionWhenFileIdIsNull() { + FileDeleteTransactionBody transactionBody = mock(FileDeleteTransactionBody.class); + TransactionBody transaction = mock(TransactionBody.class); + given(handleContext.body()).willReturn(transaction); + given(transaction.fileDeleteOrThrow()).willReturn(transactionBody); + given(transactionBody.fileID()).willReturn(null); + + assertThatThrownBy(() -> subject.pureChecks(handleContext.body())).isInstanceOf(PreCheckException.class); + } + + @Test + @DisplayName("pureChecks does not throw exception when file id is not null") + public void testPureChecksDoesNotThrowExceptionWhenFileIdIsNotNull() { + given(handleContext.body()).willReturn(newDeleteTxn()); + + assertThatCode(() -> subject.pureChecks(handleContext.body())).doesNotThrowAnyException(); + } + + @Test + @DisplayName("calculateFees method invocations") + public void testCalculateFeesInvocations() { + FeeContext feeContext = mock(FeeContext.class); + FeeCalculator feeCalculator = mock(FeeCalculator.class); + when(feeContext.feeCalculator(SubType.DEFAULT)).thenReturn(feeCalculator); + + subject.calculateFees(feeContext); + + InOrder inOrder = inOrder(feeContext, feeCalculator); + inOrder.verify(feeContext).body(); + inOrder.verify(feeContext).feeCalculator(SubType.DEFAULT); + inOrder.verify(feeCalculator).legacyCalculate(any()); + } + @Test @DisplayName("File not found returns error") void fileIdNotFound() throws PreCheckException { @@ -149,7 +189,7 @@ void preHandleWorksAsExpectedImmutable() throws PreCheckException { subject.preHandle(realPreContext); - assertEquals(0, realPreContext.requiredNonPayerKeys().size()); + assertThat(realPreContext.requiredNonPayerKeys().size()).isEqualTo(0); } @Test @@ -188,8 +228,9 @@ void fileDoesntExist() { given(handleContext.body()) .willReturn(TransactionBody.newBuilder().fileDelete(txn).build()); given(handleContext.writableStore(WritableFileStore.class)).willReturn(writableStore); - final var msg = assertThrows(HandleException.class, () -> subject.handle(handleContext)); - assertEquals(INVALID_FILE_ID, msg.getStatus()); + + HandleException thrown = (HandleException) catchThrowable(() -> subject.handle(handleContext)); + assertThat(thrown.getStatus()).isEqualTo(INVALID_FILE_ID); } @Test @@ -207,9 +248,8 @@ void keysDoesntExist() { given(handleContext.body()) .willReturn(TransactionBody.newBuilder().fileDelete(txn).build()); given(handleContext.writableStore(WritableFileStore.class)).willReturn(writableStore); - final var msg = assertThrows(HandleException.class, () -> subject.handle(handleContext)); - - assertEquals(ResponseCodeEnum.UNAUTHORIZED, msg.getStatus()); + HandleException thrown = (HandleException) catchThrowable(() -> subject.handle(handleContext)); + assertThat(thrown.getStatus()).isEqualTo(ResponseCodeEnum.UNAUTHORIZED); } @Test @@ -218,8 +258,8 @@ void handleWorksAsExpected() { final var txn = newDeleteTxn().fileDeleteOrThrow(); final var existingFile = writableStore.get(fileId); - assertTrue(existingFile.isPresent()); - assertFalse(existingFile.get().deleted()); + assertThat(existingFile.isPresent()).isTrue(); + assertThat(existingFile.get().deleted()).isFalse(); given(handleContext.body()) .willReturn(TransactionBody.newBuilder().fileDelete(txn).build()); @@ -229,9 +269,9 @@ void handleWorksAsExpected() { final var changedFile = writableStore.get(fileId); - assertTrue(changedFile.isPresent()); - assertTrue(changedFile.get().deleted()); - assertEquals(Bytes.EMPTY, changedFile.get().contents()); + assertThat(changedFile.isPresent()).isTrue(); + assertThat(changedFile.get().deleted()).isTrue(); + assertThat(Bytes.EMPTY).isEqualTo(changedFile.get().contents()); } @Test @@ -243,8 +283,8 @@ void noFileKeys() { final var txn = newDeleteTxn().fileDeleteOrThrow(); final var existingFile = writableStore.get(fileId); - assertTrue(existingFile.isPresent()); - assertFalse(existingFile.get().deleted()); + assertThat(existingFile.isPresent()).isTrue(); + assertThat(existingFile.get().deleted()).isFalse(); given(handleContext.body()) .willReturn(TransactionBody.newBuilder().fileDelete(txn).build()); @@ -267,7 +307,9 @@ private TransactionBody newDeleteTxn() { } private static void assertFailsWith(final ResponseCodeEnum status, final Runnable something) { - final var ex = assertThrows(HandleException.class, something::run); - assertEquals(status, ex.getStatus()); + assertThatThrownBy(something::run) + .isInstanceOf(HandleException.class) + .extracting(ex -> ((HandleException) ex).getStatus()) + .isEqualTo(status); } } diff --git a/hedera-node/hedera-file-service-impl/src/test/java/com/hedera/node/app/service/file/impl/test/handlers/FileGetInfoTest.java b/hedera-node/hedera-file-service-impl/src/test/java/com/hedera/node/app/service/file/impl/test/handlers/FileGetInfoTest.java index c0638ee1d079..7ca3a177d95a 100644 --- a/hedera-node/hedera-file-service-impl/src/test/java/com/hedera/node/app/service/file/impl/test/handlers/FileGetInfoTest.java +++ b/hedera-node/hedera-file-service-impl/src/test/java/com/hedera/node/app/service/file/impl/test/handlers/FileGetInfoTest.java @@ -228,8 +228,7 @@ private FileInfo getExpectedInfo() { } private FileInfo getExpectedSystemInfo() { - final var upgradeHash = - hex(CryptographyHolder.get().digestSync(contents).getValue()); + final var upgradeHash = hex(CryptographyHolder.get().digestBytesSync(contents)); return FileInfo.newBuilder() .memo(upgradeHash) .fileID(FileID.newBuilder().fileNum(fileSystemFileId.fileNum()).build()) @@ -242,8 +241,7 @@ private FileInfo getExpectedSystemInfo() { } private FileInfo getExpectedUpgradeInfo() { - final var upgradeHash = - hex(CryptographyHolder.get().digestSync(contents).getValue()); + final var upgradeHash = hex(CryptographyHolder.get().digestBytesSync(contents)); return FileInfo.newBuilder() .memo(upgradeHash) .fileID(FileID.newBuilder().fileNum(fileUpgradeFileId.fileNum()).build()) diff --git a/hedera-node/hedera-file-service-impl/src/test/java/com/hedera/node/app/service/file/impl/test/handlers/FileHandlersTest.java b/hedera-node/hedera-file-service-impl/src/test/java/com/hedera/node/app/service/file/impl/test/handlers/FileHandlersTest.java new file mode 100644 index 000000000000..5c5348095012 --- /dev/null +++ b/hedera-node/hedera-file-service-impl/src/test/java/com/hedera/node/app/service/file/impl/test/handlers/FileHandlersTest.java @@ -0,0 +1,131 @@ +/* + * Copyright (C) 2024 Hedera Hashgraph, LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.hedera.node.app.service.file.impl.test.handlers; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.mockito.Mockito.mock; + +import com.hedera.node.app.service.file.impl.handlers.FileAppendHandler; +import com.hedera.node.app.service.file.impl.handlers.FileCreateHandler; +import com.hedera.node.app.service.file.impl.handlers.FileDeleteHandler; +import com.hedera.node.app.service.file.impl.handlers.FileGetContentsHandler; +import com.hedera.node.app.service.file.impl.handlers.FileGetInfoHandler; +import com.hedera.node.app.service.file.impl.handlers.FileHandlers; +import com.hedera.node.app.service.file.impl.handlers.FileSystemDeleteHandler; +import com.hedera.node.app.service.file.impl.handlers.FileSystemUndeleteHandler; +import com.hedera.node.app.service.file.impl.handlers.FileUpdateHandler; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +class FileHandlersTest { + private FileAppendHandler fileAppendHandler; + private FileCreateHandler fileCreateHandler; + private FileDeleteHandler fileDeleteHandler; + private FileGetContentsHandler fileGetContentsHandler; + private FileGetInfoHandler fileGetInfoHandler; + private FileSystemDeleteHandler fileSystemDeleteHandler; + private FileSystemUndeleteHandler fileSystemUndeleteHandler; + private FileUpdateHandler fileUpdateHandler; + + private FileHandlers fileHandlers; + + @BeforeEach + public void setUp() { + fileAppendHandler = mock(FileAppendHandler.class); + fileCreateHandler = mock(FileCreateHandler.class); + fileDeleteHandler = mock(FileDeleteHandler.class); + fileGetContentsHandler = mock(FileGetContentsHandler.class); + fileGetInfoHandler = mock(FileGetInfoHandler.class); + fileSystemDeleteHandler = mock(FileSystemDeleteHandler.class); + fileSystemUndeleteHandler = mock(FileSystemUndeleteHandler.class); + fileUpdateHandler = mock(FileUpdateHandler.class); + + fileHandlers = new FileHandlers( + fileAppendHandler, + fileCreateHandler, + fileDeleteHandler, + fileGetContentsHandler, + fileGetInfoHandler, + fileSystemDeleteHandler, + fileSystemUndeleteHandler, + fileUpdateHandler); + } + + @Test + void fileAppendHandlerReturnsCorrectInstance() { + assertEquals( + fileAppendHandler, + fileHandlers.fileAppendHandler(), + "fileAppendHandler does not return correct instance"); + } + + @Test + void fileCreateHandlerReturnsCorrectInstance() { + assertEquals( + fileCreateHandler, + fileHandlers.fileCreateHandler(), + "fileCreateHandler does not return correct instance"); + } + + @Test + void fileDeleteHandlerReturnsCorrectInstance() { + assertEquals( + fileDeleteHandler, + fileHandlers.fileDeleteHandler(), + "fileDeleteHandler does not return correct instance"); + } + + @Test + void fileGetContentsHandlerReturnsCorrectInstance() { + assertEquals( + fileGetContentsHandler, + fileHandlers.fileGetContentsHandler(), + "fileGetContentsHandler does not return correct instance"); + } + + @Test + void fileGetInfoHandlerReturnsCorrectInstance() { + assertEquals( + fileGetInfoHandler, + fileHandlers.fileGetInfoHandler(), + "fileGetInfoHandler does not return correct instance"); + } + + @Test + void fileSystemDeleteHandlerReturnsCorrectInstance() { + assertEquals( + fileSystemDeleteHandler, + fileHandlers.fileSystemDeleteHandler(), + "fileSystemDeleteHandler does not return correct instance"); + } + + @Test + void fileSystemUndeleteHandlerReturnsCorrectInstance() { + assertEquals( + fileSystemUndeleteHandler, + fileHandlers.fileSystemUndeleteHandler(), + "fileSystemUndeleteHandler does not return correct instance"); + } + + @Test + void fileUpdateHandlerReturnsCorrectInstance() { + assertEquals( + fileUpdateHandler, + fileHandlers.fileUpdateHandler(), + "fileUpdateHandler does not return correct instance"); + } +} diff --git a/hedera-node/hedera-file-service-impl/src/test/java/com/hedera/node/app/service/file/impl/test/handlers/FileSignatureWaiversImplTest.java b/hedera-node/hedera-file-service-impl/src/test/java/com/hedera/node/app/service/file/impl/test/handlers/FileSignatureWaiversImplTest.java new file mode 100644 index 000000000000..51eb676f1699 --- /dev/null +++ b/hedera-node/hedera-file-service-impl/src/test/java/com/hedera/node/app/service/file/impl/test/handlers/FileSignatureWaiversImplTest.java @@ -0,0 +1,75 @@ +/* + * Copyright (C) 2024 Hedera Hashgraph, LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.hedera.node.app.service.file.impl.test.handlers; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.when; + +import com.hedera.hapi.node.base.AccountID; +import com.hedera.hapi.node.base.HederaFunctionality; +import com.hedera.hapi.node.transaction.TransactionBody; +import com.hedera.node.app.service.file.impl.handlers.FileSignatureWaiversImpl; +import com.hedera.node.app.spi.authorization.Authorizer; +import com.hedera.node.app.spi.authorization.SystemPrivilege; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.mockito.junit.jupiter.MockitoExtension; + +@ExtendWith(MockitoExtension.class) +public class FileSignatureWaiversImplTest { + + @Mock + private Authorizer authorizer; + + @Mock + private TransactionBody fileUpdateTxn; + + @Mock + private AccountID payer; + + private FileSignatureWaiversImpl fileSignatureWaivers; + + @BeforeEach + public void setup() { + MockitoAnnotations.openMocks(this); + fileSignatureWaivers = new FileSignatureWaiversImpl(authorizer); + } + + @Test + @DisplayName("Signatures are waived when payer has privileged authorization") + public void signaturesAreWaivedWhenPayerHasPrivilegedAuthorization() { + when(authorizer.hasPrivilegedAuthorization(payer, HederaFunctionality.FILE_UPDATE, fileUpdateTxn)) + .thenReturn(SystemPrivilege.AUTHORIZED); + + assertThat(fileSignatureWaivers.areFileUpdateSignaturesWaived(fileUpdateTxn, payer)) + .isTrue(); + } + + @Test + @DisplayName("Signatures are not waived when payer does not have privileged authorization") + public void signaturesAreNotWaivedWhenPayerDoesNotHavePrivilegedAuthorization() { + when(authorizer.hasPrivilegedAuthorization(payer, HederaFunctionality.FILE_UPDATE, fileUpdateTxn)) + .thenReturn(SystemPrivilege.UNAUTHORIZED); + + assertThat(fileSignatureWaivers.areFileUpdateSignaturesWaived(fileUpdateTxn, payer)) + .isFalse(); + } +} diff --git a/hedera-node/hedera-file-service-impl/src/test/java/com/hedera/node/app/service/file/impl/test/handlers/FileSystemDeleteTest.java b/hedera-node/hedera-file-service-impl/src/test/java/com/hedera/node/app/service/file/impl/test/handlers/FileSystemDeleteTest.java index 5793991e5cb9..d4c0bf325246 100644 --- a/hedera-node/hedera-file-service-impl/src/test/java/com/hedera/node/app/service/file/impl/test/handlers/FileSystemDeleteTest.java +++ b/hedera-node/hedera-file-service-impl/src/test/java/com/hedera/node/app/service/file/impl/test/handlers/FileSystemDeleteTest.java @@ -21,19 +21,22 @@ import static com.hedera.hapi.node.base.ResponseCodeEnum.UNAUTHORIZED; import static com.hedera.node.app.spi.fixtures.Assertions.assertThrowsPreCheck; import static com.hedera.test.utils.KeyUtils.A_COMPLEX_KEY; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertThrows; -import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatCode; +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.assertj.core.api.AssertionsForClassTypes.catchThrowable; +import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.notNull; import static org.mockito.BDDMockito.given; import static org.mockito.Mock.Strictness.LENIENT; +import static org.mockito.Mockito.inOrder; import static org.mockito.Mockito.lenient; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; import com.hedera.hapi.node.base.FileID; import com.hedera.hapi.node.base.Key; +import com.hedera.hapi.node.base.SubType; import com.hedera.hapi.node.base.TransactionID; import com.hedera.hapi.node.file.SystemDeleteTransactionBody; import com.hedera.hapi.node.state.file.File; @@ -46,6 +49,8 @@ import com.hedera.node.app.service.file.impl.handlers.FileSystemDeleteHandler; import com.hedera.node.app.service.file.impl.test.FileTestBase; import com.hedera.node.app.service.token.ReadableAccountStore; +import com.hedera.node.app.spi.fees.FeeCalculator; +import com.hedera.node.app.spi.fees.FeeContext; import com.hedera.node.app.spi.metrics.StoreMetricsService; import com.hedera.node.app.spi.workflows.HandleContext; import com.hedera.node.app.spi.workflows.HandleException; @@ -63,6 +68,7 @@ import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InOrder; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; @@ -119,6 +125,41 @@ void setUp() { when(mockStoreFactory.getStore(ReadableAccountStore.class)).thenReturn(accountStore); } + @Test + @DisplayName("pureChecks throws exception when file id is null") + public void testPureChecksThrowsExceptionWhenFileIdIsNull() { + SystemDeleteTransactionBody transactionBody = mock(SystemDeleteTransactionBody.class); + TransactionBody transaction = mock(TransactionBody.class); + given(handleContext.body()).willReturn(transaction); + given(transaction.systemDeleteOrThrow()).willReturn(transactionBody); + given(transactionBody.fileID()).willReturn(null); + + assertThatThrownBy(() -> subject.pureChecks(handleContext.body())).isInstanceOf(PreCheckException.class); + } + + @Test + @DisplayName("pureChecks does not throw exception when file id is not null") + public void testPureChecksDoesNotThrowExceptionWhenFileIdIsNotNull() { + given(handleContext.body()).willReturn(newFileDeleteTxn()); + + assertThatCode(() -> subject.pureChecks(handleContext.body())).doesNotThrowAnyException(); + } + + @Test + @DisplayName("calculateFees method invocations") + public void testCalculateFeesInvocations() { + FeeContext feeContext = mock(FeeContext.class); + FeeCalculator feeCalculator = mock(FeeCalculator.class); + when(feeContext.feeCalculator(SubType.DEFAULT)).thenReturn(feeCalculator); + + subject.calculateFees(feeContext); + + InOrder inOrder = inOrder(feeContext, feeCalculator); + inOrder.verify(feeContext).body(); + inOrder.verify(feeContext).feeCalculator(SubType.DEFAULT); + inOrder.verify(feeCalculator).legacyCalculate(any()); + } + @Test @DisplayName("File not found returns error") void fileIdNotFound() throws PreCheckException { @@ -141,8 +182,8 @@ void fileDoesntExist() { writableStore = new WritableFileStore(writableStates, testConfig, storeMetricsService); given(handleContext.writableStore(WritableFileStore.class)).willReturn(writableStore); - final var msg = assertThrows(HandleException.class, () -> subject.handle(handleContext)); - assertEquals(INVALID_FILE_ID, msg.getStatus()); + HandleException thrown = (HandleException) catchThrowable(() -> subject.handle(handleContext)); + assertThat(thrown.getStatus()).isEqualTo(INVALID_FILE_ID); } @Test @@ -151,13 +192,13 @@ void fileIsSystemFile() { given(handleContext.body()).willReturn(newSystemDeleteTxn()); final var existingFile = writableStore.get(fileSystemFileId); - assertTrue(existingFile.isPresent()); - assertFalse(existingFile.get().deleted()); + assertThat(existingFile.isPresent()).isTrue(); + assertThat(existingFile.get().deleted()).isFalse(); given(handleContext.writableStore(WritableFileStore.class)).willReturn(writableStore); - final var msg = assertThrows(HandleException.class, () -> subject.handle(handleContext)); - assertEquals(ENTITY_NOT_ALLOWED_TO_DELETE, msg.getStatus()); - assertFalse(existingFile.get().deleted()); + HandleException thrown = (HandleException) catchThrowable(() -> subject.handle(handleContext)); + assertThat(ENTITY_NOT_ALLOWED_TO_DELETE).isEqualTo(thrown.getStatus()); + assertThat(existingFile.get().deleted()).isFalse(); } @Test @@ -171,9 +212,8 @@ void keysDoesntExist() { writableStore = new WritableFileStore(writableStates, testConfig, storeMetricsService); given(handleContext.writableStore(WritableFileStore.class)).willReturn(writableStore); - final var msg = assertThrows(HandleException.class, () -> subject.handle(handleContext)); - - assertEquals(UNAUTHORIZED, msg.getStatus()); + HandleException thrown = (HandleException) catchThrowable(() -> subject.handle(handleContext)); + assertThat(thrown.getStatus()).isEqualTo(UNAUTHORIZED); } @Test @@ -182,8 +222,8 @@ void handleWorksAsExpectedWhenExpirationTimeIsExpired() { given(handleContext.body()).willReturn(newFileDeleteTxn()); final var existingFile = writableStore.get(fileId); - assertTrue(existingFile.isPresent()); - assertFalse(existingFile.get().deleted()); + assertThat(existingFile.isPresent()).isTrue(); + assertThat(existingFile.get().deleted()).isFalse(); given(handleContext.writableStore(WritableFileStore.class)).willReturn(writableStore); lenient().when(handleContext.consensusNow()).thenReturn(instant); @@ -192,7 +232,7 @@ void handleWorksAsExpectedWhenExpirationTimeIsExpired() { final var changedFile = writableStore.get(fileId); - assertEquals(changedFile, Optional.empty()); + assertThat(changedFile).isEqualTo(Optional.empty()); } @Test @@ -201,8 +241,8 @@ void handleWorksAsExpectedWhenExpirationTimeIsNotExpired() { given(handleContext.body()).willReturn(newFileDeleteTxn()); final var existingFile = writableStore.get(fileId); - assertTrue(existingFile.isPresent()); - assertFalse(existingFile.get().deleted()); + assertThat(existingFile.isPresent()).isTrue(); + assertThat(existingFile.get().deleted()).isFalse(); given(handleContext.writableStore(WritableFileStore.class)).willReturn(writableStore); lenient().when(handleContext.consensusNow()).thenReturn(instant); @@ -211,8 +251,8 @@ void handleWorksAsExpectedWhenExpirationTimeIsNotExpired() { final var changedFile = writableStore.get(fileId); - assertTrue(changedFile.isPresent()); - assertTrue(changedFile.get().deleted()); + assertThat(changedFile.isPresent()).isTrue(); + assertThat(changedFile.get().deleted()).isTrue(); } private Key mockPayerLookup() throws PreCheckException { diff --git a/hedera-node/hedera-file-service-impl/src/test/java/com/hedera/node/app/service/file/impl/test/handlers/FileSystemUndeleteTest.java b/hedera-node/hedera-file-service-impl/src/test/java/com/hedera/node/app/service/file/impl/test/handlers/FileSystemUndeleteTest.java index 73766f7e3ec1..76f309baeb0e 100644 --- a/hedera-node/hedera-file-service-impl/src/test/java/com/hedera/node/app/service/file/impl/test/handlers/FileSystemUndeleteTest.java +++ b/hedera-node/hedera-file-service-impl/src/test/java/com/hedera/node/app/service/file/impl/test/handlers/FileSystemUndeleteTest.java @@ -21,19 +21,22 @@ import static com.hedera.hapi.node.base.ResponseCodeEnum.UNAUTHORIZED; import static com.hedera.node.app.spi.fixtures.Assertions.assertThrowsPreCheck; import static com.hedera.test.utils.KeyUtils.A_COMPLEX_KEY; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertThrows; -import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.AssertionsForClassTypes.assertThatCode; +import static org.assertj.core.api.AssertionsForClassTypes.assertThatThrownBy; +import static org.assertj.core.api.AssertionsForClassTypes.catchThrowable; +import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.notNull; import static org.mockito.BDDMockito.given; import static org.mockito.Mock.Strictness.LENIENT; +import static org.mockito.Mockito.inOrder; import static org.mockito.Mockito.lenient; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; import com.hedera.hapi.node.base.FileID; import com.hedera.hapi.node.base.Key; +import com.hedera.hapi.node.base.SubType; import com.hedera.hapi.node.base.TransactionID; import com.hedera.hapi.node.file.SystemUndeleteTransactionBody; import com.hedera.hapi.node.state.file.File; @@ -46,6 +49,8 @@ import com.hedera.node.app.service.file.impl.handlers.FileSystemUndeleteHandler; import com.hedera.node.app.service.file.impl.test.FileTestBase; import com.hedera.node.app.service.token.ReadableAccountStore; +import com.hedera.node.app.spi.fees.FeeCalculator; +import com.hedera.node.app.spi.fees.FeeContext; import com.hedera.node.app.spi.metrics.StoreMetricsService; import com.hedera.node.app.spi.workflows.HandleContext; import com.hedera.node.app.spi.workflows.HandleException; @@ -63,6 +68,7 @@ import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InOrder; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; @@ -119,6 +125,41 @@ void setUp() { when(mockStoreFactory.getStore(ReadableAccountStore.class)).thenReturn(accountStore); } + @Test + @DisplayName("pureChecks throws exception when file id is null") + public void testPureChecksThrowsExceptionWhenFileIdIsNull() { + SystemUndeleteTransactionBody transactionBody = mock(SystemUndeleteTransactionBody.class); + TransactionBody transaction = mock(TransactionBody.class); + given(handleContext.body()).willReturn(transaction); + given(transaction.systemUndeleteOrThrow()).willReturn(transactionBody); + given(transactionBody.fileID()).willReturn(null); + + assertThatThrownBy(() -> subject.pureChecks(handleContext.body())).isInstanceOf(PreCheckException.class); + } + + @Test + @DisplayName("pureChecks does not throw exception when file id is not null") + public void testPureChecksDoesNotThrowExceptionWhenFileIdIsNotNull() { + given(handleContext.body()).willReturn(newFileUnDeleteTxn()); + + assertThatCode(() -> subject.pureChecks(handleContext.body())).doesNotThrowAnyException(); + } + + @Test + @DisplayName("calculateFees method invocations") + public void testCalculateFeesInvocations() { + FeeContext feeContext = mock(FeeContext.class); + FeeCalculator feeCalculator = mock(FeeCalculator.class); + when(feeContext.feeCalculator(SubType.DEFAULT)).thenReturn(feeCalculator); + + subject.calculateFees(feeContext); + + InOrder inOrder = inOrder(feeContext, feeCalculator); + inOrder.verify(feeContext).body(); + inOrder.verify(feeContext).feeCalculator(SubType.DEFAULT); + inOrder.verify(feeCalculator).legacyCalculate(any()); + } + @Test @DisplayName("File not found returns error") void fileIdNotFound() throws PreCheckException { @@ -142,8 +183,8 @@ void fileDoesntExist() { writableStore = new WritableFileStore(writableStates, testConfig, storeMetricsService); given(handleContext.writableStore(WritableFileStore.class)).willReturn(writableStore); - final var msg = assertThrows(HandleException.class, () -> subject.handle(handleContext)); - assertEquals(INVALID_FILE_ID, msg.getStatus()); + HandleException thrown = (HandleException) catchThrowable(() -> subject.handle(handleContext)); + assertThat(thrown.getStatus()).isEqualTo(INVALID_FILE_ID); } @Test @@ -152,12 +193,12 @@ void fileIsNotSystemFile() { given(handleContext.body()).willReturn(newSystemDeleteTxn()); final var existingFile = writableStore.get(fileId); - assertTrue(existingFile.isPresent()); - assertFalse(existingFile.get().deleted()); + assertThat(existingFile.isPresent()).isTrue(); + assertThat(existingFile.get().deleted()).isFalse(); given(handleContext.writableStore(WritableFileStore.class)).willReturn(writableStore); - final var msg = assertThrows(HandleException.class, () -> subject.handle(handleContext)); - assertEquals(ENTITY_NOT_ALLOWED_TO_DELETE, msg.getStatus()); + HandleException thrown = (HandleException) catchThrowable(() -> subject.handle(handleContext)); + assertThat(thrown.getStatus()).isEqualTo(ENTITY_NOT_ALLOWED_TO_DELETE); } @Test @@ -171,9 +212,8 @@ void keysDoesntExist() { writableStore = new WritableFileStore(writableStates, testConfig, storeMetricsService); given(handleContext.writableStore(WritableFileStore.class)).willReturn(writableStore); - final var msg = assertThrows(HandleException.class, () -> subject.handle(handleContext)); - - assertEquals(UNAUTHORIZED, msg.getStatus()); + HandleException thrown = (HandleException) catchThrowable(() -> subject.handle(handleContext)); + assertThat(thrown.getStatus()).isEqualTo(UNAUTHORIZED); } @Test @@ -182,8 +222,8 @@ void handleWorksAsExpectedWhenExpirationTimeIsExpired() { given(handleContext.body()).willReturn(newFileUnDeleteTxn()); final var existingFile = writableStore.get(fileId); - assertTrue(existingFile.isPresent()); - assertFalse(existingFile.get().deleted()); + assertThat(existingFile.isPresent()).isTrue(); + assertThat(existingFile.get().deleted()).isFalse(); given(handleContext.writableStore(WritableFileStore.class)).willReturn(writableStore); lenient().when(handleContext.consensusNow()).thenReturn(instant); @@ -192,7 +232,7 @@ void handleWorksAsExpectedWhenExpirationTimeIsExpired() { final var changedFile = writableStore.get(fileId); - assertEquals(changedFile, Optional.empty()); + assertThat(changedFile).isEqualTo(Optional.empty()); } @Test @@ -201,8 +241,8 @@ void handleWorksAsExpectedWhenExpirationTimeIsNotExpired() { given(handleContext.body()).willReturn(newFileUnDeleteTxn()); final var existingFile = writableStore.get(fileSystemFileId); - assertTrue(existingFile.isPresent()); - assertFalse(existingFile.get().deleted()); + assertThat(existingFile.isPresent()).isTrue(); + assertThat(existingFile.get().deleted()).isFalse(); given(handleContext.writableStore(WritableFileStore.class)).willReturn(writableStore); lenient().when(handleContext.consensusNow()).thenReturn(instant); @@ -211,8 +251,8 @@ void handleWorksAsExpectedWhenExpirationTimeIsNotExpired() { final var changedFile = writableStore.get(fileSystemFileId); - assertTrue(changedFile.isPresent()); - assertFalse(changedFile.get().deleted()); + assertThat(changedFile.isPresent()).isTrue(); + assertThat(changedFile.get().deleted()).isFalse(); } private Key mockPayerLookup() throws PreCheckException { diff --git a/hedera-node/hedera-file-service-impl/src/test/java/com/hedera/node/app/service/file/impl/test/schemas/FileSchemaTest.java b/hedera-node/hedera-file-service-impl/src/test/java/com/hedera/node/app/service/file/impl/test/schemas/FileSchemaTest.java index f5682b16ec91..f524a1c045cc 100644 --- a/hedera-node/hedera-file-service-impl/src/test/java/com/hedera/node/app/service/file/impl/test/schemas/FileSchemaTest.java +++ b/hedera-node/hedera-file-service-impl/src/test/java/com/hedera/node/app/service/file/impl/test/schemas/FileSchemaTest.java @@ -29,16 +29,16 @@ import com.hedera.node.app.service.file.impl.FileServiceImpl; import com.hedera.node.app.service.file.impl.schemas.InitialModFileGenesisSchema; import com.hedera.node.app.spi.fixtures.info.FakeNetworkInfo; -import com.hedera.node.app.spi.fixtures.state.MapWritableKVState; import com.hedera.node.app.spi.fixtures.state.MapWritableStates; import com.hedera.node.app.spi.info.NetworkInfo; import com.hedera.node.app.spi.state.EmptyReadableStates; -import com.hedera.node.app.spi.state.ReadableStates; import com.hedera.node.app.workflows.handle.record.GenesisRecordsConsensusHook; import com.hedera.node.app.workflows.handle.record.MigrationContextImpl; import com.hedera.node.config.ConfigProvider; import com.hedera.node.config.testfixtures.HederaTestConfigBuilder; import com.hedera.pbj.runtime.io.buffer.Bytes; +import com.swirlds.platform.test.fixtures.state.MapWritableKVState; +import com.swirlds.state.spi.ReadableStates; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; diff --git a/hedera-node/hedera-file-service/build.gradle.kts b/hedera-node/hedera-file-service/build.gradle.kts index 4674917a7dfb..eaa23ae6f03b 100644 --- a/hedera-node/hedera-file-service/build.gradle.kts +++ b/hedera-node/hedera-file-service/build.gradle.kts @@ -14,6 +14,9 @@ * limitations under the License. */ -plugins { id("com.hedera.hashgraph.conventions") } +plugins { + id("com.hedera.gradle.services") + id("com.hedera.gradle.services-publish") +} description = "Hedera File Service API" diff --git a/hedera-node/hedera-file-service/src/main/java/com/hedera/node/app/service/file/FileService.java b/hedera-node/hedera-file-service/src/main/java/com/hedera/node/app/service/file/FileService.java index 0888c4eaf12f..470ad74ce667 100644 --- a/hedera-node/hedera-file-service/src/main/java/com/hedera/node/app/service/file/FileService.java +++ b/hedera-node/hedera-file-service/src/main/java/com/hedera/node/app/service/file/FileService.java @@ -30,6 +30,9 @@ */ public interface FileService extends Service { + /** + * The name of the service + */ String NAME = "FileService"; @NonNull diff --git a/hedera-node/hedera-file-service/src/main/java/com/hedera/node/app/service/file/FileServiceDefinition.java b/hedera-node/hedera-file-service/src/main/java/com/hedera/node/app/service/file/FileServiceDefinition.java index c0aee1dc2dc8..81e0d355b93f 100644 --- a/hedera-node/hedera-file-service/src/main/java/com/hedera/node/app/service/file/FileServiceDefinition.java +++ b/hedera-node/hedera-file-service/src/main/java/com/hedera/node/app/service/file/FileServiceDefinition.java @@ -30,6 +30,9 @@ */ @SuppressWarnings("java:S6548") public final class FileServiceDefinition implements RpcServiceDefinition { + /** + * The singleton instance of the file service definition. + */ public static final FileServiceDefinition INSTANCE = new FileServiceDefinition(); private static final Set> methods = Set.of( diff --git a/hedera-node/hedera-file-service/src/main/java/com/hedera/node/app/service/file/ReadableFileStore.java b/hedera-node/hedera-file-service/src/main/java/com/hedera/node/app/service/file/ReadableFileStore.java index 3119de7f7efd..dc14bae4a196 100644 --- a/hedera-node/hedera-file-service/src/main/java/com/hedera/node/app/service/file/ReadableFileStore.java +++ b/hedera-node/hedera-file-service/src/main/java/com/hedera/node/app/service/file/ReadableFileStore.java @@ -43,7 +43,7 @@ public interface ReadableFileStore { /** * Returns the file needed from state, if not exist will return null. * @param id file id being looked up - * @return + * @return file if found, null otherwise */ @Nullable File getFileLeaf(@NonNull FileID id); diff --git a/hedera-node/hedera-file-service/src/main/java/com/hedera/node/app/service/file/ReadableUpgradeFileStore.java b/hedera-node/hedera-file-service/src/main/java/com/hedera/node/app/service/file/ReadableUpgradeFileStore.java index 556f4fe48654..289c48a832b4 100644 --- a/hedera-node/hedera-file-service/src/main/java/com/hedera/node/app/service/file/ReadableUpgradeFileStore.java +++ b/hedera-node/hedera-file-service/src/main/java/com/hedera/node/app/service/file/ReadableUpgradeFileStore.java @@ -18,9 +18,9 @@ import com.hedera.hapi.node.base.FileID; import com.hedera.hapi.node.state.file.File; -import com.hedera.node.app.spi.state.ReadableQueueState; import com.hedera.node.app.spi.state.Schema; import com.hedera.pbj.runtime.io.buffer.Bytes; +import com.swirlds.state.spi.ReadableQueueState; import edu.umd.cs.findbugs.annotations.NonNull; import edu.umd.cs.findbugs.annotations.Nullable; import java.io.IOException; @@ -57,7 +57,7 @@ public interface ReadableUpgradeFileStore { /** * Gets the full contents of the file from state. * @return The full contents of the file. - * @throws IOException + * @throws IOException if the file cannot be read. */ Bytes getFull(final FileID fileID) throws IOException; } diff --git a/hedera-node/hedera-file-service/src/main/java/module-info.java b/hedera-node/hedera-file-service/src/main/java/module-info.java index a912cc866ee6..c4675b5a2fde 100644 --- a/hedera-node/hedera-file-service/src/main/java/module-info.java +++ b/hedera-node/hedera-file-service/src/main/java/module-info.java @@ -3,6 +3,7 @@ uses com.hedera.node.app.service.file.FileService; + requires com.swirlds.state.api; requires transitive com.hedera.node.app.spi; requires transitive com.hedera.node.hapi; requires transitive com.hedera.pbj.runtime; diff --git a/hedera-node/hedera-mono-service/build.gradle.kts b/hedera-node/hedera-mono-service/build.gradle.kts index 356553143f4e..08236e6cd1ed 100644 --- a/hedera-node/hedera-mono-service/build.gradle.kts +++ b/hedera-node/hedera-mono-service/build.gradle.kts @@ -15,9 +15,10 @@ */ plugins { - id("com.hedera.hashgraph.conventions") - id("com.hedera.hashgraph.benchmark-conventions") - id("com.hedera.hashgraph.java-test-fixtures") + id("com.hedera.gradle.services") + id("com.hedera.gradle.services-publish") + id("com.hedera.gradle.benchmark") + id("com.hedera.gradle.java-test-fixtures") } description = "Hedera Application - MONO Service Implementation" diff --git a/hedera-node/hedera-mono-service/src/main/java/com/hedera/node/app/service/mono/cache/EntityMapWarmer.java b/hedera-node/hedera-mono-service/src/main/java/com/hedera/node/app/service/mono/cache/EntityMapWarmer.java index 0dd6ef1ca33a..dd9f8fe3c06d 100644 --- a/hedera-node/hedera-mono-service/src/main/java/com/hedera/node/app/service/mono/cache/EntityMapWarmer.java +++ b/hedera-node/hedera-mono-service/src/main/java/com/hedera/node/app/service/mono/cache/EntityMapWarmer.java @@ -220,13 +220,14 @@ private void doWarmups(Round round) { final PlatformTxnAccessor txnAccess; final TransactionBody txnBody; try { - txnAccess = PlatformTxnAccessor.from(txn.getContents()); + txnAccess = + PlatformTxnAccessor.from(txn.getApplicationPayload().toByteArray()); txnBody = txnAccess.getTxn(); } catch (InvalidProtocolBufferException e) { log.error( "Unable to parse transaction: {} \nErrant Txn: [{}]", e.getMessage(), - Hex.toHexString(txn.getContents())); + Hex.toHexString(txn.getApplicationPayload().toByteArray())); return; } diff --git a/hedera-node/hedera-mono-service/src/main/java/com/hedera/node/app/service/mono/fees/calculation/consensus/queries/GetTopicInfoResourceUsage.java b/hedera-node/hedera-mono-service/src/main/java/com/hedera/node/app/service/mono/fees/calculation/consensus/queries/GetTopicInfoResourceUsage.java index f87de47ca66e..3bde96a98519 100644 --- a/hedera-node/hedera-mono-service/src/main/java/com/hedera/node/app/service/mono/fees/calculation/consensus/queries/GetTopicInfoResourceUsage.java +++ b/hedera-node/hedera-mono-service/src/main/java/com/hedera/node/app/service/mono/fees/calculation/consensus/queries/GetTopicInfoResourceUsage.java @@ -26,6 +26,7 @@ import static com.hedera.node.app.hapi.utils.fee.FeeBuilder.getStateProofSize; import static com.hedera.node.app.service.mono.utils.EntityNum.fromTopicId; import static com.hedera.node.app.service.mono.utils.MiscUtils.asKeyUnchecked; +import static com.hedera.node.app.spi.fees.Fees.CONSTANT_FEE_DATA; import com.hedera.node.app.service.mono.context.primitives.StateView; import com.hedera.node.app.service.mono.fees.calculation.QueryResourceUsageEstimator; @@ -66,7 +67,7 @@ public FeeData usageGivenType(final Query query, final StateView view, final Res public FeeData usageGivenTypeAndTopic(@Nullable final MerkleTopic topic, final ResponseType responseType) { if (topic == null) { - return FeeData.getDefaultInstance(); + return CONSTANT_FEE_DATA; } final long bpr = BASIC_QUERY_RES_HEADER diff --git a/hedera-node/hedera-mono-service/src/main/java/com/hedera/node/app/service/mono/fees/calculation/contract/txns/ContractUpdateResourceUsage.java b/hedera-node/hedera-mono-service/src/main/java/com/hedera/node/app/service/mono/fees/calculation/contract/txns/ContractUpdateResourceUsage.java index f32180ea03b0..3b5563f7c37e 100644 --- a/hedera-node/hedera-mono-service/src/main/java/com/hedera/node/app/service/mono/fees/calculation/contract/txns/ContractUpdateResourceUsage.java +++ b/hedera-node/hedera-mono-service/src/main/java/com/hedera/node/app/service/mono/fees/calculation/contract/txns/ContractUpdateResourceUsage.java @@ -18,7 +18,10 @@ import static com.hedera.node.app.service.mono.fees.calculation.FeeCalcUtils.lookupAccountExpiry; import static com.hedera.node.app.service.mono.utils.EntityNum.fromContractId; +import static com.hedera.node.app.service.mono.utils.MiscUtils.asTimestamp; +import static com.hedera.node.app.spi.fees.Fees.CONSTANT_FEE_DATA; +import com.hedera.hapi.node.state.token.Account; import com.hedera.node.app.hapi.utils.fee.SigValueObj; import com.hedera.node.app.hapi.utils.fee.SmartContractFeeBuilder; import com.hedera.node.app.service.mono.context.primitives.StateView; @@ -26,6 +29,8 @@ import com.hederahashgraph.api.proto.java.FeeData; import com.hederahashgraph.api.proto.java.Timestamp; import com.hederahashgraph.api.proto.java.TransactionBody; +import edu.umd.cs.findbugs.annotations.NonNull; +import edu.umd.cs.findbugs.annotations.Nullable; import javax.inject.Inject; import javax.inject.Singleton; import org.apache.logging.log4j.LogManager; @@ -58,4 +63,20 @@ public FeeData usageGiven(TransactionBody txn, SigValueObj sigUsage, StateView v return FeeData.getDefaultInstance(); } } + + /** + * Returns the estimated resource usage for the given txn relative to the given state of the + * world. + * @param txn the txn in question + * @param sigUsage the signature usage + * @param contract the contract + * @return the estimated resource usage + */ + public FeeData usageGiven(@NonNull TransactionBody txn, @NonNull SigValueObj sigUsage, @Nullable Account contract) { + if (contract == null) { + return CONSTANT_FEE_DATA; + } + Timestamp expiry = asTimestamp(contract.expirationSecond()); + return usageEstimator.getContractUpdateTxFeeMatrices(txn, expiry, sigUsage); + } } diff --git a/hedera-node/hedera-mono-service/src/main/java/com/hedera/node/app/service/mono/fees/calculation/crypto/queries/GetAccountDetailsResourceUsage.java b/hedera-node/hedera-mono-service/src/main/java/com/hedera/node/app/service/mono/fees/calculation/crypto/queries/GetAccountDetailsResourceUsage.java index a935b6455b01..586c0ba3b424 100644 --- a/hedera-node/hedera-mono-service/src/main/java/com/hedera/node/app/service/mono/fees/calculation/crypto/queries/GetAccountDetailsResourceUsage.java +++ b/hedera-node/hedera-mono-service/src/main/java/com/hedera/node/app/service/mono/fees/calculation/crypto/queries/GetAccountDetailsResourceUsage.java @@ -17,6 +17,7 @@ package com.hedera.node.app.service.mono.fees.calculation.crypto.queries; import static com.hedera.node.app.service.mono.pbj.PbjConverter.fromPbj; +import static com.hedera.node.app.spi.fees.Fees.CONSTANT_FEE_DATA; import com.hedera.hapi.node.state.token.Account; import com.hedera.node.app.hapi.fees.usage.crypto.CryptoOpsUsage; @@ -91,7 +92,7 @@ public FeeData usageGiven(final Query query, final StateView view, final Map pbjRecords) { if (account == null) { - return FeeData.getDefaultInstance(); + return CONSTANT_FEE_DATA; } - final var records = - pbjRecords.stream().map(k -> PbjConverter.fromPbj(k)).toList(); + final var records = pbjRecords.stream().map(PbjConverter::fromPbj).toList(); return usageEstimator.getCryptoAccountRecordsQueryFeeMatrices(records, null); } diff --git a/hedera-node/hedera-mono-service/src/main/java/com/hedera/node/app/service/mono/fees/calculation/file/queries/GetFileInfoResourceUsage.java b/hedera-node/hedera-mono-service/src/main/java/com/hedera/node/app/service/mono/fees/calculation/file/queries/GetFileInfoResourceUsage.java index f190be998986..f6dfbb6a749d 100644 --- a/hedera-node/hedera-mono-service/src/main/java/com/hedera/node/app/service/mono/fees/calculation/file/queries/GetFileInfoResourceUsage.java +++ b/hedera-node/hedera-mono-service/src/main/java/com/hedera/node/app/service/mono/fees/calculation/file/queries/GetFileInfoResourceUsage.java @@ -17,6 +17,7 @@ package com.hedera.node.app.service.mono.fees.calculation.file.queries; import static com.hedera.node.app.service.mono.pbj.PbjConverter.fromPbj; +import static java.util.Objects.requireNonNull; import com.hedera.hapi.node.state.file.File; import com.hedera.node.app.hapi.fees.usage.file.ExtantFileContext; @@ -25,6 +26,8 @@ import com.hedera.node.app.service.mono.fees.calculation.QueryResourceUsageEstimator; import com.hederahashgraph.api.proto.java.FeeData; import com.hederahashgraph.api.proto.java.Query; +import edu.umd.cs.findbugs.annotations.NonNull; +import edu.umd.cs.findbugs.annotations.Nullable; import java.util.Map; import javax.inject.Inject; import javax.inject.Singleton; @@ -64,7 +67,11 @@ public FeeData usageGiven(final Query query, final StateView view, final Map HederaFunctionality.NETWORK_GET_EXECUTION_TIME; case NONE -> HederaFunctionality.NONE; case NodeStakeUpdate -> HederaFunctionality.NODE_STAKE_UPDATE; + case NodeCreate -> HederaFunctionality.NODE_CREATE; + case NodeUpdate -> HederaFunctionality.NODE_UPDATE; + case NodeDelete -> HederaFunctionality.NODE_DELETE; + case NodeGetInfo -> HederaFunctionality.NODE_GET_INFO; case ScheduleCreate -> HederaFunctionality.SCHEDULE_CREATE; case ScheduleDelete -> HederaFunctionality.SCHEDULE_DELETE; case ScheduleGetInfo -> HederaFunctionality.SCHEDULE_GET_INFO; @@ -411,6 +415,10 @@ public final class PbjConverter { .NetworkGetExecutionTime; case NONE -> com.hederahashgraph.api.proto.java.HederaFunctionality.NONE; case NODE_STAKE_UPDATE -> com.hederahashgraph.api.proto.java.HederaFunctionality.NodeStakeUpdate; + case NODE_CREATE -> com.hederahashgraph.api.proto.java.HederaFunctionality.NodeCreate; + case NODE_UPDATE -> com.hederahashgraph.api.proto.java.HederaFunctionality.NodeUpdate; + case NODE_DELETE -> com.hederahashgraph.api.proto.java.HederaFunctionality.NodeDelete; + case NODE_GET_INFO -> com.hederahashgraph.api.proto.java.HederaFunctionality.NodeGetInfo; case SCHEDULE_CREATE -> com.hederahashgraph.api.proto.java.HederaFunctionality.ScheduleCreate; case SCHEDULE_DELETE -> com.hederahashgraph.api.proto.java.HederaFunctionality.ScheduleDelete; case SCHEDULE_GET_INFO -> com.hederahashgraph.api.proto.java.HederaFunctionality.ScheduleGetInfo; @@ -773,6 +781,15 @@ public final class PbjConverter { case TOKEN_HAS_NO_METADATA_KEY -> ResponseCodeEnum.TOKEN_HAS_NO_METADATA_KEY; case MISSING_SERIAL_NUMBERS -> ResponseCodeEnum.MISSING_SERIAL_NUMBERS; case TOKEN_HAS_NO_ADMIN_KEY -> ResponseCodeEnum.TOKEN_HAS_NO_ADMIN_KEY; + case NODE_DELETED -> ResponseCodeEnum.NODE_DELETED; + case INVALID_NODE_ID -> ResponseCodeEnum.INVALID_NODE_ID; + case INVALID_GOSSIP_ENDPOINT -> ResponseCodeEnum.INVALID_GOSSIP_ENDPOINT; + case INVALID_NODE_ACCOUNT_ID -> ResponseCodeEnum.INVALID_NODE_ACCOUNT_ID; + case INVALID_NODE_DESCRIPTION -> ResponseCodeEnum.INVALID_NODE_DESCRIPTION; + case INVALID_SERVICE_ENDPOINT -> ResponseCodeEnum.INVALID_SERVICE_ENDPOINT; + case INVALID_GOSSIP_CAE_CERTIFICATE -> ResponseCodeEnum.INVALID_GOSSIP_CAE_CERTIFICATE; + case INVALID_GRPC_CERTIFICATE -> ResponseCodeEnum.INVALID_GRPC_CERTIFICATE; + case INVALID_MAX_AUTO_ASSOCIATIONS -> ResponseCodeEnum.INVALID_MAX_AUTO_ASSOCIATIONS; case UNRECOGNIZED -> throw new RuntimeException("UNRECOGNIZED Response code!"); }; } @@ -1309,6 +1326,20 @@ public static com.hederahashgraph.api.proto.java.ResponseCodeEnum fromPbj(@NonNu case MISSING_TOKEN_METADATA -> com.hederahashgraph.api.proto.java.ResponseCodeEnum.MISSING_TOKEN_METADATA; case MISSING_SERIAL_NUMBERS -> com.hederahashgraph.api.proto.java.ResponseCodeEnum.MISSING_SERIAL_NUMBERS; case TOKEN_HAS_NO_ADMIN_KEY -> com.hederahashgraph.api.proto.java.ResponseCodeEnum.TOKEN_HAS_NO_ADMIN_KEY; + case NODE_DELETED -> com.hederahashgraph.api.proto.java.ResponseCodeEnum.NODE_DELETED; + case INVALID_NODE_ID -> com.hederahashgraph.api.proto.java.ResponseCodeEnum.INVALID_NODE_ID; + case INVALID_GOSSIP_ENDPOINT -> com.hederahashgraph.api.proto.java.ResponseCodeEnum.INVALID_GOSSIP_ENDPOINT; + case INVALID_NODE_ACCOUNT_ID -> com.hederahashgraph.api.proto.java.ResponseCodeEnum.INVALID_NODE_ACCOUNT_ID; + case INVALID_NODE_DESCRIPTION -> com.hederahashgraph.api.proto.java.ResponseCodeEnum + .INVALID_NODE_DESCRIPTION; + case INVALID_SERVICE_ENDPOINT -> com.hederahashgraph.api.proto.java.ResponseCodeEnum + .INVALID_SERVICE_ENDPOINT; + case INVALID_GOSSIP_CAE_CERTIFICATE -> com.hederahashgraph.api.proto.java.ResponseCodeEnum + .INVALID_GOSSIP_CAE_CERTIFICATE; + case INVALID_GRPC_CERTIFICATE -> com.hederahashgraph.api.proto.java.ResponseCodeEnum + .INVALID_GRPC_CERTIFICATE; + case INVALID_MAX_AUTO_ASSOCIATIONS -> com.hederahashgraph.api.proto.java.ResponseCodeEnum + .INVALID_MAX_AUTO_ASSOCIATIONS; // case UNRECOGNIZED -> throw new RuntimeException("UNRECOGNIZED Response code!"); }; } diff --git a/hedera-node/hedera-mono-service/src/main/java/com/hedera/node/app/service/mono/state/initialization/BlocklistAccountCreator.java b/hedera-node/hedera-mono-service/src/main/java/com/hedera/node/app/service/mono/state/initialization/BlocklistAccountCreator.java index fb67e9e7fe36..6e8b78487454 100644 --- a/hedera-node/hedera-mono-service/src/main/java/com/hedera/node/app/service/mono/state/initialization/BlocklistAccountCreator.java +++ b/hedera-node/hedera-mono-service/src/main/java/com/hedera/node/app/service/mono/state/initialization/BlocklistAccountCreator.java @@ -294,6 +294,7 @@ public void forgetCreatedBlockedAccounts() { } /** + * Represents a record containing the EVM address and memo of a blocked account. * @param evmAddress the EVM address of the blocked account * @param memo the memo of the blocked account */ diff --git a/hedera-node/hedera-mono-service/src/main/java/com/hedera/node/app/service/mono/state/migration/AccountStateTranslator.java b/hedera-node/hedera-mono-service/src/main/java/com/hedera/node/app/service/mono/state/migration/AccountStateTranslator.java index 6deedd107676..89596f1397c9 100644 --- a/hedera-node/hedera-mono-service/src/main/java/com/hedera/node/app/service/mono/state/migration/AccountStateTranslator.java +++ b/hedera-node/hedera-mono-service/src/main/java/com/hedera/node/app/service/mono/state/migration/AccountStateTranslator.java @@ -16,6 +16,7 @@ package com.hedera.node.app.service.mono.state.migration; +import static com.hedera.node.app.service.mono.state.migration.ContractStateMigrator.bytesFromInts; import static java.util.Objects.requireNonNull; import com.google.protobuf.ByteString; @@ -133,11 +134,7 @@ public static Account accountFromMerkle( } public static Account accountFromOnDiskAccount(@NonNull final OnDiskAccount account) { - final var firstContractStorageKey = account.getFirstContractStorageKey() == null - ? Bytes.EMPTY - : Bytes.wrap(account.getFirstContractStorageKey() - .getKeyAsBigInteger() - .toByteArray()); + final var firstContractStorageKey = bytesFromInts(account.getFirstStorageKey()); final var stakedAccountId = account.getStakedId() > 0 ? AccountID.newBuilder().accountNum(account.getStakedId()).build() : null; diff --git a/hedera-node/hedera-mono-service/src/main/java/com/hedera/node/app/service/mono/state/migration/ContractStateMigrator.java b/hedera-node/hedera-mono-service/src/main/java/com/hedera/node/app/service/mono/state/migration/ContractStateMigrator.java index ac6042fac916..5b5cbaa37782 100644 --- a/hedera-node/hedera-mono-service/src/main/java/com/hedera/node/app/service/mono/state/migration/ContractStateMigrator.java +++ b/hedera-node/hedera-mono-service/src/main/java/com/hedera/node/app/service/mono/state/migration/ContractStateMigrator.java @@ -29,9 +29,9 @@ import com.hedera.node.app.service.mono.state.virtual.ContractKey; import com.hedera.node.app.service.mono.state.virtual.IterableContractValue; import com.hedera.node.app.service.mono.utils.NonAtomicReference; -import com.hedera.node.app.spi.state.WritableKVState; import com.hedera.pbj.runtime.io.buffer.Bytes; import com.swirlds.common.threading.interrupt.InterruptableConsumer; +import com.swirlds.state.spi.WritableKVState; import edu.umd.cs.findbugs.annotations.NonNull; import edu.umd.cs.findbugs.annotations.Nullable; import java.nio.ByteBuffer; diff --git a/hedera-node/hedera-mono-service/src/main/java/com/hedera/node/app/service/mono/store/models/Account.java b/hedera-node/hedera-mono-service/src/main/java/com/hedera/node/app/service/mono/store/models/Account.java index e16bb3a21702..e5eab33def00 100644 --- a/hedera-node/hedera-mono-service/src/main/java/com/hedera/node/app/service/mono/store/models/Account.java +++ b/hedera-node/hedera-mono-service/src/main/java/com/hedera/node/app/service/mono/store/models/Account.java @@ -252,7 +252,7 @@ public List associateWith( public void dissociateUsing(final List dissociations, final OptionValidator validator) { for (final var dissociation : dissociations) { validateTrue(id.equals(dissociation.dissociatingAccountId()), FAIL_INVALID); - dissociation.updateModelRelsSubjectTo(validator); + dissociation.updateModelRelsSubjectTo(); final var pastRel = dissociation.dissociatingAccountRel(); if (pastRel.isAutomaticAssociation()) { decrementUsedAutomaticAssociations(); diff --git a/hedera-node/hedera-mono-service/src/main/java/com/hedera/node/app/service/mono/stream/RecordStreamManager.java b/hedera-node/hedera-mono-service/src/main/java/com/hedera/node/app/service/mono/stream/RecordStreamManager.java index 380f6e9a5aba..5b4934a6e1ac 100644 --- a/hedera-node/hedera-mono-service/src/main/java/com/hedera/node/app/service/mono/stream/RecordStreamManager.java +++ b/hedera-node/hedera-mono-service/src/main/java/com/hedera/node/app/service/mono/stream/RecordStreamManager.java @@ -132,7 +132,7 @@ public RecordStreamManager( Files.createDirectories(Paths.get(nodeScopedSidecarDir)); protobufStreamFileWriter = new RecordStreamFileWriter( nodeScopedRecordLogDir, - platform, + platform::sign, streamType, nodeScopedSidecarDir, globalDynamicProperties.getSidecarMaxSizeMb() * MB_TO_BYTES, diff --git a/hedera-node/hedera-mono-service/src/main/java/com/hedera/node/app/service/mono/txns/span/ExpandHandleSpan.java b/hedera-node/hedera-mono-service/src/main/java/com/hedera/node/app/service/mono/txns/span/ExpandHandleSpan.java index 07f92b635b9c..458383fc9aa7 100644 --- a/hedera-node/hedera-mono-service/src/main/java/com/hedera/node/app/service/mono/txns/span/ExpandHandleSpan.java +++ b/hedera-node/hedera-mono-service/src/main/java/com/hedera/node/app/service/mono/txns/span/ExpandHandleSpan.java @@ -58,7 +58,7 @@ public ExpandHandleSpan(final SpanMapManager spanMapManager, final AccessorFacto } public SwirldsTxnAccessor track(final Transaction transaction) throws InvalidProtocolBufferException { - final var accessor = spanAccessorFor(transaction.getContents()); + final var accessor = spanAccessorFor(transaction.getApplicationPayload().toByteArray()); transaction.setMetadata(accessor); return accessor; } @@ -70,7 +70,7 @@ public SwirldsTxnAccessor accessorFor(final Transaction transaction) throws Inva transaction.setMetadata(null); return cachedAccessor; } else { - return spanAccessorFor(transaction.getContents()); + return spanAccessorFor(transaction.getApplicationPayload().toByteArray()); } } diff --git a/hedera-node/hedera-mono-service/src/main/java/com/hedera/node/app/service/mono/txns/token/process/Dissociation.java b/hedera-node/hedera-mono-service/src/main/java/com/hedera/node/app/service/mono/txns/token/process/Dissociation.java index 8445ff05c75d..34ebb6b4b42a 100644 --- a/hedera-node/hedera-mono-service/src/main/java/com/hedera/node/app/service/mono/txns/token/process/Dissociation.java +++ b/hedera-node/hedera-mono-service/src/main/java/com/hedera/node/app/service/mono/txns/token/process/Dissociation.java @@ -18,19 +18,18 @@ import static com.hedera.node.app.service.evm.store.tokens.TokenType.NON_FUNGIBLE_UNIQUE; import static com.hedera.node.app.service.evm.utils.ValidationUtils.validateFalse; -import static com.hedera.node.app.service.evm.utils.ValidationUtils.validateTrue; import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.ACCOUNT_FROZEN_FOR_TOKEN; import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.ACCOUNT_IS_TREASURY; import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.ACCOUNT_STILL_OWNS_NFTS; import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.TRANSACTION_REQUIRES_ZERO_TOKEN_BALANCES; import com.google.common.base.MoreObjects; +import com.hedera.node.app.service.evm.exceptions.InvalidTransactionException; import com.hedera.node.app.service.mono.store.TypedTokenStore; import com.hedera.node.app.service.mono.store.models.Account; import com.hedera.node.app.service.mono.store.models.Id; import com.hedera.node.app.service.mono.store.models.Token; import com.hedera.node.app.service.mono.store.models.TokenRelationship; -import com.hedera.node.app.service.mono.txns.validation.OptionValidator; import edu.umd.cs.findbugs.annotations.Nullable; import java.util.List; import java.util.Objects; @@ -106,15 +105,14 @@ public TokenRelationship dissociatedTokenTreasuryRel() { * not own any fungible units or NFTs of the token. * * - * @param validator the validator use to check for an expired token */ - public void updateModelRelsSubjectTo(final OptionValidator validator) { + public void updateModelRelsSubjectTo() { Objects.requireNonNull(dissociatingAccountRel); final var token = dissociatingAccountRel.getToken(); if (token.isDeleted() || token.isBelievedToHaveBeenAutoRemoved()) { updateModelsForDissociationFromDeletedOrRemovedToken(); } else { - updateModelsForDissociationFromActiveToken(validator); + updateModelsForDissociationFromActiveToken(); } dissociatingAccountRel.markAsDestroyed(); modelsAreUpdated = true; @@ -132,7 +130,7 @@ private void updateModelsForDissociationFromDeletedOrRemovedToken() { } } - private void updateModelsForDissociationFromActiveToken(final OptionValidator validator) { + private void updateModelsForDissociationFromActiveToken() { Objects.requireNonNull(dissociatedTokenTreasuryRel); final var token = dissociatingAccountRel.getToken(); final var isAccountTreasuryOfDissociatedToken = @@ -142,17 +140,11 @@ private void updateModelsForDissociationFromActiveToken(final OptionValidator va final var balance = dissociatingAccountRel.getBalance(); if (balance > 0L) { - validateFalse(token.getType() == NON_FUNGIBLE_UNIQUE, ACCOUNT_STILL_OWNS_NFTS); - - final var isTokenExpired = !validator.isAfterConsensusSecond(token.getExpiry()); - validateTrue(isTokenExpired, TRANSACTION_REQUIRES_ZERO_TOKEN_BALANCES); - - /* If the fungible common token is expired, we automatically transfer the - dissociating account's balance back to the treasury. */ - dissociatingAccountRel.setBalance(0L); - final var newTreasuryBalance = dissociatedTokenTreasuryRel.getBalance() + balance; - dissociatedTokenTreasuryRel.setBalance(newTreasuryBalance); - expiredTokenTreasuryReceivedBalance = true; + if (token.getType() == NON_FUNGIBLE_UNIQUE) { + throw new InvalidTransactionException(ACCOUNT_STILL_OWNS_NFTS); + } else { + throw new InvalidTransactionException(TRANSACTION_REQUIRES_ZERO_TOKEN_BALANCES); + } } } diff --git a/hedera-node/hedera-mono-service/src/main/java/com/hedera/node/app/service/mono/utils/MiscUtils.java b/hedera-node/hedera-mono-service/src/main/java/com/hedera/node/app/service/mono/utils/MiscUtils.java index 694e1dd8134c..bc62bb299657 100644 --- a/hedera-node/hedera-mono-service/src/main/java/com/hedera/node/app/service/mono/utils/MiscUtils.java +++ b/hedera-node/hedera-mono-service/src/main/java/com/hedera/node/app/service/mono/utils/MiscUtils.java @@ -775,14 +775,17 @@ public static long getGasLimitForContractTx( case ContractCall -> txn.getContractCall().getGas(); case EthereumTransaction -> getEthData != null ? getEthData.get().gasLimit() - : EthTxData.populateEthTxData(txn.getEthereumTransaction() - .getEthereumData() - .toByteArray()) - .gasLimit(); + : getGasLimitFromEthTxData(txn); default -> 0L; }; } + private static long getGasLimitFromEthTxData(final TransactionBody txn) { + final var ethTxData = EthTxData.populateEthTxData( + txn.getEthereumTransaction().getEthereumData().toByteArray()); + return ethTxData != null ? ethTxData.gasLimit() : 0L; + } + /** * Attempts to parse a {@code Key} from given alias {@code ByteString}. If the Key is of type * Ed25519 or ECDSA(secp256k1), returns true if it is a valid key; and false otherwise. diff --git a/hedera-node/hedera-mono-service/src/main/java/module-info.java b/hedera-node/hedera-mono-service/src/main/java/module-info.java index 561ffb8fa6d3..491e5d85a1dd 100644 --- a/hedera-node/hedera-mono-service/src/main/java/module-info.java +++ b/hedera-node/hedera-mono-service/src/main/java/module-info.java @@ -253,13 +253,13 @@ requires transitive com.hedera.node.app.hapi.fees; requires transitive com.hedera.node.app.hapi.utils; - requires transitive com.hedera.node.app.service.evm; requires transitive com.hedera.node.app.service.token; requires transitive com.hedera.node.app.spi; requires transitive com.hedera.node.hapi; requires transitive com.fasterxml.jackson.databind; requires transitive com.google.common; requires transitive com.google.protobuf; + requires transitive com.hedera.evm; requires transitive com.hedera.pbj.runtime; requires transitive com.swirlds.common; requires transitive com.swirlds.fchashmap; @@ -268,6 +268,7 @@ requires transitive com.swirlds.merkledb; requires transitive com.swirlds.metrics.api; requires transitive com.swirlds.platform.core; + requires transitive com.swirlds.state.api; requires transitive com.swirlds.virtualmap; requires transitive dagger; requires transitive grpc.netty; diff --git a/hedera-node/hedera-mono-service/src/test/java/com/hedera/node/app/service/mono/fees/calculation/consensus/queries/GetMerkleTopicInfoResourceUsageTest.java b/hedera-node/hedera-mono-service/src/test/java/com/hedera/node/app/service/mono/fees/calculation/consensus/queries/GetMerkleTopicInfoResourceUsageTest.java index e036aa60c98f..0184552a3272 100644 --- a/hedera-node/hedera-mono-service/src/test/java/com/hedera/node/app/service/mono/fees/calculation/consensus/queries/GetMerkleTopicInfoResourceUsageTest.java +++ b/hedera-node/hedera-mono-service/src/test/java/com/hedera/node/app/service/mono/fees/calculation/consensus/queries/GetMerkleTopicInfoResourceUsageTest.java @@ -16,6 +16,7 @@ package com.hedera.node.app.service.mono.fees.calculation.consensus.queries; +import static com.hedera.node.app.spi.fees.Fees.CONSTANT_FEE_DATA; import static com.hedera.test.utils.IdUtils.asTopic; import static com.hederahashgraph.api.proto.java.ResponseType.ANSWER_ONLY; import static com.hederahashgraph.api.proto.java.ResponseType.COST_ANSWER; @@ -79,7 +80,7 @@ void recognizesApplicableQuery() { void throwsIaeWhenTopicDoesNotExist() { final var query = topicInfoQuery(topicId, ANSWER_ONLY); - assertSame(FeeData.getDefaultInstance(), subject.usageGiven(query, view)); + assertSame(CONSTANT_FEE_DATA, subject.usageGiven(query, view)); } @ParameterizedTest diff --git a/hedera-node/hedera-mono-service/src/test/java/com/hedera/node/app/service/mono/fees/calculation/contract/txns/ContractUpdateResourceUsageTest.java b/hedera-node/hedera-mono-service/src/test/java/com/hedera/node/app/service/mono/fees/calculation/contract/txns/ContractUpdateResourceUsageTest.java index 5b8a4b0c7f69..360c9466d5bf 100644 --- a/hedera-node/hedera-mono-service/src/test/java/com/hedera/node/app/service/mono/fees/calculation/contract/txns/ContractUpdateResourceUsageTest.java +++ b/hedera-node/hedera-mono-service/src/test/java/com/hedera/node/app/service/mono/fees/calculation/contract/txns/ContractUpdateResourceUsageTest.java @@ -98,7 +98,7 @@ void delegatesToCorrectEstimate() throws Exception { @Test void returnsDefaultUsageOnException() throws Exception { // when: - final FeeData actual = subject.usageGiven(contractUpdateTxn, sigUsage, null); + final FeeData actual = subject.usageGiven(contractUpdateTxn, sigUsage, (StateView) null); // then: assertEquals(FeeData.getDefaultInstance(), actual); diff --git a/hedera-node/hedera-mono-service/src/test/java/com/hedera/node/app/service/mono/sigs/order/SigRequirementsTest.java b/hedera-node/hedera-mono-service/src/test/java/com/hedera/node/app/service/mono/sigs/order/SigRequirementsTest.java index 7454a17846df..32fe2a3de241 100644 --- a/hedera-node/hedera-mono-service/src/test/java/com/hedera/node/app/service/mono/sigs/order/SigRequirementsTest.java +++ b/hedera-node/hedera-mono-service/src/test/java/com/hedera/node/app/service/mono/sigs/order/SigRequirementsTest.java @@ -259,13 +259,13 @@ import static com.hedera.test.factories.scenarios.TokenUpdateScenarios.UPDATE_REPLACING_TREASURY_AS_CUSTOM_PAYER; import static com.hedera.test.factories.scenarios.TokenUpdateScenarios.UPDATE_REPLACING_TREASURY_AS_PAYER; import static com.hedera.test.factories.scenarios.TokenUpdateScenarios.UPDATE_REPLACING_WITH_MISSING_TREASURY; -import static com.hedera.test.factories.scenarios.TokenUpdateScenarios.UPDATE_WITH_FREEZE_KEYED_TOKEN; -import static com.hedera.test.factories.scenarios.TokenUpdateScenarios.UPDATE_WITH_KYC_KEYED_TOKEN; +import static com.hedera.test.factories.scenarios.TokenUpdateScenarios.UPDATE_WITH_FREEZE_KEYED_TOKEN_REPLACEMENT_KEY_NOT_REQUIRED; +import static com.hedera.test.factories.scenarios.TokenUpdateScenarios.UPDATE_WITH_KYC_KEYED_TOKEN_REPLACEMENT_KEY_NOT_REQUIRED; import static com.hedera.test.factories.scenarios.TokenUpdateScenarios.UPDATE_WITH_MISSING_TOKEN; import static com.hedera.test.factories.scenarios.TokenUpdateScenarios.UPDATE_WITH_MISSING_TOKEN_ADMIN_KEY; -import static com.hedera.test.factories.scenarios.TokenUpdateScenarios.UPDATE_WITH_NO_KEYS_AFFECTED; -import static com.hedera.test.factories.scenarios.TokenUpdateScenarios.UPDATE_WITH_SUPPLY_KEYED_TOKEN; -import static com.hedera.test.factories.scenarios.TokenUpdateScenarios.UPDATE_WITH_WIPE_KEYED_TOKEN; +import static com.hedera.test.factories.scenarios.TokenUpdateScenarios.UPDATE_WITH_NO_FIELDS_CHANGED; +import static com.hedera.test.factories.scenarios.TokenUpdateScenarios.UPDATE_WITH_SUPPLY_KEYED_TOKEN_REPLACEMENT_KEY_NOT_REQUIRED; +import static com.hedera.test.factories.scenarios.TokenUpdateScenarios.UPDATE_WITH_WIPE_KEYED_TOKEN_REPLACEMENT_KEY_NOT_REQUIRED; import static com.hedera.test.factories.scenarios.TokenWipeScenarios.VALID_WIPE_WITH_EXTANT_TOKEN; import static com.hedera.test.factories.scenarios.TxnHandlingScenario.CURRENTLY_UNUSED_ALIAS; import static com.hedera.test.factories.scenarios.TxnHandlingScenario.CUSTOM_PAYER_ACCOUNT; @@ -4567,7 +4567,7 @@ void getsTokenWipeWithRelevantKey() throws Throwable { @Test void getsUpdateNoSpecialKeys() throws Throwable { // given: - setupFor(UPDATE_WITH_NO_KEYS_AFFECTED); + setupFor(UPDATE_WITH_NO_FIELDS_CHANGED); // when: var summary = subject.keysForOtherParties(txn, summaryFactory); @@ -4580,7 +4580,7 @@ void getsUpdateNoSpecialKeys() throws Throwable { @Test void getsUpdateNoSpecialKeysWithCustomPayer() throws Throwable { // given: - setupFor(UPDATE_WITH_NO_KEYS_AFFECTED); + setupFor(UPDATE_WITH_NO_FIELDS_CHANGED); // when: var summary = subject.keysForOtherParties(txn, summaryFactory, null, CUSTOM_PAYER_ACCOUNT); @@ -4593,7 +4593,7 @@ void getsUpdateNoSpecialKeysWithCustomPayer() throws Throwable { @Test void getsUpdateWithWipe() throws Throwable { // given: - setupFor(UPDATE_WITH_WIPE_KEYED_TOKEN); + setupFor(UPDATE_WITH_WIPE_KEYED_TOKEN_REPLACEMENT_KEY_NOT_REQUIRED); // when: var summary = subject.keysForOtherParties(txn, summaryFactory); @@ -4606,7 +4606,7 @@ void getsUpdateWithWipe() throws Throwable { @Test void getsUpdateWithWipeWithCustomPayer() throws Throwable { // given: - setupFor(UPDATE_WITH_WIPE_KEYED_TOKEN); + setupFor(UPDATE_WITH_WIPE_KEYED_TOKEN_REPLACEMENT_KEY_NOT_REQUIRED); // when: var summary = subject.keysForOtherParties(txn, summaryFactory, null, CUSTOM_PAYER_ACCOUNT); @@ -4619,7 +4619,7 @@ void getsUpdateWithWipeWithCustomPayer() throws Throwable { @Test void getsUpdateWithSupply() throws Throwable { // given: - setupFor(UPDATE_WITH_SUPPLY_KEYED_TOKEN); + setupFor(UPDATE_WITH_SUPPLY_KEYED_TOKEN_REPLACEMENT_KEY_NOT_REQUIRED); // when: var summary = subject.keysForOtherParties(txn, summaryFactory); @@ -4632,7 +4632,7 @@ void getsUpdateWithSupply() throws Throwable { @Test void getsUpdateWithSupplyWithCustomPayer() throws Throwable { // given: - setupFor(UPDATE_WITH_SUPPLY_KEYED_TOKEN); + setupFor(UPDATE_WITH_SUPPLY_KEYED_TOKEN_REPLACEMENT_KEY_NOT_REQUIRED); // when: var summary = subject.keysForOtherParties(txn, summaryFactory, null, CUSTOM_PAYER_ACCOUNT); @@ -4645,7 +4645,7 @@ void getsUpdateWithSupplyWithCustomPayer() throws Throwable { @Test void getsUpdateWithKyc() throws Throwable { // given: - setupFor(UPDATE_WITH_KYC_KEYED_TOKEN); + setupFor(UPDATE_WITH_KYC_KEYED_TOKEN_REPLACEMENT_KEY_NOT_REQUIRED); // when: var summary = subject.keysForOtherParties(txn, summaryFactory); @@ -4658,7 +4658,7 @@ void getsUpdateWithKyc() throws Throwable { @Test void getsUpdateWithKycWithCustomPayer() throws Throwable { // given: - setupFor(UPDATE_WITH_KYC_KEYED_TOKEN); + setupFor(UPDATE_WITH_KYC_KEYED_TOKEN_REPLACEMENT_KEY_NOT_REQUIRED); // when: var summary = subject.keysForOtherParties(txn, summaryFactory, null, CUSTOM_PAYER_ACCOUNT); @@ -4780,7 +4780,7 @@ void getsUpdateWithNewTreasuryAsPayerWithCustomCustomPayer() throws Throwable { @Test void getsUpdateWithFreeze() throws Throwable { // given: - setupFor(UPDATE_WITH_FREEZE_KEYED_TOKEN); + setupFor(UPDATE_WITH_FREEZE_KEYED_TOKEN_REPLACEMENT_KEY_NOT_REQUIRED); // when: var summary = subject.keysForOtherParties(txn, summaryFactory); @@ -4793,7 +4793,7 @@ void getsUpdateWithFreeze() throws Throwable { @Test void getsUpdateWithFreezeWithCustomPayer() throws Throwable { // given: - setupFor(UPDATE_WITH_FREEZE_KEYED_TOKEN); + setupFor(UPDATE_WITH_FREEZE_KEYED_TOKEN_REPLACEMENT_KEY_NOT_REQUIRED); // when: var summary = subject.keysForOtherParties(txn, summaryFactory, null, CUSTOM_PAYER_ACCOUNT); diff --git a/hedera-node/hedera-mono-service/src/test/java/com/hedera/node/app/service/mono/store/models/AccountTest.java b/hedera-node/hedera-mono-service/src/test/java/com/hedera/node/app/service/mono/store/models/AccountTest.java index dc2866197e71..33e705bd671d 100644 --- a/hedera-node/hedera-mono-service/src/test/java/com/hedera/node/app/service/mono/store/models/AccountTest.java +++ b/hedera-node/hedera-mono-service/src/test/java/com/hedera/node/app/service/mono/store/models/AccountTest.java @@ -224,7 +224,7 @@ void dissociationOnLastAssociatedTokenWorks() { subject.dissociateUsing(List.of(dissociationRel), validator); // then: - verify(dissociationRel).updateModelRelsSubjectTo(validator); + verify(dissociationRel).updateModelRelsSubjectTo(); assertEquals(numPositiveBalances - 1, subject.getNumPositiveBalances()); assertEquals(numAssociations - 1, subject.getNumAssociations()); assertEquals(alreadyUsedAutoAssociations - 1, subject.getAlreadyUsedAutomaticAssociations()); @@ -251,7 +251,7 @@ void dissociationWorks() { subject.dissociateUsing(List.of(dissociationRel), validator); // then: - verify(dissociationRel).updateModelRelsSubjectTo(validator); + verify(dissociationRel).updateModelRelsSubjectTo(); assertEquals(numAssociations - 1, subject.getNumAssociations()); assertEquals(numPositiveBalances, subject.getNumPositiveBalances()); assertEquals(alreadyUsedAutoAssociations - 1, subject.getAlreadyUsedAutomaticAssociations()); @@ -279,7 +279,7 @@ void dissociatingWorksWithNonZeroBalance() { subject.dissociateUsing(List.of(dissociationRel), validator); // then: - verify(dissociationRel).updateModelRelsSubjectTo(validator); + verify(dissociationRel).updateModelRelsSubjectTo(); assertEquals(numAssociations - 1, subject.getNumAssociations()); assertEquals(numPositiveBalances, subject.getNumPositiveBalances()); assertEquals(alreadyUsedAutoAssociations - 1, subject.getAlreadyUsedAutomaticAssociations()); @@ -308,7 +308,7 @@ void treasuryDissociationWorks() { subject.dissociateUsing(List.of(dissociationRel), validator); // then: - verify(dissociationRel).updateModelRelsSubjectTo(validator); + verify(dissociationRel).updateModelRelsSubjectTo(); assertEquals(numAssociations - 1, subject.getNumAssociations()); assertEquals(numPositiveBalances, subject.getNumPositiveBalances()); assertEquals(alreadyUsedAutoAssociations - 1, subject.getAlreadyUsedAutomaticAssociations()); @@ -333,7 +333,7 @@ void dissociatingOnlyAssociationWorks() { subject.dissociateUsing(List.of(dissociationRel), validator); // then: - verify(dissociationRel).updateModelRelsSubjectTo(validator); + verify(dissociationRel).updateModelRelsSubjectTo(); assertEquals(alreadyUsedAutoAssociations - 1, subject.getAlreadyUsedAutomaticAssociations()); } diff --git a/hedera-node/hedera-mono-service/src/test/java/com/hedera/node/app/service/mono/txns/token/process/DissociationTest.java b/hedera-node/hedera-mono-service/src/test/java/com/hedera/node/app/service/mono/txns/token/process/DissociationTest.java index 803e065b875c..147f87dbde26 100644 --- a/hedera-node/hedera-mono-service/src/test/java/com/hedera/node/app/service/mono/txns/token/process/DissociationTest.java +++ b/hedera-node/hedera-mono-service/src/test/java/com/hedera/node/app/service/mono/txns/token/process/DissociationTest.java @@ -37,7 +37,6 @@ import com.hedera.node.app.service.mono.store.models.Id; import com.hedera.node.app.service.mono.store.models.Token; import com.hedera.node.app.service.mono.store.models.TokenRelationship; -import com.hedera.node.app.service.mono.txns.validation.OptionValidator; import com.hederahashgraph.api.proto.java.ResponseCodeEnum; import java.util.ArrayList; import java.util.List; @@ -52,14 +51,11 @@ class DissociationTest { private static final Id accountId = new Id(1, 2, 3); private static final Id tokenId = new Id(2, 3, 4); private static final Id treasuryId = new Id(3, 4, 5); - private static final Id veryAncientTreasuryId = new Id(0, 0, 3); private static final Account account = new Account(accountId); private static final Account treasury = new Account(treasuryId); - private static final Account ancientTreasury = new Account(veryAncientTreasuryId); private final Token token = new Token(tokenId); private final TokenRelationship dissociatingAccountRel = new TokenRelationship(token, account); private final TokenRelationship dissociatedTokenTreasuryRel = new TokenRelationship(token, treasury); - private final TokenRelationship ancientTokenTreasuryRel = new TokenRelationship(token, ancientTreasury); { token.setTreasury(treasury); @@ -71,9 +67,6 @@ class DissociationTest { @Mock private TypedTokenStore tokenStore; - @Mock - private OptionValidator validator; - @Test void loadsExpectedRelsForExtantToken() { given(tokenStore.loadPossiblyDeletedOrAutoRemovedToken(tokenId)).willReturn(token); @@ -131,7 +124,7 @@ void processesAutoRemovedTokenAsExpected() { final List changed = new ArrayList<>(); token.markAutoRemoved(); - subject.updateModelRelsSubjectTo(validator); + subject.updateModelRelsSubjectTo(); subject.addUpdatedModelRelsTo(changed); assertEquals(1, changed.size()); @@ -145,7 +138,7 @@ void rejectsDissociatingTokenTreasury() { final var subject = new Dissociation(dissociatingAccountRel, dissociatedTokenTreasuryRel); - assertFailsWith(() -> subject.updateModelRelsSubjectTo(validator), ACCOUNT_IS_TREASURY); + assertFailsWith(() -> subject.updateModelRelsSubjectTo(), ACCOUNT_IS_TREASURY); } @Test @@ -154,7 +147,7 @@ void rejectsDissociatingFrozenAccount() { final var subject = new Dissociation(dissociatingAccountRel, dissociatedTokenTreasuryRel); - assertFailsWith(() -> subject.updateModelRelsSubjectTo(validator), ACCOUNT_FROZEN_FOR_TOKEN); + assertFailsWith(() -> subject.updateModelRelsSubjectTo(), ACCOUNT_FROZEN_FOR_TOKEN); } @Test @@ -162,7 +155,7 @@ void normalCaseOnlyUpdatesDissociatingRel() { final var subject = new Dissociation(dissociatingAccountRel, dissociatedTokenTreasuryRel); final List accum = new ArrayList<>(); - subject.updateModelRelsSubjectTo(validator); + subject.updateModelRelsSubjectTo(); subject.addUpdatedModelRelsTo(accum); assertEquals(1, accum.size()); @@ -174,11 +167,10 @@ void normalCaseOnlyUpdatesDissociatingRel() { void requiresZeroBalanceWhenDissociatingFromActiveToken() { final long balance = 1_234L; dissociatingAccountRel.initBalance(balance); - given(validator.isAfterConsensusSecond(tokenExpiry)).willReturn(true); final var subject = new Dissociation(dissociatingAccountRel, dissociatedTokenTreasuryRel); - assertFailsWith(() -> subject.updateModelRelsSubjectTo(validator), TRANSACTION_REQUIRES_ZERO_TOKEN_BALANCES); + assertFailsWith(() -> subject.updateModelRelsSubjectTo(), TRANSACTION_REQUIRES_ZERO_TOKEN_BALANCES); } @Test @@ -189,47 +181,7 @@ void cannotAutoRevertOwnershipToTreasuryEvenForExpired() { final var subject = new Dissociation(dissociatingAccountRel, dissociatedTokenTreasuryRel); - assertFailsWith(() -> subject.updateModelRelsSubjectTo(validator), ACCOUNT_STILL_OWNS_NFTS); - } - - @Test - void autoTransfersBalanceBackToTreasuryForExpiredToken() { - final long balance = 1_234L; - dissociatingAccountRel.initBalance(balance); - dissociatedTokenTreasuryRel.initBalance(balance); - final var subject = new Dissociation(dissociatingAccountRel, dissociatedTokenTreasuryRel); - final List accum = new ArrayList<>(); - - subject.updateModelRelsSubjectTo(validator); - subject.addUpdatedModelRelsTo(accum); - - assertEquals(2, accum.size()); - assertEquals(token, subject.dissociatingToken()); - assertEquals(account, subject.dissociatingAccount()); - assertEquals(dissociatingAccountRel.getBalanceChange(), -balance); - assertSame(dissociatingAccountRel, accum.get(0)); - assertTrue(dissociatingAccountRel.isDestroyed()); - assertSame(dissociatedTokenTreasuryRel, accum.get(1)); - assertEquals(dissociatedTokenTreasuryRel.getBalanceChange(), +balance); - } - - @Test - void autoTransfersBalanceBackToTreasuryRespectingIdOrdering() { - final long balance = 1_234L; - dissociatingAccountRel.initBalance(balance); - ancientTokenTreasuryRel.initBalance(balance); - final var subject = new Dissociation(dissociatingAccountRel, ancientTokenTreasuryRel); - final List accum = new ArrayList<>(); - - subject.updateModelRelsSubjectTo(validator); - subject.addUpdatedModelRelsTo(accum); - - assertEquals(2, accum.size()); - assertEquals(dissociatingAccountRel.getBalanceChange(), -balance); - assertSame(dissociatingAccountRel, accum.get(1)); - assertTrue(dissociatingAccountRel.isDestroyed()); - assertSame(ancientTokenTreasuryRel, accum.get(0)); - assertEquals(ancientTokenTreasuryRel.getBalanceChange(), +balance); + assertFailsWith(() -> subject.updateModelRelsSubjectTo(), ACCOUNT_STILL_OWNS_NFTS); } @Test @@ -242,7 +194,7 @@ void oksDissociatedDeletedTokenTreasury() { final var subject = new Dissociation(dissociatingAccountRel, dissociatedTokenTreasuryRel); - subject.updateModelRelsSubjectTo(validator); + subject.updateModelRelsSubjectTo(); subject.addUpdatedModelRelsTo(accum); assertEquals(1, accum.size()); @@ -262,7 +214,7 @@ void oksDissociatedDeletedUniqueTokenTreasury() { final var subject = new Dissociation(dissociatingAccountRel, dissociatedTokenTreasuryRel); - subject.updateModelRelsSubjectTo(validator); + subject.updateModelRelsSubjectTo(); subject.addUpdatedModelRelsTo(accum); assertEquals(1, accum.size()); @@ -283,7 +235,7 @@ void stillDissociatesFromDeletedTokenWithBalanceChangeEvenAfterTreasuryGone() { final var subject = new Dissociation(dissociatingAccountRel, null); - subject.updateModelRelsSubjectTo(validator); + subject.updateModelRelsSubjectTo(); subject.addUpdatedModelRelsTo(accum); assertEquals(1, accum.size()); diff --git a/hedera-node/hedera-mono-service/src/testFixtures/java/com/hedera/test/factories/scenarios/TokenUpdateScenarios.java b/hedera-node/hedera-mono-service/src/testFixtures/java/com/hedera/test/factories/scenarios/TokenUpdateScenarios.java index 98462494522d..2658a43974d9 100644 --- a/hedera-node/hedera-mono-service/src/testFixtures/java/com/hedera/test/factories/scenarios/TokenUpdateScenarios.java +++ b/hedera-node/hedera-mono-service/src/testFixtures/java/com/hedera/test/factories/scenarios/TokenUpdateScenarios.java @@ -25,7 +25,7 @@ import java.security.SignatureException; public enum TokenUpdateScenarios implements TxnHandlingScenario { - UPDATE_WITH_NO_KEYS_AFFECTED { + UPDATE_WITH_NO_FIELDS_CHANGED { @Override public PlatformTxnAccessor platformTxn() throws InvalidProtocolBufferException, SignatureException, NoSuchAlgorithmException, @@ -90,7 +90,7 @@ public PlatformTxnAccessor platformTxn() .get()); } }, - UPDATE_WITH_SUPPLY_KEYED_TOKEN { + UPDATE_WITH_SUPPLY_KEYED_TOKEN_REPLACEMENT_KEY_NOT_REQUIRED { @Override public PlatformTxnAccessor platformTxn() throws InvalidProtocolBufferException, SignatureException, NoSuchAlgorithmException, @@ -98,10 +98,11 @@ public PlatformTxnAccessor platformTxn() return PlatformTxnAccessor.from(TokenUpdateFactory.newSignedTokenUpdate() .updating(KNOWN_TOKEN_WITH_SUPPLY) .replacingSupply() + .notValidatingRoleKeySignatures() .get()); } }, - UPDATE_WITH_KYC_KEYED_TOKEN { + UPDATE_WITH_KYC_KEYED_TOKEN_REPLACEMENT_KEY_REQUIRED { @Override public PlatformTxnAccessor platformTxn() throws InvalidProtocolBufferException, SignatureException, NoSuchAlgorithmException, @@ -112,7 +113,19 @@ public PlatformTxnAccessor platformTxn() .get()); } }, - UPDATE_WITH_FREEZE_KEYED_TOKEN { + UPDATE_WITH_KYC_KEYED_TOKEN_REPLACEMENT_KEY_NOT_REQUIRED { + @Override + public PlatformTxnAccessor platformTxn() + throws InvalidProtocolBufferException, SignatureException, NoSuchAlgorithmException, + InvalidKeyException { + return PlatformTxnAccessor.from(TokenUpdateFactory.newSignedTokenUpdate() + .updating(KNOWN_TOKEN_WITH_KYC) + .replacingKyc() + .notValidatingRoleKeySignatures() + .get()); + } + }, + UPDATE_WITH_FREEZE_KEYED_TOKEN_REPLACEMENT_KEY_NOT_REQUIRED { @Override public PlatformTxnAccessor platformTxn() throws InvalidProtocolBufferException, SignatureException, NoSuchAlgorithmException, @@ -120,10 +133,11 @@ public PlatformTxnAccessor platformTxn() return PlatformTxnAccessor.from(TokenUpdateFactory.newSignedTokenUpdate() .updating(KNOWN_TOKEN_WITH_FREEZE) .replacingFreeze() + .notValidatingRoleKeySignatures() .get()); } }, - UPDATE_WITH_WIPE_KEYED_TOKEN { + UPDATE_WITH_WIPE_KEYED_TOKEN_REPLACEMENT_KEY_NOT_REQUIRED { @Override public PlatformTxnAccessor platformTxn() throws InvalidProtocolBufferException, SignatureException, NoSuchAlgorithmException, @@ -131,6 +145,7 @@ public PlatformTxnAccessor platformTxn() return PlatformTxnAccessor.from(TokenUpdateFactory.newSignedTokenUpdate() .updating(KNOWN_TOKEN_WITH_WIPE) .replacingWipe() + .notValidatingRoleKeySignatures() .get()); } }, diff --git a/hedera-node/hedera-mono-service/src/testFixtures/java/com/hedera/test/factories/txns/TokenUpdateFactory.java b/hedera-node/hedera-mono-service/src/testFixtures/java/com/hedera/test/factories/txns/TokenUpdateFactory.java index fa4eef87a45e..d37897f69cf9 100644 --- a/hedera-node/hedera-mono-service/src/testFixtures/java/com/hedera/test/factories/txns/TokenUpdateFactory.java +++ b/hedera-node/hedera-mono-service/src/testFixtures/java/com/hedera/test/factories/txns/TokenUpdateFactory.java @@ -21,6 +21,7 @@ import com.hedera.test.factories.keys.KeyTree; import com.hederahashgraph.api.proto.java.AccountID; import com.hederahashgraph.api.proto.java.TokenID; +import com.hederahashgraph.api.proto.java.TokenKeyValidation; import com.hederahashgraph.api.proto.java.TokenUpdateTransactionBody; import com.hederahashgraph.api.proto.java.Transaction; import com.hederahashgraph.api.proto.java.TransactionBody; @@ -32,6 +33,7 @@ public class TokenUpdateFactory extends SignedTxnFactory { private Optional newTreasury = Optional.empty(); private Optional newAutoRenew = Optional.empty(); private boolean replaceFreeze, replaceSupply, replaceWipe, replaceKyc; + private TokenKeyValidation keyVerificationMode = TokenKeyValidation.FULL_VALIDATION; private TokenUpdateFactory() {} @@ -44,6 +46,11 @@ public TokenUpdateFactory updating(final TokenID id) { return this; } + public TokenUpdateFactory notValidatingRoleKeySignatures() { + this.keyVerificationMode = TokenKeyValidation.NO_VALIDATION; + return this; + } + public TokenUpdateFactory newAdmin(final KeyTree kt) { newAdminKt = Optional.of(kt); return this; @@ -106,8 +113,9 @@ protected void customizeTxn(final TransactionBody.Builder txn) { if (replaceWipe) { op.setWipeKey(TOKEN_REPLACE_KT.asKey()); } - newAutoRenew.ifPresent(a -> op.setAutoRenewAccount(a)); + newAutoRenew.ifPresent(op::setAutoRenewAccount); newTreasury.ifPresent(op::setTreasury); + op.setKeyVerificationMode(keyVerificationMode); txn.setTokenUpdate(op); } } diff --git a/hedera-node/hedera-mono-service/src/testFixtures/java/com/hedera/test/utils/StateKeyAdapter.java b/hedera-node/hedera-mono-service/src/testFixtures/java/com/hedera/test/utils/StateKeyAdapter.java index 2771a1a9d387..953e263ef062 100644 --- a/hedera-node/hedera-mono-service/src/testFixtures/java/com/hedera/test/utils/StateKeyAdapter.java +++ b/hedera-node/hedera-mono-service/src/testFixtures/java/com/hedera/test/utils/StateKeyAdapter.java @@ -16,8 +16,8 @@ package com.hedera.test.utils; -import com.hedera.node.app.spi.state.ReadableKVState; -import com.hedera.node.app.spi.state.ReadableKVStateBase; +import com.swirlds.platform.state.spi.ReadableKVStateBase; +import com.swirlds.state.spi.ReadableKVState; import edu.umd.cs.findbugs.annotations.NonNull; import java.util.Iterator; import java.util.function.Function; diff --git a/hedera-node/hedera-mono-service/src/testFixtures/java/com/hedera/test/utils/TestFixturesKeyLookup.java b/hedera-node/hedera-mono-service/src/testFixtures/java/com/hedera/test/utils/TestFixturesKeyLookup.java index 3a0fbd14a8ee..24e790287cb6 100644 --- a/hedera-node/hedera-mono-service/src/testFixtures/java/com/hedera/test/utils/TestFixturesKeyLookup.java +++ b/hedera-node/hedera-mono-service/src/testFixtures/java/com/hedera/test/utils/TestFixturesKeyLookup.java @@ -22,9 +22,9 @@ import com.hedera.hapi.node.state.primitives.ProtoBytes; import com.hedera.hapi.node.state.token.Account; import com.hedera.node.app.service.token.ReadableAccountStore; -import com.hedera.node.app.spi.state.ReadableKVState; -import com.hedera.node.app.spi.state.ReadableStates; import com.hedera.pbj.runtime.io.buffer.Bytes; +import com.swirlds.state.spi.ReadableKVState; +import com.swirlds.state.spi.ReadableStates; import edu.umd.cs.findbugs.annotations.NonNull; import edu.umd.cs.findbugs.annotations.Nullable; diff --git a/hedera-node/hedera-mono-service/src/testFixtures/java/module-info.java b/hedera-node/hedera-mono-service/src/testFixtures/java/module-info.java index c1c086e17697..9d607b92f0fd 100644 --- a/hedera-node/hedera-mono-service/src/testFixtures/java/module-info.java +++ b/hedera-node/hedera-mono-service/src/testFixtures/java/module-info.java @@ -16,9 +16,10 @@ requires transitive com.swirlds.merkle; requires transitive com.swirlds.merkledb; requires transitive com.swirlds.platform.core; + requires transitive com.swirlds.state.api; requires transitive com.swirlds.virtualmap; requires transitive org.bouncycastle.provider; - requires com.hedera.node.app.service.evm; + requires com.hedera.evm; requires net.i2p.crypto.eddsa; requires org.junit.jupiter.api; requires org.mockito; diff --git a/hedera-node/hedera-network-admin-service-impl/build.gradle.kts b/hedera-node/hedera-network-admin-service-impl/build.gradle.kts index f78e7773347a..e5e3589f5f37 100644 --- a/hedera-node/hedera-network-admin-service-impl/build.gradle.kts +++ b/hedera-node/hedera-network-admin-service-impl/build.gradle.kts @@ -14,7 +14,10 @@ * limitations under the License. */ -plugins { id("com.hedera.hashgraph.conventions") } +plugins { + id("com.hedera.gradle.services") + id("com.hedera.gradle.services-publish") +} description = "Default Hedera Network Admin Service Implementation" @@ -26,6 +29,7 @@ testModuleInfo { requires("com.hedera.node.app.service.network.admin.impl") requires("com.hedera.node.app.service.token.impl") requires("com.hedera.node.app.spi.test.fixtures") + requires("com.swirlds.platform.core.test.fixtures") requires("com.hedera.node.app.test.fixtures") requires("com.hedera.node.config.test.fixtures") requires("com.swirlds.fcqueue") diff --git a/hedera-node/hedera-network-admin-service-impl/src/main/java/com/hedera/node/app/service/networkadmin/impl/FreezeServiceImpl.java b/hedera-node/hedera-network-admin-service-impl/src/main/java/com/hedera/node/app/service/networkadmin/impl/FreezeServiceImpl.java index b8a84794372b..9745583b9e64 100644 --- a/hedera-node/hedera-network-admin-service-impl/src/main/java/com/hedera/node/app/service/networkadmin/impl/FreezeServiceImpl.java +++ b/hedera-node/hedera-network-admin-service-impl/src/main/java/com/hedera/node/app/service/networkadmin/impl/FreezeServiceImpl.java @@ -19,10 +19,11 @@ import com.hedera.hapi.node.base.SemanticVersion; import com.hedera.node.app.service.networkadmin.FreezeService; import com.hedera.node.app.service.networkadmin.impl.schemas.InitialModServiceAdminSchema; +import com.hedera.node.app.spi.Service; import com.hedera.node.app.spi.state.SchemaRegistry; import edu.umd.cs.findbugs.annotations.NonNull; -/** Standard implementation of the {@link FreezeService} {@link com.hedera.node.app.spi.Service}. */ +/** Standard implementation of the {@link FreezeService} {@link Service}. */ public final class FreezeServiceImpl implements FreezeService { public static final String UPGRADE_FILE_HASH_KEY = "UPGRADE_FILE_HASH"; public static final String FREEZE_TIME_KEY = "FREEZE_TIME"; diff --git a/hedera-node/hedera-network-admin-service-impl/src/main/java/com/hedera/node/app/service/networkadmin/impl/NetworkServiceImpl.java b/hedera-node/hedera-network-admin-service-impl/src/main/java/com/hedera/node/app/service/networkadmin/impl/NetworkServiceImpl.java index ac1bf634fa44..3aa2e6ec56de 100644 --- a/hedera-node/hedera-network-admin-service-impl/src/main/java/com/hedera/node/app/service/networkadmin/impl/NetworkServiceImpl.java +++ b/hedera-node/hedera-network-admin-service-impl/src/main/java/com/hedera/node/app/service/networkadmin/impl/NetworkServiceImpl.java @@ -19,12 +19,13 @@ import com.hedera.hapi.node.base.SemanticVersion; import com.hedera.node.app.service.networkadmin.NetworkService; import com.hedera.node.app.service.networkadmin.impl.schemas.InitialModServiceNetworkSchema; +import com.hedera.node.app.spi.Service; import com.hedera.node.app.spi.state.Schema; import com.hedera.node.app.spi.state.SchemaRegistry; import edu.umd.cs.findbugs.annotations.NonNull; /** - * Standard implementation of the {@link NetworkService} {@link com.hedera.node.app.spi.Service}. + * Standard implementation of the {@link NetworkService} {@link Service}. */ public final class NetworkServiceImpl implements NetworkService { diff --git a/hedera-node/hedera-network-admin-service-impl/src/main/java/com/hedera/node/app/service/networkadmin/impl/ReadableFreezeStoreImpl.java b/hedera-node/hedera-network-admin-service-impl/src/main/java/com/hedera/node/app/service/networkadmin/impl/ReadableFreezeStoreImpl.java index 508e2312fc5a..a855fe6305ee 100644 --- a/hedera-node/hedera-network-admin-service-impl/src/main/java/com/hedera/node/app/service/networkadmin/impl/ReadableFreezeStoreImpl.java +++ b/hedera-node/hedera-network-admin-service-impl/src/main/java/com/hedera/node/app/service/networkadmin/impl/ReadableFreezeStoreImpl.java @@ -23,9 +23,9 @@ import com.hedera.hapi.node.base.Timestamp; import com.hedera.hapi.node.state.primitives.ProtoBytes; import com.hedera.node.app.service.networkadmin.ReadableFreezeStore; -import com.hedera.node.app.spi.state.ReadableSingletonState; -import com.hedera.node.app.spi.state.ReadableStates; import com.hedera.pbj.runtime.io.buffer.Bytes; +import com.swirlds.state.spi.ReadableSingletonState; +import com.swirlds.state.spi.ReadableStates; import edu.umd.cs.findbugs.annotations.NonNull; import edu.umd.cs.findbugs.annotations.Nullable; diff --git a/hedera-node/hedera-network-admin-service-impl/src/main/java/com/hedera/node/app/service/networkadmin/impl/WritableFreezeStore.java b/hedera-node/hedera-network-admin-service-impl/src/main/java/com/hedera/node/app/service/networkadmin/impl/WritableFreezeStore.java index 434b0f035020..ddf0a6885393 100644 --- a/hedera-node/hedera-network-admin-service-impl/src/main/java/com/hedera/node/app/service/networkadmin/impl/WritableFreezeStore.java +++ b/hedera-node/hedera-network-admin-service-impl/src/main/java/com/hedera/node/app/service/networkadmin/impl/WritableFreezeStore.java @@ -20,9 +20,9 @@ import com.hedera.hapi.node.base.Timestamp; import com.hedera.hapi.node.state.primitives.ProtoBytes; -import com.hedera.node.app.spi.state.WritableSingletonState; -import com.hedera.node.app.spi.state.WritableStates; import com.hedera.pbj.runtime.io.buffer.Bytes; +import com.swirlds.state.spi.WritableSingletonState; +import com.swirlds.state.spi.WritableStates; import edu.umd.cs.findbugs.annotations.NonNull; import edu.umd.cs.findbugs.annotations.Nullable; diff --git a/hedera-node/hedera-network-admin-service-impl/src/main/java/com/hedera/node/app/service/networkadmin/impl/handlers/ReadableFreezeUpgradeActions.java b/hedera-node/hedera-network-admin-service-impl/src/main/java/com/hedera/node/app/service/networkadmin/impl/handlers/ReadableFreezeUpgradeActions.java index 76e84b1de861..daf550411e69 100644 --- a/hedera-node/hedera-network-admin-service-impl/src/main/java/com/hedera/node/app/service/networkadmin/impl/handlers/ReadableFreezeUpgradeActions.java +++ b/hedera-node/hedera-network-admin-service-impl/src/main/java/com/hedera/node/app/service/networkadmin/impl/handlers/ReadableFreezeUpgradeActions.java @@ -20,6 +20,7 @@ import static com.hedera.node.app.service.mono.context.properties.StaticPropertiesHolder.STATIC_PROPERTIES; import static com.hedera.node.app.service.mono.pbj.PbjConverter.toPbj; import static com.hedera.node.app.service.mono.utils.EntityIdUtils.readableId; +import static com.swirlds.common.io.utility.FileUtils.getAbsolutePath; import static java.util.Objects.requireNonNull; import static java.util.concurrent.CompletableFuture.runAsync; @@ -31,11 +32,9 @@ import com.swirlds.platform.state.PlatformState; import edu.umd.cs.findbugs.annotations.NonNull; import edu.umd.cs.findbugs.annotations.Nullable; -import java.io.File; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; -import java.nio.file.Paths; import java.util.Arrays; import java.util.concurrent.CompletableFuture; import java.util.concurrent.Executor; @@ -94,7 +93,7 @@ public void externalizeFreezeIfUpgradePending() { protected void writeMarker(@NonNull final String file, @Nullable final Timestamp now) { requireNonNull(file); - final Path artifactsDirPath = Paths.get(adminServiceConfig.upgradeArtifactsPath()); + final Path artifactsDirPath = getAbsolutePath(adminServiceConfig.upgradeArtifactsPath()); final var filePath = artifactsDirPath.resolve(file); try { if (!artifactsDirPath.toFile().exists()) { @@ -198,7 +197,7 @@ private CompletableFuture extractNow( requireNonNull(marker); final long size = archiveData.length(); - final String artifactsLoc = adminServiceConfig.upgradeArtifactsPath(); + final Path artifactsLoc = getAbsolutePath(adminServiceConfig.upgradeArtifactsPath()); requireNonNull(artifactsLoc); log.info("About to unzip {} bytes for {} update into {}", size, desc, artifactsLoc); // we spin off a separate thread to avoid blocking handleTransaction @@ -207,10 +206,10 @@ private CompletableFuture extractNow( } private void extractAndReplaceArtifacts( - String artifactsLoc, Bytes archiveData, long size, String desc, String marker, Timestamp now) { + Path artifactsLoc, Bytes archiveData, long size, String desc, String marker, Timestamp now) { try { - FileUtils.cleanDirectory(new File(artifactsLoc)); - UnzipUtility.unzip(archiveData.toByteArray(), Paths.get(artifactsLoc)); + FileUtils.cleanDirectory(artifactsLoc.toFile()); + UnzipUtility.unzip(archiveData.toByteArray(), artifactsLoc); log.info("Finished unzipping {} bytes for {} update into {}", size, desc, artifactsLoc); writeSecondMarker(marker, now); } catch (final IOException e) { diff --git a/hedera-node/hedera-network-admin-service-impl/src/main/java/com/hedera/node/app/service/networkadmin/impl/handlers/UnzipUtility.java b/hedera-node/hedera-network-admin-service-impl/src/main/java/com/hedera/node/app/service/networkadmin/impl/handlers/UnzipUtility.java index d2f63cf10607..39b783b742dc 100644 --- a/hedera-node/hedera-network-admin-service-impl/src/main/java/com/hedera/node/app/service/networkadmin/impl/handlers/UnzipUtility.java +++ b/hedera-node/hedera-network-admin-service-impl/src/main/java/com/hedera/node/app/service/networkadmin/impl/handlers/UnzipUtility.java @@ -48,9 +48,7 @@ public final class UnzipUtility { // max allowed ratio between uncompressed and compressed file size private static final double THRESHOLD_RATIO = 10; - private UnzipUtility() { - throw new UnsupportedOperationException("Utility Class"); - } + private UnzipUtility() {} /** * Extracts (unzips) a zipped file from a byte array diff --git a/hedera-node/hedera-network-admin-service-impl/src/main/java/module-info.java b/hedera-node/hedera-network-admin-service-impl/src/main/java/module-info.java index 62b5c0a7fb2b..56f135482cbb 100644 --- a/hedera-node/hedera-network-admin-service-impl/src/main/java/module-info.java +++ b/hedera-node/hedera-network-admin-service-impl/src/main/java/module-info.java @@ -11,6 +11,7 @@ requires transitive com.hedera.node.hapi; requires transitive com.hedera.pbj.runtime; requires transitive com.swirlds.platform.core; + requires transitive com.swirlds.state.api; requires transitive dagger; requires transitive javax.inject; requires com.hedera.node.app.hapi.utils; diff --git a/hedera-node/hedera-network-admin-service-impl/src/test/java/com/hedera/node/app/service/networkadmin/impl/test/NetworkServiceImplTest.java b/hedera-node/hedera-network-admin-service-impl/src/test/java/com/hedera/node/app/service/networkadmin/impl/test/NetworkServiceImplTest.java index 362814c118c6..00774510ca67 100644 --- a/hedera-node/hedera-network-admin-service-impl/src/test/java/com/hedera/node/app/service/networkadmin/impl/test/NetworkServiceImplTest.java +++ b/hedera-node/hedera-network-admin-service-impl/src/test/java/com/hedera/node/app/service/networkadmin/impl/test/NetworkServiceImplTest.java @@ -16,10 +16,17 @@ package com.hedera.node.app.service.networkadmin.impl.test; +import static org.assertj.core.api.AssertionsForClassTypes.assertThatThrownBy; +import static org.mockito.ArgumentMatchers.notNull; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; + +import com.hedera.hapi.node.base.SemanticVersion; import com.hedera.node.app.service.networkadmin.NetworkService; import com.hedera.node.app.service.networkadmin.impl.NetworkServiceImpl; import com.hedera.node.app.spi.state.SchemaRegistry; import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.Mock; @@ -30,6 +37,31 @@ class NetworkServiceImplTest { @Mock private SchemaRegistry registry; + private NetworkServiceImpl subject; + + @BeforeEach + void setUp() { + subject = new NetworkServiceImpl(); + } + + @SuppressWarnings("DataFlowIssue") + @Test + void registerSchemasNullArgsThrow() { + assertThatThrownBy(() -> subject.registerSchemas(null, SemanticVersion.DEFAULT)) + .isInstanceOf(NullPointerException.class); + + assertThatThrownBy(() -> subject.registerSchemas(mock(SchemaRegistry.class), null)) + .isInstanceOf(NullPointerException.class); + } + + @Test + void registerSchemasRegistersTopicSchema() { + final var schemaRegistry = mock(SchemaRegistry.class); + + subject.registerSchemas(schemaRegistry, SemanticVersion.DEFAULT); + verify(schemaRegistry).register(notNull()); + } + @Test void testSpi() { // when diff --git a/hedera-node/hedera-network-admin-service-impl/src/test/java/com/hedera/node/app/service/networkadmin/impl/test/WritableFreezeStoreTest.java b/hedera-node/hedera-network-admin-service-impl/src/test/java/com/hedera/node/app/service/networkadmin/impl/test/WritableFreezeStoreTest.java index f562ec171426..1281b21b3f6c 100644 --- a/hedera-node/hedera-network-admin-service-impl/src/test/java/com/hedera/node/app/service/networkadmin/impl/test/WritableFreezeStoreTest.java +++ b/hedera-node/hedera-network-admin-service-impl/src/test/java/com/hedera/node/app/service/networkadmin/impl/test/WritableFreezeStoreTest.java @@ -29,9 +29,9 @@ import com.hedera.node.app.service.networkadmin.impl.FreezeServiceImpl; import com.hedera.node.app.service.networkadmin.impl.ReadableFreezeStoreImpl; import com.hedera.node.app.service.networkadmin.impl.WritableFreezeStore; -import com.hedera.node.app.spi.state.WritableSingletonStateBase; -import com.hedera.node.app.spi.state.WritableStates; import com.hedera.pbj.runtime.io.buffer.Bytes; +import com.swirlds.platform.state.spi.WritableSingletonStateBase; +import com.swirlds.state.spi.WritableStates; import java.util.concurrent.atomic.AtomicReference; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; diff --git a/hedera-node/hedera-network-admin-service-impl/src/test/java/com/hedera/node/app/service/networkadmin/impl/test/handlers/NetworkAdminHandlerTestBase.java b/hedera-node/hedera-network-admin-service-impl/src/test/java/com/hedera/node/app/service/networkadmin/impl/test/handlers/NetworkAdminHandlerTestBase.java index 7563d06ca9c7..510af7500ef7 100644 --- a/hedera-node/hedera-network-admin-service-impl/src/test/java/com/hedera/node/app/service/networkadmin/impl/test/handlers/NetworkAdminHandlerTestBase.java +++ b/hedera-node/hedera-network-admin-service-impl/src/test/java/com/hedera/node/app/service/networkadmin/impl/test/handlers/NetworkAdminHandlerTestBase.java @@ -49,10 +49,9 @@ import com.hedera.node.app.service.token.impl.ReadableAccountStoreImpl; import com.hedera.node.app.service.token.impl.ReadableTokenRelationStoreImpl; import com.hedera.node.app.service.token.impl.ReadableTokenStoreImpl; -import com.hedera.node.app.spi.fixtures.state.MapReadableKVState; +import com.hedera.node.app.spi.fees.FeeCalculator; import com.hedera.node.app.spi.fixtures.state.TestSchema; import com.hedera.node.app.spi.info.NetworkInfo; -import com.hedera.node.app.spi.state.ReadableStates; import com.hedera.node.app.state.DeduplicationCache; import com.hedera.node.app.state.SingleTransactionRecord; import com.hedera.node.app.state.SingleTransactionRecord.TransactionOutputs; @@ -66,6 +65,8 @@ import com.hedera.node.config.data.LedgerConfig; import com.hedera.pbj.runtime.OneOf; import com.hedera.pbj.runtime.io.buffer.Bytes; +import com.swirlds.platform.test.fixtures.state.MapReadableKVState; +import com.swirlds.state.spi.ReadableStates; import edu.umd.cs.findbugs.annotations.NonNull; import edu.umd.cs.findbugs.annotations.Nullable; import java.time.Instant; @@ -183,6 +184,9 @@ public class NetworkAdminHandlerTestBase { @Mock private NetworkInfo networkInfo; + @Mock + FeeCalculator feeCalculator; + @BeforeEach void commonSetUp() { final var validStartTime = Instant.ofEpochMilli(123456789L); // aligned to millisecond boundary for convenience. diff --git a/hedera-node/hedera-network-admin-service-impl/src/test/java/com/hedera/node/app/service/networkadmin/impl/test/handlers/NetworkAdminHandlersTest.java b/hedera-node/hedera-network-admin-service-impl/src/test/java/com/hedera/node/app/service/networkadmin/impl/test/handlers/NetworkAdminHandlersTest.java new file mode 100644 index 000000000000..0d0d988ebb7e --- /dev/null +++ b/hedera-node/hedera-network-admin-service-impl/src/test/java/com/hedera/node/app/service/networkadmin/impl/test/handlers/NetworkAdminHandlersTest.java @@ -0,0 +1,149 @@ +/* + * Copyright (C) 2024 Hedera Hashgraph, LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.hedera.node.app.service.networkadmin.impl.test.handlers; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.mockito.Mockito.mock; + +import com.hedera.node.app.service.networkadmin.impl.handlers.FreezeHandler; +import com.hedera.node.app.service.networkadmin.impl.handlers.NetworkAdminHandlers; +import com.hedera.node.app.service.networkadmin.impl.handlers.NetworkGetAccountDetailsHandler; +import com.hedera.node.app.service.networkadmin.impl.handlers.NetworkGetByKeyHandler; +import com.hedera.node.app.service.networkadmin.impl.handlers.NetworkGetExecutionTimeHandler; +import com.hedera.node.app.service.networkadmin.impl.handlers.NetworkGetVersionInfoHandler; +import com.hedera.node.app.service.networkadmin.impl.handlers.NetworkTransactionGetFastRecordHandler; +import com.hedera.node.app.service.networkadmin.impl.handlers.NetworkTransactionGetReceiptHandler; +import com.hedera.node.app.service.networkadmin.impl.handlers.NetworkTransactionGetRecordHandler; +import com.hedera.node.app.service.networkadmin.impl.handlers.NetworkUncheckedSubmitHandler; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +class NetworkAdminHandlersTest { + private FreezeHandler freezeHandler; + + private NetworkGetAccountDetailsHandler networkGetAccountDetailsHandler; + + private NetworkGetByKeyHandler networkGetByKeyHandler; + + private NetworkGetExecutionTimeHandler networkGetExecutionTimeHandler; + + private NetworkGetVersionInfoHandler networkGetVersionInfoHandler; + + private NetworkTransactionGetReceiptHandler networkTransactionGetReceiptHandler; + + private NetworkTransactionGetRecordHandler networkTransactionGetRecordHandler; + + private NetworkTransactionGetFastRecordHandler networkTransactionGetFastRecordHandler; + + private NetworkUncheckedSubmitHandler networkUncheckedSubmitHandler; + + private NetworkAdminHandlers networkAdminHandlers; + + @BeforeEach + public void setUp() { + freezeHandler = mock(FreezeHandler.class); + networkGetAccountDetailsHandler = mock(NetworkGetAccountDetailsHandler.class); + networkGetByKeyHandler = mock(NetworkGetByKeyHandler.class); + networkGetExecutionTimeHandler = mock(NetworkGetExecutionTimeHandler.class); + networkGetVersionInfoHandler = mock(NetworkGetVersionInfoHandler.class); + networkTransactionGetReceiptHandler = mock(NetworkTransactionGetReceiptHandler.class); + networkTransactionGetRecordHandler = mock(NetworkTransactionGetRecordHandler.class); + networkTransactionGetFastRecordHandler = mock(NetworkTransactionGetFastRecordHandler.class); + networkUncheckedSubmitHandler = mock(NetworkUncheckedSubmitHandler.class); + + networkAdminHandlers = new NetworkAdminHandlers( + freezeHandler, + networkGetAccountDetailsHandler, + networkGetByKeyHandler, + networkGetExecutionTimeHandler, + networkGetVersionInfoHandler, + networkTransactionGetReceiptHandler, + networkTransactionGetRecordHandler, + networkTransactionGetFastRecordHandler, + networkUncheckedSubmitHandler); + } + + @Test + void freezeHandlerReturnsCorrectInstance() { + assertEquals( + freezeHandler, networkAdminHandlers.freezeHandler(), "freezeHandler does not return correct instance"); + } + + @Test + void networkGetAccountDetailsHandlerReturnsCorrectInstance() { + assertEquals( + networkGetAccountDetailsHandler, + networkAdminHandlers.networkGetAccountDetailsHandler(), + "networkGetAccountDetailsHandler does not return correct instance"); + } + + @Test + void networkGetByKeyHandlerReturnsCorrectInstance() { + assertEquals( + networkGetByKeyHandler, + networkAdminHandlers.networkGetByKeyHandler(), + "networkGetByKeyHandler does not return correct instance"); + } + + @Test + void networkGetExecutionTimeHandlerReturnsCorrectInstance() { + assertEquals( + networkGetExecutionTimeHandler, + networkAdminHandlers.networkGetExecutionTimeHandler(), + "networkGetExecutionTimeHandler does not return correct instance"); + } + + @Test + void networkGetVersionInfoHandlerReturnsCorrectInstance() { + assertEquals( + networkGetVersionInfoHandler, + networkAdminHandlers.networkGetVersionInfoHandler(), + "networkGetVersionInfoHandler does not return correct instance"); + } + + @Test + void networkTransactionGetReceiptHandlerReturnsCorrectInstance() { + assertEquals( + networkTransactionGetReceiptHandler, + networkAdminHandlers.networkTransactionGetReceiptHandler(), + "networkTransactionGetReceiptHandler does not return correct instance"); + } + + @Test + void networkTransactionGetRecordHandlerReturnsCorrectInstance() { + assertEquals( + networkTransactionGetRecordHandler, + networkAdminHandlers.networkTransactionGetRecordHandler(), + "networkTransactionGetRecordHandler does not return correct instance"); + } + + @Test + void networkTransactionGetFastRecordHandlerReturnsCorrectInstance() { + assertEquals( + networkTransactionGetFastRecordHandler, + networkAdminHandlers.networkTransactionGetFastRecordHandler(), + "networkTransactionGetFastRecordHandler does not return correct instance"); + } + + @Test + void networkUncheckedSubmitHandlerReturnsCorrectInstance() { + assertEquals( + networkUncheckedSubmitHandler, + networkAdminHandlers.networkUncheckedSubmitHandler(), + "networkUncheckSubmitHandler does not return correct instance"); + } +} diff --git a/hedera-node/hedera-network-admin-service-impl/src/test/java/com/hedera/node/app/service/networkadmin/impl/test/handlers/NetworkTransactionGetRecordHandlerTest.java b/hedera-node/hedera-network-admin-service-impl/src/test/java/com/hedera/node/app/service/networkadmin/impl/test/handlers/NetworkTransactionGetRecordHandlerTest.java index f2c0b49e8789..ac934f1cee52 100644 --- a/hedera-node/hedera-network-admin-service-impl/src/test/java/com/hedera/node/app/service/networkadmin/impl/test/handlers/NetworkTransactionGetRecordHandlerTest.java +++ b/hedera-node/hedera-network-admin-service-impl/src/test/java/com/hedera/node/app/service/networkadmin/impl/test/handlers/NetworkTransactionGetRecordHandlerTest.java @@ -19,13 +19,11 @@ import static com.hedera.hapi.node.base.ResponseCodeEnum.INVALID_ACCOUNT_ID; import static com.hedera.hapi.node.base.ResponseCodeEnum.INVALID_TRANSACTION_ID; import static com.hedera.node.app.spi.fixtures.Assertions.assertThrowsPreCheck; +import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatCode; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertNull; -import static org.junit.jupiter.api.Assertions.assertTrue; import static org.mockito.BDDMockito.given; import static org.mockito.Mockito.lenient; +import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import com.hedera.hapi.node.base.QueryHeader; @@ -39,10 +37,13 @@ import com.hedera.hapi.node.transaction.TransactionGetRecordResponse; import com.hedera.hapi.node.transaction.TransactionRecord; import com.hedera.node.app.service.networkadmin.impl.handlers.NetworkTransactionGetRecordHandler; +import com.hedera.node.app.spi.fees.FeeCalculator; +import com.hedera.node.app.spi.fixtures.fees.FakeFeeCalculator; import com.hedera.node.app.spi.workflows.QueryContext; import com.hedera.node.config.testfixtures.HederaTestConfigBuilder; import java.util.List; import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.Mock; @@ -67,7 +68,7 @@ void extractsHeader() { final var query = createGetTransactionRecordQuery(transactionID, false, false); final var header = networkTransactionGetRecordHandler.extractHeader(query); final var op = query.transactionGetRecordOrThrow(); - assertEquals(op.header(), header); + assertThat(op.header()).isEqualTo(header); } @Test @@ -79,23 +80,31 @@ void createsEmptyResponse() { final var expectedResponse = Response.newBuilder() .transactionGetRecord(TransactionGetRecordResponse.newBuilder().header(responseHeader)) .build(); - assertEquals(expectedResponse, response); + assertThat(expectedResponse).isEqualTo(response); } @Test void requiresPayment() { - assertTrue(networkTransactionGetRecordHandler.requiresNodePayment(ResponseType.ANSWER_ONLY)); - assertTrue(networkTransactionGetRecordHandler.requiresNodePayment(ResponseType.ANSWER_STATE_PROOF)); - assertFalse(networkTransactionGetRecordHandler.requiresNodePayment(ResponseType.COST_ANSWER)); - assertFalse(networkTransactionGetRecordHandler.requiresNodePayment(ResponseType.COST_ANSWER_STATE_PROOF)); + assertThat(networkTransactionGetRecordHandler.requiresNodePayment(ResponseType.ANSWER_ONLY)) + .isTrue(); + assertThat(networkTransactionGetRecordHandler.requiresNodePayment(ResponseType.ANSWER_STATE_PROOF)) + .isTrue(); + assertThat(networkTransactionGetRecordHandler.requiresNodePayment(ResponseType.COST_ANSWER)) + .isFalse(); + assertThat(networkTransactionGetRecordHandler.requiresNodePayment(ResponseType.COST_ANSWER_STATE_PROOF)) + .isFalse(); } @Test void needsAnswerOnlyCostForCostAnswer() { - assertFalse(networkTransactionGetRecordHandler.needsAnswerOnlyCost(ResponseType.ANSWER_ONLY)); - assertFalse(networkTransactionGetRecordHandler.needsAnswerOnlyCost(ResponseType.ANSWER_STATE_PROOF)); - assertTrue(networkTransactionGetRecordHandler.needsAnswerOnlyCost(ResponseType.COST_ANSWER)); - assertFalse(networkTransactionGetRecordHandler.needsAnswerOnlyCost(ResponseType.COST_ANSWER_STATE_PROOF)); + assertThat(networkTransactionGetRecordHandler.needsAnswerOnlyCost(ResponseType.ANSWER_ONLY)) + .isFalse(); + assertThat(networkTransactionGetRecordHandler.needsAnswerOnlyCost(ResponseType.ANSWER_STATE_PROOF)) + .isFalse(); + assertThat(networkTransactionGetRecordHandler.needsAnswerOnlyCost(ResponseType.COST_ANSWER)) + .isTrue(); + assertThat(networkTransactionGetRecordHandler.needsAnswerOnlyCost(ResponseType.COST_ANSWER_STATE_PROOF)) + .isFalse(); } @Test @@ -137,8 +146,9 @@ void getsResponseIfFailedResponse() { final var response = networkTransactionGetRecordHandler.findResponse(context, responseHeader); final var op = response.transactionGetRecordOrThrow(); - assertEquals(ResponseCodeEnum.FAIL_FEE, op.header().nodeTransactionPrecheckCode()); - assertNull(op.transactionRecord()); + assertThat(op.header()).isNotNull(); + assertThat(op.header().nodeTransactionPrecheckCode()).isEqualTo(ResponseCodeEnum.FAIL_FEE); + assertThat(op.transactionRecord()).isNull(); } @Test @@ -153,8 +163,9 @@ void getsResponseIsEmptyWhenTransactionNotExist() { final var response = networkTransactionGetRecordHandler.findResponse(context, responseHeader); final var op = response.transactionGetRecordOrThrow(); - assertEquals(ResponseCodeEnum.RECORD_NOT_FOUND, op.header().nodeTransactionPrecheckCode()); - assertNull(op.transactionRecord()); + assertThat(op.header()).isNotNull(); + assertThat(op.header().nodeTransactionPrecheckCode()).isEqualTo(ResponseCodeEnum.RECORD_NOT_FOUND); + assertThat(op.transactionRecord()).isNull(); } @Test @@ -170,8 +181,9 @@ void getsResponseIfOkResponse() { final var response = networkTransactionGetRecordHandler.findResponse(context, responseHeader); final var op = response.transactionGetRecordOrThrow(); - assertEquals(ResponseCodeEnum.OK, op.header().nodeTransactionPrecheckCode()); - assertEquals(expectedRecord, op.transactionRecord()); + assertThat(op.header()).isNotNull(); + assertThat(op.header().nodeTransactionPrecheckCode()).isEqualTo(ResponseCodeEnum.OK); + assertThat(op.transactionRecord()).isEqualTo(expectedRecord); } @Test @@ -188,12 +200,11 @@ void getsResponseIfOkResponseWithDuplicates() { final var response = networkTransactionGetRecordHandler.findResponse(context, responseHeader); final var op = response.transactionGetRecordOrThrow(); - assertEquals(ResponseCodeEnum.OK, op.header().nodeTransactionPrecheckCode()); - assertEquals(expectedRecord, op.transactionRecord()); - assertEquals(expectedDuplicateRecords, op.duplicateTransactionRecords()); - assertEquals( - expectedDuplicateRecords.size(), - op.duplicateTransactionRecords().size()); + assertThat(op.header()).isNotNull(); + assertThat(op.header().nodeTransactionPrecheckCode()).isEqualTo(ResponseCodeEnum.OK); + assertThat(op.transactionRecord()).isEqualTo(expectedRecord); + assertThat(op.duplicateTransactionRecords()).isEqualTo(expectedDuplicateRecords); + assertThat(op.duplicateTransactionRecords().size()).isEqualTo(expectedDuplicateRecords.size()); } @Test @@ -210,11 +221,40 @@ void getsResponseIfOkResponseWithChildrenRecord() { final var response = networkTransactionGetRecordHandler.findResponse(context, responseHeader); final var op = response.transactionGetRecordOrThrow(); - assertEquals(ResponseCodeEnum.OK, op.header().nodeTransactionPrecheckCode()); - assertEquals(expectedRecord, op.transactionRecord()); - assertEquals(expectedChildRecordList, op.childTransactionRecords()); - assertEquals( - expectedChildRecordList.size(), op.childTransactionRecords().size()); + assertThat(op.header()).isNotNull(); + assertThat(op.header().nodeTransactionPrecheckCode()).isEqualTo(ResponseCodeEnum.OK); + assertThat(op.transactionRecord()).isEqualTo(expectedRecord); + assertThat(op.childTransactionRecords()).isEqualTo(expectedChildRecordList); + assertThat(op.childTransactionRecords().size()).isEqualTo(expectedChildRecordList.size()); + } + + @Test + @DisplayName("test computeFees When Free") + void testComputeFees() throws Throwable { + final var query = createGetTransactionRecordQuery(transactionID, false, false); + given(context.query()).willReturn(query); + given(context.recordCache()).willReturn(cache); + given(context.feeCalculator()).willReturn(feeCalculator); + feeCalculator.addNetworkRamByteSeconds(6); + assertThatCode(() -> networkTransactionGetRecordHandler.computeFees(context)) + .doesNotThrowAnyException(); + verify(feeCalculator).addNetworkRamByteSeconds(6); + } + + @Test + @DisplayName("test computeFees with duplicates and children") + void testComputeFeesWithDuplicatesAndChildRecords() throws Throwable { + final var query = createGetTransactionRecordQuery(transactionID, true, true); + given(context.query()).willReturn(query); + given(context.recordCache()).willReturn(cache); + FeeCalculator feeCalc = new FakeFeeCalculator(); + feeCalc.addBytesPerTransaction(1000L); + feeCalc.addNetworkRamByteSeconds(6); + given(context.feeCalculator()).willReturn(feeCalc); + assertThatCode(() -> networkTransactionGetRecordHandler.computeFees(context)) + .doesNotThrowAnyException(); + var networkFee = networkTransactionGetRecordHandler.computeFees(context).networkFee(); + assertThat(networkFee).isZero(); } private TransactionRecord getExpectedRecord(TransactionID transactionID) { diff --git a/hedera-node/hedera-network-admin-service-impl/src/test/java/com/hedera/node/app/service/networkadmin/impl/test/handlers/NetworkUncheckedSubmitHandlerTest.java b/hedera-node/hedera-network-admin-service-impl/src/test/java/com/hedera/node/app/service/networkadmin/impl/test/handlers/NetworkUncheckedSubmitHandlerTest.java index d256bdd295cb..73da0b6b379d 100644 --- a/hedera-node/hedera-network-admin-service-impl/src/test/java/com/hedera/node/app/service/networkadmin/impl/test/handlers/NetworkUncheckedSubmitHandlerTest.java +++ b/hedera-node/hedera-network-admin-service-impl/src/test/java/com/hedera/node/app/service/networkadmin/impl/test/handlers/NetworkUncheckedSubmitHandlerTest.java @@ -18,14 +18,20 @@ import static com.hedera.hapi.node.base.ResponseCodeEnum.NOT_SUPPORTED; import static com.hedera.node.app.spi.fixtures.workflows.ExceptionConditions.responseCode; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatNoException; import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.mockito.Mock.Strictness.LENIENT; +import static org.mockito.Mockito.mock; import com.hedera.node.app.service.networkadmin.impl.handlers.NetworkUncheckedSubmitHandler; +import com.hedera.node.app.spi.fees.FeeContext; +import com.hedera.node.app.spi.fees.Fees; import com.hedera.node.app.spi.workflows.HandleContext; import com.hedera.node.app.spi.workflows.HandleException; import com.hedera.node.app.spi.workflows.PreCheckException; import com.hedera.node.app.spi.workflows.PreHandleContext; +import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.Mock; @@ -54,4 +60,13 @@ void handleThrowsUnsupported() { .isInstanceOf(HandleException.class) .has(responseCode(NOT_SUPPORTED)); } + + @Test + @DisplayName("testCalculateFees") + void testCalculateFees() { + FeeContext mockFeeContext = mock(FeeContext.class); + assertThatNoException().isThrownBy(() -> subject.calculateFees(mockFeeContext)); + final var result = subject.calculateFees(mockFeeContext); + assertThat(result).isEqualTo(Fees.FREE); + } } diff --git a/hedera-node/hedera-network-admin-service-impl/src/test/java/com/hedera/node/app/service/networkadmin/impl/test/handlers/UnzipUtilityTest.java b/hedera-node/hedera-network-admin-service-impl/src/test/java/com/hedera/node/app/service/networkadmin/impl/test/handlers/UnzipUtilityTest.java index 6cfb55a30733..ff13e4373fde 100644 --- a/hedera-node/hedera-network-admin-service-impl/src/test/java/com/hedera/node/app/service/networkadmin/impl/test/handlers/UnzipUtilityTest.java +++ b/hedera-node/hedera-network-admin-service-impl/src/test/java/com/hedera/node/app/service/networkadmin/impl/test/handlers/UnzipUtilityTest.java @@ -16,8 +16,8 @@ package com.hedera.node.app.service.networkadmin.impl.test.handlers; -import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; -import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.assertj.core.api.Assertions.assertThatCode; +import static org.assertj.core.api.Assertions.assertThatExceptionOfType; import com.hedera.node.app.service.networkadmin.impl.handlers.UnzipUtility; import com.hedera.node.app.spi.fixtures.util.LogCaptor; @@ -29,9 +29,13 @@ import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; +import java.nio.file.Paths; +import java.security.SecureRandom; import java.util.zip.ZipEntry; import java.util.zip.ZipOutputStream; +import org.jetbrains.annotations.NotNull; import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.junit.jupiter.api.io.TempDir; @@ -54,10 +58,15 @@ class UnzipUtilityTest { @TempDir private Path zipSourceDir; // contains test zips + @TempDir + private Path zipSourceDirTooBig; // contains test zips + private File testZipWithOneFile; private File testZipWithSubdirectory; + private File testZipWithSubdirectoryTooBig; private final String FILENAME_1 = "fileToZip.txt"; private final String FILENAME_2 = "subdirectory/subdirectoryFileToZip.txt"; + private final String FILENAME_3 = "subdirectory/subdirectoryFileToZip3.txt"; @BeforeEach void setup() throws IOException { @@ -90,13 +99,40 @@ void setup() throws IOException { out.write(data, 0, data.length); out.closeEntry(); } + + // set up large test zip files in a subdirectory + testZipWithSubdirectoryTooBig = new File(zipSourceDirTooBig + "/testZipWithSubdirectoryTooBig.zip"); + try (ZipOutputStream out = new ZipOutputStream(new FileOutputStream(testZipWithSubdirectoryTooBig))) { + byte[] data = populateLargeRandomBytes(); + + ZipEntry e1 = new ZipEntry(FILENAME_1); + out.putNextEntry(e1); + out.write(data, 0, data.length); // Write the byte array to the output stream + out.closeEntry(); + ZipEntry e2 = new ZipEntry(FILENAME_2); + out.putNextEntry(e2); + out.write(data, 0, data.length); + out.closeEntry(); + ZipEntry e3 = new ZipEntry(FILENAME_3); + out.putNextEntry(e3); + out.write(data, 0, data.length); + out.closeEntry(); + } + } + + @NotNull + private static byte[] populateLargeRandomBytes() { + int targetSize = 104857600; // 100MB in bytes + byte[] data = new byte[targetSize + 1]; // Initialize byte array, size greater than threshold 100MB + SecureRandom random = new SecureRandom(); + random.nextBytes(data); // Fill byte array with random bytes + return data; } @Test void unzipOneFileSucceedsAndLogs() throws IOException { final var data = Files.readAllBytes(testZipWithOneFile.toPath()); - - assertDoesNotThrow(() -> UnzipUtility.unzip(data, unzipOutputDir)); + assertThatCode(() -> UnzipUtility.unzip(data, unzipOutputDir)).doesNotThrowAnyException(); final Path path = unzipOutputDir.resolve(FILENAME_1); assert (path.toFile().exists()); assert (logCaptor.infoLogs().contains("- Extracted update file " + path)); @@ -105,8 +141,7 @@ void unzipOneFileSucceedsAndLogs() throws IOException { @Test void unzipWithSubDirectorySucceedsAndLogs() throws IOException { final var data = Files.readAllBytes(testZipWithSubdirectory.toPath()); - - assertDoesNotThrow(() -> UnzipUtility.unzip(data, unzipOutputDir)); + assertThatCode(() -> UnzipUtility.unzip(data, unzipOutputDir)).doesNotThrowAnyException(); final Path path = unzipOutputDir.resolve(FILENAME_1); assert (path.toFile().exists()); assert (logCaptor.infoLogs().contains("- Extracted update file " + path)); @@ -119,6 +154,26 @@ void unzipWithSubDirectorySucceedsAndLogs() throws IOException { @Test void failsWhenArchiveIsInvalidZip() { final byte[] data = new byte[] {'a', 'b', 'c'}; - assertThrows(IOException.class, () -> UnzipUtility.unzip(data, unzipOutputDir)); + assertThatExceptionOfType(IOException.class).isThrownBy(() -> UnzipUtility.unzip(data, unzipOutputDir)); + } + + @Test + @DisplayName("Unzip Fails when Zip file Size exceeds Thresholds") + void failsWhenArchiveFileSizeTooBig() throws IOException { + final var data = Files.readAllBytes(testZipWithSubdirectoryTooBig.toPath()); + assertThatExceptionOfType(IOException.class) + .isThrownBy(() -> UnzipUtility.unzip(data, unzipOutputDir)) + .withMessage("Zip bomb attack detected, aborting unzip!"); + } + + @Test + @DisplayName("Unzip Fails when Unable to Create the Parent Directories") + void failsWhenUnableToCreateParentDirectory() throws IOException { + final var data = Files.readAllBytes(testZipWithOneFile.toPath()); + // Mock the destination directory + Path dstDirMock = Paths.get("/mocked/dst/dir"); + assertThatExceptionOfType(IOException.class) + .isThrownBy(() -> UnzipUtility.unzip(data, dstDirMock)) + .withMessage("Unable to create the parent directories for the file: " + dstDirMock + "/fileToZip.txt"); } } diff --git a/hedera-node/hedera-network-admin-service-impl/src/test/java/com/hedera/node/app/service/networkadmin/impl/test/schemas/InitialModServiceAdminSchemaTest.java b/hedera-node/hedera-network-admin-service-impl/src/test/java/com/hedera/node/app/service/networkadmin/impl/test/schemas/InitialModServiceAdminSchemaTest.java new file mode 100644 index 000000000000..ba09970dc936 --- /dev/null +++ b/hedera-node/hedera-network-admin-service-impl/src/test/java/com/hedera/node/app/service/networkadmin/impl/test/schemas/InitialModServiceAdminSchemaTest.java @@ -0,0 +1,80 @@ +/* + * Copyright (C) 2024 Hedera Hashgraph, LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.hedera.node.app.service.networkadmin.impl.test.schemas; + +import static com.hedera.node.app.service.networkadmin.impl.FreezeServiceImpl.FREEZE_TIME_KEY; +import static com.hedera.node.app.service.networkadmin.impl.FreezeServiceImpl.UPGRADE_FILE_HASH_KEY; +import static com.hedera.node.app.spi.fixtures.state.TestSchema.CURRENT_VERSION; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.AssertionsForClassTypes.assertThatCode; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.mockito.BDDMockito.given; + +import com.hedera.node.app.service.networkadmin.impl.schemas.InitialModServiceAdminSchema; +import com.hedera.node.app.spi.state.MigrationContext; +import com.hedera.node.app.spi.state.StateDefinition; +import com.swirlds.state.spi.WritableSingletonState; +import com.swirlds.state.spi.WritableStates; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +@ExtendWith(MockitoExtension.class) +public class InitialModServiceAdminSchemaTest { + + @Mock + private MigrationContext migrationContext; + + @Mock + private WritableStates writableStates; + + @Mock + private WritableSingletonState upgradeFileHashKeyState; + + @Mock + private WritableSingletonState freezeTimeKeyState; + + private InitialModServiceAdminSchema subject; + + @BeforeEach + void setUp() { + subject = new InitialModServiceAdminSchema(CURRENT_VERSION); + } + + @Test + void registersExpectedSchema() { + final var statesToCreate = subject.statesToCreate(); + assertThat(statesToCreate.size()).isEqualTo(2); + final var iter = + statesToCreate.stream().map(StateDefinition::stateKey).sorted().iterator(); + assertEquals(FREEZE_TIME_KEY, iter.next()); + assertEquals(UPGRADE_FILE_HASH_KEY, iter.next()); + } + + @Test + void setFSasExpectedAndHappyPathMigration() { + InitialModServiceAdminSchema.setFs(true); + given(migrationContext.previousVersion()).willReturn(null); + given(migrationContext.newStates()).willReturn(writableStates); + given(writableStates.getSingleton(UPGRADE_FILE_HASH_KEY)).willReturn(upgradeFileHashKeyState); + given(writableStates.getSingleton(FREEZE_TIME_KEY)).willReturn(freezeTimeKeyState); + + assertThatCode(() -> subject.migrate(migrationContext)).doesNotThrowAnyException(); + } +} diff --git a/hedera-node/hedera-network-admin-service-impl/src/test/java/com/hedera/node/app/service/networkadmin/impl/test/schemas/InitialModServiceNetworkSchemaTest.java b/hedera-node/hedera-network-admin-service-impl/src/test/java/com/hedera/node/app/service/networkadmin/impl/test/schemas/InitialModServiceNetworkSchemaTest.java new file mode 100644 index 000000000000..f67f76868a08 --- /dev/null +++ b/hedera-node/hedera-network-admin-service-impl/src/test/java/com/hedera/node/app/service/networkadmin/impl/test/schemas/InitialModServiceNetworkSchemaTest.java @@ -0,0 +1,64 @@ +/* + * Copyright (C) 2024 Hedera Hashgraph, LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.hedera.node.app.service.networkadmin.impl.test.schemas; + +import static com.hedera.node.app.spi.fixtures.state.TestSchema.CURRENT_VERSION; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.AssertionsForClassTypes.assertThatCode; + +import com.hedera.node.app.service.networkadmin.impl.schemas.InitialModServiceNetworkSchema; +import com.hedera.node.app.spi.fixtures.util.LogCaptor; +import com.hedera.node.app.spi.fixtures.util.LogCaptureExtension; +import com.hedera.node.app.spi.fixtures.util.LoggingSubject; +import com.hedera.node.app.spi.fixtures.util.LoggingTarget; +import com.hedera.node.app.spi.state.MigrationContext; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +@ExtendWith({MockitoExtension.class, LogCaptureExtension.class}) +public class InitialModServiceNetworkSchemaTest { + + @LoggingTarget + private LogCaptor logCaptor; + + @Mock + private MigrationContext migrationContext; + + @LoggingSubject + private InitialModServiceNetworkSchema subject; + + @BeforeEach + void setUp() { + subject = new InitialModServiceNetworkSchema(CURRENT_VERSION); + } + + @Test + void registersExpectedSchema() { + final var statesToCreate = subject.statesToCreate(); + assertThat(statesToCreate.size()).isEqualTo(0); + } + + @Test + void HappyPathMigration() { + assertThatCode(() -> subject.migrate(migrationContext)).doesNotThrowAnyException(); + subject.migrate(migrationContext); + assertThat(logCaptor.infoLogs()).contains("BBM: no actions required for network service"); + } +} diff --git a/hedera-node/hedera-network-admin-service/build.gradle.kts b/hedera-node/hedera-network-admin-service/build.gradle.kts index 566aedbe78c5..bfa9d473394a 100644 --- a/hedera-node/hedera-network-admin-service/build.gradle.kts +++ b/hedera-node/hedera-network-admin-service/build.gradle.kts @@ -14,6 +14,9 @@ * limitations under the License. */ -plugins { id("com.hedera.hashgraph.conventions") } +plugins { + id("com.hedera.gradle.services") + id("com.hedera.gradle.services-publish") +} description = "Hedera NetworkAdmin Service API" diff --git a/hedera-node/hedera-schedule-service-impl/build.gradle.kts b/hedera-node/hedera-schedule-service-impl/build.gradle.kts index 92e76d08e381..b31574c38344 100644 --- a/hedera-node/hedera-schedule-service-impl/build.gradle.kts +++ b/hedera-node/hedera-schedule-service-impl/build.gradle.kts @@ -14,7 +14,10 @@ * limitations under the License. */ -plugins { id("com.hedera.hashgraph.conventions") } +plugins { + id("com.hedera.gradle.services") + id("com.hedera.gradle.services-publish") +} description = "Default Hedera Schedule Service Implementation" @@ -24,6 +27,9 @@ testModuleInfo { requires("com.hedera.node.app.service.token.impl") requires("com.hedera.node.app.spi.test.fixtures") requires("com.hedera.node.config.test.fixtures") + requires("com.swirlds.platform.core.test.fixtures") + requires("com.swirlds.common") + requires("com.swirlds.merkle") requires("com.hedera.node.app") requires("com.swirlds.base") requires("com.swirlds.config.extensions.test.fixtures") diff --git a/hedera-node/hedera-schedule-service-impl/src/main/java/com/hedera/node/app/service/schedule/impl/ReadableScheduleStoreImpl.java b/hedera-node/hedera-schedule-service-impl/src/main/java/com/hedera/node/app/service/schedule/impl/ReadableScheduleStoreImpl.java index 6f3638a156a3..83201c4f1d6b 100644 --- a/hedera-node/hedera-schedule-service-impl/src/main/java/com/hedera/node/app/service/schedule/impl/ReadableScheduleStoreImpl.java +++ b/hedera-node/hedera-schedule-service-impl/src/main/java/com/hedera/node/app/service/schedule/impl/ReadableScheduleStoreImpl.java @@ -22,9 +22,9 @@ import com.hedera.hapi.node.state.schedule.Schedule; import com.hedera.hapi.node.state.schedule.ScheduleList; import com.hedera.node.app.service.schedule.ReadableScheduleStore; -import com.hedera.node.app.spi.state.ReadableKVState; -import com.hedera.node.app.spi.state.ReadableStates; import com.hedera.pbj.runtime.io.buffer.Bytes; +import com.swirlds.state.spi.ReadableKVState; +import com.swirlds.state.spi.ReadableStates; import edu.umd.cs.findbugs.annotations.NonNull; import edu.umd.cs.findbugs.annotations.Nullable; import java.util.List; diff --git a/hedera-node/hedera-schedule-service-impl/src/main/java/com/hedera/node/app/service/schedule/impl/ScheduleServiceImpl.java b/hedera-node/hedera-schedule-service-impl/src/main/java/com/hedera/node/app/service/schedule/impl/ScheduleServiceImpl.java index 40a129a0393a..5dc1a4aa4859 100644 --- a/hedera-node/hedera-schedule-service-impl/src/main/java/com/hedera/node/app/service/schedule/impl/ScheduleServiceImpl.java +++ b/hedera-node/hedera-schedule-service-impl/src/main/java/com/hedera/node/app/service/schedule/impl/ScheduleServiceImpl.java @@ -20,12 +20,13 @@ import com.hedera.node.app.service.mono.state.merkle.MerkleScheduledTransactions; import com.hedera.node.app.service.schedule.ScheduleService; import com.hedera.node.app.service.schedule.impl.schemas.InitialModServiceScheduleSchema; +import com.hedera.node.app.spi.Service; import com.hedera.node.app.spi.state.SchemaRegistry; import edu.umd.cs.findbugs.annotations.NonNull; import edu.umd.cs.findbugs.annotations.Nullable; /** - * Standard implementation of the {@link ScheduleService} {@link com.hedera.node.app.spi.Service}. + * Standard implementation of the {@link ScheduleService} {@link Service}. */ public final class ScheduleServiceImpl implements ScheduleService { public static final String SCHEDULES_BY_ID_KEY = "SCHEDULES_BY_ID"; diff --git a/hedera-node/hedera-schedule-service-impl/src/main/java/com/hedera/node/app/service/schedule/impl/WritableScheduleStoreImpl.java b/hedera-node/hedera-schedule-service-impl/src/main/java/com/hedera/node/app/service/schedule/impl/WritableScheduleStoreImpl.java index 0c225df7d039..85d589c2c27b 100644 --- a/hedera-node/hedera-schedule-service-impl/src/main/java/com/hedera/node/app/service/schedule/impl/WritableScheduleStoreImpl.java +++ b/hedera-node/hedera-schedule-service-impl/src/main/java/com/hedera/node/app/service/schedule/impl/WritableScheduleStoreImpl.java @@ -27,10 +27,10 @@ import com.hedera.node.app.service.schedule.WritableScheduleStore; import com.hedera.node.app.spi.metrics.StoreMetricsService; import com.hedera.node.app.spi.metrics.StoreMetricsService.StoreType; -import com.hedera.node.app.spi.state.WritableKVState; -import com.hedera.node.app.spi.state.WritableStates; import com.hedera.node.config.data.SchedulingConfig; import com.swirlds.config.api.Configuration; +import com.swirlds.state.spi.WritableKVState; +import com.swirlds.state.spi.WritableStates; import edu.umd.cs.findbugs.annotations.NonNull; import edu.umd.cs.findbugs.annotations.Nullable; import java.time.Instant; @@ -152,7 +152,10 @@ private Schedule markDeleted(final Schedule schedule, final Instant consensusTim schedule.originalCreateTransaction(), schedule.signatories()); } - /** @inheritDoc */ + + /** + * {@inheritDoc} + */ @Override public void purgeExpiredSchedulesBetween(long firstSecondToExpire, long lastSecondToExpire) { for (long i = firstSecondToExpire; i <= lastSecondToExpire; i++) { diff --git a/hedera-node/hedera-schedule-service-impl/src/main/java/com/hedera/node/app/service/schedule/impl/handlers/HandlerUtility.java b/hedera-node/hedera-schedule-service-impl/src/main/java/com/hedera/node/app/service/schedule/impl/handlers/HandlerUtility.java index 56efce6a2581..fe394630f939 100644 --- a/hedera-node/hedera-schedule-service-impl/src/main/java/com/hedera/node/app/service/schedule/impl/handlers/HandlerUtility.java +++ b/hedera-node/hedera-schedule-service-impl/src/main/java/com/hedera/node/app/service/schedule/impl/handlers/HandlerUtility.java @@ -103,6 +103,9 @@ public static TransactionBody childAsOrdinary(@NonNull final Schedule scheduleIn case TOKEN_FEE_SCHEDULE_UPDATE -> ordinary.tokenFeeScheduleUpdate( scheduledTransaction.tokenFeeScheduleUpdateOrThrow()); case UTIL_PRNG -> ordinary.utilPrng(scheduledTransaction.utilPrngOrThrow()); + case NODE_CREATE -> ordinary.nodeCreate(scheduledTransaction.nodeCreateOrThrow()); + case NODE_UPDATE -> ordinary.nodeUpdate(scheduledTransaction.nodeUpdateOrThrow()); + case NODE_DELETE -> ordinary.nodeDelete(scheduledTransaction.nodeDeleteOrThrow()); case UNSET -> throw new HandleException(ResponseCodeEnum.INVALID_TRANSACTION); } } @@ -150,6 +153,9 @@ static HederaFunctionality functionalityForType(final DataOneOfType transactionT case TOKEN_FEE_SCHEDULE_UPDATE -> HederaFunctionality.TOKEN_FEE_SCHEDULE_UPDATE; case UTIL_PRNG -> HederaFunctionality.UTIL_PRNG; case TOKEN_UPDATE_NFTS -> HederaFunctionality.TOKEN_UPDATE_NFTS; + case NODE_CREATE -> HederaFunctionality.NODE_CREATE; + case NODE_UPDATE -> HederaFunctionality.NODE_UPDATE; + case NODE_DELETE -> HederaFunctionality.NODE_DELETE; case UNSET -> HederaFunctionality.NONE; }; } diff --git a/hedera-node/hedera-schedule-service-impl/src/main/java/com/hedera/node/app/service/schedule/impl/schemas/InitialModServiceScheduleSchema.java b/hedera-node/hedera-schedule-service-impl/src/main/java/com/hedera/node/app/service/schedule/impl/schemas/InitialModServiceScheduleSchema.java index f2cb95ab44b1..1db5899d62bd 100644 --- a/hedera-node/hedera-schedule-service-impl/src/main/java/com/hedera/node/app/service/schedule/impl/schemas/InitialModServiceScheduleSchema.java +++ b/hedera-node/hedera-schedule-service-impl/src/main/java/com/hedera/node/app/service/schedule/impl/schemas/InitialModServiceScheduleSchema.java @@ -32,9 +32,9 @@ import com.hedera.node.app.spi.state.MigrationContext; import com.hedera.node.app.spi.state.Schema; import com.hedera.node.app.spi.state.StateDefinition; -import com.hedera.node.app.spi.state.WritableKVState; -import com.hedera.node.app.spi.state.WritableKVStateBase; import com.hedera.pbj.runtime.ParseException; +import com.swirlds.platform.state.spi.WritableKVStateBase; +import com.swirlds.state.spi.WritableKVState; import edu.umd.cs.findbugs.annotations.NonNull; import edu.umd.cs.findbugs.annotations.Nullable; import java.util.ArrayList; diff --git a/hedera-node/hedera-schedule-service-impl/src/main/java/module-info.java b/hedera-node/hedera-schedule-service-impl/src/main/java/module-info.java index e1de177cc5c0..e2b03f90e6ed 100644 --- a/hedera-node/hedera-schedule-service-impl/src/main/java/module-info.java +++ b/hedera-node/hedera-schedule-service-impl/src/main/java/module-info.java @@ -7,6 +7,7 @@ requires transitive com.hedera.node.hapi; requires transitive com.hedera.pbj.runtime; requires transitive com.swirlds.config.api; + requires transitive com.swirlds.state.api; requires transitive dagger; requires transitive javax.inject; requires com.hedera.node.app.hapi.utils; @@ -14,6 +15,7 @@ requires com.hedera.node.app.service.token; requires com.hedera.node.config; requires com.google.common; + requires com.swirlds.platform.core; requires static com.github.spotbugs.annotations; requires static java.compiler; // javax.annotation.processing.Generated requires org.apache.logging.log4j; diff --git a/hedera-node/hedera-schedule-service-impl/src/test/java/com/hedera/node/app/service/schedule/impl/ReadableScheduleStoreTest.java b/hedera-node/hedera-schedule-service-impl/src/test/java/com/hedera/node/app/service/schedule/impl/ReadableScheduleStoreTest.java index 0a82664d2fd9..191c76f2956b 100644 --- a/hedera-node/hedera-schedule-service-impl/src/test/java/com/hedera/node/app/service/schedule/impl/ReadableScheduleStoreTest.java +++ b/hedera-node/hedera-schedule-service-impl/src/test/java/com/hedera/node/app/service/schedule/impl/ReadableScheduleStoreTest.java @@ -45,6 +45,11 @@ void constructorThrowsIfStatesIsNull() { .isInstanceOf(NullPointerException.class); } + @Test + void getNullReturnsNull() { + assertThat(scheduleStore.get(null)).isNull(); + } + @Test void getsExpectedSize() { assertThat(scheduleStore.numSchedulesInState()).isEqualTo(2); diff --git a/hedera-node/hedera-schedule-service-impl/src/test/java/com/hedera/node/app/service/schedule/impl/ScheduleServiceImplTest.java b/hedera-node/hedera-schedule-service-impl/src/test/java/com/hedera/node/app/service/schedule/impl/ScheduleServiceImplTest.java index b44d232ac487..64f515cd1b42 100644 --- a/hedera-node/hedera-schedule-service-impl/src/test/java/com/hedera/node/app/service/schedule/impl/ScheduleServiceImplTest.java +++ b/hedera-node/hedera-schedule-service-impl/src/test/java/com/hedera/node/app/service/schedule/impl/ScheduleServiceImplTest.java @@ -16,6 +16,10 @@ package com.hedera.node.app.service.schedule.impl; +import static org.mockito.Mockito.mock; + +import com.hedera.hapi.node.base.SemanticVersion; +import com.hedera.node.app.service.mono.state.merkle.MerkleScheduledTransactions; import com.hedera.node.app.service.schedule.ScheduleService; import com.hedera.node.app.spi.fixtures.state.TestSchema; import com.hedera.node.app.spi.state.Schema; @@ -23,6 +27,7 @@ import com.hedera.node.app.spi.state.StateDefinition; import java.util.List; import java.util.Set; +import org.assertj.core.api.Assertions; import org.assertj.core.api.BDDAssertions; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; @@ -62,4 +67,22 @@ void registersExpectedSchema() { BDDAssertions.assertThat(statesList.get(1)).isEqualTo(ScheduleServiceImpl.SCHEDULES_BY_EXPIRY_SEC_KEY); BDDAssertions.assertThat(statesList.get(2)).isEqualTo(ScheduleServiceImpl.SCHEDULES_BY_ID_KEY); } + + @Test + void triesToSetStateWithoutRegisteredScheduleSchema() { + final ScheduleServiceImpl subject = new ScheduleServiceImpl(); + final var input = mock(MerkleScheduledTransactions.class); + Assertions.assertThatThrownBy(() -> subject.setFs(input)).isInstanceOf(NullPointerException.class); + } + + @Test + void stateSettersDontThrow() { + final ScheduleServiceImpl subject = new ScheduleServiceImpl(); + final var registry = mock(SchemaRegistry.class); + // registerSchemas(...) is required to instantiate the schedule schema + subject.registerSchemas(registry, SemanticVersion.DEFAULT); + + // Verify that the following doesn't throw an exception + subject.setFs(mock(MerkleScheduledTransactions.class)); + } } diff --git a/hedera-node/hedera-schedule-service-impl/src/test/java/com/hedera/node/app/service/schedule/impl/ScheduleStoreUtilityTest.java b/hedera-node/hedera-schedule-service-impl/src/test/java/com/hedera/node/app/service/schedule/impl/ScheduleStoreUtilityTest.java index 316efa10199a..4d7c75c5ff4d 100644 --- a/hedera-node/hedera-schedule-service-impl/src/test/java/com/hedera/node/app/service/schedule/impl/ScheduleStoreUtilityTest.java +++ b/hedera-node/hedera-schedule-service-impl/src/test/java/com/hedera/node/app/service/schedule/impl/ScheduleStoreUtilityTest.java @@ -19,6 +19,7 @@ import static org.assertj.core.api.BDDAssertions.assertThat; import com.hedera.hapi.node.base.ScheduleID; +import com.hedera.hapi.node.scheduled.SchedulableTransactionBody; import com.hedera.hapi.node.state.schedule.Schedule; import com.hedera.node.app.spi.workflows.PreCheckException; import com.hedera.pbj.runtime.io.buffer.Bytes; @@ -62,6 +63,9 @@ void verifyIncludedFieldsChangeHash() { testSchedule.scheduledTransaction(createAlternateScheduled()); hashValue = ScheduleStoreUtility.calculateBytesHash(testSchedule.build()); assertThat(hashValue).isNotEqualTo(origHashValue); + testSchedule.scheduledTransaction((SchedulableTransactionBody) null); + hashValue = ScheduleStoreUtility.calculateBytesHash(testSchedule.build()); + assertThat(hashValue).isNotEqualTo(origHashValue); testSchedule.scheduledTransaction(scheduleInState.scheduledTransaction()); hashValue = ScheduleStoreUtility.calculateBytesHash(testSchedule.build()); assertThat(hashValue).isEqualTo(origHashValue); @@ -70,6 +74,10 @@ void verifyIncludedFieldsChangeHash() { testSchedule.memo(ODD_MEMO); hashValue = ScheduleStoreUtility.calculateBytesHash(testSchedule.build()); assertThat(hashValue).isNotEqualTo(origHashValue); + //noinspection DataFlowIssue + testSchedule.memo(null); + hashValue = ScheduleStoreUtility.calculateBytesHash(testSchedule.build()); + assertThat(hashValue).isNotEqualTo(origHashValue); testSchedule.memo(scheduleInState.memo()); hashValue = ScheduleStoreUtility.calculateBytesHash(testSchedule.build()); assertThat(hashValue).isEqualTo(origHashValue); diff --git a/hedera-node/hedera-schedule-service-impl/src/test/java/com/hedera/node/app/service/schedule/impl/ScheduleTestBase.java b/hedera-node/hedera-schedule-service-impl/src/test/java/com/hedera/node/app/service/schedule/impl/ScheduleTestBase.java index 7d9665722696..274fca963d52 100644 --- a/hedera-node/hedera-schedule-service-impl/src/test/java/com/hedera/node/app/service/schedule/impl/ScheduleTestBase.java +++ b/hedera-node/hedera-schedule-service-impl/src/test/java/com/hedera/node/app/service/schedule/impl/ScheduleTestBase.java @@ -79,16 +79,8 @@ import com.hedera.node.app.service.schedule.WritableScheduleStore; import com.hedera.node.app.service.token.ReadableAccountStore; import com.hedera.node.app.service.token.impl.ReadableAccountStoreImpl; -import com.hedera.node.app.spi.fixtures.state.MapReadableStates; -import com.hedera.node.app.spi.fixtures.state.MapWritableKVState; import com.hedera.node.app.spi.fixtures.state.MapWritableStates; import com.hedera.node.app.spi.metrics.StoreMetricsService; -import com.hedera.node.app.spi.state.ReadableKVState; -import com.hedera.node.app.spi.state.ReadableKVStateBase; -import com.hedera.node.app.spi.state.ReadableStates; -import com.hedera.node.app.spi.state.WritableKVState; -import com.hedera.node.app.spi.state.WritableKVStateBase; -import com.hedera.node.app.spi.state.WritableStates; import com.hedera.node.app.spi.workflows.PreCheckException; import com.hedera.node.app.workflows.dispatcher.ReadableStoreFactory; import com.hedera.node.config.data.SchedulingConfig; @@ -96,6 +88,14 @@ import com.hedera.pbj.runtime.io.buffer.Bytes; import com.swirlds.base.utility.Pair; import com.swirlds.config.api.Configuration; +import com.swirlds.platform.state.spi.ReadableKVStateBase; +import com.swirlds.platform.state.spi.WritableKVStateBase; +import com.swirlds.platform.test.fixtures.state.MapReadableStates; +import com.swirlds.platform.test.fixtures.state.MapWritableKVState; +import com.swirlds.state.spi.ReadableKVState; +import com.swirlds.state.spi.ReadableStates; +import com.swirlds.state.spi.WritableKVState; +import com.swirlds.state.spi.WritableStates; import edu.umd.cs.findbugs.annotations.NonNull; import edu.umd.cs.findbugs.annotations.Nullable; import java.security.InvalidKeyException; diff --git a/hedera-node/hedera-schedule-service-impl/src/test/java/com/hedera/node/app/service/schedule/impl/WritableScheduleStoreImplTest.java b/hedera-node/hedera-schedule-service-impl/src/test/java/com/hedera/node/app/service/schedule/impl/WritableScheduleStoreImplTest.java index fbbfdba2766a..43de1fc03adc 100644 --- a/hedera-node/hedera-schedule-service-impl/src/test/java/com/hedera/node/app/service/schedule/impl/WritableScheduleStoreImplTest.java +++ b/hedera-node/hedera-schedule-service-impl/src/test/java/com/hedera/node/app/service/schedule/impl/WritableScheduleStoreImplTest.java @@ -17,6 +17,7 @@ package com.hedera.node.app.service.schedule.impl; import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; import com.hedera.hapi.node.base.Key; import com.hedera.hapi.node.base.ScheduleID; @@ -40,6 +41,12 @@ void setUp() throws PreCheckException, InvalidKeyException { setUpBase(); } + @Test + void verifyGetForModifyNullIsNull() { + final var actual = writableSchedules.getForModify(null); + assertThat(actual).isNull(); + } + @Test void verifyDeleteMarksDeletedInState() { final ScheduleID idToDelete = scheduleInState.scheduleId(); @@ -55,6 +62,21 @@ private Timestamp asTimestamp(final Instant testConsensusTime) { return new Timestamp(testConsensusTime.getEpochSecond(), testConsensusTime.getNano()); } + @Test + void verifyDeleteNonExistentScheduleThrows() { + assertThatThrownBy(() -> writableSchedules.delete(ScheduleID.DEFAULT, testConsensusTime)) + .isInstanceOf(IllegalStateException.class) + .hasMessage( + "Schedule to be deleted, ScheduleID[shardNum=0, realmNum=0, scheduleNum=0], not found in state."); + } + + @Test + void verifyDeleteNullScheduleThrows() { + assertThatThrownBy(() -> writableSchedules.delete(null, testConsensusTime)) + .isInstanceOf(IllegalStateException.class) + .hasMessage("Request to delete null schedule ID cannot be fulfilled."); + } + @Test void verifyPutModifiesState() { final ScheduleID idToDelete = scheduleInState.scheduleId(); @@ -72,7 +94,7 @@ void verifyPutModifiesState() { } @Test - void verifyPutDoesDedupliction() { + void verifyPutDoesDeduplication() { final ScheduleID idToDelete = scheduleInState.scheduleId(); Schedule actual = writableById.getForModify(idToDelete); assertThat(actual).isNotNull(); diff --git a/hedera-node/hedera-schedule-service-impl/src/test/java/com/hedera/node/app/service/schedule/impl/handlers/ScheduleHandlersTest.java b/hedera-node/hedera-schedule-service-impl/src/test/java/com/hedera/node/app/service/schedule/impl/handlers/ScheduleHandlersTest.java new file mode 100644 index 000000000000..a9e9cc9888cf --- /dev/null +++ b/hedera-node/hedera-schedule-service-impl/src/test/java/com/hedera/node/app/service/schedule/impl/handlers/ScheduleHandlersTest.java @@ -0,0 +1,66 @@ +/* + * Copyright (C) 2024 Hedera Hashgraph, LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.hedera.node.app.service.schedule.impl.handlers; + +import org.assertj.core.api.Assertions; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +@ExtendWith(MockitoExtension.class) +class ScheduleHandlersTest { + @Mock + private ScheduleCreateHandler scheduleCreateHandler; + + @Mock + private ScheduleDeleteHandler scheduleDeleteHandler; + + @Mock + private ScheduleGetInfoHandler scheduleGetInfoHandler; + + @Mock + private ScheduleSignHandler scheduleSignHandler; + + @SuppressWarnings("DataFlowIssue") + @Test + void constructorNullArgsThrow() { + Assertions.assertThatThrownBy(() -> + new ScheduleHandlers(null, scheduleDeleteHandler, scheduleGetInfoHandler, scheduleSignHandler)) + .isInstanceOf(NullPointerException.class); + Assertions.assertThatThrownBy(() -> + new ScheduleHandlers(scheduleCreateHandler, null, scheduleGetInfoHandler, scheduleSignHandler)) + .isInstanceOf(NullPointerException.class); + Assertions.assertThatThrownBy(() -> + new ScheduleHandlers(scheduleCreateHandler, scheduleDeleteHandler, null, scheduleSignHandler)) + .isInstanceOf(NullPointerException.class); + Assertions.assertThatThrownBy(() -> new ScheduleHandlers( + scheduleCreateHandler, scheduleDeleteHandler, scheduleGetInfoHandler, null)) + .isInstanceOf(NullPointerException.class); + } + + @Test + void gettersWork() { + final var subject = new ScheduleHandlers( + scheduleCreateHandler, scheduleDeleteHandler, scheduleGetInfoHandler, scheduleSignHandler); + + Assertions.assertThat(subject.scheduleCreateHandler()).isEqualTo(scheduleCreateHandler); + Assertions.assertThat(subject.scheduleDeleteHandler()).isEqualTo(scheduleDeleteHandler); + Assertions.assertThat(subject.scheduleGetInfoHandler()).isEqualTo(scheduleGetInfoHandler); + Assertions.assertThat(subject.scheduleSignHandler()).isEqualTo(scheduleSignHandler); + } +} diff --git a/hedera-node/hedera-schedule-service-impl/src/test/java/com/hedera/node/app/service/schedule/impl/schemas/InitialModServiceScheduleSchemaTest.java b/hedera-node/hedera-schedule-service-impl/src/test/java/com/hedera/node/app/service/schedule/impl/schemas/InitialModServiceScheduleSchemaTest.java new file mode 100644 index 000000000000..0d9150182420 --- /dev/null +++ b/hedera-node/hedera-schedule-service-impl/src/test/java/com/hedera/node/app/service/schedule/impl/schemas/InitialModServiceScheduleSchemaTest.java @@ -0,0 +1,286 @@ +/* + * Copyright (C) 2024 Hedera Hashgraph, LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.hedera.node.app.service.schedule.impl.schemas; + +import static com.hedera.node.app.service.schedule.impl.ScheduleServiceImpl.SCHEDULES_BY_EQUALITY_KEY; +import static com.hedera.node.app.service.schedule.impl.ScheduleServiceImpl.SCHEDULES_BY_EXPIRY_SEC_KEY; +import static com.hedera.node.app.service.schedule.impl.ScheduleServiceImpl.SCHEDULES_BY_ID_KEY; +import static org.assertj.core.api.AssertionsForClassTypes.assertThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verifyNoInteractions; + +import com.hedera.hapi.node.base.ScheduleID; +import com.hedera.hapi.node.base.SemanticVersion; +import com.hedera.hapi.node.state.primitives.ProtoBytes; +import com.hedera.hapi.node.state.primitives.ProtoLong; +import com.hedera.hapi.node.state.schedule.Schedule; +import com.hedera.hapi.node.state.schedule.ScheduleList; +import com.hedera.node.app.service.mono.state.merkle.MerkleScheduledTransactions; +import com.hedera.node.app.service.mono.state.merkle.MerkleScheduledTransactionsState; +import com.hedera.node.app.service.mono.state.submerkle.RichInstant; +import com.hedera.node.app.service.mono.state.virtual.EntityNumVirtualKey; +import com.hedera.node.app.service.mono.state.virtual.schedule.ScheduleEqualityVirtualKey; +import com.hedera.node.app.service.mono.state.virtual.schedule.ScheduleEqualityVirtualValue; +import com.hedera.node.app.service.mono.state.virtual.schedule.ScheduleSecondVirtualValue; +import com.hedera.node.app.service.mono.state.virtual.schedule.ScheduleVirtualValue; +import com.hedera.node.app.service.mono.state.virtual.temporal.SecondSinceEpocVirtualKey; +import com.hedera.node.app.spi.fixtures.state.MapWritableStates; +import com.hedera.node.app.spi.state.MigrationContext; +import com.hedera.node.app.spi.state.StateDefinition; +import com.swirlds.merkle.map.MerkleMap; +import com.swirlds.platform.test.fixtures.state.MapWritableKVState; +import com.swirlds.state.spi.WritableStates; +import java.time.Instant; +import java.util.Comparator; +import java.util.List; +import java.util.Map; +import org.assertj.core.api.Assertions; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.BDDMockito; + +class InitialModServiceScheduleSchemaTest { + + private static final SemanticVersion VERSION_123 = new SemanticVersion(1, 2, 3, "pre", "build"); + + private InitialModServiceScheduleSchema subject; + + @BeforeEach + void setUp() { + subject = new InitialModServiceScheduleSchema(VERSION_123); + } + + @SuppressWarnings("DataFlowIssue") + @Test + void constructorNullArgThrows() { + Assertions.assertThatThrownBy(() -> new InitialModServiceScheduleSchema(null)) + .isInstanceOf(NullPointerException.class); + } + + @Test + void constructorHappyPath() { + // Instance created in setup + Assertions.assertThat(subject.getVersion()).isEqualTo(VERSION_123); + } + + @Test + void statesToCreateIsCorrect() { + var sortedResult = subject.statesToCreate().stream() + .sorted(Comparator.comparing(StateDefinition::stateKey)) + .toList(); + + final var stateDef1 = sortedResult.getFirst(); + Assertions.assertThat(stateDef1.stateKey()).isEqualTo(SCHEDULES_BY_EQUALITY_KEY); + Assertions.assertThat(stateDef1.keyCodec()).isEqualTo(ProtoBytes.PROTOBUF); + Assertions.assertThat(stateDef1.valueCodec()).isEqualTo(ScheduleList.PROTOBUF); + final var stateDef2 = sortedResult.get(1); + Assertions.assertThat(stateDef2.stateKey()).isEqualTo(SCHEDULES_BY_EXPIRY_SEC_KEY); + Assertions.assertThat(stateDef2.keyCodec()).isEqualTo(ProtoLong.PROTOBUF); + Assertions.assertThat(stateDef2.valueCodec()).isEqualTo(ScheduleList.PROTOBUF); + final var stateDef3 = sortedResult.get(2); + Assertions.assertThat(stateDef3.stateKey()).isEqualTo(SCHEDULES_BY_ID_KEY); + Assertions.assertThat(stateDef3.keyCodec()).isEqualTo(ScheduleID.PROTOBUF); + Assertions.assertThat(stateDef3.valueCodec()).isEqualTo(Schedule.PROTOBUF); + } + + @Test + void migrateWithoutFromStateDoesNothing() { + // ensure the 'from' state is null + subject.setFs(null); + + final var ctx = mock(MigrationContext.class); + Assertions.assertThatNoException().isThrownBy(() -> subject.migrate(ctx)); + verifyNoInteractions(ctx); + } + + @Test + void migrateWithEmptyFromStateMakesNoChanges() { + // set up the empty 'from' state + subject.setFs(new MerkleScheduledTransactions()); + + // set up the 'to' state + final var writableStates = newEmptySchedulesWritableStates(); + final var ctx = newMockCtx(writableStates); + + subject.migrate(ctx); + + verifyEmptyById(writableStates); + verifyEmptyByExpiry(writableStates); + verifyEmptyByEquality(writableStates); + } + + @Test + void migrateScheduleById() { + // set up the 'byId' portion of the 'from' state + final var expectedScheduleId = 5; + final var byId = new MerkleMap(); + byId.put( + new EntityNumVirtualKey(expectedScheduleId), + ScheduleVirtualValue.from(new byte[0], Instant.now().getEpochSecond())); + + // mock the other pieces of the 'from' state + final var state = mock(MerkleScheduledTransactionsState.class); + final var byEquality = mock(MerkleMap.class); + final var byExpiry = mock(MerkleMap.class); + subject.setFs(new MerkleScheduledTransactions(List.of(state, byId, byExpiry, byEquality))); + + // set up the 'to' state + final var writableStates = newEmptySchedulesWritableStates(); + final var ctx = newMockCtx(writableStates); + + subject.migrate(ctx); + + verifyNonEmptyById(writableStates); + final long actualKey = writableStates + .get(SCHEDULES_BY_ID_KEY) + .keys() + .next() + .scheduleNum(); + assertThat(actualKey).isEqualTo(expectedScheduleId); + verifyEmptyByExpiry(writableStates); + verifyEmptyByEquality(writableStates); + } + + @Test + void migrateScheduleByExpiry() { + final var currentTime = Instant.now().getEpochSecond(); + + // set up the 'byExpiry' portion of the 'from' state + final var byExpiry = new MerkleMap(); + final var scheduleValue = new ScheduleSecondVirtualValue(); + scheduleValue.add(RichInstant.fromJava(Instant.now()), new PartialLongListFixture(List.of(3L))); + byExpiry.put(new SecondSinceEpocVirtualKey(currentTime), scheduleValue); + + // mock the other pieces of the 'from' state + final var state = mock(MerkleScheduledTransactionsState.class); + final var byEquality = mock(MerkleMap.class); + final var byId = mock(MerkleMap.class); + subject.setFs(new MerkleScheduledTransactions(List.of(state, byId, byExpiry, byEquality))); + + // set up the 'to' state + final var writableStates = newEmptySchedulesWritableStates(); + final var ctx = newMockCtx(writableStates); + + subject.migrate(ctx); + + verifyNonEmptyByExpiry(writableStates); + assertThat(writableStates.get(SCHEDULES_BY_EXPIRY_SEC_KEY).get(new ProtoLong(currentTime))) + .isNotNull(); + verifyEmptyById(writableStates); + verifyEmptyByEquality(writableStates); + } + + @Test + void migrateScheduleByEquality() { + final var currentTime = Instant.now().getEpochSecond(); + + // Set up the 'byId' portion of the 'from' state (needed for the 'byEquality' piece) + final var byId = new MerkleMap(); + byId.put(new EntityNumVirtualKey(6), ScheduleVirtualValue.from(new byte[0], currentTime)); + + // set up the 'byEquality' portion of the 'from' state + final var byEquality = new MerkleMap(); + byEquality.put(new ScheduleEqualityVirtualKey(), new ScheduleEqualityVirtualValue(Map.of("ignoredKey", 6L))); + + // mock the other pieces of the 'from' state + final var state = mock(MerkleScheduledTransactionsState.class); + final var byExpiry = mock(MerkleMap.class); + subject.setFs(new MerkleScheduledTransactions(List.of(state, byId, byExpiry, byEquality))); + + // set up the 'to' state + final var writableStates = newEmptySchedulesWritableStates(); + final var ctx = newMockCtx(writableStates); + + subject.migrate(ctx); + + verifyNonEmptyById(writableStates); + verifyNonEmptyByEquality(writableStates); + verifyEmptyByExpiry(writableStates); + } + + @Test + void statesToRemoveIsEmpty() { + Assertions.assertThat(subject.statesToRemove()).isEmpty(); + } + + @SuppressWarnings("DataFlowIssue") + @Test + void restartNullArgThrows() { + Assertions.assertThatThrownBy(() -> subject.restart(null)).isInstanceOf(NullPointerException.class); + } + + @Test + void restartHappyPath() { + Assertions.assertThatNoException().isThrownBy(() -> subject.restart(mock(MigrationContext.class))); + } + + private WritableStates newEmptySchedulesWritableStates() { + final var writableStates = MapWritableStates.builder() + .state(new MapWritableKVState<>(SCHEDULES_BY_ID_KEY)) + .state(new MapWritableKVState<>(SCHEDULES_BY_EQUALITY_KEY)) + .state(new MapWritableKVState<>(SCHEDULES_BY_EXPIRY_SEC_KEY)) + .build(); + verifyEmptyById(writableStates); + verifyEmptyByExpiry(writableStates); + verifyEmptyByEquality(writableStates); + + return writableStates; + } + + private MigrationContext newMockCtx(final WritableStates ws) { + final var ctx = mock(MigrationContext.class); + BDDMockito.given(ctx.newStates()).willReturn(ws); + return ctx; + } + + private void verifyEmptyById(final WritableStates actual) { + verifyEmptyScheduleState(SCHEDULES_BY_ID_KEY, actual); + } + + private void verifyEmptyByExpiry(final WritableStates actual) { + verifyEmptyScheduleState(SCHEDULES_BY_EXPIRY_SEC_KEY, actual); + } + + private void verifyEmptyByEquality(final WritableStates actual) { + verifyEmptyScheduleState(SCHEDULES_BY_EQUALITY_KEY, actual); + } + + private void verifyEmptyScheduleState(final String scheduleStateKey, final WritableStates actual) { + assertThat(actual.get(scheduleStateKey).size()).isZero(); + } + + private void verifyNonEmptyById(final WritableStates actual) { + verifyNonEmptyScheduleState(SCHEDULES_BY_ID_KEY, actual); + } + + private void verifyNonEmptyByExpiry(final WritableStates actual) { + verifyNonEmptyScheduleState(SCHEDULES_BY_EXPIRY_SEC_KEY, actual); + } + + private void verifyNonEmptyByEquality(final WritableStates actual) { + verifyNonEmptyScheduleState(SCHEDULES_BY_EQUALITY_KEY, actual); + } + + private void verifyNonEmptyScheduleState(final String scheduleStateKey, final WritableStates actual) { + // Note: we're not worried so much about a correct entity migration; just that the entity migration happened + final var scheduleKey = actual.get(scheduleStateKey).keys().next(); + assertThat(scheduleKey).isNotNull(); + final var scheduleVal = actual.get(scheduleStateKey).get(scheduleKey); + assertThat(scheduleVal).isNotNull(); + assertThat(actual.get(scheduleStateKey).size()).isEqualTo(1); + } +} diff --git a/hedera-node/hedera-schedule-service-impl/src/test/java/com/hedera/node/app/service/schedule/impl/schemas/PartialLongListFixture.java b/hedera-node/hedera-schedule-service-impl/src/test/java/com/hedera/node/app/service/schedule/impl/schemas/PartialLongListFixture.java new file mode 100644 index 000000000000..fc80be505e59 --- /dev/null +++ b/hedera-node/hedera-schedule-service-impl/src/test/java/com/hedera/node/app/service/schedule/impl/schemas/PartialLongListFixture.java @@ -0,0 +1,278 @@ +/* + * Copyright (C) 2024 Hedera Hashgraph, LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.hedera.node.app.service.schedule.impl.schemas; + +import java.util.List; +import java.util.Spliterator; +import org.eclipse.collections.api.LazyLongIterable; +import org.eclipse.collections.api.LongIterable; +import org.eclipse.collections.api.bag.primitive.MutableLongBag; +import org.eclipse.collections.api.block.function.primitive.LongToObjectFunction; +import org.eclipse.collections.api.block.function.primitive.ObjectLongIntToObjectFunction; +import org.eclipse.collections.api.block.function.primitive.ObjectLongToObjectFunction; +import org.eclipse.collections.api.block.predicate.primitive.LongPredicate; +import org.eclipse.collections.api.block.procedure.primitive.LongIntProcedure; +import org.eclipse.collections.api.block.procedure.primitive.LongProcedure; +import org.eclipse.collections.api.iterator.LongIterator; +import org.eclipse.collections.api.list.ListIterable; +import org.eclipse.collections.api.list.primitive.ImmutableLongList; +import org.eclipse.collections.api.list.primitive.LongList; +import org.eclipse.collections.api.list.primitive.MutableLongList; +import org.eclipse.collections.api.set.primitive.MutableLongSet; + +/** + * A partial implementation of {@link LongList} that delegates to a regular java.lang {@link List}. This + * class exists solely to avoid depending on any eclipse implementations in this module's test code. + */ +class PartialLongListFixture implements LongList { + private final List list; + + PartialLongListFixture(final List list) { + this.list = list; + } + + @Override + public long get(int i) { + return list.get(i); + } + + @Override + public long dotProduct(LongList longList) { + throw new UnsupportedOperationException("not supported"); + } + + @Override + public int binarySearch(long l) { + throw new UnsupportedOperationException("not supported"); + } + + @Override + public int lastIndexOf(long l) { + throw new UnsupportedOperationException("not supported"); + } + + @Override + public long getLast() { + throw new UnsupportedOperationException("not supported"); + } + + @Override + public LazyLongIterable asReversed() { + throw new UnsupportedOperationException("not supported"); + } + + @Override + public long getFirst() { + throw new UnsupportedOperationException("not supported"); + } + + @Override + public int indexOf(long l) { + throw new UnsupportedOperationException("not supported"); + } + + @Override + public LongIterator longIterator() { + throw new UnsupportedOperationException("not supported"); + } + + @Override + public long[] toArray() { + return list.stream().mapToLong(Long::longValue).toArray(); + } + + @Override + public boolean contains(long l) { + throw new UnsupportedOperationException("not supported"); + } + + @Override + public boolean containsAll(long... longs) { + throw new UnsupportedOperationException("not supported"); + } + + @Override + public boolean containsAll(LongIterable longIterable) { + throw new UnsupportedOperationException("not supported"); + } + + @Override + public void forEach(LongProcedure longProcedure) { + throw new UnsupportedOperationException("not supported"); + } + + @Override + public void each(LongProcedure longProcedure) { + throw new UnsupportedOperationException("not supported"); + } + + @Override + public LongList select(LongPredicate longPredicate) { + throw new UnsupportedOperationException("not supported"); + } + + @Override + public LongList reject(LongPredicate longPredicate) { + throw new UnsupportedOperationException("not supported"); + } + + @Override + public ListIterable collect(LongToObjectFunction longToObjectFunction) { + throw new UnsupportedOperationException("not supported"); + } + + @Override + public long detectIfNone(LongPredicate longPredicate, long l) { + throw new UnsupportedOperationException("not supported"); + } + + @Override + public int count(LongPredicate longPredicate) { + throw new UnsupportedOperationException("not supported"); + } + + @Override + public boolean anySatisfy(LongPredicate longPredicate) { + throw new UnsupportedOperationException("not supported"); + } + + @Override + public boolean allSatisfy(LongPredicate longPredicate) { + throw new UnsupportedOperationException("not supported"); + } + + @Override + public boolean noneSatisfy(LongPredicate longPredicate) { + throw new UnsupportedOperationException("not supported"); + } + + @Override + public MutableLongList toList() { + throw new UnsupportedOperationException("not supported"); + } + + @Override + public MutableLongSet toSet() { + throw new UnsupportedOperationException("not supported"); + } + + @Override + public MutableLongBag toBag() { + throw new UnsupportedOperationException("not supported"); + } + + @Override + public LazyLongIterable asLazy() { + throw new UnsupportedOperationException("not supported"); + } + + @Override + public T injectInto(T t, ObjectLongToObjectFunction objectLongToObjectFunction) { + throw new UnsupportedOperationException("not supported"); + } + + @Override + public long sum() { + throw new UnsupportedOperationException("not supported"); + } + + @Override + public long max() { + throw new UnsupportedOperationException("not supported"); + } + + @Override + public long maxIfEmpty(long l) { + throw new UnsupportedOperationException("not supported"); + } + + @Override + public long min() { + throw new UnsupportedOperationException("not supported"); + } + + @Override + public long minIfEmpty(long l) { + throw new UnsupportedOperationException("not supported"); + } + + @Override + public double average() { + throw new UnsupportedOperationException("not supported"); + } + + @Override + public double median() { + throw new UnsupportedOperationException("not supported"); + } + + @Override + public long[] toSortedArray() { + return new long[0]; + } + + @Override + public MutableLongList toSortedList() { + throw new UnsupportedOperationException("not supported"); + } + + @Override + public ImmutableLongList toImmutable() { + throw new UnsupportedOperationException("not supported"); + } + + @Override + public LongList distinct() { + throw new UnsupportedOperationException("not supported"); + } + + @Override + public T injectIntoWithIndex( + T t, ObjectLongIntToObjectFunction objectLongIntToObjectFunction) { + throw new UnsupportedOperationException("not supported"); + } + + @Override + public void forEachWithIndex(LongIntProcedure longIntProcedure) { + throw new UnsupportedOperationException("not supported"); + } + + @Override + public LongList toReversed() { + throw new UnsupportedOperationException("not supported"); + } + + @Override + public LongList subList(int i, int i1) { + throw new UnsupportedOperationException("not supported"); + } + + @Override + public Spliterator.OfLong spliterator() { + throw new UnsupportedOperationException("not supported"); + } + + @Override + public int size() { + return list.size(); + } + + @Override + public void appendString(Appendable appendable, String s, String s1, String s2) { + throw new UnsupportedOperationException("not supported"); + } +} diff --git a/hedera-node/hedera-schedule-service/build.gradle.kts b/hedera-node/hedera-schedule-service/build.gradle.kts index bfc00aa8d285..98caa3badf33 100644 --- a/hedera-node/hedera-schedule-service/build.gradle.kts +++ b/hedera-node/hedera-schedule-service/build.gradle.kts @@ -14,6 +14,14 @@ * limitations under the License. */ -plugins { id("com.hedera.hashgraph.conventions") } +plugins { + id("com.hedera.gradle.services") + id("com.hedera.gradle.services-publish") +} description = "Hedera Scheduled Service API" + +testModuleInfo { + requires("org.assertj.core") + requires("org.junit.jupiter.api") +} diff --git a/hedera-node/hedera-schedule-service/src/main/java/com/hedera/node/app/service/schedule/ScheduleRecordBuilder.java b/hedera-node/hedera-schedule-service/src/main/java/com/hedera/node/app/service/schedule/ScheduleRecordBuilder.java index fb78535e489c..3fcf0be45316 100644 --- a/hedera-node/hedera-schedule-service/src/main/java/com/hedera/node/app/service/schedule/ScheduleRecordBuilder.java +++ b/hedera-node/hedera-schedule-service/src/main/java/com/hedera/node/app/service/schedule/ScheduleRecordBuilder.java @@ -23,7 +23,7 @@ /** * This record builder interface defines the methods needed to record schedule transactions as well * as adding a reference ID to the scheduled child transaction when it is executed. - * + *

    * It is important to note that any implementation cannot merely implement this interface. Any * implementation must also implement all other record builder interfaces, as the child transaction * to which an instance of the implementation will be provided may be substantially any non-query @@ -31,11 +31,11 @@ */ public interface ScheduleRecordBuilder { @NonNull - public ScheduleRecordBuilder scheduleRef(ScheduleID scheduleRef); + ScheduleRecordBuilder scheduleRef(ScheduleID scheduleRef); @NonNull - public ScheduleRecordBuilder scheduleID(ScheduleID scheduleID); + ScheduleRecordBuilder scheduleID(ScheduleID scheduleID); @NonNull - public ScheduleRecordBuilder scheduledTransactionID(TransactionID scheduledTransactionID); + ScheduleRecordBuilder scheduledTransactionID(TransactionID scheduledTransactionID); } diff --git a/hedera-node/hedera-schedule-service/src/test/java/com/hedera/node/app/service/schedule/ScheduleServiceDefinitionTest.java b/hedera-node/hedera-schedule-service/src/test/java/com/hedera/node/app/service/schedule/ScheduleServiceDefinitionTest.java new file mode 100644 index 000000000000..8bfe51cd4b76 --- /dev/null +++ b/hedera-node/hedera-schedule-service/src/test/java/com/hedera/node/app/service/schedule/ScheduleServiceDefinitionTest.java @@ -0,0 +1,44 @@ +/* + * Copyright (C) 2024 Hedera Hashgraph, LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.hedera.node.app.service.schedule; + +import com.hedera.hapi.node.base.Transaction; +import com.hedera.hapi.node.transaction.Query; +import com.hedera.hapi.node.transaction.Response; +import com.hedera.hapi.node.transaction.TransactionResponse; +import com.hedera.pbj.runtime.RpcMethodDefinition; +import org.assertj.core.api.Assertions; +import org.junit.jupiter.api.Test; + +class ScheduleServiceDefinitionTest { + + @Test + void checkBasePath() { + Assertions.assertThat(ScheduleServiceDefinition.INSTANCE.basePath()).isEqualTo("proto.ScheduleService"); + } + + @Test + void methodsDefined() { + final var methods = ScheduleServiceDefinition.INSTANCE.methods(); + Assertions.assertThat(methods) + .containsExactlyInAnyOrder( + new RpcMethodDefinition<>("createSchedule", Transaction.class, TransactionResponse.class), + new RpcMethodDefinition<>("signSchedule", Transaction.class, TransactionResponse.class), + new RpcMethodDefinition<>("deleteSchedule", Transaction.class, TransactionResponse.class), + new RpcMethodDefinition<>("getScheduleInfo", Query.class, Response.class)); + } +} diff --git a/hedera-node/hedera-schedule-service/src/test/java/com/hedera/node/app/service/schedule/ScheduleServiceTest.java b/hedera-node/hedera-schedule-service/src/test/java/com/hedera/node/app/service/schedule/ScheduleServiceTest.java new file mode 100644 index 000000000000..4f6153f0fdad --- /dev/null +++ b/hedera-node/hedera-schedule-service/src/test/java/com/hedera/node/app/service/schedule/ScheduleServiceTest.java @@ -0,0 +1,35 @@ +/* + * Copyright (C) 2024 Hedera Hashgraph, LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.hedera.node.app.service.schedule; + +import org.assertj.core.api.Assertions; +import org.junit.jupiter.api.Test; + +class ScheduleServiceTest { + + private final ScheduleService subject = new ScheduleService() {}; + + @Test + void verifyServiceName() { + Assertions.assertThat(subject.getServiceName()).isEqualTo("ScheduleService"); + } + + @Test + void verifyRpcDefs() { + Assertions.assertThat(subject.rpcDefinitions()).containsExactlyInAnyOrder(ScheduleServiceDefinition.INSTANCE); + } +} diff --git a/hedera-node/hedera-smart-contract-service-impl/build.gradle.kts b/hedera-node/hedera-smart-contract-service-impl/build.gradle.kts index dd4bfc14ca4f..ba8db5051e6c 100644 --- a/hedera-node/hedera-smart-contract-service-impl/build.gradle.kts +++ b/hedera-node/hedera-smart-contract-service-impl/build.gradle.kts @@ -14,7 +14,10 @@ * limitations under the License. */ -plugins { id("com.hedera.hashgraph.conventions") } +plugins { + id("com.hedera.gradle.services") + id("com.hedera.gradle.services-publish") +} description = "Default Hedera Smart Contract Service Implementation" @@ -24,6 +27,7 @@ testModuleInfo { requires("com.hedera.node.app.service.mono.test.fixtures") requires("com.hedera.node.app.spi.test.fixtures") requires("com.hedera.node.config.test.fixtures") + requires("com.swirlds.platform.core.test.fixtures") requires("com.swirlds.config.extensions.test.fixtures") requires("org.assertj.core") requires("org.junit.jupiter.api") diff --git a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/ContractServiceModule.java b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/ContractServiceModule.java index 19ff0c2622e4..c0f5a8e30e71 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/ContractServiceModule.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/ContractServiceModule.java @@ -21,6 +21,7 @@ import static com.hedera.node.app.service.contract.impl.hevm.HederaEvmVersion.VERSION_038; import static com.hedera.node.app.service.contract.impl.hevm.HederaEvmVersion.VERSION_046; import static com.hedera.node.app.service.contract.impl.hevm.HederaEvmVersion.VERSION_050; +import static com.hedera.node.app.service.contract.impl.hevm.HederaEvmVersion.VERSION_051; import static org.hyperledger.besu.evm.internal.EvmConfiguration.WorldUpdaterMode.JOURNALED; import com.hedera.node.app.service.contract.impl.annotations.ServicesV030; @@ -28,6 +29,7 @@ import com.hedera.node.app.service.contract.impl.annotations.ServicesV038; import com.hedera.node.app.service.contract.impl.annotations.ServicesV046; import com.hedera.node.app.service.contract.impl.annotations.ServicesV050; +import com.hedera.node.app.service.contract.impl.annotations.ServicesV051; import com.hedera.node.app.service.contract.impl.annotations.ServicesVersionKey; import com.hedera.node.app.service.contract.impl.exec.QueryComponent; import com.hedera.node.app.service.contract.impl.exec.TransactionComponent; @@ -39,6 +41,7 @@ import com.hedera.node.app.service.contract.impl.exec.v038.V038Module; import com.hedera.node.app.service.contract.impl.exec.v046.V046Module; import com.hedera.node.app.service.contract.impl.exec.v050.V050Module; +import com.hedera.node.app.service.contract.impl.exec.v051.V051Module; import dagger.Binds; import dagger.Module; import dagger.Provides; @@ -62,6 +65,7 @@ V038Module.class, V046Module.class, V050Module.class, + V051Module.class, ProcessorModule.class }, subcomponents = {TransactionComponent.class, QueryComponent.class}) @@ -105,4 +109,10 @@ static EvmConfiguration provideEvmConfiguration() { @Singleton @ServicesVersionKey(VERSION_050) TransactionProcessor bindV050Processor(@ServicesV050 @NonNull final TransactionProcessor processor); + + @Binds + @IntoMap + @Singleton + @ServicesVersionKey(VERSION_051) + TransactionProcessor bindV051Processor(@ServicesV051 @NonNull final TransactionProcessor processor); } diff --git a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/annotations/ServicesV051.java b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/annotations/ServicesV051.java new file mode 100644 index 000000000000..2a472bab56b0 --- /dev/null +++ b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/annotations/ServicesV051.java @@ -0,0 +1,37 @@ +/* + * Copyright (C) 2024 Hedera Hashgraph, LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.hedera.node.app.service.contract.impl.annotations; + +import static java.lang.annotation.ElementType.METHOD; +import static java.lang.annotation.ElementType.PARAMETER; +import static java.lang.annotation.ElementType.TYPE; +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +import java.lang.annotation.Documented; +import java.lang.annotation.Retention; +import java.lang.annotation.Target; +import javax.inject.Qualifier; + +/** + * Qualifies a binding for use with the {@code v0.51} (Cancun) Services EVM. Adds support for the + * Hedera Account Service system contract. + */ +@Target({METHOD, PARAMETER, TYPE}) +@Retention(RUNTIME) +@Documented +@Qualifier +public @interface ServicesV051 {} diff --git a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/ContextTransactionProcessor.java b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/ContextTransactionProcessor.java index 5c834467c53f..47f3efc1495c 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/ContextTransactionProcessor.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/ContextTransactionProcessor.java @@ -17,7 +17,6 @@ package com.hedera.node.app.service.contract.impl.exec; import static com.hedera.hapi.node.base.ResponseCodeEnum.INVALID_CONTRACT_ID; -import static com.hedera.node.app.service.contract.impl.hevm.HederaEvmVersion.EVM_VERSIONS; import static java.util.Objects.requireNonNull; import com.hedera.hapi.node.base.ContractID; @@ -30,7 +29,6 @@ import com.hedera.node.app.service.contract.impl.hevm.HederaEvmContext; import com.hedera.node.app.service.contract.impl.hevm.HederaEvmTransaction; import com.hedera.node.app.service.contract.impl.hevm.HederaEvmTransactionResult; -import com.hedera.node.app.service.contract.impl.hevm.HederaEvmVersion; import com.hedera.node.app.service.contract.impl.hevm.HederaWorldUpdater; import com.hedera.node.app.service.contract.impl.hevm.HydratedEthTxData; import com.hedera.node.app.service.contract.impl.infra.HevmTransactionFactory; @@ -41,7 +39,6 @@ import com.swirlds.config.api.Configuration; import edu.umd.cs.findbugs.annotations.NonNull; import edu.umd.cs.findbugs.annotations.Nullable; -import java.util.Map; import java.util.Objects; import java.util.concurrent.Callable; import java.util.function.Supplier; @@ -62,11 +59,11 @@ public class ContextTransactionProcessor implements Callable { @Nullable private final HydratedEthTxData hydratedEthTxData; + private final TransactionProcessor processor; private final ActionSidecarContentTracer tracer; private final RootProxyWorldUpdater rootProxyWorldUpdater; public final HevmTransactionFactory hevmTransactionFactory; private final Supplier feesOnlyUpdater; - private final Map processors; private final CustomGasCharging gasCharging; @Inject @@ -80,13 +77,13 @@ public ContextTransactionProcessor( @NonNull final RootProxyWorldUpdater worldUpdater, @NonNull final HevmTransactionFactory hevmTransactionFactory, @NonNull final Supplier feesOnlyUpdater, - @NonNull final Map processors, + @NonNull final TransactionProcessor processor, @NonNull final CustomGasCharging customGasCharging) { this.context = Objects.requireNonNull(context); this.hydratedEthTxData = hydratedEthTxData; this.tracer = Objects.requireNonNull(tracer); this.feesOnlyUpdater = Objects.requireNonNull(feesOnlyUpdater); - this.processors = Objects.requireNonNull(processors); + this.processor = Objects.requireNonNull(processor); this.rootProxyWorldUpdater = Objects.requireNonNull(worldUpdater); this.configuration = Objects.requireNonNull(configuration); this.contractsConfig = Objects.requireNonNull(contractsConfig); @@ -108,9 +105,6 @@ public CallOutcome call() { return chargeFeesAndReturnOutcome(hevmTransaction); } - // Get the appropriate processor for the EVM version - final var processor = processors.get(EVM_VERSIONS.get(contractsConfig.evmVersion())); - // Process the transaction and return its outcome try { var result = processor.processTransaction( @@ -166,7 +160,7 @@ private HederaEvmTransaction safeCreateHevmTransaction() { } } - private CallOutcome chargeFeesAndReturnOutcome(HederaEvmTransaction hevmTransaction) { + private CallOutcome chargeFeesAndReturnOutcome(@NonNull final HederaEvmTransaction hevmTransaction) { // If there was an exception while creating the HederaEvmTransaction and the transaction is a ContractCall // charge fees to the sender and return a CallOutcome reflecting the error. final var senderId = context.body().transactionIDOrThrow().accountIDOrThrow(); diff --git a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/FeatureFlags.java b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/FeatureFlags.java index a1b99c01d2ec..de8e2f5cbbf0 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/FeatureFlags.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/FeatureFlags.java @@ -20,6 +20,7 @@ import static com.hedera.node.app.service.contract.impl.exec.utils.FrameUtils.contractsConfigOf; import com.hedera.hapi.streams.SidecarType; +import com.hedera.node.config.data.ContractsConfig; import com.swirlds.config.api.Configuration; import edu.umd.cs.findbugs.annotations.NonNull; import edu.umd.cs.findbugs.annotations.Nullable; @@ -70,15 +71,14 @@ default boolean isImplicitCreationEnabled(@NonNull MessageFrame frame) { /** * If true calls to non-existing contract addresses will result in a successful NOOP. If false, * calls such calls will result in a revert with status {@code INVALID_SOLIDITY_ADDRESS}. - * @param config the {@link Configuration} - * @#param possiblyGrandFathered the account number to check for grandfathering + * @param config the contract configuration for the transaction + * @param possiblyGrandFatheredEntityNum the account number to check for grandfathering * @return true if calls to non-existing contract addresses will result in a successful NOOP. */ default boolean isAllowCallsToNonContractAccountsEnabled( - @NonNull Configuration config, @Nullable Long possiblyGrandFatheredEntityNum) { + @NonNull final ContractsConfig config, @Nullable Long possiblyGrandFatheredEntityNum) { return false; } - ; /** * If true, charge intrinsic gas for calls that fail with a pre-EVM exception. @@ -87,4 +87,12 @@ default boolean isAllowCallsToNonContractAccountsEnabled( default boolean isChargeGasOnPreEvmException(@NonNull Configuration config) { return false; } + + /** + * If true, enable calls to the Hedera Account Service system contract + * @return true whether calls to Hedera Account Service system contract are enabled. + */ + default boolean isHederaAccountServiceEnabled(@NonNull Configuration config) { + return false; + } } diff --git a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/FrameRunner.java b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/FrameRunner.java index cda9154896e9..907591e87c55 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/FrameRunner.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/FrameRunner.java @@ -17,6 +17,7 @@ package com.hedera.node.app.service.contract.impl.exec; import static com.hedera.node.app.service.contract.impl.exec.failure.CustomExceptionalHaltReason.INSUFFICIENT_CHILD_RECORDS; +import static com.hedera.node.app.service.contract.impl.exec.failure.CustomExceptionalHaltReason.INVALID_FEE_SUBMITTED; import static com.hedera.node.app.service.contract.impl.exec.utils.FrameUtils.contractsConfigOf; import static com.hedera.node.app.service.contract.impl.exec.utils.FrameUtils.getAndClearPropagatedCallFailure; import static com.hedera.node.app.service.contract.impl.exec.utils.FrameUtils.maybeNext; @@ -24,8 +25,6 @@ import static com.hedera.node.app.service.contract.impl.exec.utils.FrameUtils.setPropagatedCallFailure; import static com.hedera.node.app.service.contract.impl.hevm.HederaEvmTransactionResult.failureFrom; import static com.hedera.node.app.service.contract.impl.hevm.HederaEvmTransactionResult.successFrom; -import static com.hedera.node.app.service.contract.impl.hevm.HevmPropagatedCallFailure.NONE; -import static com.hedera.node.app.service.contract.impl.hevm.HevmPropagatedCallFailure.RESULT_CANNOT_BE_EXTERNALIZED; import static com.hedera.node.app.service.contract.impl.utils.ConversionUtils.asEvmContractId; import static com.hedera.node.app.service.contract.impl.utils.ConversionUtils.asNumberedContractId; import static com.hedera.node.app.service.contract.impl.utils.ConversionUtils.isLongZero; @@ -39,6 +38,7 @@ import com.hedera.node.app.service.contract.impl.exec.processors.CustomMessageCallProcessor; import com.hedera.node.app.service.contract.impl.hevm.ActionSidecarContentTracer; import com.hedera.node.app.service.contract.impl.hevm.HederaEvmTransactionResult; +import com.hedera.node.app.service.contract.impl.hevm.HevmPropagatedCallFailure; import edu.umd.cs.findbugs.annotations.NonNull; import edu.umd.cs.findbugs.annotations.Nullable; import javax.inject.Inject; @@ -46,6 +46,7 @@ import org.hyperledger.besu.datatypes.Address; import org.hyperledger.besu.evm.frame.ExceptionalHaltReason; import org.hyperledger.besu.evm.frame.MessageFrame; +import org.hyperledger.besu.evm.operation.Operation; import org.hyperledger.besu.evm.processor.ContractCreationProcessor; /** @@ -144,12 +145,17 @@ private void runToCompletion( frame.getExceptionalHaltReason().ifPresent(haltReason -> propagateHaltException(frame, haltReason)); // For mono-service compatibility, we need to also halt the frame on the stack that // executed the CALL operation whose dispatched frame failed due to a missing receiver - // signature; since mono-service did that check as part of the CALL operation itself + // signature; since mono-service did that check as part of the CALL operation itself. final var maybeFailureToPropagate = getAndClearPropagatedCallFailure(frame); - if (maybeFailureToPropagate != NONE) { + if (maybeFailureToPropagate != HevmPropagatedCallFailure.NONE) { maybeNext(frame).ifPresent(f -> { f.setState(EXCEPTIONAL_HALT); f.setExceptionalHaltReason(maybeFailureToPropagate.exceptionalHaltReason()); + // Finalize the CONTRACT_ACTION for the propagated halt frame as well + maybeFailureToPropagate + .exceptionalHaltReason() + .ifPresent(reason -> tracer.tracePostExecution( + f, new Operation.OperationResult(frame.getRemainingGas(), reason))); }); } } @@ -166,7 +172,9 @@ private long effectiveGasUsed(final long gasLimit, @NonNull final MessageFrame f // potentially other cases could be handled here if necessary private void propagateHaltException(MessageFrame frame, ExceptionalHaltReason haltReason) { if (haltReason.equals(INSUFFICIENT_CHILD_RECORDS)) { - setPropagatedCallFailure(frame, RESULT_CANNOT_BE_EXTERNALIZED); + setPropagatedCallFailure(frame, HevmPropagatedCallFailure.RESULT_CANNOT_BE_EXTERNALIZED); + } else if (haltReason.equals(INVALID_FEE_SUBMITTED)) { + setPropagatedCallFailure(frame, HevmPropagatedCallFailure.INVALID_FEE_SUBMITTED); } } } diff --git a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/TransactionModule.java b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/TransactionModule.java index cc173712a8dc..18fd9eb05622 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/TransactionModule.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/TransactionModule.java @@ -16,9 +16,11 @@ package com.hedera.node.app.service.contract.impl.exec; +import static com.hedera.node.app.service.contract.impl.hevm.HederaEvmVersion.EVM_VERSIONS; import static java.util.Objects.requireNonNull; import com.hedera.hapi.node.base.HederaFunctionality; +import com.hedera.hapi.node.base.Key; import com.hedera.hapi.node.base.SubType; import com.hedera.hapi.node.transaction.ExchangeRate; import com.hedera.node.app.service.contract.impl.annotations.ChildTransactionResourcePrices; @@ -40,8 +42,10 @@ import com.hedera.node.app.service.contract.impl.hevm.HandleContextHevmBlocks; import com.hedera.node.app.service.contract.impl.hevm.HederaEvmBlocks; import com.hedera.node.app.service.contract.impl.hevm.HederaEvmContext; +import com.hedera.node.app.service.contract.impl.hevm.HederaEvmVersion; import com.hedera.node.app.service.contract.impl.hevm.HederaWorldUpdater; import com.hedera.node.app.service.contract.impl.hevm.HydratedEthTxData; +import com.hedera.node.app.service.contract.impl.infra.EthTxSigsCache; import com.hedera.node.app.service.contract.impl.infra.EthereumCallDataHydration; import com.hedera.node.app.service.contract.impl.records.ContractOperationRecordBuilder; import com.hedera.node.app.service.contract.impl.state.EvmFrameStateFactory; @@ -54,17 +58,34 @@ import com.hedera.node.app.spi.workflows.ComputeDispatchFeesAsTopLevel; import com.hedera.node.app.spi.workflows.FunctionalityResourcePrices; import com.hedera.node.app.spi.workflows.HandleContext; +import com.hedera.node.config.data.ContractsConfig; import com.hedera.node.config.data.HederaConfig; +import com.hedera.pbj.runtime.io.buffer.Bytes; import dagger.Binds; import dagger.Module; import dagger.Provides; import edu.umd.cs.findbugs.annotations.NonNull; import edu.umd.cs.findbugs.annotations.Nullable; import java.time.Instant; +import java.util.Map; import java.util.function.Supplier; @Module(includes = {TransactionConfigModule.class, TransactionInitialStateModule.class}) public interface TransactionModule { + @Provides + @TransactionScope + static TransactionProcessor provideTransactionProcessor( + @NonNull final ContractsConfig contractsConfig, + @NonNull final Map processors) { + return processors.get(EVM_VERSIONS.get(contractsConfig.evmVersion())); + } + + @Provides + @TransactionScope + static FeatureFlags provideFeatureFlags(@NonNull final TransactionProcessor processor) { + return processor.featureFlags(); + } + @Provides @TransactionScope static TinybarValues provideTinybarValues( @@ -127,6 +148,29 @@ static HydratedEthTxData maybeProvideHydratedEthTxData( : null; } + /** + * If the top-level transaction is an {@code EthereumTransaction}, provides an ECDSA {@link Key} with + * the public key of the sender address; otherwise returns {@code null}. + * + * @param ethTxSigsCache the cache of Ethereum transaction signatures + * @param hydratedEthTxData the hydrated Ethereum transaction data, if this is an {@code EthereumTransaction} + * @return the ECDSA {@link Key} with the public key of the sender address, or {@code null} + */ + @Provides + @Nullable + @TransactionScope + static Key provideSenderEcdsaKey( + @NonNull final EthTxSigsCache ethTxSigsCache, @Nullable final HydratedEthTxData hydratedEthTxData) { + if (hydratedEthTxData != null && hydratedEthTxData.isAvailable()) { + final var ethTxSigs = ethTxSigsCache.computeIfAbsent(hydratedEthTxData.ethTxDataOrThrow()); + return Key.newBuilder() + .ecdsaSecp256k1(Bytes.wrap(ethTxSigs.publicKey())) + .build(); + } else { + return null; + } + } + @Provides @TransactionScope static ActionSidecarContentTracer provideActionSidecarContentTracer() { diff --git a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/TransactionProcessor.java b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/TransactionProcessor.java index f8a8a9d6bfea..0bd6758497c7 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/TransactionProcessor.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/TransactionProcessor.java @@ -43,6 +43,7 @@ import com.hedera.node.app.service.contract.impl.state.HederaEvmAccount; import com.hedera.node.app.spi.workflows.HandleException; import com.hedera.node.app.spi.workflows.ResourceExhaustedException; +import com.hedera.node.config.data.ContractsConfig; import com.swirlds.config.api.Configuration; import edu.umd.cs.findbugs.annotations.NonNull; import edu.umd.cs.findbugs.annotations.Nullable; @@ -93,6 +94,15 @@ AccountID senderId() { } } + /** + * Returns the feature flags used by this processor. + * + * @return the feature flags + */ + public FeatureFlags featureFlags() { + return featureFlags; + } + /** * Process the given transaction, returning the result of running it to completion * and committing to the given updater. @@ -270,7 +280,8 @@ private boolean contractNotRequired(@Nullable final HederaEvmAccount to, @NonNul final var maybeGrandfatheredNumber = (to == null) ? null : to.isTokenFacade() ? null : to.hederaId().accountNumOrThrow(); - return featureFlags.isAllowCallsToNonContractAccountsEnabled(config, maybeGrandfatheredNumber); + return featureFlags.isAllowCallsToNonContractAccountsEnabled( + config.getConfigData(ContractsConfig.class), maybeGrandfatheredNumber); } private InvolvedParties partiesWhenContractRequired( @@ -316,8 +327,6 @@ private InvolvedParties partiesWhenContractNotRequired( new InvolvedParties(sender, relayer, contractIDToBesuAddress(transaction.contractIdOrThrow())); } } else { - // In order to be EVM equivalent, we need to gracefully handle calls to potentially non-existent contracts - // and thus create a receiver address even if it may not exist in the ledger updater.setContractNotRequired(); parties = new InvolvedParties( sender, diff --git a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/failure/CustomExceptionalHaltReason.java b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/failure/CustomExceptionalHaltReason.java index 7453b6c4a272..998b4a034631 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/failure/CustomExceptionalHaltReason.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/failure/CustomExceptionalHaltReason.java @@ -56,6 +56,7 @@ public String getDescription() { * @param reason the halt reason * @return the status */ + // FUTURE: refactor in the future to be more readable when we start looking for cleanups public static ResponseCodeEnum statusFor(@NonNull final ExceptionalHaltReason reason) { requireNonNull(reason); if (reason == SELF_DESTRUCT_TO_SELF) { @@ -76,6 +77,8 @@ public static ResponseCodeEnum statusFor(@NonNull final ExceptionalHaltReason re return ResponseCodeEnum.MAX_CHILD_RECORDS_EXCEEDED; } else if (reason == CustomExceptionalHaltReason.INVALID_CONTRACT_ID) { return ResponseCodeEnum.INVALID_CONTRACT_ID; + } else if (reason == CustomExceptionalHaltReason.INVALID_FEE_SUBMITTED) { + return ResponseCodeEnum.INVALID_FEE_SUBMITTED; } else { return ResponseCodeEnum.CONTRACT_EXECUTION_EXCEPTION; } diff --git a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/processors/CustomMessageCallProcessor.java b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/processors/CustomMessageCallProcessor.java index 0db82d9a63c1..bf3cee99ce02 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/processors/CustomMessageCallProcessor.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/processors/CustomMessageCallProcessor.java @@ -31,7 +31,6 @@ import static com.hedera.node.app.service.contract.impl.hevm.HevmPropagatedCallFailure.MISSING_RECEIVER_SIGNATURE; import static com.hedera.node.app.service.contract.impl.hevm.HevmPropagatedCallFailure.RESULT_CANNOT_BE_EXTERNALIZED; import static org.hyperledger.besu.evm.frame.ExceptionalHaltReason.INSUFFICIENT_GAS; -import static org.hyperledger.besu.evm.frame.ExceptionalHaltReason.PRECOMPILE_ERROR; import static org.hyperledger.besu.evm.frame.MessageFrame.State.EXCEPTIONAL_HALT; import com.hedera.hapi.streams.ContractActionType; @@ -124,29 +123,35 @@ public void start(@NonNull final MessageFrame frame, @NonNull final OperationTra return; } + final var evmPrecompile = precompiles.get(codeAddress); // Check to see if the code address is a system account and possibly halt if (addressChecks.isSystemAccount(codeAddress)) { - doHaltIfInvalidSystemCall(codeAddress, frame, tracer); + doHaltIfInvalidSystemCall(frame, tracer); if (alreadyHalted(frame)) { return; } - } - // Transfer value to the contract if required and possibly halt - if (transfersValue(frame)) { - doTransferValueOrHalt(frame, tracer); - if (alreadyHalted(frame)) { + if (evmPrecompile == null) { + handleNonExtantSystemAccount(frame, tracer); + return; } } // Handle evm precompiles - final var evmPrecompile = precompiles.get(codeAddress); if (evmPrecompile != null) { doExecutePrecompile(evmPrecompile, frame, tracer); return; } + // Transfer value to the contract if required and possibly halt + if (transfersValue(frame)) { + doTransferValueOrHalt(frame, tracer); + if (alreadyHalted(frame)) { + return; + } + } + // For mono-service fidelity, we need to consider called contracts // as a special case eligible for staking rewards if (isTopLevelTransaction(frame)) { @@ -163,6 +168,13 @@ public boolean isImplicitCreationEnabled(@NonNull Configuration config) { return featureFlags.isImplicitCreationEnabled(config); } + private void handleNonExtantSystemAccount( + @NonNull final MessageFrame frame, @NonNull final OperationTracer tracer) { + final PrecompileContractResult result = PrecompileContractResult.success(Bytes.EMPTY); + frame.clearGasRemaining(); + finishPrecompileExecution(frame, result, PRECOMPILE, (ActionSidecarContentTracer) tracer); + } + private void doExecutePrecompile( @NonNull final PrecompiledContract precompile, @NonNull final MessageFrame frame, @@ -251,12 +263,8 @@ private void doTransferValueOrHalt( } private void doHaltIfInvalidSystemCall( - @NonNull final Address codeAddress, - @NonNull final MessageFrame frame, - @NonNull final OperationTracer operationTracer) { - if (precompiles.get(codeAddress) == null) { - doHalt(frame, PRECOMPILE_ERROR, operationTracer); - } else if (transfersValue(frame)) { + @NonNull final MessageFrame frame, @NonNull final OperationTracer operationTracer) { + if (transfersValue(frame)) { doHalt(frame, INVALID_FEE_SUBMITTED, operationTracer); } } @@ -275,10 +283,6 @@ private void doHalt( doHalt(frame, reason, tracer, ForLazyCreation.NO); } - private void doHalt(@NonNull final MessageFrame frame, @NonNull final ExceptionalHaltReason reason) { - doHalt(frame, reason, null, ForLazyCreation.NO); - } - private void doHalt( @NonNull final MessageFrame frame, @NonNull final ExceptionalHaltReason reason, diff --git a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/processors/HasTranslatorsModule.java b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/processors/HasTranslatorsModule.java new file mode 100644 index 000000000000..1c2115901ebb --- /dev/null +++ b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/processors/HasTranslatorsModule.java @@ -0,0 +1,59 @@ +/* + * Copyright (C) 2023-2024 Hedera Hashgraph, LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.hedera.node.app.service.contract.impl.exec.processors; + +import com.hedera.node.app.service.contract.impl.exec.systemcontracts.common.CallTranslator; +import com.hedera.node.app.service.contract.impl.exec.systemcontracts.has.hbarallowance.HbarAllowanceTranslator; +import com.hedera.node.app.service.contract.impl.exec.systemcontracts.has.hbarapprove.HbarApproveTranslator; +import dagger.Module; +import dagger.Provides; +import dagger.multibindings.IntoSet; +import edu.umd.cs.findbugs.annotations.NonNull; +import java.util.List; +import java.util.Set; +import javax.inject.Named; +import javax.inject.Singleton; + +/** + * Provides the {@link CallTranslator} implementations for the HAS system contract. + */ +@Module +public interface HasTranslatorsModule { + @Provides + @Singleton + @Named("HasTranslators") + static List provideCallAttemptTranslators( + @NonNull @Named("HasTranslators") final Set translators) { + return List.copyOf(translators); + } + + @Provides + @Singleton + @IntoSet + @Named("HasTranslators") + static CallTranslator provideHbarAllowanceTranslator(@NonNull final HbarAllowanceTranslator translator) { + return translator; + } + + @Provides + @Singleton + @IntoSet + @Named("HasTranslators") + static CallTranslator provideHbarApproveTranslator(@NonNull final HbarApproveTranslator translator) { + return translator; + } +} diff --git a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/processors/HtsTranslatorsModule.java b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/processors/HtsTranslatorsModule.java index 5ebd89092a3a..35b35c6fed83 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/processors/HtsTranslatorsModule.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/processors/HtsTranslatorsModule.java @@ -16,7 +16,7 @@ package com.hedera.node.app.service.contract.impl.exec.processors; -import com.hedera.node.app.service.contract.impl.exec.systemcontracts.HtsCallTranslator; +import com.hedera.node.app.service.contract.impl.exec.systemcontracts.common.CallTranslator; import com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.allowance.GetAllowanceTranslator; import com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.associations.AssociationsTranslator; import com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.balanceof.BalanceOfTranslator; @@ -62,206 +62,235 @@ import edu.umd.cs.findbugs.annotations.NonNull; import java.util.List; import java.util.Set; +import javax.inject.Named; import javax.inject.Singleton; /** - * Provides the {@link HtsCallTranslator} implementations for the HTS system contract. + * Provides the {@link CallTranslator} implementations for the HTS system contract. */ @Module public interface HtsTranslatorsModule { @Provides @Singleton - static List provideCallAttemptTranslators(@NonNull final Set translators) { + @Named("HtsTranslators") + static List provideCallAttemptTranslators( + @NonNull @Named("HtsTranslators") final Set translators) { return List.copyOf(translators); } @Provides @Singleton @IntoSet - static HtsCallTranslator provideAssociationsTranslator(@NonNull final AssociationsTranslator translator) { + @Named("HtsTranslators") + static CallTranslator provideAssociationsTranslator(@NonNull final AssociationsTranslator translator) { return translator; } @Provides @Singleton @IntoSet - static HtsCallTranslator provideErc20TransfersTranslator(@NonNull final Erc20TransfersTranslator translator) { + @Named("HtsTranslators") + static CallTranslator provideErc20TransfersTranslator(@NonNull final Erc20TransfersTranslator translator) { return translator; } @Provides @Singleton @IntoSet - static HtsCallTranslator provideErc721TransferFromTranslator( - @NonNull final Erc721TransferFromTranslator translator) { + @Named("HtsTranslators") + static CallTranslator provideErc721TransferFromTranslator(@NonNull final Erc721TransferFromTranslator translator) { return translator; } @Provides @Singleton @IntoSet - static HtsCallTranslator provideClassicTransfersTranslator(@NonNull final ClassicTransfersTranslator translator) { + @Named("HtsTranslators") + static CallTranslator provideClassicTransfersTranslator(@NonNull final ClassicTransfersTranslator translator) { return translator; } @Provides @Singleton @IntoSet - static HtsCallTranslator provideMintTranslator(@NonNull final MintTranslator translator) { + @Named("HtsTranslators") + static CallTranslator provideMintTranslator(@NonNull final MintTranslator translator) { return translator; } @Provides @Singleton @IntoSet - static HtsCallTranslator provideBurnTranslator(@NonNull final BurnTranslator translator) { + @Named("HtsTranslators") + static CallTranslator provideBurnTranslator(@NonNull final BurnTranslator translator) { return translator; } @Provides @Singleton @IntoSet - static HtsCallTranslator provideCreateTranslator(@NonNull final CreateTranslator translator) { + @Named("HtsTranslators") + static CallTranslator provideCreateTranslator(@NonNull final CreateTranslator translator) { return translator; } @Provides @Singleton @IntoSet - static HtsCallTranslator provideBalanceOfTranslator(@NonNull final BalanceOfTranslator translator) { + @Named("HtsTranslators") + static CallTranslator provideBalanceOfTranslator(@NonNull final BalanceOfTranslator translator) { return translator; } @Provides @Singleton @IntoSet - static HtsCallTranslator provideIsApprovedForAllTranslator(@NonNull final IsApprovedForAllTranslator translator) { + @Named("HtsTranslators") + static CallTranslator provideIsApprovedForAllTranslator(@NonNull final IsApprovedForAllTranslator translator) { return translator; } @Provides @Singleton @IntoSet - static HtsCallTranslator provideNameTranslator(@NonNull final NameTranslator translator) { + @Named("HtsTranslators") + static CallTranslator provideNameTranslator(@NonNull final NameTranslator translator) { return translator; } @Provides @Singleton @IntoSet - static HtsCallTranslator provideSymbolTranslator(@NonNull final SymbolTranslator translator) { + @Named("HtsTranslators") + static CallTranslator provideSymbolTranslator(@NonNull final SymbolTranslator translator) { return translator; } @Provides @Singleton @IntoSet - static HtsCallTranslator provideTotalSupplyTranslator(@NonNull final TotalSupplyTranslator translator) { + @Named("HtsTranslators") + static CallTranslator provideTotalSupplyTranslator(@NonNull final TotalSupplyTranslator translator) { return translator; } @Provides @Singleton @IntoSet - static HtsCallTranslator provideOwnerOfTranslator(@NonNull final OwnerOfTranslator translator) { + @Named("HtsTranslators") + static CallTranslator provideOwnerOfTranslator(@NonNull final OwnerOfTranslator translator) { return translator; } @Provides @Singleton @IntoSet - static HtsCallTranslator provideSetApprovalForAllTranslator(@NonNull final SetApprovalForAllTranslator translator) { + @Named("HtsTranslators") + static CallTranslator provideSetApprovalForAllTranslator(@NonNull final SetApprovalForAllTranslator translator) { return translator; } @Provides @Singleton @IntoSet - static HtsCallTranslator provideDecimalsTranslator(@NonNull final DecimalsTranslator translator) { + @Named("HtsTranslators") + static CallTranslator provideDecimalsTranslator(@NonNull final DecimalsTranslator translator) { return translator; } @Provides @Singleton @IntoSet - static HtsCallTranslator provideTokenUriTranslator(@NonNull final TokenUriTranslator translator) { + @Named("HtsTranslators") + static CallTranslator provideTokenUriTranslator(@NonNull final TokenUriTranslator translator) { return translator; } @Provides @Singleton @IntoSet - static HtsCallTranslator provideGetAllowanceTranslator(@NonNull final GetAllowanceTranslator translator) { + @Named("HtsTranslators") + static CallTranslator provideGetAllowanceTranslator(@NonNull final GetAllowanceTranslator translator) { return translator; } @Provides @Singleton @IntoSet - static HtsCallTranslator provideGrantApproval(@NonNull final GrantApprovalTranslator translator) { + @Named("HtsTranslators") + static CallTranslator provideGrantApproval(@NonNull final GrantApprovalTranslator translator) { return translator; } @Provides @Singleton @IntoSet - static HtsCallTranslator providePausesTranslator(@NonNull final PausesTranslator translator) { + @Named("HtsTranslators") + static CallTranslator providePausesTranslator(@NonNull final PausesTranslator translator) { return translator; } @Provides @Singleton @IntoSet - static HtsCallTranslator provideGrantRevokeKycTranslator(@NonNull final GrantRevokeKycTranslator translator) { + @Named("HtsTranslators") + static CallTranslator provideGrantRevokeKycTranslator(@NonNull final GrantRevokeKycTranslator translator) { return translator; } @Provides @Singleton @IntoSet - static HtsCallTranslator provideGetApprovedTranslator(@NonNull final GetApprovedTranslator translator) { + @Named("HtsTranslators") + static CallTranslator provideGetApprovedTranslator(@NonNull final GetApprovedTranslator translator) { return translator; } @Provides @Singleton @IntoSet - static HtsCallTranslator provideWipeTranslator(@NonNull final WipeTranslator translator) { + @Named("HtsTranslators") + static CallTranslator provideWipeTranslator(@NonNull final WipeTranslator translator) { return translator; } @Provides @Singleton @IntoSet - static HtsCallTranslator provideIsFrozenTranslator(@NonNull final IsFrozenTranslator translator) { + @Named("HtsTranslators") + static CallTranslator provideIsFrozenTranslator(@NonNull final IsFrozenTranslator translator) { return translator; } @Provides @Singleton @IntoSet - static HtsCallTranslator provideIsKycTranslator(@NonNull final IsKycTranslator translator) { + @Named("HtsTranslators") + static CallTranslator provideIsKycTranslator(@NonNull final IsKycTranslator translator) { return translator; } @Provides @Singleton @IntoSet - static HtsCallTranslator provideIsTokenTranslator(@NonNull final IsTokenTranslator translator) { + @Named("HtsTranslators") + static CallTranslator provideIsTokenTranslator(@NonNull final IsTokenTranslator translator) { return translator; } @Provides @Singleton @IntoSet - static HtsCallTranslator provideTokenTypeTranslator(@NonNull final TokenTypeTranslator translator) { + @Named("HtsTranslators") + static CallTranslator provideTokenTypeTranslator(@NonNull final TokenTypeTranslator translator) { return translator; } @Provides @Singleton @IntoSet - static HtsCallTranslator provideDefaultFreezeStatusTranslator( + @Named("HtsTranslators") + static CallTranslator provideDefaultFreezeStatusTranslator( @NonNull final DefaultFreezeStatusTranslator translator) { return translator; } @@ -269,84 +298,96 @@ static HtsCallTranslator provideDefaultFreezeStatusTranslator( @Provides @Singleton @IntoSet - static HtsCallTranslator provideDefaultKycStatusTranslator(@NonNull final DefaultKycStatusTranslator translator) { + @Named("HtsTranslators") + static CallTranslator provideDefaultKycStatusTranslator(@NonNull final DefaultKycStatusTranslator translator) { return translator; } @Provides @Singleton @IntoSet - static HtsCallTranslator provideFreezeUnfreezeTranslator(@NonNull final FreezeUnfreezeTranslator translator) { + @Named("HtsTranslators") + static CallTranslator provideFreezeUnfreezeTranslator(@NonNull final FreezeUnfreezeTranslator translator) { return translator; } @Provides @Singleton @IntoSet - static HtsCallTranslator provideDeleteTranslator(@NonNull final DeleteTranslator translator) { + @Named("HtsTranslators") + static CallTranslator provideDeleteTranslator(@NonNull final DeleteTranslator translator) { return translator; } @Provides @Singleton @IntoSet - static HtsCallTranslator provideTokenExpiryTranslator(@NonNull final TokenExpiryTranslator translator) { + @Named("HtsTranslators") + static CallTranslator provideTokenExpiryTranslator(@NonNull final TokenExpiryTranslator translator) { return translator; } @Provides @Singleton @IntoSet - static HtsCallTranslator provideTokenKeyTranslator(@NonNull final TokenKeyTranslator translator) { + @Named("HtsTranslators") + static CallTranslator provideTokenKeyTranslator(@NonNull final TokenKeyTranslator translator) { return translator; } @Provides @Singleton @IntoSet - static HtsCallTranslator provideUpdateTranslator(@NonNull final UpdateTranslator translator) { + @Named("HtsTranslators") + static CallTranslator provideUpdateTranslator(@NonNull final UpdateTranslator translator) { return translator; } @Provides @Singleton @IntoSet - static HtsCallTranslator provideTokenCustomFeesTranslator(@NonNull final TokenCustomFeesTranslator translator) { + @Named("HtsTranslators") + static CallTranslator provideTokenCustomFeesTranslator(@NonNull final TokenCustomFeesTranslator translator) { return translator; } @Provides @Singleton @IntoSet - static HtsCallTranslator provideTokenInfoTranslator(@NonNull final TokenInfoTranslator translator) { + @Named("HtsTranslators") + static CallTranslator provideTokenInfoTranslator(@NonNull final TokenInfoTranslator translator) { return translator; } @Provides @Singleton @IntoSet - static HtsCallTranslator provideUpdateExpiryTranslator(@NonNull final UpdateExpiryTranslator translator) { + @Named("HtsTranslators") + static CallTranslator provideUpdateExpiryTranslator(@NonNull final UpdateExpiryTranslator translator) { return translator; } @Provides @Singleton @IntoSet - static HtsCallTranslator provideFungibleTokenInfoTranslator(@NonNull final FungibleTokenInfoTranslator translator) { + @Named("HtsTranslators") + static CallTranslator provideFungibleTokenInfoTranslator(@NonNull final FungibleTokenInfoTranslator translator) { return translator; } @Provides @Singleton @IntoSet - static HtsCallTranslator provideNonFungibleTokenInfoTranslator(@NonNull final NftTokenInfoTranslator translator) { + @Named("HtsTranslators") + static CallTranslator provideNonFungibleTokenInfoTranslator(@NonNull final NftTokenInfoTranslator translator) { return translator; } @Provides @Singleton @IntoSet - static HtsCallTranslator provideUpdateKeysTranslator(@NonNull final UpdateKeysTranslator translator) { + @Named("HtsTranslators") + static CallTranslator provideUpdateKeysTranslator(@NonNull final UpdateKeysTranslator translator) { return translator; } } diff --git a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/processors/ProcessorModule.java b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/processors/ProcessorModule.java index ed5085534767..29a45f33144f 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/processors/ProcessorModule.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/processors/ProcessorModule.java @@ -17,12 +17,14 @@ package com.hedera.node.app.service.contract.impl.exec.processors; import static com.hedera.node.app.service.contract.impl.exec.systemcontracts.ExchangeRateSystemContract.EXCHANGE_RATE_SYSTEM_CONTRACT_ADDRESS; +import static com.hedera.node.app.service.contract.impl.exec.systemcontracts.HasSystemContract.HAS_EVM_ADDRESS; import static com.hedera.node.app.service.contract.impl.exec.systemcontracts.HtsSystemContract.HTS_EVM_ADDRESS; import static com.hedera.node.app.service.contract.impl.exec.systemcontracts.PrngSystemContract.PRNG_PRECOMPILE_ADDRESS; import static java.util.Map.entry; import static java.util.Objects.requireNonNull; import com.hedera.node.app.service.contract.impl.exec.systemcontracts.ExchangeRateSystemContract; +import com.hedera.node.app.service.contract.impl.exec.systemcontracts.HasSystemContract; import com.hedera.node.app.service.contract.impl.exec.systemcontracts.HederaSystemContract; import com.hedera.node.app.service.contract.impl.exec.systemcontracts.HtsSystemContract; import com.hedera.node.app.service.contract.impl.exec.systemcontracts.PrngSystemContract; @@ -37,9 +39,8 @@ import org.hyperledger.besu.evm.contractvalidation.MaxCodeSizeRule; import org.hyperledger.besu.evm.contractvalidation.PrefixCodeRule; -@Module(includes = HtsTranslatorsModule.class) +@Module(includes = {HtsTranslatorsModule.class, HasTranslatorsModule.class}) public interface ProcessorModule { - int EVM_ADDRESS_SIZE = 20; long INITIAL_CONTRACT_NONCE = 1L; boolean REQUIRE_CODE_DEPOSIT_TO_SUCCEED = true; int NUM_SYSTEM_ACCOUNTS = 750; @@ -63,12 +64,14 @@ static ContractValidationRule providePrefixCodeRule() { static Map provideHederaSystemContracts( @NonNull final HtsSystemContract htsSystemContract, @NonNull final ExchangeRateSystemContract exchangeRateSystemContract, - @NonNull final PrngSystemContract prngSystemContract) { + @NonNull final PrngSystemContract prngSystemContract, + @NonNull final HasSystemContract hasSystemContract) { return Map.ofEntries( entry(Address.fromHexString(HTS_EVM_ADDRESS), requireNonNull(htsSystemContract)), entry( Address.fromHexString(EXCHANGE_RATE_SYSTEM_CONTRACT_ADDRESS), requireNonNull(exchangeRateSystemContract)), - entry(Address.fromHexString(PRNG_PRECOMPILE_ADDRESS), requireNonNull(prngSystemContract))); + entry(Address.fromHexString(PRNG_PRECOMPILE_ADDRESS), requireNonNull(prngSystemContract)), + entry(Address.fromHexString(HAS_EVM_ADDRESS), requireNonNull(hasSystemContract))); } } diff --git a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/scope/EitherOrVerificationStrategy.java b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/scope/EitherOrVerificationStrategy.java index 424c0c067655..3fe0bea0956b 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/scope/EitherOrVerificationStrategy.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/scope/EitherOrVerificationStrategy.java @@ -16,6 +16,10 @@ package com.hedera.node.app.service.contract.impl.exec.scope; +import static com.hedera.node.app.service.contract.impl.exec.scope.VerificationStrategy.Decision.INVALID; +import static com.hedera.node.app.service.contract.impl.exec.scope.VerificationStrategy.Decision.VALID; +import static java.util.Objects.requireNonNull; + import com.hedera.hapi.node.base.Key; import edu.umd.cs.findbugs.annotations.NonNull; @@ -35,11 +39,30 @@ public EitherOrVerificationStrategy( this.secondStrategy = secondStrategy; } + /** + * {@inheritDoc} + * + *

    Returns {@link VerificationStrategy.Decision#INVALID} if both strategies agree the + * key's signature is invalid; {@link VerificationStrategy.Decision#VALID} if either strategy + * says the key is valid; and {@link VerificationStrategy.Decision#DELEGATE_TO_CRYPTOGRAPHIC_VERIFICATION} + * otherwise. (I.e., if one strategy says the key's signature is invalid but the other allows + * us to delegate.) + * + * @param key the key whose signature is to be verified + * @return the decision of the strategy + */ @Override public Decision decideForPrimitive(@NonNull final Key key) { - return firstStrategy.decideForPrimitive(key) == Decision.VALID - || secondStrategy.decideForPrimitive(key) == Decision.VALID - ? Decision.VALID - : Decision.INVALID; + requireNonNull(key); + final var firstDecision = firstStrategy.decideForPrimitive(key); + if (firstDecision == VALID) { + return VALID; + } else { + final var secondDecision = secondStrategy.decideForPrimitive(key); + if (secondDecision == VALID) { + return VALID; + } + return secondDecision == INVALID ? firstDecision : secondDecision; + } } } diff --git a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/scope/HandleHederaNativeOperations.java b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/scope/HandleHederaNativeOperations.java index ea70bf658aa0..47c6f9a5a43f 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/scope/HandleHederaNativeOperations.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/scope/HandleHederaNativeOperations.java @@ -42,6 +42,7 @@ import com.hedera.node.app.spi.workflows.HandleException; import com.hedera.pbj.runtime.io.buffer.Bytes; import edu.umd.cs.findbugs.annotations.NonNull; +import edu.umd.cs.findbugs.annotations.Nullable; import javax.inject.Inject; import org.hyperledger.besu.evm.frame.MessageFrame; @@ -52,9 +53,13 @@ public class HandleHederaNativeOperations implements HederaNativeOperations { private final HandleContext context; + @Nullable + private final Key maybeEthSenderKey; + @Inject - public HandleHederaNativeOperations(@NonNull final HandleContext context) { + public HandleHederaNativeOperations(@NonNull final HandleContext context, @Nullable final Key maybeEthSenderKey) { this.context = requireNonNull(context); + this.maybeEthSenderKey = maybeEthSenderKey; } /** @@ -154,7 +159,7 @@ public void finalizeHollowAccountAsContract(@NonNull final Bytes evmAddress) { final AccountID toEntityId, @NonNull final VerificationStrategy strategy) { final var to = requireNonNull(getAccount(toEntityId)); - final var signatureTest = strategy.asSignatureTestIn(context); + final var signatureTest = strategy.asSignatureTestIn(context, maybeEthSenderKey); if (to.receiverSigRequired() && !signatureTest.test(to.keyOrThrow())) { return INVALID_SIGNATURE; } diff --git a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/scope/HandleSystemContractOperations.java b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/scope/HandleSystemContractOperations.java index 8789b0e7f09c..096a0f7d62e5 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/scope/HandleSystemContractOperations.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/scope/HandleSystemContractOperations.java @@ -35,6 +35,7 @@ import com.hedera.node.app.service.contract.impl.records.ContractCallRecordBuilder; import com.hedera.node.app.spi.workflows.HandleContext; import edu.umd.cs.findbugs.annotations.NonNull; +import edu.umd.cs.findbugs.annotations.Nullable; import java.util.function.Predicate; import javax.inject.Inject; import org.apache.tuweni.bytes.Bytes; @@ -50,9 +51,13 @@ public class HandleSystemContractOperations implements SystemContractOperations { private final HandleContext context; + @Nullable + private final Key maybeEthSenderKey; + @Inject - public HandleSystemContractOperations(@NonNull final HandleContext context) { + public HandleSystemContractOperations(@NonNull final HandleContext context, @Nullable Key maybeEthSenderKey) { this.context = requireNonNull(context); + this.maybeEthSenderKey = maybeEthSenderKey; } /** @@ -60,7 +65,7 @@ public HandleSystemContractOperations(@NonNull final HandleContext context) { */ @Override public @NonNull Predicate activeSignatureTestWith(@NonNull final VerificationStrategy strategy) { - return strategy.asSignatureTestIn(context); + return strategy.asSignatureTestIn(context, maybeEthSenderKey); } /** @@ -118,7 +123,7 @@ public void externalizeResult( } @Override - public Transaction syntheticTransactionForHtsCall(Bytes input, ContractID contractID, boolean isViewCall) { + public Transaction syntheticTransactionForNativeCall(Bytes input, ContractID contractID, boolean isViewCall) { var functionParameters = tuweniToPbjBytes(input); var contractCallBodyBuilder = ContractCallTransactionBody.newBuilder().contractID(contractID).functionParameters(functionParameters); diff --git a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/scope/HederaOperations.java b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/scope/HederaOperations.java index ad3af304b3ed..86f0e3633d12 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/scope/HederaOperations.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/scope/HederaOperations.java @@ -26,10 +26,10 @@ import com.hedera.node.app.service.contract.impl.state.DispatchingEvmFrameState; import com.hedera.node.app.service.contract.impl.state.ProxyWorldUpdater; import com.hedera.node.app.service.token.api.ContractChangeSummary; -import com.hedera.node.app.spi.state.WritableStates; import com.hedera.node.app.spi.workflows.record.RecordListCheckPoint; import com.hedera.node.config.data.HederaConfig; import com.hedera.pbj.runtime.io.buffer.Bytes; +import com.swirlds.state.spi.WritableStates; import edu.umd.cs.findbugs.annotations.NonNull; import edu.umd.cs.findbugs.annotations.Nullable; import java.util.List; diff --git a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/scope/QuerySystemContractOperations.java b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/scope/QuerySystemContractOperations.java index 989edfd45de8..0bda3c9a35b2 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/scope/QuerySystemContractOperations.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/scope/QuerySystemContractOperations.java @@ -95,7 +95,7 @@ public void externalizeResult( * {@inheritDoc} */ @Override - public Transaction syntheticTransactionForHtsCall(Bytes input, ContractID contractID, boolean isViewCall) { + public Transaction syntheticTransactionForNativeCall(Bytes input, ContractID contractID, boolean isViewCall) { // no-op return null; } diff --git a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/scope/SpecificCryptoVerificationStrategy.java b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/scope/SpecificCryptoVerificationStrategy.java new file mode 100644 index 000000000000..7d543bce71e8 --- /dev/null +++ b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/scope/SpecificCryptoVerificationStrategy.java @@ -0,0 +1,55 @@ +/* + * Copyright (C) 2024 Hedera Hashgraph, LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.hedera.node.app.service.contract.impl.exec.scope; + +import static com.hedera.node.app.service.contract.impl.exec.scope.VerificationStrategy.Decision.DELEGATE_TO_CRYPTOGRAPHIC_VERIFICATION; +import static com.hedera.node.app.service.contract.impl.exec.scope.VerificationStrategy.Decision.INVALID; +import static java.util.Objects.requireNonNull; + +import com.hedera.hapi.node.base.Key; +import edu.umd.cs.findbugs.annotations.NonNull; + +/** + * A {@link VerificationStrategy} that will delegate verification of a specific key's signature + * to the top-level cryptographic verification strategy; and mark all other keys invalid. + */ +public class SpecificCryptoVerificationStrategy implements VerificationStrategy { + private final Key qualifyingKey; + + public SpecificCryptoVerificationStrategy(@NonNull final Key qualifyingKey) { + requireNonNull(qualifyingKey); + if (!qualifyingKey.hasEd25519() && !qualifyingKey.hasEcdsaSecp256k1()) { + throw new IllegalArgumentException("Qualifying key must be a cryptographic key"); + } + this.qualifyingKey = qualifyingKey; + } + + /** + * {@inheritDoc} + * + *

    Returns {@link VerificationStrategy.Decision#DELEGATE_TO_CRYPTOGRAPHIC_VERIFICATION} if the key + * matches the qualifying key; {@link VerificationStrategy.Decision#INVALID} otherwise. + * + * @param key the key whose signature is to be verified + * @return the decision of the strategy + */ + @Override + public Decision decideForPrimitive(@NonNull final Key key) { + requireNonNull(key); + return qualifyingKey.equals(key) ? DELEGATE_TO_CRYPTOGRAPHIC_VERIFICATION : INVALID; + } +} diff --git a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/scope/SystemContractOperations.java b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/scope/SystemContractOperations.java index 611400a5b35b..3c1abb35cb47 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/scope/SystemContractOperations.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/scope/SystemContractOperations.java @@ -98,10 +98,10 @@ void externalizeResult( * @param isViewCall * @return */ - Transaction syntheticTransactionForHtsCall(Bytes input, ContractID contractID, boolean isViewCall); + Transaction syntheticTransactionForNativeCall(Bytes input, ContractID contractID, boolean isViewCall); /** - * Returns the {@Link ExchangeRate} for the current consensus time. This will enable the translation from hbars + * Returns the {@link ExchangeRate} for the current consensus time. This will enable the translation from hbars * to dollars * * @return ExchangeRate for the current consensus time diff --git a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/scope/VerificationStrategy.java b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/scope/VerificationStrategy.java index 46f60be8ff36..ebdeab24cdf1 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/scope/VerificationStrategy.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/scope/VerificationStrategy.java @@ -22,6 +22,8 @@ import com.hedera.hapi.node.base.KeyList; import com.hedera.node.app.spi.workflows.HandleContext; import edu.umd.cs.findbugs.annotations.NonNull; +import edu.umd.cs.findbugs.annotations.Nullable; +import java.util.Objects; import java.util.function.Predicate; /** @@ -70,12 +72,16 @@ enum Decision { * given this strategy within the given {@link HandleContext}. * * @param context the context in which this strategy will be used + * @param maybeEthSenderKey the key that is the sender of the EVM message, if known * @return a predicate that tests whether a given key is a valid signature for a given key */ - default Predicate asSignatureTestIn(@NonNull final HandleContext context) { + default Predicate asSignatureTestIn( + @NonNull final HandleContext context, @Nullable final Key maybeEthSenderKey) { + requireNonNull(context); return new Predicate<>() { @Override - public boolean test(Key key) { + public boolean test(@NonNull final Key key) { + requireNonNull(key); return switch (key.key().kind()) { case KEY_LIST -> { final var keys = key.keyListOrThrow().keys(); @@ -108,8 +114,9 @@ public boolean test(Key key) { yield switch (decideForPrimitive(key)) { case VALID -> true; case INVALID -> false; - case DELEGATE_TO_CRYPTOGRAPHIC_VERIFICATION -> context.verificationFor(key) - .passed(); + // Note the EthereumTransaction sender's key has necessarily signed + case DELEGATE_TO_CRYPTOGRAPHIC_VERIFICATION -> Objects.equals(key, maybeEthSenderKey) + || context.verificationFor(key).passed(); }; } yield false; diff --git a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/ExchangeRateSystemContract.java b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/ExchangeRateSystemContract.java index aa428a705697..9868fd5f0c33 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/ExchangeRateSystemContract.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/ExchangeRateSystemContract.java @@ -19,12 +19,15 @@ import static com.hedera.node.app.service.contract.impl.exec.utils.FrameUtils.contractsConfigOf; import static com.hedera.node.app.service.contract.impl.exec.utils.FrameUtils.proxyUpdaterFor; import static com.hedera.node.app.service.evm.utils.ValidationUtils.validateTrue; +import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.INVALID_FEE_SUBMITTED; import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.INVALID_TRANSACTION_BODY; import static java.util.Objects.requireNonNull; import com.esaulpaugh.headlong.abi.BigIntegerType; import com.esaulpaugh.headlong.abi.TypeFactory; +import com.hedera.node.app.service.contract.impl.exec.failure.CustomExceptionalHaltReason; import com.hedera.node.app.service.contract.impl.utils.ConversionUtils; +import com.hedera.node.app.service.evm.exceptions.InvalidTransactionException; import edu.umd.cs.findbugs.annotations.NonNull; import java.math.BigInteger; import java.util.Optional; @@ -60,6 +63,7 @@ public FullResult computeFully(@NonNull Bytes input, @NonNull MessageFrame messa requireNonNull(messageFrame); try { validateTrue(input.size() >= 4, INVALID_TRANSACTION_BODY); + validateTrue(messageFrame.getValue().getAsBigInteger().equals(BigInteger.ZERO), INVALID_FEE_SUBMITTED); gasRequirement = contractsConfigOf(messageFrame).precompileExchangeRateGasCost(); final var selector = input.getInt(0); final var amount = biValueFrom(input); @@ -74,7 +78,17 @@ public FullResult computeFully(@NonNull Bytes input, @NonNull MessageFrame messa }; requireNonNull(result); return new FullResult(PrecompileContractResult.success(result), gasRequirement, null); - } catch (Exception ignore) { + } catch (InvalidTransactionException e) { + ExceptionalHaltReason haltReason; + if (e.getResponseCode() == INVALID_FEE_SUBMITTED) { + haltReason = CustomExceptionalHaltReason.INVALID_FEE_SUBMITTED; + } else { + haltReason = ExceptionalHaltReason.INVALID_OPERATION; + } + + return new FullResult( + PrecompileContractResult.halt(Bytes.EMPTY, Optional.of(haltReason)), gasRequirement, null); + } catch (Exception e) { return new FullResult( PrecompileContractResult.halt(Bytes.EMPTY, Optional.of(ExceptionalHaltReason.INVALID_OPERATION)), gasRequirement, diff --git a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/HasSystemContract.java b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/HasSystemContract.java new file mode 100644 index 000000000000..9f353478d839 --- /dev/null +++ b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/HasSystemContract.java @@ -0,0 +1,65 @@ +/* + * Copyright (C) 2023-2024 Hedera Hashgraph, LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.hedera.node.app.service.contract.impl.exec.systemcontracts; + +import static com.hedera.node.app.service.contract.impl.exec.failure.CustomExceptionalHaltReason.NOT_SUPPORTED; +import static com.hedera.node.app.service.contract.impl.exec.systemcontracts.FullResult.haltResult; +import static com.hedera.node.app.service.contract.impl.exec.utils.FrameUtils.contractsConfigOf; +import static com.hedera.node.app.service.contract.impl.utils.ConversionUtils.asNumberedContractId; +import static java.util.Objects.requireNonNull; + +import com.hedera.hapi.node.base.ContractID; +import com.hedera.node.app.service.contract.impl.exec.systemcontracts.common.AbstractNativeSystemContract; +import com.hedera.node.app.service.contract.impl.exec.systemcontracts.has.HasCallFactory; +import com.hedera.node.app.service.contract.impl.exec.utils.FrameUtils; +import edu.umd.cs.findbugs.annotations.NonNull; +import javax.inject.Inject; +import javax.inject.Singleton; +import org.apache.tuweni.bytes.Bytes; +import org.hyperledger.besu.datatypes.Address; +import org.hyperledger.besu.evm.frame.MessageFrame; +import org.hyperledger.besu.evm.gascalculator.GasCalculator; + +@Singleton +public class HasSystemContract extends AbstractNativeSystemContract implements HederaSystemContract { + public static final String HAS_SYSTEM_CONTRACT_NAME = "HAS"; + public static final String HAS_EVM_ADDRESS = "0x16a"; + public static final ContractID HAS_CONTRACT_ID = asNumberedContractId(Address.fromHexString(HAS_EVM_ADDRESS)); + + @Inject + public HasSystemContract(@NonNull final GasCalculator gasCalculator, @NonNull final HasCallFactory callFactory) { + super(HAS_SYSTEM_CONTRACT_NAME, callFactory, HAS_CONTRACT_ID, gasCalculator); + } + + @Override + protected FrameUtils.CallType callTypeOf(@NonNull MessageFrame frame) { + return FrameUtils.callTypeForAccountOf(frame); + } + + @Override + public FullResult computeFully(@NonNull final Bytes input, @NonNull final MessageFrame frame) { + requireNonNull(input); + requireNonNull(frame); + + // Check if calls to hedera account service is enabled + if (!contractsConfigOf(frame).systemContractAccountServiceEnabled()) { + return haltResult(NOT_SUPPORTED, frame.getRemainingGas()); + } + + return super.computeFully(input, frame); + } +} diff --git a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/HtsSystemContract.java b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/HtsSystemContract.java index 209d59f952a3..056b3586436c 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/HtsSystemContract.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/HtsSystemContract.java @@ -16,166 +16,32 @@ package com.hedera.node.app.service.contract.impl.exec.systemcontracts; -import static com.hedera.hapi.node.base.ResponseCodeEnum.INSUFFICIENT_GAS; -import static com.hedera.hapi.node.base.ResponseCodeEnum.MAX_CHILD_RECORDS_EXCEEDED; -import static com.hedera.hapi.node.base.ResponseCodeEnum.SUCCESS; -import static com.hedera.node.app.service.contract.impl.exec.systemcontracts.FullResult.haltResult; -import static com.hedera.node.app.service.contract.impl.exec.utils.FrameUtils.CallType.UNQUALIFIED_DELEGATE; -import static com.hedera.node.app.service.contract.impl.exec.utils.FrameUtils.callTypeOf; -import static com.hedera.node.app.service.contract.impl.exec.utils.FrameUtils.contractsConfigOf; -import static com.hedera.node.app.service.contract.impl.exec.utils.FrameUtils.proxyUpdaterFor; import static com.hedera.node.app.service.contract.impl.utils.ConversionUtils.asNumberedContractId; -import static com.hedera.node.app.service.contract.impl.utils.ConversionUtils.tuweniToPbjBytes; -import static com.hedera.node.app.service.contract.impl.utils.SystemContractUtils.contractFunctionResultFailedFor; -import static com.hedera.node.app.service.contract.impl.utils.SystemContractUtils.successResultOf; -import static com.hedera.node.app.service.evm.utils.ValidationUtils.validateTrue; -import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.INVALID_TRANSACTION_BODY; -import static java.util.Objects.requireNonNull; -import static org.hyperledger.besu.evm.frame.ExceptionalHaltReason.INVALID_OPERATION; -import static org.hyperledger.besu.evm.frame.ExceptionalHaltReason.PRECOMPILE_ERROR; import com.hedera.hapi.node.base.ContractID; -import com.hedera.hapi.node.base.ResponseCodeEnum; -import com.hedera.node.app.service.contract.impl.exec.failure.CustomExceptionalHaltReason; -import com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.HtsCall; -import com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.HtsCallAttempt; +import com.hedera.node.app.service.contract.impl.exec.systemcontracts.common.AbstractNativeSystemContract; import com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.HtsCallFactory; -import com.hedera.node.app.service.contract.impl.hevm.HederaWorldUpdater; -import com.hedera.node.app.spi.workflows.HandleException; +import com.hedera.node.app.service.contract.impl.exec.utils.FrameUtils; import edu.umd.cs.findbugs.annotations.NonNull; import javax.inject.Inject; import javax.inject.Singleton; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; -import org.apache.tuweni.bytes.Bytes; import org.hyperledger.besu.datatypes.Address; import org.hyperledger.besu.evm.frame.MessageFrame; import org.hyperledger.besu.evm.gascalculator.GasCalculator; @Singleton -public class HtsSystemContract extends AbstractFullContract implements HederaSystemContract { - private static final Logger log = LogManager.getLogger(HtsSystemContract.class); - +public class HtsSystemContract extends AbstractNativeSystemContract implements HederaSystemContract { public static final String HTS_SYSTEM_CONTRACT_NAME = "HTS"; public static final String HTS_EVM_ADDRESS = "0x167"; public static final ContractID HTS_CONTRACT_ID = asNumberedContractId(Address.fromHexString(HTS_EVM_ADDRESS)); - private final HtsCallFactory callFactory; - @Inject public HtsSystemContract(@NonNull final GasCalculator gasCalculator, @NonNull final HtsCallFactory callFactory) { - super(HTS_SYSTEM_CONTRACT_NAME, gasCalculator); - this.callFactory = requireNonNull(callFactory); + super(HTS_SYSTEM_CONTRACT_NAME, callFactory, HTS_CONTRACT_ID, gasCalculator); } @Override - public FullResult computeFully(@NonNull final Bytes input, @NonNull final MessageFrame frame) { - requireNonNull(input); - requireNonNull(frame); - final var callType = callTypeOf(frame); - if (callType == UNQUALIFIED_DELEGATE) { - return haltResult(PRECOMPILE_ERROR, frame.getRemainingGas()); - } - final HtsCall call; - final HtsCallAttempt attempt; - try { - validateTrue(input.size() >= 4, INVALID_TRANSACTION_BODY); - attempt = callFactory.createCallAttemptFrom(input, callType, frame); - call = requireNonNull(attempt.asExecutableCall()); - if (frame.isStatic() && !call.allowsStaticFrame()) { - // FUTURE - we should really set an explicit halt reason here; instead we just halt the frame - // without setting a halt reason to simulate mono-service for differential testing - return haltResult(contractsConfigOf(frame).precompileHtsDefaultGasCost()); - } - } catch (final Exception ignore) { - // Input that cannot be translated to an executable call, for any - // reason, halts the frame and consumes all remaining gas - return haltResult(INVALID_OPERATION, frame.getRemainingGas()); - } - return resultOfExecuting(attempt, call, input, frame); - } - - @SuppressWarnings({"java:S2637", "java:S2259"}) // this function is going to be refactored soon. - private static FullResult resultOfExecuting( - @NonNull final HtsCallAttempt attempt, - @NonNull final HtsCall call, - @NonNull final Bytes input, - @NonNull final MessageFrame frame) { - final HtsCall.PricedResult pricedResult; - try { - pricedResult = call.execute(frame); - final var gasRequirement = pricedResult.fullResult().gasRequirement(); - final var insufficientGas = frame.getRemainingGas() < gasRequirement; - final var dispatchedRecordBuilder = pricedResult.fullResult().recordBuilder(); - if (dispatchedRecordBuilder != null) { - if (insufficientGas) { - dispatchedRecordBuilder.status(INSUFFICIENT_GAS); - dispatchedRecordBuilder.contractCallResult(pricedResult.asResultOfInsufficientGasRemaining( - attempt.senderId(), HTS_CONTRACT_ID, tuweniToPbjBytes(input), frame.getRemainingGas())); - } else { - dispatchedRecordBuilder.contractCallResult(pricedResult.asResultOfCall( - attempt.senderId(), HTS_CONTRACT_ID, tuweniToPbjBytes(input), frame.getRemainingGas())); - } - } else if (pricedResult.isViewCall()) { - final var proxyWorldUpdater = proxyUpdaterFor(frame); - final var enhancement = proxyWorldUpdater.enhancement(); - // Insufficient gas preempts any other response code - final var status = insufficientGas ? INSUFFICIENT_GAS : pricedResult.responseCode(); - if (status == SUCCESS) { - enhancement - .systemOperations() - .externalizeResult( - successResultOf( - attempt.senderId(), - pricedResult.fullResult(), - frame, - !call.allowsStaticFrame()), - pricedResult.responseCode(), - enhancement - .systemOperations() - .syntheticTransactionForHtsCall(input, HTS_CONTRACT_ID, true)); - } else { - externalizeFailure( - gasRequirement, - input, - insufficientGas - ? Bytes.EMPTY - : pricedResult.fullResult().output(), - attempt, - status, - enhancement); - } - } - } catch (final HandleException handleException) { - return haltHandleException(handleException, frame.getRemainingGas()); - } catch (final Exception internal) { - log.error("Unhandled failure for input {} to HTS system contract", input, internal); - return haltResult(PRECOMPILE_ERROR, frame.getRemainingGas()); - } - return pricedResult.fullResult(); - } - - private static void externalizeFailure( - final long gasRequirement, - @NonNull final Bytes input, - @NonNull final Bytes output, - @NonNull final HtsCallAttempt attempt, - @NonNull final ResponseCodeEnum status, - @NonNull final HederaWorldUpdater.Enhancement enhancement) { - enhancement - .systemOperations() - .externalizeResult( - contractFunctionResultFailedFor( - attempt.senderId(), output, gasRequirement, status.toString(), HTS_CONTRACT_ID), - status, - enhancement.systemOperations().syntheticTransactionForHtsCall(input, HTS_CONTRACT_ID, true)); - } - - // potentially other cases could be handled here if necessary - private static FullResult haltHandleException(final HandleException handleException, long remainingGas) { - if (handleException.getStatus().equals(MAX_CHILD_RECORDS_EXCEEDED)) { - return haltResult(CustomExceptionalHaltReason.INSUFFICIENT_CHILD_RECORDS, remainingGas); - } - throw handleException; + protected FrameUtils.CallType callTypeOf(@NonNull MessageFrame frame) { + return FrameUtils.callTypeOf(frame); } } diff --git a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/PrngSystemContract.java b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/PrngSystemContract.java index 210494748bf2..3c0b32f32432 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/PrngSystemContract.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/PrngSystemContract.java @@ -102,7 +102,7 @@ public FullResult computeFully(@NonNull final Bytes input, @NonNull final Messag PrecompiledContract.PrecompileContractResult.halt(Bytes.EMPTY, Optional.of(INVALID_OPERATION)), gasRequirement, null); - } catch (NullPointerException e) { + } catch (Exception e) { // Log a warning as this error will be caused by insufficient entropy log.warn("Internal precompile failure", e); createFailedRecord(frame, FAIL_INVALID, contractID); diff --git a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hts/AbstractHtsCall.java b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/common/AbstractCall.java similarity index 94% rename from hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hts/AbstractHtsCall.java rename to hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/common/AbstractCall.java index 9f594ef63d8e..d9a172030666 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hts/AbstractHtsCall.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/common/AbstractCall.java @@ -14,12 +14,12 @@ * limitations under the License. */ -package com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts; +package com.hedera.node.app.service.contract.impl.exec.systemcontracts.common; import static com.hedera.node.app.service.contract.impl.exec.systemcontracts.FullResult.haltResult; import static com.hedera.node.app.service.contract.impl.exec.systemcontracts.FullResult.revertResult; import static com.hedera.node.app.service.contract.impl.exec.systemcontracts.FullResult.successResult; -import static com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.HtsCall.PricedResult.gasOnly; +import static com.hedera.node.app.service.contract.impl.exec.systemcontracts.common.Call.PricedResult.gasOnly; import static com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.ReturnTypes.encodedRc; import static com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.ReturnTypes.standardized; import static java.util.Objects.requireNonNull; @@ -36,15 +36,15 @@ import java.nio.ByteBuffer; /** - * Minimal implementation support for an {@link HtsCall} that needs an {@link HederaWorldUpdater.Enhancement} + * Minimal implementation support for an {@link Call} that needs an {@link HederaWorldUpdater.Enhancement} * and {@link SystemContractGasCalculator}. */ -public abstract class AbstractHtsCall implements HtsCall { +public abstract class AbstractCall implements Call { protected final SystemContractGasCalculator gasCalculator; protected final HederaWorldUpdater.Enhancement enhancement; private final boolean isViewCall; - protected AbstractHtsCall( + protected AbstractCall( @NonNull final SystemContractGasCalculator gasCalculator, @NonNull final HederaWorldUpdater.Enhancement enhancement, final boolean isViewCall) { diff --git a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/common/AbstractCallAttempt.java b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/common/AbstractCallAttempt.java new file mode 100644 index 000000000000..3de29b1eb057 --- /dev/null +++ b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/common/AbstractCallAttempt.java @@ -0,0 +1,247 @@ +/* + * Copyright (C) 2023-2024 Hedera Hashgraph, LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.hedera.node.app.service.contract.impl.exec.systemcontracts.common; + +import static java.util.Objects.requireNonNull; + +import com.esaulpaugh.headlong.abi.Tuple; +import com.hedera.hapi.node.base.AccountID; +import com.hedera.node.app.service.contract.impl.exec.gas.SystemContractGasCalculator; +import com.hedera.node.app.service.contract.impl.exec.scope.HederaNativeOperations; +import com.hedera.node.app.service.contract.impl.exec.scope.VerificationStrategies; +import com.hedera.node.app.service.contract.impl.exec.scope.VerificationStrategy; +import com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.AddressIdConverter; +import com.hedera.node.app.service.contract.impl.hevm.HederaWorldUpdater; +import com.swirlds.config.api.Configuration; +import edu.umd.cs.findbugs.annotations.NonNull; +import edu.umd.cs.findbugs.annotations.Nullable; +import java.nio.BufferUnderflowException; +import java.util.Arrays; +import java.util.List; +import org.apache.tuweni.bytes.Bytes; +import org.hyperledger.besu.datatypes.Address; + +/** + * Base class for HTS and HAS system contract call attempts. + */ +public class AbstractCallAttempt { + private final byte[] selector; + protected Bytes input; + private final Address authorizingAddress; + // The id of the sender in the EVM frame + protected final AccountID senderId; + private final Address senderAddress; + private final boolean onlyDelegatableContractKeysActive; + protected final HederaWorldUpdater.Enhancement enhancement; + private final Configuration configuration; + private final AddressIdConverter addressIdConverter; + private final VerificationStrategies verificationStrategies; + private final SystemContractGasCalculator gasCalculator; + private final List callTranslators; + private final boolean isStaticCall; + + // If non-null, the address of a non-contract entity (e.g., account or token) whose + // "bytecode" redirects all calls to a system contract address, and was determined + // to be the redirecting entity for this call attempt + protected @Nullable final Address redirectAddress; + + // too many parameters + @SuppressWarnings("java:S107") + public AbstractCallAttempt( + @NonNull final Bytes input, + @NonNull final Address senderAddress, + @NonNull final Address authorizingAddress, + final boolean onlyDelegatableContractKeysActive, + @NonNull final HederaWorldUpdater.Enhancement enhancement, + @NonNull final Configuration configuration, + @NonNull final AddressIdConverter addressIdConverter, + @NonNull final VerificationStrategies verificationStrategies, + @NonNull final SystemContractGasCalculator gasCalculator, + @NonNull final List callTranslators, + final boolean isStaticCall, + @NonNull final com.esaulpaugh.headlong.abi.Function redirectFunction) { + requireNonNull(input); + requireNonNull(redirectFunction); + this.callTranslators = requireNonNull(callTranslators); + this.gasCalculator = requireNonNull(gasCalculator); + this.senderAddress = requireNonNull(senderAddress); + this.authorizingAddress = requireNonNull(authorizingAddress); + this.configuration = requireNonNull(configuration); + this.addressIdConverter = requireNonNull(addressIdConverter); + this.enhancement = requireNonNull(enhancement); + this.verificationStrategies = requireNonNull(verificationStrategies); + this.onlyDelegatableContractKeysActive = onlyDelegatableContractKeysActive; + + if (isRedirectSelector(redirectFunction.selector(), input.toArrayUnsafe())) { + Tuple abiCall = null; + try { + // First try to decode the redirect with standard ABI encoding using a 32-byte address + abiCall = redirectFunction.decodeCall(input.toArrayUnsafe()); + } catch (IllegalArgumentException | BufferUnderflowException | IndexOutOfBoundsException ignore) { + // Otherwise use the "packed" encoding with a 20-byte address + } + if (abiCall != null) { + this.redirectAddress = Address.fromHexString(abiCall.get(0).toString()); + this.input = Bytes.wrap((byte[]) abiCall.get(1)); + } else { + this.redirectAddress = Address.wrap(input.slice(4, 20)); + this.input = input.slice(24); + } + } else { + this.redirectAddress = null; + this.input = input; + } + + this.selector = this.input.slice(0, 4).toArrayUnsafe(); + this.senderId = addressIdConverter.convertSender(senderAddress); + this.isStaticCall = isStaticCall; + } + + /** + * Returns the default verification strategy for this call (i.e., the strategy that treats only + * contract id and delegatable contract id keys as active when they match the call's sender address). + * + * @return the default verification strategy for this call + */ + public @NonNull VerificationStrategy defaultVerificationStrategy() { + return verificationStrategies.activatingOnlyContractKeysFor( + authorizingAddress, onlyDelegatableContractKeysActive, enhancement.nativeOperations()); + } + + /** + * Returns the updater enhancement this call was attempted within. + * + * @return the updater enhancement this call was attempted within + */ + public @NonNull HederaWorldUpdater.Enhancement enhancement() { + return enhancement; + } + + /** + * Returns the system contract gas calculator for this call. + * + * @return the system contract gas calculator for this call + */ + public @NonNull SystemContractGasCalculator systemContractGasCalculator() { + return gasCalculator; + } + + /** + * Returns the native operations this call was attempted within. + * + * @return the native operations this call was attempted within + */ + public @NonNull HederaNativeOperations nativeOperations() { + return enhancement.nativeOperations(); + } + + /** + * Tries to translate this call attempt into a {@link Call} from the given sender address. + * + * @return the executable call, or null if this attempt can't be translated to one + */ + public @Nullable Call asExecutableCall() { + for (final var translator : callTranslators) { + final var call = translator.translateCallAttempt(this); + if (call != null) { + return call; + } + } + return null; + } + + /** + * Returns the ID of the sender of this call. + * + * @return the ID of the sender of this call + */ + public @NonNull AccountID senderId() { + return senderId; + } + + /** + * Returns the address of the sender of this call. + * + * @return the address of the sender of this call + */ + public @NonNull Address senderAddress() { + return senderAddress; + } + + /** + * Returns the address ID converter for this call. + * + * @return the address ID converter for this call + */ + public AddressIdConverter addressIdConverter() { + return addressIdConverter; + } + + /** + * Returns the configuration for this call. + * + * @return the configuration for this call + */ + public Configuration configuration() { + return configuration; + } + + /** + * Returns the selector of this call. + * + * @return the selector of this call + * @throws IllegalStateException if this is not a valid call + */ + public byte[] selector() { + return selector; + } + + /** + * Returns the input of this call. + * + * @return the input of this call + * @throws IllegalStateException if this is not a valid call + */ + public Bytes input() { + return input; + } + + /** + * Returns the raw byte array input of this call. + * + * @return the raw input of this call + * @throws IllegalStateException if this is not a valid call + */ + public byte[] inputBytes() { + return input.toArrayUnsafe(); + } + + /** + * @return whether the current call attempt is a static call + */ + public boolean isStaticCall() { + return isStaticCall; + } + + public boolean isRedirect() { + return redirectAddress != null; + } + + private boolean isRedirectSelector(@NonNull final byte[] functionSelector, @NonNull final byte[] input) { + return Arrays.equals(input, 0, functionSelector.length, functionSelector, 0, functionSelector.length); + } +} diff --git a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/HtsCallTranslator.java b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/common/AbstractCallTranslator.java similarity index 55% rename from hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/HtsCallTranslator.java rename to hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/common/AbstractCallTranslator.java index ce24a7bfc825..a36e72f19979 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/HtsCallTranslator.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/common/AbstractCallTranslator.java @@ -14,24 +14,27 @@ * limitations under the License. */ -package com.hedera.node.app.service.contract.impl.exec.systemcontracts; +package com.hedera.node.app.service.contract.impl.exec.systemcontracts.common; + +import static java.util.Objects.requireNonNull; -import com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.HtsCall; -import com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.HtsCallAttempt; import edu.umd.cs.findbugs.annotations.NonNull; import edu.umd.cs.findbugs.annotations.Nullable; /** - * Strategy interface for translating {@link HtsCallAttempt}s into {@link HtsCall}s. + * Basic implementation support for a {@link CallTranslator} that returns a translated + * call when the {@link AbstractCallAttempt} matches and null otherwise. */ -public interface HtsCallTranslator { +public abstract class AbstractCallTranslator implements CallTranslator { /** - * Tries to translate the given {@code attempt} into a {@link HtsCall}, returning null if the call - * doesn't match the target type of this translator. - * - * @param attempt the attempt to translate - * @return the translated {@link HtsCall} + * {@inheritDoc} */ - @Nullable - HtsCall translateCallAttempt(@NonNull HtsCallAttempt attempt); + @Override + public @Nullable Call translateCallAttempt(@NonNull final T attempt) { + requireNonNull(attempt); + if (matches(attempt)) { + return callFrom(attempt); + } + return null; + } } diff --git a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/common/AbstractNativeSystemContract.java b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/common/AbstractNativeSystemContract.java new file mode 100644 index 000000000000..55b9dce14ca0 --- /dev/null +++ b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/common/AbstractNativeSystemContract.java @@ -0,0 +1,191 @@ +/* + * Copyright (C) 2023-2024 Hedera Hashgraph, LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.hedera.node.app.service.contract.impl.exec.systemcontracts.common; + +import static com.hedera.hapi.node.base.ResponseCodeEnum.INSUFFICIENT_GAS; +import static com.hedera.hapi.node.base.ResponseCodeEnum.MAX_CHILD_RECORDS_EXCEEDED; +import static com.hedera.hapi.node.base.ResponseCodeEnum.SUCCESS; +import static com.hedera.node.app.service.contract.impl.exec.systemcontracts.FullResult.haltResult; +import static com.hedera.node.app.service.contract.impl.exec.utils.FrameUtils.CallType.UNQUALIFIED_DELEGATE; +import static com.hedera.node.app.service.contract.impl.exec.utils.FrameUtils.contractsConfigOf; +import static com.hedera.node.app.service.contract.impl.exec.utils.FrameUtils.proxyUpdaterFor; +import static com.hedera.node.app.service.contract.impl.utils.ConversionUtils.tuweniToPbjBytes; +import static com.hedera.node.app.service.contract.impl.utils.SystemContractUtils.contractFunctionResultFailedFor; +import static com.hedera.node.app.service.contract.impl.utils.SystemContractUtils.successResultOf; +import static com.hedera.node.app.service.evm.utils.ValidationUtils.validateTrue; +import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.INVALID_TRANSACTION_BODY; +import static java.util.Objects.requireNonNull; +import static org.hyperledger.besu.evm.frame.ExceptionalHaltReason.INVALID_OPERATION; +import static org.hyperledger.besu.evm.frame.ExceptionalHaltReason.PRECOMPILE_ERROR; + +import com.hedera.hapi.node.base.ContractID; +import com.hedera.hapi.node.base.ResponseCodeEnum; +import com.hedera.node.app.service.contract.impl.exec.failure.CustomExceptionalHaltReason; +import com.hedera.node.app.service.contract.impl.exec.systemcontracts.AbstractFullContract; +import com.hedera.node.app.service.contract.impl.exec.systemcontracts.FullResult; +import com.hedera.node.app.service.contract.impl.exec.systemcontracts.HederaSystemContract; +import com.hedera.node.app.service.contract.impl.exec.utils.FrameUtils; +import com.hedera.node.app.service.contract.impl.hevm.HederaWorldUpdater; +import com.hedera.node.app.spi.workflows.HandleException; +import edu.umd.cs.findbugs.annotations.NonNull; +import javax.inject.Singleton; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.apache.tuweni.bytes.Bytes; +import org.hyperledger.besu.evm.frame.MessageFrame; +import org.hyperledger.besu.evm.gascalculator.GasCalculator; + +/** + * Abstract class for native system contracts. + * Descendents are {@link com.hedera.node.app.service.contract.impl.exec.systemcontracts.HtsSystemContract} and + * {@link com.hedera.node.app.service.contract.impl.exec.systemcontracts.HasSystemContract}. + */ +@Singleton +public abstract class AbstractNativeSystemContract extends AbstractFullContract implements HederaSystemContract { + private static final Logger log = LogManager.getLogger(AbstractNativeSystemContract.class); + private static final int FUNCTION_SELECTOR_LENGTH = 4; + private final CallFactory callFactory; + private final ContractID contractID; + + protected AbstractNativeSystemContract( + @NonNull String name, + @NonNull CallFactory callFactory, + @NonNull ContractID contractID, + @NonNull GasCalculator gasCalculator) { + super(name, gasCalculator); + this.callFactory = requireNonNull(callFactory); + this.contractID = requireNonNull(contractID); + } + + @Override + public FullResult computeFully(@NonNull final Bytes input, @NonNull final MessageFrame frame) { + requireNonNull(input); + requireNonNull(frame); + final var callType = callTypeOf(frame); + if (callType == UNQUALIFIED_DELEGATE) { + return haltResult(PRECOMPILE_ERROR, frame.getRemainingGas()); + } + final Call call; + final AbstractCallAttempt attempt; + try { + validateTrue(input.size() >= FUNCTION_SELECTOR_LENGTH, INVALID_TRANSACTION_BODY); + attempt = callFactory.createCallAttemptFrom(input, callType, frame); + call = requireNonNull(attempt.asExecutableCall()); + if (frame.isStatic() && !call.allowsStaticFrame()) { + // FUTURE - we should really set an explicit halt reason here; instead we just halt the frame + // without setting a halt reason to simulate mono-service for differential testing + return haltResult(contractsConfigOf(frame).precompileHtsDefaultGasCost()); + } + } catch (final Exception ignore) { + // Input that cannot be translated to an executable call, for any + // reason, halts the frame and consumes all remaining gas + return haltResult(INVALID_OPERATION, frame.getRemainingGas()); + } + return resultOfExecuting(attempt, call, input, frame, this.contractID); + } + + @SuppressWarnings({"java:S2637", "java:S2259"}) // this function is going to be refactored soon. + private static FullResult resultOfExecuting( + @NonNull final AbstractCallAttempt attempt, + @NonNull final Call call, + @NonNull final Bytes input, + @NonNull final MessageFrame frame, + @NonNull final ContractID contractID) { + final Call.PricedResult pricedResult; + try { + pricedResult = call.execute(frame); + final var gasRequirement = pricedResult.fullResult().gasRequirement(); + final var insufficientGas = frame.getRemainingGas() < gasRequirement; + final var dispatchedRecordBuilder = pricedResult.fullResult().recordBuilder(); + if (dispatchedRecordBuilder != null) { + if (insufficientGas) { + dispatchedRecordBuilder.status(INSUFFICIENT_GAS); + dispatchedRecordBuilder.contractCallResult(pricedResult.asResultOfInsufficientGasRemaining( + attempt.senderId(), contractID, tuweniToPbjBytes(input), frame.getRemainingGas())); + } else { + dispatchedRecordBuilder.contractCallResult(pricedResult.asResultOfCall( + attempt.senderId(), contractID, tuweniToPbjBytes(input), frame.getRemainingGas())); + } + } else if (pricedResult.isViewCall()) { + final var proxyWorldUpdater = proxyUpdaterFor(frame); + final var enhancement = proxyWorldUpdater.enhancement(); + // Insufficient gas preempts any other response code + final var status = insufficientGas ? INSUFFICIENT_GAS : pricedResult.responseCode(); + if (status == SUCCESS) { + enhancement + .systemOperations() + .externalizeResult( + successResultOf( + attempt.senderId(), + pricedResult.fullResult(), + frame, + !call.allowsStaticFrame()), + pricedResult.responseCode(), + enhancement + .systemOperations() + .syntheticTransactionForNativeCall(input, contractID, true)); + } else { + externalizeFailure( + gasRequirement, + input, + insufficientGas + ? Bytes.EMPTY + : pricedResult.fullResult().output(), + attempt, + status, + enhancement, + contractID); + } + } + } catch (final HandleException handleException) { + return haltHandleException(handleException, frame.getRemainingGas()); + } catch (final Exception internal) { + log.error("Unhandled failure for input {} to native system contract", input, internal); + return haltResult(PRECOMPILE_ERROR, frame.getRemainingGas()); + } + return pricedResult.fullResult(); + } + + private static void externalizeFailure( + final long gasRequirement, + @NonNull final Bytes input, + @NonNull final Bytes output, + @NonNull final AbstractCallAttempt attempt, + @NonNull final ResponseCodeEnum status, + @NonNull final HederaWorldUpdater.Enhancement enhancement, + @NonNull final ContractID contractID) { + enhancement + .systemOperations() + .externalizeResult( + contractFunctionResultFailedFor( + attempt.senderId(), output, gasRequirement, status.toString(), contractID), + status, + enhancement.systemOperations().syntheticTransactionForNativeCall(input, contractID, true)); + } + + // potentially other cases could be handled here if necessary + private static FullResult haltHandleException( + @NonNull final HandleException handleException, final long remainingGas) { + if (handleException.getStatus().equals(MAX_CHILD_RECORDS_EXCEEDED)) { + return haltResult(CustomExceptionalHaltReason.INSUFFICIENT_CHILD_RECORDS, remainingGas); + } + throw handleException; + } + + // + protected abstract FrameUtils.CallType callTypeOf(@NonNull final MessageFrame frame); +} diff --git a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hts/HtsCall.java b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/common/Call.java similarity index 99% rename from hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hts/HtsCall.java rename to hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/common/Call.java index 85b3c500dcff..cc97dc1d134b 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hts/HtsCall.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/common/Call.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts; +package com.hedera.node.app.service.contract.impl.exec.systemcontracts.common; import static com.hedera.hapi.node.base.ResponseCodeEnum.INSUFFICIENT_GAS; import static com.hedera.hapi.node.base.ResponseCodeEnum.SUCCESS; @@ -33,7 +33,7 @@ /** * Encapsulates a call to the HTS system contract. */ -public interface HtsCall { +public interface Call { /** * Encapsulates the result of a call to the HTS system contract. There are two elements, *

      diff --git a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hts/HtsCallAddressChecks.java b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/common/CallAddressChecks.java similarity index 89% rename from hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hts/HtsCallAddressChecks.java rename to hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/common/CallAddressChecks.java index 319575c46d77..3af3abd0ee84 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hts/HtsCallAddressChecks.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/common/CallAddressChecks.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts; +package com.hedera.node.app.service.contract.impl.exec.systemcontracts.common; import static com.hedera.node.app.service.contract.impl.exec.utils.FrameUtils.isDelegateCall; @@ -24,9 +24,9 @@ import org.hyperledger.besu.evm.frame.MessageFrame; @Singleton -public class HtsCallAddressChecks { +public class CallAddressChecks { @Inject - public HtsCallAddressChecks() { + public CallAddressChecks() { // Dagger2 } @@ -37,7 +37,7 @@ public HtsCallAddressChecks() { * @return true if the frame's parent is a delegate call */ public boolean hasParentDelegateCall(@NonNull final MessageFrame frame) { - return isDelegateCall(parentOf(frame)); + return frame.getMessageFrameStack().size() > 1 && isDelegateCall(parentOf(frame)); } private MessageFrame parentOf(@NonNull final MessageFrame frame) { diff --git a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/common/CallFactory.java b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/common/CallFactory.java new file mode 100644 index 000000000000..6ae6dbcde448 --- /dev/null +++ b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/common/CallFactory.java @@ -0,0 +1,31 @@ +/* + * Copyright (C) 2023-2024 Hedera Hashgraph, LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.hedera.node.app.service.contract.impl.exec.systemcontracts.common; + +import com.hedera.node.app.service.contract.impl.exec.utils.FrameUtils; +import edu.umd.cs.findbugs.annotations.NonNull; +import org.apache.tuweni.bytes.Bytes; +import org.hyperledger.besu.evm.frame.MessageFrame; + +/** + * Common interface for factories that create descendents of {@link AbstractCallAttempt}. + */ +public interface CallFactory { + @NonNull + AbstractCallAttempt createCallAttemptFrom( + @NonNull final Bytes input, @NonNull final FrameUtils.CallType callType, @NonNull final MessageFrame frame); +} diff --git a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hts/AbstractHtsCallTranslator.java b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/common/CallTranslator.java similarity index 64% rename from hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hts/AbstractHtsCallTranslator.java rename to hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/common/CallTranslator.java index 9fdf616de637..20262fcfd8ee 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hts/AbstractHtsCallTranslator.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/common/CallTranslator.java @@ -14,30 +14,25 @@ * limitations under the License. */ -package com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts; +package com.hedera.node.app.service.contract.impl.exec.systemcontracts.common; -import static java.util.Objects.requireNonNull; - -import com.hedera.node.app.service.contract.impl.exec.systemcontracts.HtsCallTranslator; +import com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.HtsCallAttempt; import edu.umd.cs.findbugs.annotations.NonNull; import edu.umd.cs.findbugs.annotations.Nullable; /** - * Basic implementation support for a {@link HtsCallTranslator} that returns a translated - * call when the {@link HtsCallAttempt} matches and null otherwise. + * Strategy interface for translating {@link HtsCallAttempt}s into {@link Call}s. */ -public abstract class AbstractHtsCallTranslator implements HtsCallTranslator { +public interface CallTranslator { /** - * {@inheritDoc} + * Tries to translate the given {@code attempt} into a {@link Call}, returning null if the call + * doesn't match the target type of this translator. + * + * @param attempt the attempt to translate + * @return the translated {@link Call} */ - @Override - public @Nullable HtsCall translateCallAttempt(@NonNull final HtsCallAttempt attempt) { - requireNonNull(attempt); - if (matches(attempt)) { - return callFrom(attempt); - } - return null; - } + @Nullable + Call translateCallAttempt(@NonNull T attempt); /** * Returns true if the attempt matches the selector of the call this translator is responsible for. @@ -45,7 +40,7 @@ public abstract class AbstractHtsCallTranslator implements HtsCallTranslator { * @param attempt the selector to match * @return true if the selector matches the selector of the call this translator is responsible for */ - public abstract boolean matches(@NonNull HtsCallAttempt attempt); + boolean matches(@NonNull T attempt); /** * Returns a call from the given attempt. @@ -53,5 +48,5 @@ public abstract class AbstractHtsCallTranslator implements HtsCallTranslator { * @param attempt the attempt to get the call from * @return a call from the given attempt */ - public abstract HtsCall callFrom(@NonNull HtsCallAttempt attempt); + Call callFrom(@NonNull T attempt); } diff --git a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/has/HasCallAttempt.java b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/has/HasCallAttempt.java new file mode 100644 index 000000000000..88769da34670 --- /dev/null +++ b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/has/HasCallAttempt.java @@ -0,0 +1,152 @@ +/* + * Copyright (C) 2023-2024 Hedera Hashgraph, LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.hedera.node.app.service.contract.impl.exec.systemcontracts.has; + +import static com.hedera.node.app.service.contract.impl.utils.ConversionUtils.isLongZeroAddress; +import static com.hedera.node.app.service.contract.impl.utils.ConversionUtils.numberOfLongZero; +import static java.util.Objects.requireNonNull; + +import com.esaulpaugh.headlong.abi.Function; +import com.hedera.hapi.node.base.AccountID; +import com.hedera.hapi.node.state.token.Account; +import com.hedera.node.app.service.contract.impl.exec.gas.SystemContractGasCalculator; +import com.hedera.node.app.service.contract.impl.exec.scope.VerificationStrategies; +import com.hedera.node.app.service.contract.impl.exec.systemcontracts.HasSystemContract; +import com.hedera.node.app.service.contract.impl.exec.systemcontracts.common.AbstractCall; +import com.hedera.node.app.service.contract.impl.exec.systemcontracts.common.AbstractCallAttempt; +import com.hedera.node.app.service.contract.impl.exec.systemcontracts.common.Call; +import com.hedera.node.app.service.contract.impl.exec.systemcontracts.common.CallTranslator; +import com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.AddressIdConverter; +import com.hedera.node.app.service.contract.impl.hevm.HederaWorldUpdater; +import com.swirlds.config.api.Configuration; +import edu.umd.cs.findbugs.annotations.NonNull; +import edu.umd.cs.findbugs.annotations.Nullable; +import java.util.List; +import org.apache.tuweni.bytes.Bytes; +import org.hyperledger.besu.datatypes.Address; + +/** + * Manages the call attempted by a {@link Bytes} payload received by the {@link HasSystemContract}. + * Translates a valid attempt into an appropriate {@link AbstractCall} subclass, giving the {@link Call} + * everything it will need to execute. + */ +public class HasCallAttempt extends AbstractCallAttempt { + public static final Function REDIRECT_FOR_ACCOUNT = new Function("redirectForAccount(address,bytes)"); + + @Nullable + private final Account redirectAccount; + + // too many parameters + @SuppressWarnings("java:S107") + public HasCallAttempt( + @NonNull final Bytes input, + @NonNull final Address senderAddress, + @NonNull final Address authorizingAddress, + final boolean onlyDelegatableContractKeysActive, + @NonNull final HederaWorldUpdater.Enhancement enhancement, + @NonNull final Configuration configuration, + @NonNull final AddressIdConverter addressIdConverter, + @NonNull final VerificationStrategies verificationStrategies, + @NonNull final SystemContractGasCalculator gasCalculator, + @NonNull final List callTranslators, + final boolean isStaticCall) { + super( + input, + senderAddress, + authorizingAddress, + onlyDelegatableContractKeysActive, + enhancement, + configuration, + addressIdConverter, + verificationStrategies, + gasCalculator, + callTranslators, + isStaticCall, + REDIRECT_FOR_ACCOUNT); + + if (isRedirect()) { + this.redirectAccount = linkedAccount(redirectAddress); + } else { + redirectAccount = null; + } + } + + /** + * Returns whether this is a account redirect. + * + * @return whether this is a account redirect + * @throws IllegalStateException if this is not a valid call + */ + public boolean isAccountRedirect() { + return isRedirect(); + } + + /** + * Returns the account that is the target of this redirect, if it existed. + * + * @return the account that is the target of this redirect, or null if it didn't exist + * @throws IllegalStateException if this is not an account redirect + */ + public @Nullable Account redirectAccount() { + if (!isRedirect()) { + throw new IllegalStateException("Not an account redirect"); + } + return redirectAccount; + } + + /** + * Returns the id of the account that is the target of this redirect, if it existed. + * + * @return the id of the account that is the target of this redirect, or null if it didn't exist + * @throws IllegalStateException if this is not an account redirect + */ + public @Nullable AccountID redirectAccountId() { + if (!isRedirect()) { + throw new IllegalStateException("Not a account redirect"); + } + return redirectAccount == null ? null : redirectAccount.accountId(); + } + + /** + * Returns the account at the given Besu address, if it exists. + * + * @param accountAddress the Besu address of the account to look up + * @return the account that is the target of this redirect, or null if it didn't exist + */ + public @Nullable Account linkedAccount(@NonNull final Address accountAddress) { + requireNonNull(accountAddress); + return linkedAccount(accountAddress.toArray()); + } + + /** + * Returns the account at the given EVM address, if it exists. + * + * @param evmAddress the headlong address of the account to look up + * @return the account that is the target of this redirect, or null if it didn't exist + */ + public @Nullable Account linkedAccount(@NonNull final byte[] evmAddress) { + requireNonNull(evmAddress); + if (isLongZeroAddress(evmAddress)) { + return enhancement.nativeOperations().getAccount(numberOfLongZero(evmAddress)); + } else { + final var addressNum = enhancement + .nativeOperations() + .resolveAlias(com.hedera.pbj.runtime.io.buffer.Bytes.wrap(evmAddress)); + return enhancement.nativeOperations().getAccount(addressNum); + } + } +} diff --git a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/has/HasCallFactory.java b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/has/HasCallFactory.java new file mode 100644 index 000000000000..c7e2933967b0 --- /dev/null +++ b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/has/HasCallFactory.java @@ -0,0 +1,89 @@ +/* + * Copyright (C) 2023-2024 Hedera Hashgraph, LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.hedera.node.app.service.contract.impl.exec.systemcontracts.has; + +import static com.hedera.node.app.service.contract.impl.exec.utils.FrameUtils.configOf; +import static com.hedera.node.app.service.contract.impl.exec.utils.FrameUtils.proxyUpdaterFor; +import static com.hedera.node.app.service.contract.impl.exec.utils.FrameUtils.systemContractGasCalculatorOf; +import static java.util.Objects.requireNonNull; + +import com.hedera.node.app.service.contract.impl.exec.scope.VerificationStrategies; +import com.hedera.node.app.service.contract.impl.exec.systemcontracts.common.CallAddressChecks; +import com.hedera.node.app.service.contract.impl.exec.systemcontracts.common.CallFactory; +import com.hedera.node.app.service.contract.impl.exec.systemcontracts.common.CallTranslator; +import com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.SyntheticIds; +import com.hedera.node.app.service.contract.impl.exec.utils.FrameUtils; +import edu.umd.cs.findbugs.annotations.NonNull; +import java.util.List; +import javax.inject.Inject; +import javax.inject.Named; +import javax.inject.Singleton; +import org.apache.tuweni.bytes.Bytes; +import org.hyperledger.besu.evm.frame.MessageFrame; + +/** + * Factory to create a new {@link HasCallAttempt} for a given input and message frame. + */ +@Singleton +public class HasCallFactory implements CallFactory { + private final SyntheticIds syntheticIds; + private final CallAddressChecks addressChecks; + private final VerificationStrategies verificationStrategies; + private final List callTranslators; + + @Inject + public HasCallFactory( + @NonNull final SyntheticIds syntheticIds, + @NonNull final CallAddressChecks addressChecks, + @NonNull final VerificationStrategies verificationStrategies, + @NonNull @Named("HasTranslators") final List callTranslators) { + this.syntheticIds = requireNonNull(syntheticIds); + this.addressChecks = requireNonNull(addressChecks); + this.verificationStrategies = requireNonNull(verificationStrategies); + this.callTranslators = requireNonNull(callTranslators); + } + + /** + * Creates a new {@link HasCallAttempt} for the given input and message frame. + * + * @param input the input + * @param frame the message frame + * @return the new attempt + * @throws RuntimeException if the call cannot be created + */ + @Override + public @NonNull HasCallAttempt createCallAttemptFrom( + @NonNull final Bytes input, + @NonNull final FrameUtils.CallType callType, + @NonNull final MessageFrame frame) { + requireNonNull(input); + requireNonNull(frame); + final var enhancement = proxyUpdaterFor(frame).enhancement(); + return new HasCallAttempt( + input, + frame.getSenderAddress(), + frame.getSenderAddress(), + addressChecks.hasParentDelegateCall(frame), + enhancement, + configOf(frame), + syntheticIds.converterFor(enhancement.nativeOperations()), + verificationStrategies, + systemContractGasCalculatorOf(frame), + callTranslators, + frame.isStatic()); + } +} diff --git a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/has/hbarallowance/HbarAllowanceCall.java b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/has/hbarallowance/HbarAllowanceCall.java new file mode 100644 index 000000000000..2541db83fda5 --- /dev/null +++ b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/has/hbarallowance/HbarAllowanceCall.java @@ -0,0 +1,86 @@ +/* + * Copyright (C) 2023-2024 Hedera Hashgraph, LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.hedera.node.app.service.contract.impl.exec.systemcontracts.has.hbarallowance; + +import static com.hedera.hapi.node.base.ResponseCodeEnum.INVALID_ALLOWANCE_OWNER_ID; +import static com.hedera.hapi.node.base.ResponseCodeEnum.SUCCESS; +import static com.hedera.node.app.service.contract.impl.exec.systemcontracts.FullResult.successResult; +import static com.hedera.node.app.service.contract.impl.exec.systemcontracts.common.Call.PricedResult.gasOnly; +import static java.util.Objects.requireNonNull; + +import com.hedera.hapi.node.base.AccountID; +import com.hedera.hapi.node.state.token.Account; +import com.hedera.hapi.node.state.token.AccountCryptoAllowance; +import com.hedera.node.app.service.contract.impl.exec.gas.DispatchType; +import com.hedera.node.app.service.contract.impl.exec.systemcontracts.common.AbstractCall; +import com.hedera.node.app.service.contract.impl.exec.systemcontracts.has.HasCallAttempt; +import edu.umd.cs.findbugs.annotations.NonNull; +import edu.umd.cs.findbugs.annotations.Nullable; +import java.math.BigInteger; +import java.nio.ByteBuffer; +import org.hyperledger.besu.evm.frame.MessageFrame; + +public class HbarAllowanceCall extends AbstractCall { + + private final AccountID owner; + private final AccountID spender; + + public HbarAllowanceCall( + @NonNull final HasCallAttempt attempt, @Nullable final AccountID owner, @NonNull final AccountID spender) { + super(attempt.systemContractGasCalculator(), attempt.enhancement(), true); + this.spender = requireNonNull(spender); + this.owner = owner; + } + + @Override + public boolean allowsStaticFrame() { + return true; + } + + @NonNull + @Override + public PricedResult execute(final MessageFrame frame) { + requireNonNull(frame); + if (owner == null || nativeOperations().getAccount(owner) == null) { + return reversionWith( + INVALID_ALLOWANCE_OWNER_ID, gasCalculator.canonicalGasRequirement(DispatchType.APPROVE)); + } + final var gasRequirement = gasCalculator.viewGasRequirement(); + + final var allowance = getAllowance(nativeOperations().getAccount(owner), spender); + return gasOnly(successResult(encodedAllowanceOutput(allowance), gasRequirement), SUCCESS, false); + } + + @NonNull + private BigInteger getAllowance(@NonNull final Account ownerAccount, @NonNull final AccountID spenderID) { + requireNonNull(ownerAccount); + requireNonNull(spenderID); + final var tokenAllowance = requireNonNull(ownerAccount).cryptoAllowances().stream() + .filter(allowance -> allowance.spenderIdOrThrow().equals(spenderID)) + .findFirst(); + return BigInteger.valueOf( + tokenAllowance.map(AccountCryptoAllowance::amount).orElse(0L)); + } + + @NonNull + private ByteBuffer encodedAllowanceOutput(@NonNull final BigInteger allowance) { + requireNonNull(allowance); + return HbarAllowanceTranslator.HBAR_ALLOWANCE_PROXY + .getOutputs() + .encodeElements((long) SUCCESS.protoOrdinal(), allowance); + } +} diff --git a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/has/hbarallowance/HbarAllowanceTranslator.java b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/has/hbarallowance/HbarAllowanceTranslator.java new file mode 100644 index 000000000000..cd09df0b4381 --- /dev/null +++ b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/has/hbarallowance/HbarAllowanceTranslator.java @@ -0,0 +1,100 @@ +/* + * Copyright (C) 2023-2024 Hedera Hashgraph, LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.hedera.node.app.service.contract.impl.exec.systemcontracts.has.hbarallowance; + +import static java.util.Objects.requireNonNull; + +import com.esaulpaugh.headlong.abi.Function; +import com.hedera.node.app.service.contract.impl.exec.systemcontracts.common.AbstractCallTranslator; +import com.hedera.node.app.service.contract.impl.exec.systemcontracts.common.Call; +import com.hedera.node.app.service.contract.impl.exec.systemcontracts.has.HasCallAttempt; +import com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.ReturnTypes; +import edu.umd.cs.findbugs.annotations.NonNull; +import java.util.Arrays; +import javax.inject.Inject; +import javax.inject.Singleton; + +/** + * Translates {@code hbarApprove()} calls to the HAS system contract. + */ +@Singleton +public class HbarAllowanceTranslator extends AbstractCallTranslator { + + public static final Function HBAR_ALLOWANCE_PROXY = + new Function("hbarAllowance(address)", ReturnTypes.RESPONSE_CODE_INT256); + + public static final Function HBAR_ALLOWANCE = + new Function("hbarAllowance(address,address)", ReturnTypes.RESPONSE_CODE_INT256); + + @Inject + public HbarAllowanceTranslator() { + // Dagger2 + } + + /** + * {@inheritDoc} + */ + @Override + public boolean matches(@NonNull final HasCallAttempt attempt) { + requireNonNull(attempt); + return matchesAllowanceSelector(attempt.selector()) || matchesAllowanceProxySelector(attempt.selector()); + } + + /** + * {@inheritDoc} + */ + @Override + public Call callFrom(@NonNull final HasCallAttempt attempt) { + requireNonNull(attempt); + if (matchesAllowanceSelector(attempt.selector())) { + return decodeAllowance(attempt); + } else if (matchesAllowanceProxySelector(attempt.selector())) { + return decodeAllowanceProxy(attempt); + } + return null; + } + + @NonNull + private boolean matchesAllowanceSelector(@NonNull final byte[] selector) { + requireNonNull(selector); + return Arrays.equals(selector, HBAR_ALLOWANCE.selector()); + } + + @NonNull + private boolean matchesAllowanceProxySelector(@NonNull final byte[] selector) { + requireNonNull(selector); + return Arrays.equals(selector, HBAR_ALLOWANCE_PROXY.selector()); + } + + @NonNull + private Call decodeAllowance(@NonNull final HasCallAttempt attempt) { + requireNonNull(attempt); + final var call = HBAR_ALLOWANCE.decodeCall(attempt.inputBytes()); + + var owner = attempt.addressIdConverter().convert(call.get(0)); + var spender = attempt.addressIdConverter().convert(call.get(1)); + return new HbarAllowanceCall(attempt, owner, spender); + } + + @NonNull + private Call decodeAllowanceProxy(@NonNull final HasCallAttempt attempt) { + requireNonNull(attempt); + final var call = HBAR_ALLOWANCE_PROXY.decodeCall(attempt.inputBytes()); + var spender = attempt.addressIdConverter().convert(call.get(0)); + return new HbarAllowanceCall(attempt, attempt.redirectAccountId(), spender); + } +} diff --git a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/has/hbarapprove/HbarApproveCall.java b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/has/hbarapprove/HbarApproveCall.java new file mode 100644 index 000000000000..bad3604f8b04 --- /dev/null +++ b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/has/hbarapprove/HbarApproveCall.java @@ -0,0 +1,63 @@ +/* + * Copyright (C) 2023-2024 Hedera Hashgraph, LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.hedera.node.app.service.contract.impl.exec.systemcontracts.has.hbarapprove; + +import static com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.ReturnTypes.encodedRc; +import static com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.ReturnTypes.standardized; +import static java.util.Objects.requireNonNull; + +import com.hedera.hapi.node.base.AccountID; +import com.hedera.hapi.node.base.ResponseCodeEnum; +import com.hedera.hapi.node.transaction.TransactionBody; +import com.hedera.node.app.service.contract.impl.exec.gas.DispatchType; +import com.hedera.node.app.service.contract.impl.exec.scope.VerificationStrategy; +import com.hedera.node.app.service.contract.impl.exec.systemcontracts.common.AbstractCall; +import com.hedera.node.app.service.contract.impl.exec.systemcontracts.has.HasCallAttempt; +import com.hedera.node.app.service.contract.impl.records.ContractCallRecordBuilder; +import edu.umd.cs.findbugs.annotations.NonNull; +import org.hyperledger.besu.evm.frame.MessageFrame; + +public class HbarApproveCall extends AbstractCall { + + private final VerificationStrategy verificationStrategy; + private final TransactionBody transactionBody; + private final AccountID sender; + + public HbarApproveCall(@NonNull final HasCallAttempt attempt, @NonNull final TransactionBody transactionBody) { + super(attempt.systemContractGasCalculator(), attempt.enhancement(), false); + this.transactionBody = requireNonNull(transactionBody); + this.verificationStrategy = attempt.defaultVerificationStrategy(); + this.sender = attempt.senderId(); + } + + @NonNull + @Override + public PricedResult execute(@NonNull final MessageFrame frame) { + requireNonNull(frame); + final var recordBuilder = systemContractOperations() + .dispatch(transactionBody, verificationStrategy, sender, ContractCallRecordBuilder.class); + + final var gasRequirement = gasCalculator.gasRequirement(transactionBody, DispatchType.APPROVE, sender); + + final var status = recordBuilder.status(); + if (status != ResponseCodeEnum.SUCCESS) { + return reversionWith(gasRequirement, recordBuilder); + } else { + return completionWith(gasRequirement, recordBuilder, encodedRc(standardized(status))); + } + } +} diff --git a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/has/hbarapprove/HbarApproveTranslator.java b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/has/hbarapprove/HbarApproveTranslator.java new file mode 100644 index 000000000000..ee63611d0fb3 --- /dev/null +++ b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/has/hbarapprove/HbarApproveTranslator.java @@ -0,0 +1,129 @@ +/* + * Copyright (C) 2023-2024 Hedera Hashgraph, LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.hedera.node.app.service.contract.impl.exec.systemcontracts.has.hbarapprove; + +import static java.util.Objects.requireNonNull; + +import com.esaulpaugh.headlong.abi.Function; +import com.hedera.hapi.node.base.AccountID; +import com.hedera.hapi.node.token.CryptoAllowance; +import com.hedera.hapi.node.token.CryptoApproveAllowanceTransactionBody; +import com.hedera.hapi.node.transaction.TransactionBody; +import com.hedera.node.app.service.contract.impl.exec.systemcontracts.common.AbstractCallTranslator; +import com.hedera.node.app.service.contract.impl.exec.systemcontracts.common.Call; +import com.hedera.node.app.service.contract.impl.exec.systemcontracts.has.HasCallAttempt; +import com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.ReturnTypes; +import edu.umd.cs.findbugs.annotations.NonNull; +import java.math.BigInteger; +import java.util.Arrays; +import javax.inject.Inject; +import javax.inject.Singleton; + +/** + * Translates {@code hbarApprove()} calls to the HAS system contract. + */ +@Singleton +public class HbarApproveTranslator extends AbstractCallTranslator { + + public static final Function HBAR_APPROVE_PROXY = new Function("hbarApprove(address,int256)", ReturnTypes.INT_64); + + public static final Function HBAR_APPROVE = new Function("hbarApprove(address,address,int256)", ReturnTypes.INT_64); + + @Inject + public HbarApproveTranslator() { + // Dagger2 + } + + /** + * {@inheritDoc} + */ + @Override + public boolean matches(@NonNull final HasCallAttempt attempt) { + requireNonNull(attempt); + return matchesApproveSelector(attempt.selector()) || matchesApproveProxySelector(attempt.selector()); + } + + /** + * {@inheritDoc} + */ + @Override + public Call callFrom(@NonNull final HasCallAttempt attempt) { + requireNonNull(attempt); + + if (matchesApproveSelector(attempt.selector())) { + return new HbarApproveCall(attempt, bodyForApprove(attempt)); + } else if (matchesApproveProxySelector(attempt.selector())) { + return new HbarApproveCall(attempt, bodyForApproveProxy(attempt)); + } + return null; + } + + @NonNull + private TransactionBody bodyForApprove(@NonNull final HasCallAttempt attempt) { + requireNonNull(attempt); + final var call = HBAR_APPROVE.decodeCall(attempt.inputBytes()); + var owner = attempt.addressIdConverter().convert(call.get(0)); + var spender = attempt.addressIdConverter().convert(call.get(1)); + + return bodyOf(cryptoApproveTransactionBody(owner, spender, call.get(2))); + } + + @NonNull + private TransactionBody bodyForApproveProxy(@NonNull final HasCallAttempt attempt) { + requireNonNull(attempt); + final var call = HBAR_APPROVE_PROXY.decodeCall(attempt.inputBytes()); + final var owner = attempt.redirectAccount() == null + ? attempt.senderId() + : attempt.redirectAccount().accountId(); + final var spender = attempt.addressIdConverter().convert(call.get(0)); + + return bodyOf(cryptoApproveTransactionBody(owner, spender, call.get(1))); + } + + @NonNull + private CryptoApproveAllowanceTransactionBody cryptoApproveTransactionBody( + @NonNull final AccountID owner, @NonNull final AccountID operatorId, @NonNull final BigInteger amount) { + requireNonNull(owner); + requireNonNull(operatorId); + requireNonNull(amount); + return CryptoApproveAllowanceTransactionBody.newBuilder() + .cryptoAllowances(CryptoAllowance.newBuilder() + .owner(owner) + .spender(operatorId) + .amount(amount.longValue()) + .build()) + .build(); + } + + @NonNull + private TransactionBody bodyOf( + @NonNull final CryptoApproveAllowanceTransactionBody approveAllowanceTransactionBody) { + return TransactionBody.newBuilder() + .cryptoApproveAllowance(approveAllowanceTransactionBody) + .build(); + } + + @NonNull + private boolean matchesApproveSelector(final byte[] selector) { + return Arrays.equals(selector, HBAR_APPROVE.selector()); + } + + @NonNull + private boolean matchesApproveProxySelector(final byte[] selector) { + return Arrays.equals(selector, HBAR_APPROVE_PROXY.selector()); + } +} diff --git a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hts/AbstractNftViewCall.java b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hts/AbstractNftViewCall.java index 159b90f1445a..6f5835db9ddb 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hts/AbstractNftViewCall.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hts/AbstractNftViewCall.java @@ -19,7 +19,7 @@ import static com.hedera.hapi.node.base.ResponseCodeEnum.INVALID_TOKEN_ID; import static com.hedera.node.app.service.contract.impl.exec.systemcontracts.FullResult.haltResult; import static com.hedera.node.app.service.contract.impl.exec.systemcontracts.FullResult.ordinalRevertResult; -import static com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.HtsCall.PricedResult.gasOnly; +import static com.hedera.node.app.service.contract.impl.exec.systemcontracts.common.Call.PricedResult.gasOnly; import static java.util.Objects.requireNonNull; import com.hedera.hapi.node.base.ResponseCodeEnum; diff --git a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hts/AbstractTokenViewCall.java b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hts/AbstractTokenViewCall.java index 43c76c3685fe..51b4efbff003 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hts/AbstractTokenViewCall.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hts/AbstractTokenViewCall.java @@ -17,17 +17,18 @@ package com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts; import static com.hedera.hapi.node.base.ResponseCodeEnum.INVALID_TOKEN_ID; -import static com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.HtsCall.PricedResult.gasOnly; +import static com.hedera.node.app.service.contract.impl.exec.systemcontracts.common.Call.PricedResult.gasOnly; import com.hedera.hapi.node.base.ResponseCodeEnum; import com.hedera.hapi.node.state.token.Token; import com.hedera.node.app.service.contract.impl.exec.gas.SystemContractGasCalculator; import com.hedera.node.app.service.contract.impl.exec.systemcontracts.FullResult; +import com.hedera.node.app.service.contract.impl.exec.systemcontracts.common.AbstractCall; import com.hedera.node.app.service.contract.impl.hevm.HederaWorldUpdater; import edu.umd.cs.findbugs.annotations.NonNull; import edu.umd.cs.findbugs.annotations.Nullable; -public abstract class AbstractTokenViewCall extends AbstractHtsCall { +public abstract class AbstractTokenViewCall extends AbstractCall { protected final Token token; protected AbstractTokenViewCall( diff --git a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hts/DispatchForResponseCodeHtsCall.java b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hts/DispatchForResponseCodeHtsCall.java index 6b955f747b9a..1c5f55e0c913 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hts/DispatchForResponseCodeHtsCall.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hts/DispatchForResponseCodeHtsCall.java @@ -20,8 +20,8 @@ import static com.hedera.hapi.node.base.ResponseCodeEnum.SUCCESS; import static com.hedera.node.app.service.contract.impl.exec.failure.CustomExceptionalHaltReason.ERROR_DECODING_PRECOMPILE_INPUT; import static com.hedera.node.app.service.contract.impl.exec.systemcontracts.FullResult.haltResult; +import static com.hedera.node.app.service.contract.impl.exec.systemcontracts.common.Call.PricedResult.gasOnly; import static com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.DispatchForResponseCodeHtsCall.OutputFn.STANDARD_OUTPUT_FN; -import static com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.HtsCall.PricedResult.gasOnly; import static com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.ReturnTypes.encodedRc; import static com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.ReturnTypes.standardized; import static com.hedera.node.app.service.contract.impl.exec.utils.FrameUtils.contractsConfigOf; @@ -32,6 +32,7 @@ import com.hedera.node.app.service.contract.impl.exec.gas.DispatchGasCalculator; import com.hedera.node.app.service.contract.impl.exec.gas.SystemContractGasCalculator; import com.hedera.node.app.service.contract.impl.exec.scope.VerificationStrategy; +import com.hedera.node.app.service.contract.impl.exec.systemcontracts.common.AbstractCall; import com.hedera.node.app.service.contract.impl.hevm.HederaWorldUpdater; import com.hedera.node.app.service.contract.impl.records.ContractCallRecordBuilder; import edu.umd.cs.findbugs.annotations.NonNull; @@ -45,7 +46,7 @@ * An HTS call that simply dispatches a synthetic transaction body and returns a result that is * an encoded {@link com.hedera.hapi.node.base.ResponseCodeEnum}. */ -public class DispatchForResponseCodeHtsCall extends AbstractHtsCall { +public class DispatchForResponseCodeHtsCall extends AbstractCall { /** * The "standard" failure customizer that replaces {@link ResponseCodeEnum#INVALID_SIGNATURE} with * {@link ResponseCodeEnum#INVALID_FULL_PREFIX_SIGNATURE_FOR_PRECOMPILE}. (Note this code no longer diff --git a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hts/HtsCallAttempt.java b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hts/HtsCallAttempt.java index e5e3b5bedaca..61a42b3eb212 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hts/HtsCallAttempt.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hts/HtsCallAttempt.java @@ -21,39 +21,31 @@ import static java.util.Objects.requireNonNull; import com.esaulpaugh.headlong.abi.Function; -import com.esaulpaugh.headlong.abi.Tuple; import com.hedera.hapi.node.base.AccountID; import com.hedera.hapi.node.base.TokenID; import com.hedera.hapi.node.base.TokenType; import com.hedera.hapi.node.state.token.Token; import com.hedera.node.app.service.contract.impl.exec.gas.SystemContractGasCalculator; -import com.hedera.node.app.service.contract.impl.exec.scope.HederaNativeOperations; import com.hedera.node.app.service.contract.impl.exec.scope.VerificationStrategies; -import com.hedera.node.app.service.contract.impl.exec.scope.VerificationStrategy; -import com.hedera.node.app.service.contract.impl.exec.systemcontracts.HtsCallTranslator; import com.hedera.node.app.service.contract.impl.exec.systemcontracts.HtsSystemContract; +import com.hedera.node.app.service.contract.impl.exec.systemcontracts.common.AbstractCallAttempt; +import com.hedera.node.app.service.contract.impl.exec.systemcontracts.common.Call; +import com.hedera.node.app.service.contract.impl.exec.systemcontracts.common.CallTranslator; import com.hedera.node.app.service.contract.impl.hevm.HederaWorldUpdater; import com.swirlds.config.api.Configuration; import edu.umd.cs.findbugs.annotations.NonNull; import edu.umd.cs.findbugs.annotations.Nullable; -import java.nio.BufferUnderflowException; -import java.util.Arrays; import java.util.List; import org.apache.tuweni.bytes.Bytes; import org.hyperledger.besu.datatypes.Address; /** * Manages the call attempted by a {@link Bytes} payload received by the {@link HtsSystemContract}. - * Translates a valid attempt into an appropriate {@link HtsCall} subclass, giving the {@link HtsCall} + * Translates a valid attempt into an appropriate {@link Call} subclass, giving the {@link Call} * everything it will need to execute. */ -public class HtsCallAttempt { +public class HtsCallAttempt extends AbstractCallAttempt { public static final Function REDIRECT_FOR_TOKEN = new Function("redirectForToken(address,bytes)"); - private static final byte[] REDIRECT_FOR_TOKEN_SELECTOR = REDIRECT_FOR_TOKEN.selector(); - - private final byte[] selector; - private final Bytes input; - private final boolean isRedirect; // The id address of the account authorizing the call, in the sense // that (1) a dispatch should omit the key of this account from the @@ -63,22 +55,10 @@ public class HtsCallAttempt { // delegates list, so it is possible the authorizing account can be // different from the EVM sender address private final AccountID authorizingId; - private final Address authorizingAddress; - // The id of the sender in the EVM frame - private final AccountID senderId; - private final Address senderAddress; - private final boolean onlyDelegatableContractKeysActive; @Nullable private final Token redirectToken; - private final HederaWorldUpdater.Enhancement enhancement; - private final Configuration configuration; - private final AddressIdConverter addressIdConverter; - private final VerificationStrategies verificationStrategies; - private final SystemContractGasCalculator gasCalculator; - private final List callTranslators; - private final boolean isStaticCall; // too many parameters @SuppressWarnings("java:S107") public HtsCallAttempt( @@ -91,145 +71,28 @@ public HtsCallAttempt( @NonNull final AddressIdConverter addressIdConverter, @NonNull final VerificationStrategies verificationStrategies, @NonNull final SystemContractGasCalculator gasCalculator, - @NonNull final List callTranslators, + @NonNull final List callTranslators, final boolean isStaticCall) { - requireNonNull(input); - this.callTranslators = requireNonNull(callTranslators); - this.gasCalculator = requireNonNull(gasCalculator); - this.senderAddress = requireNonNull(senderAddress); - this.authorizingAddress = requireNonNull(authorizingAddress); - this.configuration = requireNonNull(configuration); - this.addressIdConverter = requireNonNull(addressIdConverter); - this.enhancement = requireNonNull(enhancement); - this.verificationStrategies = requireNonNull(verificationStrategies); - this.onlyDelegatableContractKeysActive = onlyDelegatableContractKeysActive; - - this.isRedirect = isRedirect(input.toArrayUnsafe()); - if (this.isRedirect) { - Tuple abiCall = null; - try { - // First try to decode the redirect with standard ABI encoding using a 32-byte address - abiCall = REDIRECT_FOR_TOKEN.decodeCall(input.toArrayUnsafe()); - } catch (IllegalArgumentException | BufferUnderflowException | IndexOutOfBoundsException ignore) { - // Otherwise use the "packed" encoding with a 20-byte address - } - final Address tokenAddress; - if (abiCall != null) { - tokenAddress = Address.fromHexString(abiCall.get(0).toString()); - this.input = Bytes.wrap((byte[]) abiCall.get(1)); - } else { - tokenAddress = Address.wrap(input.slice(4, 20)); - this.input = input.slice(24); - } - this.redirectToken = linkedToken(tokenAddress); + super( + input, + senderAddress, + authorizingAddress, + onlyDelegatableContractKeysActive, + enhancement, + configuration, + addressIdConverter, + verificationStrategies, + gasCalculator, + callTranslators, + isStaticCall, + REDIRECT_FOR_TOKEN); + if (isRedirect()) { + this.redirectToken = linkedToken(redirectAddress); } else { redirectToken = null; - this.input = input; } - this.selector = this.input.slice(0, 4).toArrayUnsafe(); - this.senderId = addressIdConverter.convertSender(senderAddress); this.authorizingId = (authorizingAddress != senderAddress) ? addressIdConverter.convertSender(authorizingAddress) : senderId; - this.isStaticCall = isStaticCall; - } - - /** - * Returns the default verification strategy for this call (i.e., the strategy that treats only - * contract id and delegatable contract id keys as active when they match the call's sender address). - * - * @return the default verification strategy for this call - */ - public @NonNull VerificationStrategy defaultVerificationStrategy() { - return verificationStrategies.activatingOnlyContractKeysFor( - authorizingAddress, onlyDelegatableContractKeysActive, enhancement.nativeOperations()); - } - - /** - * Returns the updater enhancement this call was attempted within. - * - * @return the updater enhancement this call was attempted within - */ - public @NonNull HederaWorldUpdater.Enhancement enhancement() { - return enhancement; - } - - /** - * Returns the system contract gas calculator for this call. - * - * @return the system contract gas calculator for this call - */ - public @NonNull SystemContractGasCalculator systemContractGasCalculator() { - return gasCalculator; - } - - /** - * Returns the native operations this call was attempted within. - * - * @return the native operations this call was attempted within - */ - public @NonNull HederaNativeOperations nativeOperations() { - return enhancement.nativeOperations(); - } - - /** - * Tries to translate this call attempt into a {@link HtsCall} from the given sender address. - * - * @return the executable call, or null if this attempt can't be translated to one - */ - public @Nullable HtsCall asExecutableCall() { - for (final var translator : callTranslators) { - final var call = translator.translateCallAttempt(this); - if (call != null) { - return call; - } - } - return null; - } - - /** - * Returns the ID of the sender of this call. - * - * @return the ID of the sender of this call - */ - public @NonNull AccountID senderId() { - return senderId; - } - - /** - * Returns the address of the sender of this call. - * - * @return the address of the sender of this call - */ - public @NonNull Address senderAddress() { - return senderAddress; - } - - /** - * Returns the address ID converter for this call. - * - * @return the address ID converter for this call - */ - public AddressIdConverter addressIdConverter() { - return addressIdConverter; - } - - /** - * Returns the configuration for this call. - * - * @return the configuration for this call - */ - public Configuration configuration() { - return configuration; - } - - /** - * Returns the selector of this call. - * - * @return the selector of this call - * @throws IllegalStateException if this is not a valid call - */ - public byte[] selector() { - return selector; } /** @@ -239,27 +102,7 @@ public byte[] selector() { * @throws IllegalStateException if this is not a valid call */ public boolean isTokenRedirect() { - return isRedirect; - } - - /** - * Returns the input of this call. - * - * @return the input of this call - * @throws IllegalStateException if this is not a valid call - */ - public Bytes input() { - return input; - } - - /** - * Returns the raw byte array input of this call. - * - * @return the raw input of this call - * @throws IllegalStateException if this is not a valid call - */ - public byte[] inputBytes() { - return input.toArrayUnsafe(); + return isRedirect(); } /** @@ -269,7 +112,7 @@ public byte[] inputBytes() { * @throws IllegalStateException if this is not a token redirect */ public @Nullable Token redirectToken() { - if (!isRedirect) { + if (!isRedirect()) { throw new IllegalStateException("Not a token redirect"); } return redirectToken; @@ -282,7 +125,7 @@ public byte[] inputBytes() { * @throws IllegalStateException if this is not a token redirect */ public @Nullable TokenID redirectTokenId() { - if (!isRedirect) { + if (!isRedirect()) { throw new IllegalStateException("Not a token redirect"); } return redirectToken == null ? null : redirectToken.tokenId(); @@ -295,7 +138,7 @@ public byte[] inputBytes() { * @throws IllegalStateException if this is not a token redirect */ public @Nullable TokenType redirectTokenType() { - if (!isRedirect) { + if (!isRedirect()) { throw new IllegalStateException("Not a token redirect"); } return redirectToken == null ? null : redirectToken.tokenType(); @@ -328,13 +171,6 @@ public byte[] inputBytes() { } } - /** - * @return whether the current call attempt is a static call - */ - public boolean isStaticCall() { - return isStaticCall; - } - /** * Returns the ID of the sender of this call in the EVM frame. * @@ -343,14 +179,4 @@ public boolean isStaticCall() { public AccountID authorizingId() { return authorizingId; } - - private boolean isRedirect(final byte[] input) { - return Arrays.equals( - input, - 0, - REDIRECT_FOR_TOKEN_SELECTOR.length, - REDIRECT_FOR_TOKEN_SELECTOR, - 0, - REDIRECT_FOR_TOKEN_SELECTOR.length); - } } diff --git a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hts/HtsCallFactory.java b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hts/HtsCallFactory.java index b6981384f22d..86be0672a187 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hts/HtsCallFactory.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hts/HtsCallFactory.java @@ -23,11 +23,14 @@ import static java.util.Objects.requireNonNull; import com.hedera.node.app.service.contract.impl.exec.scope.VerificationStrategies; -import com.hedera.node.app.service.contract.impl.exec.systemcontracts.HtsCallTranslator; +import com.hedera.node.app.service.contract.impl.exec.systemcontracts.common.CallAddressChecks; +import com.hedera.node.app.service.contract.impl.exec.systemcontracts.common.CallFactory; +import com.hedera.node.app.service.contract.impl.exec.systemcontracts.common.CallTranslator; import com.hedera.node.app.service.contract.impl.exec.utils.FrameUtils.CallType; import edu.umd.cs.findbugs.annotations.NonNull; import java.util.List; import javax.inject.Inject; +import javax.inject.Named; import javax.inject.Singleton; import org.apache.tuweni.bytes.Bytes; import org.hyperledger.besu.evm.frame.MessageFrame; @@ -36,18 +39,18 @@ * Factory to create a new {@link HtsCallAttempt} for a given input and message frame. */ @Singleton -public class HtsCallFactory { +public class HtsCallFactory implements CallFactory { private final SyntheticIds syntheticIds; - private final HtsCallAddressChecks addressChecks; + private final CallAddressChecks addressChecks; private final VerificationStrategies verificationStrategies; - private final List callTranslators; + private final List callTranslators; @Inject public HtsCallFactory( @NonNull final SyntheticIds syntheticIds, - @NonNull final HtsCallAddressChecks addressChecks, + @NonNull final CallAddressChecks addressChecks, @NonNull final VerificationStrategies verificationStrategies, - @NonNull final List callTranslators) { + @NonNull @Named("HtsTranslators") final List callTranslators) { this.syntheticIds = requireNonNull(syntheticIds); this.addressChecks = requireNonNull(addressChecks); this.verificationStrategies = requireNonNull(verificationStrategies); @@ -63,6 +66,7 @@ public HtsCallFactory( * @return the new attempt * @throws RuntimeException if the call cannot be created */ + @Override public @NonNull HtsCallAttempt createCallAttemptFrom( @NonNull final Bytes input, @NonNull final CallType callType, @NonNull final MessageFrame frame) { requireNonNull(input); diff --git a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hts/ReturnTypes.java b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hts/ReturnTypes.java index 18a897809c82..4ce394d671ab 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hts/ReturnTypes.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hts/ReturnTypes.java @@ -63,6 +63,8 @@ private ReturnTypes() { public static final String RESPONSE_CODE_BOOL = "(int32,bool)"; public static final String RESPONSE_CODE_INT32 = "(int32,int32)"; public static final String RESPONSE_CODE_UINT256 = "(int64,uint256)"; + public static final String RESPONSE_CODE_INT256 = "(int64,int256)"; + public static final String UINT256 = "(uint256)"; public static final String RESPONSE_CODE_EXPIRY = "(int32," // Expiry diff --git a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hts/TokenTupleUtils.java b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hts/TokenTupleUtils.java index 372ad8645d4f..ed634babaecf 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hts/TokenTupleUtils.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hts/TokenTupleUtils.java @@ -247,7 +247,7 @@ public static Tuple nftTokenInfoTupleFor( tokenInfoTupleFor(token, ledgerId), serialNumber, // The odd construct allowing a token to not have a treasury account set is to accommodate - // Token.DEFAULT being passed into this method, which a few HtsCall implementations do + // Token.DEFAULT being passed into this method, which a few Call implementations do priorityAddressOf(nft.ownerIdOrElse(token.treasuryAccountIdOrElse(ZERO_ACCOUNT_ID)), nativeOperations), nft.mintTimeOrElse(new Timestamp(0, 0)).seconds(), nftMetaData, diff --git a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hts/allowance/GetAllowanceCall.java b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hts/allowance/GetAllowanceCall.java index edfe4fc25749..5eac3eba266b 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hts/allowance/GetAllowanceCall.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hts/allowance/GetAllowanceCall.java @@ -22,7 +22,7 @@ import static com.hedera.hapi.node.base.TokenType.FUNGIBLE_COMMON; import static com.hedera.node.app.service.contract.impl.exec.systemcontracts.FullResult.revertResult; import static com.hedera.node.app.service.contract.impl.exec.systemcontracts.FullResult.successResult; -import static com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.HtsCall.PricedResult.gasOnly; +import static com.hedera.node.app.service.contract.impl.exec.systemcontracts.common.Call.PricedResult.gasOnly; import static java.util.Objects.requireNonNull; import com.esaulpaugh.headlong.abi.Address; @@ -31,7 +31,7 @@ import com.hedera.hapi.node.state.token.AccountFungibleTokenAllowance; import com.hedera.hapi.node.state.token.Token; import com.hedera.node.app.service.contract.impl.exec.gas.SystemContractGasCalculator; -import com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.AbstractHtsCall; +import com.hedera.node.app.service.contract.impl.exec.systemcontracts.common.AbstractCall; import com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.AddressIdConverter; import com.hedera.node.app.service.contract.impl.hevm.HederaWorldUpdater; import edu.umd.cs.findbugs.annotations.NonNull; @@ -42,7 +42,7 @@ import javax.inject.Singleton; @Singleton -public class GetAllowanceCall extends AbstractHtsCall { +public class GetAllowanceCall extends AbstractCall { private final Address owner; private final Address spender; diff --git a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hts/allowance/GetAllowanceTranslator.java b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hts/allowance/GetAllowanceTranslator.java index 991251bbea9a..625a8b705de1 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hts/allowance/GetAllowanceTranslator.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hts/allowance/GetAllowanceTranslator.java @@ -18,8 +18,8 @@ import com.esaulpaugh.headlong.abi.Address; import com.esaulpaugh.headlong.abi.Function; -import com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.AbstractHtsCallTranslator; -import com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.HtsCall; +import com.hedera.node.app.service.contract.impl.exec.systemcontracts.common.AbstractCallTranslator; +import com.hedera.node.app.service.contract.impl.exec.systemcontracts.common.Call; import com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.HtsCallAttempt; import com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.ReturnTypes; import com.hedera.node.app.service.contract.impl.utils.ConversionUtils; @@ -32,7 +32,7 @@ * Translates {@code allowance()} calls to the HTS system contract. */ @Singleton -public class GetAllowanceTranslator extends AbstractHtsCallTranslator { +public class GetAllowanceTranslator extends AbstractCallTranslator { public static final Function GET_ALLOWANCE = new Function("allowance(address,address,address)", ReturnTypes.RESPONSE_CODE_UINT256); @@ -55,7 +55,7 @@ public boolean matches(@NonNull final HtsCallAttempt attempt) { * {@inheritDoc} */ @Override - public HtsCall callFrom(@NonNull final HtsCallAttempt attempt) { + public Call callFrom(@NonNull final HtsCallAttempt attempt) { if (matchesErcSelector(attempt.selector())) { final var call = GetAllowanceTranslator.ERC_GET_ALLOWANCE.decodeCall(attempt.inputBytes()); return new GetAllowanceCall( diff --git a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hts/associations/AssociationsTranslator.java b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hts/associations/AssociationsTranslator.java index 30a6da31a203..e269843e97fc 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hts/associations/AssociationsTranslator.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hts/associations/AssociationsTranslator.java @@ -21,9 +21,9 @@ import com.hedera.hapi.node.transaction.TransactionBody; import com.hedera.node.app.service.contract.impl.exec.gas.DispatchType; import com.hedera.node.app.service.contract.impl.exec.gas.SystemContractGasCalculator; -import com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.AbstractHtsCallTranslator; +import com.hedera.node.app.service.contract.impl.exec.systemcontracts.common.AbstractCallTranslator; +import com.hedera.node.app.service.contract.impl.exec.systemcontracts.common.Call; import com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.DispatchForResponseCodeHtsCall; -import com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.HtsCall; import com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.HtsCallAttempt; import com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.ReturnTypes; import com.hedera.node.app.service.contract.impl.hevm.HederaWorldUpdater; @@ -34,10 +34,10 @@ /** * Translates associate and dissociate calls to the HTS system contract. There are no special cases for - * these calls, so the returned {@link HtsCall} is simply an instance of {@link DispatchForResponseCodeHtsCall}. + * these calls, so the returned {@link Call} is simply an instance of {@link DispatchForResponseCodeHtsCall}. */ @Singleton -public class AssociationsTranslator extends AbstractHtsCallTranslator { +public class AssociationsTranslator extends AbstractCallTranslator { public static final Function HRC_ASSOCIATE = new Function("associate()", ReturnTypes.INT); public static final Function ASSOCIATE_ONE = new Function("associateToken(address,address)", ReturnTypes.INT_64); public static final Function DISSOCIATE_ONE = new Function("dissociateToken(address,address)", ReturnTypes.INT_64); @@ -67,7 +67,7 @@ public boolean matches(@NonNull final HtsCallAttempt attempt) { * {@inheritDoc} */ @Override - public HtsCall callFrom(@NonNull final HtsCallAttempt attempt) { + public Call callFrom(@NonNull final HtsCallAttempt attempt) { return new DispatchForResponseCodeHtsCall( attempt, matchesHrcSelector(attempt.selector()) ? bodyForHrc(attempt) : bodyForClassic(attempt), diff --git a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hts/balanceof/BalanceOfCall.java b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hts/balanceof/BalanceOfCall.java index 9b7b18e7e624..1ea6bd27ff8f 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hts/balanceof/BalanceOfCall.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hts/balanceof/BalanceOfCall.java @@ -20,7 +20,7 @@ import static com.hedera.hapi.node.base.ResponseCodeEnum.SUCCESS; import static com.hedera.node.app.service.contract.impl.exec.systemcontracts.FullResult.revertResult; import static com.hedera.node.app.service.contract.impl.exec.systemcontracts.FullResult.successResult; -import static com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.HtsCall.PricedResult.gasOnly; +import static com.hedera.node.app.service.contract.impl.exec.systemcontracts.common.Call.PricedResult.gasOnly; import static com.hedera.node.app.service.contract.impl.utils.ConversionUtils.accountNumberForEvmReference; import static java.util.Objects.requireNonNull; diff --git a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hts/balanceof/BalanceOfTranslator.java b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hts/balanceof/BalanceOfTranslator.java index cd765763a419..1a832f41de09 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hts/balanceof/BalanceOfTranslator.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hts/balanceof/BalanceOfTranslator.java @@ -18,7 +18,7 @@ import com.esaulpaugh.headlong.abi.Address; import com.esaulpaugh.headlong.abi.Function; -import com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.AbstractHtsCallTranslator; +import com.hedera.node.app.service.contract.impl.exec.systemcontracts.common.AbstractCallTranslator; import com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.HtsCallAttempt; import com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.ReturnTypes; import edu.umd.cs.findbugs.annotations.NonNull; @@ -30,7 +30,7 @@ * Translates {@code balanceOf} calls to the HTS system contract. */ @Singleton -public class BalanceOfTranslator extends AbstractHtsCallTranslator { +public class BalanceOfTranslator extends AbstractCallTranslator { public static final Function BALANCE_OF = new Function("balanceOf(address)", ReturnTypes.INT); @Inject diff --git a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hts/burn/BurnTranslator.java b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hts/burn/BurnTranslator.java index 4e2468bda90b..bddce2e16ff9 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hts/burn/BurnTranslator.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hts/burn/BurnTranslator.java @@ -24,16 +24,16 @@ import com.hedera.hapi.node.transaction.TransactionBody; import com.hedera.node.app.service.contract.impl.exec.gas.DispatchType; import com.hedera.node.app.service.contract.impl.exec.gas.SystemContractGasCalculator; -import com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.AbstractHtsCallTranslator; +import com.hedera.node.app.service.contract.impl.exec.systemcontracts.common.AbstractCallTranslator; +import com.hedera.node.app.service.contract.impl.exec.systemcontracts.common.Call; import com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.DispatchForResponseCodeHtsCall; -import com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.HtsCall; import com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.HtsCallAttempt; import com.hedera.node.app.service.contract.impl.hevm.HederaWorldUpdater; import edu.umd.cs.findbugs.annotations.NonNull; import java.util.Arrays; import javax.inject.Inject; -public class BurnTranslator extends AbstractHtsCallTranslator { +public class BurnTranslator extends AbstractCallTranslator { public static final Function BURN_TOKEN_V1 = new Function("burnToken(address,uint64,int64[])", INT64_INT64); public static final Function BURN_TOKEN_V2 = new Function("burnToken(address,int64,int64[])", INT64_INT64); @@ -51,7 +51,7 @@ public boolean matches(@NonNull HtsCallAttempt attempt) { } @Override - public HtsCall callFrom(@NonNull HtsCallAttempt attempt) { + public Call callFrom(@NonNull HtsCallAttempt attempt) { final var body = bodyForClassic(attempt); final var isFungibleMint = body.tokenBurnOrThrow().serialNumbers().isEmpty(); return new DispatchForResponseCodeHtsCall( diff --git a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hts/create/ClassicCreatesCall.java b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hts/create/ClassicCreatesCall.java index 6a387c145f1b..73e535fc8e5e 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hts/create/ClassicCreatesCall.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hts/create/ClassicCreatesCall.java @@ -18,7 +18,6 @@ import static com.hedera.hapi.node.base.ResponseCodeEnum.INSUFFICIENT_TX_FEE; import static com.hedera.hapi.node.base.ResponseCodeEnum.INVALID_ACCOUNT_ID; -import static com.hedera.hapi.node.base.ResponseCodeEnum.INVALID_EXPIRATION_TIME; import static com.hedera.hapi.node.base.ResponseCodeEnum.INVALID_TRANSACTION_BODY; import static com.hedera.hapi.node.base.ResponseCodeEnum.MISSING_TOKEN_SYMBOL; import static com.hedera.hapi.node.base.ResponseCodeEnum.OK; @@ -28,8 +27,8 @@ import static com.hedera.node.app.service.contract.impl.exec.systemcontracts.FullResult.haltResult; import static com.hedera.node.app.service.contract.impl.exec.systemcontracts.FullResult.revertResult; import static com.hedera.node.app.service.contract.impl.exec.systemcontracts.FullResult.successResult; -import static com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.HtsCall.PricedResult.gasOnly; -import static com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.HtsCall.PricedResult.gasPlus; +import static com.hedera.node.app.service.contract.impl.exec.systemcontracts.common.Call.PricedResult.gasOnly; +import static com.hedera.node.app.service.contract.impl.exec.systemcontracts.common.Call.PricedResult.gasPlus; import static com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.ReturnTypes.RC_AND_ADDRESS_ENCODER; import static com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.ReturnTypes.ZERO_ADDRESS; import static com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.ReturnTypes.standardized; @@ -47,13 +46,15 @@ import com.hedera.hapi.node.base.ResponseCodeEnum; import com.hedera.hapi.node.base.Timestamp; import com.hedera.hapi.node.base.TransactionID; +import com.hedera.hapi.node.token.TokenCreateTransactionBody; import com.hedera.hapi.node.transaction.TransactionBody; import com.hedera.node.app.service.contract.impl.exec.gas.SystemContractGasCalculator; import com.hedera.node.app.service.contract.impl.exec.scope.ActiveContractVerificationStrategy; import com.hedera.node.app.service.contract.impl.exec.scope.ActiveContractVerificationStrategy.UseTopLevelSigs; import com.hedera.node.app.service.contract.impl.exec.scope.EitherOrVerificationStrategy; +import com.hedera.node.app.service.contract.impl.exec.scope.SpecificCryptoVerificationStrategy; import com.hedera.node.app.service.contract.impl.exec.scope.VerificationStrategy; -import com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.AbstractHtsCall; +import com.hedera.node.app.service.contract.impl.exec.systemcontracts.common.AbstractCall; import com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.AddressIdConverter; import com.hedera.node.app.service.contract.impl.hevm.HederaWorldUpdater; import com.hedera.node.app.service.contract.impl.records.ContractCallRecordBuilder; @@ -66,7 +67,7 @@ import org.hyperledger.besu.datatypes.Wei; import org.hyperledger.besu.evm.frame.MessageFrame; -public class ClassicCreatesCall extends AbstractHtsCall { +public class ClassicCreatesCall extends AbstractCall { /** * The mono-service stipulated gas cost for a token creation (remaining fee is collected by sent value) */ @@ -77,7 +78,6 @@ public class ClassicCreatesCall extends AbstractHtsCall { private final VerificationStrategy verificationStrategy; private final AccountID spenderId; - private long nonGasCost; public ClassicCreatesCall( @NonNull final SystemContractGasCalculator systemContractGasCalculator, @@ -115,7 +115,7 @@ private record LegacyActivation(long contractNum, Bytes pbjAddress, Address besu .build(); final var baseCost = gasCalculator.canonicalPriceInTinybars(syntheticCreateWithId, spenderId); // The non-gas cost is a 20% surcharge on the HAPI TokenCreate price, minus the fee taken as gas - this.nonGasCost = baseCost + (baseCost / 5) - gasCalculator.gasCostInTinybars(FIXED_GAS_COST); + long nonGasCost = baseCost + (baseCost / 5) - gasCalculator.gasCostInTinybars(FIXED_GAS_COST); if (frame.getValue().lessThan(Wei.of(nonGasCost))) { return completionWith( FIXED_GAS_COST, @@ -123,17 +123,19 @@ private record LegacyActivation(long contractNum, Bytes pbjAddress, Address besu RC_AND_ADDRESS_ENCODER.encodeElements((long) INSUFFICIENT_TX_FEE.protoOrdinal(), ZERO_ADDRESS)); } else { operations().collectFee(spenderId, nonGasCost); - // (future) remove after differential testing + // (FUTURE) remove after differential testing, mono-service used the entire + // value in the frame for the non-gas cost even if it was only a portion nonGasCost = frame.getValue().toLong(); } - final var validity = validityOfSynthOp(); + final var op = syntheticCreate.tokenCreationOrThrow(); + final var validity = validityOfSynth(op); if (validity != OK) { return gasOnly(revertResult(validity, FIXED_GAS_COST), validity, true); } // Choose a dispatch verification strategy based on whether the legacy activation address is active - final var dispatchVerificationStrategy = verificationStrategyFor(frame); + final var dispatchVerificationStrategy = verificationStrategyFor(frame, op); final var recordBuilder = systemContractOperations() .dispatch(syntheticCreate, dispatchVerificationStrategy, spenderId, ContractCallRecordBuilder.class); recordBuilder.status(standardized(recordBuilder.status())); @@ -143,7 +145,6 @@ private record LegacyActivation(long contractNum, Bytes pbjAddress, Address besu return gasPlus(revertResult(recordBuilder, FIXED_GAS_COST), status, false, nonGasCost); } else { ByteBuffer encodedOutput; - final var op = syntheticCreate.tokenCreationOrThrow(); final var customFees = op.customFees(); if (op.tokenType() == FUNGIBLE_COMMON) { if (customFees.isEmpty()) { @@ -170,8 +171,7 @@ private record LegacyActivation(long contractNum, Bytes pbjAddress, Address besu } } - private ResponseCodeEnum validityOfSynthOp() { - final var op = syntheticCreate.tokenCreationOrThrow(); + private ResponseCodeEnum validityOfSynth(@NonNull final TokenCreateTransactionBody op) { if (op.symbol().isEmpty()) { return MISSING_TOKEN_SYMBOL; } @@ -179,20 +179,24 @@ private ResponseCodeEnum validityOfSynthOp() { if (treasuryAccount == null) { return INVALID_ACCOUNT_ID; } - if (op.autoRenewAccount() == null) { - return INVALID_EXPIRATION_TIME; - } return OK; } - private VerificationStrategy verificationStrategyFor(@NonNull final MessageFrame frame) { + private VerificationStrategy verificationStrategyFor( + @NonNull final MessageFrame frame, @NonNull final TokenCreateTransactionBody op) { final var legacyActivation = legacyActivationIn(frame); - // Choose a dispatch verification strategy based on whether the legacy - // activation address is active (somewhere on the stack) + // If there is a crypto admin key, we need an either/or strategy to let it + // be activated by a top-level signature with that key + final var baseVerificationStrategy = hasCryptoAdminKey(op) + ? new EitherOrVerificationStrategy( + verificationStrategy, new SpecificCryptoVerificationStrategy(op.adminKeyOrThrow())) + : verificationStrategy; + // And our final dispatch verification strategy must very depending on if + // a legacy activation address is active (somewhere on the stack) return stackIncludesActiveAddress(frame, legacyActivation.besuAddress()) ? new EitherOrVerificationStrategy( - verificationStrategy, + baseVerificationStrategy, new ActiveContractVerificationStrategy( ContractID.newBuilder() .contractNum(legacyActivation.contractNum()) @@ -200,7 +204,12 @@ private VerificationStrategy verificationStrategyFor(@NonNull final MessageFrame legacyActivation.pbjAddress(), false, UseTopLevelSigs.NO)) - : verificationStrategy; + : baseVerificationStrategy; + } + + private boolean hasCryptoAdminKey(@NonNull final TokenCreateTransactionBody op) { + return op.hasAdminKey() + && (op.adminKeyOrThrow().hasEd25519() || op.adminKeyOrThrow().hasEcdsaSecp256k1()); } private LegacyActivation legacyActivationIn(@NonNull final MessageFrame frame) { diff --git a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hts/create/CreateTranslator.java b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hts/create/CreateTranslator.java index dadeadf4ea72..8b06f8c8c2bf 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hts/create/CreateTranslator.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hts/create/CreateTranslator.java @@ -29,14 +29,14 @@ import com.esaulpaugh.headlong.abi.Function; import com.hedera.hapi.node.transaction.TransactionBody; -import com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.AbstractHtsCallTranslator; +import com.hedera.node.app.service.contract.impl.exec.systemcontracts.common.AbstractCallTranslator; import com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.HtsCallAttempt; import edu.umd.cs.findbugs.annotations.NonNull; import edu.umd.cs.findbugs.annotations.Nullable; import java.util.Arrays; import javax.inject.Inject; -public class CreateTranslator extends AbstractHtsCallTranslator { +public class CreateTranslator extends AbstractCallTranslator { public static final Function CREATE_FUNGIBLE_TOKEN_V1 = new Function("createFungibleToken(" + HEDERA_TOKEN_V1 + ",uint,uint)", "(int64,address)"); diff --git a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hts/customfees/TokenCustomFeesCall.java b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hts/customfees/TokenCustomFeesCall.java index 2dacf333d619..cff9a5a2dd9b 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hts/customfees/TokenCustomFeesCall.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hts/customfees/TokenCustomFeesCall.java @@ -18,7 +18,7 @@ import static com.hedera.hapi.node.base.ResponseCodeEnum.SUCCESS; import static com.hedera.node.app.service.contract.impl.exec.systemcontracts.FullResult.successResult; -import static com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.HtsCall.PricedResult.gasOnly; +import static com.hedera.node.app.service.contract.impl.exec.systemcontracts.common.Call.PricedResult.gasOnly; import static com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.TokenTupleUtils.feesTupleFor; import static com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.customfees.TokenCustomFeesTranslator.TOKEN_CUSTOM_FEES; import static java.util.Objects.requireNonNull; diff --git a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hts/customfees/TokenCustomFeesTranslator.java b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hts/customfees/TokenCustomFeesTranslator.java index 3729fbc5cac4..4fb172aeb7c3 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hts/customfees/TokenCustomFeesTranslator.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hts/customfees/TokenCustomFeesTranslator.java @@ -19,15 +19,15 @@ import static com.hedera.node.app.service.contract.impl.utils.ConversionUtils.fromHeadlongAddress; import com.esaulpaugh.headlong.abi.Function; -import com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.AbstractHtsCallTranslator; -import com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.HtsCall; +import com.hedera.node.app.service.contract.impl.exec.systemcontracts.common.AbstractCallTranslator; +import com.hedera.node.app.service.contract.impl.exec.systemcontracts.common.Call; import com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.HtsCallAttempt; import com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.ReturnTypes; import edu.umd.cs.findbugs.annotations.NonNull; import java.util.Arrays; import javax.inject.Inject; -public class TokenCustomFeesTranslator extends AbstractHtsCallTranslator { +public class TokenCustomFeesTranslator extends AbstractCallTranslator { public static final Function TOKEN_CUSTOM_FEES = new Function("getTokenCustomFees(address)", ReturnTypes.RESPONSE_CODE_CUSTOM_FEES); @@ -49,7 +49,7 @@ public boolean matches(@NonNull final HtsCallAttempt attempt) { * {@inheritDoc} */ @Override - public HtsCall callFrom(@NonNull final HtsCallAttempt attempt) { + public Call callFrom(@NonNull final HtsCallAttempt attempt) { final var args = TOKEN_CUSTOM_FEES.decodeCall(attempt.input().toArrayUnsafe()); final var token = attempt.linkedToken(fromHeadlongAddress(args.get(0))); return new TokenCustomFeesCall( diff --git a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hts/decimals/DecimalsCall.java b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hts/decimals/DecimalsCall.java index 5ba390cc22e6..cfc88eee2010 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hts/decimals/DecimalsCall.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hts/decimals/DecimalsCall.java @@ -20,7 +20,7 @@ import static com.hedera.hapi.node.base.ResponseCodeEnum.SUCCESS; import static com.hedera.node.app.service.contract.impl.exec.systemcontracts.FullResult.haltResult; import static com.hedera.node.app.service.contract.impl.exec.systemcontracts.FullResult.successResult; -import static com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.HtsCall.PricedResult.gasOnly; +import static com.hedera.node.app.service.contract.impl.exec.systemcontracts.common.Call.PricedResult.gasOnly; import com.hedera.hapi.node.base.TokenType; import com.hedera.hapi.node.state.token.Token; diff --git a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hts/decimals/DecimalsTranslator.java b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hts/decimals/DecimalsTranslator.java index faf6e0e0c4a6..515f20673f1b 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hts/decimals/DecimalsTranslator.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hts/decimals/DecimalsTranslator.java @@ -17,8 +17,8 @@ package com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.decimals; import com.esaulpaugh.headlong.abi.Function; -import com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.AbstractHtsCallTranslator; -import com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.HtsCall; +import com.hedera.node.app.service.contract.impl.exec.systemcontracts.common.AbstractCallTranslator; +import com.hedera.node.app.service.contract.impl.exec.systemcontracts.common.Call; import com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.HtsCallAttempt; import com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.ReturnTypes; import edu.umd.cs.findbugs.annotations.NonNull; @@ -30,7 +30,7 @@ * Translates {@code decimals} calls to the HTS system contract. */ @Singleton -public class DecimalsTranslator extends AbstractHtsCallTranslator { +public class DecimalsTranslator extends AbstractCallTranslator { public static final Function DECIMALS = new Function("decimals()", ReturnTypes.BYTE); @Inject @@ -50,7 +50,7 @@ public boolean matches(@NonNull final HtsCallAttempt attempt) { * {@inheritDoc} */ @Override - public HtsCall callFrom(@NonNull final HtsCallAttempt attempt) { + public Call callFrom(@NonNull final HtsCallAttempt attempt) { return new DecimalsCall(attempt.enhancement(), attempt.systemContractGasCalculator(), attempt.redirectToken()); } } diff --git a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hts/defaultfreezestatus/DefaultFreezeStatusCall.java b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hts/defaultfreezestatus/DefaultFreezeStatusCall.java index c1a477b8530e..cda3ebee797c 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hts/defaultfreezestatus/DefaultFreezeStatusCall.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hts/defaultfreezestatus/DefaultFreezeStatusCall.java @@ -19,7 +19,7 @@ import static com.hedera.hapi.node.base.ResponseCodeEnum.SUCCESS; import static com.hedera.node.app.service.contract.impl.exec.systemcontracts.FullResult.revertResult; import static com.hedera.node.app.service.contract.impl.exec.systemcontracts.FullResult.successResult; -import static com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.HtsCall.PricedResult.gasOnly; +import static com.hedera.node.app.service.contract.impl.exec.systemcontracts.common.Call.PricedResult.gasOnly; import static com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.defaultfreezestatus.DefaultFreezeStatusTranslator.DEFAULT_FREEZE_STATUS; import static java.util.Objects.requireNonNull; diff --git a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hts/defaultfreezestatus/DefaultFreezeStatusTranslator.java b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hts/defaultfreezestatus/DefaultFreezeStatusTranslator.java index f4c4f24c17af..b4c9bf9ff813 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hts/defaultfreezestatus/DefaultFreezeStatusTranslator.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hts/defaultfreezestatus/DefaultFreezeStatusTranslator.java @@ -19,15 +19,15 @@ import static com.hedera.node.app.service.contract.impl.utils.ConversionUtils.fromHeadlongAddress; import com.esaulpaugh.headlong.abi.Function; -import com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.AbstractHtsCallTranslator; -import com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.HtsCall; +import com.hedera.node.app.service.contract.impl.exec.systemcontracts.common.AbstractCallTranslator; +import com.hedera.node.app.service.contract.impl.exec.systemcontracts.common.Call; import com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.HtsCallAttempt; import com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.ReturnTypes; import edu.umd.cs.findbugs.annotations.NonNull; import java.util.Arrays; import javax.inject.Inject; -public class DefaultFreezeStatusTranslator extends AbstractHtsCallTranslator { +public class DefaultFreezeStatusTranslator extends AbstractCallTranslator { public static final Function DEFAULT_FREEZE_STATUS = new Function("getTokenDefaultFreezeStatus(address)", ReturnTypes.RESPONSE_CODE_BOOL); @@ -49,7 +49,7 @@ public boolean matches(@NonNull final HtsCallAttempt attempt) { * {@inheritDoc} */ @Override - public HtsCall callFrom(@NonNull final HtsCallAttempt attempt) { + public Call callFrom(@NonNull final HtsCallAttempt attempt) { final var args = DEFAULT_FREEZE_STATUS.decodeCall(attempt.input().toArrayUnsafe()); final var token = attempt.linkedToken(fromHeadlongAddress(args.get(0))); return new DefaultFreezeStatusCall( diff --git a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hts/defaultkycstatus/DefaultKycStatusCall.java b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hts/defaultkycstatus/DefaultKycStatusCall.java index f877334299b3..e5ebfc3146e0 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hts/defaultkycstatus/DefaultKycStatusCall.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hts/defaultkycstatus/DefaultKycStatusCall.java @@ -19,7 +19,7 @@ import static com.hedera.hapi.node.base.ResponseCodeEnum.SUCCESS; import static com.hedera.node.app.service.contract.impl.exec.systemcontracts.FullResult.revertResult; import static com.hedera.node.app.service.contract.impl.exec.systemcontracts.FullResult.successResult; -import static com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.HtsCall.PricedResult.gasOnly; +import static com.hedera.node.app.service.contract.impl.exec.systemcontracts.common.Call.PricedResult.gasOnly; import static com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.defaultkycstatus.DefaultKycStatusTranslator.DEFAULT_KYC_STATUS; import static java.util.Objects.requireNonNull; diff --git a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hts/defaultkycstatus/DefaultKycStatusTranslator.java b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hts/defaultkycstatus/DefaultKycStatusTranslator.java index 32eb6b007156..a6ac6a6f7e1f 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hts/defaultkycstatus/DefaultKycStatusTranslator.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hts/defaultkycstatus/DefaultKycStatusTranslator.java @@ -19,15 +19,15 @@ import static com.hedera.node.app.service.contract.impl.utils.ConversionUtils.fromHeadlongAddress; import com.esaulpaugh.headlong.abi.Function; -import com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.AbstractHtsCallTranslator; -import com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.HtsCall; +import com.hedera.node.app.service.contract.impl.exec.systemcontracts.common.AbstractCallTranslator; +import com.hedera.node.app.service.contract.impl.exec.systemcontracts.common.Call; import com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.HtsCallAttempt; import com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.ReturnTypes; import edu.umd.cs.findbugs.annotations.NonNull; import java.util.Arrays; import javax.inject.Inject; -public class DefaultKycStatusTranslator extends AbstractHtsCallTranslator { +public class DefaultKycStatusTranslator extends AbstractCallTranslator { public static final Function DEFAULT_KYC_STATUS = new Function("getTokenDefaultKycStatus(address)", ReturnTypes.RESPONSE_CODE_BOOL); @@ -49,7 +49,7 @@ public boolean matches(@NonNull final HtsCallAttempt attempt) { * {@inheritDoc} */ @Override - public HtsCall callFrom(@NonNull final HtsCallAttempt attempt) { + public Call callFrom(@NonNull final HtsCallAttempt attempt) { final var args = DEFAULT_KYC_STATUS.decodeCall(attempt.input().toArrayUnsafe()); final var token = attempt.linkedToken(fromHeadlongAddress(args.get(0))); return new DefaultKycStatusCall( diff --git a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hts/delete/DeleteTranslator.java b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hts/delete/DeleteTranslator.java index 85e65abd63ef..d8e37c7a2be8 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hts/delete/DeleteTranslator.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hts/delete/DeleteTranslator.java @@ -22,9 +22,9 @@ import com.hedera.hapi.node.transaction.TransactionBody; import com.hedera.node.app.service.contract.impl.exec.gas.DispatchType; import com.hedera.node.app.service.contract.impl.exec.gas.SystemContractGasCalculator; -import com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.AbstractHtsCallTranslator; +import com.hedera.node.app.service.contract.impl.exec.systemcontracts.common.AbstractCallTranslator; +import com.hedera.node.app.service.contract.impl.exec.systemcontracts.common.Call; import com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.DispatchForResponseCodeHtsCall; -import com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.HtsCall; import com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.HtsCallAttempt; import com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.ReturnTypes; import com.hedera.node.app.service.contract.impl.hevm.HederaWorldUpdater; @@ -33,7 +33,7 @@ import java.util.Arrays; import javax.inject.Inject; -public class DeleteTranslator extends AbstractHtsCallTranslator { +public class DeleteTranslator extends AbstractCallTranslator { public static final Function DELETE_TOKEN = new Function("deleteToken(address)", ReturnTypes.INT); @Inject @@ -47,7 +47,7 @@ public boolean matches(@NonNull HtsCallAttempt attempt) { } @Override - public HtsCall callFrom(@NonNull HtsCallAttempt attempt) { + public Call callFrom(@NonNull HtsCallAttempt attempt) { return new DispatchForResponseCodeHtsCall(attempt, bodyForClassic(attempt), DeleteTranslator::gasRequirement); } diff --git a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hts/freeze/FreezeUnfreezeTranslator.java b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hts/freeze/FreezeUnfreezeTranslator.java index 6400acb7e197..a6e61f877e80 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hts/freeze/FreezeUnfreezeTranslator.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hts/freeze/FreezeUnfreezeTranslator.java @@ -23,9 +23,9 @@ import com.hedera.hapi.node.transaction.TransactionBody; import com.hedera.node.app.service.contract.impl.exec.gas.DispatchType; import com.hedera.node.app.service.contract.impl.exec.gas.SystemContractGasCalculator; -import com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.AbstractHtsCallTranslator; +import com.hedera.node.app.service.contract.impl.exec.systemcontracts.common.AbstractCallTranslator; +import com.hedera.node.app.service.contract.impl.exec.systemcontracts.common.Call; import com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.DispatchForResponseCodeHtsCall; -import com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.HtsCall; import com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.HtsCallAttempt; import com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.ReturnTypes; import com.hedera.node.app.service.contract.impl.hevm.HederaWorldUpdater; @@ -38,7 +38,7 @@ * Translates {@code freeze()}, {@code unfreeze()} calls to the HTS system contract. */ @Singleton -public class FreezeUnfreezeTranslator extends AbstractHtsCallTranslator { +public class FreezeUnfreezeTranslator extends AbstractCallTranslator { public static final Function FREEZE = new Function("freezeToken(address,address)", ReturnTypes.INT_64); public static final Function UNFREEZE = new Function("unfreezeToken(address,address)", ReturnTypes.INT_64); private final FreezeUnfreezeDecoder decoder; @@ -60,7 +60,7 @@ public boolean matches(@NonNull final HtsCallAttempt attempt) { * {@inheritDoc} */ @Override - public HtsCall callFrom(@NonNull final HtsCallAttempt attempt) { + public Call callFrom(@NonNull final HtsCallAttempt attempt) { return new DispatchForResponseCodeHtsCall( attempt, bodyForClassic(attempt), diff --git a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hts/fungibletokeninfo/FungibleTokenInfoCall.java b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hts/fungibletokeninfo/FungibleTokenInfoCall.java index 3c610439ec86..cbac41f920a2 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hts/fungibletokeninfo/FungibleTokenInfoCall.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hts/fungibletokeninfo/FungibleTokenInfoCall.java @@ -19,7 +19,7 @@ import static com.hedera.hapi.node.base.ResponseCodeEnum.SUCCESS; import static com.hedera.node.app.service.contract.impl.exec.systemcontracts.FullResult.revertResult; import static com.hedera.node.app.service.contract.impl.exec.systemcontracts.FullResult.successResult; -import static com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.HtsCall.PricedResult.gasOnly; +import static com.hedera.node.app.service.contract.impl.exec.systemcontracts.common.Call.PricedResult.gasOnly; import static com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.TokenTupleUtils.fungibleTokenInfoTupleFor; import static com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.fungibletokeninfo.FungibleTokenInfoTranslator.FUNGIBLE_TOKEN_INFO; import static java.util.Objects.requireNonNull; diff --git a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hts/fungibletokeninfo/FungibleTokenInfoTranslator.java b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hts/fungibletokeninfo/FungibleTokenInfoTranslator.java index 3ea930b43cd4..0281454fa6a3 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hts/fungibletokeninfo/FungibleTokenInfoTranslator.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hts/fungibletokeninfo/FungibleTokenInfoTranslator.java @@ -20,15 +20,15 @@ import static java.util.Objects.requireNonNull; import com.esaulpaugh.headlong.abi.Function; -import com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.AbstractHtsCallTranslator; -import com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.HtsCall; +import com.hedera.node.app.service.contract.impl.exec.systemcontracts.common.AbstractCallTranslator; +import com.hedera.node.app.service.contract.impl.exec.systemcontracts.common.Call; import com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.HtsCallAttempt; import com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.ReturnTypes; import edu.umd.cs.findbugs.annotations.NonNull; import java.util.Arrays; import javax.inject.Inject; -public class FungibleTokenInfoTranslator extends AbstractHtsCallTranslator { +public class FungibleTokenInfoTranslator extends AbstractCallTranslator { public static final Function FUNGIBLE_TOKEN_INFO = new Function("getFungibleTokenInfo(address)", ReturnTypes.RESPONSE_CODE_FUNGIBLE_TOKEN_INFO); @@ -51,7 +51,7 @@ public boolean matches(@NonNull final HtsCallAttempt attempt) { * {@inheritDoc} */ @Override - public HtsCall callFrom(@NonNull final HtsCallAttempt attempt) { + public Call callFrom(@NonNull final HtsCallAttempt attempt) { requireNonNull(attempt); final var args = FUNGIBLE_TOKEN_INFO.decodeCall(attempt.input().toArrayUnsafe()); final var token = attempt.linkedToken(fromHeadlongAddress(args.get(0))); diff --git a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hts/getapproved/GetApprovedCall.java b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hts/getapproved/GetApprovedCall.java index c00e3d0c4b14..7a95572cc169 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hts/getapproved/GetApprovedCall.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hts/getapproved/GetApprovedCall.java @@ -21,7 +21,7 @@ import static com.hedera.hapi.node.base.ResponseCodeEnum.SUCCESS; import static com.hedera.node.app.service.contract.impl.exec.systemcontracts.FullResult.revertResult; import static com.hedera.node.app.service.contract.impl.exec.systemcontracts.FullResult.successResult; -import static com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.HtsCall.PricedResult.gasOnly; +import static com.hedera.node.app.service.contract.impl.exec.systemcontracts.common.Call.PricedResult.gasOnly; import static com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.getapproved.GetApprovedTranslator.ERC_GET_APPROVED; import static com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.getapproved.GetApprovedTranslator.HAPI_GET_APPROVED; import static com.hedera.node.app.service.contract.impl.utils.ConversionUtils.asHeadlongAddress; diff --git a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hts/getapproved/GetApprovedTranslator.java b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hts/getapproved/GetApprovedTranslator.java index ee27461a5bbb..de075ad44df8 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hts/getapproved/GetApprovedTranslator.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hts/getapproved/GetApprovedTranslator.java @@ -20,7 +20,7 @@ import static com.hedera.node.app.service.contract.impl.utils.ConversionUtils.fromHeadlongAddress; import com.esaulpaugh.headlong.abi.Function; -import com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.AbstractHtsCallTranslator; +import com.hedera.node.app.service.contract.impl.exec.systemcontracts.common.AbstractCallTranslator; import com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.HtsCallAttempt; import com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.ReturnTypes; import edu.umd.cs.findbugs.annotations.NonNull; @@ -29,7 +29,7 @@ import javax.inject.Singleton; @Singleton -public class GetApprovedTranslator extends AbstractHtsCallTranslator { +public class GetApprovedTranslator extends AbstractCallTranslator { public static final Function HAPI_GET_APPROVED = new Function("getApproved(address,uint256)", "(int32,address)"); public static final Function ERC_GET_APPROVED = new Function("getApproved(uint256)", ReturnTypes.ADDRESS); diff --git a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hts/grantapproval/AbstractGrantApprovalCall.java b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hts/grantapproval/AbstractGrantApprovalCall.java index de4ef4675031..c1df4b3e9614 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hts/grantapproval/AbstractGrantApprovalCall.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hts/grantapproval/AbstractGrantApprovalCall.java @@ -31,13 +31,13 @@ import com.hedera.hapi.node.transaction.TransactionBody; import com.hedera.node.app.service.contract.impl.exec.gas.SystemContractGasCalculator; import com.hedera.node.app.service.contract.impl.exec.scope.VerificationStrategy; -import com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.AbstractHtsCall; +import com.hedera.node.app.service.contract.impl.exec.systemcontracts.common.AbstractCall; import com.hedera.node.app.service.contract.impl.hevm.HederaWorldUpdater.Enhancement; import com.hedera.node.app.service.contract.impl.records.ContractCallRecordBuilder; import edu.umd.cs.findbugs.annotations.NonNull; import edu.umd.cs.findbugs.annotations.Nullable; -public abstract class AbstractGrantApprovalCall extends AbstractHtsCall { +public abstract class AbstractGrantApprovalCall extends AbstractCall { protected final VerificationStrategy verificationStrategy; protected final AccountID senderId; protected final TokenID tokenId; diff --git a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hts/grantapproval/ClassicGrantApprovalCall.java b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hts/grantapproval/ClassicGrantApprovalCall.java index 293c7f2a17b1..4466fa167c3e 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hts/grantapproval/ClassicGrantApprovalCall.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hts/grantapproval/ClassicGrantApprovalCall.java @@ -19,7 +19,7 @@ import static com.hedera.hapi.node.base.ResponseCodeEnum.INVALID_TOKEN_ID; import static com.hedera.hapi.node.base.ResponseCodeEnum.SUCCESS; import static com.hedera.node.app.service.contract.impl.exec.systemcontracts.FullResult.successResult; -import static com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.HtsCall.PricedResult.gasOnly; +import static com.hedera.node.app.service.contract.impl.exec.systemcontracts.common.Call.PricedResult.gasOnly; import static com.hedera.node.app.service.contract.impl.utils.ConversionUtils.asLongZeroAddress; import com.hedera.hapi.node.base.AccountID; diff --git a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hts/grantapproval/ERCGrantApprovalCall.java b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hts/grantapproval/ERCGrantApprovalCall.java index 272575fe71a3..fc698cfffe76 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hts/grantapproval/ERCGrantApprovalCall.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hts/grantapproval/ERCGrantApprovalCall.java @@ -20,7 +20,7 @@ import static com.hedera.hapi.node.base.ResponseCodeEnum.SUCCESS; import static com.hedera.node.app.service.contract.impl.exec.systemcontracts.FullResult.revertResult; import static com.hedera.node.app.service.contract.impl.exec.systemcontracts.FullResult.successResult; -import static com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.HtsCall.PricedResult.gasOnly; +import static com.hedera.node.app.service.contract.impl.exec.systemcontracts.common.Call.PricedResult.gasOnly; import com.hedera.hapi.node.base.AccountID; import com.hedera.hapi.node.base.TokenID; diff --git a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hts/grantapproval/GrantApprovalTranslator.java b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hts/grantapproval/GrantApprovalTranslator.java index 2b3bdd840721..c560da2cbb26 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hts/grantapproval/GrantApprovalTranslator.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hts/grantapproval/GrantApprovalTranslator.java @@ -25,9 +25,9 @@ import com.hedera.hapi.node.transaction.TransactionBody; import com.hedera.node.app.service.contract.impl.exec.gas.DispatchType; import com.hedera.node.app.service.contract.impl.exec.gas.SystemContractGasCalculator; -import com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.AbstractHtsCallTranslator; +import com.hedera.node.app.service.contract.impl.exec.systemcontracts.common.AbstractCallTranslator; +import com.hedera.node.app.service.contract.impl.exec.systemcontracts.common.Call; import com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.DispatchForResponseCodeHtsCall; -import com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.HtsCall; import com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.HtsCallAttempt; import com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.ReturnTypes; import com.hedera.node.app.service.contract.impl.hevm.HederaWorldUpdater; @@ -42,7 +42,7 @@ * Translates {@code approve}, {@code approveNFT} calls to the HTS system contract. */ @Singleton -public class GrantApprovalTranslator extends AbstractHtsCallTranslator { +public class GrantApprovalTranslator extends AbstractCallTranslator { public static final Function ERC_GRANT_APPROVAL = new Function("approve(address,uint256)", ReturnTypes.BOOL); public static final Function ERC_GRANT_APPROVAL_NFT = new Function("approve(address,uint256)"); @@ -68,7 +68,7 @@ public boolean matches(@NonNull final HtsCallAttempt attempt) { * {@inheritDoc} */ @Override - public HtsCall callFrom(@NonNull final HtsCallAttempt attempt) { + public Call callFrom(@NonNull final HtsCallAttempt attempt) { if (matchesErcSelector(attempt.selector())) { return bodyForErc(attempt); } else if (matchesClassicSelector(attempt.selector())) { diff --git a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hts/grantrevokekyc/GrantRevokeKycTranslator.java b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hts/grantrevokekyc/GrantRevokeKycTranslator.java index ad2ffd12f905..a7a500e1429f 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hts/grantrevokekyc/GrantRevokeKycTranslator.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hts/grantrevokekyc/GrantRevokeKycTranslator.java @@ -23,6 +23,8 @@ import com.hedera.hapi.node.transaction.TransactionBody; import com.hedera.node.app.service.contract.impl.exec.gas.DispatchType; import com.hedera.node.app.service.contract.impl.exec.gas.SystemContractGasCalculator; +import com.hedera.node.app.service.contract.impl.exec.systemcontracts.common.AbstractCallTranslator; +import com.hedera.node.app.service.contract.impl.exec.systemcontracts.common.Call; import com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.*; import com.hedera.node.app.service.contract.impl.hevm.HederaWorldUpdater; import edu.umd.cs.findbugs.annotations.NonNull; @@ -32,10 +34,10 @@ /** * Translates grantKyc and revokeKyc calls to the HTS system contract. There are no special cases for - * these calls, so the returned {@link HtsCall} is simply an instance of {@link DispatchForResponseCodeHtsCall}. + * these calls, so the returned {@link Call} is simply an instance of {@link DispatchForResponseCodeHtsCall}. */ @Singleton -public class GrantRevokeKycTranslator extends AbstractHtsCallTranslator { +public class GrantRevokeKycTranslator extends AbstractCallTranslator { public static final Function GRANT_KYC = new Function("grantTokenKyc(address,address)", ReturnTypes.INT_64); public static final Function REVOKE_KYC = new Function("revokeTokenKyc(address,address)", ReturnTypes.INT_64); @@ -58,7 +60,7 @@ public boolean matches(@NonNull HtsCallAttempt attempt) { * {@inheritDoc} */ @Override - public HtsCall callFrom(@NonNull HtsCallAttempt attempt) { + public Call callFrom(@NonNull HtsCallAttempt attempt) { return new DispatchForResponseCodeHtsCall( attempt, bodyForClassic(attempt), diff --git a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hts/isapprovedforall/IsApprovedForAllCall.java b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hts/isapprovedforall/IsApprovedForAllCall.java index 573646edd4de..47711bec608b 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hts/isapprovedforall/IsApprovedForAllCall.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hts/isapprovedforall/IsApprovedForAllCall.java @@ -20,7 +20,7 @@ import static com.hedera.hapi.node.base.ResponseCodeEnum.SUCCESS; import static com.hedera.node.app.service.contract.impl.exec.systemcontracts.FullResult.revertResult; import static com.hedera.node.app.service.contract.impl.exec.systemcontracts.FullResult.successResult; -import static com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.HtsCall.PricedResult.gasOnly; +import static com.hedera.node.app.service.contract.impl.exec.systemcontracts.common.Call.PricedResult.gasOnly; import static com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.isapprovedforall.IsApprovedForAllTranslator.CLASSIC_IS_APPROVED_FOR_ALL; import static com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.isapprovedforall.IsApprovedForAllTranslator.ERC_IS_APPROVED_FOR_ALL; import static com.hedera.node.app.service.contract.impl.utils.ConversionUtils.accountNumberForEvmReference; diff --git a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hts/isapprovedforall/IsApprovedForAllTranslator.java b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hts/isapprovedforall/IsApprovedForAllTranslator.java index b422a1d133bb..93ede35c3aa0 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hts/isapprovedforall/IsApprovedForAllTranslator.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hts/isapprovedforall/IsApprovedForAllTranslator.java @@ -19,7 +19,7 @@ import static com.hedera.node.app.service.contract.impl.utils.ConversionUtils.fromHeadlongAddress; import com.esaulpaugh.headlong.abi.Function; -import com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.AbstractHtsCallTranslator; +import com.hedera.node.app.service.contract.impl.exec.systemcontracts.common.AbstractCallTranslator; import com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.HtsCallAttempt; import edu.umd.cs.findbugs.annotations.NonNull; import java.util.Arrays; @@ -30,7 +30,7 @@ * Translates {@code isApprovedForAll} calls to the HTS system contract. */ @Singleton -public class IsApprovedForAllTranslator extends AbstractHtsCallTranslator { +public class IsApprovedForAllTranslator extends AbstractCallTranslator { public static final Function CLASSIC_IS_APPROVED_FOR_ALL = new Function("isApprovedForAll(address,address,address)", "(int64,bool)"); public static final Function ERC_IS_APPROVED_FOR_ALL = new Function("isApprovedForAll(address,address)", "(bool)"); diff --git a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hts/isfrozen/IsFrozenCall.java b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hts/isfrozen/IsFrozenCall.java index 7b6be6d9d58f..32957398a9a6 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hts/isfrozen/IsFrozenCall.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hts/isfrozen/IsFrozenCall.java @@ -20,7 +20,7 @@ import static com.hedera.hapi.node.base.ResponseCodeEnum.SUCCESS; import static com.hedera.node.app.service.contract.impl.exec.systemcontracts.FullResult.revertResult; import static com.hedera.node.app.service.contract.impl.exec.systemcontracts.FullResult.successResult; -import static com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.HtsCall.PricedResult.gasOnly; +import static com.hedera.node.app.service.contract.impl.exec.systemcontracts.common.Call.PricedResult.gasOnly; import static com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.defaultfreezestatus.DefaultFreezeStatusTranslator.DEFAULT_FREEZE_STATUS; import static com.hedera.node.app.service.contract.impl.utils.ConversionUtils.accountNumberForEvmReference; import static java.util.Objects.requireNonNull; diff --git a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hts/isfrozen/IsFrozenTranslator.java b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hts/isfrozen/IsFrozenTranslator.java index 79ca9fb440a1..4baf1101eddd 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hts/isfrozen/IsFrozenTranslator.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hts/isfrozen/IsFrozenTranslator.java @@ -19,15 +19,15 @@ import static com.hedera.node.app.service.contract.impl.utils.ConversionUtils.fromHeadlongAddress; import com.esaulpaugh.headlong.abi.Function; -import com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.AbstractHtsCallTranslator; -import com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.HtsCall; +import com.hedera.node.app.service.contract.impl.exec.systemcontracts.common.AbstractCallTranslator; +import com.hedera.node.app.service.contract.impl.exec.systemcontracts.common.Call; import com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.HtsCallAttempt; import com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.ReturnTypes; import edu.umd.cs.findbugs.annotations.NonNull; import java.util.Arrays; import javax.inject.Inject; -public class IsFrozenTranslator extends AbstractHtsCallTranslator { +public class IsFrozenTranslator extends AbstractCallTranslator { public static final Function IS_FROZEN = new Function("isFrozen(address,address)", ReturnTypes.RESPONSE_CODE_BOOL); @@ -48,7 +48,7 @@ public boolean matches(@NonNull final HtsCallAttempt attempt) { * {@inheritDoc} */ @Override - public HtsCall callFrom(@NonNull final HtsCallAttempt attempt) { + public Call callFrom(@NonNull final HtsCallAttempt attempt) { final var args = IS_FROZEN.decodeCall(attempt.input().toArrayUnsafe()); final var token = attempt.linkedToken(fromHeadlongAddress(args.get(0))); return new IsFrozenCall( diff --git a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hts/iskyc/IsKycCall.java b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hts/iskyc/IsKycCall.java index 1d3859f59d1f..7471ecfd5fe3 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hts/iskyc/IsKycCall.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hts/iskyc/IsKycCall.java @@ -20,7 +20,7 @@ import static com.hedera.hapi.node.base.ResponseCodeEnum.SUCCESS; import static com.hedera.node.app.service.contract.impl.exec.systemcontracts.FullResult.revertResult; import static com.hedera.node.app.service.contract.impl.exec.systemcontracts.FullResult.successResult; -import static com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.HtsCall.PricedResult.gasOnly; +import static com.hedera.node.app.service.contract.impl.exec.systemcontracts.common.Call.PricedResult.gasOnly; import static com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.iskyc.IsKycTranslator.IS_KYC; import static com.hedera.node.app.service.contract.impl.utils.ConversionUtils.accountNumberForEvmReference; import static java.util.Objects.requireNonNull; diff --git a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hts/iskyc/IsKycTranslator.java b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hts/iskyc/IsKycTranslator.java index 2f8d23c2e4a8..bfe7f6c24d2f 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hts/iskyc/IsKycTranslator.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hts/iskyc/IsKycTranslator.java @@ -19,15 +19,15 @@ import static com.hedera.node.app.service.contract.impl.utils.ConversionUtils.fromHeadlongAddress; import com.esaulpaugh.headlong.abi.Function; -import com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.AbstractHtsCallTranslator; -import com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.HtsCall; +import com.hedera.node.app.service.contract.impl.exec.systemcontracts.common.AbstractCallTranslator; +import com.hedera.node.app.service.contract.impl.exec.systemcontracts.common.Call; import com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.HtsCallAttempt; import com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.ReturnTypes; import edu.umd.cs.findbugs.annotations.NonNull; import java.util.Arrays; import javax.inject.Inject; -public class IsKycTranslator extends AbstractHtsCallTranslator { +public class IsKycTranslator extends AbstractCallTranslator { public static final Function IS_KYC = new Function("isKyc(address,address)", ReturnTypes.RESPONSE_CODE_BOOL); @@ -48,7 +48,7 @@ public boolean matches(@NonNull final HtsCallAttempt attempt) { * {@inheritDoc} */ @Override - public HtsCall callFrom(@NonNull final HtsCallAttempt attempt) { + public Call callFrom(@NonNull final HtsCallAttempt attempt) { final var args = IS_KYC.decodeCall(attempt.input().toArrayUnsafe()); final var token = attempt.linkedToken(fromHeadlongAddress(args.get(0))); return new IsKycCall( diff --git a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hts/istoken/IsTokenCall.java b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hts/istoken/IsTokenCall.java index 33bf33738813..22bc29408975 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hts/istoken/IsTokenCall.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hts/istoken/IsTokenCall.java @@ -18,20 +18,20 @@ import static com.hedera.hapi.node.base.ResponseCodeEnum.SUCCESS; import static com.hedera.node.app.service.contract.impl.exec.systemcontracts.FullResult.successResult; -import static com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.HtsCall.PricedResult.gasOnly; +import static com.hedera.node.app.service.contract.impl.exec.systemcontracts.common.Call.PricedResult.gasOnly; import static com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.istoken.IsTokenTranslator.IS_TOKEN; import com.hedera.hapi.node.base.ResponseCodeEnum; import com.hedera.hapi.node.state.token.Token; import com.hedera.node.app.service.contract.impl.exec.gas.SystemContractGasCalculator; import com.hedera.node.app.service.contract.impl.exec.systemcontracts.FullResult; -import com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.AbstractHtsCall; +import com.hedera.node.app.service.contract.impl.exec.systemcontracts.common.AbstractCall; import com.hedera.node.app.service.contract.impl.hevm.HederaWorldUpdater; import edu.umd.cs.findbugs.annotations.NonNull; import edu.umd.cs.findbugs.annotations.Nullable; import org.hyperledger.besu.evm.frame.MessageFrame; -public class IsTokenCall extends AbstractHtsCall { +public class IsTokenCall extends AbstractCall { private final boolean isStaticCall; @Nullable diff --git a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hts/istoken/IsTokenTranslator.java b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hts/istoken/IsTokenTranslator.java index c523ddd94582..cc965935c473 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hts/istoken/IsTokenTranslator.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hts/istoken/IsTokenTranslator.java @@ -19,15 +19,15 @@ import static com.hedera.node.app.service.contract.impl.utils.ConversionUtils.fromHeadlongAddress; import com.esaulpaugh.headlong.abi.Function; -import com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.AbstractHtsCallTranslator; -import com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.HtsCall; +import com.hedera.node.app.service.contract.impl.exec.systemcontracts.common.AbstractCallTranslator; +import com.hedera.node.app.service.contract.impl.exec.systemcontracts.common.Call; import com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.HtsCallAttempt; import com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.ReturnTypes; import edu.umd.cs.findbugs.annotations.NonNull; import java.util.Arrays; import javax.inject.Inject; -public class IsTokenTranslator extends AbstractHtsCallTranslator { +public class IsTokenTranslator extends AbstractCallTranslator { public static final Function IS_TOKEN = new Function("isToken(address)", ReturnTypes.RESPONSE_CODE_BOOL); @@ -48,7 +48,7 @@ public boolean matches(@NonNull final HtsCallAttempt attempt) { * {@inheritDoc} */ @Override - public HtsCall callFrom(@NonNull final HtsCallAttempt attempt) { + public Call callFrom(@NonNull final HtsCallAttempt attempt) { final var args = IS_TOKEN.decodeCall(attempt.input().toArrayUnsafe()); final var token = attempt.linkedToken(fromHeadlongAddress(args.get(0))); return new IsTokenCall( diff --git a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hts/mint/MintTranslator.java b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hts/mint/MintTranslator.java index c41a77248220..2c20d4ea6c23 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hts/mint/MintTranslator.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hts/mint/MintTranslator.java @@ -23,9 +23,9 @@ import com.hedera.hapi.node.transaction.TransactionBody; import com.hedera.node.app.service.contract.impl.exec.gas.DispatchType; import com.hedera.node.app.service.contract.impl.exec.gas.SystemContractGasCalculator; -import com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.AbstractHtsCallTranslator; +import com.hedera.node.app.service.contract.impl.exec.systemcontracts.common.AbstractCallTranslator; +import com.hedera.node.app.service.contract.impl.exec.systemcontracts.common.Call; import com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.DispatchForResponseCodeHtsCall; -import com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.HtsCall; import com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.HtsCallAttempt; import com.hedera.node.app.service.contract.impl.hevm.HederaWorldUpdater; import edu.umd.cs.findbugs.annotations.NonNull; @@ -37,7 +37,7 @@ * Translates {@code mintToken()} calls to the HTS system contract. */ @Singleton -public class MintTranslator extends AbstractHtsCallTranslator { +public class MintTranslator extends AbstractCallTranslator { public static final Function MINT = new Function("mintToken(address,uint64,bytes[])", "(int64,int64,int64[])"); public static final Function MINT_V2 = new Function("mintToken(address,int64,bytes[])", "(int64,int64,int64[])"); private final MintDecoder decoder; @@ -57,7 +57,7 @@ public boolean matches(@NonNull final HtsCallAttempt attempt) { } @Override - public HtsCall callFrom(@NonNull final HtsCallAttempt attempt) { + public Call callFrom(@NonNull final HtsCallAttempt attempt) { final var body = bodyForClassic(attempt); final var isFungibleMint = body.tokenMintOrThrow().metadata().isEmpty(); return new DispatchForResponseCodeHtsCall( diff --git a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hts/name/NameCall.java b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hts/name/NameCall.java index 1618352752aa..c87ff3753bc2 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hts/name/NameCall.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hts/name/NameCall.java @@ -18,7 +18,7 @@ import static com.hedera.hapi.node.base.ResponseCodeEnum.SUCCESS; import static com.hedera.node.app.service.contract.impl.exec.systemcontracts.FullResult.successResult; -import static com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.HtsCall.PricedResult.gasOnly; +import static com.hedera.node.app.service.contract.impl.exec.systemcontracts.common.Call.PricedResult.gasOnly; import static java.util.Objects.requireNonNull; import com.hedera.hapi.node.state.token.Token; diff --git a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hts/name/NameTranslator.java b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hts/name/NameTranslator.java index 3cf89a135c79..16165477fae6 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hts/name/NameTranslator.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hts/name/NameTranslator.java @@ -17,7 +17,7 @@ package com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.name; import com.esaulpaugh.headlong.abi.Function; -import com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.AbstractHtsCallTranslator; +import com.hedera.node.app.service.contract.impl.exec.systemcontracts.common.AbstractCallTranslator; import com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.HtsCallAttempt; import com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.ReturnTypes; import edu.umd.cs.findbugs.annotations.NonNull; @@ -29,7 +29,7 @@ * Translates {@code name()} calls to the HTS system contract. */ @Singleton -public class NameTranslator extends AbstractHtsCallTranslator { +public class NameTranslator extends AbstractCallTranslator { public static final Function NAME = new Function("name()", ReturnTypes.STRING); @Inject diff --git a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hts/nfttokeninfo/NftTokenInfoCall.java b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hts/nfttokeninfo/NftTokenInfoCall.java index efe8cc417fe3..80313fb7dfae 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hts/nfttokeninfo/NftTokenInfoCall.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hts/nfttokeninfo/NftTokenInfoCall.java @@ -20,7 +20,7 @@ import static com.hedera.hapi.node.base.ResponseCodeEnum.SUCCESS; import static com.hedera.node.app.service.contract.impl.exec.systemcontracts.FullResult.revertResult; import static com.hedera.node.app.service.contract.impl.exec.systemcontracts.FullResult.successResult; -import static com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.HtsCall.PricedResult.gasOnly; +import static com.hedera.node.app.service.contract.impl.exec.systemcontracts.common.Call.PricedResult.gasOnly; import static com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.ReturnTypes.ZERO_TOKEN_ID; import static com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.TokenTupleUtils.nftTokenInfoTupleFor; import static com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.nfttokeninfo.NftTokenInfoTranslator.NON_FUNGIBLE_TOKEN_INFO; diff --git a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hts/nfttokeninfo/NftTokenInfoTranslator.java b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hts/nfttokeninfo/NftTokenInfoTranslator.java index 047e72ca9a05..2ee8239ca21c 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hts/nfttokeninfo/NftTokenInfoTranslator.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hts/nfttokeninfo/NftTokenInfoTranslator.java @@ -20,15 +20,15 @@ import static java.util.Objects.requireNonNull; import com.esaulpaugh.headlong.abi.Function; -import com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.AbstractHtsCallTranslator; -import com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.HtsCall; +import com.hedera.node.app.service.contract.impl.exec.systemcontracts.common.AbstractCallTranslator; +import com.hedera.node.app.service.contract.impl.exec.systemcontracts.common.Call; import com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.HtsCallAttempt; import com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.ReturnTypes; import edu.umd.cs.findbugs.annotations.NonNull; import java.util.Arrays; import javax.inject.Inject; -public class NftTokenInfoTranslator extends AbstractHtsCallTranslator { +public class NftTokenInfoTranslator extends AbstractCallTranslator { public static final Function NON_FUNGIBLE_TOKEN_INFO = new Function("getNonFungibleTokenInfo(address,int64)", ReturnTypes.RESPONSE_CODE_NON_FUNGIBLE_TOKEN_INFO); @@ -51,7 +51,7 @@ public boolean matches(@NonNull final HtsCallAttempt attempt) { * {@inheritDoc} */ @Override - public HtsCall callFrom(@NonNull final HtsCallAttempt attempt) { + public Call callFrom(@NonNull final HtsCallAttempt attempt) { requireNonNull(attempt); final var args = NON_FUNGIBLE_TOKEN_INFO.decodeCall(attempt.input().toArrayUnsafe()); final var token = attempt.linkedToken(fromHeadlongAddress(args.get(0))); diff --git a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hts/ownerof/OwnerOfCall.java b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hts/ownerof/OwnerOfCall.java index 777749140804..bef6e58243a2 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hts/ownerof/OwnerOfCall.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hts/ownerof/OwnerOfCall.java @@ -21,7 +21,7 @@ import static com.hedera.hapi.node.base.ResponseCodeEnum.SUCCESS; import static com.hedera.node.app.service.contract.impl.exec.systemcontracts.FullResult.revertResult; import static com.hedera.node.app.service.contract.impl.exec.systemcontracts.FullResult.successResult; -import static com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.HtsCall.PricedResult.gasOnly; +import static com.hedera.node.app.service.contract.impl.exec.systemcontracts.common.Call.PricedResult.gasOnly; import static com.hedera.node.app.service.contract.impl.utils.ConversionUtils.headlongAddressOf; import static java.util.Objects.requireNonNull; diff --git a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hts/ownerof/OwnerOfTranslator.java b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hts/ownerof/OwnerOfTranslator.java index f7c71601d27d..877b2a02aba4 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hts/ownerof/OwnerOfTranslator.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hts/ownerof/OwnerOfTranslator.java @@ -19,7 +19,7 @@ import static com.hedera.node.app.service.contract.impl.utils.ConversionUtils.asExactLongValueOrZero; import com.esaulpaugh.headlong.abi.Function; -import com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.AbstractHtsCallTranslator; +import com.hedera.node.app.service.contract.impl.exec.systemcontracts.common.AbstractCallTranslator; import com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.HtsCallAttempt; import com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.ReturnTypes; import edu.umd.cs.findbugs.annotations.NonNull; @@ -31,7 +31,7 @@ * Translates {@code ownerOf()} calls to the HTS system contract. */ @Singleton -public class OwnerOfTranslator extends AbstractHtsCallTranslator { +public class OwnerOfTranslator extends AbstractCallTranslator { public static final Function OWNER_OF = new Function("ownerOf(uint256)", ReturnTypes.ADDRESS); @Inject diff --git a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hts/pauses/PausesTranslator.java b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hts/pauses/PausesTranslator.java index e2bf0f9c50cf..a33df2e3722d 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hts/pauses/PausesTranslator.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hts/pauses/PausesTranslator.java @@ -21,9 +21,9 @@ import com.hedera.hapi.node.transaction.TransactionBody; import com.hedera.node.app.service.contract.impl.exec.gas.DispatchType; import com.hedera.node.app.service.contract.impl.exec.gas.SystemContractGasCalculator; -import com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.AbstractHtsCallTranslator; +import com.hedera.node.app.service.contract.impl.exec.systemcontracts.common.AbstractCallTranslator; +import com.hedera.node.app.service.contract.impl.exec.systemcontracts.common.Call; import com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.DispatchForResponseCodeHtsCall; -import com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.HtsCall; import com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.HtsCallAttempt; import com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.ReturnTypes; import com.hedera.node.app.service.contract.impl.hevm.HederaWorldUpdater; @@ -34,10 +34,10 @@ /** * Translates pause and unpause calls to the HTS system contract. There are no special cases for - * these calls, so the returned {@link HtsCall} is simply an instance of {@link DispatchForResponseCodeHtsCall}. + * these calls, so the returned {@link Call} is simply an instance of {@link DispatchForResponseCodeHtsCall}. */ @Singleton -public class PausesTranslator extends AbstractHtsCallTranslator { +public class PausesTranslator extends AbstractCallTranslator { public static final Function PAUSE = new Function("pauseToken(address)", ReturnTypes.INT_64); public static final Function UNPAUSE = new Function("unpauseToken(address)", ReturnTypes.INT_64); @@ -60,7 +60,7 @@ public boolean matches(@NonNull final HtsCallAttempt attempt) { * {@inheritDoc} */ @Override - public HtsCall callFrom(@NonNull final HtsCallAttempt attempt) { + public Call callFrom(@NonNull final HtsCallAttempt attempt) { return new DispatchForResponseCodeHtsCall( attempt, bodyForClassic(attempt), diff --git a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hts/setapproval/SetApprovalForAllCall.java b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hts/setapproval/SetApprovalForAllCall.java index c8e8865cdf5d..55762f6b6ed8 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hts/setapproval/SetApprovalForAllCall.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hts/setapproval/SetApprovalForAllCall.java @@ -32,7 +32,7 @@ import com.hedera.hapi.node.transaction.TransactionBody; import com.hedera.node.app.service.contract.impl.exec.gas.DispatchGasCalculator; import com.hedera.node.app.service.contract.impl.exec.scope.VerificationStrategy; -import com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.AbstractHtsCall; +import com.hedera.node.app.service.contract.impl.exec.systemcontracts.common.AbstractCall; import com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.HtsCallAttempt; import com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.LogBuilder; import com.hedera.node.app.service.contract.impl.records.ContractCallRecordBuilder; @@ -43,7 +43,7 @@ import org.hyperledger.besu.evm.log.Log; // @Future remove to revert #9214 after modularization is completed -public class SetApprovalForAllCall extends AbstractHtsCall { +public class SetApprovalForAllCall extends AbstractCall { private final VerificationStrategy verificationStrategy; private final TransactionBody transactionBody; diff --git a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hts/setapproval/SetApprovalForAllTranslator.java b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hts/setapproval/SetApprovalForAllTranslator.java index 8ecc977bf8c5..491d7b767082 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hts/setapproval/SetApprovalForAllTranslator.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hts/setapproval/SetApprovalForAllTranslator.java @@ -21,9 +21,9 @@ import com.hedera.hapi.node.transaction.TransactionBody; import com.hedera.node.app.service.contract.impl.exec.gas.DispatchType; import com.hedera.node.app.service.contract.impl.exec.gas.SystemContractGasCalculator; -import com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.AbstractHtsCallTranslator; +import com.hedera.node.app.service.contract.impl.exec.systemcontracts.common.AbstractCallTranslator; +import com.hedera.node.app.service.contract.impl.exec.systemcontracts.common.Call; import com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.DispatchForResponseCodeHtsCall; -import com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.HtsCall; import com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.HtsCallAttempt; import com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.ReturnTypes; import com.hedera.node.app.service.contract.impl.hevm.HederaWorldUpdater; @@ -33,9 +33,9 @@ /** * Translates setApprovalForAll (including ERC) call to the HTS system contract. There are no special cases for these - * calls, so the returned {@link HtsCall} is simply an instance of {@link DispatchForResponseCodeHtsCall}. + * calls, so the returned {@link Call} is simply an instance of {@link DispatchForResponseCodeHtsCall}. */ -public class SetApprovalForAllTranslator extends AbstractHtsCallTranslator { +public class SetApprovalForAllTranslator extends AbstractCallTranslator { public static final Function SET_APPROVAL_FOR_ALL = new Function("setApprovalForAll(address,address,bool)", ReturnTypes.INT); @@ -62,7 +62,7 @@ public boolean matches(@NonNull final HtsCallAttempt attempt) { * {@inheritDoc} */ @Override - public HtsCall callFrom(@NonNull final HtsCallAttempt attempt) { + public Call callFrom(@NonNull final HtsCallAttempt attempt) { final var result = bodyForClassic(attempt); // @Future remove to revert #9214 after modularization is completed return new SetApprovalForAllCall( diff --git a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hts/symbol/SymbolCall.java b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hts/symbol/SymbolCall.java index fd5500681afd..b2e4430d0fb8 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hts/symbol/SymbolCall.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hts/symbol/SymbolCall.java @@ -18,7 +18,7 @@ import static com.hedera.hapi.node.base.ResponseCodeEnum.SUCCESS; import static com.hedera.node.app.service.contract.impl.exec.systemcontracts.FullResult.successResult; -import static com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.HtsCall.PricedResult.gasOnly; +import static com.hedera.node.app.service.contract.impl.exec.systemcontracts.common.Call.PricedResult.gasOnly; import com.hedera.hapi.node.state.token.Token; import com.hedera.node.app.service.contract.impl.exec.gas.SystemContractGasCalculator; diff --git a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hts/symbol/SymbolTranslator.java b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hts/symbol/SymbolTranslator.java index 703ed26752c4..b3443d2c2d23 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hts/symbol/SymbolTranslator.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hts/symbol/SymbolTranslator.java @@ -17,7 +17,7 @@ package com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.symbol; import com.esaulpaugh.headlong.abi.Function; -import com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.AbstractHtsCallTranslator; +import com.hedera.node.app.service.contract.impl.exec.systemcontracts.common.AbstractCallTranslator; import com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.HtsCallAttempt; import com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.ReturnTypes; import edu.umd.cs.findbugs.annotations.NonNull; @@ -29,7 +29,7 @@ * Translates {@code symbol()} calls to the HTS system contract. */ @Singleton -public class SymbolTranslator extends AbstractHtsCallTranslator { +public class SymbolTranslator extends AbstractCallTranslator { public static final Function SYMBOL = new Function("symbol()", ReturnTypes.STRING); @Inject diff --git a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hts/tokenexpiry/TokenExpiryCall.java b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hts/tokenexpiry/TokenExpiryCall.java index f7c4975596f5..da18e3baf943 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hts/tokenexpiry/TokenExpiryCall.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hts/tokenexpiry/TokenExpiryCall.java @@ -19,7 +19,7 @@ import static com.hedera.hapi.node.base.ResponseCodeEnum.SUCCESS; import static com.hedera.node.app.service.contract.impl.exec.systemcontracts.FullResult.revertResult; import static com.hedera.node.app.service.contract.impl.exec.systemcontracts.FullResult.successResult; -import static com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.HtsCall.PricedResult.gasOnly; +import static com.hedera.node.app.service.contract.impl.exec.systemcontracts.common.Call.PricedResult.gasOnly; import static com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.TokenTupleUtils.expiryTupleFor; import static com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.tokenexpiry.TokenExpiryTranslator.TOKEN_EXPIRY; import static java.util.Objects.requireNonNull; diff --git a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hts/tokenexpiry/TokenExpiryTranslator.java b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hts/tokenexpiry/TokenExpiryTranslator.java index 043aaa7aa289..c92b759b62ea 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hts/tokenexpiry/TokenExpiryTranslator.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hts/tokenexpiry/TokenExpiryTranslator.java @@ -19,15 +19,15 @@ import static com.hedera.node.app.service.contract.impl.utils.ConversionUtils.fromHeadlongAddress; import com.esaulpaugh.headlong.abi.Function; -import com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.AbstractHtsCallTranslator; -import com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.HtsCall; +import com.hedera.node.app.service.contract.impl.exec.systemcontracts.common.AbstractCallTranslator; +import com.hedera.node.app.service.contract.impl.exec.systemcontracts.common.Call; import com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.HtsCallAttempt; import com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.ReturnTypes; import edu.umd.cs.findbugs.annotations.NonNull; import java.util.Arrays; import javax.inject.Inject; -public class TokenExpiryTranslator extends AbstractHtsCallTranslator { +public class TokenExpiryTranslator extends AbstractCallTranslator { public static final Function TOKEN_EXPIRY = new Function("getTokenExpiryInfo(address)", ReturnTypes.RESPONSE_CODE_EXPIRY); @@ -49,7 +49,7 @@ public boolean matches(@NonNull final HtsCallAttempt attempt) { * {@inheritDoc} */ @Override - public HtsCall callFrom(@NonNull final HtsCallAttempt attempt) { + public Call callFrom(@NonNull final HtsCallAttempt attempt) { final var args = TOKEN_EXPIRY.decodeCall(attempt.input().toArrayUnsafe()); final var token = attempt.linkedToken(fromHeadlongAddress(args.get(0))); return new TokenExpiryCall( diff --git a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hts/tokeninfo/TokenInfoCall.java b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hts/tokeninfo/TokenInfoCall.java index a993bf98dc69..7a9728861ca6 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hts/tokeninfo/TokenInfoCall.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hts/tokeninfo/TokenInfoCall.java @@ -19,7 +19,7 @@ import static com.hedera.hapi.node.base.ResponseCodeEnum.SUCCESS; import static com.hedera.node.app.service.contract.impl.exec.systemcontracts.FullResult.revertResult; import static com.hedera.node.app.service.contract.impl.exec.systemcontracts.FullResult.successResult; -import static com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.HtsCall.PricedResult.gasOnly; +import static com.hedera.node.app.service.contract.impl.exec.systemcontracts.common.Call.PricedResult.gasOnly; import static com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.TokenTupleUtils.tokenInfoTupleFor; import static com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.tokeninfo.TokenInfoTranslator.TOKEN_INFO; import static java.util.Objects.requireNonNull; diff --git a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hts/tokeninfo/TokenInfoTranslator.java b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hts/tokeninfo/TokenInfoTranslator.java index 46b1d99f0792..f7f92250b136 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hts/tokeninfo/TokenInfoTranslator.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hts/tokeninfo/TokenInfoTranslator.java @@ -20,15 +20,15 @@ import static java.util.Objects.requireNonNull; import com.esaulpaugh.headlong.abi.Function; -import com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.AbstractHtsCallTranslator; -import com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.HtsCall; +import com.hedera.node.app.service.contract.impl.exec.systemcontracts.common.AbstractCallTranslator; +import com.hedera.node.app.service.contract.impl.exec.systemcontracts.common.Call; import com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.HtsCallAttempt; import com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.ReturnTypes; import edu.umd.cs.findbugs.annotations.NonNull; import java.util.Arrays; import javax.inject.Inject; -public class TokenInfoTranslator extends AbstractHtsCallTranslator { +public class TokenInfoTranslator extends AbstractCallTranslator { public static final Function TOKEN_INFO = new Function("getTokenInfo(address)", ReturnTypes.RESPONSE_CODE_TOKEN_INFO); @@ -51,7 +51,7 @@ public boolean matches(@NonNull final HtsCallAttempt attempt) { * {@inheritDoc} */ @Override - public HtsCall callFrom(@NonNull final HtsCallAttempt attempt) { + public Call callFrom(@NonNull final HtsCallAttempt attempt) { requireNonNull(attempt); final var args = TOKEN_INFO.decodeCall(attempt.input().toArrayUnsafe()); final var token = attempt.linkedToken(fromHeadlongAddress(args.get(0))); diff --git a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hts/tokenkey/TokenKeyCall.java b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hts/tokenkey/TokenKeyCall.java index 09cda4354764..4c044362526b 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hts/tokenkey/TokenKeyCall.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hts/tokenkey/TokenKeyCall.java @@ -20,7 +20,7 @@ import static com.hedera.hapi.node.base.ResponseCodeEnum.SUCCESS; import static com.hedera.node.app.service.contract.impl.exec.systemcontracts.FullResult.revertResult; import static com.hedera.node.app.service.contract.impl.exec.systemcontracts.FullResult.successResult; -import static com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.HtsCall.PricedResult.gasOnly; +import static com.hedera.node.app.service.contract.impl.exec.systemcontracts.common.Call.PricedResult.gasOnly; import static com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.TokenTupleUtils.keyTupleFor; import static com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.tokenkey.TokenKeyTranslator.TOKEN_KEY; import static java.util.Objects.requireNonNull; diff --git a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hts/tokenkey/TokenKeyTranslator.java b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hts/tokenkey/TokenKeyTranslator.java index 81dcbe9cb40d..0b18a6430290 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hts/tokenkey/TokenKeyTranslator.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hts/tokenkey/TokenKeyTranslator.java @@ -21,8 +21,8 @@ import com.esaulpaugh.headlong.abi.Function; import com.hedera.hapi.node.base.Key; import com.hedera.hapi.node.state.token.Token; -import com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.AbstractHtsCallTranslator; -import com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.HtsCall; +import com.hedera.node.app.service.contract.impl.exec.systemcontracts.common.AbstractCallTranslator; +import com.hedera.node.app.service.contract.impl.exec.systemcontracts.common.Call; import com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.HtsCallAttempt; import com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.ReturnTypes; import com.hedera.node.app.service.evm.exceptions.InvalidTransactionException; @@ -31,7 +31,7 @@ import java.util.Arrays; import javax.inject.Inject; -public class TokenKeyTranslator extends AbstractHtsCallTranslator { +public class TokenKeyTranslator extends AbstractCallTranslator { public static final Function TOKEN_KEY = new Function("getTokenKey(address,uint)", ReturnTypes.RESPONSE_CODE_TOKEN_KEY); @@ -53,7 +53,7 @@ public boolean matches(@NonNull final HtsCallAttempt attempt) { * {@inheritDoc} */ @Override - public HtsCall callFrom(@NonNull final HtsCallAttempt attempt) { + public Call callFrom(@NonNull final HtsCallAttempt attempt) { final var args = TOKEN_KEY.decodeCall(attempt.input().toArrayUnsafe()); final var token = attempt.linkedToken(fromHeadlongAddress(args.get(0))); final BigInteger keyType = args.get(1); diff --git a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hts/tokentype/TokenTypeCall.java b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hts/tokentype/TokenTypeCall.java index c95d20902d7a..b725095e2e0b 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hts/tokentype/TokenTypeCall.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hts/tokentype/TokenTypeCall.java @@ -19,7 +19,7 @@ import static com.hedera.hapi.node.base.ResponseCodeEnum.SUCCESS; import static com.hedera.node.app.service.contract.impl.exec.systemcontracts.FullResult.revertResult; import static com.hedera.node.app.service.contract.impl.exec.systemcontracts.FullResult.successResult; -import static com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.HtsCall.PricedResult.gasOnly; +import static com.hedera.node.app.service.contract.impl.exec.systemcontracts.common.Call.PricedResult.gasOnly; import static com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.tokentype.TokenTypeTranslator.TOKEN_TYPE; import static java.util.Objects.requireNonNull; diff --git a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hts/tokentype/TokenTypeTranslator.java b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hts/tokentype/TokenTypeTranslator.java index c0a76eeaf183..997dbc1dc5ff 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hts/tokentype/TokenTypeTranslator.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hts/tokentype/TokenTypeTranslator.java @@ -19,15 +19,15 @@ import static com.hedera.node.app.service.contract.impl.utils.ConversionUtils.fromHeadlongAddress; import com.esaulpaugh.headlong.abi.Function; -import com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.AbstractHtsCallTranslator; -import com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.HtsCall; +import com.hedera.node.app.service.contract.impl.exec.systemcontracts.common.AbstractCallTranslator; +import com.hedera.node.app.service.contract.impl.exec.systemcontracts.common.Call; import com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.HtsCallAttempt; import com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.ReturnTypes; import edu.umd.cs.findbugs.annotations.NonNull; import java.util.Arrays; import javax.inject.Inject; -public class TokenTypeTranslator extends AbstractHtsCallTranslator { +public class TokenTypeTranslator extends AbstractCallTranslator { public static final Function TOKEN_TYPE = new Function("getTokenType(address)", ReturnTypes.RESPONSE_CODE_INT32); @Inject @@ -47,7 +47,7 @@ public boolean matches(@NonNull final HtsCallAttempt attempt) { * {@inheritDoc} */ @Override - public HtsCall callFrom(@NonNull final HtsCallAttempt attempt) { + public Call callFrom(@NonNull final HtsCallAttempt attempt) { final var args = TOKEN_TYPE.decodeCall(attempt.input().toArrayUnsafe()); final var token = attempt.linkedToken(fromHeadlongAddress(args.get(0))); return new TokenTypeCall( diff --git a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hts/tokenuri/TokenUriCall.java b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hts/tokenuri/TokenUriCall.java index f8864df7791e..06dbd2aa27cf 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hts/tokenuri/TokenUriCall.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hts/tokenuri/TokenUriCall.java @@ -21,11 +21,11 @@ import static com.hedera.hapi.node.base.TokenType.FUNGIBLE_COMMON; import static com.hedera.node.app.service.contract.impl.exec.systemcontracts.FullResult.haltResult; import static com.hedera.node.app.service.contract.impl.exec.systemcontracts.FullResult.successResult; -import static com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.HtsCall.PricedResult.gasOnly; +import static com.hedera.node.app.service.contract.impl.exec.systemcontracts.common.Call.PricedResult.gasOnly; import com.hedera.hapi.node.state.token.Token; import com.hedera.node.app.service.contract.impl.exec.gas.SystemContractGasCalculator; -import com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.AbstractHtsCall; +import com.hedera.node.app.service.contract.impl.exec.systemcontracts.common.AbstractCall; import com.hedera.node.app.service.contract.impl.hevm.HederaWorldUpdater; import com.hedera.node.app.service.evm.contracts.operations.HederaExceptionalHaltReason; import edu.umd.cs.findbugs.annotations.NonNull; @@ -35,7 +35,7 @@ /** * Implements the token redirect {@code tokenURI()} call of the HTS system contract. */ -public class TokenUriCall extends AbstractHtsCall { +public class TokenUriCall extends AbstractCall { public static final String URI_QUERY_NON_EXISTING_TOKEN_ERROR = "ERC721Metadata: URI query for nonexistent token"; private final long serialNo; diff --git a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hts/tokenuri/TokenUriTranslator.java b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hts/tokenuri/TokenUriTranslator.java index f7b8441cfb48..c6be5a75dcac 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hts/tokenuri/TokenUriTranslator.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hts/tokenuri/TokenUriTranslator.java @@ -19,8 +19,8 @@ import static com.hedera.node.app.service.contract.impl.utils.ConversionUtils.asExactLongValueOrZero; import com.esaulpaugh.headlong.abi.Function; -import com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.AbstractHtsCallTranslator; -import com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.HtsCall; +import com.hedera.node.app.service.contract.impl.exec.systemcontracts.common.AbstractCallTranslator; +import com.hedera.node.app.service.contract.impl.exec.systemcontracts.common.Call; import com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.HtsCallAttempt; import com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.ReturnTypes; import edu.umd.cs.findbugs.annotations.NonNull; @@ -32,7 +32,7 @@ * Translates {@code tokenUri()} calls to the HTS system contract. */ @Singleton -public class TokenUriTranslator extends AbstractHtsCallTranslator { +public class TokenUriTranslator extends AbstractCallTranslator { public static final Function TOKEN_URI = new Function("tokenURI(uint256)", ReturnTypes.STRING); @Inject @@ -52,7 +52,7 @@ public boolean matches(@NonNull final HtsCallAttempt attempt) { * {@inheritDoc} */ @Override - public HtsCall callFrom(@NonNull final HtsCallAttempt attempt) { + public Call callFrom(@NonNull final HtsCallAttempt attempt) { final var serialNo = asExactLongValueOrZero(TokenUriTranslator.TOKEN_URI .decodeCall(attempt.input().toArrayUnsafe()) .get(0)); diff --git a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hts/totalsupply/TotalSupplyCall.java b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hts/totalsupply/TotalSupplyCall.java index 9cd20d1518c4..cbf7d5dd0abc 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hts/totalsupply/TotalSupplyCall.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hts/totalsupply/TotalSupplyCall.java @@ -18,7 +18,7 @@ import static com.hedera.hapi.node.base.ResponseCodeEnum.SUCCESS; import static com.hedera.node.app.service.contract.impl.exec.systemcontracts.FullResult.successResult; -import static com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.HtsCall.PricedResult.gasOnly; +import static com.hedera.node.app.service.contract.impl.exec.systemcontracts.common.Call.PricedResult.gasOnly; import com.hedera.hapi.node.state.token.Token; import com.hedera.node.app.service.contract.impl.exec.gas.SystemContractGasCalculator; diff --git a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hts/totalsupply/TotalSupplyTranslator.java b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hts/totalsupply/TotalSupplyTranslator.java index 0b29d658a3f0..65b9baf941ae 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hts/totalsupply/TotalSupplyTranslator.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hts/totalsupply/TotalSupplyTranslator.java @@ -17,7 +17,7 @@ package com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.totalsupply; import com.esaulpaugh.headlong.abi.Function; -import com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.AbstractHtsCallTranslator; +import com.hedera.node.app.service.contract.impl.exec.systemcontracts.common.AbstractCallTranslator; import com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.HtsCallAttempt; import com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.ReturnTypes; import edu.umd.cs.findbugs.annotations.NonNull; @@ -26,7 +26,7 @@ import javax.inject.Singleton; @Singleton -public class TotalSupplyTranslator extends AbstractHtsCallTranslator { +public class TotalSupplyTranslator extends AbstractCallTranslator { public static final Function TOTAL_SUPPLY = new Function("totalSupply()", ReturnTypes.INT); @Inject diff --git a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hts/transfer/ClassicTransfersCall.java b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hts/transfer/ClassicTransfersCall.java index 6cd955bc9fce..5fda643d8b09 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hts/transfer/ClassicTransfersCall.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hts/transfer/ClassicTransfersCall.java @@ -21,7 +21,7 @@ import static com.hedera.hapi.node.base.ResponseCodeEnum.NOT_SUPPORTED; import static com.hedera.hapi.node.base.ResponseCodeEnum.SUCCESS; import static com.hedera.node.app.service.contract.impl.exec.systemcontracts.FullResult.haltResult; -import static com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.HtsCall.PricedResult.gasOnly; +import static com.hedera.node.app.service.contract.impl.exec.systemcontracts.common.Call.PricedResult.gasOnly; import static com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.ReturnTypes.encodedRc; import static com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.transfer.ClassicTransfersTranslator.TRANSFER_TOKEN; import static com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.transfer.TransferEventLoggingUtils.logSuccessfulFungibleTransfer; @@ -36,7 +36,7 @@ import com.hedera.node.app.service.contract.impl.exec.gas.DispatchType; import com.hedera.node.app.service.contract.impl.exec.gas.SystemContractGasCalculator; import com.hedera.node.app.service.contract.impl.exec.scope.VerificationStrategy; -import com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.AbstractHtsCall; +import com.hedera.node.app.service.contract.impl.exec.systemcontracts.common.AbstractCall; import com.hedera.node.app.service.contract.impl.hevm.HederaWorldUpdater; import com.hedera.node.app.service.contract.impl.records.ContractCallRecordBuilder; import com.hedera.node.app.service.token.ReadableAccountStore; @@ -65,7 +65,7 @@ *
    * But the basic pattern of constructing and dispatching a synthetic {@link CryptoTransferTransactionBody} remains. */ -public class ClassicTransfersCall extends AbstractHtsCall { +public class ClassicTransfersCall extends AbstractCall { private final byte[] selector; private final AccountID senderId; private final ResponseCodeEnum preemptingFailureStatus; diff --git a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hts/transfer/ClassicTransfersTranslator.java b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hts/transfer/ClassicTransfersTranslator.java index a3e321aab02a..539ad520dddf 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hts/transfer/ClassicTransfersTranslator.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hts/transfer/ClassicTransfersTranslator.java @@ -23,7 +23,7 @@ import com.esaulpaugh.headlong.abi.Function; import com.hedera.hapi.node.transaction.TransactionBody; -import com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.AbstractHtsCallTranslator; +import com.hedera.node.app.service.contract.impl.exec.systemcontracts.common.AbstractCallTranslator; import com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.HtsCallAttempt; import com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.ReturnTypes; import edu.umd.cs.findbugs.annotations.NonNull; @@ -36,7 +36,7 @@ * Translates "classic" {@code cryptoTransfer()} calls to the HTS system contract. */ @Singleton -public class ClassicTransfersTranslator extends AbstractHtsCallTranslator { +public class ClassicTransfersTranslator extends AbstractCallTranslator { public static final Function CRYPTO_TRANSFER = new Function("cryptoTransfer((address,(address,int64)[],(address,address,int64)[])[])", ReturnTypes.INT_64); public static final Function CRYPTO_TRANSFER_V2 = new Function( diff --git a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hts/transfer/Erc20TransfersCall.java b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hts/transfer/Erc20TransfersCall.java index 5beaa63eec0c..9519c775afcf 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hts/transfer/Erc20TransfersCall.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hts/transfer/Erc20TransfersCall.java @@ -21,7 +21,7 @@ import static com.hedera.hapi.node.base.ResponseCodeEnum.SUCCESS; import static com.hedera.node.app.service.contract.impl.exec.systemcontracts.FullResult.revertResult; import static com.hedera.node.app.service.contract.impl.exec.systemcontracts.FullResult.successResult; -import static com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.HtsCall.PricedResult.gasOnly; +import static com.hedera.node.app.service.contract.impl.exec.systemcontracts.common.Call.PricedResult.gasOnly; import static com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.transfer.ClassicTransfersCall.transferGasRequirement; import static com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.transfer.Erc20TransfersTranslator.ERC_20_TRANSFER; import static com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.transfer.Erc20TransfersTranslator.ERC_20_TRANSFER_FROM; @@ -37,7 +37,7 @@ import com.hedera.hapi.node.transaction.TransactionBody; import com.hedera.node.app.service.contract.impl.exec.gas.SystemContractGasCalculator; import com.hedera.node.app.service.contract.impl.exec.scope.VerificationStrategy; -import com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.AbstractHtsCall; +import com.hedera.node.app.service.contract.impl.exec.systemcontracts.common.AbstractCall; import com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.AddressIdConverter; import com.hedera.node.app.service.contract.impl.hevm.HederaWorldUpdater; import com.hedera.node.app.service.contract.impl.records.ContractCallRecordBuilder; @@ -49,7 +49,7 @@ /** * Implements the ERC-20 {@code transfer()} and {@code transferFrom()} calls of the HTS contract. */ -public class Erc20TransfersCall extends AbstractHtsCall { +public class Erc20TransfersCall extends AbstractCall { private final long amount; @Nullable diff --git a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hts/transfer/Erc20TransfersTranslator.java b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hts/transfer/Erc20TransfersTranslator.java index b56ef823943b..036c1f4fa1d3 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hts/transfer/Erc20TransfersTranslator.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hts/transfer/Erc20TransfersTranslator.java @@ -22,8 +22,8 @@ import com.esaulpaugh.headlong.abi.Address; import com.esaulpaugh.headlong.abi.Function; -import com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.AbstractHtsCallTranslator; -import com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.HtsCall; +import com.hedera.node.app.service.contract.impl.exec.systemcontracts.common.AbstractCallTranslator; +import com.hedera.node.app.service.contract.impl.exec.systemcontracts.common.Call; import com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.HtsCallAttempt; import com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.ReturnTypes; import edu.umd.cs.findbugs.annotations.NonNull; @@ -37,7 +37,7 @@ * Translates ERC-20 transfer calls to the HTS system contract. */ @Singleton -public class Erc20TransfersTranslator extends AbstractHtsCallTranslator { +public class Erc20TransfersTranslator extends AbstractCallTranslator { public static final Function ERC_20_TRANSFER = new Function("transfer(address,uint256)", ReturnTypes.BOOL); public static final Function ERC_20_TRANSFER_FROM = new Function("transferFrom(address,address,uint256)", ReturnTypes.BOOL); @@ -56,7 +56,7 @@ && selectorsInclude(attempt.selector()) } @Override - public @Nullable HtsCall callFrom(@NonNull final HtsCallAttempt attempt) { + public @Nullable Call callFrom(@NonNull final HtsCallAttempt attempt) { if (isErc20Transfer(attempt.selector())) { final var call = Erc20TransfersTranslator.ERC_20_TRANSFER.decodeCall( attempt.input().toArrayUnsafe()); diff --git a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hts/transfer/Erc721TransferFromCall.java b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hts/transfer/Erc721TransferFromCall.java index 6409b87b510c..3f3761622dc5 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hts/transfer/Erc721TransferFromCall.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hts/transfer/Erc721TransferFromCall.java @@ -19,7 +19,7 @@ import static com.hedera.hapi.node.base.ResponseCodeEnum.INVALID_TOKEN_ID; import static com.hedera.node.app.service.contract.impl.exec.systemcontracts.FullResult.revertResult; import static com.hedera.node.app.service.contract.impl.exec.systemcontracts.FullResult.successResult; -import static com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.HtsCall.PricedResult.gasOnly; +import static com.hedera.node.app.service.contract.impl.exec.systemcontracts.common.Call.PricedResult.gasOnly; import static com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.transfer.ClassicTransfersCall.transferGasRequirement; import static com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.transfer.Erc721TransferFromTranslator.ERC_721_TRANSFER_FROM; import static com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.transfer.TransferEventLoggingUtils.logSuccessfulNftTransfer; @@ -36,7 +36,7 @@ import com.hedera.node.app.service.contract.impl.exec.gas.DispatchType; import com.hedera.node.app.service.contract.impl.exec.gas.SystemContractGasCalculator; import com.hedera.node.app.service.contract.impl.exec.scope.VerificationStrategy; -import com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.AbstractHtsCall; +import com.hedera.node.app.service.contract.impl.exec.systemcontracts.common.AbstractCall; import com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.AddressIdConverter; import com.hedera.node.app.service.contract.impl.hevm.HederaWorldUpdater; import com.hedera.node.app.service.contract.impl.records.ContractCallRecordBuilder; @@ -47,7 +47,7 @@ /** * Implements the ERC-721 {@code transferFrom()} call of the HTS contract. */ -public class Erc721TransferFromCall extends AbstractHtsCall { +public class Erc721TransferFromCall extends AbstractCall { private final long serialNo; private final Address from; private final Address to; diff --git a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hts/transfer/Erc721TransferFromTranslator.java b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hts/transfer/Erc721TransferFromTranslator.java index c5722da4d25b..65a18add132c 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hts/transfer/Erc721TransferFromTranslator.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hts/transfer/Erc721TransferFromTranslator.java @@ -21,8 +21,8 @@ import static java.util.Objects.requireNonNull; import com.esaulpaugh.headlong.abi.Function; -import com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.AbstractHtsCallTranslator; -import com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.HtsCall; +import com.hedera.node.app.service.contract.impl.exec.systemcontracts.common.AbstractCallTranslator; +import com.hedera.node.app.service.contract.impl.exec.systemcontracts.common.Call; import com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.HtsCallAttempt; import edu.umd.cs.findbugs.annotations.NonNull; import java.math.BigInteger; @@ -34,7 +34,7 @@ * Translates ERC-721 {@code transferFrom()} calls to the HTS system contract. */ @Singleton -public class Erc721TransferFromTranslator extends AbstractHtsCallTranslator { +public class Erc721TransferFromTranslator extends AbstractCallTranslator { public static final Function ERC_721_TRANSFER_FROM = new Function("transferFrom(address,address,uint256)"); @@ -52,7 +52,7 @@ public boolean matches(@NonNull final HtsCallAttempt attempt) { } @Override - public HtsCall callFrom(@NonNull final HtsCallAttempt attempt) { + public Call callFrom(@NonNull final HtsCallAttempt attempt) { final var call = Erc721TransferFromTranslator.ERC_721_TRANSFER_FROM.decodeCall( attempt.input().toArrayUnsafe()); return new Erc721TransferFromCall( diff --git a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hts/update/UpdateExpiryTranslator.java b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hts/update/UpdateExpiryTranslator.java index adbfab246e0a..c38432297940 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hts/update/UpdateExpiryTranslator.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hts/update/UpdateExpiryTranslator.java @@ -25,9 +25,9 @@ import com.hedera.hapi.node.transaction.TransactionBody; import com.hedera.node.app.service.contract.impl.exec.gas.DispatchType; import com.hedera.node.app.service.contract.impl.exec.gas.SystemContractGasCalculator; -import com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.AbstractHtsCallTranslator; +import com.hedera.node.app.service.contract.impl.exec.systemcontracts.common.AbstractCallTranslator; +import com.hedera.node.app.service.contract.impl.exec.systemcontracts.common.Call; import com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.DispatchForResponseCodeHtsCall; -import com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.HtsCall; import com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.HtsCallAttempt; import com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.ReturnTypes; import com.hedera.node.app.service.contract.impl.hevm.HederaWorldUpdater; @@ -35,7 +35,7 @@ import java.util.Arrays; import javax.inject.Inject; -public class UpdateExpiryTranslator extends AbstractHtsCallTranslator { +public class UpdateExpiryTranslator extends AbstractCallTranslator { public static final Function UPDATE_TOKEN_EXPIRY_INFO_V1 = new Function("updateTokenExpiryInfo(address," + EXPIRY + ")", ReturnTypes.INT); public static final Function UPDATE_TOKEN_EXPIRY_INFO_V2 = @@ -55,7 +55,7 @@ public boolean matches(@NonNull HtsCallAttempt attempt) { } @Override - public HtsCall callFrom(@NonNull HtsCallAttempt attempt) { + public Call callFrom(@NonNull HtsCallAttempt attempt) { return new DispatchForResponseCodeHtsCall( attempt, nominalBodyFor(attempt), UpdateTranslator::gasRequirement, FAILURE_CUSTOMIZER); } diff --git a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hts/update/UpdateKeysTranslator.java b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hts/update/UpdateKeysTranslator.java index cdb34e6ddd1d..9f0ea300068e 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hts/update/UpdateKeysTranslator.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hts/update/UpdateKeysTranslator.java @@ -25,9 +25,9 @@ import com.hedera.hapi.node.transaction.TransactionBody; import com.hedera.node.app.service.contract.impl.exec.gas.DispatchType; import com.hedera.node.app.service.contract.impl.exec.gas.SystemContractGasCalculator; -import com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.AbstractHtsCallTranslator; +import com.hedera.node.app.service.contract.impl.exec.systemcontracts.common.AbstractCallTranslator; +import com.hedera.node.app.service.contract.impl.exec.systemcontracts.common.Call; import com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.DispatchForResponseCodeHtsCall; -import com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.HtsCall; import com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.HtsCallAttempt; import com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.ReturnTypes; import com.hedera.node.app.service.contract.impl.hevm.HederaWorldUpdater; @@ -35,7 +35,7 @@ import java.util.Arrays; import javax.inject.Inject; -public class UpdateKeysTranslator extends AbstractHtsCallTranslator { +public class UpdateKeysTranslator extends AbstractCallTranslator { public static final Function TOKEN_UPDATE_KEYS_FUNCTION = new Function("updateTokenKeys(address," + TOKEN_KEY + ARRAY_BRACKETS + ")", ReturnTypes.INT); @@ -52,7 +52,7 @@ public boolean matches(@NonNull HtsCallAttempt attempt) { } @Override - public HtsCall callFrom(@NonNull HtsCallAttempt attempt) { + public Call callFrom(@NonNull HtsCallAttempt attempt) { return new DispatchForResponseCodeHtsCall( attempt, decoder.decodeTokenUpdateKeys(attempt), diff --git a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hts/update/UpdateTranslator.java b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hts/update/UpdateTranslator.java index 0158739c48ca..3b5d7b950499 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hts/update/UpdateTranslator.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hts/update/UpdateTranslator.java @@ -26,9 +26,9 @@ import com.hedera.hapi.node.transaction.TransactionBody; import com.hedera.node.app.service.contract.impl.exec.gas.DispatchType; import com.hedera.node.app.service.contract.impl.exec.gas.SystemContractGasCalculator; -import com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.AbstractHtsCallTranslator; +import com.hedera.node.app.service.contract.impl.exec.systemcontracts.common.AbstractCallTranslator; +import com.hedera.node.app.service.contract.impl.exec.systemcontracts.common.Call; import com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.DispatchForResponseCodeHtsCall; -import com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.HtsCall; import com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.HtsCallAttempt; import com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.ReturnTypes; import com.hedera.node.app.service.contract.impl.hevm.HederaWorldUpdater; @@ -36,7 +36,7 @@ import java.util.Arrays; import javax.inject.Inject; -public class UpdateTranslator extends AbstractHtsCallTranslator { +public class UpdateTranslator extends AbstractCallTranslator { private static final String UPDATE_TOKEN_INFO_STRING = "updateTokenInfo(address,"; private static final String HEDERA_TOKEN_STRUCT = "(string,string,address,string,bool,uint32,bool," + TOKEN_KEY + ARRAY_BRACKETS + "," + EXPIRY + ")"; @@ -67,7 +67,7 @@ public boolean matches(@NonNull HtsCallAttempt attempt) { } @Override - public HtsCall callFrom(@NonNull HtsCallAttempt attempt) { + public Call callFrom(@NonNull HtsCallAttempt attempt) { return new DispatchForResponseCodeHtsCall( attempt, nominalBodyFor(attempt), UpdateTranslator::gasRequirement, UpdateDecoder.FAILURE_CUSTOMIZER); } diff --git a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hts/wipe/WipeTranslator.java b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hts/wipe/WipeTranslator.java index e61cd6e818f4..8bf343832408 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hts/wipe/WipeTranslator.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hts/wipe/WipeTranslator.java @@ -23,9 +23,9 @@ import com.hedera.hapi.node.transaction.TransactionBody; import com.hedera.node.app.service.contract.impl.exec.gas.DispatchType; import com.hedera.node.app.service.contract.impl.exec.gas.SystemContractGasCalculator; -import com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.AbstractHtsCallTranslator; +import com.hedera.node.app.service.contract.impl.exec.systemcontracts.common.AbstractCallTranslator; +import com.hedera.node.app.service.contract.impl.exec.systemcontracts.common.Call; import com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.DispatchForResponseCodeHtsCall; -import com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.HtsCall; import com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.HtsCallAttempt; import com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.ReturnTypes; import com.hedera.node.app.service.contract.impl.hevm.HederaWorldUpdater; @@ -35,7 +35,7 @@ import javax.inject.Singleton; @Singleton -public class WipeTranslator extends AbstractHtsCallTranslator { +public class WipeTranslator extends AbstractCallTranslator { public static final Function WIPE_FUNGIBLE_V1 = new Function("wipeTokenAccount(address,address,uint32)", ReturnTypes.INT); @@ -65,7 +65,7 @@ public boolean matches(@NonNull final HtsCallAttempt attempt) { * {@inheritDoc} */ @Override - public HtsCall callFrom(@NonNull final HtsCallAttempt attempt) { + public Call callFrom(@NonNull final HtsCallAttempt attempt) { final var body = bodyForClassic(attempt); final var isFungibleWipe = body.tokenWipeOrThrow().serialNumbers().isEmpty(); return new DispatchForResponseCodeHtsCall( diff --git a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/utils/ActionStack.java b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/utils/ActionStack.java index 1bec593d5f30..2dfc1f18fb32 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/utils/ActionStack.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/utils/ActionStack.java @@ -22,15 +22,14 @@ import static com.hedera.hapi.streams.ContractActionType.CREATE; import static com.hedera.hapi.streams.codec.ContractActionProtoCodec.RECIPIENT_UNSET; import static com.hedera.node.app.service.contract.impl.exec.failure.CustomExceptionalHaltReason.INVALID_SOLIDITY_ADDRESS; +import static com.hedera.node.app.service.contract.impl.exec.utils.FrameUtils.proxyUpdaterFor; import static com.hedera.node.app.service.contract.impl.utils.ConversionUtils.asNumberedContractId; import static com.hedera.node.app.service.contract.impl.utils.ConversionUtils.hederaIdNumOfContractIn; import static com.hedera.node.app.service.contract.impl.utils.ConversionUtils.hederaIdNumOfOriginatorIn; -import static com.hedera.node.app.service.contract.impl.utils.ConversionUtils.hederaIdNumberIn; import static com.hedera.node.app.service.contract.impl.utils.ConversionUtils.pbjToBesuAddress; import static com.hedera.node.app.service.contract.impl.utils.ConversionUtils.tuweniToPbjBytes; import static java.nio.charset.StandardCharsets.UTF_8; import static java.util.Objects.requireNonNull; -import static org.hyperledger.besu.evm.frame.MessageFrame.State.EXCEPTIONAL_HALT; import static org.hyperledger.besu.evm.frame.MessageFrame.Type.CONTRACT_CREATION; import static org.hyperledger.besu.evm.frame.MessageFrame.Type.MESSAGE_CALL; @@ -192,12 +191,13 @@ private ContractAction finalFormOf(@NonNull final ContractAction action, @NonNul } else { builder.output(tuweniToPbjBytes(frame.getOutputData())); if (action.targetedAddress() != null) { - final var lazyCreatedAddress = pbjToBesuAddress(action.targetedAddressOrThrow()); - try { - builder.recipientAccount(accountIdWith(hederaIdNumberIn(frame, lazyCreatedAddress))); - } catch (final IllegalArgumentException | NullPointerException | ArithmeticException e) { - // handle non-existing to/receiver address - builder.recipientAccount((AccountID) null); + final var maybeCreatedAddress = pbjToBesuAddress(action.targetedAddressOrThrow()); + final var maybeCreatedAccount = proxyUpdaterFor(frame).getHederaAccount(maybeCreatedAddress); + // Fill in the account of id of a successful lazy creation; but just leave + // the targeted address in case of a failed lazy-creation or a call to a + // non-existent address + if (maybeCreatedAccount != null) { + builder.recipientAccount(maybeCreatedAccount.hederaId()); } } } @@ -282,7 +282,7 @@ private void completePush(@NonNull ContractAction.Builder builder, @NonNull fina } else { try { builder.recipientContract(contractIdWith(hederaIdNumOfContractIn(frame))); - } catch (NullPointerException e) { + } catch (NullPointerException ignore) { builder.targetedAddress(tuweniToPbjBytes(frame.getContractAddress())); } } diff --git a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/utils/FrameBuilder.java b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/utils/FrameBuilder.java index 18a00cc00f0e..b46fa687379a 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/utils/FrameBuilder.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/utils/FrameBuilder.java @@ -114,7 +114,8 @@ public MessageFrame buildInitialFrameWith( if (transaction.isCreate()) { return finishedAsCreate(to, builder, transaction); } else { - return finishedAsCall(to, worldUpdater, builder, transaction, featureFlags, config); + return finishedAsCall( + to, worldUpdater, builder, transaction, featureFlags, config.getConfigData(ContractsConfig.class)); } } @@ -155,7 +156,7 @@ private MessageFrame finishedAsCall( @NonNull final MessageFrame.Builder builder, @NonNull final HederaEvmTransaction transaction, @NonNull final FeatureFlags featureFlags, - @NonNull final Configuration config) { + @NonNull final ContractsConfig config) { Code code = CodeV0.EMPTY_CODE; final var contractId = transaction.contractIdOrThrow(); @@ -182,7 +183,7 @@ private boolean canLoadCodeFromAccount( @NonNull final HederaEvmTransaction transaction, @NonNull final HederaWorldUpdater worldUpdater, @NonNull final ContractID contractId, - @NonNull final Configuration config, + @NonNull final ContractsConfig config, @NonNull final FeatureFlags featureFlags) { requireNonNull(transaction); requireNonNull(worldUpdater); @@ -202,7 +203,7 @@ private boolean canLoadCodeFromAccount( } private boolean contractMustBePresent( - @NonNull final Configuration config, + @NonNull final ContractsConfig config, @NonNull final FeatureFlags featureFlags, @NonNull final ContractID contractID) { final var possiblyGrandFatheredEntityNumOf = contractID.hasContractNum() ? contractID.contractNum() : null; @@ -210,7 +211,7 @@ private boolean contractMustBePresent( } private boolean emptyCodePossiblyAllowed( - @NonNull final Configuration config, + @NonNull final ContractsConfig config, @NonNull final FeatureFlags featureFlags, @NonNull final ContractID contractId, @NonNull final HederaEvmTransaction transaction, diff --git a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/utils/FrameUtils.java b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/utils/FrameUtils.java index f6d723f57b51..f5b61e8eb4c7 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/utils/FrameUtils.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/utils/FrameUtils.java @@ -21,7 +21,6 @@ import static com.hedera.node.app.service.contract.impl.utils.ConversionUtils.asNumberedContractId; import static com.hedera.node.app.service.contract.impl.utils.ConversionUtils.isLongZero; import static com.hedera.node.app.service.contract.impl.utils.ConversionUtils.numberOfLongZero; -import static com.hedera.node.app.service.evm.store.contracts.HederaEvmWorldStateTokenAccount.TOKEN_PROXY_ACCOUNT_NONCE; import static java.util.Objects.requireNonNull; import com.hedera.hapi.node.base.ContractID; @@ -50,6 +49,7 @@ public class FrameUtils { public static final String PROPAGATED_CALL_FAILURE_CONTEXT_VARIABLE = "propagatedCallFailure"; public static final String SYSTEM_CONTRACT_GAS_CALCULATOR_CONTEXT_VARIABLE = "systemContractGasCalculator"; public static final String PENDING_CREATION_BUILDER_CONTEXT_VARIABLE = "pendingCreationBuilder"; + private static final long TOKEN_PROXY_ACCOUNT_NONCE = -1; private FrameUtils() { throw new UnsupportedOperationException("Utility Class"); @@ -257,12 +257,12 @@ public static boolean stackIncludesActiveAddress( public enum CallType { QUALIFIED_DELEGATE, UNQUALIFIED_DELEGATE, - DIRECT_OR_TOKEN_REDIRECT, + DIRECT_OR_PROXY_REDIRECT, } public static CallType callTypeOf(final MessageFrame frame) { if (!isDelegateCall(frame)) { - return CallType.DIRECT_OR_TOKEN_REDIRECT; + return CallType.DIRECT_OR_PROXY_REDIRECT; } final var recipient = frame.getRecipientAddress(); // Evaluate whether the recipient is either a token or on the permitted callers list. @@ -270,12 +270,37 @@ public static CallType callTypeOf(final MessageFrame frame) { // We accept delegates if the token redirect contract calls us. final CallType viableType; if (isToken(frame, recipient)) { - viableType = CallType.DIRECT_OR_TOKEN_REDIRECT; + viableType = CallType.DIRECT_OR_PROXY_REDIRECT; } else if (isQualifiedDelegate(recipient, frame)) { viableType = CallType.QUALIFIED_DELEGATE; } else { return CallType.UNQUALIFIED_DELEGATE; } + // make sure we have a parent calling context + return validateParentCallType(frame, viableType); + } + + public static CallType callTypeForAccountOf(final MessageFrame frame) { + if (!isDelegateCall(frame)) { + return CallType.DIRECT_OR_PROXY_REDIRECT; + } + final var recipient = frame.getRecipientAddress(); + // Evaluate whether the recipient is a regular account. + // This determines if we should treat this as a delegate call. + // We accept delegates if the account redirect contract calls us. + final CallType viableType; + if (isRegularAccount(frame, recipient)) { + viableType = CallType.DIRECT_OR_PROXY_REDIRECT; + return validateParentCallType(frame, viableType); + } else { + return CallType.UNQUALIFIED_DELEGATE; + } + } + + private static CallType validateParentCallType(@NonNull MessageFrame frame, @NonNull CallType viableType) { + requireNonNull(frame); + requireNonNull(viableType); + // make sure we have a parent calling context final var stack = frame.getMessageFrameStack(); final var frames = stack.iterator(); @@ -291,10 +316,10 @@ public static CallType callTypeOf(final MessageFrame frame) { /** * Returns true if the given frame is a call to a contract that must be present based on feature flag settings. * - * @param frame + * @param frame the current message frame * @param address to check for possible grandfathering - * @param featureFlags - * @return + * @param featureFlags current evm module feature flags + * @return true if the contract address must be present in the ledger */ public static boolean contractRequired( @NonNull final MessageFrame frame, @@ -312,7 +337,8 @@ public static boolean contractRequired( // Not a valid numbered contract id } } - return !featureFlags.isAllowCallsToNonContractAccountsEnabled(configOf(frame), maybeGrandfatheredNumber); + return !featureFlags.isAllowCallsToNonContractAccountsEnabled( + contractsConfigOf(frame), maybeGrandfatheredNumber); } private static boolean isToken(final MessageFrame frame, final Address address) { @@ -323,6 +349,18 @@ private static boolean isToken(final MessageFrame frame, final Address address) return false; } + private static boolean isRegularAccount(@NonNull final MessageFrame frame, @NonNull final Address address) { + requireNonNull(frame); + requireNonNull(address); + + final var updater = (ProxyWorldUpdater) frame.getWorldUpdater(); + final var recipient = updater.getHederaAccount(address); + if (recipient != null) { + return recipient.isRegularAccount(); + } + return false; + } + private static @NonNull MessageFrame initialFrameOf(@NonNull final MessageFrame frame) { final var stack = frame.getMessageFrameStack(); return stack.isEmpty() ? frame : stack.getLast(); diff --git a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/v030/Version030FeatureFlags.java b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/v030/Version030FeatureFlags.java index d1b99f182a36..312f96125330 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/v030/Version030FeatureFlags.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/v030/Version030FeatureFlags.java @@ -22,7 +22,6 @@ import com.hedera.node.config.data.ContractsConfig; import com.swirlds.config.api.Configuration; import edu.umd.cs.findbugs.annotations.NonNull; -import edu.umd.cs.findbugs.annotations.Nullable; import javax.inject.Inject; import javax.inject.Singleton; import org.hyperledger.besu.evm.frame.MessageFrame; @@ -47,10 +46,4 @@ public boolean isCreate2Enabled(@NonNull final MessageFrame frame) { public boolean isImplicitCreationEnabled(@NonNull Configuration config) { return false; } - - @Override - public boolean isAllowCallsToNonContractAccountsEnabled( - @NonNull Configuration config, @Nullable Long possiblyGrandFatheredEntityNum) { - return false; - } } diff --git a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/v046/Version046FeatureFlags.java b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/v046/Version046FeatureFlags.java index 69f6c4272b7c..c961bf7e5cfc 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/v046/Version046FeatureFlags.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/v046/Version046FeatureFlags.java @@ -33,12 +33,10 @@ public Version046FeatureFlags() { @Override public boolean isAllowCallsToNonContractAccountsEnabled( - @NonNull Configuration config, @Nullable Long possiblyGrandFatheredEntityNum) { + @NonNull ContractsConfig config, @Nullable Long possiblyGrandFatheredEntityNum) { final var grandfathered = possiblyGrandFatheredEntityNum != null - && config.getConfigData(ContractsConfig.class) - .evmNonExtantContractsFail() - .contains(possiblyGrandFatheredEntityNum); - return config.getConfigData(ContractsConfig.class).evmAllowCallsToNonContractAccounts() && !grandfathered; + && config.evmNonExtantContractsFail().contains(possiblyGrandFatheredEntityNum); + return config.evmAllowCallsToNonContractAccounts() && !grandfathered; } @Override diff --git a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/v050/V050Module.java b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/v050/V050Module.java index b35bb7532326..3e5746fa19b8 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/v050/V050Module.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/v050/V050Module.java @@ -142,7 +142,7 @@ static EVM provideEVM( oneTimeEVMModuleInitialization(); - // Use Cancun EVM with 0.49 custom operations and 0x00 chain id (set at runtime) + // Use Cancun EVM with 0.50 custom operations and 0x00 chain id (set at runtime) final var operationRegistry = new OperationRegistry(); registerCancunOperations(operationRegistry, gasCalculator, BigInteger.ZERO); customOperations.forEach(operationRegistry::put); diff --git a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/v051/V051Module.java b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/v051/V051Module.java new file mode 100644 index 000000000000..5b5dcce962a1 --- /dev/null +++ b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/v051/V051Module.java @@ -0,0 +1,347 @@ +/* + * Copyright (C) 2024 Hedera Hashgraph, LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.hedera.node.app.service.contract.impl.exec.v051; + +import static com.hedera.node.app.service.contract.impl.exec.processors.ProcessorModule.INITIAL_CONTRACT_NONCE; +import static com.hedera.node.app.service.contract.impl.exec.processors.ProcessorModule.REQUIRE_CODE_DEPOSIT_TO_SUCCEED; +import static org.hyperledger.besu.evm.MainnetEVMs.registerCancunOperations; +import static org.hyperledger.besu.evm.operation.SStoreOperation.FRONTIER_MINIMUM; + +import com.hedera.node.app.service.contract.impl.annotations.ServicesV051; +import com.hedera.node.app.service.contract.impl.exec.AddressChecks; +import com.hedera.node.app.service.contract.impl.exec.FeatureFlags; +import com.hedera.node.app.service.contract.impl.exec.FrameRunner; +import com.hedera.node.app.service.contract.impl.exec.TransactionProcessor; +import com.hedera.node.app.service.contract.impl.exec.gas.CustomGasCharging; +import com.hedera.node.app.service.contract.impl.exec.operations.CustomBalanceOperation; +import com.hedera.node.app.service.contract.impl.exec.operations.CustomCallCodeOperation; +import com.hedera.node.app.service.contract.impl.exec.operations.CustomCallOperation; +import com.hedera.node.app.service.contract.impl.exec.operations.CustomChainIdOperation; +import com.hedera.node.app.service.contract.impl.exec.operations.CustomCreate2Operation; +import com.hedera.node.app.service.contract.impl.exec.operations.CustomCreateOperation; +import com.hedera.node.app.service.contract.impl.exec.operations.CustomDelegateCallOperation; +import com.hedera.node.app.service.contract.impl.exec.operations.CustomExtCodeCopyOperation; +import com.hedera.node.app.service.contract.impl.exec.operations.CustomExtCodeHashOperation; +import com.hedera.node.app.service.contract.impl.exec.operations.CustomExtCodeSizeOperation; +import com.hedera.node.app.service.contract.impl.exec.operations.CustomLogOperation; +import com.hedera.node.app.service.contract.impl.exec.operations.CustomPrevRandaoOperation; +import com.hedera.node.app.service.contract.impl.exec.operations.CustomSLoadOperation; +import com.hedera.node.app.service.contract.impl.exec.operations.CustomSStoreOperation; +import com.hedera.node.app.service.contract.impl.exec.operations.CustomSelfDestructOperation; +import com.hedera.node.app.service.contract.impl.exec.operations.CustomSelfDestructOperation.UseEIP6780Semantics; +import com.hedera.node.app.service.contract.impl.exec.operations.CustomStaticCallOperation; +import com.hedera.node.app.service.contract.impl.exec.processors.CustomContractCreationProcessor; +import com.hedera.node.app.service.contract.impl.exec.processors.CustomMessageCallProcessor; +import com.hedera.node.app.service.contract.impl.exec.systemcontracts.HederaSystemContract; +import com.hedera.node.app.service.contract.impl.exec.utils.FrameBuilder; +import com.hedera.node.app.service.contract.impl.exec.v038.Version038AddressChecks; +import dagger.Binds; +import dagger.Module; +import dagger.Provides; +import dagger.multibindings.IntoSet; +import edu.umd.cs.findbugs.annotations.NonNull; +import java.math.BigInteger; +import java.util.List; +import java.util.Map; +import java.util.Set; +import javax.inject.Singleton; +import org.hyperledger.besu.datatypes.Address; +import org.hyperledger.besu.evm.EVM; +import org.hyperledger.besu.evm.EvmSpecVersion; +import org.hyperledger.besu.evm.contractvalidation.ContractValidationRule; +import org.hyperledger.besu.evm.gascalculator.GasCalculator; +import org.hyperledger.besu.evm.internal.EvmConfiguration; +import org.hyperledger.besu.evm.operation.Operation; +import org.hyperledger.besu.evm.operation.OperationRegistry; +import org.hyperledger.besu.evm.operation.SLoadOperation; +import org.hyperledger.besu.evm.operation.SStoreOperation; +import org.hyperledger.besu.evm.precompile.KZGPointEvalPrecompiledContract; +import org.hyperledger.besu.evm.precompile.MainnetPrecompiledContracts; +import org.hyperledger.besu.evm.precompile.PrecompileContractRegistry; +import org.hyperledger.besu.evm.processor.ContractCreationProcessor; + +/** + * Provides the Services 0.51 EVM implementation, which consists of Cancun operations and + * precompiles plus the Hedera gas calculator, system contracts, and operations + * as they were configured in the 0.51 release (with support for Hedera Account Service system contract calls + * at address 0x16a). + */ +@Module +public interface V051Module { + + /** Initialization that must be performed when module is created - typically stuff from Besu's + * `BesuCommand.run()` + */ + private static void oneTimeEVMModuleInitialization() { + // KZG precompile needs to have a native library loaded, "trusted state" loaded + KZGPointEvalPrecompiledContract.init(); + } + + @Provides + @Singleton + @ServicesV051 + static TransactionProcessor provideTransactionProcessor( + @NonNull final FrameBuilder frameBuilder, + @NonNull final FrameRunner frameRunner, + @ServicesV051 @NonNull final CustomMessageCallProcessor messageCallProcessor, + @ServicesV051 @NonNull final ContractCreationProcessor contractCreationProcessor, + @NonNull final CustomGasCharging gasCharging, + @ServicesV051 @NonNull final FeatureFlags featureFlags) { + return new TransactionProcessor( + frameBuilder, frameRunner, gasCharging, messageCallProcessor, contractCreationProcessor, featureFlags); + } + + @Provides + @Singleton + @ServicesV051 + static ContractCreationProcessor provideContractCreationProcessor( + @ServicesV051 @NonNull final EVM evm, + @NonNull final GasCalculator gasCalculator, + @NonNull final Set validationRules) { + return new CustomContractCreationProcessor( + evm, + gasCalculator, + REQUIRE_CODE_DEPOSIT_TO_SUCCEED, + List.copyOf(validationRules), + INITIAL_CONTRACT_NONCE); + } + + @Provides + @Singleton + @ServicesV051 + static CustomMessageCallProcessor provideMessageCallProcessor( + @ServicesV051 @NonNull final EVM evm, + @ServicesV051 @NonNull final FeatureFlags featureFlags, + @ServicesV051 @NonNull final AddressChecks addressChecks, + @ServicesV051 @NonNull final PrecompileContractRegistry registry, + @NonNull final Map systemContracts) { + return new CustomMessageCallProcessor(evm, featureFlags, registry, addressChecks, systemContracts); + } + + @Provides + @Singleton + @ServicesV051 + static EVM provideEVM( + @ServicesV051 @NonNull final Set customOperations, + @NonNull final EvmConfiguration evmConfiguration, + @NonNull final GasCalculator gasCalculator) { + + oneTimeEVMModuleInitialization(); + + // Use Cancun EVM with 0.50 custom operations and 0x00 chain id (set at runtime) + final var operationRegistry = new OperationRegistry(); + registerCancunOperations(operationRegistry, gasCalculator, BigInteger.ZERO); + customOperations.forEach(operationRegistry::put); + return new EVM(operationRegistry, gasCalculator, evmConfiguration, EvmSpecVersion.CANCUN); + } + + @Provides + @Singleton + @ServicesV051 + static PrecompileContractRegistry providePrecompileContractRegistry(@NonNull final GasCalculator gasCalculator) { + final var precompileContractRegistry = new PrecompileContractRegistry(); + MainnetPrecompiledContracts.populateForCancun(precompileContractRegistry, gasCalculator); + return precompileContractRegistry; + } + + @Binds + @ServicesV051 + FeatureFlags bindFeatureFlags(Version051FeatureFlags featureFlags); + + @Binds + @ServicesV051 + AddressChecks bindAddressChecks(Version038AddressChecks addressChecks); + + @Provides + @IntoSet + @ServicesV051 + static Operation provideBalanceOperation( + @NonNull final GasCalculator gasCalculator, + @ServicesV051 @NonNull final AddressChecks addressChecks, + @ServicesV051 @NonNull final FeatureFlags featureFlags) { + return new CustomBalanceOperation(gasCalculator, addressChecks, featureFlags); + } + + @Provides + @IntoSet + @ServicesV051 + static Operation provideDelegateCallOperation( + @NonNull final GasCalculator gasCalculator, + @ServicesV051 @NonNull final AddressChecks addressChecks, + @ServicesV051 @NonNull final FeatureFlags featureFlags) { + return new CustomDelegateCallOperation(gasCalculator, addressChecks, featureFlags); + } + + @Provides + @IntoSet + @ServicesV051 + static Operation provideCallCodeOperation( + @NonNull final GasCalculator gasCalculator, + @ServicesV051 @NonNull final AddressChecks addressChecks, + @ServicesV051 @NonNull final FeatureFlags featureFlags) { + return new CustomCallCodeOperation(gasCalculator, addressChecks, featureFlags); + } + + @Provides + @IntoSet + @ServicesV051 + static Operation provideStaticCallOperation( + @NonNull final GasCalculator gasCalculator, + @ServicesV051 @NonNull final AddressChecks addressChecks, + @ServicesV051 @NonNull final FeatureFlags featureFlags) { + return new CustomStaticCallOperation(gasCalculator, addressChecks, featureFlags); + } + + @Provides + @IntoSet + @ServicesV051 + static Operation provideCallOperation( + @NonNull final GasCalculator gasCalculator, + @ServicesV051 @NonNull final FeatureFlags featureFlags, + @ServicesV051 @NonNull final AddressChecks addressChecks) { + return new CustomCallOperation(featureFlags, gasCalculator, addressChecks); + } + + @Provides + @IntoSet + @ServicesV051 + static Operation provideChainIdOperation(@NonNull final GasCalculator gasCalculator) { + return new CustomChainIdOperation(gasCalculator); + } + + @Provides + @IntoSet + @ServicesV051 + static Operation provideCreateOperation(@NonNull final GasCalculator gasCalculator) { + return new CustomCreateOperation(gasCalculator); + } + + @Provides + @IntoSet + @ServicesV051 + static Operation provideCreate2Operation( + @NonNull final GasCalculator gasCalculator, @ServicesV051 @NonNull final FeatureFlags featureFlags) { + return new CustomCreate2Operation(gasCalculator, featureFlags); + } + + @Provides + @Singleton + @IntoSet + @ServicesV051 + static Operation provideLog0Operation(@NonNull final GasCalculator gasCalculator) { + return new CustomLogOperation(0, gasCalculator); + } + + @Provides + @Singleton + @IntoSet + @ServicesV051 + static Operation provideLog1Operation(final GasCalculator gasCalculator) { + return new CustomLogOperation(1, gasCalculator); + } + + @Provides + @Singleton + @IntoSet + @ServicesV051 + static Operation provideLog2Operation(final GasCalculator gasCalculator) { + return new CustomLogOperation(2, gasCalculator); + } + + @Provides + @Singleton + @IntoSet + @ServicesV051 + static Operation provideLog3Operation(final GasCalculator gasCalculator) { + return new CustomLogOperation(3, gasCalculator); + } + + @Provides + @Singleton + @IntoSet + @ServicesV051 + static Operation provideLog4Operation(final GasCalculator gasCalculator) { + return new CustomLogOperation(4, gasCalculator); + } + + @Provides + @Singleton + @IntoSet + @ServicesV051 + static Operation provideExtCodeHashOperation( + @NonNull final GasCalculator gasCalculator, + @ServicesV051 @NonNull final AddressChecks addressChecks, + @ServicesV051 @NonNull final FeatureFlags featureFlags) { + return new CustomExtCodeHashOperation(gasCalculator, addressChecks, featureFlags); + } + + @Provides + @Singleton + @IntoSet + @ServicesV051 + static Operation provideExtCodeSizeOperation( + @NonNull final GasCalculator gasCalculator, + @ServicesV051 @NonNull final AddressChecks addressChecks, + @ServicesV051 @NonNull final FeatureFlags featureFlags) { + return new CustomExtCodeSizeOperation(gasCalculator, addressChecks, featureFlags); + } + + @Provides + @Singleton + @IntoSet + @ServicesV051 + static Operation provideExtCodeCopyOperation( + @NonNull final GasCalculator gasCalculator, + @ServicesV051 @NonNull final AddressChecks addressChecks, + @ServicesV051 @NonNull final FeatureFlags featureFlags) { + return new CustomExtCodeCopyOperation(gasCalculator, addressChecks, featureFlags); + } + + @Provides + @Singleton + @IntoSet + @ServicesV051 + static Operation providePrevRandaoOperation(@NonNull final GasCalculator gasCalculator) { + return new CustomPrevRandaoOperation(gasCalculator); + } + + @Provides + @Singleton + @IntoSet + @ServicesV051 + static Operation provideSelfDestructOperation( + @NonNull final GasCalculator gasCalculator, @ServicesV051 @NonNull final AddressChecks addressChecks) { + // Here we adopt EIP-6780 semantics, for SELFDESTRUCT, for the first time + return new CustomSelfDestructOperation(gasCalculator, addressChecks, UseEIP6780Semantics.YES); + } + + @Provides + @IntoSet + @ServicesV051 + static Operation provideSLoadOperation( + @NonNull final GasCalculator gasCalculator, @ServicesV051 @NonNull final FeatureFlags featureFlags) { + return new CustomSLoadOperation(featureFlags, new SLoadOperation(gasCalculator)); + } + + @Provides + @IntoSet + @ServicesV051 + static Operation provideSStoreOperation( + @NonNull final GasCalculator gasCalculator, @ServicesV051 @NonNull final FeatureFlags featureFlags) { + return new CustomSStoreOperation(featureFlags, new SStoreOperation(gasCalculator, FRONTIER_MINIMUM)); + } +} diff --git a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/v051/Version051FeatureFlags.java b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/v051/Version051FeatureFlags.java new file mode 100644 index 000000000000..f13f8070919f --- /dev/null +++ b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/v051/Version051FeatureFlags.java @@ -0,0 +1,40 @@ +/* + * Copyright (C) 2024 Hedera Hashgraph, LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.hedera.node.app.service.contract.impl.exec.v051; + +import static java.util.Objects.requireNonNull; + +import com.hedera.node.app.service.contract.impl.exec.v050.Version050FeatureFlags; +import com.hedera.node.config.data.ContractsConfig; +import com.swirlds.config.api.Configuration; +import edu.umd.cs.findbugs.annotations.NonNull; +import javax.inject.Inject; +import javax.inject.Singleton; + +@Singleton +public class Version051FeatureFlags extends Version050FeatureFlags { + @Inject + public Version051FeatureFlags() { + // Dagger2 + } + + @Override + public boolean isHederaAccountServiceEnabled(@NonNull Configuration config) { + requireNonNull(config); + return config.getConfigData(ContractsConfig.class).systemContractAccountServiceEnabled(); + } +} diff --git a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/handlers/ContractCallLocalHandler.java b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/handlers/ContractCallLocalHandler.java index 0d02854260d2..99b3b7f58db8 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/handlers/ContractCallLocalHandler.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/handlers/ContractCallLocalHandler.java @@ -95,8 +95,9 @@ public void validate(@NonNull final QueryContext context) throws PreCheckExcepti // to call) final var contract = context.createStore(ReadableAccountStore.class).getContractById(contractID); if (contract == null) { - final var tokenID = - TokenID.newBuilder().tokenNum(contractID.contractNum()).build(); + final var tokenID = TokenID.newBuilder() + .tokenNum(contractID.contractNumOrElse(0L)) + .build(); final var tokenContract = context.createStore(ReadableTokenStore.class).get(tokenID); mustExist(tokenContract, INVALID_CONTRACT_ID); diff --git a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/handlers/ContractGetInfoHandler.java b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/handlers/ContractGetInfoHandler.java index 384bf5452e3e..4e10fdd6f0b8 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/handlers/ContractGetInfoHandler.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/handlers/ContractGetInfoHandler.java @@ -23,6 +23,7 @@ import static com.hedera.node.app.service.token.api.AccountSummariesApi.hexedEvmAddressOf; import static com.hedera.node.app.service.token.api.AccountSummariesApi.summarizeStakingInfo; import static com.hedera.node.app.service.token.api.AccountSummariesApi.tokenRelationshipsOf; +import static com.hedera.node.app.spi.fees.Fees.CONSTANT_FEE_DATA; import static com.hedera.node.app.spi.workflows.PreCheckException.validateFalsePreCheck; import static java.util.Objects.requireNonNull; @@ -50,7 +51,6 @@ import com.hedera.node.config.data.LedgerConfig; import com.hedera.node.config.data.StakingConfig; import com.hedera.node.config.data.TokensConfig; -import com.hederahashgraph.api.proto.java.FeeData; import edu.umd.cs.findbugs.annotations.NonNull; import edu.umd.cs.findbugs.annotations.Nullable; import javax.inject.Inject; @@ -114,7 +114,7 @@ public Fees computeFees(@NonNull final QueryContext context) { return context.feeCalculator().legacyCalculate(sigValueObj -> { final var contract = contractFrom(context); if (contract == null) { - return FeeData.getDefaultInstance(); + return CONSTANT_FEE_DATA; } else { return ContractGetInfoUsage.newEstimate(fromPbj(context.query())) .givenCurrentKey(fromPbj(contract.keyOrThrow())) diff --git a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/handlers/ContractUpdateHandler.java b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/handlers/ContractUpdateHandler.java index 79066677331c..a962f6eb9bd4 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/handlers/ContractUpdateHandler.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/handlers/ContractUpdateHandler.java @@ -298,8 +298,11 @@ public Account update( public Fees calculateFees(@NonNull final FeeContext feeContext) { requireNonNull(feeContext); final var op = feeContext.body(); + final var contractId = op.contractUpdateInstanceOrThrow().contractIDOrElse(ContractID.DEFAULT); + final var accountStore = feeContext.readableStore(ReadableAccountStore.class); + final var contract = accountStore.getContractById(contractId); return feeContext.feeCalculator(SubType.DEFAULT).legacyCalculate(sigValueObj -> new ContractUpdateResourceUsage( new SmartContractFeeBuilder()) - .usageGiven(fromPbj(op), sigValueObj, null)); + .usageGiven(fromPbj(op), sigValueObj, contract)); } } diff --git a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/hevm/HederaEvmBlocks.java b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/hevm/HederaEvmBlocks.java index d28ace80f61e..22611eb0a1fe 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/hevm/HederaEvmBlocks.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/hevm/HederaEvmBlocks.java @@ -18,9 +18,6 @@ import com.hedera.node.app.service.evm.contracts.execution.BlockMetaSource; import com.hedera.node.app.service.evm.contracts.execution.HederaEvmTxProcessor; -import com.swirlds.common.crypto.DigestType; -import com.swirlds.common.crypto.ImmutableHash; -import edu.umd.cs.findbugs.annotations.NonNull; import org.apache.tuweni.bytes.Bytes32; import org.hyperledger.besu.datatypes.Hash; import org.hyperledger.besu.evm.frame.BlockValues; @@ -29,7 +26,7 @@ * Provides block information as context for a {@link HederaEvmTxProcessor}. */ public interface HederaEvmBlocks { - Hash UNAVAILABLE_BLOCK_HASH = besuHashFrom(new ImmutableHash(new byte[DigestType.SHA_384.digestLength()])); + Hash UNAVAILABLE_BLOCK_HASH = org.hyperledger.besu.datatypes.Hash.wrap(Bytes32.wrap(new byte[32])); /** * Returns the hash of the given block number, or {@link BlockMetaSource#UNAVAILABLE_BLOCK_HASH} @@ -47,11 +44,4 @@ public interface HederaEvmBlocks { * @return the scoped block values */ BlockValues blockValuesOf(long gasLimit); - - private static Hash besuHashFrom(@NonNull final com.swirlds.common.crypto.Hash hash) { - final byte[] hashBytesToConvert = hash.getValue(); - final byte[] prefixBytes = new byte[32]; - System.arraycopy(hashBytesToConvert, 0, prefixBytes, 0, 32); - return org.hyperledger.besu.datatypes.Hash.wrap(Bytes32.wrap(prefixBytes)); - } } diff --git a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/hevm/HederaEvmVersion.java b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/hevm/HederaEvmVersion.java index 948b728b24d4..1079047a4cd1 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/hevm/HederaEvmVersion.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/hevm/HederaEvmVersion.java @@ -23,14 +23,16 @@ public enum HederaEvmVersion { VERSION_034("v0.34"), VERSION_038("v0.38"), VERSION_046("v0.46"), - VERSION_050("v0.50") /* Cancun */; + VERSION_050("v0.50"), /* Cancun */ + VERSION_051("v0.51") /* Hedera Account Service System Contract */; public static final Map EVM_VERSIONS = Map.of( VERSION_030.key(), VERSION_030, VERSION_034.key(), VERSION_034, VERSION_038.key(), VERSION_038, VERSION_046.key(), VERSION_046, - VERSION_050.key(), VERSION_050); + VERSION_050.key(), VERSION_050, + VERSION_051.key(), VERSION_051); HederaEvmVersion(String key) { this.key = key; diff --git a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/hevm/HederaWorldUpdater.java b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/hevm/HederaWorldUpdater.java index b4f0dba255b6..f6942487f9f5 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/hevm/HederaWorldUpdater.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/hevm/HederaWorldUpdater.java @@ -288,11 +288,6 @@ void externalizeSystemContractResults( @NonNull ExchangeRate currentExchangeRate(); - /** - * Revert the last child record. - */ - void revertChildRecords(); - /** * Sets the world updater to not check for the existence of the contractId * in the ledger when the getHederaContractId() method is called diff --git a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/hevm/HevmPropagatedCallFailure.java b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/hevm/HevmPropagatedCallFailure.java index ec9c8ab805bc..361090866ca9 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/hevm/HevmPropagatedCallFailure.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/hevm/HevmPropagatedCallFailure.java @@ -16,9 +16,6 @@ package com.hedera.node.app.service.contract.impl.hevm; -import static com.hedera.node.app.service.contract.impl.exec.failure.CustomExceptionalHaltReason.INSUFFICIENT_CHILD_RECORDS; -import static com.hedera.node.app.service.contract.impl.exec.failure.CustomExceptionalHaltReason.INVALID_SIGNATURE; - import com.hedera.node.app.service.contract.impl.exec.failure.CustomExceptionalHaltReason; import edu.umd.cs.findbugs.annotations.Nullable; import java.util.Optional; @@ -37,11 +34,15 @@ public enum HevmPropagatedCallFailure { /** * The call failed due to a missing signature on the receiver account. */ - MISSING_RECEIVER_SIGNATURE(INVALID_SIGNATURE), + MISSING_RECEIVER_SIGNATURE(CustomExceptionalHaltReason.INVALID_SIGNATURE), /** * The call failed because its externalizing its result would exceed the maximum number of child records. */ - RESULT_CANNOT_BE_EXTERNALIZED(INSUFFICIENT_CHILD_RECORDS); + RESULT_CANNOT_BE_EXTERNALIZED(CustomExceptionalHaltReason.INSUFFICIENT_CHILD_RECORDS), + /** + * The call failed because invalid fee was submitted for an EVM call + */ + INVALID_FEE_SUBMITTED(CustomExceptionalHaltReason.INVALID_FEE_SUBMITTED); private final @Nullable CustomExceptionalHaltReason exceptionalHaltReason; diff --git a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/hevm/HydratedEthTxData.java b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/hevm/HydratedEthTxData.java index dac423c4ff7e..ae559bf47797 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/hevm/HydratedEthTxData.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/hevm/HydratedEthTxData.java @@ -43,4 +43,8 @@ public static HydratedEthTxData failureFrom(@NonNull final ResponseCodeEnum stat public boolean isAvailable() { return ethTxData != null; } + + public @NonNull EthTxData ethTxDataOrThrow() { + return requireNonNull(ethTxData); + } } diff --git a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/infra/EthereumCallDataHydration.java b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/infra/EthereumCallDataHydration.java index 03d8a9e94ddc..13b1370d51d6 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/infra/EthereumCallDataHydration.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/infra/EthereumCallDataHydration.java @@ -74,15 +74,23 @@ public HydratedEthTxData tryToHydrate( if (callDataFile.deleted()) { return failureFrom(FILE_DELETED); } + + // Bytes.fromHex() doesn't appreciate a leading '0x' but we supported it in mono-service + final var hexPrefix = new byte[] {(byte) '0', (byte) 'x'}; + final var contents = callDataFile.contents(); + final var offset = contents.matchesPrefix(hexPrefix) ? hexPrefix.length : 0L; + final var len = contents.length() - offset; final byte[] callData; try { - callData = Hex.decode(callDataFile.contents().toByteArray()); + callData = Hex.decode(contents.getBytes(offset, len).toByteArray()); } catch (final DecoderException ignore) { return failureFrom(INVALID_FILE_ID); } + if (callData.length == 0) { return failureFrom(CONTRACT_FILE_EMPTY); } + return successFrom(ethTxData.replaceCallData(callData)); } else { return successFrom(ethTxData); diff --git a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/infra/HevmTransactionFactory.java b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/infra/HevmTransactionFactory.java index 3b69ea86dd07..b46208619be2 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/infra/HevmTransactionFactory.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/infra/HevmTransactionFactory.java @@ -59,6 +59,7 @@ import com.hedera.node.app.service.contract.impl.ContractServiceImpl; import com.hedera.node.app.service.contract.impl.annotations.InitialState; import com.hedera.node.app.service.contract.impl.annotations.TransactionScope; +import com.hedera.node.app.service.contract.impl.exec.FeatureFlags; import com.hedera.node.app.service.contract.impl.hevm.HederaEvmContext; import com.hedera.node.app.service.contract.impl.hevm.HederaEvmTransaction; import com.hedera.node.app.service.contract.impl.hevm.HydratedEthTxData; @@ -86,6 +87,7 @@ public class HevmTransactionFactory { private final NetworkInfo networkInfo; private final LedgerConfig ledgerConfig; private final HederaConfig hederaConfig; + private final FeatureFlags featureFlags; private final GasCalculator gasCalculator; private final StakingConfig stakingConfig; private final ContractsConfig contractsConfig; @@ -103,6 +105,7 @@ public HevmTransactionFactory( @NonNull final NetworkInfo networkInfo, @NonNull final LedgerConfig ledgerConfig, @NonNull final HederaConfig hederaConfig, + @NonNull final FeatureFlags featureFlags, @NonNull final GasCalculator gasCalculator, @NonNull final StakingConfig stakingConfig, @NonNull final ContractsConfig contractsConfig, @@ -114,6 +117,7 @@ public HevmTransactionFactory( @NonNull @InitialState final TokenServiceApi tokenServiceApi, @NonNull final EthTxSigsCache ethereumSignatures, @NonNull final HederaEvmContext hederaEvmContext) { + this.featureFlags = featureFlags; this.hydratedEthTxData = hydratedEthTxData; this.gasCalculator = requireNonNull(gasCalculator); this.fileStore = requireNonNull(fileStore); @@ -126,7 +130,7 @@ public HevmTransactionFactory( this.tokenServiceApi = requireNonNull(tokenServiceApi); this.expiryValidator = requireNonNull(expiryValidator); this.attributeValidator = requireNonNull(attributeValidator); - this.ethereumSignatures = ethereumSignatures; + this.ethereumSignatures = requireNonNull(ethereumSignatures); this.hederaEvmContext = requireNonNull(hederaEvmContext); } @@ -202,6 +206,7 @@ private HederaEvmTransaction fromHapiEthereum( @NonNull final AccountID senderId, @NonNull final EthTxData ethTxData, final long maxGasAllowance) { + validateTrue(ethTxData.getAmount() >= 0, CONTRACT_NEGATIVE_VALUE); return new HederaEvmTransaction( senderId, relayerId, @@ -295,8 +300,10 @@ private void assertValidCall(@NonNull final ContractCallTransactionBody body) { validateTrue(body.gas() <= contractsConfig.maxGasPerSec(), MAX_GAS_LIMIT_EXCEEDED); final var contract = accountStore.getContractById(body.contractIDOrThrow()); - if (contract != null && !contractsConfig.evmAllowCallsToNonContractAccounts()) { - validateFalse(contract.deleted(), CONTRACT_DELETED); + if (contract != null) { + final var contractNum = contract.accountIdOrThrow().accountNumOrThrow(); + final var mayNotExist = featureFlags.isAllowCallsToNonContractAccountsEnabled(contractsConfig, contractNum); + validateTrue(mayNotExist || !contract.deleted(), CONTRACT_DELETED); } } diff --git a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/infra/IterableStorageManager.java b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/infra/IterableStorageManager.java index 2961445cfaac..d43b5052e5c2 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/infra/IterableStorageManager.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/infra/IterableStorageManager.java @@ -17,6 +17,7 @@ package com.hedera.node.app.service.contract.impl.infra; import static com.hedera.node.app.service.contract.impl.utils.ConversionUtils.tuweniToPbjBytes; +import static java.util.Objects.requireNonNull; import com.hedera.hapi.node.base.ContractID; import com.hedera.hapi.node.state.contract.SlotKey; @@ -32,7 +33,6 @@ import java.util.HashMap; import java.util.List; import java.util.Map; -import java.util.Objects; import javax.inject.Inject; import javax.inject.Singleton; import org.apache.logging.log4j.LogManager; @@ -59,7 +59,7 @@ public IterableStorageManager() { * *

    Besides updating the first keys of these linked lists in the scoped accounts, also updates the * slots used per contract via - * {@link HandleHederaOperations#updateStorageMetadata(long, Bytes, int)}. + * {@link HandleHederaOperations#updateStorageMetadata(ContractID, Bytes, int)}. * * @param enhancement the enhancement for the current transaction * @param allAccesses the pending changes to storage values @@ -77,19 +77,31 @@ public void persistChanges( // Adjust the storage linked lists for each contract allAccesses.forEach(contractAccesses -> contractAccesses.accesses().forEach(access -> { if (access.isUpdate()) { - var firstContractKey = contractFirstKeyOf(enhancement, contractAccesses.contractID()); - - switch (StorageAccessType.getAccessType(access)) { - case REMOVAL -> firstContractKey = removeAccessedValue( - store, firstContractKey, contractAccesses.contractID(), tuweniToPbjBytes(access.key())); - case INSERTION -> firstContractKey = insertAccessedValue( - store, - firstContractKey, - tuweniToPbjBytes(access.writtenValue()), - contractAccesses.contractID(), - tuweniToPbjBytes(access.key())); - } - firstKeys.put(contractAccesses.contractID(), firstContractKey); + final var contractId = contractAccesses.contractID(); + // If we have already changed the head pointer for this contract, + // use that; otherwise, get the contract's head pointer from state + final var firstContractKey = + firstKeys.computeIfAbsent(contractId, cid -> contractFirstKeyOf(enhancement, contractId)); + + // Only certain access types can change the head slot in a contract's storage linked list + final var newFirstContractKey = + switch (StorageAccessType.getAccessType(access)) { + case UNKNOWN, READ_ONLY, UPDATE -> firstContractKey; + // We might be removing the head slot from the existing list + case REMOVAL -> removeAccessedValue( + store, + firstContractKey, + contractAccesses.contractID(), + tuweniToPbjBytes(access.key())); + // We always insert the new slot at the head + case INSERTION -> insertAccessedValue( + store, + firstContractKey, + tuweniToPbjBytes(requireNonNull(access.writtenValue())), + contractAccesses.contractID(), + tuweniToPbjBytes(access.key())); + }; + firstKeys.put(contractAccesses.contractID(), newFirstContractKey); } })); @@ -108,16 +120,15 @@ public void persistChanges( /** * Returns the first storage key for the contract or Bytes.Empty if none exists. + * * @param enhancement the enhancement for the current transaction - * @param contractNumber the contract number + * @param contractID the contract id * @return the first storage key for the contract or null if none exists. */ @NonNull - private Bytes contractFirstKeyOf(@NonNull final Enhancement enhancement, ContractID contractID) { + private Bytes contractFirstKeyOf(@NonNull final Enhancement enhancement, @NonNull final ContractID contractID) { final var account = enhancement.nativeOperations().getAccount(contractID); - return account != null && account.firstContractStorageKey() != null - ? account.firstContractStorageKey() - : Bytes.EMPTY; + return account != null ? account.firstContractStorageKey() : Bytes.EMPTY; } /** @@ -132,46 +143,34 @@ private Bytes contractFirstKeyOf(@NonNull final Enhancement enhancement, Contrac @NonNull private Bytes removeAccessedValue( @NonNull final ContractStateStore store, - @NonNull final Bytes firstContractKey, - ContractID contractID, + @NonNull Bytes firstContractKey, + @NonNull final ContractID contractID, @NonNull final Bytes key) { + requireNonNull(firstContractKey); + requireNonNull(contractID); + requireNonNull(store); + requireNonNull(key); + final var slotKey = new SlotKey(contractID, key); try { - Objects.requireNonNull(store); - Objects.requireNonNull(key); - final var slotKey = newSlotKeyFor(contractID, key); final var slotValue = slotValueFor(store, false, slotKey, "Missing key "); final var nextKey = slotValue.nextKey(); final var prevKey = slotValue.previousKey(); - - if (!nextKey.equals(Bytes.EMPTY)) { - // Look up the next slot value - final var nextSlotKey = newSlotKeyFor(contractID, nextKey); - final var nextValue = slotValueFor(store, true, nextSlotKey, "Missing next key "); - - // Create new next value and put into the store - final var newNextValue = - nextValue.copyBuilder().previousKey(prevKey).build(); - store.putSlot(nextSlotKey, newNextValue); + if (!Bytes.EMPTY.equals(nextKey)) { + updatePrevFor(new SlotKey(contractID, nextKey), prevKey, store); } - if (!prevKey.equals(Bytes.EMPTY)) { - // Look up the previous slot value - final var prevSlotKey = newSlotKeyFor(contractID, prevKey); - final var prevValue = slotValueFor(store, true, prevSlotKey, "Missing previous key "); - - // Create new previous value and put into the store - final var newPrevValue = - prevValue.copyBuilder().nextKey(nextKey).build(); - store.putSlot(prevSlotKey, newPrevValue); + if (!Bytes.EMPTY.equals(prevKey)) { + updateNextFor(new SlotKey(contractID, prevKey), nextKey, store); } - store.removeSlot(slotKey); - return key.equals(firstContractKey) ? slotValue.nextKey() : firstContractKey; + firstContractKey = key.equals(firstContractKey) ? nextKey : firstContractKey; } catch (Exception irreparable) { + // Since maintaining linked lists is not mission-critical, just log the error and continue log.error( - "Failed link management when removing {}; will be unable to" - + " expire all slots for this contract", + "Failed link management when removing {}; will be unable to" + " expire all slots for contract {}", key, + contractID, irreparable); } + store.removeSlot(slotKey); return firstContractKey; } @@ -190,30 +189,37 @@ private Bytes insertAccessedValue( @NonNull final ContractStateStore store, @NonNull final Bytes firstContractKey, @NonNull final Bytes newValue, - ContractID contractID, + @NonNull final ContractID contractID, @NonNull final Bytes newKey) { + requireNonNull(store); + requireNonNull(newKey); + requireNonNull(newValue); try { - Objects.requireNonNull(store); - Objects.requireNonNull(newValue); - Objects.requireNonNull(newKey); - // Create new slot key and value and put into the store - final var newSlotKey = newSlotKeyFor(contractID, newKey); - final var newSlotValue = SlotValue.newBuilder() - .value(newValue) - .previousKey(Bytes.EMPTY) - .nextKey(firstContractKey) - .build(); - store.putSlot(newSlotKey, newSlotValue); - return newKey; + if (!Bytes.EMPTY.equals(firstContractKey)) { + updatePrevFor(new SlotKey(contractID, firstContractKey), newKey, store); + } } catch (Exception irreparable) { - log.error("Failed link management when inserting {}", newKey, irreparable); + // Since maintaining linked lists is not mission-critical, just log the error and continue + log.error( + "Failed link management when inserting {}; will be unable to" + " expire all slots for contract {}", + newKey, + contractID, + irreparable); } - return firstContractKey; + store.putSlot(new SlotKey(contractID, newKey), new SlotValue(newValue, Bytes.EMPTY, firstContractKey)); + return newKey; } - @NonNull - private SlotKey newSlotKeyFor(ContractID contractNumber, @NonNull final Bytes key) { - return new SlotKey(contractNumber, key); + private void updatePrevFor( + @NonNull final SlotKey key, @NonNull final Bytes newPrevKey, @NonNull final ContractStateStore store) { + final var value = slotValueFor(store, true, key, "Missing next key "); + store.putSlot(key, value.copyBuilder().previousKey(newPrevKey).build()); + } + + private void updateNextFor( + @NonNull final SlotKey key, @NonNull final Bytes newNextKey, @NonNull final ContractStateStore store) { + final var value = slotValueFor(store, true, key, "Missing prev key "); + store.putSlot(key, value.copyBuilder().nextKey(newNextKey).build()); } @NonNull @@ -223,7 +229,7 @@ private SlotValue slotValueFor( @NonNull final SlotKey slotKey, @NonNull final String msgOnError) { return forModify - ? Objects.requireNonNull(store.getSlotValueForModify(slotKey), () -> msgOnError + slotKey.key()) - : Objects.requireNonNull(store.getSlotValue(slotKey), () -> msgOnError + slotKey.key()); + ? requireNonNull(store.getSlotValueForModify(slotKey), () -> msgOnError + slotKey.key()) + : requireNonNull(store.getSlotValue(slotKey), () -> msgOnError + slotKey.key()); } } diff --git a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/state/DispatchingEvmFrameState.java b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/state/DispatchingEvmFrameState.java index 7c8345c710ac..caf34b98942a 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/state/DispatchingEvmFrameState.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/state/DispatchingEvmFrameState.java @@ -52,7 +52,7 @@ import com.hedera.node.app.service.contract.impl.exec.scope.HandleHederaNativeOperations; import com.hedera.node.app.service.contract.impl.exec.scope.HederaNativeOperations; import com.hedera.node.app.spi.HapiUtils; -import com.hedera.node.app.spi.state.WritableKVState; +import com.swirlds.state.spi.WritableKVState; import edu.umd.cs.findbugs.annotations.NonNull; import edu.umd.cs.findbugs.annotations.Nullable; import java.util.ArrayList; diff --git a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/state/HederaEvmAccount.java b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/state/HederaEvmAccount.java index 3b829b5edb24..728ff74f5d8b 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/state/HederaEvmAccount.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/state/HederaEvmAccount.java @@ -38,6 +38,13 @@ public interface HederaEvmAccount extends MutableAccount { */ boolean isTokenFacade(); + /** + * Returns whether this account is a regular account. + * + * @return whether this account is regular account + */ + boolean isRegularAccount(); + /** * Returns the Hedera account id for this account. * diff --git a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/state/InitialModServiceContractSchema.java b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/state/InitialModServiceContractSchema.java index fa9841436760..09c7a575fb14 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/state/InitialModServiceContractSchema.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/state/InitialModServiceContractSchema.java @@ -31,9 +31,9 @@ import com.hedera.node.app.spi.state.MigrationContext; import com.hedera.node.app.spi.state.Schema; import com.hedera.node.app.spi.state.StateDefinition; -import com.hedera.node.app.spi.state.WritableKVStateBase; import com.hedera.pbj.runtime.io.buffer.Bytes; import com.swirlds.common.threading.manager.AdHocThreadManager; +import com.swirlds.platform.state.spi.WritableKVStateBase; import edu.umd.cs.findbugs.annotations.NonNull; import edu.umd.cs.findbugs.annotations.Nullable; import java.util.ArrayList; diff --git a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/state/ProxyEvmAccount.java b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/state/ProxyEvmAccount.java index 097dc389e2ec..edddf23120b9 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/state/ProxyEvmAccount.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/state/ProxyEvmAccount.java @@ -123,6 +123,14 @@ public boolean isTokenFacade() { return false; } + /** + * {@inheritDoc} + */ + @Override + public boolean isRegularAccount() { + return !isContract(); + } + @Override public @NonNull AccountID hederaId() { return accountID; diff --git a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/state/ProxyWorldUpdater.java b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/state/ProxyWorldUpdater.java index 961c53a1d499..52210895c82e 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/state/ProxyWorldUpdater.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/state/ProxyWorldUpdater.java @@ -175,20 +175,16 @@ public ProxyWorldUpdater( public ContractID getHederaContractId(@NonNull final Address address) { requireNonNull(address); final var account = (HederaEvmAccount) get(address); - // As an important special case, return the pending creation's contract ID if - // its address matches and there is no extant account; but still prioritize - // existing accounts of course if (account == null) { - // If configured to allow non-existent contracts, return the address as a contract ID if the account is - // not found. - if (!contractMustBePresent) { - return isLongZero(address) ? asNumberedContractId(address) : asEvmContractId(address); - } + // Also return ids for pending creations if (pendingCreation != null && pendingCreation.address().equals(address)) { return ContractID.newBuilder() .contractNum(pendingCreation.number()) .build(); } else { + if (!contractMustBePresent) { + return isLongZero(address) ? asNumberedContractId(address) : asEvmContractId(address); + } throw new IllegalArgumentException("No contract pending or extant at " + address); } } @@ -410,16 +406,6 @@ public void revert() { reverted = true; } - /** - * {@inheritDoc} - */ - @Override - public void revertChildRecords() { - if (recordListCheckPoint != null) { - enhancement.operations().revertRecordsFrom(recordListCheckPoint); - } - } - /** * {@inheritDoc} */ diff --git a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/state/ReadableContractStateStore.java b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/state/ReadableContractStateStore.java index 63e6cd7c26bf..f6c7ea57276a 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/state/ReadableContractStateStore.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/state/ReadableContractStateStore.java @@ -22,8 +22,8 @@ import com.hedera.hapi.node.state.contract.Bytecode; import com.hedera.hapi.node.state.contract.SlotKey; import com.hedera.hapi.node.state.contract.SlotValue; -import com.hedera.node.app.spi.state.ReadableKVState; -import com.hedera.node.app.spi.state.ReadableStates; +import com.swirlds.state.spi.ReadableKVState; +import com.swirlds.state.spi.ReadableStates; import edu.umd.cs.findbugs.annotations.NonNull; import edu.umd.cs.findbugs.annotations.Nullable; import java.util.Collections; diff --git a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/state/TokenEvmAccount.java b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/state/TokenEvmAccount.java index 5a757942ef0d..16016ac6c640 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/state/TokenEvmAccount.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/state/TokenEvmAccount.java @@ -136,6 +136,14 @@ public boolean isTokenFacade() { return true; } + /** + * {@inheritDoc} + */ + @Override + public boolean isRegularAccount() { + return false; + } + @Override public @NonNull AccountID hederaId() { throw new IllegalStateException("Token facade has no usable Hedera id"); diff --git a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/state/WritableContractStateStore.java b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/state/WritableContractStateStore.java index 376b368e7558..4063a16e9d77 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/state/WritableContractStateStore.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/state/WritableContractStateStore.java @@ -24,10 +24,10 @@ import com.hedera.hapi.node.state.contract.SlotValue; import com.hedera.node.app.spi.metrics.StoreMetricsService; import com.hedera.node.app.spi.metrics.StoreMetricsService.StoreType; -import com.hedera.node.app.spi.state.WritableKVState; -import com.hedera.node.app.spi.state.WritableStates; import com.hedera.node.config.data.ContractsConfig; import com.swirlds.config.api.Configuration; +import com.swirlds.state.spi.WritableKVState; +import com.swirlds.state.spi.WritableStates; import edu.umd.cs.findbugs.annotations.NonNull; import edu.umd.cs.findbugs.annotations.Nullable; import java.util.Set; diff --git a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/utils/ConversionUtils.java b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/utils/ConversionUtils.java index e21296d04d8a..7e76a7eecca0 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/utils/ConversionUtils.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/utils/ConversionUtils.java @@ -726,14 +726,15 @@ public static long fromTinycentsToTinybars(final ExchangeRate exchangeRate, fina * Given a {@link ContractID} return the corresponding Besu {@link Address} * Importantly, this method does NOT check for the existence of the contract in the ledger * - * @param contractId + * @param contractId the contract id * @return the equivalent Besu address */ public static @NonNull Address contractIDToBesuAddress(final ContractID contractId) { if (contractId.hasEvmAddress()) { - return pbjToBesuAddress(contractId.evmAddress()); + return pbjToBesuAddress(contractId.evmAddressOrThrow()); } else { - return asLongZeroAddress(contractId.contractNumOrThrow()); + // OrElse(0) is needed, as an UNSET contract OneOf has null number + return asLongZeroAddress(contractId.contractNumOrElse(0L)); } } diff --git a/hedera-node/hedera-smart-contract-service-impl/src/main/java/module-info.java b/hedera-node/hedera-smart-contract-service-impl/src/main/java/module-info.java index efb39e17768f..0c5cdcb784ef 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/main/java/module-info.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/main/java/module-info.java @@ -10,6 +10,7 @@ requires transitive com.hedera.node.hapi; requires transitive com.hedera.pbj.runtime; requires transitive com.swirlds.config.api; + requires transitive com.swirlds.state.api; requires transitive dagger; requires transitive headlong; requires transitive javax.inject; @@ -18,12 +19,13 @@ requires transitive org.hyperledger.besu.evm; requires transitive tuweni.bytes; requires transitive tuweni.units; - requires com.hedera.node.app.service.evm; requires com.github.benmanes.caffeine; requires com.google.common; requires com.google.protobuf; + requires com.hedera.evm; requires com.swirlds.base; requires com.swirlds.common; + requires com.swirlds.platform.core; requires org.bouncycastle.provider; requires static com.github.spotbugs.annotations; requires static java.compiler; // javax.annotation.processing.Generated diff --git a/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/TestHelpers.java b/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/TestHelpers.java index 96d58d4f9ab6..040b9128e368 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/TestHelpers.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/TestHelpers.java @@ -75,6 +75,7 @@ import com.hedera.node.app.service.contract.impl.exec.scope.ActiveContractVerificationStrategy.UseTopLevelSigs; import com.hedera.node.app.service.contract.impl.exec.scope.VerificationStrategy; import com.hedera.node.app.service.contract.impl.exec.systemcontracts.FullResult; +import com.hedera.node.app.service.contract.impl.exec.systemcontracts.has.HasCallAttempt; import com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.HtsCallAttempt; import com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.TokenTupleUtils.TokenKeyType; import com.hedera.node.app.service.contract.impl.exec.utils.PendingCreationMetadataRef; @@ -228,6 +229,14 @@ public class TestHelpers { public static final com.esaulpaugh.headlong.abi.Address NON_FUNGIBLE_TOKEN_HEADLONG_ADDRESS = asHeadlongAddress(asEvmAddress(NON_FUNGIBLE_TOKEN_ID.tokenNum())); + public static final Account A_DELETED_CONTRACT = Account.newBuilder() + .deleted(true) + .smartContract(true) + .accountId(AccountID.newBuilder() + .accountNum(CALLED_CONTRACT_ID.contractNumOrThrow()) + .build()) + .build(); + public static final Token FUNGIBLE_TOKEN = Token.newBuilder() .tokenId(FUNGIBLE_TOKEN_ID) .name("Fungible Token") @@ -828,6 +837,21 @@ public static org.apache.tuweni.bytes.Bytes bytesForRedirect(final byte[] subSel org.apache.tuweni.bytes.Bytes.of(subSelector)); } + // Encode given a ByteBuffer and accountId input bytes for a call to a given contract. + // Largely, this is used to encode the call to redirectToAccount() proxy contract for testing purposes. + public static org.apache.tuweni.bytes.Bytes bytesForRedirectAccount( + final ByteBuffer encodedCall, final AccountID accountID) { + return bytesForRedirectAccount(encodedCall.array(), asLongZeroAddress(accountID.accountNum())); + } + + public static org.apache.tuweni.bytes.Bytes bytesForRedirectAccount( + final byte[] subSelector, final Address accountAddress) { + return org.apache.tuweni.bytes.Bytes.concatenate( + org.apache.tuweni.bytes.Bytes.wrap(HasCallAttempt.REDIRECT_FOR_ACCOUNT.selector()), + accountAddress, + org.apache.tuweni.bytes.Bytes.of(subSelector)); + } + public static org.apache.tuweni.bytes.Bytes asBytesResult(final ByteBuffer encoded) { return org.apache.tuweni.bytes.Bytes.wrap(encoded.array()); } diff --git a/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/ContextTransactionProcessorTest.java b/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/ContextTransactionProcessorTest.java index 752b20bdfd0b..367916972aa6 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/ContextTransactionProcessorTest.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/ContextTransactionProcessorTest.java @@ -98,7 +98,6 @@ class ContextTransactionProcessorTest { @Test void callsComponentInfraAsExpectedForValidEthTx() { final var contractsConfig = CONFIGURATION.getConfigData(ContractsConfig.class); - final var processors = processorsForAllCurrentEvmVersions(processor); final var hydratedEthTxData = HydratedEthTxData.successFrom(ETH_DATA_WITH_TO_ADDRESS); final var subject = new ContextTransactionProcessor( hydratedEthTxData, @@ -110,7 +109,7 @@ void callsComponentInfraAsExpectedForValidEthTx() { rootProxyWorldUpdater, hevmTransactionFactory, feesOnlyUpdater, - processors, + processor, customGasCharging); givenSenderAccount(); @@ -136,7 +135,6 @@ void callsComponentInfraAsExpectedForValidEthTx() { @Test void callsComponentInfraAsExpectedForValidEthTxWithoutTo() { final var contractsConfig = CONFIGURATION.getConfigData(ContractsConfig.class); - final var processors = processorsForAllCurrentEvmVersions(processor); final var hydratedEthTxData = HydratedEthTxData.successFrom(ETH_DATA_WITHOUT_TO_ADDRESS); final var subject = new ContextTransactionProcessor( hydratedEthTxData, @@ -148,7 +146,7 @@ void callsComponentInfraAsExpectedForValidEthTxWithoutTo() { rootProxyWorldUpdater, hevmTransactionFactory, feesOnlyUpdater, - processors, + processor, customGasCharging); givenSenderAccount(); @@ -174,7 +172,6 @@ void callsComponentInfraAsExpectedForValidEthTxWithoutTo() { @Test void callsComponentInfraAsExpectedForNonEthTx() { final var contractsConfig = CONFIGURATION.getConfigData(ContractsConfig.class); - final var processors = processorsForAllCurrentEvmVersions(processor); final var subject = new ContextTransactionProcessor( null, context, @@ -185,7 +182,7 @@ void callsComponentInfraAsExpectedForNonEthTx() { rootProxyWorldUpdater, hevmTransactionFactory, feesOnlyUpdater, - processors, + processor, customGasCharging); given(context.body()).willReturn(TransactionBody.DEFAULT); @@ -215,7 +212,7 @@ void stillChargesHapiFeesOnAbort() { rootProxyWorldUpdater, hevmTransactionFactory, feesOnlyUpdater, - processors, + processor, customGasCharging); given(context.body()).willReturn(TransactionBody.DEFAULT); @@ -233,7 +230,6 @@ void stillChargesHapiFeesOnAbort() { @Test void stillChargesHapiFeesOnHevmException() { final var contractsConfig = CONFIGURATION.getConfigData(ContractsConfig.class); - final var processors = processorsForAllCurrentEvmVersions(processor); final var subject = new ContextTransactionProcessor( null, context, @@ -244,7 +240,7 @@ void stillChargesHapiFeesOnHevmException() { rootProxyWorldUpdater, hevmTransactionFactory, feesOnlyUpdater, - processors, + processor, customGasCharging); given(context.body()).willReturn(transactionBody); @@ -261,7 +257,6 @@ void stillChargesHapiFeesOnHevmException() { @Test void stillChargesHapiFeesOnExceptionThrown() { final var contractsConfig = CONFIGURATION.getConfigData(ContractsConfig.class); - final var processors = processorsForAllCurrentEvmVersions(processor); final var subject = new ContextTransactionProcessor( null, context, @@ -272,7 +267,7 @@ void stillChargesHapiFeesOnExceptionThrown() { rootProxyWorldUpdater, hevmTransactionFactory, feesOnlyUpdater, - processors, + processor, customGasCharging); given(context.body()).willReturn(transactionBody); @@ -291,7 +286,6 @@ void stillChargesHapiFeesOnExceptionThrown() { @Test void reThrowsExceptionWhenNotContractCall() { final var contractsConfig = CONFIGURATION.getConfigData(ContractsConfig.class); - final var processors = processorsForAllCurrentEvmVersions(processor); final var subject = new ContextTransactionProcessor( null, context, @@ -302,7 +296,7 @@ void reThrowsExceptionWhenNotContractCall() { rootProxyWorldUpdater, hevmTransactionFactory, feesOnlyUpdater, - processors, + processor, customGasCharging); given(context.body()).willReturn(transactionBody); @@ -320,7 +314,6 @@ void reThrowsExceptionWhenNotContractCall() { @Test void failsImmediatelyIfEthTxInvalid() { final var contractsConfig = CONFIGURATION.getConfigData(ContractsConfig.class); - final var processors = processorsForAllCurrentEvmVersions(processor); final var subject = new ContextTransactionProcessor( HydratedEthTxData.failureFrom(INVALID_ETHEREUM_TRANSACTION), context, @@ -331,7 +324,7 @@ void failsImmediatelyIfEthTxInvalid() { rootProxyWorldUpdater, hevmTransactionFactory, feesOnlyUpdater, - processors, + processor, customGasCharging); assertFailsWith(INVALID_ETHEREUM_TRANSACTION, subject::call); diff --git a/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/FeatureFlagsTest.java b/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/FeatureFlagsTest.java index 3e0f01944ce7..0a2fdc08ea86 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/FeatureFlagsTest.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/FeatureFlagsTest.java @@ -28,8 +28,8 @@ import com.hedera.node.app.service.contract.impl.exec.FeatureFlags; import com.hedera.node.app.service.contract.impl.exec.utils.FrameUtils; import com.hedera.node.app.service.contract.impl.exec.v046.Version046FeatureFlags; -import com.hedera.node.app.service.contract.impl.state.HederaEvmAccount; import com.hedera.node.app.service.contract.impl.utils.ConversionUtils; +import com.hedera.node.config.data.ContractsConfig; import com.hedera.node.config.testfixtures.HederaTestConfigBuilder; import java.util.Deque; import org.hyperledger.besu.evm.frame.MessageFrame; @@ -67,18 +67,15 @@ void sidecarsEnabledBasedOnConfig() { @Test void isAllowCallsToNonContractAccountsEnabledGrandfatherTest() { final var subject = new Version046FeatureFlags(); - final var config = HederaTestConfigBuilder.create() - .withValue("contracts.evm.nonExtantContractsFail", 1000L) - .getOrCreateConfig(); final var config2 = HederaTestConfigBuilder.create() .withValue( "contracts.evm.nonExtantContractsFail", ConversionUtils.numberOfLongZero(NON_SYSTEM_LONG_ZERO_ADDRESS)) .getOrCreateConfig(); - final var grandfathered = mock(HederaEvmAccount.class); - assertTrue(subject.isAllowCallsToNonContractAccountsEnabled(config, 1L)); + final var contractsConfig = config2.getConfigData(ContractsConfig.class); + assertTrue(subject.isAllowCallsToNonContractAccountsEnabled(contractsConfig, 1L)); assertFalse(subject.isAllowCallsToNonContractAccountsEnabled( - config2, ConversionUtils.numberOfLongZero(NON_SYSTEM_LONG_ZERO_ADDRESS))); + contractsConfig, ConversionUtils.numberOfLongZero(NON_SYSTEM_LONG_ZERO_ADDRESS))); } } diff --git a/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/FrameRunnerTest.java b/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/FrameRunnerTest.java index bbc10bb921b7..52c9db19e44a 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/FrameRunnerTest.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/FrameRunnerTest.java @@ -39,6 +39,8 @@ import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; import static org.mockito.BDDMockito.given; import static org.mockito.Mockito.doAnswer; @@ -63,6 +65,7 @@ import org.hyperledger.besu.datatypes.Address; import org.hyperledger.besu.datatypes.Wei; import org.hyperledger.besu.evm.frame.MessageFrame; +import org.hyperledger.besu.evm.operation.Operation; import org.hyperledger.besu.evm.processor.ContractCreationProcessor; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -220,6 +223,7 @@ void failurePathWorksWithHaltReasonWhenExceedingChildRecords() { inOrder.verify(tracer).traceOriginAction(frame); inOrder.verify(contractCreationProcessor).process(frame, tracer); + inOrder.verify(tracer).tracePostExecution(eq(childFrame), any(Operation.OperationResult.class)); inOrder.verify(messageCallProcessor).process(childFrame, tracer); inOrder.verify(tracer).sanitizeTracedActions(frame); diff --git a/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/TransactionModuleTest.java b/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/TransactionModuleTest.java index eed488b53a6c..65dccfe83fb0 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/TransactionModuleTest.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/TransactionModuleTest.java @@ -16,10 +16,15 @@ package com.hedera.node.app.service.contract.impl.test.exec; +import static com.hedera.hapi.node.base.ResponseCodeEnum.ACCOUNT_DELETED; import static com.hedera.node.app.service.contract.impl.exec.TransactionModule.provideActionSidecarContentTracer; import static com.hedera.node.app.service.contract.impl.exec.TransactionModule.provideHederaEvmContext; +import static com.hedera.node.app.service.contract.impl.exec.TransactionModule.provideSenderEcdsaKey; +import static com.hedera.node.app.service.contract.impl.test.TestHelpers.A_SECP256K1_KEY; +import static com.hedera.node.app.service.contract.impl.test.TestHelpers.DEFAULT_CONTRACTS_CONFIG; import static com.hedera.node.app.service.contract.impl.test.TestHelpers.DEFAULT_HEDERA_CONFIG; import static com.hedera.node.app.service.contract.impl.test.TestHelpers.ETH_DATA_WITH_CALL_DATA; +import static org.assertj.core.api.AssertionsForClassTypes.assertThat; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertInstanceOf; import static org.junit.jupiter.api.Assertions.assertNotNull; @@ -33,8 +38,11 @@ import com.hedera.hapi.node.contract.ContractCallTransactionBody; import com.hedera.hapi.node.contract.EthereumTransactionBody; import com.hedera.hapi.node.transaction.TransactionBody; +import com.hedera.node.app.hapi.utils.ethereum.EthTxSigs; import com.hedera.node.app.service.contract.impl.exec.EvmActionTracer; +import com.hedera.node.app.service.contract.impl.exec.FeatureFlags; import com.hedera.node.app.service.contract.impl.exec.TransactionModule; +import com.hedera.node.app.service.contract.impl.exec.TransactionProcessor; import com.hedera.node.app.service.contract.impl.exec.gas.CanonicalDispatchPrices; import com.hedera.node.app.service.contract.impl.exec.gas.DispatchType; import com.hedera.node.app.service.contract.impl.exec.gas.SystemContractGasCalculator; @@ -44,8 +52,10 @@ import com.hedera.node.app.service.contract.impl.exec.scope.SystemContractOperations; import com.hedera.node.app.service.contract.impl.exec.utils.PendingCreationMetadataRef; import com.hedera.node.app.service.contract.impl.hevm.HederaEvmBlocks; +import com.hedera.node.app.service.contract.impl.hevm.HederaEvmVersion; import com.hedera.node.app.service.contract.impl.hevm.HederaWorldUpdater; import com.hedera.node.app.service.contract.impl.hevm.HydratedEthTxData; +import com.hedera.node.app.service.contract.impl.infra.EthTxSigsCache; import com.hedera.node.app.service.contract.impl.infra.EthereumCallDataHydration; import com.hedera.node.app.service.contract.impl.records.ContractOperationRecordBuilder; import com.hedera.node.app.service.contract.impl.state.EvmFrameStateFactory; @@ -59,6 +69,7 @@ import com.hedera.node.app.spi.workflows.ComputeDispatchFeesAsTopLevel; import com.hedera.node.app.spi.workflows.HandleContext; import java.time.Instant; +import java.util.Map; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.Mock; @@ -96,12 +107,21 @@ class TransactionModuleTest { @Mock private EthereumCallDataHydration hydration; + @Mock + private EthTxSigsCache ethTxSigsCache; + @Mock private ReadableFileStore fileStore; @Mock private HandleContext context; + @Mock + private TransactionProcessor processor; + + @Mock + private FeatureFlags featureFlags; + @Test void createsEvmActionTracer() { assertInstanceOf(EvmActionTracer.class, provideActionSidecarContentTracer()); @@ -117,6 +137,39 @@ void feesOnlyUpdaterIsProxyUpdater() { verify(hederaOperations).begin(); } + @Test + void providesExpectedProcessor() { + final var version = HederaEvmVersion.EVM_VERSIONS.get(DEFAULT_CONTRACTS_CONFIG.evmVersion()); + final var processors = Map.of(version, processor); + assertSame(processor, TransactionModule.provideTransactionProcessor(DEFAULT_CONTRACTS_CONFIG, processors)); + } + + @Test + void providesFeatureFlags() { + given(processor.featureFlags()).willReturn(featureFlags); + assertSame(featureFlags, TransactionModule.provideFeatureFlags(processor)); + } + + @Test + void providesNullSenderEcdsaKeyWithoutHydratedEthTxData() { + assertNull(provideSenderEcdsaKey(ethTxSigsCache, null)); + } + + @Test + void providesNullSenderEcdsaKeyWithUnavailableEthTxData() { + final var failedHydration = HydratedEthTxData.failureFrom(ACCOUNT_DELETED); + assertNull(provideSenderEcdsaKey(ethTxSigsCache, failedHydration)); + } + + @Test + void providesCorrespondingKeyForAvailableEthTxData() { + final var hydration = HydratedEthTxData.successFrom(ETH_DATA_WITH_CALL_DATA); + given(ethTxSigsCache.computeIfAbsent(ETH_DATA_WITH_CALL_DATA)) + .willReturn( + new EthTxSigs(A_SECP256K1_KEY.ecdsaSecp256k1OrThrow().toByteArray(), new byte[0])); + assertThat(provideSenderEcdsaKey(ethTxSigsCache, hydration)).isEqualTo(A_SECP256K1_KEY); + } + @Test void providesExpectedEvmContext() { final var recordBuilder = mock(ContractOperationRecordBuilder.class); diff --git a/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/processors/CustomMessageCallProcessorTest.java b/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/processors/CustomMessageCallProcessorTest.java index 76f79f357a87..916015124783 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/processors/CustomMessageCallProcessorTest.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/processors/CustomMessageCallProcessorTest.java @@ -29,6 +29,7 @@ import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; import com.hedera.node.app.service.contract.impl.exec.AddressChecks; import com.hedera.node.app.service.contract.impl.exec.FeatureFlags; @@ -68,6 +69,7 @@ class CustomMessageCallProcessorTest { private static final long GAS_REQUIREMENT = 2L; private static final Bytes INPUT_DATA = Bytes.fromHexString("0x1234"); private static final Bytes OUTPUT_DATA = Bytes.fromHexString("0x5678"); + private static final Bytes NOOP_OUTPUT_DATA = Bytes.fromHexString("0x"); private static final Address NON_EVM_PRECOMPILE_SYSTEM_ADDRESS = Address.fromHexString("0x222"); private static final Address CODE_ADDRESS = Address.fromHexString("0x111222333"); private static final Address SENDER_ADDRESS = Address.fromHexString("0x222333444"); @@ -155,14 +157,16 @@ void callPrngSystemContractInsufficientGas() { @Test void callsToNonStandardSystemContractsAreNotSupported() { - final var isHalted = new AtomicBoolean(); - givenHaltableFrame(isHalted); + final Deque stack = new ArrayDeque<>(); givenCallWithCode(NON_EVM_PRECOMPILE_SYSTEM_ADDRESS); + given(addressChecks.isSystemAccount(NON_EVM_PRECOMPILE_SYSTEM_ADDRESS)).willReturn(true); + when(frame.getValue()).thenReturn(Wei.ZERO); subject.start(frame, operationTracer); - - verifyHalt(ExceptionalHaltReason.PRECOMPILE_ERROR); + verify(frame).setOutputData(NOOP_OUTPUT_DATA); + verify(frame).setState(MessageFrame.State.COMPLETED_SUCCESS); + verify(frame).setExceptionalHaltReason(Optional.empty()); } @Test @@ -171,7 +175,6 @@ void valueCannotBeTransferredToSystemContracts() { givenHaltableFrame(isHalted); givenCallWithCode(ADDRESS_6); given(addressChecks.isSystemAccount(ADDRESS_6)).willReturn(true); - given(registry.get(ADDRESS_6)).willReturn(nativePrecompile); given(frame.getValue()).willReturn(Wei.ONE); subject.start(frame, operationTracer); @@ -198,8 +201,6 @@ void haltsIfValueTransferFails() { @Test void haltsAndTracesInsufficientGasIfPrecompileGasRequirementExceedsRemaining() { - final var isHalted = new AtomicBoolean(); - givenHaltableFrame(isHalted); givenEvmPrecompileCall(); given(nativePrecompile.gasRequirement(INPUT_DATA)).willReturn(GAS_REQUIREMENT); given(frame.getRemainingGas()).willReturn(1L); @@ -218,7 +219,6 @@ void updatesFrameBySuccessfulPrecompileResultWithGasRefund() { given(nativePrecompile.computePrecompile(INPUT_DATA, frame)).willReturn(result); given(nativePrecompile.gasRequirement(INPUT_DATA)).willReturn(GAS_REQUIREMENT); given(frame.getRemainingGas()).willReturn(3L); - given(frame.getState()).willReturn(MessageFrame.State.COMPLETED_SUCCESS); subject.start(frame, operationTracer); @@ -237,7 +237,6 @@ void revertsFrameFromPrecompileResult() { given(nativePrecompile.computePrecompile(INPUT_DATA, frame)).willReturn(result); given(nativePrecompile.gasRequirement(INPUT_DATA)).willReturn(GAS_REQUIREMENT); given(frame.getRemainingGas()).willReturn(3L); - given(frame.getState()).willReturn(MessageFrame.State.REVERT); subject.start(frame, operationTracer); @@ -291,6 +290,15 @@ private void givenHaltableFrame(@NonNull final AtomicBoolean isHalted) { .getState(); } + private void givenCallWithIsTopLevelTransaction(@NonNull final AtomicBoolean isTopLevelTransaction) { + doAnswer(invocation -> { + isTopLevelTransaction.set(false); + return null; + }) + .when(frame) + .setExceptionalHaltReason(any()); + } + private void givenCallWithCode(@NonNull final Address contract) { given(frame.getContractAddress()).willReturn(contract); } @@ -302,11 +310,9 @@ private void givenWellKnownUserSpaceCall() { } private void givenEvmPrecompileCall() { - given(addressChecks.isSystemAccount(ADDRESS_6)).willReturn(true); given(registry.get(ADDRESS_6)).willReturn(nativePrecompile); given(frame.getContractAddress()).willReturn(ADDRESS_6); given(frame.getInputData()).willReturn(INPUT_DATA); - given(frame.getValue()).willReturn(Wei.ZERO); } private void givenPrngCall(long gasRequirement) { diff --git a/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/processors/ProcessorModuleTest.java b/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/processors/ProcessorModuleTest.java index fbb51da7193b..a9cad0c3bb74 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/processors/ProcessorModuleTest.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/processors/ProcessorModuleTest.java @@ -16,12 +16,14 @@ package com.hedera.node.app.service.contract.impl.test.exec.processors; +import static com.hedera.node.app.service.contract.impl.exec.systemcontracts.HasSystemContract.HAS_EVM_ADDRESS; import static com.hedera.node.app.service.contract.impl.exec.systemcontracts.HtsSystemContract.HTS_EVM_ADDRESS; import static com.hedera.node.app.service.contract.impl.exec.systemcontracts.PrngSystemContract.PRNG_PRECOMPILE_ADDRESS; import static org.assertj.core.api.Assertions.assertThat; import com.hedera.node.app.service.contract.impl.exec.processors.ProcessorModule; import com.hedera.node.app.service.contract.impl.exec.systemcontracts.ExchangeRateSystemContract; +import com.hedera.node.app.service.contract.impl.exec.systemcontracts.HasSystemContract; import com.hedera.node.app.service.contract.impl.exec.systemcontracts.HtsSystemContract; import com.hedera.node.app.service.contract.impl.exec.systemcontracts.PrngSystemContract; import org.hyperledger.besu.datatypes.Address; @@ -41,15 +43,19 @@ class ProcessorModuleTest { @Mock private PrngSystemContract prngSystemContract; + @Mock + private HasSystemContract hasSystemContract; + @Test void provideHederaSystemContracts() { final var hederaSystemContracts = ProcessorModule.provideHederaSystemContracts( - htsSystemContract, exchangeRateSystemContract, prngSystemContract); + htsSystemContract, exchangeRateSystemContract, prngSystemContract, hasSystemContract); assertThat(hederaSystemContracts) .isNotNull() - .hasSize(3) + .hasSize(4) .containsKey(Address.fromHexString(HTS_EVM_ADDRESS)) .containsKey(Address.fromHexString(ExchangeRateSystemContract.EXCHANGE_RATE_SYSTEM_CONTRACT_ADDRESS)) - .containsKey(Address.fromHexString(PRNG_PRECOMPILE_ADDRESS)); + .containsKey(Address.fromHexString(PRNG_PRECOMPILE_ADDRESS)) + .containsKey(Address.fromHexString(HAS_EVM_ADDRESS)); } } diff --git a/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/scope/ActiveContractVerificationStrategyTest.java b/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/scope/ActiveContractVerificationStrategyTest.java index ac51bee7f262..90a33380dc6a 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/scope/ActiveContractVerificationStrategyTest.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/scope/ActiveContractVerificationStrategyTest.java @@ -18,6 +18,8 @@ import static com.hedera.node.app.service.contract.impl.test.TestHelpers.ANOTHER_ED25519_KEY; import static com.hedera.node.app.service.contract.impl.test.TestHelpers.AN_ED25519_KEY; +import static com.hedera.node.app.service.contract.impl.test.TestHelpers.A_SECP256K1_KEY; +import static com.hedera.node.app.service.contract.impl.test.TestHelpers.B_SECP256K1_KEY; import static com.hedera.node.app.service.contract.impl.test.TestHelpers.YET_ANOTHER_ED25519_KEY; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; @@ -33,6 +35,7 @@ import com.hedera.node.app.service.contract.impl.exec.scope.ActiveContractVerificationStrategy; import com.hedera.node.app.service.contract.impl.exec.scope.ActiveContractVerificationStrategy.UseTopLevelSigs; import com.hedera.node.app.service.contract.impl.exec.scope.VerificationStrategy; +import com.hedera.node.app.spi.signatures.SignatureVerification; import com.hedera.node.app.spi.workflows.HandleContext; import com.hedera.pbj.runtime.io.buffer.Bytes; import org.junit.jupiter.api.Test; @@ -135,15 +138,40 @@ void validatesKeysAsExpectedWhenDelegatePermissionRequiredAndNotUsingTopLevelSig assertEquals(VerificationStrategy.Decision.INVALID, subject.decideForPrimitive(CRYPTO_KEY)); } + @Test + void signatureTestApprovesEthSenderKeyWhenDelegating() { + final var subject = mock(VerificationStrategy.class); + doCallRealMethod().when(subject).asSignatureTestIn(context, A_SECP256K1_KEY); + given(subject.decideForPrimitive(A_SECP256K1_KEY)) + .willReturn(VerificationStrategy.Decision.DELEGATE_TO_CRYPTOGRAPHIC_VERIFICATION); + + final var test = subject.asSignatureTestIn(context, A_SECP256K1_KEY); + assertTrue(test.test(A_SECP256K1_KEY)); + } + + @Test + void signatureTestUsesContextVerificationWhenNotEthSenderKey() { + final var verification = mock(SignatureVerification.class); + final var subject = mock(VerificationStrategy.class); + doCallRealMethod().when(subject).asSignatureTestIn(context, null); + given(verification.passed()).willReturn(true); + given(context.verificationFor(B_SECP256K1_KEY)).willReturn(verification); + given(subject.decideForPrimitive(B_SECP256K1_KEY)) + .willReturn(VerificationStrategy.Decision.DELEGATE_TO_CRYPTOGRAPHIC_VERIFICATION); + + final var test = subject.asSignatureTestIn(context, null); + assertTrue(test.test(B_SECP256K1_KEY)); + } + @Test void signatureTestApprovesAllValidKeyLists() { final var subject = mock(VerificationStrategy.class); - doCallRealMethod().when(subject).asSignatureTestIn(context); + doCallRealMethod().when(subject).asSignatureTestIn(context, null); given(subject.decideForPrimitive(AN_ED25519_KEY)).willReturn(VerificationStrategy.Decision.VALID); given(subject.decideForPrimitive(ANOTHER_ED25519_KEY)).willReturn(VerificationStrategy.Decision.VALID); given(subject.decideForPrimitive(YET_ANOTHER_ED25519_KEY)).willReturn(VerificationStrategy.Decision.VALID); - final var test = subject.asSignatureTestIn(context); + final var test = subject.asSignatureTestIn(context, null); final var key = Key.newBuilder() .keyList(KeyList.newBuilder().keys(AN_ED25519_KEY, ANOTHER_ED25519_KEY, YET_ANOTHER_ED25519_KEY)) .build(); @@ -153,11 +181,11 @@ void signatureTestApprovesAllValidKeyLists() { @Test void signatureTestRejectsIncompleteKeyLists() { final var subject = mock(VerificationStrategy.class); - doCallRealMethod().when(subject).asSignatureTestIn(context); + doCallRealMethod().when(subject).asSignatureTestIn(context, null); given(subject.decideForPrimitive(AN_ED25519_KEY)).willReturn(VerificationStrategy.Decision.VALID); given(subject.decideForPrimitive(ANOTHER_ED25519_KEY)).willReturn(VerificationStrategy.Decision.INVALID); - final var test = subject.asSignatureTestIn(context); + final var test = subject.asSignatureTestIn(context, null); final var key = Key.newBuilder() .keyList(KeyList.newBuilder().keys(AN_ED25519_KEY, ANOTHER_ED25519_KEY, YET_ANOTHER_ED25519_KEY)) .build(); @@ -167,12 +195,12 @@ void signatureTestRejectsIncompleteKeyLists() { @Test void signatureTestApprovesSufficientThresholdKeys() { final var subject = mock(VerificationStrategy.class); - doCallRealMethod().when(subject).asSignatureTestIn(context); + doCallRealMethod().when(subject).asSignatureTestIn(context, null); given(subject.decideForPrimitive(AN_ED25519_KEY)).willReturn(VerificationStrategy.Decision.VALID); given(subject.decideForPrimitive(ANOTHER_ED25519_KEY)).willReturn(VerificationStrategy.Decision.INVALID); given(subject.decideForPrimitive(YET_ANOTHER_ED25519_KEY)).willReturn(VerificationStrategy.Decision.VALID); - final var test = subject.asSignatureTestIn(context); + final var test = subject.asSignatureTestIn(context, null); final var key = Key.newBuilder() .thresholdKey(ThresholdKey.newBuilder() .threshold(2) @@ -185,12 +213,12 @@ void signatureTestApprovesSufficientThresholdKeys() { @Test void signatureTestRejectsInsufficientThresholdKeys() { final var subject = mock(VerificationStrategy.class); - doCallRealMethod().when(subject).asSignatureTestIn(context); + doCallRealMethod().when(subject).asSignatureTestIn(context, null); given(subject.decideForPrimitive(AN_ED25519_KEY)).willReturn(VerificationStrategy.Decision.VALID); given(subject.decideForPrimitive(ANOTHER_ED25519_KEY)).willReturn(VerificationStrategy.Decision.INVALID); given(subject.decideForPrimitive(YET_ANOTHER_ED25519_KEY)).willReturn(VerificationStrategy.Decision.INVALID); - final var test = subject.asSignatureTestIn(context); + final var test = subject.asSignatureTestIn(context, null); final var key = Key.newBuilder() .thresholdKey(ThresholdKey.newBuilder() .threshold(2) @@ -203,11 +231,11 @@ void signatureTestRejectsInsufficientThresholdKeys() { @Test void unsupportedKeyTypesAreNotPrimitive() { final var subject = mock(VerificationStrategy.class); - doCallRealMethod().when(subject).asSignatureTestIn(context); + doCallRealMethod().when(subject).asSignatureTestIn(context, null); final var aRsa3072Key = Key.newBuilder().rsa3072(Bytes.wrap("NONSENSE")).build(); - final var test = subject.asSignatureTestIn(context); + final var test = subject.asSignatureTestIn(context, null); assertFalse(test.test(aRsa3072Key)); } } diff --git a/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/scope/EitherOrVerificationStrategyTest.java b/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/scope/EitherOrVerificationStrategyTest.java index 99308c7e5555..b595d1bf22da 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/scope/EitherOrVerificationStrategyTest.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/scope/EitherOrVerificationStrategyTest.java @@ -51,15 +51,26 @@ void firstStrategyValidSuffices() { @Test void secondStrategyValidSuffices() { - given(firstStrategy.decideForPrimitive(Key.DEFAULT)).willReturn(VerificationStrategy.Decision.INVALID); + given(firstStrategy.decideForPrimitive(Key.DEFAULT)) + .willReturn(VerificationStrategy.Decision.DELEGATE_TO_CRYPTOGRAPHIC_VERIFICATION); given(secondStrategy.decideForPrimitive(Key.DEFAULT)).willReturn(VerificationStrategy.Decision.VALID); assertSame(VerificationStrategy.Decision.VALID, subject.decideForPrimitive(Key.DEFAULT)); } @Test - void oneStrategyMustBeValid() { + void invalidIfNeitherStrategyValid() { given(firstStrategy.decideForPrimitive(Key.DEFAULT)).willReturn(VerificationStrategy.Decision.INVALID); given(secondStrategy.decideForPrimitive(Key.DEFAULT)).willReturn(VerificationStrategy.Decision.INVALID); assertSame(VerificationStrategy.Decision.INVALID, subject.decideForPrimitive(Key.DEFAULT)); } + + @Test + void delegatesIfPossibleAndNotAlreadyValid() { + given(firstStrategy.decideForPrimitive(Key.DEFAULT)).willReturn(VerificationStrategy.Decision.INVALID); + given(secondStrategy.decideForPrimitive(Key.DEFAULT)) + .willReturn(VerificationStrategy.Decision.DELEGATE_TO_CRYPTOGRAPHIC_VERIFICATION); + assertSame( + VerificationStrategy.Decision.DELEGATE_TO_CRYPTOGRAPHIC_VERIFICATION, + subject.decideForPrimitive(Key.DEFAULT)); + } } diff --git a/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/scope/HandleHederaNativeOperationsTest.java b/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/scope/HandleHederaNativeOperationsTest.java index 2376225b887f..e4620156ef70 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/scope/HandleHederaNativeOperationsTest.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/scope/HandleHederaNativeOperationsTest.java @@ -23,6 +23,7 @@ import static com.hedera.node.app.service.contract.impl.exec.utils.FrameUtils.HAPI_RECORD_BUILDER_CONTEXT_VARIABLE; import static com.hedera.node.app.service.contract.impl.test.TestHelpers.A_FUNGIBLE_RELATION; import static com.hedera.node.app.service.contract.impl.test.TestHelpers.A_NEW_ACCOUNT_ID; +import static com.hedera.node.app.service.contract.impl.test.TestHelpers.A_SECP256K1_KEY; import static com.hedera.node.app.service.contract.impl.test.TestHelpers.CANONICAL_ALIAS; import static com.hedera.node.app.service.contract.impl.test.TestHelpers.CIVILIAN_OWNED_NFT; import static com.hedera.node.app.service.contract.impl.test.TestHelpers.EIP_1014_ADDRESS; @@ -121,7 +122,7 @@ class HandleHederaNativeOperationsTest { @BeforeEach void setUp() { - subject = new HandleHederaNativeOperations(context); + subject = new HandleHederaNativeOperations(context, A_SECP256K1_KEY); deletedAccount = AccountID.newBuilder().accountNum(1L).build(); beneficiaryAccount = AccountID.newBuilder().accountNum(2L).build(); } @@ -251,7 +252,7 @@ void transferWithReceiverSigCheckUsesApi() { .accountNum(NON_SYSTEM_CONTRACT_ID.contractNumOrThrow()) .build(); given(accountStore.getAccountById(contractAccountId)).willReturn(PARANOID_SOMEBODY); - given(verificationStrategy.asSignatureTestIn(context)).willReturn(signatureTest); + given(verificationStrategy.asSignatureTestIn(context, A_SECP256K1_KEY)).willReturn(signatureTest); given(signatureTest.test(PARANOID_SOMEBODY.keyOrThrow())).willReturn(true); final var result = subject.transferWithReceiverSigCheck( @@ -272,7 +273,7 @@ void transferWithReceiverSigCheckReturnsInvalidSigIfAppropriate() { .accountNum(NON_SYSTEM_CONTRACT_ID.contractNumOrThrow()) .build(); given(accountStore.getAccountById(contractAccountId)).willReturn(PARANOID_SOMEBODY); - given(verificationStrategy.asSignatureTestIn(context)).willReturn(signatureTest); + given(verificationStrategy.asSignatureTestIn(context, A_SECP256K1_KEY)).willReturn(signatureTest); given(signatureTest.test(PARANOID_SOMEBODY.keyOrThrow())).willReturn(false); final var result = subject.transferWithReceiverSigCheck( diff --git a/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/scope/HandleSystemContractOperationsTest.java b/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/scope/HandleSystemContractOperationsTest.java index c596aee56865..cc617402a476 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/scope/HandleSystemContractOperationsTest.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/scope/HandleSystemContractOperationsTest.java @@ -18,6 +18,7 @@ import static com.hedera.node.app.service.contract.impl.test.TestHelpers.AN_ED25519_KEY; import static com.hedera.node.app.service.contract.impl.test.TestHelpers.A_NEW_ACCOUNT_ID; +import static com.hedera.node.app.service.contract.impl.test.TestHelpers.A_SECP256K1_KEY; import static com.hedera.node.app.spi.workflows.HandleContext.TransactionCategory.CHILD; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertNotNull; @@ -82,7 +83,7 @@ class HandleSystemContractOperationsTest { @BeforeEach void setUp() { - subject = new HandleSystemContractOperations(context); + subject = new HandleSystemContractOperations(context, A_SECP256K1_KEY); } @Test @@ -97,7 +98,7 @@ void dispatchesRespectingGivenStrategy() { given(passed.passed()).willReturn(true); given(context.verificationFor(AN_ED25519_KEY)).willReturn(passed); given(context.verificationFor(TestHelpers.B_SECP256K1_KEY)).willReturn(failed); - doCallRealMethod().when(strategy).asSignatureTestIn(context); + doCallRealMethod().when(strategy).asSignatureTestIn(context, A_SECP256K1_KEY); subject.dispatch(TransactionBody.DEFAULT, strategy, A_NEW_ACCOUNT_ID, CryptoTransferRecordBuilder.class); @@ -194,7 +195,7 @@ void externalizeFailedResultTest() { @Test void syntheticTransactionForHtsCallTest() { - assertNotNull(subject.syntheticTransactionForHtsCall(Bytes.EMPTY, ContractID.DEFAULT, true)); + assertNotNull(subject.syntheticTransactionForNativeCall(Bytes.EMPTY, ContractID.DEFAULT, true)); } @Test diff --git a/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/scope/SpecificCryptoVerificationStrategyTest.java b/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/scope/SpecificCryptoVerificationStrategyTest.java new file mode 100644 index 000000000000..0a5b65155ce5 --- /dev/null +++ b/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/scope/SpecificCryptoVerificationStrategyTest.java @@ -0,0 +1,53 @@ +/* + * Copyright (C) 2024 Hedera Hashgraph, LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.hedera.node.app.service.contract.impl.test.exec.scope; + +import static com.hedera.node.app.service.contract.impl.exec.scope.VerificationStrategy.Decision.DELEGATE_TO_CRYPTOGRAPHIC_VERIFICATION; +import static com.hedera.node.app.service.contract.impl.exec.scope.VerificationStrategy.Decision.INVALID; +import static com.hedera.node.app.service.contract.impl.test.TestHelpers.ANOTHER_ED25519_KEY; +import static com.hedera.node.app.service.contract.impl.test.TestHelpers.AN_ED25519_KEY; +import static com.hedera.node.app.service.contract.impl.test.TestHelpers.A_CONTRACT_KEY; +import static com.hedera.node.app.service.contract.impl.test.TestHelpers.A_SECP256K1_KEY; +import static com.hedera.node.app.service.contract.impl.test.TestHelpers.B_SECP256K1_KEY; +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.*; + +import com.hedera.node.app.service.contract.impl.exec.scope.SpecificCryptoVerificationStrategy; +import org.junit.jupiter.api.Test; + +class SpecificCryptoVerificationStrategyTest { + @Test + void delegatesVerificationForSpecificEd25519KeyOnly() { + final var subject = new SpecificCryptoVerificationStrategy(AN_ED25519_KEY); + + assertThat(subject.decideForPrimitive(AN_ED25519_KEY)).isSameAs(DELEGATE_TO_CRYPTOGRAPHIC_VERIFICATION); + assertThat(subject.decideForPrimitive(ANOTHER_ED25519_KEY)).isSameAs(INVALID); + } + + @Test + void delegatesVerificationForSpecificECDSAKeyOnly() { + final var subject = new SpecificCryptoVerificationStrategy(A_SECP256K1_KEY); + + assertThat(subject.decideForPrimitive(A_SECP256K1_KEY)).isSameAs(DELEGATE_TO_CRYPTOGRAPHIC_VERIFICATION); + assertThat(subject.decideForPrimitive(B_SECP256K1_KEY)).isSameAs(INVALID); + } + + @Test + void failsToConstructWithoutCryptoKey() { + assertThrows(IllegalArgumentException.class, () -> new SpecificCryptoVerificationStrategy(A_CONTRACT_KEY)); + } +} diff --git a/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/systemcontracts/ExchangeRateSystemContractTest.java b/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/systemcontracts/ExchangeRateSystemContractTest.java index 6f26901bf8cc..f598e22fa523 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/systemcontracts/ExchangeRateSystemContractTest.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/systemcontracts/ExchangeRateSystemContractTest.java @@ -25,6 +25,7 @@ import com.google.common.primitives.Longs; import com.hedera.hapi.node.transaction.ExchangeRate; +import com.hedera.node.app.service.contract.impl.exec.failure.CustomExceptionalHaltReason; import com.hedera.node.app.service.contract.impl.exec.systemcontracts.ExchangeRateSystemContract; import com.hedera.node.app.service.contract.impl.exec.utils.FrameUtils; import com.hedera.node.app.service.contract.impl.state.ProxyWorldUpdater; @@ -34,6 +35,7 @@ import java.util.Optional; import org.apache.tuweni.bytes.Bytes; import org.apache.tuweni.bytes.Bytes32; +import org.hyperledger.besu.datatypes.Wei; import org.hyperledger.besu.evm.frame.ExceptionalHaltReason; import org.hyperledger.besu.evm.frame.MessageFrame; import org.hyperledger.besu.evm.gascalculator.GasCalculator; @@ -80,6 +82,7 @@ void closeMocks() { @Test void convertsPositiveNumberToTinybarsAsExpected() { + given(frame.getValue()).willReturn(Wei.ZERO); givenRate(someRate); final var someInput = tinycentsInput(someTinycentAmount); @@ -90,6 +93,7 @@ void convertsPositiveNumberToTinybarsAsExpected() { @Test void convertsPositiveNumberToTinycentsAsExpected() { + given(frame.getValue()).willReturn(Wei.ZERO); givenRate(someRate); final var positiveInput = tinybarsInput(someTinybarAmount); @@ -100,6 +104,7 @@ void convertsPositiveNumberToTinycentsAsExpected() { @Test void convertsZeroToTinybarsAsExpected() { + given(frame.getValue()).willReturn(Wei.ZERO); givenRate(someRate); final var zeroInput = tinycentsInput(0); @@ -118,6 +123,17 @@ void inputCannotUnderflow() { assertThat(result.result().getHaltReason().get()).isEqualTo(ExceptionalHaltReason.INVALID_OPERATION); } + @Test + void valueShouldNotBeSentToThePrecompile() { + given(frame.getValue()).willReturn(Wei.MAX_WEI); + + final var zeroInput = tinycentsInput(0); + final var result = subject.computeFully(zeroInput, frame); + + assertThat(result.output()).isEqualTo(Bytes.EMPTY); + assertThat(result.result().getHaltReason().get()).isEqualTo(CustomExceptionalHaltReason.INVALID_FEE_SUBMITTED); + } + @Test void selectorMustBeFullyPresent() { final var fragmentSelector = Bytes.of(0xab); diff --git a/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/systemcontracts/HasSystemContractTest.java b/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/systemcontracts/HasSystemContractTest.java new file mode 100644 index 000000000000..6216b0b834df --- /dev/null +++ b/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/systemcontracts/HasSystemContractTest.java @@ -0,0 +1,99 @@ +/* + * Copyright (C) 2023-2024 Hedera Hashgraph, LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.hedera.node.app.service.contract.impl.test.exec.systemcontracts; + +import static com.hedera.node.app.service.contract.impl.exec.failure.CustomExceptionalHaltReason.NOT_SUPPORTED; +import static com.hedera.node.app.service.contract.impl.exec.systemcontracts.FullResult.haltResult; +import static com.hedera.node.app.service.contract.impl.exec.utils.FrameUtils.contractsConfigOf; +import static com.hedera.node.app.service.contract.impl.test.TestHelpers.assertSamePrecompileResult; +import static org.mockito.Mockito.when; + +import com.hedera.node.app.service.contract.impl.exec.systemcontracts.HasSystemContract; +import com.hedera.node.app.service.contract.impl.exec.systemcontracts.common.Call; +import com.hedera.node.app.service.contract.impl.exec.systemcontracts.has.HasCallFactory; +import com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.HtsCallAttempt; +import com.hedera.node.app.service.contract.impl.exec.utils.FrameUtils; +import com.hedera.node.app.service.contract.impl.hevm.HederaWorldUpdater; +import com.hedera.node.app.service.contract.impl.state.ProxyWorldUpdater; +import com.hedera.node.config.data.ContractsConfig; +import org.apache.tuweni.bytes.Bytes; +import org.hyperledger.besu.evm.frame.MessageFrame; +import org.hyperledger.besu.evm.gascalculator.GasCalculator; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.MockedStatic; +import org.mockito.Mockito; +import org.mockito.junit.jupiter.MockitoExtension; + +@ExtendWith(MockitoExtension.class) +class HasSystemContractTest { + @Mock + private Call call; + + @Mock + private HtsCallAttempt attempt; + + @Mock + private MessageFrame frame; + + @Mock + private ProxyWorldUpdater updater; + + @Mock + private HederaWorldUpdater.Enhancement enhancement; + + @Mock + private ContractsConfig contractsConfig; + + @Mock + private HasCallFactory attemptFactory; + + @Mock + private GasCalculator gasCalculator; + + private MockedStatic frameUtils; + + private HasSystemContract subject; + private final Bytes validInput = Bytes.fromHexString("91548228"); + + @BeforeEach + void setUp() { + frameUtils = Mockito.mockStatic(FrameUtils.class); + subject = new HasSystemContract(gasCalculator, attemptFactory); + } + + @AfterEach + void clear() { + frameUtils.close(); + } + + /** + * The unit tests for HtsSystemContract are also valid for HasSystemContract. + * Only add tests for unique functionality. + */ + @Test + void haltsAndConsumesRemainingGasIfConfigIsOff() { + frameUtils.when(() -> contractsConfigOf(frame)).thenReturn(contractsConfig); + when(contractsConfig.systemContractAccountServiceEnabled()).thenReturn(false); + final var expected = haltResult(NOT_SUPPORTED, frame.getRemainingGas()); + final var result = subject.computeFully(validInput, frame); + assertSamePrecompileResult(expected, result); + } +} diff --git a/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/systemcontracts/HtsSystemContractTest.java b/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/systemcontracts/HtsSystemContractTest.java index 4e19fe22dcd8..c8ce1b8a0fcf 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/systemcontracts/HtsSystemContractTest.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/systemcontracts/HtsSystemContractTest.java @@ -19,7 +19,7 @@ import static com.hedera.hapi.node.base.ResponseCodeEnum.SUCCESS; import static com.hedera.node.app.service.contract.impl.exec.systemcontracts.FullResult.haltResult; import static com.hedera.node.app.service.contract.impl.exec.systemcontracts.FullResult.successResult; -import static com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.HtsCall.PricedResult.gasOnly; +import static com.hedera.node.app.service.contract.impl.exec.systemcontracts.common.Call.PricedResult.gasOnly; import static com.hedera.node.app.service.contract.impl.exec.utils.FrameUtils.callTypeOf; import static com.hedera.node.app.service.contract.impl.exec.utils.FrameUtils.isDelegateCall; import static com.hedera.node.app.service.contract.impl.exec.utils.FrameUtils.proxyUpdaterFor; @@ -31,7 +31,7 @@ import com.hedera.node.app.service.contract.impl.exec.scope.SystemContractOperations; import com.hedera.node.app.service.contract.impl.exec.systemcontracts.HtsSystemContract; -import com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.HtsCall; +import com.hedera.node.app.service.contract.impl.exec.systemcontracts.common.Call; import com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.HtsCallAttempt; import com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.HtsCallFactory; import com.hedera.node.app.service.contract.impl.exec.utils.FrameUtils; @@ -54,7 +54,7 @@ @ExtendWith(MockitoExtension.class) class HtsSystemContractTest { @Mock - private HtsCall call; + private Call call; @Mock private HtsCallAttempt attempt; @@ -96,7 +96,7 @@ void clear() { @Test void returnsResultFromImpliedCall() { givenValidCallAttempt(); - frameUtils.when(() -> callTypeOf(frame)).thenReturn(FrameUtils.CallType.DIRECT_OR_TOKEN_REDIRECT); + frameUtils.when(() -> callTypeOf(frame)).thenReturn(FrameUtils.CallType.DIRECT_OR_PROXY_REDIRECT); final var pricedResult = gasOnly(successResult(ByteBuffer.allocate(1), 123L), SUCCESS, true); given(call.execute(frame)).willReturn(pricedResult); @@ -107,7 +107,7 @@ void returnsResultFromImpliedCall() { @Test void invalidCallAttemptHaltsAndConsumesRemainingGas() { - given(attemptFactory.createCallAttemptFrom(Bytes.EMPTY, FrameUtils.CallType.DIRECT_OR_TOKEN_REDIRECT, frame)) + given(attemptFactory.createCallAttemptFrom(Bytes.EMPTY, FrameUtils.CallType.DIRECT_OR_PROXY_REDIRECT, frame)) .willThrow(RuntimeException.class); final var expected = haltResult(ExceptionalHaltReason.INVALID_OPERATION, frame.getRemainingGas()); final var result = subject.computeFully(validInput, frame); @@ -117,7 +117,7 @@ void invalidCallAttemptHaltsAndConsumesRemainingGas() { @Test void internalErrorAttemptHaltsAndConsumesRemainingGas() { givenValidCallAttempt(); - frameUtils.when(() -> callTypeOf(frame)).thenReturn(FrameUtils.CallType.DIRECT_OR_TOKEN_REDIRECT); + frameUtils.when(() -> callTypeOf(frame)).thenReturn(FrameUtils.CallType.DIRECT_OR_PROXY_REDIRECT); given(call.execute(frame)).willThrow(RuntimeException.class); final var expected = haltResult(ExceptionalHaltReason.PRECOMPILE_ERROR, frame.getRemainingGas()); @@ -137,7 +137,7 @@ private void givenValidCallAttempt() { frameUtils.when(() -> proxyUpdaterFor(frame)).thenReturn(updater); lenient().when(updater.enhancement()).thenReturn(enhancement); lenient().when(enhancement.systemOperations()).thenReturn(systemOperations); - given(attemptFactory.createCallAttemptFrom(validInput, FrameUtils.CallType.DIRECT_OR_TOKEN_REDIRECT, frame)) + given(attemptFactory.createCallAttemptFrom(validInput, FrameUtils.CallType.DIRECT_OR_PROXY_REDIRECT, frame)) .willReturn(attempt); given(attempt.asExecutableCall()).willReturn(call); } diff --git a/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/systemcontracts/hts/HtsCallTestBase.java b/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/systemcontracts/common/CallTestBase.java similarity index 97% rename from hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/systemcontracts/hts/HtsCallTestBase.java rename to hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/systemcontracts/common/CallTestBase.java index d60278e7ddc6..c471fa2183c0 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/systemcontracts/hts/HtsCallTestBase.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/systemcontracts/common/CallTestBase.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.hedera.node.app.service.contract.impl.test.exec.systemcontracts.hts; +package com.hedera.node.app.service.contract.impl.test.exec.systemcontracts.common; import com.hedera.node.app.service.contract.impl.exec.gas.SystemContractGasCalculator; import com.hedera.node.app.service.contract.impl.exec.scope.HederaNativeOperations; @@ -27,7 +27,7 @@ import org.mockito.junit.jupiter.MockitoExtension; @ExtendWith(MockitoExtension.class) -public class HtsCallTestBase { +public class CallTestBase { @Mock protected HederaOperations operations; diff --git a/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/systemcontracts/has/HasCallAttemptTest.java b/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/systemcontracts/has/HasCallAttemptTest.java new file mode 100644 index 000000000000..255d78cb5f0c --- /dev/null +++ b/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/systemcontracts/has/HasCallAttemptTest.java @@ -0,0 +1,203 @@ +/* + * Copyright (C) 2023-2024 Hedera Hashgraph, LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.hedera.node.app.service.contract.impl.test.exec.systemcontracts.has; + +import static com.hedera.node.app.service.contract.impl.test.TestHelpers.A_NEW_ACCOUNT_ID; +import static com.hedera.node.app.service.contract.impl.test.TestHelpers.B_NEW_ACCOUNT_ID; +import static com.hedera.node.app.service.contract.impl.test.TestHelpers.DEFAULT_CONFIG; +import static com.hedera.node.app.service.contract.impl.test.TestHelpers.EIP_1014_ADDRESS; +import static com.hedera.node.app.service.contract.impl.test.TestHelpers.NON_SYSTEM_BUT_IS_LONG_ZERO_ADDRESS; +import static com.hedera.node.app.service.contract.impl.test.TestHelpers.NON_SYSTEM_LONG_ZERO_ADDRESS; +import static com.hedera.node.app.service.contract.impl.test.TestHelpers.asHeadlongAddress; +import static com.hedera.node.app.service.contract.impl.utils.ConversionUtils.numberOfLongZero; +import static org.junit.jupiter.api.Assertions.assertInstanceOf; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.mockito.BDDMockito.given; + +import com.hedera.node.app.service.contract.impl.exec.scope.VerificationStrategies; +import com.hedera.node.app.service.contract.impl.exec.systemcontracts.common.CallTranslator; +import com.hedera.node.app.service.contract.impl.exec.systemcontracts.has.HasCallAttempt; +import com.hedera.node.app.service.contract.impl.exec.systemcontracts.has.hbarallowance.HbarAllowanceCall; +import com.hedera.node.app.service.contract.impl.exec.systemcontracts.has.hbarallowance.HbarAllowanceTranslator; +import com.hedera.node.app.service.contract.impl.exec.systemcontracts.has.hbarapprove.HbarApproveCall; +import com.hedera.node.app.service.contract.impl.exec.systemcontracts.has.hbarapprove.HbarApproveTranslator; +import com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.AddressIdConverter; +import com.hedera.node.app.service.contract.impl.test.TestHelpers; +import com.hedera.node.app.service.contract.impl.test.exec.systemcontracts.common.CallTestBase; +import java.math.BigInteger; +import java.util.List; +import org.apache.tuweni.bytes.Bytes; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.Mock; + +class HasCallAttemptTest extends CallTestBase { + @Mock + private VerificationStrategies verificationStrategies; + + @Mock + private AddressIdConverter addressIdConverter; + + private List callTranslators; + + @BeforeEach + void setUp() { + callTranslators = List.of(new HbarAllowanceTranslator(), new HbarApproveTranslator()); + } + + @Test + void returnNullAccountIfAccountNotFound() { + given(nativeOperations.getAccount(numberOfLongZero(NON_SYSTEM_LONG_ZERO_ADDRESS))) + .willReturn(null); + final var input = TestHelpers.bytesForRedirectAccount( + HbarAllowanceTranslator.HBAR_ALLOWANCE_PROXY.selector(), NON_SYSTEM_LONG_ZERO_ADDRESS); + final var subject = new HasCallAttempt( + input, + EIP_1014_ADDRESS, + EIP_1014_ADDRESS, + false, + mockEnhancement(), + DEFAULT_CONFIG, + addressIdConverter, + verificationStrategies, + gasCalculator, + callTranslators, + false); + assertNull(subject.redirectAccount()); + } + + @Test + void invalidSelectorLeadsToMissingCall() { + final var input = TestHelpers.bytesForRedirectAccount(new byte[4], NON_SYSTEM_LONG_ZERO_ADDRESS); + final var subject = new HasCallAttempt( + input, + EIP_1014_ADDRESS, + EIP_1014_ADDRESS, + false, + mockEnhancement(), + DEFAULT_CONFIG, + addressIdConverter, + verificationStrategies, + gasCalculator, + callTranslators, + false); + assertNull(subject.asExecutableCall()); + } + + @Test + void constructsHbarAllowanceProxy() { + given(addressIdConverter.convert(asHeadlongAddress(NON_SYSTEM_BUT_IS_LONG_ZERO_ADDRESS))) + .willReturn(A_NEW_ACCOUNT_ID); + final var input = TestHelpers.bytesForRedirectAccount( + HbarAllowanceTranslator.HBAR_ALLOWANCE_PROXY + .encodeCallWithArgs(asHeadlongAddress(NON_SYSTEM_BUT_IS_LONG_ZERO_ADDRESS)) + .array(), + NON_SYSTEM_LONG_ZERO_ADDRESS); + final var subject = new HasCallAttempt( + input, + EIP_1014_ADDRESS, + EIP_1014_ADDRESS, + false, + mockEnhancement(), + DEFAULT_CONFIG, + addressIdConverter, + verificationStrategies, + gasCalculator, + callTranslators, + false); + assertInstanceOf(HbarAllowanceCall.class, subject.asExecutableCall()); + } + + @Test + void constructsHbarAllowanceDirect() { + given(addressIdConverter.convert(asHeadlongAddress(NON_SYSTEM_BUT_IS_LONG_ZERO_ADDRESS))) + .willReturn(A_NEW_ACCOUNT_ID); + given(addressIdConverter.convert(asHeadlongAddress(NON_SYSTEM_LONG_ZERO_ADDRESS))) + .willReturn(B_NEW_ACCOUNT_ID); + final var input = Bytes.wrap(HbarAllowanceTranslator.HBAR_ALLOWANCE + .encodeCallWithArgs( + asHeadlongAddress(NON_SYSTEM_BUT_IS_LONG_ZERO_ADDRESS), + asHeadlongAddress(NON_SYSTEM_LONG_ZERO_ADDRESS)) + .array()); + final var subject = new HasCallAttempt( + input, + EIP_1014_ADDRESS, + EIP_1014_ADDRESS, + false, + mockEnhancement(), + DEFAULT_CONFIG, + addressIdConverter, + verificationStrategies, + gasCalculator, + callTranslators, + false); + assertInstanceOf(HbarAllowanceCall.class, subject.asExecutableCall()); + } + + @Test + void constructsHbarApproveProxy() { + given(addressIdConverter.convert(asHeadlongAddress(NON_SYSTEM_BUT_IS_LONG_ZERO_ADDRESS))) + .willReturn(A_NEW_ACCOUNT_ID); + given(addressIdConverter.convertSender(EIP_1014_ADDRESS)).willReturn(B_NEW_ACCOUNT_ID); + final var input = TestHelpers.bytesForRedirectAccount( + HbarApproveTranslator.HBAR_APPROVE_PROXY + .encodeCallWithArgs( + asHeadlongAddress(NON_SYSTEM_BUT_IS_LONG_ZERO_ADDRESS), BigInteger.valueOf(10)) + .array(), + NON_SYSTEM_LONG_ZERO_ADDRESS); + final var subject = new HasCallAttempt( + input, + EIP_1014_ADDRESS, + EIP_1014_ADDRESS, + false, + mockEnhancement(), + DEFAULT_CONFIG, + addressIdConverter, + verificationStrategies, + gasCalculator, + callTranslators, + false); + assertInstanceOf(HbarApproveCall.class, subject.asExecutableCall()); + } + + @Test + void constructsHbarApproveDirect() { + given(addressIdConverter.convert(asHeadlongAddress(NON_SYSTEM_BUT_IS_LONG_ZERO_ADDRESS))) + .willReturn(A_NEW_ACCOUNT_ID); + given(addressIdConverter.convert(asHeadlongAddress(NON_SYSTEM_LONG_ZERO_ADDRESS))) + .willReturn(B_NEW_ACCOUNT_ID); + final var input = Bytes.wrap(HbarApproveTranslator.HBAR_APPROVE + .encodeCallWithArgs( + asHeadlongAddress(NON_SYSTEM_BUT_IS_LONG_ZERO_ADDRESS), + asHeadlongAddress(NON_SYSTEM_LONG_ZERO_ADDRESS), + BigInteger.valueOf(10)) + .array()); + final var subject = new HasCallAttempt( + input, + EIP_1014_ADDRESS, + EIP_1014_ADDRESS, + false, + mockEnhancement(), + DEFAULT_CONFIG, + addressIdConverter, + verificationStrategies, + gasCalculator, + callTranslators, + false); + assertInstanceOf(HbarApproveCall.class, subject.asExecutableCall()); + } +} diff --git a/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/systemcontracts/has/HasCallFactoryTest.java b/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/systemcontracts/has/HasCallFactoryTest.java new file mode 100644 index 000000000000..23e3a09fb619 --- /dev/null +++ b/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/systemcontracts/has/HasCallFactoryTest.java @@ -0,0 +1,147 @@ +/* + * Copyright (C) 2023-2024 Hedera Hashgraph, LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.hedera.node.app.service.contract.impl.test.exec.systemcontracts.has; + +import static com.hedera.node.app.service.contract.impl.test.TestHelpers.A_NEW_ACCOUNT_ID; +import static com.hedera.node.app.service.contract.impl.test.TestHelpers.B_NEW_ACCOUNT_ID; +import static com.hedera.node.app.service.contract.impl.test.TestHelpers.CALLED_EOA_ID; +import static com.hedera.node.app.service.contract.impl.test.TestHelpers.DEFAULT_CONFIG; +import static com.hedera.node.app.service.contract.impl.test.TestHelpers.EIP_1014_ADDRESS; +import static com.hedera.node.app.service.contract.impl.test.TestHelpers.NON_SYSTEM_BUT_IS_LONG_ZERO_ADDRESS; +import static com.hedera.node.app.service.contract.impl.test.TestHelpers.NON_SYSTEM_LONG_ZERO_ADDRESS; +import static com.hedera.node.app.service.contract.impl.test.TestHelpers.asHeadlongAddress; +import static com.hedera.node.app.service.contract.impl.test.TestHelpers.bytesForRedirectAccount; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertInstanceOf; +import static org.mockito.BDDMockito.given; + +import com.hedera.node.app.service.contract.impl.exec.gas.SystemContractGasCalculator; +import com.hedera.node.app.service.contract.impl.exec.scope.VerificationStrategies; +import com.hedera.node.app.service.contract.impl.exec.systemcontracts.common.CallAddressChecks; +import com.hedera.node.app.service.contract.impl.exec.systemcontracts.has.HasCallFactory; +import com.hedera.node.app.service.contract.impl.exec.systemcontracts.has.hbarallowance.HbarAllowanceCall; +import com.hedera.node.app.service.contract.impl.exec.systemcontracts.has.hbarallowance.HbarAllowanceTranslator; +import com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.AddressIdConverter; +import com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.SyntheticIds; +import com.hedera.node.app.service.contract.impl.exec.utils.FrameUtils; +import com.hedera.node.app.service.contract.impl.state.ProxyWorldUpdater; +import com.hedera.node.app.service.contract.impl.test.exec.systemcontracts.common.CallTestBase; +import java.util.ArrayDeque; +import java.util.Deque; +import java.util.List; +import java.util.Objects; +import org.apache.tuweni.bytes.Bytes; +import org.hyperledger.besu.datatypes.Address; +import org.hyperledger.besu.evm.frame.MessageFrame; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.Mock; + +class HasCallFactoryTest extends CallTestBase { + @Mock + private CallAddressChecks addressChecks; + + @Mock + private SystemContractGasCalculator systemContractGasCalculator; + + @Mock + private VerificationStrategies verificationStrategies; + + @Mock + private AddressIdConverter idConverter; + + @Mock + private SyntheticIds syntheticIds; + + @Mock + private MessageFrame frame; + + @Mock + private MessageFrame initialFrame; + + private Deque stack = new ArrayDeque<>(); + + @Mock + private ProxyWorldUpdater updater; + + private HasCallFactory subject; + + @BeforeEach + void setUp() { + subject = new HasCallFactory( + syntheticIds, addressChecks, verificationStrategies, List.of(new HbarAllowanceTranslator())); + } + + @Test + void instantiatesCallWithInContextEnhancementAndDelegateCallInfo() { + given(initialFrame.getContextVariable(FrameUtils.CONFIG_CONTEXT_VARIABLE)) + .willReturn(DEFAULT_CONFIG); + given(initialFrame.getContextVariable(FrameUtils.SYSTEM_CONTRACT_GAS_CALCULATOR_CONTEXT_VARIABLE)) + .willReturn(systemContractGasCalculator); + stack.push(initialFrame); + stack.addFirst(frame); + given(frame.getMessageFrameStack()).willReturn(stack); + given(frame.getWorldUpdater()).willReturn(updater); + given(updater.enhancement()).willReturn(mockEnhancement()); + given(frame.getSenderAddress()).willReturn(EIP_1014_ADDRESS); + given(addressChecks.hasParentDelegateCall(frame)).willReturn(true); + given(syntheticIds.converterFor(nativeOperations)).willReturn(idConverter); + given(idConverter.convert(asHeadlongAddress(NON_SYSTEM_BUT_IS_LONG_ZERO_ADDRESS))) + .willReturn(A_NEW_ACCOUNT_ID); + + final var input = bytesForRedirectAccount( + HbarAllowanceTranslator.HBAR_ALLOWANCE_PROXY.encodeCallWithArgs( + asHeadlongAddress(NON_SYSTEM_BUT_IS_LONG_ZERO_ADDRESS)), + CALLED_EOA_ID); + final var attempt = subject.createCallAttemptFrom(input, FrameUtils.CallType.DIRECT_OR_PROXY_REDIRECT, frame); + final var call = Objects.requireNonNull(attempt.asExecutableCall()); + + assertInstanceOf(HbarAllowanceCall.class, call); + } + + @Test + void instantiatesDirectCall() { + given(initialFrame.getContextVariable(FrameUtils.CONFIG_CONTEXT_VARIABLE)) + .willReturn(DEFAULT_CONFIG); + given(initialFrame.getContextVariable(FrameUtils.SYSTEM_CONTRACT_GAS_CALCULATOR_CONTEXT_VARIABLE)) + .willReturn(systemContractGasCalculator); + stack.push(initialFrame); + stack.addFirst(frame); + given(frame.getMessageFrameStack()).willReturn(stack); + given(frame.getWorldUpdater()).willReturn(updater); + given(updater.enhancement()).willReturn(mockEnhancement()); + given(frame.getSenderAddress()).willReturn(Address.ALTBN128_ADD); + given(idConverter.convertSender(Address.ALTBN128_ADD)).willReturn(A_NEW_ACCOUNT_ID); + given(addressChecks.hasParentDelegateCall(frame)).willReturn(true); + given(syntheticIds.converterFor(nativeOperations)).willReturn(idConverter); + given(idConverter.convert(asHeadlongAddress(NON_SYSTEM_BUT_IS_LONG_ZERO_ADDRESS))) + .willReturn(A_NEW_ACCOUNT_ID); + given(idConverter.convert(asHeadlongAddress(NON_SYSTEM_LONG_ZERO_ADDRESS))) + .willReturn(B_NEW_ACCOUNT_ID); + + final var input = Bytes.wrap(HbarAllowanceTranslator.HBAR_ALLOWANCE + .encodeCallWithArgs( + asHeadlongAddress(NON_SYSTEM_BUT_IS_LONG_ZERO_ADDRESS), + asHeadlongAddress(NON_SYSTEM_LONG_ZERO_ADDRESS)) + .array()); + final var attempt = subject.createCallAttemptFrom(input, FrameUtils.CallType.DIRECT_OR_PROXY_REDIRECT, frame); + final var call = Objects.requireNonNull(attempt.asExecutableCall()); + + assertInstanceOf(HbarAllowanceCall.class, call); + assertEquals(A_NEW_ACCOUNT_ID, attempt.senderId()); + } +} diff --git a/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/systemcontracts/has/hbarAllowance/HbarAllowanceCallTest.java b/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/systemcontracts/has/hbarAllowance/HbarAllowanceCallTest.java new file mode 100644 index 000000000000..ed19631039c6 --- /dev/null +++ b/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/systemcontracts/has/hbarAllowance/HbarAllowanceCallTest.java @@ -0,0 +1,73 @@ +/* + * Copyright (C) 2023-2024 Hedera Hashgraph, LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.hedera.node.app.service.contract.impl.test.exec.systemcontracts.has.hbarAllowance; + +import static com.hedera.hapi.node.base.ResponseCodeEnum.INVALID_ALLOWANCE_OWNER_ID; +import static com.hedera.node.app.service.contract.impl.test.TestHelpers.APPROVED_ID; +import static com.hedera.node.app.service.contract.impl.test.TestHelpers.B_NEW_ACCOUNT_ID; +import static com.hedera.node.app.service.contract.impl.test.TestHelpers.OPERATOR; +import static com.hedera.node.app.service.contract.impl.test.TestHelpers.UNAUTHORIZED_SPENDER_ID; +import static com.hedera.node.app.service.contract.impl.test.TestHelpers.revertOutputFor; +import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.SUCCESS; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.mockito.BDDMockito.given; + +import com.hedera.node.app.service.contract.impl.exec.systemcontracts.has.HasCallAttempt; +import com.hedera.node.app.service.contract.impl.exec.systemcontracts.has.hbarallowance.HbarAllowanceCall; +import com.hedera.node.app.service.contract.impl.exec.systemcontracts.has.hbarallowance.HbarAllowanceTranslator; +import com.hedera.node.app.service.contract.impl.test.exec.systemcontracts.common.CallTestBase; +import java.math.BigInteger; +import org.apache.tuweni.bytes.Bytes; +import org.hyperledger.besu.evm.frame.MessageFrame; +import org.junit.jupiter.api.Test; +import org.mockito.Mock; + +class HbarAllowanceCallTest extends CallTestBase { + private HbarAllowanceCall subject; + + @Mock + private HasCallAttempt attempt; + + @Test + void revertsWithNoOwner() { + given(attempt.systemContractGasCalculator()).willReturn(gasCalculator); + given(attempt.enhancement()).willReturn(mockEnhancement()); + subject = new HbarAllowanceCall(attempt, APPROVED_ID, UNAUTHORIZED_SPENDER_ID); + + final var result = subject.execute(frame).fullResult().result(); + + assertEquals(MessageFrame.State.REVERT, result.getState()); + assertEquals(revertOutputFor(INVALID_ALLOWANCE_OWNER_ID), result.getOutput()); + } + + @Test + void callHbarAllowance() { + given(attempt.systemContractGasCalculator()).willReturn(gasCalculator); + given(attempt.enhancement()).willReturn(mockEnhancement()); + given(nativeOperations.getAccount(B_NEW_ACCOUNT_ID)).willReturn(OPERATOR); + subject = new HbarAllowanceCall(attempt, B_NEW_ACCOUNT_ID, UNAUTHORIZED_SPENDER_ID); + + final var result = subject.execute(frame).fullResult().result(); + assertEquals(MessageFrame.State.COMPLETED_SUCCESS, result.getState()); + assertEquals( + Bytes.wrap(HbarAllowanceTranslator.HBAR_ALLOWANCE_PROXY + .getOutputs() + .encodeElements((long) SUCCESS.getNumber(), BigInteger.valueOf(0L)) + .array()), + result.getOutput()); + } +} diff --git a/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/systemcontracts/has/hbarAllowance/HbarAllowanceTranslatorTest.java b/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/systemcontracts/has/hbarAllowance/HbarAllowanceTranslatorTest.java new file mode 100644 index 000000000000..66fe090e1a98 --- /dev/null +++ b/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/systemcontracts/has/hbarAllowance/HbarAllowanceTranslatorTest.java @@ -0,0 +1,112 @@ +/* + * Copyright (C) 2023-2024 Hedera Hashgraph, LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.hedera.node.app.service.contract.impl.test.exec.systemcontracts.has.hbarAllowance; + +import static com.hedera.node.app.service.contract.impl.test.TestHelpers.APPROVED_HEADLONG_ADDRESS; +import static com.hedera.node.app.service.contract.impl.test.TestHelpers.A_NEW_ACCOUNT_ID; +import static com.hedera.node.app.service.contract.impl.test.TestHelpers.B_NEW_ACCOUNT_ID; +import static com.hedera.node.app.service.contract.impl.test.TestHelpers.UNAUTHORIZED_SPENDER_HEADLONG_ADDRESS; +import static org.assertj.core.api.AssertionsForClassTypes.assertThat; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.BDDMockito.given; + +import com.esaulpaugh.headlong.abi.Tuple; +import com.hedera.node.app.service.contract.impl.exec.gas.SystemContractGasCalculator; +import com.hedera.node.app.service.contract.impl.exec.systemcontracts.has.HasCallAttempt; +import com.hedera.node.app.service.contract.impl.exec.systemcontracts.has.hbarallowance.HbarAllowanceCall; +import com.hedera.node.app.service.contract.impl.exec.systemcontracts.has.hbarallowance.HbarAllowanceTranslator; +import com.hedera.node.app.service.contract.impl.exec.systemcontracts.has.hbarapprove.HbarApproveTranslator; +import com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.AddressIdConverter; +import com.hedera.node.app.service.contract.impl.hevm.HederaWorldUpdater; +import org.apache.tuweni.bytes.Bytes; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +@ExtendWith(MockitoExtension.class) +public class HbarAllowanceTranslatorTest { + @Mock + private HasCallAttempt attempt; + + @Mock + private SystemContractGasCalculator gasCalculator; + + @Mock + private AddressIdConverter addressIdConverter; + + @Mock + private HederaWorldUpdater.Enhancement enhancement; + + private HbarAllowanceTranslator subject; + + @BeforeEach + void setUp() { + subject = new HbarAllowanceTranslator(); + } + + @Test + void matchesHbarAllowance() { + given(attempt.selector()).willReturn(HbarAllowanceTranslator.HBAR_ALLOWANCE_PROXY.selector()); + var matches = subject.matches(attempt); + assertTrue(matches); + + given(attempt.selector()).willReturn(HbarAllowanceTranslator.HBAR_ALLOWANCE.selector()); + matches = subject.matches(attempt); + assertTrue(matches); + } + + @Test + void failsOnInvalidSelector() { + given(attempt.selector()).willReturn(HbarApproveTranslator.HBAR_APPROVE.selector()); + final var matches = subject.matches(attempt); + assertFalse(matches); + } + + @Test + void callFromHbarAllowanceTest() { + final Bytes inputBytes = Bytes.wrapByteBuffer(HbarAllowanceTranslator.HBAR_ALLOWANCE.encodeCall( + Tuple.of(APPROVED_HEADLONG_ADDRESS, UNAUTHORIZED_SPENDER_HEADLONG_ADDRESS))); + givenCommonForCall(inputBytes); + given(addressIdConverter.convert(UNAUTHORIZED_SPENDER_HEADLONG_ADDRESS)).willReturn(A_NEW_ACCOUNT_ID); + + final var call = subject.callFrom(attempt); + assertThat(call).isInstanceOf(HbarAllowanceCall.class); + } + + @Test + void callFromHbarAllowanceProxyTest() { + final Bytes inputBytes = Bytes.wrapByteBuffer( + HbarAllowanceTranslator.HBAR_ALLOWANCE_PROXY.encodeCall(Tuple.of(APPROVED_HEADLONG_ADDRESS))); + givenCommonForCall(inputBytes); + given(attempt.redirectAccountId()).willReturn(A_NEW_ACCOUNT_ID); + + final var call = subject.callFrom(attempt); + assertThat(call).isInstanceOf(HbarAllowanceCall.class); + } + + private void givenCommonForCall(Bytes inputBytes) { + given(attempt.inputBytes()).willReturn(inputBytes.toArray()); + given(attempt.selector()).willReturn(inputBytes.slice(0, 4).toArray()); + given(attempt.enhancement()).willReturn(enhancement); + given(attempt.addressIdConverter()).willReturn(addressIdConverter); + given(addressIdConverter.convert(APPROVED_HEADLONG_ADDRESS)).willReturn(B_NEW_ACCOUNT_ID); + given(attempt.systemContractGasCalculator()).willReturn(gasCalculator); + } +} diff --git a/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/systemcontracts/has/hbarApprove/HbarApproveCallTest.java b/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/systemcontracts/has/hbarApprove/HbarApproveCallTest.java new file mode 100644 index 000000000000..a6ea5fb1393f --- /dev/null +++ b/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/systemcontracts/has/hbarApprove/HbarApproveCallTest.java @@ -0,0 +1,102 @@ +/* + * Copyright (C) 2023-2024 Hedera Hashgraph, LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.hedera.node.app.service.contract.impl.test.exec.systemcontracts.has.hbarApprove; + +import static com.hedera.hapi.node.base.ResponseCodeEnum.INVALID_ALLOWANCE_OWNER_ID; +import static com.hedera.node.app.service.contract.impl.test.TestHelpers.ordinalRevertOutputFor; +import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.REVERTED_SUCCESS; +import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.SUCCESS; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.BDDMockito.given; + +import com.hedera.hapi.node.base.ResponseCodeEnum; +import com.hedera.hapi.node.transaction.TransactionBody; +import com.hedera.node.app.service.contract.impl.exec.systemcontracts.has.HasCallAttempt; +import com.hedera.node.app.service.contract.impl.exec.systemcontracts.has.hbarapprove.HbarApproveCall; +import com.hedera.node.app.service.contract.impl.exec.systemcontracts.has.hbarapprove.HbarApproveTranslator; +import com.hedera.node.app.service.contract.impl.records.ContractCallRecordBuilder; +import com.hedera.node.app.service.contract.impl.test.exec.systemcontracts.common.CallTestBase; +import org.apache.tuweni.bytes.Bytes; +import org.hyperledger.besu.evm.frame.MessageFrame.State; +import org.junit.jupiter.api.Test; +import org.mockito.Mock; + +class HbarApproveCallTest extends CallTestBase { + private HbarApproveCall subject; + + @Mock + private HasCallAttempt attempt; + + @Mock + private TransactionBody transactionBody; + + @Mock + private ContractCallRecordBuilder recordBuilder; + + @Test + void revertsWithNoOwner() { + given(attempt.systemContractGasCalculator()).willReturn(gasCalculator); + given(attempt.enhancement()).willReturn(mockEnhancement()); + given(systemContractOperations.dispatch(any(), any(), any(), any())).willReturn(recordBuilder); + given(recordBuilder.status()).willReturn(ResponseCodeEnum.INVALID_ALLOWANCE_OWNER_ID); + subject = new HbarApproveCall(attempt, transactionBody); + + final var result = subject.execute(frame).fullResult().result(); + + assertEquals(State.REVERT, result.getState()); + assertEquals(ordinalRevertOutputFor(INVALID_ALLOWANCE_OWNER_ID), result.getOutput()); + } + + @Test + void getHbarApprove() { + given(attempt.systemContractGasCalculator()).willReturn(gasCalculator); + given(attempt.enhancement()).willReturn(mockEnhancement()); + given(systemContractOperations.dispatch(any(), any(), any(), any())).willReturn(recordBuilder); + given(recordBuilder.status()).willReturn(ResponseCodeEnum.SUCCESS); + + subject = new HbarApproveCall(attempt, transactionBody); + + final var result = subject.execute(frame).fullResult().result(); + assertEquals(State.COMPLETED_SUCCESS, result.getState()); + assertEquals( + Bytes.wrap(HbarApproveTranslator.HBAR_APPROVE + .getOutputs() + .encodeElements((long) SUCCESS.getNumber()) + .array()), + result.getOutput()); + } + + @Test + void getHbarApproveRevert() { + given(attempt.systemContractGasCalculator()).willReturn(gasCalculator); + given(attempt.enhancement()).willReturn(mockEnhancement()); + given(systemContractOperations.dispatch(any(), any(), any(), any())).willReturn(recordBuilder); + given(recordBuilder.status()).willReturn(ResponseCodeEnum.REVERTED_SUCCESS); + + subject = new HbarApproveCall(attempt, transactionBody); + + final var result = subject.execute(frame).fullResult().result(); + assertEquals(State.REVERT, result.getState()); + assertEquals( + Bytes.wrap(HbarApproveTranslator.HBAR_APPROVE + .getOutputs() + .encodeElements((long) REVERTED_SUCCESS.getNumber()) + .array()), + result.getOutput()); + } +} diff --git a/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/systemcontracts/has/hbarApprove/HbarApproveTranslatorTest.java b/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/systemcontracts/has/hbarApprove/HbarApproveTranslatorTest.java new file mode 100644 index 000000000000..585c397bfc1a --- /dev/null +++ b/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/systemcontracts/has/hbarApprove/HbarApproveTranslatorTest.java @@ -0,0 +1,114 @@ +/* + * Copyright (C) 2023-2024 Hedera Hashgraph, LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.hedera.node.app.service.contract.impl.test.exec.systemcontracts.has.hbarApprove; + +import static com.hedera.node.app.service.contract.impl.test.TestHelpers.APPROVED_HEADLONG_ADDRESS; +import static com.hedera.node.app.service.contract.impl.test.TestHelpers.A_NEW_ACCOUNT_ID; +import static com.hedera.node.app.service.contract.impl.test.TestHelpers.B_NEW_ACCOUNT_ID; +import static com.hedera.node.app.service.contract.impl.test.TestHelpers.UNAUTHORIZED_SPENDER_HEADLONG_ADDRESS; +import static org.assertj.core.api.AssertionsForClassTypes.assertThat; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.BDDMockito.given; + +import com.esaulpaugh.headlong.abi.Tuple; +import com.hedera.node.app.service.contract.impl.exec.gas.SystemContractGasCalculator; +import com.hedera.node.app.service.contract.impl.exec.systemcontracts.has.HasCallAttempt; +import com.hedera.node.app.service.contract.impl.exec.systemcontracts.has.hbarallowance.HbarAllowanceTranslator; +import com.hedera.node.app.service.contract.impl.exec.systemcontracts.has.hbarapprove.HbarApproveCall; +import com.hedera.node.app.service.contract.impl.exec.systemcontracts.has.hbarapprove.HbarApproveTranslator; +import com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.AddressIdConverter; +import com.hedera.node.app.service.contract.impl.hevm.HederaWorldUpdater; +import java.math.BigInteger; +import org.apache.tuweni.bytes.Bytes; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +@ExtendWith(MockitoExtension.class) +public class HbarApproveTranslatorTest { + @Mock + private HasCallAttempt attempt; + + @Mock + private SystemContractGasCalculator gasCalculator; + + @Mock + private AddressIdConverter addressIdConverter; + + @Mock + private HederaWorldUpdater.Enhancement enhancement; + + private HbarApproveTranslator subject; + + @BeforeEach + void setUp() { + subject = new HbarApproveTranslator(); + } + + @Test + void matchesHbarApprove() { + given(attempt.selector()).willReturn(HbarApproveTranslator.HBAR_APPROVE.selector()); + var matches = subject.matches(attempt); + assertTrue(matches); + + given(attempt.selector()).willReturn(HbarApproveTranslator.HBAR_APPROVE_PROXY.selector()); + matches = subject.matches(attempt); + assertTrue(matches); + } + + @Test + void failsOnInvalidSelector() { + given(attempt.selector()).willReturn(HbarAllowanceTranslator.HBAR_ALLOWANCE_PROXY.selector()); + final var matches = subject.matches(attempt); + assertFalse(matches); + } + + @Test + void callFromHbarApproveProxyTest() { + final Bytes inputBytes = Bytes.wrapByteBuffer(HbarApproveTranslator.HBAR_APPROVE_PROXY.encodeCall( + Tuple.of(APPROVED_HEADLONG_ADDRESS, BigInteger.ONE))); + givenCommonForCall(inputBytes); + given(attempt.senderId()).willReturn(A_NEW_ACCOUNT_ID); + + final var call = subject.callFrom(attempt); + assertThat(call).isInstanceOf(HbarApproveCall.class); + } + + @Test + void callFromHbarApproveTest() { + final Bytes inputBytes = Bytes.wrapByteBuffer(HbarApproveTranslator.HBAR_APPROVE.encodeCall( + Tuple.of(APPROVED_HEADLONG_ADDRESS, UNAUTHORIZED_SPENDER_HEADLONG_ADDRESS, BigInteger.ONE))); + givenCommonForCall(inputBytes); + given(addressIdConverter.convert(APPROVED_HEADLONG_ADDRESS)).willReturn(B_NEW_ACCOUNT_ID); + given(addressIdConverter.convert(UNAUTHORIZED_SPENDER_HEADLONG_ADDRESS)).willReturn(A_NEW_ACCOUNT_ID); + + final var call = subject.callFrom(attempt); + assertThat(call).isInstanceOf(HbarApproveCall.class); + } + + private void givenCommonForCall(Bytes inputBytes) { + given(attempt.inputBytes()).willReturn(inputBytes.toArray()); + given(attempt.selector()).willReturn(inputBytes.slice(0, 4).toArray()); + given(attempt.enhancement()).willReturn(enhancement); + given(attempt.addressIdConverter()).willReturn(addressIdConverter); + given(addressIdConverter.convert(APPROVED_HEADLONG_ADDRESS)).willReturn(B_NEW_ACCOUNT_ID); + given(attempt.systemContractGasCalculator()).willReturn(gasCalculator); + } +} diff --git a/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/systemcontracts/hts/HtsCallAddressChecksTest.java b/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/systemcontracts/hts/CallAddressChecksTest.java similarity index 92% rename from hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/systemcontracts/hts/HtsCallAddressChecksTest.java rename to hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/systemcontracts/hts/CallAddressChecksTest.java index f399f4a3707c..910eec2f7714 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/systemcontracts/hts/HtsCallAddressChecksTest.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/systemcontracts/hts/CallAddressChecksTest.java @@ -19,7 +19,7 @@ import static org.junit.jupiter.api.Assertions.*; import static org.mockito.BDDMockito.given; -import com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.HtsCallAddressChecks; +import com.hedera.node.app.service.contract.impl.exec.systemcontracts.common.CallAddressChecks; import com.hedera.node.app.service.contract.impl.test.TestHelpers; import java.util.ArrayDeque; import java.util.Deque; @@ -30,7 +30,7 @@ import org.mockito.junit.jupiter.MockitoExtension; @ExtendWith(MockitoExtension.class) -class HtsCallAddressChecksTest { +class CallAddressChecksTest { @Mock private MessageFrame frame; @@ -39,7 +39,7 @@ class HtsCallAddressChecksTest { private Deque stack = new ArrayDeque<>(); - private final HtsCallAddressChecks subject = new HtsCallAddressChecks(); + private final CallAddressChecks subject = new CallAddressChecks(); @Test void detectsParentDelegateCall() { diff --git a/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/systemcontracts/hts/DispatchForResponseCodeHtsCallTest.java b/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/systemcontracts/hts/DispatchForResponseCodeHtsCallTest.java index 2829e22c350a..2ec83c6ff424 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/systemcontracts/hts/DispatchForResponseCodeHtsCallTest.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/systemcontracts/hts/DispatchForResponseCodeHtsCallTest.java @@ -35,6 +35,7 @@ import com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.DispatchForResponseCodeHtsCall; import com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.ReturnTypes; import com.hedera.node.app.service.contract.impl.records.ContractCallRecordBuilder; +import com.hedera.node.app.service.contract.impl.test.exec.systemcontracts.common.CallTestBase; import java.util.ArrayDeque; import java.util.Deque; import java.util.Optional; @@ -46,7 +47,7 @@ import org.mockito.junit.jupiter.MockitoExtension; @ExtendWith(MockitoExtension.class) -class DispatchForResponseCodeHtsCallTest extends HtsCallTestBase { +class DispatchForResponseCodeHtsCallTest extends CallTestBase { @Mock private DispatchForResponseCodeHtsCall.FailureCustomizer failureCustomizer; diff --git a/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/systemcontracts/hts/HtsCallAttemptTest.java b/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/systemcontracts/hts/HtsCallAttemptTest.java index 3210a98884c5..110f5fe434f1 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/systemcontracts/hts/HtsCallAttemptTest.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/systemcontracts/hts/HtsCallAttemptTest.java @@ -40,7 +40,7 @@ import com.hedera.hapi.node.transaction.TransactionBody; import com.hedera.node.app.service.contract.impl.exec.scope.VerificationStrategies; import com.hedera.node.app.service.contract.impl.exec.scope.VerificationStrategy; -import com.hedera.node.app.service.contract.impl.exec.systemcontracts.HtsCallTranslator; +import com.hedera.node.app.service.contract.impl.exec.systemcontracts.common.CallTranslator; import com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.AddressIdConverter; import com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.DispatchForResponseCodeHtsCall; import com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.HtsCallAttempt; @@ -72,6 +72,7 @@ import com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.transfer.Erc721TransferFromCall; import com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.transfer.Erc721TransferFromTranslator; import com.hedera.node.app.service.contract.impl.test.TestHelpers; +import com.hedera.node.app.service.contract.impl.test.exec.systemcontracts.common.CallTestBase; import com.swirlds.common.utility.CommonUtils; import java.math.BigInteger; import java.util.Arrays; @@ -83,7 +84,7 @@ import org.junit.jupiter.params.provider.CsvSource; import org.mockito.Mock; -class HtsCallAttemptTest extends HtsCallTestBase { +class HtsCallAttemptTest extends CallTestBase { @Mock private VerificationStrategies verificationStrategies; @@ -102,7 +103,7 @@ class HtsCallAttemptTest extends HtsCallTestBase { @Mock private MintDecoder mintDecoder; - private List callTranslators; + private List callTranslators; @BeforeEach void setUp() { diff --git a/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/systemcontracts/hts/HtsCallFactoryTest.java b/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/systemcontracts/hts/HtsCallFactoryTest.java index 23c769c93029..3766b82242f3 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/systemcontracts/hts/HtsCallFactoryTest.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/systemcontracts/hts/HtsCallFactoryTest.java @@ -30,14 +30,15 @@ import com.hedera.node.app.service.contract.impl.exec.gas.SystemContractGasCalculator; import com.hedera.node.app.service.contract.impl.exec.scope.VerificationStrategies; +import com.hedera.node.app.service.contract.impl.exec.systemcontracts.common.CallAddressChecks; import com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.AddressIdConverter; -import com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.HtsCallAddressChecks; import com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.HtsCallFactory; import com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.SyntheticIds; import com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.balanceof.BalanceOfCall; import com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.balanceof.BalanceOfTranslator; import com.hedera.node.app.service.contract.impl.exec.utils.FrameUtils; import com.hedera.node.app.service.contract.impl.state.ProxyWorldUpdater; +import com.hedera.node.app.service.contract.impl.test.exec.systemcontracts.common.CallTestBase; import java.util.ArrayDeque; import java.util.Deque; import java.util.List; @@ -48,9 +49,9 @@ import org.junit.jupiter.api.Test; import org.mockito.Mock; -class HtsCallFactoryTest extends HtsCallTestBase { +class HtsCallFactoryTest extends CallTestBase { @Mock - private HtsCallAddressChecks addressChecks; + private CallAddressChecks addressChecks; @Mock private SystemContractGasCalculator systemContractGasCalculator; @@ -101,7 +102,7 @@ void instantiatesCallWithInContextEnhancementAndDelegateCallInfo() { final var input = bytesForRedirect( BALANCE_OF.encodeCallWithArgs(asHeadlongAddress(NON_SYSTEM_LONG_ZERO_ADDRESS)), FUNGIBLE_TOKEN_ID); - final var attempt = subject.createCallAttemptFrom(input, FrameUtils.CallType.DIRECT_OR_TOKEN_REDIRECT, frame); + final var attempt = subject.createCallAttemptFrom(input, FrameUtils.CallType.DIRECT_OR_PROXY_REDIRECT, frame); final var call = Objects.requireNonNull(attempt.asExecutableCall()); assertInstanceOf(BalanceOfCall.class, call); diff --git a/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/systemcontracts/hts/allowance/GetAllowanceCallTest.java b/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/systemcontracts/hts/allowance/GetAllowanceCallTest.java index 4dfdbc801c73..a1fb0981f32d 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/systemcontracts/hts/allowance/GetAllowanceCallTest.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/systemcontracts/hts/allowance/GetAllowanceCallTest.java @@ -27,14 +27,14 @@ import com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.AddressIdConverter; import com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.allowance.GetAllowanceCall; import com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.allowance.GetAllowanceTranslator; -import com.hedera.node.app.service.contract.impl.test.exec.systemcontracts.hts.HtsCallTestBase; +import com.hedera.node.app.service.contract.impl.test.exec.systemcontracts.common.CallTestBase; import java.math.BigInteger; import org.apache.tuweni.bytes.Bytes; import org.hyperledger.besu.evm.frame.MessageFrame; import org.junit.jupiter.api.Test; import org.mockito.Mock; -class GetAllowanceCallTest extends HtsCallTestBase { +class GetAllowanceCallTest extends CallTestBase { private GetAllowanceCall subject; @Mock diff --git a/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/systemcontracts/hts/associations/AssociationsTranslatorTest.java b/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/systemcontracts/hts/associations/AssociationsTranslatorTest.java index 475a5911defa..cb7f59f664a8 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/systemcontracts/hts/associations/AssociationsTranslatorTest.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/systemcontracts/hts/associations/AssociationsTranslatorTest.java @@ -23,13 +23,13 @@ import com.hedera.hapi.node.transaction.TransactionBody; import com.hedera.node.app.service.contract.impl.exec.gas.DispatchType; import com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.associations.AssociationsTranslator; -import com.hedera.node.app.service.contract.impl.test.exec.systemcontracts.hts.HtsCallTestBase; +import com.hedera.node.app.service.contract.impl.test.exec.systemcontracts.common.CallTestBase; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.junit.jupiter.MockitoExtension; @ExtendWith(MockitoExtension.class) -class AssociationsTranslatorTest extends HtsCallTestBase { +class AssociationsTranslatorTest extends CallTestBase { @Test void dispatchesAssociateType() { given(gasCalculator.gasRequirement(TransactionBody.DEFAULT, DispatchType.ASSOCIATE, AccountID.DEFAULT)) diff --git a/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/systemcontracts/hts/balanceof/BalanceOfCallTest.java b/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/systemcontracts/hts/balanceof/BalanceOfCallTest.java index 4a0fd7167a7d..8235ee848148 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/systemcontracts/hts/balanceof/BalanceOfCallTest.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/systemcontracts/hts/balanceof/BalanceOfCallTest.java @@ -34,13 +34,13 @@ import com.esaulpaugh.headlong.abi.Address; import com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.balanceof.BalanceOfCall; import com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.balanceof.BalanceOfTranslator; -import com.hedera.node.app.service.contract.impl.test.exec.systemcontracts.hts.HtsCallTestBase; +import com.hedera.node.app.service.contract.impl.test.exec.systemcontracts.common.CallTestBase; import java.math.BigInteger; import org.apache.tuweni.bytes.Bytes; import org.hyperledger.besu.evm.frame.MessageFrame; import org.junit.jupiter.api.Test; -class BalanceOfCallTest extends HtsCallTestBase { +class BalanceOfCallTest extends CallTestBase { private final Address OWNER = asHeadlongAddress(EIP_1014_ADDRESS); private BalanceOfCall subject; diff --git a/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/systemcontracts/hts/burn/BurnDecoderTest.java b/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/systemcontracts/hts/burn/BurnDecoderTest.java index a2fe53a48f3e..154fc4827c45 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/systemcontracts/hts/burn/BurnDecoderTest.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/systemcontracts/hts/burn/BurnDecoderTest.java @@ -25,12 +25,12 @@ import com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.HtsCallAttempt; import com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.burn.BurnDecoder; import com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.burn.BurnTranslator; -import com.hedera.node.app.service.contract.impl.test.exec.systemcontracts.hts.HtsCallTestBase; +import com.hedera.node.app.service.contract.impl.test.exec.systemcontracts.common.CallTestBase; import java.math.BigInteger; import org.junit.jupiter.api.Test; import org.mockito.Mock; -public class BurnDecoderTest extends HtsCallTestBase { +public class BurnDecoderTest extends CallTestBase { @Mock private HtsCallAttempt attempt; diff --git a/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/systemcontracts/hts/create/ClassicCreatesCallTest.java b/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/systemcontracts/hts/create/ClassicCreatesCallTest.java index e001f744b4de..881f952780c9 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/systemcontracts/hts/create/ClassicCreatesCallTest.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/systemcontracts/hts/create/ClassicCreatesCallTest.java @@ -44,7 +44,7 @@ import com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.create.ClassicCreatesCall; import com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.create.CreateTranslator; import com.hedera.node.app.service.contract.impl.records.ContractCallRecordBuilder; -import com.hedera.node.app.service.contract.impl.test.exec.systemcontracts.hts.HtsCallTestBase; +import com.hedera.node.app.service.contract.impl.test.exec.systemcontracts.common.CallTestBase; import java.math.BigInteger; import java.util.ArrayDeque; import java.util.Deque; @@ -55,7 +55,7 @@ import org.junit.jupiter.api.Test; import org.mockito.Mock; -public class ClassicCreatesCallTest extends HtsCallTestBase { +public class ClassicCreatesCallTest extends CallTestBase { private static final org.hyperledger.besu.datatypes.Address FRAME_SENDER_ADDRESS = EIP_1014_ADDRESS; @Mock diff --git a/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/systemcontracts/hts/customfees/TokenCustomFeesCallTest.java b/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/systemcontracts/hts/customfees/TokenCustomFeesCallTest.java index 41cf351b9d79..b4f8104a7931 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/systemcontracts/hts/customfees/TokenCustomFeesCallTest.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/systemcontracts/hts/customfees/TokenCustomFeesCallTest.java @@ -27,7 +27,7 @@ import com.esaulpaugh.headlong.abi.Tuple; import com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.customfees.TokenCustomFeesCall; import com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.customfees.TokenCustomFeesTranslator; -import com.hedera.node.app.service.contract.impl.test.exec.systemcontracts.hts.HtsCallTestBase; +import com.hedera.node.app.service.contract.impl.test.exec.systemcontracts.common.CallTestBase; import java.util.Collections; import org.apache.tuweni.bytes.Bytes; import org.hyperledger.besu.evm.frame.MessageFrame; @@ -36,7 +36,7 @@ import org.mockito.junit.jupiter.MockitoExtension; @ExtendWith(MockitoExtension.class) -class TokenCustomFeesCallTest extends HtsCallTestBase { +class TokenCustomFeesCallTest extends CallTestBase { @Test void returnsTokenCustomFeesStatusForPresentToken() { final var subject = new TokenCustomFeesCall(gasCalculator, mockEnhancement(), false, FUNGIBLE_EVERYTHING_TOKEN); diff --git a/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/systemcontracts/hts/decimals/DecimalsCallTest.java b/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/systemcontracts/hts/decimals/DecimalsCallTest.java index 6e307f9bf5a8..076aff621f79 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/systemcontracts/hts/decimals/DecimalsCallTest.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/systemcontracts/hts/decimals/DecimalsCallTest.java @@ -23,13 +23,13 @@ import com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.decimals.DecimalsCall; import com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.decimals.DecimalsTranslator; -import com.hedera.node.app.service.contract.impl.test.exec.systemcontracts.hts.HtsCallTestBase; +import com.hedera.node.app.service.contract.impl.test.exec.systemcontracts.common.CallTestBase; import com.hedera.node.app.service.evm.contracts.operations.HederaExceptionalHaltReason; import org.apache.tuweni.bytes.Bytes; import org.hyperledger.besu.evm.frame.MessageFrame; import org.junit.jupiter.api.Test; -class DecimalsCallTest extends HtsCallTestBase { +class DecimalsCallTest extends CallTestBase { private DecimalsCall subject; diff --git a/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/systemcontracts/hts/defaultfreezestatus/DefaultFreezeStatusCallTest.java b/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/systemcontracts/hts/defaultfreezestatus/DefaultFreezeStatusCallTest.java index c06e6ce0684a..5e0a440f6f84 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/systemcontracts/hts/defaultfreezestatus/DefaultFreezeStatusCallTest.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/systemcontracts/hts/defaultfreezestatus/DefaultFreezeStatusCallTest.java @@ -24,7 +24,7 @@ import com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.defaultfreezestatus.DefaultFreezeStatusCall; import com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.defaultfreezestatus.DefaultFreezeStatusTranslator; -import com.hedera.node.app.service.contract.impl.test.exec.systemcontracts.hts.HtsCallTestBase; +import com.hedera.node.app.service.contract.impl.test.exec.systemcontracts.common.CallTestBase; import org.apache.tuweni.bytes.Bytes; import org.hyperledger.besu.evm.frame.MessageFrame; import org.junit.jupiter.api.Test; @@ -32,7 +32,7 @@ import org.mockito.junit.jupiter.MockitoExtension; @ExtendWith(MockitoExtension.class) -class DefaultFreezeStatusCallTest extends HtsCallTestBase { +class DefaultFreezeStatusCallTest extends CallTestBase { @Test void returnsDefaultFreezeStatusForPresentToken() { final var subject = new DefaultFreezeStatusCall(gasCalculator, mockEnhancement(), false, FUNGIBLE_TOKEN); diff --git a/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/systemcontracts/hts/defaultkycstatus/DefaultKycStatusCallTest.java b/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/systemcontracts/hts/defaultkycstatus/DefaultKycStatusCallTest.java index 0a2ae357038d..1ee4f0a24a0b 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/systemcontracts/hts/defaultkycstatus/DefaultKycStatusCallTest.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/systemcontracts/hts/defaultkycstatus/DefaultKycStatusCallTest.java @@ -24,7 +24,7 @@ import com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.defaultkycstatus.DefaultKycStatusCall; import com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.defaultkycstatus.DefaultKycStatusTranslator; -import com.hedera.node.app.service.contract.impl.test.exec.systemcontracts.hts.HtsCallTestBase; +import com.hedera.node.app.service.contract.impl.test.exec.systemcontracts.common.CallTestBase; import org.apache.tuweni.bytes.Bytes; import org.hyperledger.besu.evm.frame.MessageFrame; import org.junit.jupiter.api.Test; @@ -32,7 +32,7 @@ import org.mockito.junit.jupiter.MockitoExtension; @ExtendWith(MockitoExtension.class) -class DefaultKycStatusCallTest extends HtsCallTestBase { +class DefaultKycStatusCallTest extends CallTestBase { @Test void returnsDefaultKycStatusForPresentToken() { final var subject = new DefaultKycStatusCall(gasCalculator, mockEnhancement(), false, FUNGIBLE_TOKEN); diff --git a/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/systemcontracts/hts/fungibletokeninfo/FungibleTokenInfoCallTest.java b/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/systemcontracts/hts/fungibletokeninfo/FungibleTokenInfoCallTest.java index d5c6fcadef7a..16c2e7bd0ce7 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/systemcontracts/hts/fungibletokeninfo/FungibleTokenInfoCallTest.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/systemcontracts/hts/fungibletokeninfo/FungibleTokenInfoCallTest.java @@ -34,7 +34,7 @@ import com.esaulpaugh.headlong.abi.Tuple; import com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.fungibletokeninfo.FungibleTokenInfoCall; import com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.fungibletokeninfo.FungibleTokenInfoTranslator; -import com.hedera.node.app.service.contract.impl.test.exec.systemcontracts.hts.HtsCallTestBase; +import com.hedera.node.app.service.contract.impl.test.exec.systemcontracts.common.CallTestBase; import com.hedera.node.config.data.LedgerConfig; import com.swirlds.config.api.Configuration; import java.util.Collections; @@ -46,7 +46,7 @@ import org.mockito.junit.jupiter.MockitoExtension; @ExtendWith(MockitoExtension.class) -class FungibleTokenInfoCallTest extends HtsCallTestBase { +class FungibleTokenInfoCallTest extends CallTestBase { @Mock private Configuration config; diff --git a/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/systemcontracts/hts/getapproved/GetApprovedCallTest.java b/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/systemcontracts/hts/getapproved/GetApprovedCallTest.java index 32552b967d59..439e4302ae32 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/systemcontracts/hts/getapproved/GetApprovedCallTest.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/systemcontracts/hts/getapproved/GetApprovedCallTest.java @@ -31,12 +31,12 @@ import com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.getapproved.GetApprovedCall; import com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.getapproved.GetApprovedTranslator; -import com.hedera.node.app.service.contract.impl.test.exec.systemcontracts.hts.HtsCallTestBase; +import com.hedera.node.app.service.contract.impl.test.exec.systemcontracts.common.CallTestBase; import org.apache.tuweni.bytes.Bytes; import org.hyperledger.besu.evm.frame.MessageFrame; import org.junit.jupiter.api.Test; -public class GetApprovedCallTest extends HtsCallTestBase { +public class GetApprovedCallTest extends CallTestBase { private GetApprovedCall subject; diff --git a/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/systemcontracts/hts/grantapproval/ClassicGrantApprovalCallTest.java b/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/systemcontracts/hts/grantapproval/ClassicGrantApprovalCallTest.java index b4c1ff86d250..942deab0e6f5 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/systemcontracts/hts/grantapproval/ClassicGrantApprovalCallTest.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/systemcontracts/hts/grantapproval/ClassicGrantApprovalCallTest.java @@ -35,12 +35,12 @@ import com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.grantapproval.ClassicGrantApprovalCall; import com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.grantapproval.GrantApprovalTranslator; import com.hedera.node.app.service.contract.impl.records.ContractCallRecordBuilder; -import com.hedera.node.app.service.contract.impl.test.exec.systemcontracts.hts.HtsCallTestBase; +import com.hedera.node.app.service.contract.impl.test.exec.systemcontracts.common.CallTestBase; import org.hyperledger.besu.evm.frame.MessageFrame; import org.junit.jupiter.api.Test; import org.mockito.Mock; -public class ClassicGrantApprovalCallTest extends HtsCallTestBase { +public class ClassicGrantApprovalCallTest extends CallTestBase { private ClassicGrantApprovalCall subject; diff --git a/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/systemcontracts/hts/grantapproval/ERCGrantApprovalCallTest.java b/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/systemcontracts/hts/grantapproval/ERCGrantApprovalCallTest.java index 0299d927811c..e1c8ebf4cf45 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/systemcontracts/hts/grantapproval/ERCGrantApprovalCallTest.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/systemcontracts/hts/grantapproval/ERCGrantApprovalCallTest.java @@ -44,7 +44,7 @@ import com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.grantapproval.ERCGrantApprovalCall; import com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.grantapproval.GrantApprovalTranslator; import com.hedera.node.app.service.contract.impl.records.ContractCallRecordBuilder; -import com.hedera.node.app.service.contract.impl.test.exec.systemcontracts.hts.HtsCallTestBase; +import com.hedera.node.app.service.contract.impl.test.exec.systemcontracts.common.CallTestBase; import com.hedera.node.app.service.token.ReadableAccountStore; import org.apache.tuweni.units.bigints.UInt256; import org.hyperledger.besu.evm.frame.MessageFrame; @@ -52,7 +52,7 @@ import org.junit.jupiter.api.Test; import org.mockito.Mock; -class ERCGrantApprovalCallTest extends HtsCallTestBase { +class ERCGrantApprovalCallTest extends CallTestBase { private ERCGrantApprovalCall subject; @Mock diff --git a/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/systemcontracts/hts/isfrozen/IsFrozenCallTest.java b/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/systemcontracts/hts/isfrozen/IsFrozenCallTest.java index d39e556daf7e..812a03fc513b 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/systemcontracts/hts/isfrozen/IsFrozenCallTest.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/systemcontracts/hts/isfrozen/IsFrozenCallTest.java @@ -28,7 +28,7 @@ import com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.isfrozen.IsFrozenCall; import com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.isfrozen.IsFrozenTranslator; -import com.hedera.node.app.service.contract.impl.test.exec.systemcontracts.hts.HtsCallTestBase; +import com.hedera.node.app.service.contract.impl.test.exec.systemcontracts.common.CallTestBase; import com.hedera.node.app.service.contract.impl.utils.ConversionUtils; import org.apache.tuweni.bytes.Bytes; import org.hyperledger.besu.evm.frame.MessageFrame; @@ -38,7 +38,7 @@ import org.mockito.junit.jupiter.MockitoExtension; @ExtendWith(MockitoExtension.class) -class IsFrozenCallTest extends HtsCallTestBase { +class IsFrozenCallTest extends CallTestBase { @Test void returnsIsFrozenForPresentToken() { final var subject = new IsFrozenCall( diff --git a/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/systemcontracts/hts/iskyc/IsKycCallTest.java b/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/systemcontracts/hts/iskyc/IsKycCallTest.java index 796d35b56835..f38e7c0e1b3c 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/systemcontracts/hts/iskyc/IsKycCallTest.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/systemcontracts/hts/iskyc/IsKycCallTest.java @@ -28,7 +28,7 @@ import com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.iskyc.IsKycCall; import com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.iskyc.IsKycTranslator; -import com.hedera.node.app.service.contract.impl.test.exec.systemcontracts.hts.HtsCallTestBase; +import com.hedera.node.app.service.contract.impl.test.exec.systemcontracts.common.CallTestBase; import com.hedera.node.app.service.contract.impl.utils.ConversionUtils; import org.apache.tuweni.bytes.Bytes; import org.hyperledger.besu.evm.frame.MessageFrame; @@ -38,7 +38,7 @@ import org.mockito.junit.jupiter.MockitoExtension; @ExtendWith(MockitoExtension.class) -class IsKycCallTest extends HtsCallTestBase { +class IsKycCallTest extends CallTestBase { @Test void returnsIsKycForPresentToken() { final var subject = diff --git a/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/systemcontracts/hts/isoperator/IsApprovedForAllCallTest.java b/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/systemcontracts/hts/isoperator/IsApprovedForAllCallTest.java index 3ddcc8a57d4a..d9bf41037e44 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/systemcontracts/hts/isoperator/IsApprovedForAllCallTest.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/systemcontracts/hts/isoperator/IsApprovedForAllCallTest.java @@ -33,13 +33,13 @@ import com.esaulpaugh.headlong.abi.Address; import com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.isapprovedforall.IsApprovedForAllCall; import com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.isapprovedforall.IsApprovedForAllTranslator; -import com.hedera.node.app.service.contract.impl.test.exec.systemcontracts.hts.HtsCallTestBase; +import com.hedera.node.app.service.contract.impl.test.exec.systemcontracts.common.CallTestBase; import edu.umd.cs.findbugs.annotations.NonNull; import org.apache.tuweni.bytes.Bytes; import org.hyperledger.besu.evm.frame.MessageFrame; import org.junit.jupiter.api.Test; -class IsApprovedForAllCallTest extends HtsCallTestBase { +class IsApprovedForAllCallTest extends CallTestBase { private final Address THE_OWNER = asHeadlongAddress(asEvmAddress(A_NEW_ACCOUNT_ID.accountNumOrThrow())); private final Address THE_OPERATOR = asHeadlongAddress(asEvmAddress(B_NEW_ACCOUNT_ID.accountNumOrThrow())); private IsApprovedForAllCall subject; diff --git a/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/systemcontracts/hts/istoken/IsTokenCallTest.java b/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/systemcontracts/hts/istoken/IsTokenCallTest.java index 3cc5020b7e07..92f702b68146 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/systemcontracts/hts/istoken/IsTokenCallTest.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/systemcontracts/hts/istoken/IsTokenCallTest.java @@ -22,7 +22,7 @@ import com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.iskyc.IsKycTranslator; import com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.istoken.IsTokenCall; -import com.hedera.node.app.service.contract.impl.test.exec.systemcontracts.hts.HtsCallTestBase; +import com.hedera.node.app.service.contract.impl.test.exec.systemcontracts.common.CallTestBase; import org.apache.tuweni.bytes.Bytes; import org.hyperledger.besu.evm.frame.MessageFrame; import org.junit.jupiter.api.Test; @@ -30,7 +30,7 @@ import org.mockito.junit.jupiter.MockitoExtension; @ExtendWith(MockitoExtension.class) -class IsTokenCallTest extends HtsCallTestBase { +class IsTokenCallTest extends CallTestBase { @Test void returnsIsTokenForPresentToken() { final var subject = new IsTokenCall(gasCalculator, mockEnhancement(), false, FUNGIBLE_TOKEN); diff --git a/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/systemcontracts/hts/name/NameCallTest.java b/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/systemcontracts/hts/name/NameCallTest.java index fb6b4ff17761..b179251d0597 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/systemcontracts/hts/name/NameCallTest.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/systemcontracts/hts/name/NameCallTest.java @@ -23,12 +23,12 @@ import com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.name.NameCall; import com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.name.NameTranslator; -import com.hedera.node.app.service.contract.impl.test.exec.systemcontracts.hts.HtsCallTestBase; +import com.hedera.node.app.service.contract.impl.test.exec.systemcontracts.common.CallTestBase; import org.apache.tuweni.bytes.Bytes; import org.hyperledger.besu.evm.frame.MessageFrame; import org.junit.jupiter.api.Test; -class NameCallTest extends HtsCallTestBase { +class NameCallTest extends CallTestBase { private NameCall subject; @Test diff --git a/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/systemcontracts/hts/nfttokeninfo/NftTokenInfoCallTest.java b/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/systemcontracts/hts/nfttokeninfo/NftTokenInfoCallTest.java index 253d6c69b8a5..c811e2e90319 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/systemcontracts/hts/nfttokeninfo/NftTokenInfoCallTest.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/systemcontracts/hts/nfttokeninfo/NftTokenInfoCallTest.java @@ -39,7 +39,7 @@ import com.hedera.hapi.node.base.Timestamp; import com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.nfttokeninfo.NftTokenInfoCall; import com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.nfttokeninfo.NftTokenInfoTranslator; -import com.hedera.node.app.service.contract.impl.test.exec.systemcontracts.hts.HtsCallTestBase; +import com.hedera.node.app.service.contract.impl.test.exec.systemcontracts.common.CallTestBase; import com.hedera.node.config.data.LedgerConfig; import com.swirlds.config.api.Configuration; import java.util.Collections; @@ -51,7 +51,7 @@ import org.mockito.junit.jupiter.MockitoExtension; @ExtendWith(MockitoExtension.class) -class NftTokenInfoCallTest extends HtsCallTestBase { +class NftTokenInfoCallTest extends CallTestBase { @Mock private Configuration config; diff --git a/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/systemcontracts/hts/ownerof/OwnerOfCallTest.java b/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/systemcontracts/hts/ownerof/OwnerOfCallTest.java index 0e335c3cd7c1..c495e921880b 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/systemcontracts/hts/ownerof/OwnerOfCallTest.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/systemcontracts/hts/ownerof/OwnerOfCallTest.java @@ -36,13 +36,13 @@ import com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.ownerof.OwnerOfCall; import com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.ownerof.OwnerOfTranslator; import com.hedera.node.app.service.contract.impl.test.TestHelpers; -import com.hedera.node.app.service.contract.impl.test.exec.systemcontracts.hts.HtsCallTestBase; +import com.hedera.node.app.service.contract.impl.test.exec.systemcontracts.common.CallTestBase; import com.hedera.node.app.service.evm.contracts.operations.HederaExceptionalHaltReason; import org.apache.tuweni.bytes.Bytes; import org.hyperledger.besu.evm.frame.MessageFrame; import org.junit.jupiter.api.Test; -class OwnerOfCallTest extends HtsCallTestBase { +class OwnerOfCallTest extends CallTestBase { private OwnerOfCall subject; diff --git a/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/systemcontracts/hts/setapproval/SetApprovalForAllCallTest.java b/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/systemcontracts/hts/setapproval/SetApprovalForAllCallTest.java index 8a8efac5242d..3d12e0b1d74e 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/systemcontracts/hts/setapproval/SetApprovalForAllCallTest.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/systemcontracts/hts/setapproval/SetApprovalForAllCallTest.java @@ -34,7 +34,7 @@ import com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.setapproval.SetApprovalForAllCall; import com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.setapproval.SetApprovalForAllTranslator; import com.hedera.node.app.service.contract.impl.records.ContractCallRecordBuilder; -import com.hedera.node.app.service.contract.impl.test.exec.systemcontracts.hts.HtsCallTestBase; +import com.hedera.node.app.service.contract.impl.test.exec.systemcontracts.common.CallTestBase; import java.math.BigInteger; import org.apache.tuweni.bytes.Bytes; import org.hyperledger.besu.evm.frame.MessageFrame; @@ -47,7 +47,7 @@ import org.mockito.junit.jupiter.MockitoExtension; @ExtendWith(MockitoExtension.class) -public class SetApprovalForAllCallTest extends HtsCallTestBase { +public class SetApprovalForAllCallTest extends CallTestBase { @Mock private HtsCallAttempt attempt; diff --git a/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/systemcontracts/hts/symbol/SymbolCallTest.java b/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/systemcontracts/hts/symbol/SymbolCallTest.java index eda5f752706e..c308cbe71316 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/systemcontracts/hts/symbol/SymbolCallTest.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/systemcontracts/hts/symbol/SymbolCallTest.java @@ -23,12 +23,12 @@ import com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.symbol.SymbolCall; import com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.symbol.SymbolTranslator; -import com.hedera.node.app.service.contract.impl.test.exec.systemcontracts.hts.HtsCallTestBase; +import com.hedera.node.app.service.contract.impl.test.exec.systemcontracts.common.CallTestBase; import org.apache.tuweni.bytes.Bytes; import org.hyperledger.besu.evm.frame.MessageFrame; import org.junit.jupiter.api.Test; -class SymbolCallTest extends HtsCallTestBase { +class SymbolCallTest extends CallTestBase { private SymbolCall subject; diff --git a/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/systemcontracts/hts/tokenexpiry/TokenExpiryCallTest.java b/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/systemcontracts/hts/tokenexpiry/TokenExpiryCallTest.java index f92fc9fc38d0..d3e1467eaf7d 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/systemcontracts/hts/tokenexpiry/TokenExpiryCallTest.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/systemcontracts/hts/tokenexpiry/TokenExpiryCallTest.java @@ -28,7 +28,7 @@ import com.esaulpaugh.headlong.abi.Tuple; import com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.tokenexpiry.TokenExpiryCall; import com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.tokenexpiry.TokenExpiryTranslator; -import com.hedera.node.app.service.contract.impl.test.exec.systemcontracts.hts.HtsCallTestBase; +import com.hedera.node.app.service.contract.impl.test.exec.systemcontracts.common.CallTestBase; import org.apache.tuweni.bytes.Bytes; import org.hyperledger.besu.evm.frame.MessageFrame; import org.junit.jupiter.api.Test; @@ -36,7 +36,7 @@ import org.mockito.junit.jupiter.MockitoExtension; @ExtendWith(MockitoExtension.class) -class TokenExpiryCallTest extends HtsCallTestBase { +class TokenExpiryCallTest extends CallTestBase { @Test void returnsValidTokenExpiryStatusForPresentToken() { final var subject = new TokenExpiryCall(gasCalculator, mockEnhancement(), false, FUNGIBLE_EVERYTHING_TOKEN); diff --git a/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/systemcontracts/hts/tokeninfo/TokenInfoCallTest.java b/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/systemcontracts/hts/tokeninfo/TokenInfoCallTest.java index 225b3617ea1e..c700043454f9 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/systemcontracts/hts/tokeninfo/TokenInfoCallTest.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/systemcontracts/hts/tokeninfo/TokenInfoCallTest.java @@ -35,7 +35,7 @@ import com.esaulpaugh.headlong.abi.Tuple; import com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.tokeninfo.TokenInfoCall; import com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.tokeninfo.TokenInfoTranslator; -import com.hedera.node.app.service.contract.impl.test.exec.systemcontracts.hts.HtsCallTestBase; +import com.hedera.node.app.service.contract.impl.test.exec.systemcontracts.common.CallTestBase; import com.hedera.node.config.data.LedgerConfig; import com.swirlds.config.api.Configuration; import java.util.Collections; @@ -47,7 +47,7 @@ import org.mockito.junit.jupiter.MockitoExtension; @ExtendWith(MockitoExtension.class) -class TokenInfoCallTest extends HtsCallTestBase { +class TokenInfoCallTest extends CallTestBase { @Mock private Configuration config; diff --git a/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/systemcontracts/hts/tokenkey/TokenKeyCallTest.java b/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/systemcontracts/hts/tokenkey/TokenKeyCallTest.java index 67c2230c47cd..a95473bb75b7 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/systemcontracts/hts/tokenkey/TokenKeyCallTest.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/systemcontracts/hts/tokenkey/TokenKeyCallTest.java @@ -30,7 +30,7 @@ import com.hedera.hapi.node.base.Key; import com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.tokenkey.TokenKeyCall; import com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.tokenkey.TokenKeyTranslator; -import com.hedera.node.app.service.contract.impl.test.exec.systemcontracts.hts.HtsCallTestBase; +import com.hedera.node.app.service.contract.impl.test.exec.systemcontracts.common.CallTestBase; import org.apache.tuweni.bytes.Bytes; import org.hyperledger.besu.evm.frame.MessageFrame; import org.junit.jupiter.api.Test; @@ -38,7 +38,7 @@ import org.mockito.junit.jupiter.MockitoExtension; @ExtendWith(MockitoExtension.class) -class TokenKeyCallTest extends HtsCallTestBase { +class TokenKeyCallTest extends CallTestBase { @Test void returnsEd25519KeyStatusForPresentToken() { final var key = Key.newBuilder().ed25519(AN_ED25519_KEY.ed25519()).build(); diff --git a/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/systemcontracts/hts/tokentype/TokenTypeCallTest.java b/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/systemcontracts/hts/tokentype/TokenTypeCallTest.java index 10e66625c36e..a29c2e874073 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/systemcontracts/hts/tokentype/TokenTypeCallTest.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/systemcontracts/hts/tokentype/TokenTypeCallTest.java @@ -24,7 +24,7 @@ import com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.tokentype.TokenTypeCall; import com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.tokentype.TokenTypeTranslator; -import com.hedera.node.app.service.contract.impl.test.exec.systemcontracts.hts.HtsCallTestBase; +import com.hedera.node.app.service.contract.impl.test.exec.systemcontracts.common.CallTestBase; import org.apache.tuweni.bytes.Bytes; import org.hyperledger.besu.evm.frame.MessageFrame; import org.junit.jupiter.api.Test; @@ -32,7 +32,7 @@ import org.mockito.junit.jupiter.MockitoExtension; @ExtendWith(MockitoExtension.class) -class TokenTypeCallTest extends HtsCallTestBase { +class TokenTypeCallTest extends CallTestBase { @Test void returnsTokenTypeForPresentToken() { final var subject = new TokenTypeCall(gasCalculator, mockEnhancement(), false, FUNGIBLE_TOKEN); diff --git a/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/systemcontracts/hts/tokenuri/TokenUriCallTest.java b/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/systemcontracts/hts/tokenuri/TokenUriCallTest.java index 40acecb2f5fc..933e1f97968a 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/systemcontracts/hts/tokenuri/TokenUriCallTest.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/systemcontracts/hts/tokenuri/TokenUriCallTest.java @@ -25,12 +25,12 @@ import com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.tokenuri.TokenUriCall; import com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.tokenuri.TokenUriTranslator; -import com.hedera.node.app.service.contract.impl.test.exec.systemcontracts.hts.HtsCallTestBase; +import com.hedera.node.app.service.contract.impl.test.exec.systemcontracts.common.CallTestBase; import org.apache.tuweni.bytes.Bytes; import org.hyperledger.besu.evm.frame.MessageFrame; import org.junit.jupiter.api.Test; -class TokenUriCallTest extends HtsCallTestBase { +class TokenUriCallTest extends CallTestBase { private TokenUriCall subject; @Test diff --git a/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/systemcontracts/hts/totalsupply/TotalSupplyCallTest.java b/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/systemcontracts/hts/totalsupply/TotalSupplyCallTest.java index 05f6b4e51bcc..53ec243094d9 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/systemcontracts/hts/totalsupply/TotalSupplyCallTest.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/systemcontracts/hts/totalsupply/TotalSupplyCallTest.java @@ -23,13 +23,13 @@ import com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.totalsupply.TotalSupplyCall; import com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.totalsupply.TotalSupplyTranslator; -import com.hedera.node.app.service.contract.impl.test.exec.systemcontracts.hts.HtsCallTestBase; +import com.hedera.node.app.service.contract.impl.test.exec.systemcontracts.common.CallTestBase; import java.math.BigInteger; import org.apache.tuweni.bytes.Bytes; import org.hyperledger.besu.evm.frame.MessageFrame; import org.junit.jupiter.api.Test; -class TotalSupplyCallTest extends HtsCallTestBase { +class TotalSupplyCallTest extends CallTestBase { private TotalSupplyCall subject; diff --git a/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/systemcontracts/hts/transfer/ClassicTransfersCallTest.java b/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/systemcontracts/hts/transfer/ClassicTransfersCallTest.java index 9a38a050547d..9762ab85a592 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/systemcontracts/hts/transfer/ClassicTransfersCallTest.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/systemcontracts/hts/transfer/ClassicTransfersCallTest.java @@ -48,7 +48,7 @@ import com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.transfer.SpecialRewardReceivers; import com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.transfer.SystemAccountCreditScreen; import com.hedera.node.app.service.contract.impl.records.ContractCallRecordBuilder; -import com.hedera.node.app.service.contract.impl.test.exec.systemcontracts.hts.HtsCallTestBase; +import com.hedera.node.app.service.contract.impl.test.exec.systemcontracts.common.CallTestBase; import com.hedera.node.config.testfixtures.HederaTestConfigBuilder; import java.util.Optional; import java.util.function.Predicate; @@ -56,7 +56,7 @@ import org.junit.jupiter.api.Test; import org.mockito.Mock; -class ClassicTransfersCallTest extends HtsCallTestBase { +class ClassicTransfersCallTest extends CallTestBase { private static final TupleType INT64_ENCODER = TupleType.parse(ReturnTypes.INT_64); @Mock diff --git a/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/systemcontracts/hts/transfer/ClassicTransfersGasCalcTest.java b/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/systemcontracts/hts/transfer/ClassicTransfersGasCalcTest.java index 6d55b671aabb..bb23dd32cf9a 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/systemcontracts/hts/transfer/ClassicTransfersGasCalcTest.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/systemcontracts/hts/transfer/ClassicTransfersGasCalcTest.java @@ -31,12 +31,12 @@ import com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.transfer.ClassicTransfersCall; import com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.transfer.ClassicTransfersTranslator; import com.hedera.node.app.service.contract.impl.test.TestHelpers; -import com.hedera.node.app.service.contract.impl.test.exec.systemcontracts.hts.HtsCallTestBase; +import com.hedera.node.app.service.contract.impl.test.exec.systemcontracts.common.CallTestBase; import com.hedera.node.app.service.token.ReadableAccountStore; import org.junit.jupiter.api.Test; import org.mockito.Mock; -class ClassicTransfersGasCalcTest extends HtsCallTestBase { +class ClassicTransfersGasCalcTest extends CallTestBase { private static final long PRETEND_CRYPTO_CREATE_TINYBAR_PRICE = 1_234L; private static final long PRETEND_CRYPTO_UPDATE_TINYBAR_PRICE = 2_345L; private static final long PRETEND_LAZY_CREATION_TINYBAR_PRICE = diff --git a/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/systemcontracts/hts/transfer/ClassicTransfersTranslatorTest.java b/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/systemcontracts/hts/transfer/ClassicTransfersTranslatorTest.java index 536afdd98b86..35e3a8784199 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/systemcontracts/hts/transfer/ClassicTransfersTranslatorTest.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/systemcontracts/hts/transfer/ClassicTransfersTranslatorTest.java @@ -28,13 +28,13 @@ import com.hedera.hapi.node.base.AccountID; import com.hedera.node.app.service.contract.impl.exec.scope.VerificationStrategies; import com.hedera.node.app.service.contract.impl.exec.scope.VerificationStrategy; -import com.hedera.node.app.service.contract.impl.exec.systemcontracts.HtsCallTranslator; +import com.hedera.node.app.service.contract.impl.exec.systemcontracts.common.CallTranslator; import com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.AddressIdConverter; import com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.HtsCallAttempt; import com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.transfer.ClassicTransfersCall; import com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.transfer.ClassicTransfersDecoder; import com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.transfer.ClassicTransfersTranslator; -import com.hedera.node.app.service.contract.impl.test.exec.systemcontracts.hts.HtsCallTestBase; +import com.hedera.node.app.service.contract.impl.test.exec.systemcontracts.common.CallTestBase; import com.swirlds.common.utility.CommonUtils; import java.lang.reflect.Field; import java.util.List; @@ -46,7 +46,7 @@ import org.mockito.junit.jupiter.MockitoExtension; @ExtendWith(MockitoExtension.class) -class ClassicTransfersTranslatorTest extends HtsCallTestBase { +class ClassicTransfersTranslatorTest extends CallTestBase { private static final String ABI_ID_TRANSFER_TOKEN = "eca36917"; private static final String ABI_ID_CRYPTO_TRANSFER_V2 = "0e71804f"; @@ -65,7 +65,7 @@ class ClassicTransfersTranslatorTest extends HtsCallTestBase { private ClassicTransfersTranslator subject; - private List callTranslators; + private List callTranslators; @BeforeEach void setUp() { diff --git a/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/systemcontracts/hts/transfer/Erc20TransfersCallTest.java b/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/systemcontracts/hts/transfer/Erc20TransfersCallTest.java index 1f776a6801fd..2df0c464e9e9 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/systemcontracts/hts/transfer/Erc20TransfersCallTest.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/systemcontracts/hts/transfer/Erc20TransfersCallTest.java @@ -44,7 +44,7 @@ import com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.transfer.Erc20TransfersCall; import com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.transfer.SpecialRewardReceivers; import com.hedera.node.app.service.contract.impl.records.ContractCallRecordBuilder; -import com.hedera.node.app.service.contract.impl.test.exec.systemcontracts.hts.HtsCallTestBase; +import com.hedera.node.app.service.contract.impl.test.exec.systemcontracts.common.CallTestBase; import com.hedera.node.app.service.contract.impl.utils.ConversionUtils; import com.hedera.node.app.service.token.ReadableAccountStore; import org.apache.tuweni.bytes.Bytes; @@ -52,7 +52,7 @@ import org.junit.jupiter.api.Test; import org.mockito.Mock; -class Erc20TransfersCallTest extends HtsCallTestBase { +class Erc20TransfersCallTest extends CallTestBase { private static final Address FROM_ADDRESS = ConversionUtils.asHeadlongAddress(EIP_1014_ADDRESS.toArray()); private static final Address TO_ADDRESS = ConversionUtils.asHeadlongAddress(asEvmAddress(B_NEW_ACCOUNT_ID.accountNumOrThrow())); diff --git a/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/systemcontracts/hts/transfer/Erc721TransferFromCallTest.java b/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/systemcontracts/hts/transfer/Erc721TransferFromCallTest.java index 646d372f568c..dea852518d32 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/systemcontracts/hts/transfer/Erc721TransferFromCallTest.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/systemcontracts/hts/transfer/Erc721TransferFromCallTest.java @@ -38,7 +38,7 @@ import com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.transfer.Erc721TransferFromCall; import com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.transfer.SpecialRewardReceivers; import com.hedera.node.app.service.contract.impl.records.ContractCallRecordBuilder; -import com.hedera.node.app.service.contract.impl.test.exec.systemcontracts.hts.HtsCallTestBase; +import com.hedera.node.app.service.contract.impl.test.exec.systemcontracts.common.CallTestBase; import com.hedera.node.app.service.contract.impl.utils.ConversionUtils; import com.hedera.node.app.service.token.ReadableAccountStore; import org.apache.tuweni.bytes.Bytes; @@ -46,7 +46,7 @@ import org.junit.jupiter.api.Test; import org.mockito.Mock; -class Erc721TransferFromCallTest extends HtsCallTestBase { +class Erc721TransferFromCallTest extends CallTestBase { private static final Address FROM_ADDRESS = ConversionUtils.asHeadlongAddress(EIP_1014_ADDRESS.toArray()); private static final Address TO_ADDRESS = ConversionUtils.asHeadlongAddress(asEvmAddress(B_NEW_ACCOUNT_ID.accountNumOrThrow())); diff --git a/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/systemcontracts/hts/update/UpdateTranslatorTest.java b/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/systemcontracts/hts/update/UpdateTranslatorTest.java index 7ad8304aa956..140810066efa 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/systemcontracts/hts/update/UpdateTranslatorTest.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/systemcontracts/hts/update/UpdateTranslatorTest.java @@ -43,7 +43,7 @@ import com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.HtsCallAttempt; import com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.update.UpdateDecoder; import com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.update.UpdateTranslator; -import com.hedera.node.app.service.contract.impl.test.exec.systemcontracts.hts.HtsCallTestBase; +import com.hedera.node.app.service.contract.impl.test.exec.systemcontracts.common.CallTestBase; import com.hedera.node.app.service.token.ReadableAccountStore; import com.hedera.node.app.service.token.ReadableTokenStore; import org.apache.tuweni.bytes.Bytes; @@ -54,7 +54,7 @@ import org.mockito.junit.jupiter.MockitoExtension; @ExtendWith(MockitoExtension.class) -class UpdateTranslatorTest extends HtsCallTestBase { +class UpdateTranslatorTest extends CallTestBase { @Mock private HtsCallAttempt attempt; diff --git a/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/utils/ActionStackTest.java b/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/utils/ActionStackTest.java index 617a8e115ae1..cd4b61b468a8 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/utils/ActionStackTest.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/utils/ActionStackTest.java @@ -59,12 +59,14 @@ import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyNoInteractions; +import com.hedera.hapi.node.base.AccountID; import com.hedera.hapi.node.base.ContractID; import com.hedera.hapi.streams.ContractAction; import com.hedera.hapi.streams.ContractActionType; import com.hedera.node.app.service.contract.impl.exec.utils.ActionStack; import com.hedera.node.app.service.contract.impl.exec.utils.ActionWrapper; import com.hedera.node.app.service.contract.impl.exec.utils.ActionsHelper; +import com.hedera.node.app.service.contract.impl.state.HederaEvmAccount; import com.hedera.node.app.service.contract.impl.state.ProxyWorldUpdater; import com.hedera.node.app.service.contract.impl.utils.ConversionUtils; import com.hedera.pbj.runtime.io.buffer.Bytes; @@ -94,6 +96,9 @@ class ActionStackTest { @Mock private Account account; + @Mock + private HederaEvmAccount evmAccount; + @Mock private Operation operation; @@ -414,7 +419,7 @@ void finalizationLazyCallWithUnresolvedAddress() { final var gasUsed = REMAINING_GAS / 3; given(parentFrame.getRemainingGas()).willReturn(REMAINING_GAS - gasUsed); given(parentFrame.getOutputData()).willReturn(pbjToTuweniBytes(OUTPUT_DATA)); - givenUnresolvableEvmAddress(); + given(parentFrame.getWorldUpdater()).willReturn(worldUpdater); final var wrappedAction = new ActionWrapper(LAZY_CREATE_ACTION); allActions.add(wrappedAction); @@ -426,7 +431,8 @@ void finalizationLazyCallWithUnresolvedAddress() { assertEquals(wrappedAction, allActions.get(0)); final var finalAction = allActions.get(0).get(); assertEquals(gasUsed, finalAction.gasUsed()); - assertEquals(null, finalAction.recipientAccount()); + assertNull(finalAction.recipientAccount()); + assertEquals(tuweniToPbjBytes(EIP_1014_ADDRESS), finalAction.targetedAddress()); assertTrue(actionsStack.isEmpty()); } @@ -474,7 +480,7 @@ void emptyRevertReasonUsedIfMissing() { @Test void tracksTopLevelCreationAsExpected() { - givenResolvableEvmAddress(); + givenPresentEvmAddress(); given(parentFrame.getType()).willReturn(CONTRACT_CREATION); given(parentFrame.getOriginatorAddress()).willReturn(NON_SYSTEM_LONG_ZERO_ADDRESS); @@ -504,7 +510,7 @@ void tracksTopLevelCreationAsExpected() { @Test void tracksTopLevelCallToEoaAsExpected() { - givenResolvableEvmAddress(); + givenPresentEvmAddress(); given(worldUpdater.get(EIP_1014_ADDRESS)).willReturn(account); given(parentFrame.getType()).willReturn(MessageFrame.Type.MESSAGE_CALL); @@ -599,11 +605,15 @@ void tracksIntermediateCallAsExpected() { private void givenResolvableEvmAddress() { given(parentFrame.getWorldUpdater()).willReturn(worldUpdater); - given(worldUpdater.getHederaContractId(EIP_1014_ADDRESS)).willReturn(CALLED_CONTRACT_ID); + given(worldUpdater.getHederaAccount(EIP_1014_ADDRESS)).willReturn(evmAccount); + given(evmAccount.hederaId()) + .willReturn(AccountID.newBuilder() + .accountNum(CALLED_CONTRACT_ID.contractNumOrThrow()) + .build()); } - private void givenUnresolvableEvmAddress() { + private void givenPresentEvmAddress() { given(parentFrame.getWorldUpdater()).willReturn(worldUpdater); - given(worldUpdater.getHederaContractId(EIP_1014_ADDRESS)).willReturn(null); + given(worldUpdater.getHederaContractId(EIP_1014_ADDRESS)).willReturn(CALLED_CONTRACT_ID); } } diff --git a/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/utils/FrameUtilsTest.java b/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/utils/FrameUtilsTest.java index 02678fcfee46..079c5aeafa91 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/utils/FrameUtilsTest.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/utils/FrameUtilsTest.java @@ -17,7 +17,7 @@ package com.hedera.node.app.service.contract.impl.test.exec.utils; import static com.hedera.node.app.service.contract.impl.exec.utils.FrameUtils.CONFIG_CONTEXT_VARIABLE; -import static com.hedera.node.app.service.contract.impl.exec.utils.FrameUtils.CallType.DIRECT_OR_TOKEN_REDIRECT; +import static com.hedera.node.app.service.contract.impl.exec.utils.FrameUtils.CallType.DIRECT_OR_PROXY_REDIRECT; import static com.hedera.node.app.service.contract.impl.exec.utils.FrameUtils.CallType.QUALIFIED_DELEGATE; import static com.hedera.node.app.service.contract.impl.exec.utils.FrameUtils.CallType.UNQUALIFIED_DELEGATE; import static com.hedera.node.app.service.contract.impl.exec.utils.FrameUtils.HAPI_RECORD_BUILDER_CONTEXT_VARIABLE; @@ -27,6 +27,7 @@ import static com.hedera.node.app.service.contract.impl.exec.utils.FrameUtils.selfDestructBeneficiariesFor; import static com.hedera.node.app.service.contract.impl.exec.utils.FrameUtils.stackIncludesActiveAddress; import static com.hedera.node.app.service.contract.impl.test.TestHelpers.DEFAULT_CONFIG; +import static com.hedera.node.app.service.contract.impl.test.TestHelpers.DEFAULT_CONTRACTS_CONFIG; import static com.hedera.node.app.service.contract.impl.test.TestHelpers.EIP_1014_ADDRESS; import static com.hedera.node.app.service.contract.impl.test.TestHelpers.NON_SYSTEM_BUT_IS_LONG_ZERO_ADDRESS; import static com.hedera.node.app.service.contract.impl.test.TestHelpers.NON_SYSTEM_LONG_ZERO_ADDRESS; @@ -49,6 +50,8 @@ import com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.transfer.TransferEventLoggingUtils; import com.hedera.node.app.service.contract.impl.exec.utils.FrameUtils; import com.hedera.node.app.service.contract.impl.infra.StorageAccessTracker; +import com.hedera.node.app.service.contract.impl.state.ProxyEvmAccount; +import com.hedera.node.app.service.contract.impl.state.ProxyWorldUpdater; import com.hedera.node.app.service.contract.impl.utils.ConversionUtils; import com.hedera.node.app.service.contract.impl.utils.OpcodeUtils; import com.hedera.node.app.service.contract.impl.utils.SynthTxnUtils; @@ -92,9 +95,15 @@ class FrameUtilsTest { @Mock private WorldUpdater worldUpdater; + @Mock + private ProxyWorldUpdater proxyWorldUpdater; + @Mock private FeatureFlags featureFlags; + @Mock + private ProxyEvmAccount proxyEvmAccount; + private final Deque stack = new ArrayDeque<>(); @Test @@ -171,7 +180,7 @@ void unqualifiedDelegateDetectedValidationPass() { given(worldUpdater.get(EIP_1014_ADDRESS)).willReturn(account); given(account.getNonce()).willReturn(TOKEN_PROXY_ACCOUNT_NONCE); - assertEquals(DIRECT_OR_TOKEN_REDIRECT, FrameUtils.callTypeOf(frame)); + assertEquals(DIRECT_OR_PROXY_REDIRECT, FrameUtils.callTypeOf(frame)); } @Test @@ -207,6 +216,53 @@ void unqualifiedDelegateDetectedValidationPassWithPermittedCaller() { assertEquals(QUALIFIED_DELEGATE, FrameUtils.callTypeOf(frame)); } + @Test + void detectDelegateCallToAccount() { + // given + stack.push(initialFrame); + stack.push(frame); + + given(frame.getRecipientAddress()).willReturn(EIP_1014_ADDRESS); + given(frame.getContractAddress()).willReturn(EIP_1014_ADDRESS); + + assertEquals(DIRECT_OR_PROXY_REDIRECT, FrameUtils.callTypeForAccountOf(frame)); + } + + @Test + void detectRedirectToRegularAccount() { + // given + stack.push(initialFrame); + stack.push(frame); + given(frame.getWorldUpdater()).willReturn(proxyWorldUpdater); + given(frame.getMessageFrameStack()).willReturn(stack); + + given(frame.getRecipientAddress()).willReturn(EIP_1014_ADDRESS); + given(frame.getContractAddress()).willReturn(NON_SYSTEM_LONG_ZERO_ADDRESS); + given(initialFrame.getRecipientAddress()).willReturn(EIP_1014_ADDRESS); + given(initialFrame.getContractAddress()).willReturn(EIP_1014_ADDRESS); + + given(proxyWorldUpdater.getHederaAccount(EIP_1014_ADDRESS)).willReturn(proxyEvmAccount); + given(proxyEvmAccount.isRegularAccount()).willReturn(true); + + assertEquals(DIRECT_OR_PROXY_REDIRECT, FrameUtils.callTypeForAccountOf(frame)); + } + + @Test + void detectRedirectToNonRegularAccount() { + // given + stack.push(initialFrame); + stack.push(frame); + given(frame.getWorldUpdater()).willReturn(proxyWorldUpdater); + + given(frame.getRecipientAddress()).willReturn(EIP_1014_ADDRESS); + given(frame.getContractAddress()).willReturn(NON_SYSTEM_LONG_ZERO_ADDRESS); + + given(proxyWorldUpdater.getHederaAccount(EIP_1014_ADDRESS)).willReturn(proxyEvmAccount); + given(proxyEvmAccount.isRegularAccount()).willReturn(false); + + assertEquals(UNQUALIFIED_DELEGATE, FrameUtils.callTypeForAccountOf(frame)); + } + @Test void childOfParentExecutingDelegateCodeDoesAcquireSenderAuthorizationViaDelegateCall() { given(initialFrame.getRecipientAddress()).willReturn(EIP_1014_ADDRESS); @@ -264,7 +320,7 @@ void checkContractRequired() { given(frame.getMessageFrameStack()).willReturn(stack); given(initialFrame.getContextVariable(CONFIG_CONTEXT_VARIABLE)).willReturn(DEFAULT_CONFIG); assertTrue(FrameUtils.contractRequired(frame, EIP_1014_ADDRESS, featureFlags)); - verify(featureFlags).isAllowCallsToNonContractAccountsEnabled(DEFAULT_CONFIG, null); + verify(featureFlags).isAllowCallsToNonContractAccountsEnabled(DEFAULT_CONTRACTS_CONFIG, null); } @Test @@ -275,7 +331,7 @@ void checkContractRequiredLongZero() { assertTrue(FrameUtils.contractRequired(frame, NON_SYSTEM_BUT_IS_LONG_ZERO_ADDRESS, featureFlags)); verify(featureFlags) .isAllowCallsToNonContractAccountsEnabled( - DEFAULT_CONFIG, + DEFAULT_CONTRACTS_CONFIG, NON_SYSTEM_BUT_IS_LONG_ZERO_ADDRESS .toUnsignedBigInteger() .longValueExact()); @@ -287,7 +343,7 @@ void checkContractRequiredLongZeroTooBig() { given(frame.getMessageFrameStack()).willReturn(stack); given(initialFrame.getContextVariable(CONFIG_CONTEXT_VARIABLE)).willReturn(DEFAULT_CONFIG); assertTrue(FrameUtils.contractRequired(frame, Address.fromHexString("0xFFFFFFFFFFFFFFFF"), featureFlags)); - verify(featureFlags).isAllowCallsToNonContractAccountsEnabled(DEFAULT_CONFIG, null); + verify(featureFlags).isAllowCallsToNonContractAccountsEnabled(DEFAULT_CONTRACTS_CONFIG, null); } void givenNonInitialFrame() { diff --git a/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/v030/Version030FeatureFlagsTest.java b/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/v030/Version030FeatureFlagsTest.java index a292d419c260..0e8c6e78d9e2 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/v030/Version030FeatureFlagsTest.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/v030/Version030FeatureFlagsTest.java @@ -17,6 +17,8 @@ package com.hedera.node.app.service.contract.impl.test.exec.v030; import static com.hedera.node.app.service.contract.impl.exec.utils.FrameUtils.CONFIG_CONTEXT_VARIABLE; +import static com.hedera.node.app.service.contract.impl.test.TestHelpers.DEFAULT_CONFIG; +import static com.hedera.node.app.service.contract.impl.test.TestHelpers.DEFAULT_CONTRACTS_CONFIG; import static org.junit.jupiter.api.Assertions.*; import static org.mockito.BDDMockito.given; @@ -43,10 +45,9 @@ class Version030FeatureFlagsTest { void everythingIsDisabled() { given(frame.getMessageFrameStack()).willReturn(stack); given(stack.isEmpty()).willReturn(true); - final var config = HederaTestConfigBuilder.create().getOrCreateConfig(); - given(frame.getContextVariable(CONFIG_CONTEXT_VARIABLE)).willReturn(config); + given(frame.getContextVariable(CONFIG_CONTEXT_VARIABLE)).willReturn(DEFAULT_CONFIG); assertFalse(subject.isImplicitCreationEnabled(frame)); - assertFalse(subject.isAllowCallsToNonContractAccountsEnabled(config, null)); + assertFalse(subject.isAllowCallsToNonContractAccountsEnabled(DEFAULT_CONTRACTS_CONFIG, null)); } @Test diff --git a/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/handlers/AdapterUtils.java b/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/handlers/AdapterUtils.java index 1bf5b98d6214..1f13c0b63dad 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/handlers/AdapterUtils.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/handlers/AdapterUtils.java @@ -79,12 +79,12 @@ import com.hedera.hapi.node.state.token.AccountCryptoAllowance; import com.hedera.hapi.node.state.token.AccountFungibleTokenAllowance; import com.hedera.node.app.service.token.ReadableAccountStore; -import com.hedera.node.app.spi.fixtures.state.MapReadableKVState; -import com.hedera.node.app.spi.state.ReadableKVState; -import com.hedera.node.app.spi.state.ReadableStates; import com.hedera.pbj.runtime.OneOf; import com.hedera.pbj.runtime.io.buffer.Bytes; import com.hedera.test.utils.TestFixturesKeyLookup; +import com.swirlds.platform.test.fixtures.state.MapReadableKVState; +import com.swirlds.state.spi.ReadableKVState; +import com.swirlds.state.spi.ReadableStates; import java.util.HashMap; import java.util.List; import java.util.Map; diff --git a/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/handlers/EthereumTransactionHandlerTest.java b/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/handlers/EthereumTransactionHandlerTest.java index cbc8a43b577a..8cf22220f9d1 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/handlers/EthereumTransactionHandlerTest.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/handlers/EthereumTransactionHandlerTest.java @@ -25,7 +25,6 @@ import static com.hedera.node.app.service.contract.impl.test.TestHelpers.HEVM_CREATION; import static com.hedera.node.app.service.contract.impl.test.TestHelpers.SENDER_ID; import static com.hedera.node.app.service.contract.impl.test.TestHelpers.SUCCESS_RESULT_WITH_SIGNER_NONCE; -import static com.hedera.node.app.service.contract.impl.test.TestHelpers.processorsForAllCurrentEvmVersions; import static com.hedera.node.app.spi.fixtures.Assertions.assertThrowsPreCheck; import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; import static org.mockito.BDDMockito.given; @@ -132,7 +131,6 @@ void setUp() { void setUpTransactionProcessing() { final var contractsConfig = DEFAULT_CONFIG.getConfigData(ContractsConfig.class); - final var processors = processorsForAllCurrentEvmVersions(transactionProcessor); final var contextTransactionProcessor = new ContextTransactionProcessor( HydratedEthTxData.successFrom(ETH_DATA_WITH_TO_ADDRESS), @@ -144,7 +142,7 @@ void setUpTransactionProcessing() { baseProxyWorldUpdater, hevmTransactionFactory, feesOnlyUpdater, - processors, + transactionProcessor, customGasCharging); given(component.contextTransactionProcessor()).willReturn(contextTransactionProcessor); diff --git a/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/infra/HevmTransactionFactoryTest.java b/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/infra/HevmTransactionFactoryTest.java index c8bf3d348724..59c19fdb42b9 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/infra/HevmTransactionFactoryTest.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/infra/HevmTransactionFactoryTest.java @@ -19,6 +19,7 @@ import static com.hedera.hapi.node.base.ResponseCodeEnum.AUTORENEW_DURATION_NOT_IN_RANGE; import static com.hedera.hapi.node.base.ResponseCodeEnum.BAD_ENCODING; import static com.hedera.hapi.node.base.ResponseCodeEnum.CONTRACT_BYTECODE_EMPTY; +import static com.hedera.hapi.node.base.ResponseCodeEnum.CONTRACT_DELETED; import static com.hedera.hapi.node.base.ResponseCodeEnum.CONTRACT_FILE_EMPTY; import static com.hedera.hapi.node.base.ResponseCodeEnum.CONTRACT_NEGATIVE_GAS; import static com.hedera.hapi.node.base.ResponseCodeEnum.CONTRACT_NEGATIVE_VALUE; @@ -42,6 +43,7 @@ import static com.hedera.node.app.service.contract.impl.test.TestHelpers.AN_ED25519_KEY; import static com.hedera.node.app.service.contract.impl.test.TestHelpers.AUTO_ASSOCIATING_CONTRACTS_CONFIG; import static com.hedera.node.app.service.contract.impl.test.TestHelpers.AUTO_ASSOCIATING_LEDGER_CONFIG; +import static com.hedera.node.app.service.contract.impl.test.TestHelpers.A_DELETED_CONTRACT; import static com.hedera.node.app.service.contract.impl.test.TestHelpers.CALLED_CONTRACT_ID; import static com.hedera.node.app.service.contract.impl.test.TestHelpers.CALL_DATA; import static com.hedera.node.app.service.contract.impl.test.TestHelpers.CONSTRUCTOR_PARAMS; @@ -89,6 +91,7 @@ import com.hedera.hapi.node.transaction.TransactionBody; import com.hedera.node.app.hapi.utils.ethereum.EthTxData; import com.hedera.node.app.hapi.utils.ethereum.EthTxSigs; +import com.hedera.node.app.service.contract.impl.exec.FeatureFlags; import com.hedera.node.app.service.contract.impl.hevm.HederaEvmContext; import com.hedera.node.app.service.contract.impl.hevm.HederaEvmTransaction; import com.hedera.node.app.service.contract.impl.hevm.HydratedEthTxData; @@ -127,6 +130,9 @@ class HevmTransactionFactoryTest { @Mock private GasCalculator gasCalculator; + @Mock + private FeatureFlags featureFlags; + @Mock private ReadableFileStore fileStore; @@ -152,6 +158,7 @@ void setUp() { networkInfo, DEFAULT_LEDGER_CONFIG, DEFAULT_HEDERA_CONFIG, + featureFlags, gasCalculator, DEFAULT_STAKING_CONFIG, DEFAULT_CONTRACTS_CONFIG, @@ -220,6 +227,38 @@ void fromHapiCallGenerateExceptionTransaction() { assertNull(transaction.hapiCreation()); } + @Test + void fromHapiCallThrowsOnDeletedContractIfFeatureFlagNotEnabled() { + given(accountStore.getContractById(CALLED_CONTRACT_ID)).willReturn(A_DELETED_CONTRACT); + assertCallFailsWith(CONTRACT_DELETED, b -> b.amount(123L) + .functionParameters(CALL_DATA) + .contractID(CALLED_CONTRACT_ID) + .gas(DEFAULT_CONTRACTS_CONFIG.maxGasPerSec())); + } + + @Test + void fromHapiCallIgnoresDeletedContractIfFeatureFlagEnabled() { + given(accountStore.getContractById(CALLED_CONTRACT_ID)).willReturn(A_DELETED_CONTRACT); + given(featureFlags.isAllowCallsToNonContractAccountsEnabled( + DEFAULT_CONTRACTS_CONFIG, CALLED_CONTRACT_ID.contractNumOrThrow())) + .willReturn(true); + final var transaction = getManufacturedCall(b -> b.amount(123L) + .functionParameters(CALL_DATA) + .contractID(CALLED_CONTRACT_ID) + .gas(DEFAULT_CONTRACTS_CONFIG.maxGasPerSec())); + assertEquals(SENDER_ID, transaction.senderId()); + assertEquals(CALLED_CONTRACT_ID, transaction.contractId()); + assertNull(transaction.relayerId()); + assertFalse(transaction.hasExpectedNonce()); + assertEquals(CALL_DATA, transaction.payload()); + assertNull(transaction.chainId()); + assertEquals(123L, transaction.value()); + assertEquals(DEFAULT_CONTRACTS_CONFIG.maxGasPerSec(), transaction.gasLimit()); + assertFalse(transaction.hasOfferedGasPrice()); + assertFalse(transaction.hasMaxGasAllowance()); + assertNull(transaction.hapiCreation()); + } + @Test void fromHapiCallUsesCallParamsWhenSet() { final var transaction = getManufacturedCall(b -> b.amount(123L) @@ -529,7 +568,6 @@ void fromHapiEthRepresentsCallAsExpected() { givenInsteadHydratedEthTxWithRightChainId(ETH_DATA_WITH_TO_ADDRESS); final var sig = EthTxSigs.extractSignatures(ETH_DATA_WITH_TO_ADDRESS); given(ethereumSignatures.computeIfAbsent(ETH_DATA_WITH_TO_ADDRESS)).willReturn(sig); - System.out.println(ETH_DATA_WITH_TO_ADDRESS); final var transaction = getManufacturedEthTx(b -> b.maxGasAllowance(MAX_GAS_ALLOWANCE)); final var expectedSenderId = AccountID.newBuilder().alias(Bytes.wrap(sig.address())).build(); @@ -679,6 +717,7 @@ private void givenInsteadAutoAssociatingSubject() { networkInfo, AUTO_ASSOCIATING_LEDGER_CONFIG, DEFAULT_HEDERA_CONFIG, + featureFlags, gasCalculator, DEFAULT_STAKING_CONFIG, AUTO_ASSOCIATING_CONTRACTS_CONFIG, @@ -697,6 +736,7 @@ private void givenInsteadFailedHydrationSubject() { networkInfo, AUTO_ASSOCIATING_LEDGER_CONFIG, DEFAULT_HEDERA_CONFIG, + featureFlags, gasCalculator, DEFAULT_STAKING_CONFIG, AUTO_ASSOCIATING_CONTRACTS_CONFIG, @@ -715,6 +755,7 @@ private void givenInsteadHydratedEthTxWithWrongChainId(@NonNull final EthTxData networkInfo, AUTO_ASSOCIATING_LEDGER_CONFIG, DEFAULT_HEDERA_CONFIG, + featureFlags, gasCalculator, DEFAULT_STAKING_CONFIG, DEFAULT_CONTRACTS_CONFIG, @@ -733,6 +774,7 @@ private void givenInsteadHydratedEthTxWithRightChainId(@NonNull final EthTxData networkInfo, AUTO_ASSOCIATING_LEDGER_CONFIG, DEFAULT_HEDERA_CONFIG, + featureFlags, gasCalculator, DEFAULT_STAKING_CONFIG, DEV_CHAIN_ID_CONTRACTS_CONFIG, diff --git a/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/infra/IterableStorageManagerTest.java b/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/infra/IterableStorageManagerTest.java index 49ba2509b631..6e3a97b012e7 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/infra/IterableStorageManagerTest.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/infra/IterableStorageManagerTest.java @@ -16,6 +16,7 @@ package com.hedera.node.app.service.contract.impl.test.infra; +import static com.hedera.node.app.service.contract.impl.utils.ConversionUtils.tuweniToPbjBytes; import static org.mockito.ArgumentMatchers.any; import static org.mockito.BDDMockito.given; import static org.mockito.Mockito.never; @@ -34,7 +35,6 @@ import com.hedera.node.app.service.contract.impl.state.StorageAccess; import com.hedera.node.app.service.contract.impl.state.StorageAccesses; import com.hedera.node.app.service.contract.impl.state.StorageSizeChange; -import com.hedera.node.app.service.contract.impl.utils.ConversionUtils; import com.hedera.pbj.runtime.io.buffer.Bytes; import java.util.List; import org.apache.tuweni.units.bigints.UInt256; @@ -49,9 +49,9 @@ class IterableStorageManagerTest { ContractID.newBuilder().contractNum(1L).build(); private final ContractID CONTRACT_2 = ContractID.newBuilder().contractNum(2L).build(); - private final Bytes BYTES_1 = ConversionUtils.tuweniToPbjBytes(UInt256.ONE); - private final Bytes BYTES_2 = ConversionUtils.tuweniToPbjBytes(UInt256.valueOf(2L)); - private final Bytes BYTES_3 = ConversionUtils.tuweniToPbjBytes(UInt256.valueOf(3L)); + private final Bytes BYTES_1 = tuweniToPbjBytes(UInt256.ONE); + private final Bytes BYTES_2 = tuweniToPbjBytes(UInt256.valueOf(2L)); + private final Bytes BYTES_3 = tuweniToPbjBytes(UInt256.valueOf(3L)); @Mock private HederaOperations hederaOperations; @@ -151,6 +151,33 @@ void removeFirstSlot() { verifyNoMoreInteractions(hederaOperations); } + @Test + void stillRemovesSlotEvenIfNextSlotIsMissing() { + final var accesses = List.of(new StorageAccesses( + CONTRACT_1, List.of(StorageAccess.newWrite(UInt256.ONE, UInt256.MAX_VALUE, UInt256.ZERO)))); + + final var sizeChanges = List.of(new StorageSizeChange(CONTRACT_1, 1, 0)); + + given(enhancement.nativeOperations()).willReturn(hederaNativeOperations); + given(hederaNativeOperations.getAccount(CONTRACT_1)).willReturn(account); + given(account.firstContractStorageKey()).willReturn(BYTES_1); + given(enhancement.operations()).willReturn(hederaOperations); + // Deleting the first slot + given(store.getSlotValue(new SlotKey(CONTRACT_1, BYTES_1))) + .willReturn(new SlotValue(BYTES_1, Bytes.EMPTY, BYTES_2)); + // The next slot is missing (invariant failure, should be impossible) + given(store.getSlotValueForModify(new SlotKey(CONTRACT_1, BYTES_2))).willReturn(null); + + subject.persistChanges(enhancement, accesses, sizeChanges, store); + + // Model deleting the first contract storage + verify(store).removeSlot(new SlotKey(CONTRACT_1, BYTES_1)); + // The new first key is BYTES_2 as the first slot for the contract was deleted. + verify(hederaOperations).updateStorageMetadata(CONTRACT_1, BYTES_1, -1); + verifyNoMoreInteractions(store); + verifyNoMoreInteractions(hederaOperations); + } + @Test void removeSecondSlot() { final var accesses = List.of(new StorageAccesses( @@ -199,7 +226,7 @@ void removeSlotValueNotFound() { // The new first key is BYTES_1 as before running the test verify(hederaOperations).updateStorageMetadata(CONTRACT_1, BYTES_1, -1); - verifyNoMoreInteractions(store); + verify(store).removeSlot(new SlotKey(CONTRACT_1, BYTES_2)); verifyNoMoreInteractions(hederaOperations); } @@ -222,7 +249,7 @@ void insertSlotIntoEmptyStorage() { verify(store) .putSlot( new SlotKey(CONTRACT_1, BYTES_2), - new SlotValue(ConversionUtils.tuweniToPbjBytes(UInt256.MAX_VALUE), Bytes.EMPTY, Bytes.EMPTY)); + new SlotValue(tuweniToPbjBytes(UInt256.MAX_VALUE), Bytes.EMPTY, Bytes.EMPTY)); // The new first key is BYTES_2 verify(hederaOperations).updateStorageMetadata(CONTRACT_1, BYTES_2, 1); @@ -230,6 +257,53 @@ void insertSlotIntoEmptyStorage() { verifyNoMoreInteractions(hederaOperations); } + @Test + void multipleInsertsUseLatestHeadPointer() { + final var accesses = List.of(new StorageAccesses( + CONTRACT_1, + List.of( + StorageAccess.newWrite(UInt256.valueOf(2L), UInt256.ZERO, UInt256.MAX_VALUE), + StorageAccess.newWrite(UInt256.valueOf(3L), UInt256.ZERO, UInt256.MAX_VALUE)))); + + final var sizeChanges = List.of(new StorageSizeChange(CONTRACT_1, 0, 2)); + + given(enhancement.nativeOperations()).willReturn(hederaNativeOperations); + given(hederaNativeOperations.getAccount(CONTRACT_1)).willReturn(account); + given(account.firstContractStorageKey()).willReturn(BYTES_1); + given(enhancement.operations()).willReturn(hederaOperations); + given(store.getSlotValueForModify(new SlotKey(CONTRACT_1, BYTES_1))) + .willReturn(new SlotValue(tuweniToPbjBytes(UInt256.ONE), Bytes.EMPTY, Bytes.EMPTY)); + given(store.getSlotValueForModify(new SlotKey(CONTRACT_1, BYTES_2))) + .willReturn(new SlotValue(tuweniToPbjBytes(UInt256.ONE), Bytes.EMPTY, BYTES_1)); + + // Should insert into the head of the existing storage list + subject.persistChanges(enhancement, accesses, sizeChanges, store); + + // The first insert (BYTES_2) + verify(store) + .putSlot( + new SlotKey(CONTRACT_1, BYTES_2), + new SlotValue(tuweniToPbjBytes(UInt256.MAX_VALUE), Bytes.EMPTY, BYTES_1)); + verify(store) + .putSlot( + new SlotKey(CONTRACT_1, BYTES_1), + new SlotValue(tuweniToPbjBytes(UInt256.ONE), BYTES_2, Bytes.EMPTY)); + // The second insert (BYTES_3) + verify(store) + .putSlot( + new SlotKey(CONTRACT_1, BYTES_3), + new SlotValue(tuweniToPbjBytes(UInt256.MAX_VALUE), Bytes.EMPTY, BYTES_2)); + verify(store) + .putSlot( + new SlotKey(CONTRACT_1, BYTES_2), + new SlotValue(tuweniToPbjBytes(UInt256.ONE), BYTES_3, BYTES_1)); + + // The new first key is BYTES_3 + verify(hederaOperations).updateStorageMetadata(CONTRACT_1, BYTES_3, 2); + verifyNoMoreInteractions(store); + verifyNoMoreInteractions(hederaOperations); + } + @Test void insertSlotIntoExistingStorage() { final var accesses = List.of(new StorageAccesses( @@ -241,6 +315,40 @@ void insertSlotIntoExistingStorage() { given(hederaNativeOperations.getAccount(CONTRACT_1)).willReturn(account); given(account.firstContractStorageKey()).willReturn(BYTES_1); given(enhancement.operations()).willReturn(hederaOperations); + given(store.getSlotValueForModify(new SlotKey(CONTRACT_1, BYTES_1))) + .willReturn(new SlotValue(tuweniToPbjBytes(UInt256.ONE), Bytes.EMPTY, Bytes.EMPTY)); + + // Should insert into the head of the existing storage list + subject.persistChanges(enhancement, accesses, sizeChanges, store); + + verify(store) + .putSlot( + new SlotKey(CONTRACT_1, BYTES_2), + new SlotValue(tuweniToPbjBytes(UInt256.MAX_VALUE), Bytes.EMPTY, BYTES_1)); + verify(store) + .putSlot( + new SlotKey(CONTRACT_1, BYTES_1), + new SlotValue(tuweniToPbjBytes(UInt256.ONE), BYTES_2, Bytes.EMPTY)); + + // The new first key is BYTES_2 + verify(hederaOperations).updateStorageMetadata(CONTRACT_1, BYTES_2, 1); + verifyNoMoreInteractions(store); + verifyNoMoreInteractions(hederaOperations); + } + + @Test + void slotStillInsertedEvenWithMissingPointer() { + final var accesses = List.of(new StorageAccesses( + CONTRACT_1, List.of(StorageAccess.newWrite(UInt256.valueOf(2L), UInt256.ZERO, UInt256.MAX_VALUE)))); + + final var sizeChanges = List.of(new StorageSizeChange(CONTRACT_1, 0, 1)); + + given(enhancement.nativeOperations()).willReturn(hederaNativeOperations); + given(hederaNativeOperations.getAccount(CONTRACT_1)).willReturn(account); + given(account.firstContractStorageKey()).willReturn(BYTES_1); + given(enhancement.operations()).willReturn(hederaOperations); + // The next slot is missing (invariant failure, should be impossible) + given(store.getSlotValueForModify(new SlotKey(CONTRACT_1, BYTES_1))).willReturn(null); // Insert into the second slot subject.persistChanges(enhancement, accesses, sizeChanges, store); @@ -249,7 +357,7 @@ void insertSlotIntoExistingStorage() { verify(store) .putSlot( new SlotKey(CONTRACT_1, BYTES_2), - new SlotValue(ConversionUtils.tuweniToPbjBytes(UInt256.MAX_VALUE), Bytes.EMPTY, BYTES_1)); + new SlotValue(tuweniToPbjBytes(UInt256.MAX_VALUE), Bytes.EMPTY, BYTES_1)); // The new first key is BYTES_2 verify(hederaOperations).updateStorageMetadata(CONTRACT_1, BYTES_2, 1); diff --git a/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/state/ProxyEvmAccountTest.java b/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/state/ProxyEvmAccountTest.java index 9b3b16e7eb7c..54d0dc73da79 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/state/ProxyEvmAccountTest.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/state/ProxyEvmAccountTest.java @@ -175,4 +175,13 @@ void delegatesCheckingContract() { given(hederaState.isContract(ACCOUNT_ID)).willReturn(true); assertTrue(subject.isContract()); } + + @Test + void testRegularAccount() { + given(hederaState.isContract(ACCOUNT_ID)).willReturn(true); + assertFalse(subject.isRegularAccount()); + + given(hederaState.isContract(ACCOUNT_ID)).willReturn(false); + assertTrue(subject.isRegularAccount()); + } } diff --git a/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/state/ReadableContractStateStoreTest.java b/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/state/ReadableContractStateStoreTest.java index 52a810460b7c..4159b5a19c9e 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/state/ReadableContractStateStoreTest.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/state/ReadableContractStateStoreTest.java @@ -29,9 +29,9 @@ import com.hedera.hapi.node.state.contract.SlotKey; import com.hedera.hapi.node.state.contract.SlotValue; import com.hedera.node.app.service.contract.impl.state.ReadableContractStateStore; -import com.hedera.node.app.spi.state.ReadableKVState; -import com.hedera.node.app.spi.state.ReadableStates; import com.hedera.pbj.runtime.io.buffer.Bytes; +import com.swirlds.state.spi.ReadableKVState; +import com.swirlds.state.spi.ReadableStates; import java.util.Collections; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; diff --git a/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/state/TokenEvmAccountTest.java b/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/state/TokenEvmAccountTest.java index ec606d8d3c2d..92e12fca3b96 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/state/TokenEvmAccountTest.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/state/TokenEvmAccountTest.java @@ -128,4 +128,9 @@ void doesNotSupportMutators() { assertThrows(UnsupportedOperationException.class, subject::clearStorage); assertThrows(UnsupportedOperationException.class, subject::getUpdatedStorage); } + + @Test + void neverRegularAccount() { + assertFalse(subject.isRegularAccount()); + } } diff --git a/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/state/WritableContractStateStoreTest.java b/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/state/WritableContractStateStoreTest.java index feaef69d2aea..bd7d92898efe 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/state/WritableContractStateStoreTest.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/state/WritableContractStateStoreTest.java @@ -31,10 +31,10 @@ import com.hedera.hapi.node.state.contract.SlotValue; import com.hedera.node.app.service.contract.impl.state.WritableContractStateStore; import com.hedera.node.app.spi.metrics.StoreMetricsService; -import com.hedera.node.app.spi.state.WritableKVState; -import com.hedera.node.app.spi.state.WritableStates; import com.hedera.node.config.testfixtures.HederaTestConfigBuilder; import com.hedera.pbj.runtime.io.buffer.Bytes; +import com.swirlds.state.spi.WritableKVState; +import com.swirlds.state.spi.WritableStates; import java.util.Set; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; diff --git a/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/utils/ConversionUtilsTest.java b/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/utils/ConversionUtilsTest.java index 4342d196bf33..9931d46c77fa 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/utils/ConversionUtilsTest.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/utils/ConversionUtilsTest.java @@ -37,6 +37,7 @@ import static com.hedera.node.app.service.contract.impl.utils.ConversionUtils.asLongZeroAddress; import static com.hedera.node.app.service.contract.impl.utils.ConversionUtils.asNumberedAccountId; import static com.hedera.node.app.service.contract.impl.utils.ConversionUtils.asNumberedContractId; +import static com.hedera.node.app.service.contract.impl.utils.ConversionUtils.contractIDToBesuAddress; import static com.hedera.node.app.service.contract.impl.utils.ConversionUtils.numberOfLongZero; import static com.hedera.node.app.service.contract.impl.utils.ConversionUtils.pbjLogFrom; import static com.hedera.node.app.service.contract.impl.utils.ConversionUtils.pbjLogsFrom; @@ -81,6 +82,11 @@ void outOfRangeBiValuesAreZero() { 0L, asExactLongValueOrZero(BigInteger.valueOf(Long.MIN_VALUE).subtract(BigInteger.ONE))); } + @Test + void besuAddressIsZeroForDefaultContractId() { + assertEquals(Address.ZERO, contractIDToBesuAddress(ContractID.DEFAULT)); + } + @Test void inRangeBiValuesAreExact() { assertEquals(Long.MAX_VALUE, asExactLongValueOrZero(BigInteger.valueOf(Long.MAX_VALUE))); diff --git a/hedera-node/hedera-smart-contract-service/build.gradle.kts b/hedera-node/hedera-smart-contract-service/build.gradle.kts index 874a07998be4..de9461838dae 100644 --- a/hedera-node/hedera-smart-contract-service/build.gradle.kts +++ b/hedera-node/hedera-smart-contract-service/build.gradle.kts @@ -14,6 +14,9 @@ * limitations under the License. */ -plugins { id("com.hedera.hashgraph.conventions") } +plugins { + id("com.hedera.gradle.services") + id("com.hedera.gradle.services-publish") +} description = "Hedera Smart Contract Service API" diff --git a/hedera-node/hedera-token-service-impl/build.gradle.kts b/hedera-node/hedera-token-service-impl/build.gradle.kts index db36886d9d78..f9aad8365a50 100644 --- a/hedera-node/hedera-token-service-impl/build.gradle.kts +++ b/hedera-node/hedera-token-service-impl/build.gradle.kts @@ -14,7 +14,10 @@ * limitations under the License. */ -plugins { id("com.hedera.hashgraph.conventions") } +plugins { + id("com.hedera.gradle.services") + id("com.hedera.gradle.services-publish") +} description = "Default Hedera Token Service Implementation" @@ -27,6 +30,7 @@ testModuleInfo { requires("com.hedera.node.app.spi.test.fixtures") requires("com.hedera.node.config.test.fixtures") requires("com.hedera.node.app.service.token.test.fixtures") + requires("com.swirlds.platform.core.test.fixtures") requires("com.swirlds.config.extensions.test.fixtures") requires("org.assertj.core") requires("org.hamcrest") diff --git a/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/BlocklistParser.java b/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/BlocklistParser.java index 54b54c9194cd..12b7e300e58e 100644 --- a/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/BlocklistParser.java +++ b/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/BlocklistParser.java @@ -44,6 +44,8 @@ public class BlocklistParser { * Makes sure that all blocked accounts contained in the blocklist resource are present in state, and creates their definitions (if necessary). * *

    Note: this method assumes that blocklists are enabled – it does not check that config property + * @param blocklistResourceName the blocklist resource + * @return a list of blocked account info records */ public List parse(@NonNull final String blocklistResourceName) { final List fileLines = readFileLines(blocklistResourceName); diff --git a/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/CryptoSignatureWaiversImpl.java b/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/CryptoSignatureWaiversImpl.java index d3236ed2d863..e8f4b556a01d 100644 --- a/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/CryptoSignatureWaiversImpl.java +++ b/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/CryptoSignatureWaiversImpl.java @@ -37,6 +37,10 @@ public class CryptoSignatureWaiversImpl implements CryptoSignatureWaivers { private final Authorizer authorizer; + /** + * Default constructor for injection. + * @param authorizer the {@link Authorizer} to use for checking authorization + */ @Inject public CryptoSignatureWaiversImpl(@NonNull final Authorizer authorizer) { this.authorizer = requireNonNull(authorizer); diff --git a/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/ReadableAccountStoreImpl.java b/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/ReadableAccountStoreImpl.java index 0de638482532..c73306d1c742 100644 --- a/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/ReadableAccountStoreImpl.java +++ b/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/ReadableAccountStoreImpl.java @@ -27,9 +27,9 @@ import com.hedera.hapi.node.state.primitives.ProtoBytes; import com.hedera.hapi.node.state.token.Account; import com.hedera.node.app.service.token.ReadableAccountStore; -import com.hedera.node.app.spi.state.ReadableKVState; -import com.hedera.node.app.spi.state.ReadableStates; import com.hedera.pbj.runtime.io.buffer.Bytes; +import com.swirlds.state.spi.ReadableKVState; +import com.swirlds.state.spi.ReadableStates; import edu.umd.cs.findbugs.annotations.NonNull; import edu.umd.cs.findbugs.annotations.Nullable; import java.util.Optional; diff --git a/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/ReadableNetworkStakingRewardsStoreImpl.java b/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/ReadableNetworkStakingRewardsStoreImpl.java index d99d913aee98..faf3841322b8 100644 --- a/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/ReadableNetworkStakingRewardsStoreImpl.java +++ b/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/ReadableNetworkStakingRewardsStoreImpl.java @@ -21,8 +21,8 @@ import com.hedera.hapi.node.state.token.NetworkStakingRewards; import com.hedera.node.app.service.token.ReadableNetworkStakingRewardsStore; -import com.hedera.node.app.spi.state.ReadableSingletonState; -import com.hedera.node.app.spi.state.ReadableStates; +import com.swirlds.state.spi.ReadableSingletonState; +import com.swirlds.state.spi.ReadableStates; import edu.umd.cs.findbugs.annotations.NonNull; /** diff --git a/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/ReadableNftStoreImpl.java b/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/ReadableNftStoreImpl.java index d37405c7111a..2f166bfc4087 100644 --- a/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/ReadableNftStoreImpl.java +++ b/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/ReadableNftStoreImpl.java @@ -22,8 +22,8 @@ import com.hedera.hapi.node.state.token.Nft; import com.hedera.node.app.service.token.ReadableNftStore; import com.hedera.node.app.service.token.ReadableTokenStore; -import com.hedera.node.app.spi.state.ReadableKVState; -import com.hedera.node.app.spi.state.ReadableStates; +import com.swirlds.state.spi.ReadableKVState; +import com.swirlds.state.spi.ReadableStates; import edu.umd.cs.findbugs.annotations.NonNull; import edu.umd.cs.findbugs.annotations.Nullable; diff --git a/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/ReadableStakingInfoStoreImpl.java b/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/ReadableStakingInfoStoreImpl.java index e96ebd924f4f..f3c09be5ba1c 100644 --- a/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/ReadableStakingInfoStoreImpl.java +++ b/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/ReadableStakingInfoStoreImpl.java @@ -21,8 +21,8 @@ import com.hedera.hapi.node.state.common.EntityNumber; import com.hedera.hapi.node.state.token.StakingNodeInfo; import com.hedera.node.app.service.token.ReadableStakingInfoStore; -import com.hedera.node.app.spi.state.ReadableKVState; -import com.hedera.node.app.spi.state.ReadableStates; +import com.swirlds.state.spi.ReadableKVState; +import com.swirlds.state.spi.ReadableStates; import edu.umd.cs.findbugs.annotations.NonNull; import edu.umd.cs.findbugs.annotations.Nullable; import java.util.Collections; diff --git a/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/ReadableTokenRelationStoreImpl.java b/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/ReadableTokenRelationStoreImpl.java index 51792f310811..569c8489e4fe 100644 --- a/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/ReadableTokenRelationStoreImpl.java +++ b/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/ReadableTokenRelationStoreImpl.java @@ -23,8 +23,8 @@ import com.hedera.hapi.node.state.common.EntityIDPair; import com.hedera.hapi.node.state.token.TokenRelation; import com.hedera.node.app.service.token.ReadableTokenRelationStore; -import com.hedera.node.app.spi.state.ReadableKVState; -import com.hedera.node.app.spi.state.ReadableStates; +import com.swirlds.state.spi.ReadableKVState; +import com.swirlds.state.spi.ReadableStates; import edu.umd.cs.findbugs.annotations.NonNull; import edu.umd.cs.findbugs.annotations.Nullable; diff --git a/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/ReadableTokenStoreImpl.java b/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/ReadableTokenStoreImpl.java index 28b46df71fec..b44ad9059e98 100644 --- a/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/ReadableTokenStoreImpl.java +++ b/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/ReadableTokenStoreImpl.java @@ -23,8 +23,8 @@ import com.hedera.hapi.node.state.token.Token; import com.hedera.hapi.node.transaction.CustomFee; import com.hedera.node.app.service.token.ReadableTokenStore; -import com.hedera.node.app.spi.state.ReadableKVState; -import com.hedera.node.app.spi.state.ReadableStates; +import com.swirlds.state.spi.ReadableKVState; +import com.swirlds.state.spi.ReadableStates; import edu.umd.cs.findbugs.annotations.NonNull; import edu.umd.cs.findbugs.annotations.Nullable; import java.util.Optional; diff --git a/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/RecordFinalizerBase.java b/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/RecordFinalizerBase.java index af41cb4cde21..c95221f02eb3 100644 --- a/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/RecordFinalizerBase.java +++ b/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/RecordFinalizerBase.java @@ -30,10 +30,8 @@ import com.hedera.hapi.node.base.NftTransfer; import com.hedera.hapi.node.base.TokenID; import com.hedera.hapi.node.base.TokenTransferList; -import com.hedera.hapi.node.base.TokenType; import com.hedera.hapi.node.state.common.EntityIDPair; import com.hedera.hapi.node.state.token.Token; -import com.hedera.node.app.service.token.ReadableTokenStore; import com.hedera.node.app.spi.workflows.HandleException; import edu.umd.cs.findbugs.annotations.NonNull; import edu.umd.cs.findbugs.annotations.Nullable; @@ -95,26 +93,16 @@ protected Map hbarChangesFrom( * Gets all token tokenRelation balances for all modified token relations from the given {@link WritableTokenRelationStore} depending on the given token type. * * @param writableTokenRelStore the {@link WritableTokenRelationStore} to get the token relation balances from - * @param tokenStore the {@link ReadableTokenStore} to get the token from - * @param tokenType the type of token to get token changes from * @return a {@link Map} of {@link EntityIDPair} to {@link Long} representing the token relation balances for all * modified token relations */ @NonNull protected Map tokenRelChangesFrom( - @NonNull final WritableTokenRelationStore writableTokenRelStore, - @NonNull final ReadableTokenStore tokenStore, - @NonNull TokenType tokenType, - final boolean filterZeroAmounts) { + @NonNull final WritableTokenRelationStore writableTokenRelStore, final boolean filterZeroAmounts) { final var tokenRelChanges = new HashMap(); for (final EntityIDPair modifiedRel : writableTokenRelStore.modifiedTokens()) { final var relAcctId = modifiedRel.accountIdOrThrow(); final var relTokenId = modifiedRel.tokenIdOrThrow(); - final var token = requireNonNull(tokenStore.get(relTokenId)); - // Add this to fungible token transfer list only if this token is a fungible token - if (!token.tokenType().equals(tokenType)) { - continue; - } final var modifiedTokenRel = writableTokenRelStore.get(relAcctId, relTokenId); final var persistedTokenRel = writableTokenRelStore.getOriginalValue(relAcctId, relTokenId); @@ -193,12 +181,21 @@ protected List asTokenTransferListFrom( /** * Gets all nft ownership changes for all modified nfts from the given {@link WritableNftStore}. + * While building the nft changes, we also update the token relation changes. If there are any token relation + * changes for the sender and receiver of the NFTs, we reduce the balance change for that relation by 1 for the + * receiver and increment the balance change for sender by 1. This is to ensure that the NFT transfer is not double + * counted in the token relation changes and the NFT changes. + * We also update the token relation changes for the treasury account when the treasury account changes. + * * @param writableNftStore the {@link WritableNftStore} to get the nft ownership changes from + * @param tokenRelChanges the {@link Map} of {@link EntityIDPair} to {@link Long} representing the token relation * @return a {@link Map} of {@link TokenID} to {@link List} of {@link NftTransfer} representing the nft ownership */ @NonNull protected Map> nftChangesFrom( - @NonNull final WritableNftStore writableNftStore, @NonNull final WritableTokenStore writableTokenStore) { + @NonNull final WritableNftStore writableNftStore, + @NonNull final WritableTokenStore writableTokenStore, + final Map tokenRelChanges) { final var nftChanges = new HashMap>(); for (final NftID nftId : writableNftStore.modifiedNfts()) { final var modifiedNft = writableNftStore.get(nftId); @@ -241,7 +238,7 @@ protected Map> nftChangesFrom( if (receiverAccountId.equals(senderAccountId)) { continue; } - updateNftChanges(nftId, senderAccountId, receiverAccountId, nftChanges); + updateNftChanges(nftId, senderAccountId, receiverAccountId, nftChanges, tokenRelChanges); } for (final var tokenId : writableTokenStore.modifiedTokens()) { @@ -257,7 +254,8 @@ protected Map> nftChangesFrom( NftID.newBuilder().tokenId(tokenId).serialNumber(-1).build(), originalToken.treasuryAccountId(), modifiedToken.treasuryAccountId(), - nftChanges); + nftChanges, + tokenRelChanges); } } return nftChanges; @@ -278,16 +276,27 @@ private static boolean bothExistButTreasuryChanged( /** * Updates the given {@link Map} of {@link TokenID} to {@link List} of {@link NftTransfer} representing the nft * ownership changes. - * @param nftId the {@link NftID} representing the nft - * @param senderAccountId the {@link AccountID} representing the sender account ID + * While building the nft changes, we also update the token relation changes. If there are any token relation + * changes for the sender and receiver of the NFTs, we reduce the balance change for that relation by 1 for the + * receiver and increment the balance change for sender by 1. This is to ensure that the NFT transfer is not double + * counted in the token relation changes and the NFT changes. + * We also update the token relation changes for the treasury account when the treasury account changes. + * + * @param nftId the {@link NftID} representing the nft + * @param senderAccountId the {@link AccountID} representing the sender account ID * @param receiverAccountId the {@link AccountID} representing the receiver account ID - * @param nftChanges the {@link Map} of {@link TokenID} to {@link List} of {@link NftTransfer} representing the nft + * @param nftChanges the {@link Map} of {@link TokenID} to {@link List} of {@link NftTransfer} representing the nft + * @param tokenRelChanges the {@link Map} of {@link EntityIDPair} to {@link Long} representing the token relation */ private static void updateNftChanges( final NftID nftId, final AccountID senderAccountId, final AccountID receiverAccountId, - final HashMap> nftChanges) { + final HashMap> nftChanges, + @Nullable final Map tokenRelChanges) { + final var isMint = senderAccountId.accountNum() == 0; + final var isWipeOrBurn = receiverAccountId.accountNum() == 0; + final var isTreasuryChange = nftId.serialNumber() == -1; final var nftTransfer = NftTransfer.newBuilder() .serialNumber(nftId.serialNumber()) .senderAccountID(senderAccountId) @@ -301,6 +310,35 @@ private static void updateNftChanges( final var currentNftChanges = nftChanges.get(nftId.tokenId()); currentNftChanges.add(nftTransfer); nftChanges.put(nftId.tokenId(), currentNftChanges); + + if (!tokenRelChanges.isEmpty()) { + final var receiverEntityIdPair = EntityIDPair.newBuilder() + .accountId(receiverAccountId) + .tokenId(nftId.tokenId()) + .build(); + final var senderEntityIdPair = EntityIDPair.newBuilder() + .accountId(senderAccountId) + .tokenId(nftId.tokenId()) + .build(); + if (isMint || isWipeOrBurn || isTreasuryChange) { + // The mint amount is not shown in the token transfer list. So for a mint transaction we need not show + // the token transfer changes to treasury. If it is a treasury change, we need not show the transfer + // changes to treasury + tokenRelChanges.remove(receiverEntityIdPair); + tokenRelChanges.remove(senderEntityIdPair); + } else { + // Because the NFT transfer list already contains all information about how the + // sender/receiver # of owned NFT counts are changing, we don't repeat this in + // the tokenTransferLists; these merge() calls remove the duplicate information + if (tokenRelChanges.merge(receiverEntityIdPair, -1L, Long::sum) == 0) { + tokenRelChanges.remove(receiverEntityIdPair); + } + // We don't need to show the mint amounts in token transfer List + if (tokenRelChanges.merge(senderEntityIdPair, 1L, Long::sum) == 0) { + tokenRelChanges.remove(senderEntityIdPair); + } + } + } } /** diff --git a/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/TokenServiceImpl.java b/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/TokenServiceImpl.java index 3f1d3c648f56..438889286018 100644 --- a/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/TokenServiceImpl.java +++ b/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/TokenServiceImpl.java @@ -43,13 +43,35 @@ /** An implementation of the {@link TokenService} interface. */ public class TokenServiceImpl implements TokenService { + /** + * The state key for NFTs. + */ public static final String NFTS_KEY = "NFTS"; + /** + * The state key for tokens. + */ public static final String TOKENS_KEY = "TOKENS"; + /** + * The state key for aliases. + */ public static final String ALIASES_KEY = "ALIASES"; + /** + * The state key for accounts. + */ public static final String ACCOUNTS_KEY = "ACCOUNTS"; + /** + * The state key for token relations. + */ public static final String TOKEN_RELS_KEY = "TOKEN_RELS"; + /** + * The state key for staking infos. + */ public static final String STAKING_INFO_KEY = "STAKING_INFOS"; + /** + * The state key for network rewards. + */ public static final String STAKING_NETWORK_REWARDS_KEY = "STAKING_NETWORK_REWARDS"; + private final Supplier> sysAccts; private final Supplier> stakingAccts; private final Supplier> treasuryAccts; @@ -62,11 +84,11 @@ public class TokenServiceImpl implements TokenService { * of {@link Account} objects, where each account object represents a SYNTHETIC RECORD (see {@link * SyntheticRecordsGenerator} for more details). Even though these sorted sets contain account objects, * these account objects may or may not yet exist in state. They're needed for event recovery circumstances - * @param sysAccts - * @param stakingAccts - * @param treasuryAccts - * @param miscAccts - * @param blocklistAccts + * @param sysAccts the supplier for system accounts + * @param stakingAccts the supplier for staking accounts + * @param treasuryAccts the supplier for treasury accounts + * @param miscAccts the supplier for miscellaneous accounts + * @param blocklistAccts the supplier for blocklisted accounts */ public TokenServiceImpl( @NonNull final Supplier> sysAccts, @@ -74,11 +96,11 @@ public TokenServiceImpl( @NonNull final Supplier> treasuryAccts, @NonNull final Supplier> miscAccts, @NonNull final Supplier> blocklistAccts) { - this.sysAccts = sysAccts; - this.stakingAccts = stakingAccts; - this.treasuryAccts = treasuryAccts; - this.miscAccts = miscAccts; - this.blocklistAccts = blocklistAccts; + this.sysAccts = requireNonNull(sysAccts); + this.stakingAccts = requireNonNull(stakingAccts); + this.treasuryAccts = requireNonNull(treasuryAccts); + this.miscAccts = requireNonNull(miscAccts); + this.blocklistAccts = requireNonNull(blocklistAccts); } /** @@ -100,22 +122,43 @@ public void registerSchemas(@NonNull final SchemaRegistry registry, @NonNull fin registry.register(modTokenSchema); } + /** + * Sets the NFTs from state + * @param fs the virtual map of unique token keys to unique token values + */ public void setNftsFromState(@Nullable final VirtualMap fs) { modTokenSchema.setNftsFromState(fs); } + /** + * Sets the token relations from state + * @param fs the virtual map of entity num virtual keys to on disk token relations + */ public void setTokenRelsFromState(@Nullable final VirtualMap fs) { modTokenSchema.setTokenRelsFromState(fs); } + /** + * Sets the accounts from state + * @param fs the virtual map of entity num virtual keys to on disk accounts + */ public void setAcctsFromState(@Nullable final VirtualMap fs) { modTokenSchema.setAcctsFromState(fs); } + /** + * Sets the tokens from state + * @param fs the merkle map of entity nums to merkle tokens + */ public void setTokensFromState(@Nullable final MerkleMap fs) { modTokenSchema.setTokensFromState(fs); } + /** + * Sets the staking info from state + * @param stakingFs the merkle map of entity nums to merkle staking infos + * @param mnc the merkle network context + */ public void setStakingFs( @Nullable final MerkleMap stakingFs, @Nullable final MerkleNetworkContext mnc) { diff --git a/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/TokenServiceInjectionModule.java b/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/TokenServiceInjectionModule.java index c759788bba68..adf159fa7ad7 100644 --- a/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/TokenServiceInjectionModule.java +++ b/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/TokenServiceInjectionModule.java @@ -66,85 +66,205 @@ */ @Module public interface TokenServiceInjectionModule { - + /** + * Binds the {@link CryptoSignatureWaivers} to the {@link CryptoSignatureWaiversImpl} + * @param impl the implementation of the {@link CryptoSignatureWaivers} + * @return the bound implementation + */ @Binds CryptoSignatureWaivers cryptoSignatureWaivers(CryptoSignatureWaiversImpl impl); + /** + * Binds the {@link StakingRewardsHandler} to the {@link StakingRewardsHandlerImpl} + * @param stakingRewardsHandler the implementation of the {@link StakingRewardsHandler} + * @return the bound implementation + */ @Binds StakingRewardsHandler stakingRewardHandler(StakingRewardsHandlerImpl stakingRewardsHandler); - + /** + * Binds the {@link StakeRewardCalculator} to the {@link StakeRewardCalculatorImpl} + * @param rewardCalculator the implementation of the {@link StakeRewardCalculator} + * @return the bound implementation + */ @Binds StakeRewardCalculator stakeRewardCalculator(StakeRewardCalculatorImpl rewardCalculator); - + /** + * Binds the {@link ParentRecordFinalizer} to the {@link FinalizeParentRecordHandler} + * @param parentRecordFinalizer the implementation of the {@link ParentRecordFinalizer} + * @return the bound implementation + */ @Binds ParentRecordFinalizer parentRecordFinalizer(FinalizeParentRecordHandler parentRecordFinalizer); - + /** + * Binds the {@link ChildRecordFinalizer} to the {@link FinalizeChildRecordHandler} + * @param childRecordHandler the implementation of the {@link ChildRecordFinalizer} + * @return the bound implementation + */ @Binds ChildRecordFinalizer childRecordFinalizer(FinalizeChildRecordHandler childRecordHandler); - + /** + * Returns the {@link CryptoAddLiveHashHandler} + * @return the handler + */ CryptoAddLiveHashHandler cryptoAddLiveHashHandler(); - + /** + * Returns the {@link CryptoApproveAllowanceHandler} + * @return the handler + */ CryptoApproveAllowanceHandler cryptoApproveAllowanceHandler(); - + /** + * Returns the {@link CryptoCreateHandler} + * @return the handler + */ CryptoCreateHandler cryptoCreateHandler(); - + /** + * Returns the {@link CryptoDeleteAllowanceHandler} + * @return the handler + */ CryptoDeleteAllowanceHandler cryptoDeleteAllowanceHandler(); - + /** + * Returns the {@link CryptoDeleteHandler} + * @return the handler + */ CryptoDeleteHandler cryptoDeleteHandler(); - + /** + * Returns the {@link CryptoDeleteLiveHashHandler} + * @return the handler + */ CryptoDeleteLiveHashHandler cryptoDeleteLiveHashHandler(); - + /** + * Returns the {@link CryptoGetAccountBalanceHandler} + * @return the handler + */ CryptoGetAccountBalanceHandler cryptoGetAccountBalanceHandler(); - + /** + * Returns the {@link CryptoGetAccountInfoHandler} + * @return the handler + */ CryptoGetAccountInfoHandler cryptoGetAccountInfoHandler(); - + /** + * Returns the {@link CryptoGetAccountRecordsHandler} + * @return the handler + */ CryptoGetAccountRecordsHandler cryptoGetAccountRecordsHandler(); - + /** + * Returns the {@link CryptoGetLiveHashHandler} + * @return the handler + */ CryptoGetLiveHashHandler cryptoGetLiveHashHandler(); - + /** + * Returns the {@link CryptoGetStakersHandler} + * @return the handler + */ CryptoGetStakersHandler cryptoGetStakersHandler(); - + /** + * Returns the {@link CryptoTransferHandler} + * @return the handler + */ CryptoTransferHandler cryptoTransferHandler(); - + /** + * Returns the {@link CryptoUpdateHandler} + * @return the handler + */ CryptoUpdateHandler cryptoUpdateHandler(); - + /** + * Returns the {@link TokenAccountWipeHandler} + * @return the handler + */ TokenAccountWipeHandler tokenAccountWipeHandler(); - + /** + * Returns the {@link TokenAssociateToAccountHandler} + * @return the handler + */ TokenAssociateToAccountHandler tokenAssociateToAccountHandler(); - + /** + * Returns the {@link TokenBurnHandler} + * @return the handler + */ TokenBurnHandler tokenBurnHandler(); - + /** + * Returns the {@link TokenCreateHandler} + * @return the handler + */ TokenCreateHandler tokenCreateHandler(); - + /** + * Returns the {@link TokenDeleteHandler} + * @return the handler + */ TokenDeleteHandler tokenDeleteHandler(); - + /** + * Returns the {@link TokenDissociateFromAccountHandler} + * @return the handler + */ TokenDissociateFromAccountHandler tokenDissociateFromAccountHandler(); - + /** + * Returns the {@link TokenFeeScheduleUpdateHandler} + * @return the handler + */ TokenFeeScheduleUpdateHandler tokenFeeScheduleUpdateHandler(); - + /** + * Returns the {@link TokenFreezeAccountHandler} + * @return the handler + */ TokenFreezeAccountHandler tokenFreezeAccountHandler(); - + /** + * Returns the {@link TokenGetAccountNftInfosHandler} + * @return the handler + */ TokenGetAccountNftInfosHandler tokenGetAccountNftInfosHandler(); - + /** + * Returns the {@link TokenGetInfoHandler} + * @return the handler + */ TokenGetInfoHandler tokenGetInfoHandler(); - + /** + * Returns the {@link TokenGetNftInfoHandler} + * @return the handler + */ TokenGetNftInfoHandler tokenGetNftInfoHandler(); - + /** + * Returns the {@link TokenGetNftInfosHandler} + * @return the handler + */ TokenGetNftInfosHandler tokenGetNftInfosHandler(); - + /** + * Returns the {@link TokenGrantKycToAccountHandler} + * @return the handler + */ TokenGrantKycToAccountHandler tokenGrantKycToAccountHandler(); - + /** + * Returns the {@link TokenMintHandler} + * @return the handler + */ TokenMintHandler tokenMintHandler(); - + /** + * Returns the {@link TokenPauseHandler} + * @return the handler + */ TokenPauseHandler tokenPauseHandler(); - + /** + * Returns the {@link TokenRevokeKycFromAccountHandler} + * @return the handler + */ TokenRevokeKycFromAccountHandler tokenRevokeKycFromAccountHandler(); - + /** + * Returns the {@link TokenUnfreezeAccountHandler} + * @return the handler + */ TokenUnfreezeAccountHandler tokenUnfreezeAccountHandler(); - + /** + * Returns the {@link TokenUnpauseHandler} + * @return the handler + */ TokenUnpauseHandler tokenUnpauseHandler(); - + /** + * Returns the {@link TokenUpdateHandler} + * @return the handler + */ TokenUpdateHandler tokenUpdateHandler(); - + /** + * Returns the {@link TokenHandlers} + * @return the handler + */ TokenHandlers tokenComponent(); } diff --git a/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/WritableAccountStore.java b/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/WritableAccountStore.java index 70710ec77c3a..0af97aedb43e 100644 --- a/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/WritableAccountStore.java +++ b/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/WritableAccountStore.java @@ -28,11 +28,11 @@ import com.hedera.node.app.service.token.api.ContractChangeSummary; import com.hedera.node.app.spi.metrics.StoreMetricsService; import com.hedera.node.app.spi.metrics.StoreMetricsService.StoreType; -import com.hedera.node.app.spi.state.WritableKVState; -import com.hedera.node.app.spi.state.WritableStates; import com.hedera.node.config.data.AccountsConfig; import com.hedera.pbj.runtime.io.buffer.Bytes; import com.swirlds.config.api.Configuration; +import com.swirlds.state.spi.WritableKVState; +import com.swirlds.state.spi.WritableStates; import edu.umd.cs.findbugs.annotations.NonNull; import edu.umd.cs.findbugs.annotations.Nullable; import java.util.ArrayList; @@ -135,6 +135,7 @@ public void removeAlias(@NonNull final Bytes alias) { * null} * * @param accountID - the id of the Account to be retrieved. + * @return the Account with the given AccountID, or null if no such account exists. */ @Nullable public Account get(@NonNull final AccountID accountID) { @@ -142,10 +143,11 @@ public Account get(@NonNull final AccountID accountID) { } /** - * Returns the {@link Account} with the given {@link AccountID}. - * If no such account exists, returns {@code Optional.empty()} + * Returns the {@link Account} with the given {@link AccountID}.It uses the getForModify method + * to get the account. If no such account exists, returns {@code null} * * @param id - the number of the account to be retrieved. + * @return the account with the given account number, or null if no such account exists. */ @Nullable public Account getForModify(@NonNull final AccountID id) { @@ -249,6 +251,10 @@ public Set modifiedAliasesInState() { return aliases().modifiedKeys(); } + /** + * Checks if the given accountId is not the default accountId. If it is, throws an {@link IllegalArgumentException}. + * @param accountId The accountId to check. + */ public static void requireNotDefault(@NonNull final AccountID accountId) { if (accountId.equals(AccountID.DEFAULT)) { throw new IllegalArgumentException("Account ID cannot be default"); diff --git a/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/WritableNetworkStakingRewardsStore.java b/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/WritableNetworkStakingRewardsStore.java index 5226624f7058..5acaa79a05aa 100644 --- a/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/WritableNetworkStakingRewardsStore.java +++ b/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/WritableNetworkStakingRewardsStore.java @@ -20,8 +20,8 @@ import static java.util.Objects.requireNonNull; import com.hedera.hapi.node.state.token.NetworkStakingRewards; -import com.hedera.node.app.spi.state.WritableSingletonState; -import com.hedera.node.app.spi.state.WritableStates; +import com.swirlds.state.spi.WritableSingletonState; +import com.swirlds.state.spi.WritableStates; import edu.umd.cs.findbugs.annotations.NonNull; /** diff --git a/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/WritableNftStore.java b/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/WritableNftStore.java index f87afe3481b9..4cb6db4b7a6e 100644 --- a/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/WritableNftStore.java +++ b/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/WritableNftStore.java @@ -24,10 +24,10 @@ import com.hedera.hapi.node.state.token.Token; import com.hedera.node.app.spi.metrics.StoreMetricsService; import com.hedera.node.app.spi.metrics.StoreMetricsService.StoreType; -import com.hedera.node.app.spi.state.WritableKVState; -import com.hedera.node.app.spi.state.WritableStates; import com.hedera.node.config.data.TokensConfig; import com.swirlds.config.api.Configuration; +import com.swirlds.state.spi.WritableKVState; +import com.swirlds.state.spi.WritableStates; import edu.umd.cs.findbugs.annotations.NonNull; import edu.umd.cs.findbugs.annotations.Nullable; import java.util.Objects; @@ -48,6 +48,7 @@ public class WritableNftStore extends ReadableNftStoreImpl { * Create a new {@link WritableNftStore} instance. * * @param states The state to use. + * @param configuration The configuration used to read the maximum allowed mints. * @param storeMetricsService Service that provides utilization metrics. */ public WritableNftStore( @@ -78,6 +79,7 @@ public void put(@NonNull final Nft nft) { * Returns the {@link Token} with the given number using {@link WritableKVState#getForModify}. * If no such token exists, returns {@code Optional.empty()} * @param id - the number of the unique token id to be retrieved. + * @return the Nft with the given NftId, or null if no such token exists. */ @Nullable public Nft getForModify(final NftID id) { @@ -88,6 +90,8 @@ public Nft getForModify(final NftID id) { * Returns the {@link Nft} with the given number using {@link WritableKVState#getForModify}. * If no such token exists, returns {@code Optional.empty()} * @param tokenId - the number of the unique token id to be retrieved. + * @param serialNumber - the serial number of the NFT to be retrieved. + * @return the Nft with the given tokenId and serial, or null if no such token exists. */ @Nullable public Nft getForModify(final TokenID tokenId, final long serialNumber) { diff --git a/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/WritableStakingInfoStore.java b/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/WritableStakingInfoStore.java index 369870cb9530..c23e76b24d82 100644 --- a/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/WritableStakingInfoStore.java +++ b/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/WritableStakingInfoStore.java @@ -20,8 +20,8 @@ import com.hedera.hapi.node.state.common.EntityNumber; import com.hedera.hapi.node.state.token.StakingNodeInfo; -import com.hedera.node.app.spi.state.WritableKVState; -import com.hedera.node.app.spi.state.WritableStates; +import com.swirlds.state.spi.WritableKVState; +import com.swirlds.state.spi.WritableStates; import edu.umd.cs.findbugs.annotations.NonNull; import edu.umd.cs.findbugs.annotations.Nullable; @@ -52,6 +52,7 @@ public WritableStakingInfoStore(@NonNull final WritableStates states) { * Returns the {@link StakingNodeInfo} for the given node's numeric ID (NOT the account ID) * * @param nodeId - the node ID of the node to retrieve the staking info for + * @return the staking info for the given node ID, or null if no such node exists */ @Nullable public StakingNodeInfo getForModify(final long nodeId) { diff --git a/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/WritableTokenRelationStore.java b/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/WritableTokenRelationStore.java index 408668835202..a225f0b84d2e 100644 --- a/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/WritableTokenRelationStore.java +++ b/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/WritableTokenRelationStore.java @@ -26,10 +26,10 @@ import com.hedera.hapi.node.state.token.TokenRelation; import com.hedera.node.app.spi.metrics.StoreMetricsService; import com.hedera.node.app.spi.metrics.StoreMetricsService.StoreType; -import com.hedera.node.app.spi.state.WritableKVState; -import com.hedera.node.app.spi.state.WritableStates; import com.hedera.node.config.data.TokensConfig; import com.swirlds.config.api.Configuration; +import com.swirlds.state.spi.WritableKVState; +import com.swirlds.state.spi.WritableStates; import edu.umd.cs.findbugs.annotations.NonNull; import edu.umd.cs.findbugs.annotations.Nullable; import java.util.Objects; @@ -99,6 +99,8 @@ public void remove(@NonNull final TokenRelation tokenRelation) { * * @param accountId - the number of the account to be retrieved * @param tokenId - the number of the token to be retrieved + * @return the token relation with the given token number and account number, or {@code Optional.empty()} if no such + * token relation exists */ @Nullable public TokenRelation getForModify(@NonNull final AccountID accountId, @NonNull final TokenID tokenId) { diff --git a/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/WritableTokenStore.java b/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/WritableTokenStore.java index 5642eaef40b5..8ab7f9299142 100644 --- a/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/WritableTokenStore.java +++ b/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/WritableTokenStore.java @@ -23,10 +23,10 @@ import com.hedera.node.app.service.mono.state.merkle.MerkleToken; import com.hedera.node.app.spi.metrics.StoreMetricsService; import com.hedera.node.app.spi.metrics.StoreMetricsService.StoreType; -import com.hedera.node.app.spi.state.WritableKVState; -import com.hedera.node.app.spi.state.WritableStates; import com.hedera.node.config.data.TokensConfig; import com.swirlds.config.api.Configuration; +import com.swirlds.state.spi.WritableKVState; +import com.swirlds.state.spi.WritableStates; import edu.umd.cs.findbugs.annotations.NonNull; import edu.umd.cs.findbugs.annotations.Nullable; import java.util.Objects; @@ -79,6 +79,7 @@ public void put(@NonNull final Token token) { * Returns the {@link Token} with the given number using {@link WritableKVState#getForModify}. * If no such token exists, returns {@code Optional.empty()} * @param tokenId - the id of the token to be retrieved. + * @return the token with the given tokenId, or {@code Optional.empty()} if no such token exists. */ @NonNull public Optional getForModify(final TokenID tokenId) { @@ -117,6 +118,10 @@ public Token getOriginalValue(@NonNull final TokenID tokenId) { return tokenState.getOriginalValue(tokenId); } + /** + * Checks if the given tokenId is not the default tokenId. If it is, throws an {@link IllegalArgumentException}. + * @param tokenId The tokenId to check. + */ public static void requireNotDefault(@NonNull final TokenID tokenId) { if (tokenId.equals(TokenID.DEFAULT)) { throw new IllegalArgumentException("Token ID cannot be default"); diff --git a/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/api/TokenServiceApiImpl.java b/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/api/TokenServiceApiImpl.java index 4c81bf2c0250..395852874787 100644 --- a/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/api/TokenServiceApiImpl.java +++ b/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/api/TokenServiceApiImpl.java @@ -43,7 +43,6 @@ import com.hedera.node.app.spi.fees.Fees; import com.hedera.node.app.spi.info.NetworkInfo; import com.hedera.node.app.spi.metrics.StoreMetricsService; -import com.hedera.node.app.spi.state.WritableStates; import com.hedera.node.app.spi.validation.EntityType; import com.hedera.node.app.spi.validation.ExpiryValidator; import com.hedera.node.app.spi.workflows.record.DeleteCapableTransactionRecordBuilder; @@ -53,6 +52,7 @@ import com.hedera.node.config.data.StakingConfig; import com.hedera.pbj.runtime.io.buffer.Bytes; import com.swirlds.config.api.Configuration; +import com.swirlds.state.spi.WritableStates; import edu.umd.cs.findbugs.annotations.NonNull; import edu.umd.cs.findbugs.annotations.Nullable; import java.util.function.Predicate; @@ -75,6 +75,14 @@ public class TokenServiceApiImpl implements TokenServiceApi { private final StakingConfig stakingConfig; private final Predicate customFeeTest; + /** + * Constructs a {@link TokenServiceApiImpl} + * @param config the configuration + * @param storeMetricsService the store metrics service + * @param stakingValidator the staking validator + * @param writableStates the writable states + * @param customFeeTest a predicate for determining if a transfer has custom fees + */ public TokenServiceApiImpl( @NonNull final Configuration config, @NonNull final StoreMetricsService storeMetricsService, diff --git a/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/api/TokenServiceApiProvider.java b/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/api/TokenServiceApiProvider.java index 2117c1e1ed4a..4a82880cdff1 100644 --- a/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/api/TokenServiceApiProvider.java +++ b/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/api/TokenServiceApiProvider.java @@ -26,14 +26,15 @@ import com.hedera.node.app.service.token.impl.validators.StakingValidator; import com.hedera.node.app.spi.api.ServiceApiProvider; import com.hedera.node.app.spi.metrics.StoreMetricsService; -import com.hedera.node.app.spi.state.WritableStates; import com.swirlds.config.api.Configuration; +import com.swirlds.state.spi.WritableStates; import edu.umd.cs.findbugs.annotations.NonNull; /** * Provides {@link TokenServiceApi} instances. */ public enum TokenServiceApiProvider implements ServiceApiProvider { + /** The singleton instance. */ TOKEN_SERVICE_API_PROVIDER; private final StakingValidator stakingValidator = new StakingValidator(); diff --git a/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/codec/NetworkingStakingTranslator.java b/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/codec/NetworkingStakingTranslator.java index f74d4ebcf20d..d554583f5311 100644 --- a/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/codec/NetworkingStakingTranslator.java +++ b/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/codec/NetworkingStakingTranslator.java @@ -19,22 +19,26 @@ import static java.util.Objects.requireNonNull; import com.hedera.hapi.node.state.token.NetworkStakingRewards; +import com.hedera.node.app.service.mono.state.merkle.MerkleNetworkContext; import edu.umd.cs.findbugs.annotations.NonNull; +/** + * Networking Staking Translator from merkle network context to network staking rewards. + */ public final class NetworkingStakingTranslator { private NetworkingStakingTranslator() { throw new UnsupportedOperationException("Utility class"); } - @NonNull /** - * Converts a Part of {@link com.hedera.node.app.service.mono.state.merkle.MerkleNetworkContext} to {@link NetworkStakingRewards}. - * @param merkleNetworkContext the {@link com.hedera.node.app.service.mono.state.merkle.MerkleNetworkContext} - * @return the {@link NetworkStakingRewards} converted from the {@link com.hedera.node.app.service.mono.state.merkle.MerkleNetworkContext} + * Converts a Part of {@link MerkleNetworkContext} to {@link NetworkStakingRewards}. + * @param merkleNetworkContext the {@link MerkleNetworkContext} from which to convert + * @return the {@link NetworkStakingRewards} converted from the {@link MerkleNetworkContext} */ + @NonNull public static NetworkStakingRewards networkStakingRewardsFromMerkleNetworkContext( - @NonNull final com.hedera.node.app.service.mono.state.merkle.MerkleNetworkContext merkleNetworkContext) { + @NonNull final MerkleNetworkContext merkleNetworkContext) { requireNonNull(merkleNetworkContext); return NetworkStakingRewards.newBuilder() .stakingRewardsActivated(merkleNetworkContext.areRewardsActivated()) diff --git a/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/comparator/TokenComparators.java b/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/comparator/TokenComparators.java index 7ed0e966a448..21c9b363f7a3 100644 --- a/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/comparator/TokenComparators.java +++ b/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/comparator/TokenComparators.java @@ -26,18 +26,36 @@ import java.util.Comparator; import java.util.Objects; +/** + * Utility class for token-related comparators. + */ public final class TokenComparators { private TokenComparators() { throw new IllegalStateException("Utility Class"); } + /** + * Comparator for {@link Account} objects. + */ public static final Comparator ACCOUNT_COMPARATOR = Comparator.comparing(Account::accountId, ACCOUNT_ID_COMPARATOR); + /** + * Comparator for {@link AccountAmount} objects. + */ public static final Comparator ACCOUNT_AMOUNT_COMPARATOR = Comparator.comparing(AccountAmount::accountID, ACCOUNT_ID_COMPARATOR); + /** + * Comparator for {@link TokenID} objects. + */ public static final Comparator TOKEN_ID_COMPARATOR = Comparator.comparingLong(TokenID::tokenNum); + /** + * Comparator for {@link TokenTransferList} objects. + */ public static final Comparator TOKEN_TRANSFER_LIST_COMPARATOR = (o1, o2) -> Objects.compare(o1.token(), o2.token(), TOKEN_ID_COMPARATOR); + /** + * Comparator for {@link NftTransfer} objects. + */ public static final Comparator NFT_TRANSFER_COMPARATOR = Comparator.comparing( NftTransfer::senderAccountID, ACCOUNT_ID_COMPARATOR) .thenComparing(NftTransfer::receiverAccountID, ACCOUNT_ID_COMPARATOR) diff --git a/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/handlers/BaseCryptoHandler.java b/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/handlers/BaseCryptoHandler.java index f2973a38d42b..c9b298f0caf2 100644 --- a/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/handlers/BaseCryptoHandler.java +++ b/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/handlers/BaseCryptoHandler.java @@ -27,33 +27,10 @@ */ public class BaseCryptoHandler { /** - * Gets the stakedId from the provided staked_account_id or staked_node_id. - * When staked_node_id is provided, it is stored as negative number in state to - * distinguish it from staked_account_id. It will be converted back to positive number - * when it is retrieved from state. - * To distinguish for node 0, it will be stored as - node_id -1. - * For example, if staked_node_id is 0, it will be stored as -1 in state. - * - * @param stakedIdType staked id type, if staked node id or staked account id - * @param stakedNodeId staked node id - * @param stakedAccountId staked account id - * @return valid staked id + * Gets the accountId from the account number provided. + * @param num the account number + * @return the accountID */ - protected long getStakedId( - @NonNull final String stakedIdType, - @Nullable final Long stakedNodeId, - @Nullable final AccountID stakedAccountId) { - if ("STAKED_ACCOUNT_ID".equals(stakedIdType) && stakedAccountId != null) { - return stakedAccountId.accountNum(); - } else if ("STAKED_NODE_ID".equals(stakedIdType) && stakedNodeId != null) { - // return a number less than the given node Id, in order to recognize the if nodeId 0 is - // set - return -stakedNodeId - 1; - } else { - throw new IllegalStateException("StakedIdOneOfType is not set"); - } - } - @NonNull public static AccountID asAccount(final long num) { return AccountID.newBuilder().accountNum(num).build(); @@ -61,16 +38,15 @@ public static AccountID asAccount(final long num) { /** * Checks that an accountId represents one of the staking accounts + * @param configuration the configuration * @param accountID the accountID to check + * @return {@code true} if the accountID represents one of the staking accounts, {@code false} otherwise */ public static boolean isStakingAccount( @NonNull final Configuration configuration, @Nullable final AccountID accountID) { final var accountNum = accountID != null ? accountID.accountNum() : 0; final var accountsConfig = configuration.getConfigData(AccountsConfig.class); - if (accountNum == accountsConfig.stakingRewardAccount() || accountNum == accountsConfig.nodeRewardAccount()) { - return true; - } - return false; + return accountNum == accountsConfig.stakingRewardAccount() || accountNum == accountsConfig.nodeRewardAccount(); } /** diff --git a/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/handlers/BaseTokenHandler.java b/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/handlers/BaseTokenHandler.java index a1d8b506d296..aae6b903eb51 100644 --- a/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/handlers/BaseTokenHandler.java +++ b/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/handlers/BaseTokenHandler.java @@ -42,19 +42,34 @@ import com.hedera.node.app.service.token.impl.WritableAccountStore; import com.hedera.node.app.service.token.impl.WritableTokenRelationStore; import com.hedera.node.app.service.token.impl.WritableTokenStore; +import com.hedera.node.app.service.token.impl.util.TokenKey; import com.hedera.node.config.data.EntitiesConfig; import com.hedera.node.config.data.TokensConfig; import com.swirlds.config.api.Configuration; import edu.umd.cs.findbugs.annotations.NonNull; import java.util.ArrayList; +import java.util.EnumSet; import java.util.List; import java.util.Objects; import java.util.Optional; +import java.util.Set; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; +/** + * Provides common functionality for token handlers. + */ public class BaseTokenHandler { private static final Logger log = LogManager.getLogger(BaseTokenHandler.class); + /** + * The set of token keys that are not admin keys. + */ + protected static final Set NON_ADMIN_TOKEN_KEYS = EnumSet.complementOf(EnumSet.of(TokenKey.ADMIN_KEY)); + /** + * The set of all token keys. + */ + protected static final Set TOKEN_KEYS = EnumSet.allOf(TokenKey.class); + /** * Mints fungible tokens. This method is called in both token create and mint. * @param token the new or existing token to mint @@ -403,44 +418,22 @@ protected void validateNotFrozenAndKycOnRelation(@NonNull final TokenRelation re } /* ------------------------- Helper functions ------------------------- */ - /** - * Returns true if the given token update op is an expiry-only update op. - * This is needed for validating whether a token update op has admin key present on the token, - * to update any other fields other than expiry. + * Checks if the given op updates any of the non-key token properties that can only be + * changed given the admin key signature. + * * @param op the token update op to check - * @return true if the given token update op is an expiry-only update op + * @return true if it requires admin key signature */ - public static boolean isExpiryOnlyUpdateOp(@NonNull final TokenUpdateTransactionBody op) { - if (!op.hasExpiry()) { - return false; - } - final var defaultWithExpiry = TokenUpdateTransactionBody.newBuilder() - .expiry(op.expiry()) - .token(op.token()) - .build(); - return op.equals(defaultWithExpiry); + protected static boolean updatesAdminOnlyNonKeyTokenProperty(@NonNull final TokenUpdateTransactionBody op) { + return !op.symbol().isEmpty() || !op.name().isEmpty() || op.hasAutoRenewPeriod() || op.hasMemo(); } /** - * Returns true if the given token update op is a metadata-only update op. - * This is needed for validating whether a token update op has admin key present on the token, - * to update any other fields other than metadata. - * For updating metadata we need signature from either admin key or metadata key - * @param op the token update op to check - * @return true if the given token update op is an metadata-only update op + * Returns a new {@link TokenID} with the given number. + * @param num the token number + * @return the new token ID */ - public static boolean isMetadataOnlyUpdateOp(@NonNull final TokenUpdateTransactionBody op) { - if (!op.hasMetadata()) { - return false; - } - final var defaultWithMetadata = TokenUpdateTransactionBody.newBuilder() - .metadata(op.metadata()) - .token(op.token()) - .build(); - return op.equals(defaultWithMetadata); - } - @NonNull public static TokenID asToken(final long num) { return TokenID.newBuilder().tokenNum(num).build(); diff --git a/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/handlers/CryptoAddLiveHashHandler.java b/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/handlers/CryptoAddLiveHashHandler.java index 4762a7c4e244..33cbf367e474 100644 --- a/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/handlers/CryptoAddLiveHashHandler.java +++ b/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/handlers/CryptoAddLiveHashHandler.java @@ -40,6 +40,9 @@ */ @Singleton public class CryptoAddLiveHashHandler implements TransactionHandler { + /** + * Default constructor for injection. + */ @Inject public CryptoAddLiveHashHandler() { // Exists for injection diff --git a/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/handlers/CryptoApproveAllowanceHandler.java b/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/handlers/CryptoApproveAllowanceHandler.java index 1834b82da1b5..144187c7e0a9 100644 --- a/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/handlers/CryptoApproveAllowanceHandler.java +++ b/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/handlers/CryptoApproveAllowanceHandler.java @@ -83,6 +83,10 @@ public class CryptoApproveAllowanceHandler implements TransactionHandler { private final ApproveAllowanceValidator allowanceValidator; + /** + * Constructs a {@link CryptoApproveAllowanceHandler} with the given {@link ApproveAllowanceValidator}. + * @param allowanceValidator the validator to use for validating the transaction + */ @Inject public CryptoApproveAllowanceHandler(@NonNull final ApproveAllowanceValidator allowanceValidator) { this.allowanceValidator = allowanceValidator; diff --git a/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/handlers/CryptoCreateHandler.java b/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/handlers/CryptoCreateHandler.java index 06fd1c07a151..7dbc682bcaee 100644 --- a/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/handlers/CryptoCreateHandler.java +++ b/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/handlers/CryptoCreateHandler.java @@ -20,12 +20,14 @@ import static com.hedera.hapi.node.base.ResponseCodeEnum.ALIAS_ALREADY_ASSIGNED; import static com.hedera.hapi.node.base.ResponseCodeEnum.BAD_ENCODING; import static com.hedera.hapi.node.base.ResponseCodeEnum.INSUFFICIENT_PAYER_BALANCE; +import static com.hedera.hapi.node.base.ResponseCodeEnum.INVALID_ACCOUNT_ID; import static com.hedera.hapi.node.base.ResponseCodeEnum.INVALID_ALIAS_KEY; import static com.hedera.hapi.node.base.ResponseCodeEnum.INVALID_INITIAL_BALANCE; import static com.hedera.hapi.node.base.ResponseCodeEnum.INVALID_PAYER_ACCOUNT_ID; import static com.hedera.hapi.node.base.ResponseCodeEnum.INVALID_RECEIVE_RECORD_THRESHOLD; import static com.hedera.hapi.node.base.ResponseCodeEnum.INVALID_RENEWAL_PERIOD; import static com.hedera.hapi.node.base.ResponseCodeEnum.INVALID_SEND_RECORD_THRESHOLD; +import static com.hedera.hapi.node.base.ResponseCodeEnum.INVALID_TRANSACTION_BODY; import static com.hedera.hapi.node.base.ResponseCodeEnum.KEY_NOT_PROVIDED; import static com.hedera.hapi.node.base.ResponseCodeEnum.KEY_REQUIRED; import static com.hedera.hapi.node.base.ResponseCodeEnum.MAX_ENTITIES_IN_PRICE_REGIME_HAVE_BEEN_CREATED; @@ -98,6 +100,11 @@ public class CryptoCreateHandler extends BaseCryptoHandler implements Transactio private final CryptoCreateValidator cryptoCreateValidator; private final StakingValidator stakingValidator; + /** + * Constructs a {@link CryptoCreateHandler} with the given {@link CryptoCreateValidator} and {@link StakingValidator}. + * @param cryptoCreateValidator the validator for the crypto create transaction + * @param stakingValidator the validator for the staking information in the crypto create transaction + */ @Inject public CryptoCreateHandler( @NonNull final CryptoCreateValidator cryptoCreateValidator, @@ -111,8 +118,18 @@ public CryptoCreateHandler( @Override public void pureChecks(@NonNull final TransactionBody txn) throws PreCheckException { final var op = txn.cryptoCreateAccountOrThrow(); - validateTruePreCheck(op.initialBalance() >= 0L, INVALID_INITIAL_BALANCE); + // Note: validation lives here for now but should take place in handle in the future validateTruePreCheck(op.hasAutoRenewPeriod(), INVALID_RENEWAL_PERIOD); + validateTruePreCheck(op.autoRenewPeriodOrThrow().seconds() >= 0, INVALID_RENEWAL_PERIOD); + if (op.hasShardID()) { + validateTruePreCheck(op.shardIDOrThrow().shardNum() == 0, INVALID_ACCOUNT_ID); + } + if (op.hasRealmID()) { + validateTruePreCheck(op.realmIDOrThrow().realmNum() == 0, INVALID_ACCOUNT_ID); + } + // HIP 904 now allows for unlimited auto-associations + validateTruePreCheck(op.maxAutomaticTokenAssociations() >= -1, INVALID_TRANSACTION_BODY); + validateTruePreCheck(op.initialBalance() >= 0L, INVALID_INITIAL_BALANCE); // FUTURE: should this return SEND_RECORD_THRESHOLD_FIELD_IS_DEPRECATED validateTruePreCheck(op.sendRecordThreshold() >= 0L, INVALID_SEND_RECORD_THRESHOLD); // FUTURE: should this return RECEIVE_RECORD_THRESHOLD_FIELD_IS_DEPRECATED @@ -120,6 +137,8 @@ public void pureChecks(@NonNull final TransactionBody txn) throws PreCheckExcept validateTruePreCheck( op.proxyAccountIDOrElse(AccountID.DEFAULT).equals(AccountID.DEFAULT), PROXY_ACCOUNT_ID_FIELD_IS_DEPRECATED); + // sendRecordThreshold, receiveRecordThreshold and proxyAccountID are deprecated. So no need to check them. + validateFalsePreCheck(op.hasProxyAccountID(), PROXY_ACCOUNT_ID_FIELD_IS_DEPRECATED); // You can never set the alias to be an "entity num alias" (sometimes called "long-zero"). final var alias = op.alias(); validateFalsePreCheck(isEntityNumAlias(alias), INVALID_ALIAS_KEY); diff --git a/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/handlers/CryptoDeleteAllowanceHandler.java b/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/handlers/CryptoDeleteAllowanceHandler.java index 8b9638002ecc..74ed4b3129a2 100644 --- a/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/handlers/CryptoDeleteAllowanceHandler.java +++ b/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/handlers/CryptoDeleteAllowanceHandler.java @@ -65,6 +65,10 @@ public class CryptoDeleteAllowanceHandler implements TransactionHandler { private final DeleteAllowanceValidator deleteAllowanceValidator; + /** + * Default constructor for injection. + * @param validator the validator for validating a delete allowance transaction + */ @Inject public CryptoDeleteAllowanceHandler(@NonNull final DeleteAllowanceValidator validator) { this.deleteAllowanceValidator = validator; diff --git a/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/handlers/CryptoDeleteHandler.java b/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/handlers/CryptoDeleteHandler.java index 987e50e64cc0..3332bc008170 100644 --- a/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/handlers/CryptoDeleteHandler.java +++ b/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/handlers/CryptoDeleteHandler.java @@ -49,6 +49,9 @@ */ @Singleton public class CryptoDeleteHandler implements TransactionHandler { + /** + * Default constructor for injection. + */ @Inject public CryptoDeleteHandler() { // Exists for injection diff --git a/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/handlers/CryptoDeleteLiveHashHandler.java b/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/handlers/CryptoDeleteLiveHashHandler.java index ceb27246067f..09a2ce8caf6c 100644 --- a/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/handlers/CryptoDeleteLiveHashHandler.java +++ b/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/handlers/CryptoDeleteLiveHashHandler.java @@ -38,6 +38,9 @@ */ @Singleton public class CryptoDeleteLiveHashHandler implements TransactionHandler { + /** + * Default constructor for injection. + */ @Inject public CryptoDeleteLiveHashHandler() { // Exists for injection diff --git a/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/handlers/CryptoGetAccountBalanceHandler.java b/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/handlers/CryptoGetAccountBalanceHandler.java index 7408fff5bf2a..827ac2cc7851 100644 --- a/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/handlers/CryptoGetAccountBalanceHandler.java +++ b/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/handlers/CryptoGetAccountBalanceHandler.java @@ -21,10 +21,14 @@ import static com.hedera.hapi.node.base.ResponseCodeEnum.INVALID_ACCOUNT_ID; import static com.hedera.hapi.node.base.ResponseCodeEnum.INVALID_CONTRACT_ID; import static com.hedera.hapi.node.base.ResponseCodeEnum.OK; +import static com.hedera.node.app.spi.validation.Validations.mustExist; +import static com.hedera.node.app.spi.validation.Validations.validateAccountID; import static com.hedera.node.app.spi.workflows.PreCheckException.validateFalsePreCheck; +import static com.hedera.node.app.spi.workflows.PreCheckException.validateTruePreCheck; import static java.util.Objects.requireNonNull; import com.hedera.hapi.node.base.AccountID; +import com.hedera.hapi.node.base.ContractID; import com.hedera.hapi.node.base.HederaFunctionality; import com.hedera.hapi.node.base.QueryHeader; import com.hedera.hapi.node.base.ResponseHeader; @@ -56,6 +60,9 @@ */ @Singleton public class CryptoGetAccountBalanceHandler extends FreeQueryHandler { + /** + * Default constructor for injection. + */ @Inject public CryptoGetAccountBalanceHandler() { // Exists for injection @@ -83,18 +90,40 @@ public void validate(@NonNull final QueryContext context) throws PreCheckExcepti final var accountStore = context.createStore(ReadableAccountStore.class); final CryptoGetAccountBalanceQuery op = query.cryptogetAccountBalanceOrThrow(); if (op.hasAccountID()) { - final var account = accountStore.getAliasedAccountById(requireNonNull(op.accountID())); - validateFalsePreCheck(account == null, INVALID_ACCOUNT_ID); - validateFalsePreCheck(account.deleted(), ACCOUNT_DELETED); + validateAccountId(op, accountStore); } else if (op.hasContractID()) { - final var contract = accountStore.getContractById(requireNonNull(op.contractID())); - validateFalsePreCheck(contract == null || !contract.smartContract(), INVALID_CONTRACT_ID); - validateFalsePreCheck(contract.deleted(), CONTRACT_DELETED); + validateContractId(op, accountStore); } else { throw new PreCheckException(INVALID_ACCOUNT_ID); } } + private void validateContractId(CryptoGetAccountBalanceQuery op, ReadableAccountStore accountStore) + throws PreCheckException { + mustExist(op.contractID(), INVALID_CONTRACT_ID); + final ContractID contractId = (ContractID) op.balanceSource().value(); + validateTruePreCheck(contractId.shardNum() == 0, INVALID_CONTRACT_ID); + validateTruePreCheck(contractId.realmNum() == 0, INVALID_CONTRACT_ID); + validateTruePreCheck( + (contractId.hasContractNum() && contractId.contractNumOrThrow() >= 0) || contractId.hasEvmAddress(), + INVALID_CONTRACT_ID); + final var contract = accountStore.getContractById(requireNonNull(op.contractID())); + validateFalsePreCheck(contract == null, INVALID_CONTRACT_ID); + validateTruePreCheck(contract.smartContract(), INVALID_CONTRACT_ID); + validateFalsePreCheck(contract.deleted(), CONTRACT_DELETED); + } + + private void validateAccountId(CryptoGetAccountBalanceQuery op, ReadableAccountStore accountStore) + throws PreCheckException { + AccountID accountId = (AccountID) op.balanceSource().value(); + validateTruePreCheck(accountId.shardNum() == 0, INVALID_ACCOUNT_ID); + validateTruePreCheck(accountId.realmNum() == 0, INVALID_ACCOUNT_ID); + validateAccountID(accountId, INVALID_ACCOUNT_ID); + final var account = accountStore.getAliasedAccountById(requireNonNull(op.accountID())); + validateFalsePreCheck(account == null, INVALID_ACCOUNT_ID); + validateFalsePreCheck(account.deleted(), ACCOUNT_DELETED); + } + @Override public Response findResponse(@NonNull final QueryContext context, @NonNull final ResponseHeader header) { requireNonNull(context); diff --git a/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/handlers/CryptoGetAccountInfoHandler.java b/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/handlers/CryptoGetAccountInfoHandler.java index b65e5e5811b4..b83f8e525a84 100644 --- a/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/handlers/CryptoGetAccountInfoHandler.java +++ b/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/handlers/CryptoGetAccountInfoHandler.java @@ -65,6 +65,10 @@ public class CryptoGetAccountInfoHandler extends PaidQueryHandler { private final CryptoOpsUsage cryptoOpsUsage; + /** + * Default constructor for injection. + * @param cryptoOpsUsage the usage of the crypto operations for calculating fees + */ @Inject public CryptoGetAccountInfoHandler(final CryptoOpsUsage cryptoOpsUsage) { this.cryptoOpsUsage = cryptoOpsUsage; diff --git a/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/handlers/CryptoGetAccountRecordsHandler.java b/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/handlers/CryptoGetAccountRecordsHandler.java index a47ed0c069b5..1de171492a66 100644 --- a/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/handlers/CryptoGetAccountRecordsHandler.java +++ b/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/handlers/CryptoGetAccountRecordsHandler.java @@ -52,6 +52,10 @@ public class CryptoGetAccountRecordsHandler extends PaidQueryHandler { private final RecordCache recordCache; + /** + * Default constructor for injection. + * @param recordCache the record cache to use to get the records + */ @Inject public CryptoGetAccountRecordsHandler(@NonNull final RecordCache recordCache) { // Exists for injection @@ -117,9 +121,8 @@ public Fees computeFees(@NonNull final QueryContext queryContext) { final var query = queryContext.query(); final var accountStore = queryContext.createStore(ReadableAccountStore.class); final var op = query.cryptoGetAccountRecordsOrThrow(); - final var accountId = op.accountIDOrThrow(); + final var accountId = op.accountIDOrElse(AccountID.DEFAULT); final var account = accountStore.getAccountById(accountId); - final var records = recordCache.getRecords(accountId); return queryContext.feeCalculator().legacyCalculate(sigValueObj -> new GetAccountRecordsResourceUsage( null, new CryptoFeeBuilder()) diff --git a/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/handlers/CryptoGetLiveHashHandler.java b/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/handlers/CryptoGetLiveHashHandler.java index c7eaea8698bd..ad238c791e6a 100644 --- a/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/handlers/CryptoGetLiveHashHandler.java +++ b/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/handlers/CryptoGetLiveHashHandler.java @@ -40,6 +40,9 @@ */ @Singleton public class CryptoGetLiveHashHandler extends FreeQueryHandler { + /** + * Default constructor for injection. + */ @Inject public CryptoGetLiveHashHandler() { // Exists for injection diff --git a/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/handlers/CryptoGetStakersHandler.java b/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/handlers/CryptoGetStakersHandler.java index 6309c7186683..fa846295f360 100644 --- a/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/handlers/CryptoGetStakersHandler.java +++ b/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/handlers/CryptoGetStakersHandler.java @@ -38,6 +38,9 @@ */ @Singleton public class CryptoGetStakersHandler extends FreeQueryHandler { + /** + * Default constructor for injection. + */ @Inject public CryptoGetStakersHandler() { // Exists for injection diff --git a/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/handlers/CryptoTransferHandler.java b/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/handlers/CryptoTransferHandler.java index 7700371b1d79..0a1698cbf2cb 100644 --- a/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/handlers/CryptoTransferHandler.java +++ b/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/handlers/CryptoTransferHandler.java @@ -103,11 +103,20 @@ public class CryptoTransferHandler implements TransactionHandler { private final CryptoTransferValidator validator; private final boolean enforceMonoServiceRestrictionsOnAutoCreationCustomFeePayments; + /** + * Default constructor for injection. + * @param validator the validator to use to validate the transaction + */ @Inject public CryptoTransferHandler(@NonNull final CryptoTransferValidator validator) { this(validator, true); } + /** + * Constructor for injection with the option to enforce mono-service restrictions on auto-creation custom fee + * @param validator the validator to use to validate the transaction + * @param enforceMonoServiceRestrictionsOnAutoCreationCustomFeePayments whether to enforce mono-service restrictions + */ public CryptoTransferHandler( @NonNull final CryptoTransferValidator validator, final boolean enforceMonoServiceRestrictionsOnAutoCreationCustomFeePayments) { diff --git a/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/handlers/CryptoUpdateHandler.java b/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/handlers/CryptoUpdateHandler.java index 6dcbaf1dcbd8..bca6da819584 100644 --- a/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/handlers/CryptoUpdateHandler.java +++ b/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/handlers/CryptoUpdateHandler.java @@ -88,6 +88,12 @@ public class CryptoUpdateHandler extends BaseCryptoHandler implements Transactio private final StakingValidator stakingValidator; private final NetworkInfo networkInfo; + /** + * Default constructor for injection. + * @param waivers the {@link CryptoSignatureWaivers} to use for checking signature waivers + * @param stakingValidator the {@link StakingValidator} to use for staking validation + * @param networkInfo the {@link NetworkInfo} to use for network information + */ @Inject public CryptoUpdateHandler( @NonNull final CryptoSignatureWaivers waivers, diff --git a/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/handlers/FinalizeChildRecordHandler.java b/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/handlers/FinalizeChildRecordHandler.java index dba5a02c975b..091d6deefd22 100644 --- a/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/handlers/FinalizeChildRecordHandler.java +++ b/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/handlers/FinalizeChildRecordHandler.java @@ -22,9 +22,7 @@ import com.hedera.hapi.node.base.HederaFunctionality; import com.hedera.hapi.node.base.ResponseCodeEnum; import com.hedera.hapi.node.base.TokenTransferList; -import com.hedera.hapi.node.base.TokenType; import com.hedera.hapi.node.base.TransferList; -import com.hedera.node.app.service.token.ReadableTokenStore; import com.hedera.node.app.service.token.impl.RecordFinalizerBase; import com.hedera.node.app.service.token.impl.WritableAccountStore; import com.hedera.node.app.service.token.impl.WritableNftStore; @@ -44,6 +42,9 @@ */ @Singleton public class FinalizeChildRecordHandler extends RecordFinalizerBase implements ChildRecordFinalizer { + /** + * Constructs a {@link FinalizeChildRecordHandler} instance. + */ @Inject public FinalizeChildRecordHandler() { // For Dagger Injection @@ -59,7 +60,6 @@ public void finalizeChildRecord(@NonNull final ChildFinalizeContext context, fin final var writableAccountStore = context.writableStore(WritableAccountStore.class); final var writableTokenRelStore = context.writableStore(WritableTokenRelationStore.class); final var writableNftStore = context.writableStore(WritableNftStore.class); - final var readableTokenStore = context.readableStore(ReadableTokenStore.class); final var writableTokenStore = context.writableStore(WritableTokenStore.class); /* ------------------------- Hbar changes from child transaction ------------------------- */ @@ -88,15 +88,21 @@ public void finalizeChildRecord(@NonNull final ChildFinalizeContext context, fin // If the function is not a crypto transfer, then we filter all zero amounts from token transfer list. // To be compatible with mono-service records, we _don't_ filter zero token transfers in the record final var isCryptoTransfer = function == HederaFunctionality.CRYPTO_TRANSFER; - final var fungibleChanges = tokenRelChangesFrom( - writableTokenRelStore, readableTokenStore, TokenType.FUNGIBLE_COMMON, !isCryptoTransfer); + final var fungibleChanges = tokenRelChangesFrom(writableTokenRelStore, !isCryptoTransfer); + // get all the NFT changes. Go through the nft changes and see if there are any token relation changes + // for the sender and receiver of the NFTs. If there are, then reduce the balance change for that relation + // by 1 for receiver and increment the balance change for sender by 1. This is to ensure that the NFT + // transfer is not double counted in the token relation changes and the NFT changes + final var nftChanges = nftChangesFrom(writableNftStore, writableTokenStore, fungibleChanges); + final var fungibleTokenTransferLists = asTokenTransferListFrom(fungibleChanges, !isCryptoTransfer); tokenTransferLists = new ArrayList<>(fungibleTokenTransferLists); // ---------- nft transfers ------------------------- - final var nftChanges = nftChangesFrom(writableNftStore, writableTokenStore); - final var nftTokenTransferLists = asTokenTransferListFromNftChanges(nftChanges); - tokenTransferLists.addAll(nftTokenTransferLists); + if (!nftChanges.isEmpty()) { + final var nftTokenTransferLists = asTokenTransferListFromNftChanges(nftChanges); + tokenTransferLists.addAll(nftTokenTransferLists); + } // Record the modified fungible and non-fungible changes so records can be written if (!tokenTransferLists.isEmpty()) { diff --git a/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/handlers/FinalizeParentRecordHandler.java b/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/handlers/FinalizeParentRecordHandler.java index 739ae10fb74e..c8e17aff49cf 100644 --- a/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/handlers/FinalizeParentRecordHandler.java +++ b/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/handlers/FinalizeParentRecordHandler.java @@ -29,10 +29,8 @@ import com.hedera.hapi.node.base.NftID; import com.hedera.hapi.node.base.NftTransfer; import com.hedera.hapi.node.base.TokenID; -import com.hedera.hapi.node.base.TokenType; import com.hedera.hapi.node.base.TransferList; import com.hedera.hapi.node.state.common.EntityIDPair; -import com.hedera.node.app.service.token.ReadableTokenStore; import com.hedera.node.app.service.token.impl.RecordFinalizerBase; import com.hedera.node.app.service.token.impl.WritableAccountStore; import com.hedera.node.app.service.token.impl.WritableNftStore; @@ -68,6 +66,10 @@ public class FinalizeParentRecordHandler extends RecordFinalizerBase implements private final StakingRewardsHandler stakingRewardsHandler; + /** + * Constructs a {@link FinalizeParentRecordHandler} instance. + * @param stakingRewardsHandler the {@link StakingRewardsHandler} instance + */ @Inject public FinalizeParentRecordHandler(@NonNull final StakingRewardsHandler stakingRewardsHandler) { this.stakingRewardsHandler = stakingRewardsHandler; @@ -90,7 +92,6 @@ public void finalizeParentRecord( final var writableTokenRelStore = context.writableStore(WritableTokenRelationStore.class); final var writableNftStore = context.writableStore(WritableNftStore.class); final var stakingConfig = context.configuration().getConfigData(StakingConfig.class); - final var readableTokenStore = context.readableStore(ReadableTokenStore.class); final var writableTokenStore = context.writableStore(WritableTokenStore.class); if (stakingConfig.isEnabled()) { @@ -123,22 +124,21 @@ public void finalizeParentRecord( // If the function is not a crypto transfer, then we filter all zero amounts from token transfer list. // To be compatible with mono-service records, we _don't_ filter zero token transfers in the record final var isCryptoTransfer = functionality == HederaFunctionality.CRYPTO_TRANSFER; - final var tokenChanges = tokenRelChangesFrom( - writableTokenRelStore, readableTokenStore, TokenType.FUNGIBLE_COMMON, !isCryptoTransfer); - final var nftChanges = nftChangesFrom(writableNftStore, writableTokenStore); - - if (nftChanges.isEmpty()) { - final var nonFungibleTokenChanges = tokenRelChangesFrom( - writableTokenRelStore, readableTokenStore, TokenType.NON_FUNGIBLE_UNIQUE, !isCryptoTransfer); - nonFungibleTokenChanges.forEach(tokenChanges::putIfAbsent); - } + // get all the token relation changes for fungible and non-fungible tokens + final var tokenRelChanges = tokenRelChangesFrom(writableTokenRelStore, !isCryptoTransfer); + // get all the NFT changes. Go through the nft changes and see if there are any token relation changes + // for the sender and receiver of the NFTs. If there are, then reduce the balance change for that relation + // by 1 for receiver and increment the balance change for sender by 1. This is to ensure that the NFT + // transfer is not double counted in the token relation changes and the NFT changes. Also we don't need to + // represent the changes for Mint or Wipe of NFTs in the token relation changes. + final var nftChanges = nftChangesFrom(writableNftStore, writableTokenStore, tokenRelChanges); if (context.hasChildRecords()) { // All the above changes maps are mutable - deductChangesFromChildRecords(context, tokenChanges, nftChanges, hbarChanges); + deductChangesFromChildRecords(context, tokenRelChanges, nftChanges, hbarChanges); // In the current system a parent transaction that has child transactions cannot // *itself* cause any net fungible or NFT transfers; fail fast if that happens - validateTrue(tokenChanges.isEmpty(), FAIL_INVALID); + validateTrue(tokenRelChanges.isEmpty(), FAIL_INVALID); validateTrue(nftChanges.isEmpty(), FAIL_INVALID); } if (!hbarChanges.isEmpty()) { @@ -147,9 +147,9 @@ public void finalizeParentRecord( .accountAmounts(asAccountAmounts(hbarChanges)) .build()); } - final var hasTokenTransferLists = !tokenChanges.isEmpty() || !nftChanges.isEmpty(); + final var hasTokenTransferLists = !tokenRelChanges.isEmpty() || !nftChanges.isEmpty(); if (hasTokenTransferLists) { - final var tokenTransferLists = asTokenTransferListFrom(tokenChanges, !isCryptoTransfer); + final var tokenTransferLists = asTokenTransferListFrom(tokenRelChanges, !isCryptoTransfer); final var nftTokenTransferLists = asTokenTransferListFromNftChanges(nftChanges); tokenTransferLists.addAll(nftTokenTransferLists); tokenTransferLists.sort(TOKEN_TRANSFER_LIST_COMPARATOR); diff --git a/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/handlers/TokenAccountWipeHandler.java b/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/handlers/TokenAccountWipeHandler.java index d3e327b43852..93a06b5a663a 100644 --- a/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/handlers/TokenAccountWipeHandler.java +++ b/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/handlers/TokenAccountWipeHandler.java @@ -80,6 +80,10 @@ public final class TokenAccountWipeHandler implements TransactionHandler { @NonNull private final TokenSupplyChangeOpsValidator validator; + /** + * Default constructor for injection. + * @param validator the {@link TokenSupplyChangeOpsValidator} to use + */ @Inject public TokenAccountWipeHandler(@NonNull final TokenSupplyChangeOpsValidator validator) { this.validator = validator; diff --git a/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/handlers/TokenAssociateToAccountHandler.java b/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/handlers/TokenAssociateToAccountHandler.java index 06169fc37504..c2151b9e0cb7 100644 --- a/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/handlers/TokenAssociateToAccountHandler.java +++ b/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/handlers/TokenAssociateToAccountHandler.java @@ -68,7 +68,9 @@ */ @Singleton public class TokenAssociateToAccountHandler extends BaseTokenHandler implements TransactionHandler { - + /** + * Default constructor for injection. + */ @Inject public TokenAssociateToAccountHandler() { // Exists for injection diff --git a/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/handlers/TokenBurnHandler.java b/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/handlers/TokenBurnHandler.java index 08f076af80b7..33d2d139b0ec 100644 --- a/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/handlers/TokenBurnHandler.java +++ b/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/handlers/TokenBurnHandler.java @@ -72,6 +72,10 @@ public final class TokenBurnHandler extends BaseTokenHandler implements Transact @NonNull private final TokenSupplyChangeOpsValidator validator; + /** + * Default constructor for injection. + * @param validator the {@link TokenSupplyChangeOpsValidator} to use + */ @Inject public TokenBurnHandler(@NonNull final TokenSupplyChangeOpsValidator validator) { this.validator = requireNonNull(validator); diff --git a/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/handlers/TokenCreateHandler.java b/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/handlers/TokenCreateHandler.java index 6db534751dbf..8e5578ee5f79 100644 --- a/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/handlers/TokenCreateHandler.java +++ b/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/handlers/TokenCreateHandler.java @@ -71,6 +71,11 @@ public class TokenCreateHandler extends BaseTokenHandler implements TransactionH private final CustomFeesValidator customFeesValidator; private final TokenCreateValidator tokenCreateValidator; + /** + * Default constructor for injection. + * @param customFeesValidator custom fees validator + * @param tokenCreateValidator token create validator + */ @Inject public TokenCreateHandler( @NonNull final CustomFeesValidator customFeesValidator, @@ -437,6 +442,12 @@ public Fees calculateFees(@NonNull final FeeContext feeContext) { .calculate(); } + /** + * Get the token subtype to be used for the fees calculation. + * @param tokenType the token type + * @param hasCustomFees if the token has custom fees + * @return the token subtype + */ public static SubType tokenSubTypeFrom(final TokenType tokenType, boolean hasCustomFees) { return switch (tokenType) { case FUNGIBLE_COMMON -> hasCustomFees diff --git a/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/handlers/TokenDeleteHandler.java b/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/handlers/TokenDeleteHandler.java index 0dbb615981eb..575b3e7386d4 100644 --- a/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/handlers/TokenDeleteHandler.java +++ b/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/handlers/TokenDeleteHandler.java @@ -52,6 +52,9 @@ */ @Singleton public class TokenDeleteHandler implements TransactionHandler { + /** + * Default constructor for injection. + */ @Inject public TokenDeleteHandler() {} @@ -100,6 +103,12 @@ final var record = context.recordBuilder(TokenBaseRecordBuilder.class); record.tokenType(updatedToken.tokenType()); } + /** + * Validates the semantics of the token to be deleted. + * @param tokenId the token to be deleted + * @param tokenStore the token store + * @return the token to be deleted + */ @NonNull public Token validateSemantics(@NonNull final TokenID tokenId, @NonNull final ReadableTokenStore tokenStore) { final var token = TokenHandlerHelper.getIfUsable(tokenId, tokenStore); diff --git a/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/handlers/TokenDissociateFromAccountHandler.java b/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/handlers/TokenDissociateFromAccountHandler.java index 11afd5e7e08b..d87607f8a72a 100644 --- a/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/handlers/TokenDissociateFromAccountHandler.java +++ b/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/handlers/TokenDissociateFromAccountHandler.java @@ -50,12 +50,12 @@ import com.hedera.node.app.spi.fees.Fees; import com.hedera.node.app.spi.validation.ExpiryValidator; import com.hedera.node.app.spi.workflows.HandleContext; +import com.hedera.node.app.spi.workflows.HandleException; import com.hedera.node.app.spi.workflows.PreCheckException; import com.hedera.node.app.spi.workflows.PreHandleContext; import com.hedera.node.app.spi.workflows.TransactionHandler; import edu.umd.cs.findbugs.annotations.NonNull; import edu.umd.cs.findbugs.annotations.Nullable; -import java.time.Instant; import java.util.ArrayList; import java.util.List; import javax.inject.Inject; @@ -71,6 +71,9 @@ public class TokenDissociateFromAccountHandler implements TransactionHandler { private static final TokenID NO_ASSOCIATED_TOKENS = TokenID.newBuilder().tokenNum(-1).build(); + /** + * Default constructor for injection. + */ @Inject public TokenDissociateFromAccountHandler() {} @@ -147,20 +150,10 @@ public void handle(@NonNull final HandleContext context) { validateFalse(tokenRel.frozen(), ACCOUNT_FROZEN_FOR_TOKEN); if (tokenRelBalance > 0) { - validateFalse(token.tokenType() == NON_FUNGIBLE_UNIQUE, ACCOUNT_STILL_OWNS_NFTS); - - final var tokenIsExpired = tokenIsExpired(token, context.consensusNow()); - validateTrue(tokenIsExpired, TRANSACTION_REQUIRES_ZERO_TOKEN_BALANCES); - - // If the fungible common token is expired, we automatically transfer the - // dissociating account's balance back to the token's treasury - final var treasuryTokenRel = dissociation.treasuryTokenRel(); - if (treasuryTokenRel != null) { - final var updatedTreasuryBalanceTokenRel = treasuryTokenRel.balance() + tokenRelBalance; - treasuryBalancesToUpdate.add(treasuryTokenRel - .copyBuilder() - .balance(updatedTreasuryBalanceTokenRel) - .build()); + if (token.tokenType() == NON_FUNGIBLE_UNIQUE) { + throw new HandleException(ACCOUNT_STILL_OWNS_NFTS); + } else { + throw new HandleException(TRANSACTION_REQUIRES_ZERO_TOKEN_BALANCES); } } } @@ -261,10 +254,6 @@ private ValidatedResult validateSemantics( return new ValidatedResult(acct, dissociations); } - private boolean tokenIsExpired(final Token token, final Instant consensusNow) { - return token.expirationSecond() <= consensusNow.getEpochSecond(); - } - private record ValidatedResult(@NonNull Account account, @NonNull List dissociations) {} private record Dissociation( diff --git a/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/handlers/TokenFeeScheduleUpdateHandler.java b/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/handlers/TokenFeeScheduleUpdateHandler.java index a51b556d5255..7161a88fe656 100644 --- a/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/handlers/TokenFeeScheduleUpdateHandler.java +++ b/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/handlers/TokenFeeScheduleUpdateHandler.java @@ -62,6 +62,10 @@ public class TokenFeeScheduleUpdateHandler implements TransactionHandler { private final CustomFeesValidator customFeesValidator; + /** + * Default constructor for injection. + * @param customFeesValidator the custom fees validator + */ @Inject public TokenFeeScheduleUpdateHandler(@NonNull final CustomFeesValidator customFeesValidator) { requireNonNull(customFeesValidator); diff --git a/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/handlers/TokenFreezeAccountHandler.java b/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/handlers/TokenFreezeAccountHandler.java index 6a30763f9035..afbfe598c79f 100644 --- a/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/handlers/TokenFreezeAccountHandler.java +++ b/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/handlers/TokenFreezeAccountHandler.java @@ -53,6 +53,9 @@ */ @Singleton public class TokenFreezeAccountHandler implements TransactionHandler { + /** + * Default constructor for injection. + */ @Inject public TokenFreezeAccountHandler() { // Exists for injection diff --git a/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/handlers/TokenGetAccountNftInfosHandler.java b/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/handlers/TokenGetAccountNftInfosHandler.java index 11feba64c4a1..6f00d439a2a6 100644 --- a/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/handlers/TokenGetAccountNftInfosHandler.java +++ b/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/handlers/TokenGetAccountNftInfosHandler.java @@ -41,6 +41,9 @@ */ @Singleton public class TokenGetAccountNftInfosHandler extends FreeQueryHandler { + /** + * Default constructor for injection. + */ @Inject public TokenGetAccountNftInfosHandler() { // Exists for injection diff --git a/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/handlers/TokenGetInfoHandler.java b/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/handlers/TokenGetInfoHandler.java index f3886c4fa8fd..4a8a1aac6ece 100644 --- a/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/handlers/TokenGetInfoHandler.java +++ b/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/handlers/TokenGetInfoHandler.java @@ -62,6 +62,9 @@ */ @Singleton public class TokenGetInfoHandler extends PaidQueryHandler { + /** + * Default constructor for injection. + */ @Inject public TokenGetInfoHandler() { // Exists for injection @@ -207,7 +210,7 @@ public Fees computeFees(@NonNull final QueryContext queryContext) { final var query = queryContext.query(); final var tokenStore = queryContext.createStore(ReadableTokenStore.class); final var op = query.tokenGetInfoOrThrow(); - final var tokenId = op.tokenOrThrow(); + final var tokenId = op.tokenOrElse(TokenID.DEFAULT); final var token = tokenStore.get(tokenId); return queryContext.feeCalculator().legacyCalculate(sigValueObj -> new GetTokenInfoResourceUsage() diff --git a/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/handlers/TokenGetNftInfoHandler.java b/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/handlers/TokenGetNftInfoHandler.java index b1cefe7ed219..b45f61bf9a7f 100644 --- a/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/handlers/TokenGetNftInfoHandler.java +++ b/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/handlers/TokenGetNftInfoHandler.java @@ -52,6 +52,9 @@ */ @Singleton public class TokenGetNftInfoHandler extends PaidQueryHandler { + /** + * Default constructor for injection. + */ @Inject public TokenGetNftInfoHandler() { // Exists for injection diff --git a/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/handlers/TokenGetNftInfosHandler.java b/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/handlers/TokenGetNftInfosHandler.java index e9a6afceef3f..f849d345c62b 100644 --- a/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/handlers/TokenGetNftInfosHandler.java +++ b/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/handlers/TokenGetNftInfosHandler.java @@ -41,6 +41,9 @@ */ @Singleton public class TokenGetNftInfosHandler extends FreeQueryHandler { + /** + * Default constructor for injection. + */ @Inject public TokenGetNftInfosHandler() { // Exists for injection diff --git a/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/handlers/TokenGrantKycToAccountHandler.java b/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/handlers/TokenGrantKycToAccountHandler.java index a10df15c7d45..1802da41d767 100644 --- a/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/handlers/TokenGrantKycToAccountHandler.java +++ b/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/handlers/TokenGrantKycToAccountHandler.java @@ -53,7 +53,9 @@ */ @Singleton public class TokenGrantKycToAccountHandler implements TransactionHandler { - + /** + * Default constructor for injection. + */ @Inject public TokenGrantKycToAccountHandler() {} diff --git a/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/handlers/TokenHandlers.java b/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/handlers/TokenHandlers.java index b384bba5da88..e25c8286859b 100644 --- a/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/handlers/TokenHandlers.java +++ b/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/handlers/TokenHandlers.java @@ -65,7 +65,40 @@ public class TokenHandlers { private final TokenUpdateNftsHandler tokenUpdateNftsHandler; /** - * Constructor for the TokenHandlers class + * Constructor for the TokenHandlers. + * @param cryptoCreateHandler crypto create handler + * @param cryptoUpdateHandler crypto update handler + * @param cryptoTransferHandler crypto transfer handler + * @param cryptoDeleteHandler crypto delete handler + * @param cryptoApproveAllowanceHandler crypto approve allowance handler + * @param cryptoDeleteAllowanceHandler crypto delete allowance handler + * @param cryptoAddLiveHashHandler crypto add live hash handler + * @param cryptoDeleteLiveHashHandler crypto delete live hash handler + * @param tokenCreateHandler token create handler + * @param tokenUpdateHandler token update handler + * @param tokenMintHandler token mint handler + * @param tokenBurnHandler token burn handler + * @param tokenDeleteHandler token delete handler + * @param tokenAccountWipeHandler token account wipe handler + * @param tokenFreezeAccountHandler token freeze account handler + * @param tokenUnfreezeAccountHandler token unfreeze account handler + * @param tokenGrantKycToAccountHandler token grant kyc to account handler + * @param tokenRevokeKycFromAccountHandler token revoke kyc from account handler + * @param tokenAssociateToAccountHandler token associate to account handler + * @param tokenDissociateFromAccountHandler token dissociate from account handler + * @param tokenFeeScheduleUpdateHandler token fee schedule update handler + * @param tokenPauseHandler token pause handler + * @param tokenUnpauseHandler token unpause handler + * @param cryptoGetAccountBalanceHandler crypto get account balance handler + * @param cryptoGetAccountInfoHandler crypto get account info handler + * @param cryptoGetAccountRecordsHandler crypto get account records handler + * @param cryptoGetLiveHashHandler crypto get live hash handler + * @param cryptoGetStakersHandler crypto get stakers handler + * @param tokenGetInfoHandler token get info handler + * @param tokenGetAccountNftInfosHandler token get account nft infos handler + * @param tokenGetNftInfoHandler token get nft info handler + * @param tokenGetNftInfosHandler token get nft infos handler + * @param tokenUpdateNftsHandler token update nfts handler */ @Inject public TokenHandlers( diff --git a/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/handlers/TokenMintHandler.java b/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/handlers/TokenMintHandler.java index dfd28b24b936..50d302dea6f2 100644 --- a/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/handlers/TokenMintHandler.java +++ b/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/handlers/TokenMintHandler.java @@ -79,6 +79,10 @@ public class TokenMintHandler extends BaseTokenHandler implements TransactionHandler { private final TokenSupplyChangeOpsValidator validator; + /** + * Default constructor for injection. + * @param validator the token supply change ops validator + */ @Inject public TokenMintHandler(@NonNull final TokenSupplyChangeOpsValidator validator) { this.validator = requireNonNull(validator); diff --git a/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/handlers/TokenPauseHandler.java b/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/handlers/TokenPauseHandler.java index 647bafec8000..1f016da484d1 100644 --- a/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/handlers/TokenPauseHandler.java +++ b/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/handlers/TokenPauseHandler.java @@ -47,6 +47,9 @@ */ @Singleton public class TokenPauseHandler implements TransactionHandler { + /** + * Default constructor for injection. + */ @Inject public TokenPauseHandler() { // Exists for injection diff --git a/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/handlers/TokenRevokeKycFromAccountHandler.java b/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/handlers/TokenRevokeKycFromAccountHandler.java index c65f56d8348d..15f0e6744db3 100644 --- a/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/handlers/TokenRevokeKycFromAccountHandler.java +++ b/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/handlers/TokenRevokeKycFromAccountHandler.java @@ -53,9 +53,13 @@ */ @Singleton public class TokenRevokeKycFromAccountHandler implements TransactionHandler { - + /** + * Default constructor for injection. + */ @Inject - public TokenRevokeKycFromAccountHandler() {} + public TokenRevokeKycFromAccountHandler() { + // No-op + } /** * This method is called during the pre-handle workflow. diff --git a/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/handlers/TokenUnfreezeAccountHandler.java b/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/handlers/TokenUnfreezeAccountHandler.java index 86d8cba53bf7..9c0a3057a710 100644 --- a/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/handlers/TokenUnfreezeAccountHandler.java +++ b/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/handlers/TokenUnfreezeAccountHandler.java @@ -53,6 +53,9 @@ */ @Singleton public class TokenUnfreezeAccountHandler implements TransactionHandler { + /** + * Default constructor for injection. + */ @Inject public TokenUnfreezeAccountHandler() { // Exists for injection diff --git a/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/handlers/TokenUnpauseHandler.java b/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/handlers/TokenUnpauseHandler.java index ce3c96f33081..48fc60606b03 100644 --- a/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/handlers/TokenUnpauseHandler.java +++ b/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/handlers/TokenUnpauseHandler.java @@ -45,6 +45,9 @@ */ @Singleton public class TokenUnpauseHandler implements TransactionHandler { + /** + * Default constructor for injection. + */ @Inject public TokenUnpauseHandler() { // Exists for injection diff --git a/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/handlers/TokenUpdateHandler.java b/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/handlers/TokenUpdateHandler.java index 9f281d3f5438..5ed97ea371b5 100644 --- a/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/handlers/TokenUpdateHandler.java +++ b/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/handlers/TokenUpdateHandler.java @@ -20,25 +20,21 @@ import static com.hedera.hapi.node.base.ResponseCodeEnum.CURRENT_TREASURY_STILL_OWNS_NFTS; import static com.hedera.hapi.node.base.ResponseCodeEnum.INVALID_ACCOUNT_ID; import static com.hedera.hapi.node.base.ResponseCodeEnum.INVALID_AUTORENEW_ACCOUNT; -import static com.hedera.hapi.node.base.ResponseCodeEnum.INVALID_CUSTOM_FEE_SCHEDULE_KEY; import static com.hedera.hapi.node.base.ResponseCodeEnum.INVALID_TOKEN_ID; import static com.hedera.hapi.node.base.ResponseCodeEnum.INVALID_TREASURY_ACCOUNT_FOR_TOKEN; -import static com.hedera.hapi.node.base.ResponseCodeEnum.TOKEN_HAS_NO_FEE_SCHEDULE_KEY; -import static com.hedera.hapi.node.base.ResponseCodeEnum.TOKEN_HAS_NO_FREEZE_KEY; import static com.hedera.hapi.node.base.ResponseCodeEnum.TOKEN_HAS_NO_KYC_KEY; -import static com.hedera.hapi.node.base.ResponseCodeEnum.TOKEN_HAS_NO_METADATA_KEY; -import static com.hedera.hapi.node.base.ResponseCodeEnum.TOKEN_HAS_NO_PAUSE_KEY; -import static com.hedera.hapi.node.base.ResponseCodeEnum.TOKEN_HAS_NO_SUPPLY_KEY; -import static com.hedera.hapi.node.base.ResponseCodeEnum.TOKEN_HAS_NO_WIPE_KEY; import static com.hedera.hapi.node.base.ResponseCodeEnum.TOKEN_IS_IMMUTABLE; import static com.hedera.hapi.node.base.ResponseCodeEnum.TRANSACTION_REQUIRES_ZERO_TOKEN_BALANCES; +import static com.hedera.hapi.node.base.TokenKeyValidation.NO_VALIDATION; import static com.hedera.hapi.node.base.TokenType.FUNGIBLE_COMMON; import static com.hedera.hapi.node.base.TokenType.NON_FUNGIBLE_UNIQUE; import static com.hedera.node.app.hapi.fees.usage.crypto.CryptoOpsUsage.txnEstimateFactory; import static com.hedera.node.app.service.mono.pbj.PbjConverter.fromPbj; import static com.hedera.node.app.service.token.impl.util.TokenHandlerHelper.getIfUsable; +import static com.hedera.node.app.service.token.impl.util.TokenKey.METADATA_KEY; import static com.hedera.node.app.spi.key.KeyUtils.isValid; import static com.hedera.node.app.spi.validation.AttributeValidator.isKeyRemoval; +import static com.hedera.node.app.spi.validation.Validations.mustExist; import static com.hedera.node.app.spi.workflows.HandleException.validateTrue; import static com.hedera.node.app.spi.workflows.PreCheckException.validateTruePreCheck; import static java.util.Objects.requireNonNull; @@ -58,6 +54,7 @@ import com.hedera.node.app.service.token.impl.WritableAccountStore; import com.hedera.node.app.service.token.impl.WritableTokenRelationStore; import com.hedera.node.app.service.token.impl.WritableTokenStore; +import com.hedera.node.app.service.token.impl.util.TokenKey; import com.hedera.node.app.service.token.impl.validators.TokenUpdateValidator; import com.hedera.node.app.service.token.records.TokenUpdateRecordBuilder; import com.hedera.node.app.spi.fees.FeeContext; @@ -70,8 +67,8 @@ import com.hedera.node.app.spi.workflows.TransactionHandler; import com.hedera.node.config.data.TokensConfig; import edu.umd.cs.findbugs.annotations.NonNull; -import java.util.ArrayList; -import java.util.List; +import edu.umd.cs.findbugs.annotations.Nullable; +import java.util.Arrays; import javax.inject.Inject; import javax.inject.Singleton; @@ -80,8 +77,14 @@ */ @Singleton public class TokenUpdateHandler extends BaseTokenHandler implements TransactionHandler { + private static final AccountID ZERO_ACCOUNT_ID = + AccountID.newBuilder().accountNum(0L).build(); private final TokenUpdateValidator tokenUpdateValidator; + /** + * Create a new {@link TokenUpdateHandler} instance. + * @param tokenUpdateValidator The {@link TokenUpdateValidator} to use. + */ @Inject public TokenUpdateHandler(@NonNull final TokenUpdateValidator tokenUpdateValidator) { this.tokenUpdateValidator = tokenUpdateValidator; @@ -92,8 +95,18 @@ public void pureChecks(@NonNull final TransactionBody txn) throws PreCheckExcept requireNonNull(txn); final var op = txn.tokenUpdateOrThrow(); validateTruePreCheck(op.hasToken(), INVALID_TOKEN_ID); - if (op.hasFeeScheduleKey()) { - validateTruePreCheck(isValid(op.feeScheduleKey()), INVALID_CUSTOM_FEE_SCHEDULE_KEY); + // IMPORTANT: No matter the TokenKeyValidation mode, we always require keys to + // be structurally valid. Putting structurally invalid keys into ledger state + // makes no sense, and could create problems for mirror nodes and block explorers. + // That is, using NO_VALIDATION only lets a user set a new key without its signature; + // and thus set a low-entropy key that proves a role function has been "disabled" + for (final var tokenKey : TOKEN_KEYS) { + if (tokenKey.isPresentInUpdate(op)) { + final var key = tokenKey.getFromUpdate(op); + if (!isKeyRemoval(key)) { + validateTruePreCheck(isValid(key), tokenKey.invalidKeyStatus()); + } + } } } @@ -101,44 +114,9 @@ public void pureChecks(@NonNull final TransactionBody txn) throws PreCheckExcept public void preHandle(@NonNull final PreHandleContext context) throws PreCheckException { requireNonNull(context); final var op = context.body().tokenUpdateOrThrow(); - pureChecks(context.body()); - - final var tokenId = op.tokenOrThrow(); - - final var tokenStore = context.createStore(ReadableTokenStore.class); - final var token = tokenStore.get(tokenId); - if (token == null) throw new PreCheckException(INVALID_TOKEN_ID); - - if (op.hasAutoRenewAccount()) { - context.requireKeyOrThrow(op.autoRenewAccountOrThrow(), INVALID_AUTORENEW_ACCOUNT); - } - if (op.hasTreasury()) { - context.requireKeyOrThrow(op.treasuryOrThrow(), INVALID_ACCOUNT_ID); - } - if (op.hasAdminKey()) { - context.requireKey(op.adminKeyOrThrow()); - } - // To update metadata either admin key or metadata key should sign. - // For updating any other fields admin key should sign. - if (isMetadataOnlyUpdateOp(op) && (token.hasAdminKey() || token.hasMetadataKey())) { - final List keys = new ArrayList<>(); - if (token.hasAdminKey()) { - keys.add(token.adminKey()); - } - if (token.hasMetadataKey()) { - keys.add(token.metadataKey()); - } - final Key threshKey = Key.newBuilder() - .thresholdKey(ThresholdKey.newBuilder() - .keys(KeyList.newBuilder().keys(keys).build()) - .threshold(1) - .build()) - .build(); - context.requireKey(threshKey); - } else if (!isExpiryOnlyUpdateOp(op) && token.hasAdminKey()) { - // For expiry only op admin key is not required - context.requireKey(token.adminKey()); - } + final var token = context.createStore(ReadableTokenStore.class).get(op.tokenOrThrow()); + mustExist(token, INVALID_TOKEN_ID); + addRequiredSigners(context, op, token); } @Override @@ -166,8 +144,9 @@ public void handle(@NonNull final HandleContext context) throws HandleException // enabled and has open slots. If so, auto-associate. // We allow existing treasuries to have any nft balances left over, but the new treasury should // not have any balances left over. Transfer all balances for the current token to new treasury - if (op.hasTreasury()) { - final var existingTreasury = token.treasuryAccountId(); + // Also check if the treasury is not a zero account if the transaction is a contract call + if (op.hasTreasury() && isHapiCallOrNonZeroTreasuryAccount(txn.hasTransactionID(), op)) { + final var existingTreasury = token.treasuryAccountIdOrThrow(); final var newTreasury = op.treasuryOrThrow(); final var newTreasuryAccount = getIfUsable( newTreasury, accountStore, context.expiryValidator(), INVALID_TREASURY_ACCOUNT_FOR_TOKEN); @@ -198,7 +177,7 @@ public void handle(@NonNull final HandleContext context) throws HandleException transferTokensToNewTreasury(existingTreasury, newTreasury, token, tokenRelStore, accountStore); } } - final var tokenBuilder = customizeToken(token, resolvedExpiry, op); + final var tokenBuilder = customizeToken(token, resolvedExpiry, op, txn.hasTransactionID()); tokenStore.put(tokenBuilder.build()); recordBuilder.tokenType(token.tokenType()); } @@ -219,7 +198,7 @@ private void transferTokensToNewTreasury( final Token token, final WritableTokenRelationStore tokenRelStore, final WritableAccountStore accountStore) { - final var tokenId = token.tokenId(); + final var tokenId = token.tokenIdOrThrow(); // Validate both accounts are not frozen and have the right keys final var oldTreasuryRel = getIfUsable(oldTreasury, tokenId, tokenRelStore); final var newTreasuryRel = getIfUsable(newTreasury, tokenId, tokenRelStore); @@ -251,14 +230,14 @@ private void transferTokensToNewTreasury( * @param accountStore account store */ private void transferFungibleTokensToTreasury( - final TokenRelation fromTreasuryRel, - final TokenRelation toTreasuryRel, + @NonNull final TokenRelation fromTreasuryRel, + @NonNull final TokenRelation toTreasuryRel, final WritableTokenRelationStore tokenRelStore, final WritableAccountStore accountStore) { final var adjustment = fromTreasuryRel.balance(); - final var fromTreasury = accountStore.getAccountById(fromTreasuryRel.accountId()); - final var toTreasury = accountStore.getAccountById(toTreasuryRel.accountId()); + final var fromTreasury = requireNonNull(accountStore.getAccountById(fromTreasuryRel.accountIdOrThrow())); + final var toTreasury = requireNonNull(accountStore.getAccountById(toTreasuryRel.accountIdOrThrow())); adjustBalance(fromTreasuryRel, fromTreasury, -adjustment, tokenRelStore, accountStore); adjustBalance(toTreasuryRel, toTreasury, adjustment, tokenRelStore, accountStore); @@ -330,13 +309,14 @@ private void validateFrozenAndKey(final TokenRelation tokenRel) { private Token.Builder customizeToken( @NonNull final Token token, @NonNull final ExpiryMeta resolvedExpiry, - @NonNull final TokenUpdateTransactionBody op) { + @NonNull final TokenUpdateTransactionBody op, + final boolean isHapiCall) { final var copyToken = token.copyBuilder(); // All these keys are validated in validateSemantics // If these keys did not exist on the token already, they can't be changed on update updateKeys(op, token, copyToken); updateExpiryFields(op, resolvedExpiry, copyToken); - updateTokenAttributes(op, copyToken, token); + updateTokenAttributes(op, copyToken, token, isHapiCall); return copyToken; } @@ -349,7 +329,10 @@ private Token.Builder customizeToken( * @param originalToken original token */ private void updateTokenAttributes( - final TokenUpdateTransactionBody op, final Token.Builder builder, final Token originalToken) { + final TokenUpdateTransactionBody op, + final Token.Builder builder, + final Token originalToken, + final boolean isHapiCall) { if (op.symbol() != null && op.symbol().length() > 0) { builder.symbol(op.symbol()); } @@ -357,12 +340,23 @@ private void updateTokenAttributes( builder.name(op.name()); } if (op.hasMemo()) { - builder.memo(op.memo()); + final var memo = op.memoOrThrow(); + // Since an tokenUpdate() system call cannot encode a difference between + // (1) choosing not to update the memo and (2) setting it to a blank string, + // we only set a blank memo if the update came from HAPI + if (!memo.isBlank() || isHapiCall) { + builder.memo(memo); + } } if (op.hasMetadata()) { builder.metadata(op.metadata()); } - if (op.hasTreasury() && !op.treasuryOrThrow().equals(originalToken.treasuryAccountId())) { + // Here we check that there is a treasury to be updated, + // that if the transaction is a contract call the treasury shouldn't be a zero account + // and that the provided treasury account is different from the current one + if (op.hasTreasury() + && isHapiCallOrNonZeroTreasuryAccount(isHapiCall, op) + && !op.treasuryOrThrow().equals(originalToken.treasuryAccountId())) { builder.treasuryAccountId(op.treasuryOrThrow()); } } @@ -398,49 +392,175 @@ private void updateExpiryFields( */ private void updateKeys( final TokenUpdateTransactionBody op, final Token originalToken, final Token.Builder builder) { - if (op.hasKycKey()) { - validateTrue(originalToken.hasKycKey(), TOKEN_HAS_NO_KYC_KEY); - builder.kycKey(op.kycKey()); + TOKEN_KEYS.forEach(key -> key.updateKey(op, originalToken, builder)); + } + + /** + * Add all signature requirements for TokenUpdateTx. + * Note: those requirements drastically changed after HIP-540 + * + * @param context pre handle context + * @param op token update transaction body + * @param token original token + */ + private void addRequiredSigners( + @NonNull PreHandleContext context, @NonNull final TokenUpdateTransactionBody op, @NonNull final Token token) + throws PreCheckException { + // Since we de-duplicate all the keys in the PreHandleContext, + // we can safely add one key multiple times for the transaction to keep the logic simple. + + // metadata can be updated with either admin key or metadata key + if (op.hasMetadata()) { + if (token.hasMetadataKey()) { + requireAdminOrRole(context, token, METADATA_KEY); + } else { + requireAdmin(context, token); + } } - if (op.hasFreezeKey()) { - validateTrue(originalToken.hasFreezeKey(), TOKEN_HAS_NO_FREEZE_KEY); - builder.freezeKey(op.freezeKey()); + // updating treasury needs admin and new treasury key + // If the transactionID is missing we have contract call, + // so we need to verify that the provided treasury is not a zero account. + if (op.hasTreasury() + && isHapiCallOrNonZeroTreasuryAccount(context.body().hasTransactionID(), op)) { + requireAdmin(context, token); + context.requireKeyOrThrow(op.treasuryOrThrow(), INVALID_ACCOUNT_ID); } - if (op.hasWipeKey()) { - validateTrue(originalToken.hasWipeKey(), TOKEN_HAS_NO_WIPE_KEY); - builder.wipeKey(op.wipeKey()); + // updating auto-renew account needs admin key and the new auto-renewal account key + if (op.hasAutoRenewAccount()) { + requireAdmin(context, token); + context.requireKeyOrThrow(op.autoRenewAccountOrThrow(), INVALID_AUTORENEW_ACCOUNT); } - if (op.hasSupplyKey()) { - validateTrue(originalToken.hasSupplyKey(), TOKEN_HAS_NO_SUPPLY_KEY); - builder.supplyKey(op.supplyKey()); + // updating admin key needs the old admin key and the new admin key + if (op.hasAdminKey()) { + requireAdmin(context, token); + context.requireKey(op.adminKeyOrThrow()); } - if (op.hasFeeScheduleKey()) { - validateTrue(originalToken.hasFeeScheduleKey(), TOKEN_HAS_NO_FEE_SCHEDULE_KEY); - builder.feeScheduleKey(op.feeScheduleKey()); + // Any key removal requires admin key + if (containsKeyRemoval(op)) { + requireAdmin(context, token); } - if (op.hasPauseKey()) { - validateTrue(originalToken.hasPauseKey(), TOKEN_HAS_NO_PAUSE_KEY); - builder.pauseKey(op.pauseKey()); + // updating memo, name, symbol, auto-renew period needs admin key + if (updatesAdminOnlyNonKeyTokenProperty(op)) { + requireAdmin(context, token); } - if (op.hasMetadataKey()) { - validateTrue(originalToken.hasMetadataKey(), TOKEN_HAS_NO_METADATA_KEY); - builder.metadataKey(op.metadataKey()); + // Any role key can be updated if the key is not empty of the token. + // Updating any non-admin keys with the key verification mode is NO_VALIDATION needs either + // admin key or the role key to sign.If the key verification mode is FULL_VALIDATION, then + // the new key also should sign the transaction. + for (final var tokenKey : NON_ADMIN_TOKEN_KEYS) { + if (tokenKey.isPresentInUpdate(op)) { + final var newRoleKey = tokenKey.getFromUpdate(op); + if (!isKeyRemoval(newRoleKey)) { + if (op.keyVerificationMode() == NO_VALIDATION) { + requireAdminOrRole(context, token, tokenKey); + } else { + // With "full" verification mode, our required key + // structure is a 1/2 threshold with components: + // - Admin key + // - A 2/2 list including the role key and its replacement key + final var key = tokenKey.getFromUpdate(op); + requireAdminOrRole(context, token, tokenKey, key); + } + } + } } + } + + /** + * Check if the token update transaction body updates any non-key token property. + * @param context pre handle context + * @param token original token + * @param roleKey role key + * @throws PreCheckException if the token is immutable + */ + private void requireAdminOrRole( + @NonNull final PreHandleContext context, @NonNull final Token token, @NonNull final TokenKey roleKey) + throws PreCheckException { + requireAdminOrRole(context, token, roleKey, null); + } - if (isMetadataOnlyUpdateOp(op)) { - validateTrue(originalToken.hasAdminKey() || originalToken.hasMetadataKey(), TOKEN_IS_IMMUTABLE); - } else if (!isExpiryOnlyUpdateOp(op)) { - validateTrue(originalToken.hasAdminKey(), TOKEN_IS_IMMUTABLE); + /** + * If the original token has RoleKey, only then updating role key is possible. Otherwise, fail with TOKEN_IS_IMMUTABLE. + * If the original token has AdminKey, then require the admin key to sign the transaction. Otherwise, require the + * role key to sign the transaction. + * If the original token has neither AdminKey nor RoleKey, then fail with TOKEN_IS_IMMUTABLE. + * @param context pre handle context + * @param token original token + * @param roleKey role key + * @param replacementKey replacement key + * @throws PreCheckException if the token is immutable + */ + private void requireAdminOrRole( + @NonNull final PreHandleContext context, + @NonNull final Token token, + @NonNull final TokenKey roleKey, + @Nullable final Key replacementKey) + throws PreCheckException { + final var maybeRoleKey = roleKey.getFromToken(token); + // Prioritize TOKEN_IS_IMMUTABLE for completely immutable tokens + mustExist(maybeRoleKey, token.hasAdminKey() ? roleKey.tokenHasNoKeyStatus() : TOKEN_IS_IMMUTABLE); + if (token.hasAdminKey()) { + context.requireKey(oneOf( + replacementKey == null ? maybeRoleKey : allOf(maybeRoleKey, replacementKey), + token.adminKeyOrThrow())); + } else { + context.requireKey(maybeRoleKey); + if (replacementKey != null) { + context.requireKey(replacementKey); + } } + } - if (op.hasAdminKey()) { - final var newAdminKey = op.adminKey(); - if (isKeyRemoval(newAdminKey)) { - builder.adminKey((Key) null); - } else { - builder.adminKey(newAdminKey); + /** + * Checks if the token has adminKey, if so require the admin key to sign the transaction. + * If the token does not have adminKey, then fail with TOKEN_IS_IMMUTABLE. + * @param context pre handle context + * @param originalToken original token + * @throws PreCheckException if the token is immutable + */ + private void requireAdmin(@NonNull final PreHandleContext context, @NonNull final Token originalToken) + throws PreCheckException { + validateTruePreCheck(originalToken.hasAdminKey(), TOKEN_IS_IMMUTABLE); + context.requireKey(originalToken.adminKeyOrThrow()); + } + + /** + * Checks if the token update transaction body has any key removals, by using immutable sentinel keys to remove the key. + * @param op token update transaction body + * @return true if the token update transaction body has any key removals, false otherwise + */ + private boolean containsKeyRemoval(@NonNull final TokenUpdateTransactionBody op) { + for (final var tokenKey : TOKEN_KEYS) { + if (tokenKey.containsKeyRemoval(op)) { + return true; } } + return false; + } + + /** + * Creates a threshold key with threshold 1 with the given keys. + * @param keysRequired keys required + * @return threshold key with threshold 1 + */ + private Key oneOf(@NonNull final Key... keysRequired) { + return Key.newBuilder() + .thresholdKey(ThresholdKey.newBuilder() + .keys(new KeyList(Arrays.asList(keysRequired))) + .threshold(1) + .build()) + .build(); + } + + /** + * Creates a key list with the given keys. + * @param keysRequired keys required + * @return key list + */ + private Key allOf(@NonNull final Key... keysRequired) { + return Key.newBuilder() + .keyList(new KeyList(Arrays.asList(keysRequired))) + .build(); } /** @@ -495,4 +615,12 @@ public Fees calculateFees(@NonNull final FeeContext feeContext) { txnEstimateFactory) .usageGiven(fromPbj(body), sigValueObj, token)); } + + private boolean isHapiCallOrNonZeroTreasuryAccount(final boolean isHapiCall, final TokenUpdateTransactionBody op) { + return isHapiCall || !isZeroAccount(op.treasury()); + } + + private boolean isZeroAccount(@NonNull final AccountID accountID) { + return accountID.equals(ZERO_ACCOUNT_ID); + } } diff --git a/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/handlers/TokenUpdateNftsHandler.java b/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/handlers/TokenUpdateNftsHandler.java index bc5cc79b3141..9f9b36bd3903 100644 --- a/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/handlers/TokenUpdateNftsHandler.java +++ b/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/handlers/TokenUpdateNftsHandler.java @@ -56,6 +56,10 @@ public class TokenUpdateNftsHandler implements TransactionHandler { private final TokenAttributesValidator validator; + /** + * Create a new {@link TokenUpdateNftsHandler} instance. + * @param validator The {@link TokenAttributesValidator} to use. + */ @Inject public TokenUpdateNftsHandler(@NonNull final TokenAttributesValidator validator) { this.validator = validator; diff --git a/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/handlers/staking/EndOfStakingPeriodUpdater.java b/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/handlers/staking/EndOfStakingPeriodUpdater.java index 358937528abc..f66eb71a45b7 100644 --- a/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/handlers/staking/EndOfStakingPeriodUpdater.java +++ b/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/handlers/staking/EndOfStakingPeriodUpdater.java @@ -23,6 +23,7 @@ import static com.hedera.node.app.service.token.impl.handlers.staking.EndOfStakingPeriodUtils.computeNextStake; import static com.hedera.node.app.service.token.impl.handlers.staking.EndOfStakingPeriodUtils.readableNonZeroHistory; import static com.hedera.node.app.spi.workflows.record.SingleTransactionRecordBuilder.transactionWith; +import static java.util.Objects.requireNonNull; import com.google.common.annotations.VisibleForTesting; import com.hedera.hapi.node.base.Fraction; @@ -58,6 +59,9 @@ import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; +/** + * Updates the stake and reward values for all nodes at the end of a staking period. + */ @Singleton public class EndOfStakingPeriodUpdater { private static final Logger log = LogManager.getLogger(EndOfStakingPeriodUpdater.class); @@ -68,6 +72,11 @@ public class EndOfStakingPeriodUpdater { private final HederaAccountNumbers accountNumbers; private final StakingRewardsHelper stakeRewardsHelper; + /** + * Constructs an {@link EndOfStakingPeriodUpdater} instance. + * @param accountNumbers the account numbers + * @param stakeRewardsHelper the staking rewards helper + */ @Inject public EndOfStakingPeriodUpdater( @NonNull final HederaAccountNumbers accountNumbers, @@ -119,7 +128,7 @@ public void updateNodes(@NonNull final TokenContext context) { final Map updatedNodeInfos = new HashMap<>(); final Map newPendingRewardRates = new HashMap<>(); for (final var nodeNum : nodeIds.stream().sorted().toList()) { - var currStakingInfo = stakingInfoStore.getForModify(nodeNum); + var currStakingInfo = requireNonNull(stakingInfoStore.getForModify(nodeNum)); // The return value here includes both the new reward sum history, and the reward rate // (tinybars-per-hbar-staked-to-reward) that will be paid to all accounts who had staked-to-reward for this @@ -136,7 +145,8 @@ public void updateNodes(@NonNull final TokenContext context) { .rewardSumHistory(newRewardSumHistory.rewardSumHistory()) .build(); log.info( - " > Non-zero reward sum history is now {}", + "Non-zero reward sum history for node number {} is now {}", + () -> nodeNum, () -> readableNonZeroHistory(newRewardSumHistory.rewardSumHistory())); final var oldStakeRewardStart = currStakingInfo.stakeRewardStart(); @@ -260,6 +270,8 @@ public void updateNodes(@NonNull final TokenContext context) { * @param weight weight of the node * @param newMinStake min stake of the node * @param newMaxStake real max stake of all nodes computed by taking max(stakeOfNode1, stakeOfNode2, ...) + * @param totalStakeOfAllNodes total stake of all nodes at the start of new period + * @param sumOfConsensusWeights sum of consensus weights of all nodes * @return scaled weight of the node */ @VisibleForTesting @@ -312,6 +324,7 @@ public static long scaleUpWeightToStake( * * @param stake the stake of current node, includes stake rewarded and non-rewarded * @param totalStakeOfAllNodes the total stake of all nodes at the start of new period + * @param sumOfConsensusWeights the sum of consensus weights of all nodes * @return calculated consensus weight of the node */ @VisibleForTesting @@ -333,9 +346,13 @@ public static int calculateWeightFromStake( return (int) Math.max(weight, 1); } + /** + * Returns the timestamp that is just before midnight of the day of the given consensus time. + * @param consensusTime the consensus time + * @return the timestamp that is just before midnight of the day of the given consensus time + */ @VisibleForTesting - public static com.hedera.hapi.node.base.Timestamp lastInstantOfPreviousPeriodFor( - @NonNull final Instant consensusTime) { + public static Timestamp lastInstantOfPreviousPeriodFor(@NonNull final Instant consensusTime) { final var justBeforeMidNightTime = LocalDate.ofInstant(consensusTime, ZoneId.of("UTC")) .atStartOfDay() .minusNanos(1); // give out the timestamp that is just before midnight @@ -414,8 +431,7 @@ long rescaledPerHbarRewardRate( } private long getRewardsBalance(@NonNull final ReadableAccountStore accountStore) { - return accountStore - .getAccountById(asAccount(accountNumbers.stakingRewardAccount())) + return requireNonNull(accountStore.getAccountById(asAccount(accountNumbers.stakingRewardAccount()))) .tinybarBalance(); } @@ -455,9 +471,9 @@ private static NodeStake fromStakingInfo(final long rewardRate, StakingNodeInfo * @return the transaction builder with the {@code NodeStakeUpdateTransactionBody} set */ private static TransactionBody.Builder newNodeStakeUpdateBuilder( - final com.hedera.hapi.node.base.Timestamp stakingPeriodEnd, - final List nodeStakes, - final StakingConfig stakingConfig, + final Timestamp stakingPeriodEnd, + @NonNull final List nodeStakes, + @NonNull final StakingConfig stakingConfig, final long totalStakedRewardStart, final long maxPerHbarRewardRate, final long reservedStakingRewards, @@ -468,7 +484,7 @@ private static TransactionBody.Builder newNodeStakeUpdateBuilder( final var stakingPeriod = stakingConfig.periodMins(); final var stakingPeriodsStored = stakingConfig.rewardHistoryNumStoredPeriods(); - final var nodeRewardFeeFraction = com.hedera.hapi.node.base.Fraction.newBuilder() + final var nodeRewardFeeFraction = Fraction.newBuilder() .numerator(stakingConfig.feesNodeRewardPercentage()) .denominator(100L) .build(); diff --git a/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/handlers/staking/StakeIdChangeType.java b/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/handlers/staking/StakeIdChangeType.java index cb6d7e15cd31..0359d0a80056 100644 --- a/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/handlers/staking/StakeIdChangeType.java +++ b/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/handlers/staking/StakeIdChangeType.java @@ -32,6 +32,9 @@ * ABSENT_TO_ABSENT also means that the account was never staked before and not staked now. */ public enum StakeIdChangeType { + /** + * If the account was not staked to a node and now is staked to a node. + */ /* --- Cases ending with staking to a node */ FROM_ABSENT_TO_NODE { @Override @@ -39,6 +42,9 @@ boolean awardsToNode() { return true; } }, + /** + * If the account was staked to an account and now is staked to a node. + */ FROM_ACCOUNT_TO_NODE { @Override boolean withdrawsFromAccount() { @@ -50,6 +56,9 @@ boolean awardsToNode() { return true; } }, + /** + * If the account was staked to a node and now is staked to a different node or same node. + */ FROM_NODE_TO_NODE { @Override boolean withdrawsFromNode() { @@ -62,12 +71,18 @@ boolean awardsToNode() { } }, /* --- Cases ending with staking to an account */ + /** + * If the account was not staked and now is staked to an account. + */ FROM_ABSENT_TO_ACCOUNT { @Override boolean awardsToAccount() { return true; } }, + /** + * If the account was staked to a node and now is staked to an account. + */ FROM_NODE_TO_ACCOUNT { @Override boolean withdrawsFromNode() { @@ -79,6 +94,9 @@ boolean awardsToAccount() { return true; } }, + /** + * If the account was staked to an account and now is staked to another account. + */ FROM_ACCOUNT_TO_ACCOUNT { @Override boolean withdrawsFromAccount() { @@ -91,20 +109,35 @@ boolean awardsToAccount() { } }, /* --- Cases ending with absent staking */ + /** + * If the account was not staked and now is not staked. + */ FROM_ABSENT_TO_ABSENT {}, + /** + * If the account was staked to an account and now is not staked. + */ FROM_ACCOUNT_TO_ABSENT { @Override boolean withdrawsFromAccount() { return true; } }, + /** + * If the account was staked to a node and now is not staked. + */ FROM_NODE_TO_ABSENT { @Override boolean withdrawsFromNode() { return true; } }; - + /** + * Returns the type of staking change for the given account. + * + * @param currentAccount the current account + * @param modifiedAccount the modified account + * @return the type of staking change for the given account + */ public static StakeIdChangeType forCase( @Nullable final Account currentAccount, @NonNull final Account modifiedAccount) { final var curStakedIdCase = currentAccount == null ? UNSET : getCurrentStakedIdCase(currentAccount); diff --git a/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/handlers/staking/StakeInfoHelper.java b/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/handlers/staking/StakeInfoHelper.java index a531bc8a97e1..6af484970a10 100644 --- a/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/handlers/staking/StakeInfoHelper.java +++ b/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/handlers/staking/StakeInfoHelper.java @@ -35,6 +35,9 @@ public class StakeInfoHelper { private static final Logger log = LogManager.getLogger(StakeInfoHelper.class); + /** + * Default constructor for injection. + */ @Inject public StakeInfoHelper() { // Needed for Dagger injection @@ -45,6 +48,7 @@ public StakeInfoHelper() { * * @param nodeId the node's numeric ID * @param amount the amount to increase the unclaimed stake reward start by + * @param stakingInfoStore the store for the staking info */ public void increaseUnclaimedStakeRewards( @NonNull final Long nodeId, final long amount, @NonNull final WritableStakingInfoStore stakingInfoStore) { diff --git a/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/handlers/staking/StakePeriodManager.java b/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/handlers/staking/StakePeriodManager.java index 9d77c6ca72c4..30d723e15bec 100644 --- a/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/handlers/staking/StakePeriodManager.java +++ b/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/handlers/staking/StakePeriodManager.java @@ -34,9 +34,13 @@ */ @Singleton public class StakePeriodManager { - // Sentinel value for a field that wasn't applicable to this transaction - public static final long NA = Long.MIN_VALUE; + /** + * The UTC time zone. + */ public static final ZoneId ZONE_UTC = ZoneId.of("UTC"); + /** + * The default staking period in minutes. + */ public static final long DEFAULT_STAKING_PERIOD_MINS = 1440L; private final int numStoredPeriods; @@ -44,6 +48,10 @@ public class StakePeriodManager { private long currentStakePeriod; private long prevConsensusSecs; + /** + * Default constructor for injection. + * @param configProvider the configuration provider + */ @Inject public StakePeriodManager(@NonNull final ConfigProvider configProvider) { final var config = configProvider.getConfiguration().getConfigData(StakingConfig.class); @@ -195,6 +203,10 @@ public long startUpdateFor( return -1; } + /** + * Returns the consensus time of previous transaction, that is used to change the current stake period. + * @return the consensus time of previous transaction + */ @VisibleForTesting public long getPrevConsensusSecs() { return prevConsensusSecs; diff --git a/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/handlers/staking/StakeRewardCalculatorImpl.java b/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/handlers/staking/StakeRewardCalculatorImpl.java index 1c0708f0e1af..ddd7499b36d9 100644 --- a/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/handlers/staking/StakeRewardCalculatorImpl.java +++ b/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/handlers/staking/StakeRewardCalculatorImpl.java @@ -30,11 +30,18 @@ import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; +/** + * This class manages calculations of staking rewards + */ @Singleton public class StakeRewardCalculatorImpl implements StakeRewardCalculator { private static final Logger logger = LogManager.getLogger(StakeRewardCalculatorImpl.class); private final StakePeriodManager stakePeriodManager; + /** + * Default constructor for injection. + * @param stakePeriodManager the stake period manager + */ @Inject public StakeRewardCalculatorImpl(@NonNull final StakePeriodManager stakePeriodManager) { this.stakePeriodManager = stakePeriodManager; @@ -87,6 +94,14 @@ public long epochSecondAtStartOfPeriod(final long stakePeriod) { return stakePeriodManager.epochSecondAtStartOfPeriod(stakePeriod); } + /** + * Computes the reward for the given account based on the given node staking info. + * @param account the account + * @param nodeStakingInfo the node staking info + * @param currentStakePeriod the current stake period + * @param effectiveStart the effective start + * @return the reward for the given account based on the given node staking info + */ @VisibleForTesting public static long computeRewardFromDetails( @NonNull final Account account, diff --git a/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/handlers/staking/StakingRewardsDistributor.java b/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/handlers/staking/StakingRewardsDistributor.java index 2c6437a01acb..d7c22c8e9b1e 100644 --- a/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/handlers/staking/StakingRewardsDistributor.java +++ b/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/handlers/staking/StakingRewardsDistributor.java @@ -45,6 +45,11 @@ public class StakingRewardsDistributor { private StakingRewardsHelper stakingRewardHelper; private StakeRewardCalculatorImpl rewardCalculator; + /** + * Default constructor for injection. + * @param stakingRewardHelper the staking rewards helper + * @param rewardCalculator the reward calculator + */ @Inject public StakingRewardsDistributor( @NonNull final StakingRewardsHelper stakingRewardHelper, @@ -53,6 +58,16 @@ public StakingRewardsDistributor( this.rewardCalculator = rewardCalculator; } + /** + * Pays out rewards to the possible reward receivers by updating the receiver's balance. + * @param possibleRewardReceivers The accounts that are possible reward receivers. + * @param writableStore The store to update the receiver's balance in. + * @param stakingRewardsStore The store to update the staking rewards in. + * @param stakingInfoStore The store to update the staking info in. + * @param consensusNow The current consensus time. + * @param recordBuilder The record builder to use for deleted account beneficiaries. + * @return The map of rewards paid to each receiver, which includes zero rewards if the calculated reward is zero. + */ public Map payRewardsIfPending( @NonNull final Set possibleRewardReceivers, @NonNull final WritableAccountStore writableStore, diff --git a/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/handlers/staking/StakingRewardsHandlerImpl.java b/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/handlers/staking/StakingRewardsHandlerImpl.java index 3afb6b526e85..beba968b385e 100644 --- a/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/handlers/staking/StakingRewardsHandlerImpl.java +++ b/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/handlers/staking/StakingRewardsHandlerImpl.java @@ -49,6 +49,9 @@ import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; +/** + * This handler manages the paying staking rewards for the accounts. + */ @Singleton public class StakingRewardsHandlerImpl implements StakingRewardsHandler { private static final Logger log = LogManager.getLogger(StakingRewardsHandlerImpl.class); @@ -56,6 +59,12 @@ public class StakingRewardsHandlerImpl implements StakingRewardsHandler { private final StakePeriodManager stakePeriodManager; private final StakeInfoHelper stakeInfoHelper; + /** + * Default constructor for injection. + * @param rewardsPayer the rewards payer + * @param stakePeriodManager the stake period manager + * @param stakeInfoHelper the stake info helper + */ @Inject public StakingRewardsHandlerImpl( @NonNull final StakingRewardsDistributor rewardsPayer, diff --git a/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/handlers/staking/StakingRewardsHelper.java b/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/handlers/staking/StakingRewardsHelper.java index dbb3d0e8116e..31ab699f272e 100644 --- a/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/handlers/staking/StakingRewardsHelper.java +++ b/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/handlers/staking/StakingRewardsHelper.java @@ -48,8 +48,14 @@ @Singleton public class StakingRewardsHelper { private static final Logger log = LogManager.getLogger(StakingRewardsHelper.class); + /** + * The maximum pending rewards that can be paid out in a single staking period, which is 50B hbar. + */ public static final long MAX_PENDING_REWARDS = 50_000_000_000L * HBARS_TO_TINYBARS; + /** + * Default constructor for injection. + */ @Inject public StakingRewardsHelper() { // Exists for Dagger injection diff --git a/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/handlers/staking/StakingUtilities.java b/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/handlers/staking/StakingUtilities.java index 7728a5018d60..0ec12325f4f6 100644 --- a/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/handlers/staking/StakingUtilities.java +++ b/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/handlers/staking/StakingUtilities.java @@ -24,18 +24,38 @@ import edu.umd.cs.findbugs.annotations.NonNull; import edu.umd.cs.findbugs.annotations.Nullable; +/** + * Utility class for staking. + */ public class StakingUtilities { private StakingUtilities() { throw new UnsupportedOperationException("Utility Class"); } + /** + * The default value to represent that the account has not been rewarded since the last staking metadata change. + */ public static final long NOT_REWARDED_SINCE_LAST_STAKING_META_CHANGE = -1; + /** + * The default value to represent that the account has no stake period start. + */ public static final long NO_STAKE_PERIOD_START = -1; + /** + * Rounds the value to the nearest hbar. + * @param value the value to round + * @return the value rounded to the nearest hbar + */ public static long roundedToHbar(final long value) { return (value / HBARS_TO_TINYBARS) * HBARS_TO_TINYBARS; } + /** + * Calculates the total stake of the account. The total stake is the sum of the tinybars balance and the amount + * staked to the account. + * @param account the account + * @return the total stake of the account + */ public static long totalStake(@NonNull Account account) { return account.tinybarBalance() + account.stakedToMe(); } diff --git a/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/handlers/transfer/AdjustFungibleTokenChangesStep.java b/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/handlers/transfer/AdjustFungibleTokenChangesStep.java index b181ba969142..8c58252bde1c 100644 --- a/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/handlers/transfer/AdjustFungibleTokenChangesStep.java +++ b/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/handlers/transfer/AdjustFungibleTokenChangesStep.java @@ -54,6 +54,11 @@ public class AdjustFungibleTokenChangesStep extends BaseTokenHandler implements private final CryptoTransferTransactionBody op; private final AccountID topLevelPayer; + /** + * Constructs the step with the CryptoTransferTransactionBody and the topLevelPayer. + * @param op the CryptoTransferTransactionBody + * @param topLevelPayer the payer account + */ public AdjustFungibleTokenChangesStep( @NonNull final CryptoTransferTransactionBody op, @NonNull final AccountID topLevelPayer) { requireNonNull(op); diff --git a/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/handlers/transfer/AdjustHbarChangesStep.java b/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/handlers/transfer/AdjustHbarChangesStep.java index fe1c450f6541..475516696cdc 100644 --- a/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/handlers/transfer/AdjustHbarChangesStep.java +++ b/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/handlers/transfer/AdjustHbarChangesStep.java @@ -38,10 +38,18 @@ import java.util.List; import java.util.Map; +/** + * Adjusts the hbar balances and token allowances for the accounts involved in the transfer. + */ public class AdjustHbarChangesStep extends BaseTokenHandler implements TransferStep { final CryptoTransferTransactionBody op; private final AccountID topLevelPayer; + /** + * Constructs the step with the operation and the top level payer account. + * @param op - the operation + * @param topLevelPayer - the top level payer account + */ public AdjustHbarChangesStep( @NonNull final CryptoTransferTransactionBody op, @NonNull final AccountID topLevelPayer) { requireNonNull(op); @@ -157,6 +165,12 @@ private void modifyAggregatedTransfers( } } + /** + * Checks if the effective payment was made by the payer already by checking the assessed custom fees. + * @param payer - the payer accountId + * @param assessedCustomFees - the assessed custom fees + * @return true if the effective payment was made, false otherwise + */ private boolean effectivePaymentWasMade( @NonNull final AccountID payer, @NonNull final List assessedCustomFees) { for (final var fee : assessedCustomFees) { diff --git a/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/handlers/transfer/AssociateTokenRecipientsStep.java b/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/handlers/transfer/AssociateTokenRecipientsStep.java index 657438f02c4c..a642499cf6ea 100644 --- a/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/handlers/transfer/AssociateTokenRecipientsStep.java +++ b/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/handlers/transfer/AssociateTokenRecipientsStep.java @@ -57,6 +57,10 @@ public class AssociateTokenRecipientsStep extends BaseTokenHandler implements TransferStep { private final CryptoTransferTransactionBody op; + /** + * Constructs the step with the operation. + * @param op the operation + */ public AssociateTokenRecipientsStep(@NonNull final CryptoTransferTransactionBody op) { this.op = requireNonNull(op); } diff --git a/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/handlers/transfer/AutoAccountCreator.java b/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/handlers/transfer/AutoAccountCreator.java index 1a5bb58026f3..1bfd508fea25 100644 --- a/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/handlers/transfer/AutoAccountCreator.java +++ b/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/handlers/transfer/AutoAccountCreator.java @@ -42,6 +42,10 @@ import com.hedera.pbj.runtime.io.buffer.Bytes; import edu.umd.cs.findbugs.annotations.NonNull; +/** + * Functionality needed for auto-creating accounts when a CryptoTransfer transaction sends hbar or tokens to an + * alias that does not yet have an account. + */ public class AutoAccountCreator { private WritableAccountStore accountStore; private HandleContext handleContext; @@ -49,6 +53,10 @@ public class AutoAccountCreator { CryptoUpdateTransactionBody.newBuilder() .key(Key.newBuilder().ecdsaSecp256k1(Bytes.EMPTY).build()); + /** + * Constructs an {@link AutoAccountCreator} with the given {@link HandleContext}. + * @param handleContext the context to use for the creation + */ public AutoAccountCreator(@NonNull final HandleContext handleContext) { this.handleContext = requireNonNull(handleContext); this.accountStore = handleContext.writableStore(WritableAccountStore.class); @@ -59,6 +67,7 @@ public AutoAccountCreator(@NonNull final HandleContext handleContext) { * * @param alias the alias to create the account for * @param maxAutoAssociations the maxAutoAssociations to set on the account + * @return the account ID of the created account */ public AccountID create(@NonNull final Bytes alias, int maxAutoAssociations) { requireNonNull(alias); diff --git a/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/handlers/transfer/CustomFeeAssessmentStep.java b/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/handlers/transfer/CustomFeeAssessmentStep.java index e5cf0a1e74bc..96cf3e5b0563 100644 --- a/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/handlers/transfer/CustomFeeAssessmentStep.java +++ b/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/handlers/transfer/CustomFeeAssessmentStep.java @@ -76,10 +76,13 @@ public class CustomFeeAssessmentStep { private final CryptoTransferTransactionBody op; private final CustomFeeAssessor customFeeAssessor; private int levelNum = 0; - private int totalBalanceChanges = 0; private static final int MAX_PLAUSIBLE_LEVEL_NUM = 10; private static final Logger log = LogManager.getLogger(CustomFeeAssessmentStep.class); + /** + * Constructs a {@link CustomFeeAssessmentStep} for the given transaction body. + * @param op the transaction body + */ public CustomFeeAssessmentStep(@NonNull final CryptoTransferTransactionBody op) { this.op = op; final var fixedFeeAssessor = new CustomFixedFeeAssessor(); diff --git a/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/handlers/transfer/EnsureAliasesStep.java b/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/handlers/transfer/EnsureAliasesStep.java index 4dc818e0119a..77321ec2e533 100644 --- a/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/handlers/transfer/EnsureAliasesStep.java +++ b/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/handlers/transfer/EnsureAliasesStep.java @@ -42,11 +42,17 @@ public class EnsureAliasesStep implements TransferStep { final CryptoTransferTransactionBody op; - // Temporary token transfer resolutions map containing the token transfers to alias, is needed to check if - // an alias is repeated. It is allowed to be repeated in multiple token transfer lists, but not in a single - // token transfer list + /** + * Temporary token transfer resolutions map containing the token transfers to alias, is needed to check if + * an alias is repeated. It is allowed to be repeated in multiple token transfer lists, but not in a single + * token transfer list + */ private final Map tokenTransferResolutions = new LinkedHashMap<>(); + /** + * Constructs a {@link EnsureAliasesStep} instance. + * @param op the crypto transfer transaction body + */ public EnsureAliasesStep(@NonNull final CryptoTransferTransactionBody op) { this.op = requireNonNull(op); } diff --git a/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/handlers/transfer/NFTOwnersChangeStep.java b/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/handlers/transfer/NFTOwnersChangeStep.java index 259991b2b069..1052d3640510 100644 --- a/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/handlers/transfer/NFTOwnersChangeStep.java +++ b/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/handlers/transfer/NFTOwnersChangeStep.java @@ -38,14 +38,20 @@ import com.hedera.node.app.service.token.impl.WritableTokenStore; import com.hedera.node.app.service.token.impl.handlers.BaseTokenHandler; import edu.umd.cs.findbugs.annotations.NonNull; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; +/** + * Handles the ownership change of NFTs in a token transfer. + */ public class NFTOwnersChangeStep extends BaseTokenHandler implements TransferStep { - private static final Logger logger = LogManager.getLogger(NFTOwnersChangeStep.class); private final CryptoTransferTransactionBody op; private final AccountID topLevelPayer; + /** + * Constructs the {@link NFTOwnersChangeStep} with the given {@link CryptoTransferTransactionBody} and payer + * {@link AccountID}. + * @param op the {@link CryptoTransferTransactionBody} + * @param topLevelPayer the payer {@link AccountID} + */ public NFTOwnersChangeStep(final CryptoTransferTransactionBody op, final AccountID topLevelPayer) { this.op = op; this.topLevelPayer = topLevelPayer; diff --git a/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/handlers/transfer/TransferContext.java b/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/handlers/transfer/TransferContext.java index 3cebf9633fac..7c54cd0d9180 100644 --- a/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/handlers/transfer/TransferContext.java +++ b/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/handlers/transfer/TransferContext.java @@ -69,9 +69,16 @@ public interface TransferContext { */ Map resolutions(); - // Throw if the fee cannot be charged for whatever reason + /** + * Charges extra fee to the HAPI payer account in the current transfer context with the given amount + * @param amount the amount to charge + */ void chargeExtraFeeToHapiPayer(long amount); + /** + * Returns the handle context of the current transfer context + * @return the handle context of the current transfer context + */ HandleContext getHandleContext(); /* ------------------- Needed for building records ------------------- */ @@ -112,8 +119,7 @@ public interface TransferContext { /** * Validates hbar allowances for the top-level operation in this transfer context. - * - *

    (FUTURE) Remove this, only needed for diff testing and has no logical priority. */ + // @Future Remove this, only needed for diff testing and has no logical priority. void validateHbarAllowances(); } diff --git a/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/handlers/transfer/TransferContextImpl.java b/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/handlers/transfer/TransferContextImpl.java index 0e3bdaa7430f..c2b74ba94774 100644 --- a/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/handlers/transfer/TransferContextImpl.java +++ b/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/handlers/transfer/TransferContextImpl.java @@ -60,10 +60,20 @@ public class TransferContextImpl implements TransferContext { private final List assessedCustomFees = new ArrayList<>(); private final boolean enforceMonoServiceRestrictionsOnAutoCreationCustomFeePayments; + /** + * Create a new {@link TransferContextImpl} instance. + * @param context The context to use. + */ public TransferContextImpl(final HandleContext context) { this(context, true); } + /** + * Create a new {@link TransferContextImpl} instance. + * @param context The context to use. + * @param enforceMonoServiceRestrictionsOnAutoCreationCustomFeePayments Whether to enforce mono service restrictions + * on auto creation custom fee payments. + */ public TransferContextImpl( final HandleContext context, final boolean enforceMonoServiceRestrictionsOnAutoCreationCustomFeePayments) { this.context = context; @@ -135,6 +145,11 @@ public int numOfLazyCreations() { return numLazyCreations; } + /** + * Check if the given alias is of EVM address size. + * @param alias The alias to check. + * @return {@code true} if the alias is of EVM address size, {@code false} otherwise. + */ public static boolean isOfEvmAddressSize(final Bytes alias) { return alias.length() == EVM_ADDRESS_SIZE; } @@ -144,6 +159,10 @@ public void addToAutomaticAssociations(TokenAssociation newAssociation) { automaticAssociations.add(newAssociation); } + /** + * Get the automatic associations. + * @return The automatic associations. + */ public List getAutomaticAssociations() { return automaticAssociations; } diff --git a/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/handlers/transfer/customfees/AdjustmentUtils.java b/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/handlers/transfer/customfees/AdjustmentUtils.java index f47b4763c483..5f852dac12aa 100644 --- a/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/handlers/transfer/customfees/AdjustmentUtils.java +++ b/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/handlers/transfer/customfees/AdjustmentUtils.java @@ -33,7 +33,13 @@ import java.util.Objects; import java.util.function.Function; +/** + * Utility class for custom fee adjustments. + */ public class AdjustmentUtils { + /** + * Factory for creating adjustments map for a given token. + */ public static final Function> ADJUSTMENTS_MAP_FACTORY = ignore -> new LinkedHashMap<>(); @@ -188,6 +194,15 @@ public static List getFungibleCredits(final AssessmentResult res return credits; } + /** + * Represents the exchanged value between accounts. It can be hbar or fungible token adjustments. + * It is used to track the credits that can be used to deduct the custom royalty fees for an NFT transfer. + * If there are no fungible units to the receiver, and if there is a fallback fee on NFT then receiver + * should pay the fallback fee + * @param account The account ID of the receiver + * @param tokenId The token ID of the fungible token + * @param amount The amount exchanged + */ public record ExchangedValue(AccountID account, TokenID tokenId, long amount) {} /** @@ -208,10 +223,23 @@ public static void adjustHbarFees(final AssessmentResult result, final AccountID } } + /** + * Adds two longs and throws an exception if the result overflows. + * @param a The first long + * @param b The second long + * @return The sum of the two longs + */ public static long addExactOrThrow(final long a, final long b) { return addExactOrThrowReason(a, b, INSUFFICIENT_SENDER_ACCOUNT_BALANCE_FOR_CUSTOM_FEE); } + /** + * Adds two longs and throws an exception if the result overflows. + * @param a The first long + * @param b The second long + * @param failureReason The reason for the failure + * @return The sum of the two longs + */ public static long addExactOrThrowReason( final long a, final long b, @NonNull final ResponseCodeEnum failureReason) { try { diff --git a/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/handlers/transfer/customfees/AssessmentResult.java b/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/handlers/transfer/customfees/AssessmentResult.java index 369af0c02658..eab7e8a3fa45 100644 --- a/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/handlers/transfer/customfees/AssessmentResult.java +++ b/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/handlers/transfer/customfees/AssessmentResult.java @@ -32,25 +32,54 @@ import java.util.stream.Collectors; import org.apache.commons.lang3.tuple.Pair; +/** + * Contains the adjustments of accounts and tokens as a result of a token transfer. This result is passed through all + * the steps of CryptoTransfer. + *

    The adjustment maps are divided into two categories: mutable and immutable (for both hbar and tokens). + * The mutable maps are used to accumulate custom fee assessed changes during the assessment process. The immutable + * maps are used to store the original changes that were passed to the assessment process from the input transaction + * body.

    + *

    The immutableInputTokenAdjustments and immutableInputHbarAdjustments are used to store the original changes that + * were passed to the assessment process from the input transaction body.

    + *

    The mutableInputBalanceAdjustments is created from immutableInputHbarAdjustments and is used to accumulate + * custom fee assessed balance changes during the assessment process.

    + *

    The htsAdjustments map is created from immutableInputTokenAdjustments and used to store the token balance + * changes that are assessed custom fees.

    + *

    The mutable maps are passed to next level by constructing a valid transaction body from the changes accumulated. + * Then the transaction body is passed to next level to assess custom fees.

    + *

    All the assessed custom fees are stored in assessedCustomFees, which is used to construct transaction record

    + */ public class AssessmentResult { - + /** + * The default token ID for representing hbar changes + */ public static TokenID HBAR_TOKEN_ID = TokenID.DEFAULT; - private Map> htsAdjustments; + + private final Map> htsAdjustments; // two maps to aggregate all custom fee balance changes. These two maps are used // to construct a transaction body that needs to be assessed again for custom fees - private Map hbarAdjustments; - private Set> royaltiesPaid; - private Map> immutableInputTokenAdjustments; + private final Map hbarAdjustments; + // In a given CryptoTransfer, we only charge royalties to an account once per token type; so + // even if 0.0.A is sending multiple NFTs of type 0.0.T in a single transfer, we only deduct + // royalty fees once from the value it receives in return. This is used to track the royalties + // that have been paid in a given CryptoTransfer. + private final Set> royaltiesPaid; + // Contains token adjustments from the transaction body. + private final Map> immutableInputTokenAdjustments; // Contains Hbar and token adjustments. Hbar adjustments are used using a sentinel tokenId key - private Map> mutableInputBalanceAdjustments; - private Map immutableInputHbarAdjustments; + private final Map> mutableInputBalanceAdjustments; + private final Map immutableInputHbarAdjustments; /* And for each "assessable change" that can be charged a custom fee, delegate to our fee assessor to update the balance changes with the custom fee. */ - private List assessedCustomFees; - - public AssessmentResult( - final List inputTokenTransfers, final List inputHbarTransfers) { - mutableInputBalanceAdjustments = buildFungibleTokenTransferMap(inputTokenTransfers); + private final List assessedCustomFees; + + /** + * Constructs an AssessmentResult object with the input token transfers and hbar transfers from the transaction body. + * @param tokenTransfers the token transfers + * @param hbarTransfers the hbar transfers + */ + public AssessmentResult(final List tokenTransfers, final List hbarTransfers) { + mutableInputBalanceAdjustments = buildFungibleTokenTransferMap(tokenTransfers); immutableInputTokenAdjustments = Collections.unmodifiableMap(mutableInputBalanceAdjustments.entrySet().stream() .collect(Collectors.toMap( Map.Entry::getKey, @@ -58,7 +87,7 @@ public AssessmentResult( (a, b) -> a, LinkedHashMap::new))); - immutableInputHbarAdjustments = buildHbarTransferMap(inputHbarTransfers); + immutableInputHbarAdjustments = buildHbarTransferMap(hbarTransfers); mutableInputBalanceAdjustments.put(HBAR_TOKEN_ID, new LinkedHashMap<>(immutableInputHbarAdjustments)); htsAdjustments = new LinkedHashMap<>(); @@ -67,46 +96,83 @@ public AssessmentResult( assessedCustomFees = new ArrayList<>(); } + /** + * Returns the immutable input token adjustments. + * @return the immutable input token adjustments + */ public Map> getImmutableInputTokenAdjustments() { return immutableInputTokenAdjustments; } + /** + * Returns the mutable input balance adjustments. + * @return the mutable input balance adjustments + */ public Map> getMutableInputBalanceAdjustments() { return mutableInputBalanceAdjustments; } + /** + * Returns the hbar adjustments. + * @return the hbar adjustments + */ public Map getHbarAdjustments() { return hbarAdjustments; } + /** + * Returns the hts adjustments. + * @return the hts adjustments + */ public Map> getHtsAdjustments() { return htsAdjustments; } + /** + * Returns the assessed custom fees. + * @return the assessed custom fees + */ public List getAssessedCustomFees() { return assessedCustomFees; } + /** + * Adds an assessed custom fee. + * @param assessedCustomFee the assessed custom fee + */ public void addAssessedCustomFee(final AssessedCustomFee assessedCustomFee) { assessedCustomFees.add(assessedCustomFee); } + /** + * Returns the royalties paid. + * @return the royalties paid + */ public Set> getRoyaltiesPaid() { return royaltiesPaid; } - public void setRoyaltiesPaid(final Set> royaltiesPaid) { - this.royaltiesPaid = royaltiesPaid; - } - + /** + * Adds a pair of account ID and token ID to the royalties paid. + * @param paid the pair of account ID and token ID + */ public void addToRoyaltiesPaid(final Pair paid) { royaltiesPaid.add(paid); } + /** + * Returns the immutable input hbar adjustments. + * @return the immutable input hbar adjustments + */ public Map getImmutableInputHbarAdjustments() { return immutableInputHbarAdjustments; } + /** + * Builds a map of fungible token transfers from the given {@link TokenTransferList}. + * @param tokenTransfers the token transfers + * @return the map of fungible token transfers + */ private Map> buildFungibleTokenTransferMap( final List tokenTransfers) { final var fungibleTransfersMap = new LinkedHashMap>(); @@ -125,6 +191,11 @@ private Map> buildFungibleTokenTransferMap( return fungibleTransfersMap; } + /** + * Builds a map of hbar transfers from the given {@link AccountAmount}. + * @param hbarTransfers the hbar transfers + * @return the map of hbar transfers + */ private Map buildHbarTransferMap(@NonNull final List hbarTransfers) { final var adjustments = new LinkedHashMap(); for (final var aa : hbarTransfers) { @@ -132,8 +203,4 @@ private Map buildHbarTransferMap(@NonNull final List customFees, @NonNull TokenType tokenType) { + /** + * Constructs a {@link CustomFeeMeta} instance for the given token id, treasury id, custom fees and token type. + * @param token the token for which this is the custom fee metadata + * @return the custom fee metadata for the given token + */ public static CustomFeeMeta customFeeMetaFrom(@NonNull final Token token) { requireNonNull(token); - return new CustomFeeMeta(token.tokenId(), token.treasuryAccountId(), token.customFees(), token.tokenType()); + return new CustomFeeMeta( + token.tokenIdOrThrow(), token.treasuryAccountId(), token.customFees(), token.tokenType()); } } diff --git a/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/handlers/transfer/customfees/CustomFixedFeeAssessor.java b/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/handlers/transfer/customfees/CustomFixedFeeAssessor.java index e081d93d28fd..f3a97c1dba23 100644 --- a/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/handlers/transfer/customfees/CustomFixedFeeAssessor.java +++ b/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/handlers/transfer/customfees/CustomFixedFeeAssessor.java @@ -35,6 +35,9 @@ */ @Singleton public class CustomFixedFeeAssessor { + /** + * Constructs a {@link CustomFixedFeeAssessor} instance. + */ @Inject public CustomFixedFeeAssessor() { // For Dagger injection diff --git a/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/handlers/transfer/customfees/CustomFractionalFeeAssessor.java b/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/handlers/transfer/customfees/CustomFractionalFeeAssessor.java index 3c1a5d0a2edc..bbeaf27991e1 100644 --- a/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/handlers/transfer/customfees/CustomFractionalFeeAssessor.java +++ b/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/handlers/transfer/customfees/CustomFractionalFeeAssessor.java @@ -50,6 +50,10 @@ public class CustomFractionalFeeAssessor { private final CustomFixedFeeAssessor fixedFeeAssessor; + /** + * Constructs a {@link CustomFractionalFeeAssessor} instance. + * @param fixedFeeAssessor the fixed fee assessor + */ @Inject public CustomFractionalFeeAssessor(CustomFixedFeeAssessor fixedFeeAssessor) { this.fixedFeeAssessor = fixedFeeAssessor; @@ -196,7 +200,7 @@ private Map filteredByExemptions( * @param fractionalFee the fractional fee * @return the amount owned to be paid as fractional custom fee */ - private long amountOwed(final long givenUnits, @NonNull final FractionalFee fractionalFee) { + public long amountOwed(final long givenUnits, @NonNull final FractionalFee fractionalFee) { final var numerator = fractionalFee.fractionalAmountOrThrow().numerator(); final var denominator = fractionalFee.fractionalAmountOrThrow().denominator(); var nominalFee = 0L; diff --git a/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/handlers/transfer/customfees/CustomRoyaltyFeeAssessor.java b/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/handlers/transfer/customfees/CustomRoyaltyFeeAssessor.java index f146e489fec6..e90ca84b300a 100644 --- a/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/handlers/transfer/customfees/CustomRoyaltyFeeAssessor.java +++ b/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/handlers/transfer/customfees/CustomRoyaltyFeeAssessor.java @@ -47,6 +47,10 @@ public class CustomRoyaltyFeeAssessor { private final CustomFixedFeeAssessor fixedFeeAssessor; + /** + * Constructs a {@link CustomRoyaltyFeeAssessor} instance. + * @param fixedFeeAssessor the fixed fee assessor + */ @Inject public CustomRoyaltyFeeAssessor(final CustomFixedFeeAssessor fixedFeeAssessor) { this.fixedFeeAssessor = fixedFeeAssessor; @@ -102,13 +106,19 @@ public void assessRoyaltyFees( } } } + // We check this outside the for loop above because a sender should only be marked as paid royalty, after + // assessing + // all the fees for the given token. If a sender is sending multiple NFTs of the same token, royalty fee + // should be paid only once. // We don't want to charge the fallback fee for each nft transfer, if the receiver has already - // paid it for this token + // paid it for this token. + if (exchangedValue.isEmpty()) { // Receiver pays fallback fees result.addToRoyaltiesPaid(Pair.of(receiver, tokenId)); } else { - // Sender effectively pays percent royalties + // Sender effectively pays percent royalties. Here we don't check isPayerExempt because + // the sender could be exempt for one fee on token, but not other fees(if any) on the same token. result.addToRoyaltiesPaid(Pair.of(sender, tokenId)); } } diff --git a/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/schemas/InitialModServiceTokenSchema.java b/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/schemas/InitialModServiceTokenSchema.java index 2f975638aba5..718fddde82f1 100644 --- a/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/schemas/InitialModServiceTokenSchema.java +++ b/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/schemas/InitialModServiceTokenSchema.java @@ -71,9 +71,6 @@ import com.hedera.node.app.spi.state.MigrationContext; import com.hedera.node.app.spi.state.Schema; import com.hedera.node.app.spi.state.StateDefinition; -import com.hedera.node.app.spi.state.WritableKVState; -import com.hedera.node.app.spi.state.WritableKVStateBase; -import com.hedera.node.app.spi.state.WritableSingletonStateBase; import com.hedera.node.config.data.AccountsConfig; import com.hedera.node.config.data.HederaConfig; import com.hedera.node.config.data.LedgerConfig; @@ -82,6 +79,9 @@ import com.swirlds.common.threading.manager.AdHocThreadManager; import com.swirlds.config.api.Configuration; import com.swirlds.merkle.map.MerkleMap; +import com.swirlds.platform.state.spi.WritableKVStateBase; +import com.swirlds.platform.state.spi.WritableSingletonStateBase; +import com.swirlds.state.spi.WritableKVState; import com.swirlds.virtualmap.VirtualMap; import edu.umd.cs.findbugs.annotations.NonNull; import edu.umd.cs.findbugs.annotations.Nullable; @@ -138,6 +138,12 @@ public class InitialModServiceTokenSchema extends Schema { * SyntheticRecordsGenerator} for more details). Even though these sorted sets contain account * objects, these account objects may or may not yet exist in state. They're usually not needed, * but are required for an event recovery situation. + * @param sysAccts a supplier of synthetic system account records + * @param stakingAccts a supplier of synthetic staking account records + * @param treasuryAccts a supplier of synthetic treasury account records + * @param miscAccts a supplier of synthetic miscellaneous account records + * @param blocklistAccts a supplier of synthetic account records that are to be blocked + * @param version the semantic version of the software */ public InitialModServiceTokenSchema( final Supplier> sysAccts, @@ -169,22 +175,43 @@ public Set statesToCreate() { StateDefinition.singleton(STAKING_NETWORK_REWARDS_KEY, NetworkStakingRewards.PROTOBUF)); } + /** + * Sets the in-state NFTs to be migrated from. + * @param fs the in-state NFTs + */ public void setNftsFromState(@Nullable final VirtualMap fs) { this.nftsFs = fs; } + /** + * Sets the in-state token rels to be migrated from. + * @param fs the in-state token rels + */ public void setTokenRelsFromState(@Nullable final VirtualMap fs) { this.trFs = fs; } + /** + * Sets the in-state accounts to be migrated from. + * @param fs the in-state accounts + */ public void setAcctsFromState(@Nullable final VirtualMap fs) { this.acctsFs = fs; } + /** + * Sets the in-state tokens to be migrated from. + * @param fs the in-state tokens + */ public void setTokensFromState(@Nullable final MerkleMap fs) { this.tFs = fs; } + /** + * Sets the in-state staking info to be migrated from. + * @param stakingFs the in-state staking info + * @param mnc the in-state network context + */ public void setStakingFs( @Nullable final MerkleMap stakingFs, @Nullable final MerkleNetworkContext mnc) { @@ -598,6 +625,11 @@ public long getTotalBalanceOfAllAccounts( return totalBalance; } + /** + * Get the entity numbers of all system entities that are not contracts. + * @param numReservedSystemEntities The number of reserved system entities + * @return The entity numbers of all system entities that are not contracts + */ @VisibleForTesting public static long[] nonContractSystemNums(final long numReservedSystemEntities) { return LongStream.rangeClosed(FIRST_POST_SYSTEM_FILE_ENTITY, numReservedSystemEntities) diff --git a/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/schemas/SyntheticRecordsGenerator.java b/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/schemas/SyntheticRecordsGenerator.java index d1676fb18374..25206f4feaf6 100644 --- a/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/schemas/SyntheticRecordsGenerator.java +++ b/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/schemas/SyntheticRecordsGenerator.java @@ -39,6 +39,9 @@ import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; +/** + * This class generates synthetic records for all reserved system accounts + */ public class SyntheticRecordsGenerator { private static final Logger log = LogManager.getLogger(SyntheticRecordsGenerator.class); private static final long FIRST_RESERVED_SYSTEM_CONTRACT = 350L; @@ -59,22 +62,42 @@ public SyntheticRecordsGenerator() { blocklistParser = new BlocklistParser(); } + /** + * Returns the synthetic records for system accounts + * @return the set of accounts for which records are generated + */ public SortedSet sysAcctRecords() { return systemAcctRcds; } + /** + * Returns the synthetic records for staking accounts + * @return the set of accounts for which records are generated + */ public SortedSet stakingAcctRecords() { return stakingAcctRcds; } + /** + * Returns the synthetic records for treasury accounts + * @return the set of accounts for which records are generated + */ public SortedSet treasuryAcctRecords() { return treasuryAcctRcds; } + /** + * Returns the synthetic records for multi-use accounts + * @return the set of accounts for which records are generated + */ public SortedSet multiUseAcctRecords() { return multiUseAcctRcds; } + /** + * Returns the synthetic records for blocklist accounts + * @return the set of accounts for which records are generated + */ public SortedSet blocklistAcctRecords() { return blocklistAcctRcds; } @@ -218,6 +241,11 @@ static AccountID asAccountId(final long acctNum, final HederaConfig hederaConfig .build(); } + /** + * Returns an array of account numbers that are not reserved for system contracts. + * @param numReservedSystemEntities the number of reserved system entities + * @return an array of account numbers that are not reserved for system contracts + */ @VisibleForTesting public static long[] nonContractSystemNums(final long numReservedSystemEntities) { return LongStream.rangeClosed(FIRST_POST_SYSTEM_FILE_ENTITY, numReservedSystemEntities) diff --git a/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/serdes/EntityNumCodec.java b/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/serdes/EntityNumCodec.java index d16d2b9fd4b6..5f54a6aca24b 100644 --- a/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/serdes/EntityNumCodec.java +++ b/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/serdes/EntityNumCodec.java @@ -26,6 +26,9 @@ import edu.umd.cs.findbugs.annotations.NonNull; import java.io.IOException; +/** + * Codec for {@link EntityNum}. + */ public class EntityNumCodec implements Codec { @NonNull @Override diff --git a/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/util/TokenHandlerHelper.java b/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/util/TokenHandlerHelper.java index 0e1134b506d8..59bba789acb1 100644 --- a/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/util/TokenHandlerHelper.java +++ b/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/util/TokenHandlerHelper.java @@ -65,7 +65,7 @@ import edu.umd.cs.findbugs.annotations.Nullable; /** - * Class for retrieving objects in a certain context, e.g. during a {@code handler.handle(...)} call. + * Class for retrieving objects in a certain context. For example, during a {@code handler.handle(...)} call. * This allows compartmentalizing common validation logic without requiring store implementations to * throw inappropriately-contextual exceptions, and also abstracts duplicated business logic out of * multiple handlers. @@ -76,8 +76,17 @@ private TokenHandlerHelper() { throw new UnsupportedOperationException("Utility class only"); } + /** + * Enum to determine the type of account ID, aliased or not aliased + */ public enum AccountIDType { + /** + * Account ID is aliased + */ ALIASED_ID, + /** + * Account ID is not aliased + */ NOT_ALIASED_ID } @@ -91,6 +100,7 @@ public enum AccountIDType { * @param expiryValidator the {@link ExpiryValidator} to determine if the account is expired * @param errorIfNotUsable the {@link ResponseCodeEnum} to use if the account is not found/usable * @throws HandleException if any of the account conditions are not met + * @return the account if it exists and is usable */ @NonNull public static Account getIfUsable( @@ -117,6 +127,7 @@ public static Account getIfUsable( * @param expiryValidator the {@link ExpiryValidator} to determine if the account is expired * @param errorIfNotUsable the {@link ResponseCodeEnum} to use if the account is not found/usable * @throws HandleException if any of the account conditions are not met + * @return the account if it exists and is usable */ @NonNull public static Account getIfUsableForAliasedId( @@ -136,7 +147,9 @@ public static Account getIfUsableForAliasedId( * @param accountId the ID of the account to get * @param accountStore the {@link ReadableTokenStore} to use for account retrieval * @param expiryValidator the {@link ExpiryValidator} to determine if the account is expired + * @param errorIfNotUsable the {@link ResponseCodeEnum} to use if the account is not found/usable * @throws HandleException if any of the account conditions are not met + * @return the account if it exists and is usable */ @NonNull public static Account getIfUsableForAutoRenew( @@ -161,7 +174,9 @@ public static Account getIfUsableForAutoRenew( * @param accountId the ID of the account to get * @param accountStore the {@link ReadableTokenStore} to use for account retrieval * @param expiryValidator the {@link ExpiryValidator} to determine if the account is expired + * @param errorIfNotUsable the {@link ResponseCodeEnum} to use if the account is not found/usable * @throws HandleException if any of the account conditions are not met + * @return the account if it exists and is usable */ @NonNull public static Account getIfUsableWithTreasury( @@ -178,6 +193,16 @@ public static Account getIfUsableWithTreasury( AccountIDType.NOT_ALIASED_ID); } + /** + * Returns the account if it exists and is usable. A {@link HandleException} is thrown if the account is invalid. + * @param accountId the ID of the account to get + * @param accountStore the {@link ReadableTokenStore} to use for account retrieval + * @param expiryValidator the {@link ExpiryValidator} to determine if the account is expired + * @param errorIfNotUsable the {@link ResponseCodeEnum} to use if the account is not found/usable + * @param errorOnAccountDeleted the {@link ResponseCodeEnum} to use if the account is deleted + * @param accountIDType the type of account ID + * @return the account if it exists and is usable + */ @NonNull public static Account getIfUsable( @NonNull final AccountID accountId, @@ -211,11 +236,27 @@ public static Account getIfUsable( return acct; } + /** + * Enum to determine the type of validations to be performed on the token. If the token is allowed to be paused + * or not + */ public enum TokenValidations { + /** + * Token should not be paused + */ REQUIRE_NOT_PAUSED, + /** + * Token can be paused + */ PERMIT_PAUSED } + /** + * Returns the token if it exists and is usable. A {@link HandleException} is thrown if the token is invalid + * @param tokenId the ID of the token to get + * @param tokenStore the {@link ReadableTokenStore} to use for token retrieval + * @return the token if it exists and is usable + */ public static Token getIfUsable(@NonNull final TokenID tokenId, @NonNull final ReadableTokenStore tokenStore) { return getIfUsable(tokenId, tokenStore, REQUIRE_NOT_PAUSED); } @@ -227,6 +268,7 @@ public static Token getIfUsable(@NonNull final TokenID tokenId, @NonNull final R * @param tokenStore the {@link ReadableTokenStore} to use for token retrieval * @param tokenValidations whether validate paused token status * @throws HandleException if any of the token conditions are not met + * @return the token if it exists and is usable */ @NonNull public static Token getIfUsable( @@ -253,6 +295,7 @@ public static Token getIfUsable( * @param tokenId the ID of the token * @param tokenRelStore the {@link ReadableTokenRelationStore} to use for token relation retrieval * @throws HandleException if any of the token relation conditions are not met + * @return the token relation if it exists and is usable */ @NonNull public static TokenRelation getIfUsable( @@ -271,6 +314,12 @@ public static TokenRelation getIfUsable( return tokenRel; } + /** + * Returns the token relation if it exists and is usable + * @param key the key to check + * @param responseCode the response code to throw if the key is empty + * @throws PreCheckException if the key is empty + */ public static void verifyNotEmptyKey(@Nullable final Key key, @NonNull final ResponseCodeEnum responseCode) throws PreCheckException { if (EMPTY_KEY_LIST.equals(key)) { diff --git a/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/util/TokenKey.java b/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/util/TokenKey.java new file mode 100644 index 000000000000..aaf7ae05f81e --- /dev/null +++ b/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/util/TokenKey.java @@ -0,0 +1,445 @@ +/* + * Copyright (C) 2024 Hedera Hashgraph, LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.hedera.node.app.service.token.impl.util; + +import static com.hedera.hapi.node.base.ResponseCodeEnum.INVALID_ADMIN_KEY; +import static com.hedera.hapi.node.base.ResponseCodeEnum.INVALID_CUSTOM_FEE_SCHEDULE_KEY; +import static com.hedera.hapi.node.base.ResponseCodeEnum.INVALID_FREEZE_KEY; +import static com.hedera.hapi.node.base.ResponseCodeEnum.INVALID_KYC_KEY; +import static com.hedera.hapi.node.base.ResponseCodeEnum.INVALID_METADATA_KEY; +import static com.hedera.hapi.node.base.ResponseCodeEnum.INVALID_PAUSE_KEY; +import static com.hedera.hapi.node.base.ResponseCodeEnum.INVALID_SUPPLY_KEY; +import static com.hedera.hapi.node.base.ResponseCodeEnum.INVALID_WIPE_KEY; +import static com.hedera.hapi.node.base.ResponseCodeEnum.TOKEN_HAS_NO_ADMIN_KEY; +import static com.hedera.hapi.node.base.ResponseCodeEnum.TOKEN_HAS_NO_FEE_SCHEDULE_KEY; +import static com.hedera.hapi.node.base.ResponseCodeEnum.TOKEN_HAS_NO_FREEZE_KEY; +import static com.hedera.hapi.node.base.ResponseCodeEnum.TOKEN_HAS_NO_KYC_KEY; +import static com.hedera.hapi.node.base.ResponseCodeEnum.TOKEN_HAS_NO_METADATA_KEY; +import static com.hedera.hapi.node.base.ResponseCodeEnum.TOKEN_HAS_NO_PAUSE_KEY; +import static com.hedera.hapi.node.base.ResponseCodeEnum.TOKEN_HAS_NO_SUPPLY_KEY; +import static com.hedera.hapi.node.base.ResponseCodeEnum.TOKEN_HAS_NO_WIPE_KEY; +import static com.hedera.node.app.spi.validation.AttributeValidator.isKeyRemoval; +import static com.hedera.node.app.spi.workflows.HandleException.validateTrue; + +import com.hedera.hapi.node.base.Key; +import com.hedera.hapi.node.base.ResponseCodeEnum; +import com.hedera.hapi.node.state.token.Token; +import com.hedera.hapi.node.token.TokenUpdateTransactionBody; +import edu.umd.cs.findbugs.annotations.NonNull; +import edu.umd.cs.findbugs.annotations.Nullable; + +/** + * All the keys that can be updated on a Token. This provides a way to update token keys. + */ +public enum TokenKey { + /** + * The admin key. + */ + ADMIN_KEY { + @Override + public boolean isPresentInUpdate(TokenUpdateTransactionBody update) { + return update.hasAdminKey(); + } + + @Override + public boolean isPresentInitially(Token originalToken) { + return originalToken.hasAdminKey(); + } + + @Override + public void setOn(final Token.Builder builder, TokenUpdateTransactionBody update) { + builder.adminKey(getNewKeyValue(update.adminKey())); + } + + @Override + public Key getFromUpdate(TokenUpdateTransactionBody update) { + return update.adminKey(); + } + + @Override + public Key getFromToken(Token originalToken) { + return originalToken.adminKey(); + } + + @Override + public ResponseCodeEnum tokenHasNoKeyStatus() { + return TOKEN_HAS_NO_ADMIN_KEY; + } + + @Override + public ResponseCodeEnum invalidKeyStatus() { + return INVALID_ADMIN_KEY; + } + }, + /** + * The wipe key. + */ + WIPE_KEY { + @Override + public boolean isPresentInUpdate(TokenUpdateTransactionBody update) { + return update.hasWipeKey(); + } + + @Override + public boolean isPresentInitially(Token originalToken) { + return originalToken.hasWipeKey(); + } + + @Override + public void setOn(final Token.Builder builder, TokenUpdateTransactionBody update) { + builder.wipeKey(getNewKeyValue(update.wipeKey())); + } + + @Override + public Key getFromUpdate(TokenUpdateTransactionBody update) { + return update.wipeKey(); + } + + @Override + public Key getFromToken(Token originalToken) { + return originalToken.wipeKey(); + } + + @Override + public ResponseCodeEnum tokenHasNoKeyStatus() { + return TOKEN_HAS_NO_WIPE_KEY; + } + + @Override + public ResponseCodeEnum invalidKeyStatus() { + return INVALID_WIPE_KEY; + } + }, + /** + * The KYC key. + */ + KYC_KEY { + @Override + public boolean isPresentInUpdate(TokenUpdateTransactionBody update) { + return update.hasKycKey(); + } + + @Override + public boolean isPresentInitially(Token originalToken) { + return originalToken.hasKycKey(); + } + + @Override + public void setOn(final Token.Builder builder, TokenUpdateTransactionBody update) { + builder.kycKey(getNewKeyValue(update.kycKey())); + } + + @Override + public Key getFromUpdate(TokenUpdateTransactionBody update) { + return update.kycKey(); + } + + @Override + public Key getFromToken(Token originalToken) { + return originalToken.kycKey(); + } + + @Override + public ResponseCodeEnum tokenHasNoKeyStatus() { + return TOKEN_HAS_NO_KYC_KEY; + } + + @Override + public ResponseCodeEnum invalidKeyStatus() { + return INVALID_KYC_KEY; + } + }, + /** + * The supply key. + */ + SUPPLY_KEY { + @Override + public boolean isPresentInUpdate(TokenUpdateTransactionBody update) { + return update.hasSupplyKey(); + } + + @Override + public boolean isPresentInitially(Token originalToken) { + return originalToken.hasSupplyKey(); + } + + @Override + public void setOn(final Token.Builder builder, TokenUpdateTransactionBody update) { + builder.supplyKey(getNewKeyValue(update.supplyKey())); + } + + @Override + public Key getFromUpdate(TokenUpdateTransactionBody update) { + return update.supplyKey(); + } + + @Override + public Key getFromToken(Token originalToken) { + return originalToken.supplyKey(); + } + + @Override + public ResponseCodeEnum tokenHasNoKeyStatus() { + return TOKEN_HAS_NO_SUPPLY_KEY; + } + + @Override + public ResponseCodeEnum invalidKeyStatus() { + return INVALID_SUPPLY_KEY; + } + }, + /** + * The freeze key. + */ + FREEZE_KEY { + @Override + public boolean isPresentInUpdate(TokenUpdateTransactionBody update) { + return update.hasFreezeKey(); + } + + @Override + public boolean isPresentInitially(Token originalToken) { + return originalToken.hasFreezeKey(); + } + + @Override + public void setOn(final Token.Builder builder, TokenUpdateTransactionBody update) { + builder.freezeKey(getNewKeyValue(update.freezeKey())); + } + + @Override + public Key getFromUpdate(TokenUpdateTransactionBody update) { + return update.freezeKey(); + } + + @Override + public Key getFromToken(Token originalToken) { + return originalToken.freezeKey(); + } + + @Override + public ResponseCodeEnum tokenHasNoKeyStatus() { + return TOKEN_HAS_NO_FREEZE_KEY; + } + + @Override + public ResponseCodeEnum invalidKeyStatus() { + return INVALID_FREEZE_KEY; + } + }, + /** + * The fee schedule key. + */ + FEE_SCHEDULE_KEY { + @Override + public boolean isPresentInUpdate(TokenUpdateTransactionBody update) { + return update.hasFeeScheduleKey(); + } + + @Override + public boolean isPresentInitially(Token originalToken) { + return originalToken.hasFeeScheduleKey(); + } + + @Override + public void setOn(final Token.Builder builder, TokenUpdateTransactionBody update) { + builder.feeScheduleKey(getNewKeyValue(update.feeScheduleKey())); + } + + @Override + public Key getFromUpdate(TokenUpdateTransactionBody update) { + return update.feeScheduleKey(); + } + + @Override + public Key getFromToken(Token originalToken) { + return originalToken.feeScheduleKey(); + } + + @Override + public ResponseCodeEnum tokenHasNoKeyStatus() { + return TOKEN_HAS_NO_FEE_SCHEDULE_KEY; + } + + @Override + public ResponseCodeEnum invalidKeyStatus() { + return INVALID_CUSTOM_FEE_SCHEDULE_KEY; + } + }, + /** + * The pause key. + */ + PAUSE_KEY { + @Override + public boolean isPresentInUpdate(TokenUpdateTransactionBody update) { + return update.hasPauseKey(); + } + + @Override + public boolean isPresentInitially(Token originalToken) { + return originalToken.hasPauseKey(); + } + + @Override + public void setOn(final Token.Builder builder, TokenUpdateTransactionBody update) { + builder.pauseKey(getNewKeyValue(update.pauseKey())); + } + + @Override + public Key getFromUpdate(TokenUpdateTransactionBody update) { + return update.pauseKey(); + } + + @Override + public Key getFromToken(Token originalToken) { + return originalToken.pauseKey(); + } + + @Override + public ResponseCodeEnum tokenHasNoKeyStatus() { + return TOKEN_HAS_NO_PAUSE_KEY; + } + + @Override + public ResponseCodeEnum invalidKeyStatus() { + return INVALID_PAUSE_KEY; + } + }, + /** + * The metadata key. + */ + METADATA_KEY { + @Override + public ResponseCodeEnum invalidKeyStatus() { + return INVALID_METADATA_KEY; + } + + @Override + public boolean isPresentInUpdate(TokenUpdateTransactionBody update) { + return update.hasMetadataKey(); + } + + @Override + public boolean isPresentInitially(Token originalToken) { + return originalToken.hasMetadataKey(); + } + + @Override + public void setOn(final Token.Builder builder, TokenUpdateTransactionBody update) { + builder.metadataKey(getNewKeyValue(update.metadataKey())); + } + + @Override + public Key getFromUpdate(TokenUpdateTransactionBody update) { + return update.metadataKey(); + } + + @Override + public Key getFromToken(Token originalToken) { + return originalToken.metadataKey(); + } + + @Override + public ResponseCodeEnum tokenHasNoKeyStatus() { + return TOKEN_HAS_NO_METADATA_KEY; + } + }; + + /** + * Check if the key is present in the update transaction body. + * @param update the update transaction body + * @return true if the key is present in the update transaction body + */ + public abstract boolean isPresentInUpdate(TokenUpdateTransactionBody update); + + /** + * Check if the key is present in the original token. + * @param originalToken the original token + * @return true if the key is present in the original token and false otherwise + */ + public abstract boolean isPresentInitially(Token originalToken); + + /** + * Set the key on the token builder. + * @param builder the token builder + * @param update the update transaction body + */ + public abstract void setOn(final Token.Builder builder, TokenUpdateTransactionBody update); + + /** + * Get the key from the update transaction body. + * @param update the update transaction body + * @return the key or null if the key is not present + */ + public abstract Key getFromUpdate(TokenUpdateTransactionBody update); + + /** + * Get the key from the original token. + * @param originalToken the original token + * @return the key or null if the key is not present + */ + public abstract @Nullable Key getFromToken(Token originalToken); + + /** + * Get the status code to be returned when the token has no key, based on the key type. + * @return the status code + */ + public abstract ResponseCodeEnum tokenHasNoKeyStatus(); + + /** + * Get the status code to be returned when the key is invalid, based on the key type. + * @return the status code + */ + public abstract ResponseCodeEnum invalidKeyStatus(); + + /** + * Check if the transaction body has immutable sentinel key used for key removals. + * @param update the update transaction body + * @return true if the transaction body has the sentinel key and false otherwise + */ + public boolean containsKeyRemoval(@NonNull final TokenUpdateTransactionBody update) { + if (isPresentInUpdate(update)) { + return isKeyRemoval(getFromUpdate(update)); + } + return false; + } + + /** + * Update the key on the token builder. + * @param update the update transaction body + * @param originalToken the original token + * @param builder the token builder + */ + public void updateKey( + @NonNull final TokenUpdateTransactionBody update, + @NonNull final Token originalToken, + @NonNull final Token.Builder builder) { + if (isPresentInUpdate(update)) { + validateTrue(isPresentInitially(originalToken), tokenHasNoKeyStatus()); + setOn(builder, update); + } + } + + /** + * Get the new key value. If the key is a sentinel key used for key removals, return null. Otherwise, return the key. + * @param newKey the new key + * @return the new key value + */ + private static Key getNewKeyValue(Key newKey) { + return isKeyRemoval(newKey) ? null : newKey; + } +} diff --git a/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/util/TokenRelListCalculator.java b/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/util/TokenRelListCalculator.java index 3411cbc81bca..a6686dfb1d02 100644 --- a/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/util/TokenRelListCalculator.java +++ b/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/util/TokenRelListCalculator.java @@ -44,6 +44,10 @@ public class TokenRelListCalculator { private final ReadableTokenRelationStore tokenRelStore; + /** + * Constructs a new {@link TokenRelListCalculator} object + * @param tokenRelStore the token relation store to use for fetching token relations + */ public TokenRelListCalculator(@NonNull final ReadableTokenRelationStore tokenRelStore) { this.tokenRelStore = requireNonNull(tokenRelStore); } diff --git a/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/validators/AllowanceValidator.java b/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/validators/AllowanceValidator.java index 04fdb2e19db9..01aeb040cdef 100644 --- a/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/validators/AllowanceValidator.java +++ b/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/validators/AllowanceValidator.java @@ -39,17 +39,37 @@ import java.util.List; import javax.inject.Inject; +/** + * Provides validation for allowances. + */ public class AllowanceValidator { - + /** + * Default constructor for Dagger injection. + */ @Inject public AllowanceValidator() { // Dagger } + /** + * Validates the total number of allowances in a transaction. The total number of allowances + * should not exceed the maximum limit. This limit includes the total number of crypto + * allowances, total number of token allowances and total number of approvedForAll Nft allowances + * on owner account. + * @param totalAllowances total number of allowances in the transaction + * @param hederaConfig the Hedera configuration + */ protected void validateTotalAllowancesPerTxn(final int totalAllowances, @NonNull final HederaConfig hederaConfig) { validateFalse(totalAllowances > hederaConfig.allowancesMaxTransactionLimit(), MAX_ALLOWANCES_EXCEEDED); } + /** + * Validates the serial numbers in the NftAllowance. The serial numbers should be valid and + * should exist in the NftStore. + * @param serialNums list of serial numbers + * @param tokenId token id + * @param nftStore nft store + */ protected void validateSerialNums( final List serialNums, final TokenID tokenId, final ReadableNftStore nftStore) { final var serialsSet = new HashSet<>(serialNums); @@ -127,6 +147,7 @@ public static boolean isValidOwner( * @param owner given owner * @param payer given payer for the transaction * @param accountStore account store + * @param expiryValidator expiry validator * @return owner account */ public static Account getEffectiveOwner( diff --git a/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/validators/ApproveAllowanceValidator.java b/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/validators/ApproveAllowanceValidator.java index 51d45ad72e24..d548e2c8d425 100644 --- a/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/validators/ApproveAllowanceValidator.java +++ b/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/validators/ApproveAllowanceValidator.java @@ -57,12 +57,22 @@ */ @Singleton public class ApproveAllowanceValidator extends AllowanceValidator { - + /** + * Default constructor for Dagger injection. + */ @Inject public ApproveAllowanceValidator() { // Dagger } + /** + * Validates the {@link CryptoApproveAllowanceTransactionBody} transaction. + * It validates the total number of allowances has not exceeded limit, all crypto allowances, fungible token + * allowances and nft allowances. + * @param context handle context + * @param payerAccount payer account for the approveAllowance txn + * @param accountStore readable account store + */ public void validate( @NonNull final HandleContext context, final Account payerAccount, final ReadableAccountStore accountStore) { // create stores and config from context @@ -118,6 +128,16 @@ void validateCryptoAllowances( } } + /** + * Validates the FungibleTokenAllowances given in {@link CryptoApproveAllowanceTransactionBody}. + * It validates the token exists, owner exists, spender exists, token amount is valid and token is not NFT. + * @param tokenAllowances token allowances + * @param payer payer account for approveAllowance txn + * @param accountStore account store + * @param tokenStore token store + * @param tokenRelStore token relation store + * @param expiryValidator expiry validator + */ private void validateFungibleTokenAllowances( final List tokenAllowances, @NonNull final Account payer, @@ -209,6 +229,15 @@ private void validateNftAllowances( } } + /** + * Validates the total number of allowances in the transaction. The total number of allowances should not exceed + * the configured maximum. The total number of allowances includes the number of crypto allowances, the number of + * fungible token allowances and the number of approvedForAll Nft allowances. + * @param cryptoAllowances crypto allowances + * @param tokenAllowances token allowances + * @param nftAllowances nft allowances + * @param hederaConfig hedera config + */ private void validateAllowanceCount( @NonNull final List cryptoAllowances, @NonNull final List tokenAllowances, @@ -221,6 +250,13 @@ private void validateAllowanceCount( validateTotalAllowancesPerTxn(totalAllowances, hederaConfig); } + /** + * Validates some of the token's basic fields like token type and token relation exists. + * @param owner owner account + * @param spender spender account + * @param token token + * @param tokenRelStore token relation store + */ private void validateTokenBasics( final Account owner, final AccountID spender, @@ -249,12 +285,21 @@ private void validateSpender(final long amount, @Nullable final Account spenderA amount == 0 || (spenderAccount != null && !spenderAccount.deleted()), INVALID_ALLOWANCE_SPENDER_ID); } + /** + * Validates the NFT serial numbers and the spender account. + * @param serialNumbers list of serial numbers + * @param spenderAccount spender account + */ private void validateNFTSpender(final List serialNumbers, final Account spenderAccount) { validateTrue( serialNumbers.isEmpty() || (spenderAccount != null && !spenderAccount.deleted()), INVALID_ALLOWANCE_SPENDER_ID); } + /** + * Validates the spender account for NFT allowances. + * @param spenderAccount spender account + */ private void validateNFTSpender(final Account spenderAccount) { validateTrue((spenderAccount != null && !spenderAccount.deleted()), INVALID_ALLOWANCE_SPENDER_ID); } diff --git a/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/validators/CryptoCreateValidator.java b/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/validators/CryptoCreateValidator.java index 7994d2ef3ec6..668e4fe2e07b 100644 --- a/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/validators/CryptoCreateValidator.java +++ b/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/validators/CryptoCreateValidator.java @@ -34,13 +34,17 @@ import javax.inject.Singleton; /** - * Provides validation for token fields like token type, token supply type, token symbol etc.,. + * Provides validation for token fields like token type, token supply type, token symbol etc., * It is used in pureChecks for token creation. */ @Singleton public class CryptoCreateValidator { + /** + * Default constructor for injection. + */ @Inject - public CryptoCreateValidator() { // Exists for injection + public CryptoCreateValidator() { + // Exists for injection } /** @@ -82,11 +86,12 @@ public void validateKey( } } - /** check if the number of auto associations is too many + /** Check if the number of auto associations is too many. * @param n number to check * @param ledgerConfig LedgerConfig * @param entitiesConfig EntitiesConfig * @param tokensConfig TokensConfig + * @return true the given number is greater than the max number of auto associations */ public boolean tooManyAutoAssociations( final int n, diff --git a/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/validators/CryptoTransferValidator.java b/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/validators/CryptoTransferValidator.java index 98c7a8df7eb9..34d2379ad700 100644 --- a/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/validators/CryptoTransferValidator.java +++ b/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/validators/CryptoTransferValidator.java @@ -54,9 +54,14 @@ import javax.inject.Singleton; import org.apache.commons.lang3.tuple.Pair; +/** + * A validator for the crypto transfer transaction. + */ @Singleton public class CryptoTransferValidator { - + /** + * Default constructor for injection. + */ @Inject public CryptoTransferValidator() { // For Dagger injection diff --git a/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/validators/CustomFeesValidator.java b/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/validators/CustomFeesValidator.java index d591ffbff562..3ec90fcccd2c 100644 --- a/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/validators/CustomFeesValidator.java +++ b/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/validators/CustomFeesValidator.java @@ -50,10 +50,15 @@ * to validate custom fees for token creation and fee schedule updates. */ public class CustomFeesValidator { - // Sentinel token id used to denote that the fee is denominated in the same token as the token being created. + /** + * Sentinel token id used to denote that the fee is denominated in the same token as the token being created. + */ public static final TokenID SENTINEL_TOKEN_ID = TokenID.newBuilder().shardNum(0L).realmNum(0L).tokenNum(0L).build(); + /** + * Constructs a {@link CustomFeesValidator} instance. + */ @Inject public CustomFeesValidator() { // Needed for Dagger injection diff --git a/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/validators/DeleteAllowanceValidator.java b/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/validators/DeleteAllowanceValidator.java index ac6e2edbfac7..cb20f2019df9 100644 --- a/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/validators/DeleteAllowanceValidator.java +++ b/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/validators/DeleteAllowanceValidator.java @@ -44,17 +44,23 @@ import javax.inject.Inject; import javax.inject.Singleton; +/** + * Validator for {@link CryptoDeleteAllowanceTransactionBody} + */ @Singleton public class DeleteAllowanceValidator extends AllowanceValidator { - + /** + * Constructs a {@link DeleteAllowanceValidator} instance. + */ @Inject public DeleteAllowanceValidator() { - // Dagger + // Dagger Injection } /** * Validates all allowances provided in {@link CryptoDeleteAllowanceTransactionBody} * + * @param handleContext handle context * @param nftAllowances given nft serials allowances to remove * @param payerAccount payer for the transaction * @param accountStore account store diff --git a/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/validators/StakingValidator.java b/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/validators/StakingValidator.java index 8354172a3134..2248bf22c032 100644 --- a/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/validators/StakingValidator.java +++ b/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/validators/StakingValidator.java @@ -35,6 +35,9 @@ */ @Singleton public class StakingValidator { + /** + * Default constructor for injection. + */ @Inject public StakingValidator() { // Dagger2 @@ -50,6 +53,7 @@ public StakingValidator() { * @param stakedAccountIdInOp staked account id * @param stakedNodeIdInOp staked node id * @param accountStore readable account store + * @param networkInfo network info */ public void validateStakedIdForCreation( final boolean isStakingEnabled, @@ -83,6 +87,7 @@ public void validateStakedIdForCreation( * @param stakedAccountIdInOp staked account id * @param stakedNodeIdInOp staked node id * @param accountStore readable account store + * @param networkInfo network info */ public void validateStakedIdForUpdate( final boolean isStakingEnabled, diff --git a/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/validators/TokenAttributesValidator.java b/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/validators/TokenAttributesValidator.java index 1fe58647ac01..464d3797ef2f 100644 --- a/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/validators/TokenAttributesValidator.java +++ b/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/validators/TokenAttributesValidator.java @@ -51,7 +51,9 @@ */ @Singleton public class TokenAttributesValidator { - + /** + * Default constructor for injection. + */ @Inject public TokenAttributesValidator() { // Dagger @@ -60,6 +62,7 @@ public TokenAttributesValidator() { /** * Validates the token symbol, if it exists and is not empty or not too long. * @param symbol the token symbol to validate + * @param tokensConfig the tokens configuration */ public void validateTokenSymbol(@Nullable final String symbol, @NonNull final TokensConfig tokensConfig) { tokenStringCheck(symbol, tokensConfig.maxSymbolUtf8Bytes(), MISSING_TOKEN_SYMBOL, TOKEN_SYMBOL_TOO_LONG); @@ -68,6 +71,7 @@ public void validateTokenSymbol(@Nullable final String symbol, @NonNull final To /** * Validates the token name, if it is exists and is not empty or not too long. * @param name the token name to validate + * @param tokensConfig the tokens configuration */ public void validateTokenName(@Nullable final String name, @NonNull final TokensConfig tokensConfig) { tokenStringCheck(name, tokensConfig.maxTokenNameUtf8Bytes(), MISSING_TOKEN_NAME, TOKEN_NAME_TOO_LONG); @@ -76,6 +80,7 @@ public void validateTokenName(@Nullable final String name, @NonNull final Tokens /** * Validates the token metadata, if it exists and is not too long. * @param metadata the token metadata to validate + * @param tokensConfig the tokens configuration */ public void validateTokenMetadata(@NonNull final Bytes metadata, @NonNull final TokensConfig tokensConfig) { if (metadata.length() > 0) { @@ -122,6 +127,8 @@ private void tokenStringCheck( * @param feeScheduleKey the token fee schedule key to validate * @param hasPauseKey whether the token has a pause key * @param pauseKey the token pause key to validate + * @param hasMetadataKey whether the token has a metadata key + * @param metadataKey the token metadata key to validate */ public void validateTokenKeys( final boolean hasAdminKey, @@ -143,25 +150,25 @@ public void validateTokenKeys( if (hasAdminKey && !isKeyRemoval(adminKey)) { validateTrue(isValid(adminKey), INVALID_ADMIN_KEY); } - if (hasKycKey) { + if (hasKycKey && !isKeyRemoval(kycKey)) { validateTrue(isValid(kycKey), INVALID_KYC_KEY); } - if (hasWipeKey) { + if (hasWipeKey && !isKeyRemoval(wipeKey)) { validateTrue(isValid(wipeKey), INVALID_WIPE_KEY); } - if (hasSupplyKey) { + if (hasSupplyKey && !isKeyRemoval(supplyKey)) { validateTrue(isValid(supplyKey), INVALID_SUPPLY_KEY); } - if (hasFreezeKey) { + if (hasFreezeKey && !isKeyRemoval(freezeKey)) { validateTrue(isValid(freezeKey), INVALID_FREEZE_KEY); } - if (hasFeeScheduleKey) { + if (hasFeeScheduleKey && !isKeyRemoval(feeScheduleKey)) { validateTrue(isValid(feeScheduleKey), INVALID_CUSTOM_FEE_SCHEDULE_KEY); } - if (hasPauseKey) { + if (hasPauseKey && !isKeyRemoval(pauseKey)) { validateTrue(isValid(pauseKey), INVALID_PAUSE_KEY); } - if (hasMetadataKey) { + if (hasMetadataKey && !isKeyRemoval(metadataKey)) { validateTrue(isValid(metadataKey), INVALID_METADATA_KEY); } } diff --git a/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/validators/TokenCreateValidator.java b/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/validators/TokenCreateValidator.java index 0a947267728b..2d0b07ce4151 100644 --- a/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/validators/TokenCreateValidator.java +++ b/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/validators/TokenCreateValidator.java @@ -60,6 +60,10 @@ public class TokenCreateValidator { private final TokenAttributesValidator tokenAttributesValidator; + /** + * Default constructor for injection + * @param tokenAttributesValidator token attributes validator + */ @Inject public TokenCreateValidator(@NonNull final TokenAttributesValidator tokenAttributesValidator) { this.tokenAttributesValidator = tokenAttributesValidator; diff --git a/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/validators/TokenListChecks.java b/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/validators/TokenListChecks.java index 617bd1fc98a0..9c9f9ecca026 100644 --- a/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/validators/TokenListChecks.java +++ b/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/validators/TokenListChecks.java @@ -37,6 +37,7 @@ private TokenListChecks() { * * @param tokens the list of {@link TokenID}s to check * @throws NullPointerException if {@code tokens} is {@code null} + * @return {@code true} if the list contains no duplicates, {@code false} otherwise */ public static boolean repeatsItself(@NonNull final List tokens) { requireNonNull(tokens); diff --git a/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/validators/TokenSupplyChangeOpsValidator.java b/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/validators/TokenSupplyChangeOpsValidator.java index 40a267ce7d5d..485769ce164b 100644 --- a/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/validators/TokenSupplyChangeOpsValidator.java +++ b/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/validators/TokenSupplyChangeOpsValidator.java @@ -41,7 +41,9 @@ * Token Burn operations in handle */ public class TokenSupplyChangeOpsValidator { - + /** + * Default constructor for injection. + */ @Inject public TokenSupplyChangeOpsValidator() { // Dagger @@ -52,6 +54,7 @@ public TokenSupplyChangeOpsValidator() { * * @param fungibleCount the number of fungible tokens to mint * @param metaDataList the list of metadata for the NFTs to mint + * @param tokensConfig the tokens configuration * @throws HandleException if the transaction data is invalid */ public void validateMint( @@ -70,6 +73,7 @@ public void validateMint( * * @param fungibleCount the number of fungible tokens to burn * @param nftSerialNums the list of NFT serial numbers to burn + * @param tokensConfig the tokens configuration * @throws HandleException if the transaction data is invalid */ public void validateBurn( @@ -119,6 +123,7 @@ public static void verifyTokenInstanceAmounts( * * @param fungibleCount the number of fungible tokens to wipe * @param nftSerialNums the list of NFT serial numbers to wipe + * @param tokensConfig the tokens configuration * @throws HandleException if the transaction data is invalid */ public void validateWipe( diff --git a/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/validators/TokenUpdateValidator.java b/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/validators/TokenUpdateValidator.java index 6af29d1defc0..60d65693438f 100644 --- a/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/validators/TokenUpdateValidator.java +++ b/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/validators/TokenUpdateValidator.java @@ -17,14 +17,8 @@ package com.hedera.node.app.service.token.impl.validators; import static com.hedera.hapi.node.base.ResponseCodeEnum.INVALID_AUTORENEW_ACCOUNT; -import static com.hedera.hapi.node.base.ResponseCodeEnum.TOKEN_IS_IMMUTABLE; -import static com.hedera.node.app.service.token.impl.handlers.BaseTokenHandler.isExpiryOnlyUpdateOp; -import static com.hedera.node.app.service.token.impl.handlers.BaseTokenHandler.isMetadataOnlyUpdateOp; import static com.hedera.node.app.service.token.impl.util.TokenHandlerHelper.getIfUsable; -import static com.hedera.node.app.spi.key.KeyUtils.isEmpty; import static com.hedera.node.app.spi.validation.ExpiryMeta.NA; -import static com.hedera.node.app.spi.workflows.HandleException.validateFalse; -import static com.hedera.node.app.spi.workflows.HandleException.validateTrue; import com.hedera.hapi.node.base.AccountID; import com.hedera.hapi.node.state.token.Token; @@ -38,60 +32,53 @@ import edu.umd.cs.findbugs.annotations.NonNull; import javax.inject.Inject; +/** + * Validator for token update transactions. + */ public class TokenUpdateValidator { private final TokenAttributesValidator validator; + /** + * Create a new {@link TokenUpdateValidator} instance. + * @param validator The {@link TokenAttributesValidator} to use. + */ @Inject public TokenUpdateValidator(@NonNull final TokenAttributesValidator validator) { this.validator = validator; } + /** + * Validate the semantics of a token update transaction. + * @param token The token to update. + * @param resolvedExpiryMeta The resolved expiry metadata. + */ public record ValidationResult(@NonNull Token token, @NonNull ExpiryMeta resolvedExpiryMeta) {} + /** + * Validate the semantics of a token update transaction. + * @param context The context to use. + * @param op The token update transaction body. + * @return The result of the validation. + */ @NonNull public ValidationResult validateSemantics( @NonNull final HandleContext context, @NonNull final TokenUpdateTransactionBody op) { final var readableAccountStore = context.readableStore(ReadableAccountStore.class); final var tokenStore = context.readableStore(ReadableTokenStore.class); - final var tokenId = op.tokenOrThrow(); - final var token = getIfUsable(tokenId, tokenStore); + final var token = getIfUsable(op.tokenOrThrow(), tokenStore); final var tokensConfig = context.configuration().getConfigData(TokensConfig.class); - // If the token has an empty admin key it can't be updated for any other fields other than metadata - // For updating only metadata the transaction should have admin key or metadata key - if (isMetadataOnlyUpdateOp(op)) { - validateTrue(token.hasAdminKey() || token.hasMetadataKey(), TOKEN_IS_IMMUTABLE); - } else if (!isExpiryOnlyUpdateOp(op)) { - validateFalse(isEmpty(token.adminKey()), TOKEN_IS_IMMUTABLE); - } - // validate memo if (op.hasMemo()) { context.attributeValidator().validateMemo(op.memo()); } - // validate metadata if (op.hasMetadata()) { validator.validateTokenMetadata(op.metadataOrThrow(), tokensConfig); } - // validate token symbol, if being changed - if (op.symbol() != null && !op.symbol().isEmpty()) { + if (!op.symbol().isEmpty()) { validator.validateTokenSymbol(op.symbol(), tokensConfig); } - // validate token name, if being changed - if (op.name() != null && !op.name().isEmpty()) { + if (!op.name().isEmpty()) { validator.validateTokenName(op.name(), tokensConfig); } - // validate token keys, if any being changed - validator.validateTokenKeys( - op.hasAdminKey(), op.adminKey(), - op.hasKycKey(), op.kycKey(), - op.hasWipeKey(), op.wipeKey(), - op.hasSupplyKey(), op.supplyKey(), - op.hasFreezeKey(), op.freezeKey(), - op.hasFeeScheduleKey(), op.feeScheduleKey(), - op.hasPauseKey(), op.pauseKey(), - op.hasMetadataKey(), op.metadataKey()); - - // Check whether there is change on the following properties in the transaction body - // If no change occurred, no need to change them or validate them if (!(op.hasExpiry() || op.hasAutoRenewPeriod() || op.hasAutoRenewAccount())) { return new ValidationResult( token, diff --git a/hedera-node/hedera-token-service-impl/src/main/java/module-info.java b/hedera-node/hedera-token-service-impl/src/main/java/module-info.java index 7f610dbc4e19..fee3aa6a60a0 100644 --- a/hedera-node/hedera-token-service-impl/src/main/java/module-info.java +++ b/hedera-node/hedera-token-service-impl/src/main/java/module-info.java @@ -1,3 +1,6 @@ +/** + * Module that provides the implementation of the Hedera Token Service. + */ module com.hedera.node.app.service.token.impl { requires transitive com.hedera.node.app.hapi.fees; requires transitive com.hedera.node.app.service.mono; @@ -8,14 +11,16 @@ requires transitive com.hedera.pbj.runtime; requires transitive com.swirlds.config.api; requires transitive com.swirlds.merkle; + requires transitive com.swirlds.state.api; requires transitive com.swirlds.virtualmap; requires transitive dagger; requires transitive javax.inject; requires com.hedera.node.app.hapi.utils; - requires com.hedera.node.app.service.evm; requires com.google.common; + requires com.hedera.evm; requires com.swirlds.base; requires com.swirlds.common; + requires com.swirlds.platform.core; requires org.apache.commons.lang3; requires org.apache.logging.log4j; requires org.bouncycastle.provider; diff --git a/hedera-node/hedera-token-service-impl/src/test/java/com/hedera/node/app/service/token/impl/TokenServiceImplTest.java b/hedera-node/hedera-token-service-impl/src/test/java/com/hedera/node/app/service/token/impl/TokenServiceImplTest.java new file mode 100644 index 000000000000..d2da9358a6b6 --- /dev/null +++ b/hedera-node/hedera-token-service-impl/src/test/java/com/hedera/node/app/service/token/impl/TokenServiceImplTest.java @@ -0,0 +1,187 @@ +/* + * Copyright (C) 2024 Hedera Hashgraph, LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.hedera.node.app.service.token.impl; + +import static com.hedera.node.app.service.token.impl.comparator.TokenComparators.ACCOUNT_COMPARATOR; +import static org.mockito.ArgumentMatchers.notNull; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; + +import com.hedera.hapi.node.base.SemanticVersion; +import com.hedera.hapi.node.state.token.Account; +import com.hedera.node.app.service.mono.state.merkle.MerkleNetworkContext; +import com.hedera.node.app.service.token.CryptoServiceDefinition; +import com.hedera.node.app.service.token.TokenServiceDefinition; +import com.hedera.node.app.spi.state.SchemaRegistry; +import com.swirlds.merkle.map.MerkleMap; +import com.swirlds.virtualmap.VirtualMap; +import java.util.Collections; +import java.util.Set; +import java.util.SortedSet; +import java.util.TreeSet; +import org.assertj.core.api.Assertions; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +class TokenServiceImplTest { + + private TokenServiceImpl subject; + + @BeforeEach + void setUp() { + subject = new TokenServiceImpl(); + } + + @SuppressWarnings("DataFlowIssue") + @Test + void constructorNullArgsThrow() { + Assertions.assertThatThrownBy(() -> new TokenServiceImpl( + null, + Collections::emptySortedSet, + Collections::emptySortedSet, + Collections::emptySortedSet, + Collections::emptySortedSet)) + .isInstanceOf(NullPointerException.class); + + Assertions.assertThatThrownBy(() -> new TokenServiceImpl( + Collections::emptySortedSet, + null, + Collections::emptySortedSet, + Collections::emptySortedSet, + Collections::emptySortedSet)) + .isInstanceOf(NullPointerException.class); + + Assertions.assertThatThrownBy(() -> new TokenServiceImpl( + Collections::emptySortedSet, + Collections::emptySortedSet, + null, + Collections::emptySortedSet, + Collections::emptySortedSet)) + .isInstanceOf(NullPointerException.class); + + Assertions.assertThatThrownBy(() -> new TokenServiceImpl( + Collections::emptySortedSet, + Collections::emptySortedSet, + Collections::emptySortedSet, + null, + Collections::emptySortedSet)) + .isInstanceOf(NullPointerException.class); + + Assertions.assertThatThrownBy(() -> new TokenServiceImpl( + Collections::emptySortedSet, + Collections::emptySortedSet, + Collections::emptySortedSet, + Collections::emptySortedSet, + null)) + .isInstanceOf(NullPointerException.class); + } + + @Test + void defaultConstructor() { + Assertions.assertThat(new TokenServiceImpl()).isNotNull(); + } + + @Test + void argConstructor() { + final SortedSet anyNonNullAccts = new TreeSet<>(ACCOUNT_COMPARATOR); + final var acc1 = mock(Account.class); + final var acc2 = mock(Account.class); + final var acc3 = mock(Account.class); + anyNonNullAccts.addAll(Set.of(acc1, acc2, acc3)); + + Assertions.assertThat(new TokenServiceImpl( + () -> anyNonNullAccts, + () -> anyNonNullAccts, + () -> anyNonNullAccts, + () -> anyNonNullAccts, + () -> anyNonNullAccts)) + .isNotNull(); + } + + @SuppressWarnings("DataFlowIssue") + @Test + void registerSchemasNullArgsThrow() { + Assertions.assertThatThrownBy(() -> subject.registerSchemas(null, SemanticVersion.DEFAULT)) + .isInstanceOf(NullPointerException.class); + + Assertions.assertThatThrownBy(() -> subject.registerSchemas(mock(SchemaRegistry.class), null)) + .isInstanceOf(NullPointerException.class); + } + + @Test + void registerSchemasRegistersTokenSchema() { + final var schemaRegistry = mock(SchemaRegistry.class); + + subject.registerSchemas(schemaRegistry, SemanticVersion.DEFAULT); + verify(schemaRegistry).register(notNull()); + } + + @SuppressWarnings("unchecked") + @Test + void triesToSetStateWithoutRegisteredTokenSchema() { + final var vMap = mock(VirtualMap.class); + final var mMap = mock(MerkleMap.class); + + Assertions.assertThatThrownBy(() -> subject.setNftsFromState(vMap)).isInstanceOf(NullPointerException.class); + Assertions.assertThatThrownBy(() -> subject.setTokenRelsFromState(vMap)) + .isInstanceOf(NullPointerException.class); + Assertions.assertThatThrownBy(() -> subject.setAcctsFromState(vMap)).isInstanceOf(NullPointerException.class); + Assertions.assertThatThrownBy(() -> subject.setTokensFromState(mMap)).isInstanceOf(NullPointerException.class); + Assertions.assertThatThrownBy(() -> subject.setStakingFs(mMap, mock(MerkleNetworkContext.class))) + .isInstanceOf(NullPointerException.class); + } + + @SuppressWarnings("unchecked") + @Test + void stateSettersDontThrow() { + final var registry = mock(SchemaRegistry.class); + // registerSchemas(...) is required to instantiate the token schema + subject.registerSchemas(registry, SemanticVersion.DEFAULT); + + final var vmap = mock(VirtualMap.class); + final var mMap = mock(MerkleMap.class); + final var netCtx = mock(MerkleNetworkContext.class); + + subject.setNftsFromState(null); + subject.setNftsFromState(vmap); + + subject.setTokenRelsFromState(null); + subject.setTokenRelsFromState(vmap); + + subject.setAcctsFromState(null); + subject.setAcctsFromState(vmap); + + subject.setTokensFromState(null); + subject.setTokensFromState(mMap); + + subject.setStakingFs(null, null); + subject.setStakingFs(mMap, null); + subject.setStakingFs(null, netCtx); + subject.setStakingFs(mMap, netCtx); + } + + @Test + void verifyServiceName() { + Assertions.assertThat(subject.getServiceName()).isEqualTo("TokenService"); + } + + @Test + void rpcDefinitions() { + Assertions.assertThat(subject.rpcDefinitions()) + .containsExactlyInAnyOrder(CryptoServiceDefinition.INSTANCE, TokenServiceDefinition.INSTANCE); + } +} diff --git a/hedera-node/hedera-token-service-impl/src/test/java/com/hedera/node/app/service/token/impl/handlers/transfer/customfees/CustomFeeExemptionsTest.java b/hedera-node/hedera-token-service-impl/src/test/java/com/hedera/node/app/service/token/impl/handlers/transfer/customfees/CustomFeeExemptionsTest.java new file mode 100644 index 000000000000..8743d5d146de --- /dev/null +++ b/hedera-node/hedera-token-service-impl/src/test/java/com/hedera/node/app/service/token/impl/handlers/transfer/customfees/CustomFeeExemptionsTest.java @@ -0,0 +1,181 @@ +/* + * Copyright (C) 2024 Hedera Hashgraph, LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.hedera.node.app.service.token.impl.handlers.transfer.customfees; + +import static com.hedera.node.app.service.token.impl.handlers.BaseCryptoHandler.asAccount; +import static org.assertj.core.api.Assertions.assertThat; + +import com.hedera.hapi.node.base.AccountID; +import com.hedera.hapi.node.base.TokenID; +import com.hedera.hapi.node.base.TokenType; +import com.hedera.hapi.node.state.token.Token; +import com.hedera.hapi.node.transaction.CustomFee; +import com.hedera.node.app.service.token.impl.test.handlers.util.TokenHandlerTestBase; +import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationTargetException; +import java.util.List; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +class CustomFeeExemptionsTest extends TokenHandlerTestBase { + + private Token treasuryToken; + private Token nonTreasuryToken; + private final TokenID tokenId = TokenID.newBuilder().tokenNum(1000).build(); + private final AccountID feeCollector = asAccount(7001); + private final AccountID minter = asAccount(2001); + private final AccountID sender = asAccount(4001); + private final CustomFee customFee = CustomFee.newBuilder() + .fixedFee(fixedFee) + .feeCollectorAccountId(feeCollector) + .fractionalFee(fractionalFee) + .royaltyFee(royaltyFee) + .build(); + + private final CustomFee customFeeCollectorSender = CustomFee.newBuilder() + .fixedFee(fixedFee) + .feeCollectorAccountId(sender) + .fractionalFee(fractionalFee) + .royaltyFee(royaltyFee) + .build(); + + @BeforeEach + void setUp() { + treasuryToken = Token.newBuilder() + .tokenId(tokenId) + .treasuryAccountId(sender) + .customFees(customFee) + .tokenType(TokenType.FUNGIBLE_COMMON) + .build(); + nonTreasuryToken = Token.newBuilder() + .tokenId(tokenId) + .treasuryAccountId(feeCollector) + .customFees(customFee) + .tokenType(TokenType.FUNGIBLE_COMMON) + .build(); + } + + @Test + void testCoverageOfPrivateConstructor() + throws NoSuchMethodException, InstantiationException, IllegalAccessException { + final Constructor constructor = CustomFeeExemptions.class.getDeclaredConstructor(); + constructor.setAccessible(true); + try { + constructor.newInstance(); + } catch (InvocationTargetException e) { + Throwable cause = e.getCause(); + assertThat(cause.getClass()).isEqualTo(UnsupportedOperationException.class); + } + } + + @Test + void testPayerExemptWhenPayerIsTokenTreasury() { + CustomFeeMeta customFeeMetaFromTreasury = CustomFeeMeta.customFeeMetaFrom(treasuryToken); + assertThat(CustomFeeExemptions.isPayerExempt(customFeeMetaFromTreasury, customFee, sender)) + .isTrue(); + } + + @Test + void testPayerExemptWhenPayerIsTheFeeCollector() { + CustomFeeMeta customFeeMeta = CustomFeeMeta.customFeeMetaFrom(nonTreasuryToken); + assertThat(CustomFeeExemptions.isPayerExempt(customFeeMeta, customFeeCollectorSender, sender)) + .isTrue(); + } + + @Test + void testSomePayersExemptWhenPayerCollectorForAnyFee() { + final var customFee1 = CustomFee.newBuilder() + .feeCollectorAccountId(feeCollector) + .fixedFee(fixedFee) + .build(); + final var customFee2 = CustomFee.newBuilder() + .feeCollectorAccountId(sender) + .fixedFee(fixedFee) + .build(); + final var customFeeMeta = + new CustomFeeMeta(tokenId, minter, List.of(customFee1, customFee2), TokenType.FUNGIBLE_COMMON); + assertThat(CustomFeeExemptions.isPayerExempt(customFeeMeta, customFeeCollectorSender, sender)) + .isTrue(); + } + + @Test + void testAllPayersExemptWhenPayerCollectorForAnyFee() { + final var customFee1 = CustomFee.newBuilder() + .allCollectorsAreExempt(true) + .feeCollectorAccountId(sender) + .fixedFee(fixedFee) + .build(); + final var customFee2 = + CustomFee.newBuilder().allCollectorsAreExempt(true).build(); + + final var customFeeCollector = CustomFee.newBuilder() + .feeCollectorAccountId(feeCollector) + .allCollectorsAreExempt(true) + .fixedFee(fixedFee) + .build(); + final var customFeeMeta = + new CustomFeeMeta(tokenId, minter, List.of(customFee1, customFee2), TokenType.FUNGIBLE_COMMON); + assertThat(CustomFeeExemptions.isPayerExempt(customFeeMeta, customFeeCollector, sender)) + .isTrue(); + } + + @Test + void testAllPayersExemptButPayerNotCollectorForAnyFee() { + final var customFee = + CustomFee.newBuilder().allCollectorsAreExempt(true).build(); + final var customFeeCollector = CustomFee.newBuilder() + .feeCollectorAccountId(feeCollector) + .allCollectorsAreExempt(true) + .fixedFee(fixedFee) + .build(); + final var customFeeMeta = new CustomFeeMeta(tokenId, minter, List.of(customFee), TokenType.FUNGIBLE_COMMON); + assertThat(CustomFeeExemptions.isPayerExempt(customFeeMeta, customFeeCollector, sender)) + .isFalse(); + } + + @Test + void testPayerNotExemptWhenPayerIsNotCollectorForAnyFee() { + final var customFeeCollector = CustomFee.newBuilder() + .feeCollectorAccountId(feeCollector) + .fixedFee(fixedFee) + .build(); + final var customFeeMeta = + new CustomFeeMeta(tokenId, minter, List.of(customFeeCollector), TokenType.FUNGIBLE_COMMON); + assertThat(CustomFeeExemptions.isPayerExempt(customFeeMeta, customFeeCollector, sender)) + .isFalse(); + } + + @Test + void testAllCollectorsExemptAndPayerNotCollectorForAnyTokenFee() { + final var customFee1 = CustomFee.newBuilder() + .allCollectorsAreExempt(true) + .feeCollectorAccountId(minter) + .fixedFee(fixedFee) + .build(); + final var customFee2 = + CustomFee.newBuilder().allCollectorsAreExempt(true).build(); + + final var customFeeCollector = CustomFee.newBuilder() + .feeCollectorAccountId(feeCollector) + .fixedFee(fixedFee) + .build(); + final var customFeeMeta = + new CustomFeeMeta(tokenId, minter, List.of(customFee1, customFee2), TokenType.FUNGIBLE_COMMON); + assertThat(CustomFeeExemptions.isPayerExempt(customFeeMeta, customFeeCollector, sender)) + .isFalse(); + } +} diff --git a/hedera-node/hedera-token-service-impl/src/test/java/com/hedera/node/app/service/token/impl/test/ReadableAccountStoreImplTest.java b/hedera-node/hedera-token-service-impl/src/test/java/com/hedera/node/app/service/token/impl/test/ReadableAccountStoreImplTest.java index acc26a813e8d..b86e4b8e38d0 100644 --- a/hedera-node/hedera-token-service-impl/src/test/java/com/hedera/node/app/service/token/impl/test/ReadableAccountStoreImplTest.java +++ b/hedera-node/hedera-token-service-impl/src/test/java/com/hedera/node/app/service/token/impl/test/ReadableAccountStoreImplTest.java @@ -18,6 +18,8 @@ import static com.hedera.node.app.service.mono.utils.Units.HBARS_TO_TINYBARS; import static com.hedera.node.app.service.token.impl.handlers.BaseCryptoHandler.asAccount; +import static com.hedera.node.app.service.token.impl.test.handlers.util.StateBuilderUtil.ACCOUNTS; +import static com.hedera.node.app.service.token.impl.test.handlers.util.StateBuilderUtil.ALIASES; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.mockito.BDDMockito.given; @@ -30,8 +32,8 @@ import com.hedera.node.app.service.evm.utils.EthSigsUtils; import com.hedera.node.app.service.token.impl.ReadableAccountStoreImpl; import com.hedera.node.app.service.token.impl.test.handlers.util.CryptoHandlerTestBase; -import com.hedera.node.app.spi.state.ReadableKVState; import com.hedera.pbj.runtime.io.buffer.Bytes; +import com.swirlds.state.spi.ReadableKVState; import org.assertj.core.api.Assertions; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; diff --git a/hedera-node/hedera-token-service-impl/src/test/java/com/hedera/node/app/service/token/impl/test/ReadableNetworkStakingRewardsStoreImplTest.java b/hedera-node/hedera-token-service-impl/src/test/java/com/hedera/node/app/service/token/impl/test/ReadableNetworkStakingRewardsStoreImplTest.java index fcb893295ff1..c38f8811e9bd 100644 --- a/hedera-node/hedera-token-service-impl/src/test/java/com/hedera/node/app/service/token/impl/test/ReadableNetworkStakingRewardsStoreImplTest.java +++ b/hedera-node/hedera-token-service-impl/src/test/java/com/hedera/node/app/service/token/impl/test/ReadableNetworkStakingRewardsStoreImplTest.java @@ -23,8 +23,8 @@ import com.hedera.hapi.node.state.token.NetworkStakingRewards; import com.hedera.node.app.service.token.impl.ReadableNetworkStakingRewardsStoreImpl; -import com.hedera.node.app.spi.state.ReadableSingletonState; -import com.hedera.node.app.spi.state.ReadableStates; +import com.swirlds.state.spi.ReadableSingletonState; +import com.swirlds.state.spi.ReadableStates; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; diff --git a/hedera-node/hedera-token-service-impl/src/test/java/com/hedera/node/app/service/token/impl/test/ReadableStakingInfoStoreImplTest.java b/hedera-node/hedera-token-service-impl/src/test/java/com/hedera/node/app/service/token/impl/test/ReadableStakingInfoStoreImplTest.java index 65706d0b8be1..ac95c266b424 100644 --- a/hedera-node/hedera-token-service-impl/src/test/java/com/hedera/node/app/service/token/impl/test/ReadableStakingInfoStoreImplTest.java +++ b/hedera-node/hedera-token-service-impl/src/test/java/com/hedera/node/app/service/token/impl/test/ReadableStakingInfoStoreImplTest.java @@ -24,8 +24,8 @@ import com.hedera.hapi.node.state.common.EntityNumber; import com.hedera.hapi.node.state.token.StakingNodeInfo; import com.hedera.node.app.service.token.impl.ReadableStakingInfoStoreImpl; -import com.hedera.node.app.spi.fixtures.state.MapReadableKVState; -import com.hedera.node.app.spi.state.ReadableStates; +import com.swirlds.platform.test.fixtures.state.MapReadableKVState; +import com.swirlds.state.spi.ReadableStates; import org.assertj.core.api.Assertions; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; diff --git a/hedera-node/hedera-token-service-impl/src/test/java/com/hedera/node/app/service/token/impl/test/ReadableTokenRelationStoreImplTest.java b/hedera-node/hedera-token-service-impl/src/test/java/com/hedera/node/app/service/token/impl/test/ReadableTokenRelationStoreImplTest.java index 3e4c0c159124..987574e95d0c 100644 --- a/hedera-node/hedera-token-service-impl/src/test/java/com/hedera/node/app/service/token/impl/test/ReadableTokenRelationStoreImplTest.java +++ b/hedera-node/hedera-token-service-impl/src/test/java/com/hedera/node/app/service/token/impl/test/ReadableTokenRelationStoreImplTest.java @@ -27,8 +27,8 @@ import com.hedera.hapi.node.state.token.TokenRelation; import com.hedera.node.app.service.token.impl.ReadableTokenRelationStoreImpl; import com.hedera.node.app.service.token.impl.TokenServiceImpl; -import com.hedera.node.app.spi.state.ReadableKVState; -import com.hedera.node.app.spi.state.ReadableStates; +import com.swirlds.state.spi.ReadableKVState; +import com.swirlds.state.spi.ReadableStates; import org.assertj.core.api.Assertions; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; diff --git a/hedera-node/hedera-token-service-impl/src/test/java/com/hedera/node/app/service/token/impl/test/ReadableTokenStoreImplTest.java b/hedera-node/hedera-token-service-impl/src/test/java/com/hedera/node/app/service/token/impl/test/ReadableTokenStoreImplTest.java index fff5e0a5409d..cf20c01ac13a 100644 --- a/hedera-node/hedera-token-service-impl/src/test/java/com/hedera/node/app/service/token/impl/test/ReadableTokenStoreImplTest.java +++ b/hedera-node/hedera-token-service-impl/src/test/java/com/hedera/node/app/service/token/impl/test/ReadableTokenStoreImplTest.java @@ -34,9 +34,9 @@ import com.hedera.node.app.service.mono.state.submerkle.FixedFeeSpec; import com.hedera.node.app.service.token.impl.ReadableTokenStoreImpl; import com.hedera.node.app.service.token.impl.test.handlers.util.TokenHandlerTestBase; -import com.hedera.node.app.spi.state.ReadableKVState; -import com.hedera.node.app.spi.state.ReadableStates; import com.hedera.node.app.spi.workflows.PreCheckException; +import com.swirlds.state.spi.ReadableKVState; +import com.swirlds.state.spi.ReadableStates; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; diff --git a/hedera-node/hedera-token-service-impl/src/test/java/com/hedera/node/app/service/token/impl/test/WritableNetworkStakingRewardsStoreImplTest.java b/hedera-node/hedera-token-service-impl/src/test/java/com/hedera/node/app/service/token/impl/test/WritableNetworkStakingRewardsStoreImplTest.java index 06123231a95a..312ca62fe80c 100644 --- a/hedera-node/hedera-token-service-impl/src/test/java/com/hedera/node/app/service/token/impl/test/WritableNetworkStakingRewardsStoreImplTest.java +++ b/hedera-node/hedera-token-service-impl/src/test/java/com/hedera/node/app/service/token/impl/test/WritableNetworkStakingRewardsStoreImplTest.java @@ -22,9 +22,9 @@ import com.hedera.hapi.node.state.token.NetworkStakingRewards; import com.hedera.node.app.service.token.impl.WritableNetworkStakingRewardsStore; -import com.hedera.node.app.spi.state.WritableSingletonState; -import com.hedera.node.app.spi.state.WritableSingletonStateBase; -import com.hedera.node.app.spi.state.WritableStates; +import com.swirlds.platform.state.spi.WritableSingletonStateBase; +import com.swirlds.state.spi.WritableSingletonState; +import com.swirlds.state.spi.WritableStates; import java.util.concurrent.atomic.AtomicReference; import org.assertj.core.api.Assertions; import org.junit.jupiter.api.BeforeEach; diff --git a/hedera-node/hedera-token-service-impl/src/test/java/com/hedera/node/app/service/token/impl/test/WritableNftStoreTest.java b/hedera-node/hedera-token-service-impl/src/test/java/com/hedera/node/app/service/token/impl/test/WritableNftStoreTest.java index be42de653a22..a15c682113b6 100644 --- a/hedera-node/hedera-token-service-impl/src/test/java/com/hedera/node/app/service/token/impl/test/WritableNftStoreTest.java +++ b/hedera-node/hedera-token-service-impl/src/test/java/com/hedera/node/app/service/token/impl/test/WritableNftStoreTest.java @@ -33,7 +33,7 @@ import com.hedera.node.app.service.token.impl.WritableNftStore; import com.hedera.node.app.service.token.impl.test.handlers.util.CryptoTokenHandlerTestBase; import com.hedera.node.app.spi.metrics.StoreMetricsService; -import com.hedera.node.app.spi.state.WritableKVState; +import com.swirlds.state.spi.WritableKVState; import java.util.Collections; import java.util.Set; import org.junit.jupiter.api.BeforeEach; diff --git a/hedera-node/hedera-token-service-impl/src/test/java/com/hedera/node/app/service/token/impl/test/WritableStakingInfoStoreImplTest.java b/hedera-node/hedera-token-service-impl/src/test/java/com/hedera/node/app/service/token/impl/test/WritableStakingInfoStoreImplTest.java index 241314120dbe..d848fe97e72e 100644 --- a/hedera-node/hedera-token-service-impl/src/test/java/com/hedera/node/app/service/token/impl/test/WritableStakingInfoStoreImplTest.java +++ b/hedera-node/hedera-token-service-impl/src/test/java/com/hedera/node/app/service/token/impl/test/WritableStakingInfoStoreImplTest.java @@ -22,15 +22,21 @@ import com.hedera.hapi.node.state.token.StakingNodeInfo; import com.hedera.node.app.service.token.impl.TokenServiceImpl; import com.hedera.node.app.service.token.impl.WritableStakingInfoStore; -import com.hedera.node.app.spi.fixtures.state.MapWritableKVState; import com.hedera.node.app.spi.fixtures.state.MapWritableStates; -import com.hedera.node.app.spi.state.WritableStates; +import com.swirlds.platform.test.fixtures.state.MapWritableKVState; +import com.swirlds.state.spi.WritableStates; import java.util.Map; import org.assertj.core.api.Assertions; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +/** + * Unit tests for {@link WritableStakingInfoStore}. + */ public class WritableStakingInfoStoreImplTest { + /** + * Node ID 1. + */ public static final EntityNumber NODE_ID_1 = EntityNumber.newBuilder().number(1L).build(); diff --git a/hedera-node/hedera-token-service-impl/src/test/java/com/hedera/node/app/service/token/impl/test/WritableTokenRelationStoreTest.java b/hedera-node/hedera-token-service-impl/src/test/java/com/hedera/node/app/service/token/impl/test/WritableTokenRelationStoreTest.java index 83b80459c140..427dfedff01a 100644 --- a/hedera-node/hedera-token-service-impl/src/test/java/com/hedera/node/app/service/token/impl/test/WritableTokenRelationStoreTest.java +++ b/hedera-node/hedera-token-service-impl/src/test/java/com/hedera/node/app/service/token/impl/test/WritableTokenRelationStoreTest.java @@ -31,10 +31,10 @@ import com.hedera.node.app.service.token.impl.TokenServiceImpl; import com.hedera.node.app.service.token.impl.WritableTokenRelationStore; import com.hedera.node.app.spi.metrics.StoreMetricsService; -import com.hedera.node.app.spi.state.WritableKVStateBase; -import com.hedera.node.app.spi.state.WritableStates; import com.hedera.node.config.testfixtures.HederaTestConfigBuilder; import com.swirlds.config.api.Configuration; +import com.swirlds.platform.state.spi.WritableKVStateBase; +import com.swirlds.state.spi.WritableStates; import java.util.Set; import org.assertj.core.api.Assertions; import org.junit.jupiter.api.BeforeEach; diff --git a/hedera-node/hedera-token-service-impl/src/test/java/com/hedera/node/app/service/token/impl/test/api/TokenServiceApiImplTest.java b/hedera-node/hedera-token-service-impl/src/test/java/com/hedera/node/app/service/token/impl/test/api/TokenServiceApiImplTest.java index 0972018db9a3..5a4e745b7635 100644 --- a/hedera-node/hedera-token-service-impl/src/test/java/com/hedera/node/app/service/token/impl/test/api/TokenServiceApiImplTest.java +++ b/hedera-node/hedera-token-service-impl/src/test/java/com/hedera/node/app/service/token/impl/test/api/TokenServiceApiImplTest.java @@ -41,17 +41,17 @@ import com.hedera.node.app.service.token.impl.api.TokenServiceApiImpl; import com.hedera.node.app.service.token.impl.validators.StakingValidator; import com.hedera.node.app.spi.fees.Fees; -import com.hedera.node.app.spi.fixtures.state.MapWritableKVState; import com.hedera.node.app.spi.fixtures.state.MapWritableStates; import com.hedera.node.app.spi.info.NetworkInfo; import com.hedera.node.app.spi.metrics.StoreMetricsService; -import com.hedera.node.app.spi.state.WritableKVState; -import com.hedera.node.app.spi.state.WritableKVStateBase; -import com.hedera.node.app.spi.state.WritableStates; import com.hedera.node.config.testfixtures.HederaTestConfigBuilder; import com.hedera.pbj.runtime.io.buffer.Bytes; import com.swirlds.config.api.Configuration; import com.swirlds.config.extensions.test.fixtures.TestConfigBuilder; +import com.swirlds.platform.state.spi.WritableKVStateBase; +import com.swirlds.platform.test.fixtures.state.MapWritableKVState; +import com.swirlds.state.spi.WritableKVState; +import com.swirlds.state.spi.WritableStates; import java.util.ArrayList; import java.util.List; import java.util.Map; diff --git a/hedera-node/hedera-token-service-impl/src/test/java/com/hedera/node/app/service/token/impl/test/api/TokenServiceApiProviderTest.java b/hedera-node/hedera-token-service-impl/src/test/java/com/hedera/node/app/service/token/impl/test/api/TokenServiceApiProviderTest.java index 68ae9bf9cc40..fe4bb35e3b2d 100644 --- a/hedera-node/hedera-token-service-impl/src/test/java/com/hedera/node/app/service/token/impl/test/api/TokenServiceApiProviderTest.java +++ b/hedera-node/hedera-token-service-impl/src/test/java/com/hedera/node/app/service/token/impl/test/api/TokenServiceApiProviderTest.java @@ -27,11 +27,11 @@ import com.hedera.node.app.service.token.TokenService; import com.hedera.node.app.service.token.impl.TokenServiceImpl; import com.hedera.node.app.service.token.impl.api.TokenServiceApiImpl; -import com.hedera.node.app.spi.fixtures.state.MapWritableKVState; import com.hedera.node.app.spi.metrics.StoreMetricsService; -import com.hedera.node.app.spi.state.WritableStates; import com.hedera.node.config.testfixtures.HederaTestConfigBuilder; import com.swirlds.config.api.Configuration; +import com.swirlds.platform.test.fixtures.state.MapWritableKVState; +import com.swirlds.state.spi.WritableStates; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.Mock; diff --git a/hedera-node/hedera-token-service-impl/src/test/java/com/hedera/node/app/service/token/impl/test/codec/NetworkingStakingTranslatorTest.java b/hedera-node/hedera-token-service-impl/src/test/java/com/hedera/node/app/service/token/impl/test/codec/NetworkingStakingTranslatorTest.java index a7984b70ab40..3470398c3e34 100644 --- a/hedera-node/hedera-token-service-impl/src/test/java/com/hedera/node/app/service/token/impl/test/codec/NetworkingStakingTranslatorTest.java +++ b/hedera-node/hedera-token-service-impl/src/test/java/com/hedera/node/app/service/token/impl/test/codec/NetworkingStakingTranslatorTest.java @@ -17,14 +17,20 @@ package com.hedera.node.app.service.token.impl.test.codec; import static com.hedera.node.app.service.token.impl.TokenServiceImpl.STAKING_NETWORK_REWARDS_KEY; +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; import static org.mockito.BDDMockito.given; import com.hedera.hapi.node.state.token.NetworkStakingRewards; -import com.hedera.node.app.service.token.impl.ReadableNetworkStakingRewardsStoreImpl; +import com.hedera.node.app.service.mono.state.merkle.MerkleNetworkContext; +import com.hedera.node.app.service.mono.state.submerkle.ExchangeRates; +import com.hedera.node.app.service.mono.state.submerkle.SequenceNumber; import com.hedera.node.app.service.token.impl.codec.NetworkingStakingTranslator; -import com.hedera.node.app.spi.state.ReadableSingletonState; -import com.hedera.node.app.spi.state.ReadableStates; +import com.swirlds.state.spi.ReadableSingletonState; +import com.swirlds.state.spi.ReadableStates; +import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationTargetException; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; @@ -40,19 +46,51 @@ class NetworkingStakingTranslatorTest { @Mock(strictness = Mock.Strictness.LENIENT) private ReadableSingletonState stakingRewardsState; - private ReadableNetworkStakingRewardsStoreImpl subject; + @Test + void testCoverageForPrivateConstructor() + throws NoSuchMethodException, InstantiationException, IllegalAccessException { + final Constructor constructor = + NetworkingStakingTranslator.class.getDeclaredConstructor(); + constructor.setAccessible(true); + try { + constructor.newInstance(); + } catch (InvocationTargetException e) { + Throwable cause = e.getCause(); + assertEquals(UnsupportedOperationException.class, cause.getClass()); + } + } @BeforeEach void setUp() { given(states.getSingleton(STAKING_NETWORK_REWARDS_KEY)).willReturn(stakingRewardsState); given(stakingRewardsState.get()).willReturn(new NetworkStakingRewards(true, 1L, 2L, 3L)); - subject = new ReadableNetworkStakingRewardsStoreImpl(states); + } + + @Test + void testNetworkStakingRewardsFromMerkleNetworkContextWithNullInput() { + assertThrows(NullPointerException.class, () -> { + NetworkingStakingTranslator.networkStakingRewardsFromMerkleNetworkContext(null); + }); + } + + @Test + void testNetworkStakingRewardsFromMerkleNetworkContextWithInvalidInput() { + MerkleNetworkContext merkleNetworkContextInvalid = new MerkleNetworkContext(); + merkleNetworkContextInvalid.setStakingRewardsActivated(true); + merkleNetworkContextInvalid.setTotalStakedRewardStart(-1L); + merkleNetworkContextInvalid.setPendingRewards(100_000_000_001L); + merkleNetworkContextInvalid.setTotalStakedStart(-5L); + merkleNetworkContextInvalid.setSeqNo(new SequenceNumber(1001)); + merkleNetworkContextInvalid.setMidnightRates(new ExchangeRates()); + + assertDoesNotThrow(() -> { + NetworkingStakingTranslator.networkStakingRewardsFromMerkleNetworkContext(merkleNetworkContextInvalid); + }); } @Test void createNetworkStakingRewardsFromMerkleNetworkContext() { - final com.hedera.node.app.service.mono.state.merkle.MerkleNetworkContext merkleNetworkContext = - new com.hedera.node.app.service.mono.state.merkle.MerkleNetworkContext(); + final MerkleNetworkContext merkleNetworkContext = new MerkleNetworkContext(); merkleNetworkContext.setStakingRewardsActivated(true); merkleNetworkContext.setTotalStakedRewardStart(1L); @@ -65,9 +103,8 @@ void createNetworkStakingRewardsFromMerkleNetworkContext() { assertEquals(getExpectedNetworkStakingRewards(), convertedNetworkStakingRewards); } - private com.hedera.node.app.service.mono.state.merkle.MerkleNetworkContext getExpectedMerkleNetworkContext() { - final com.hedera.node.app.service.mono.state.merkle.MerkleNetworkContext merkleNetworkContext = - new com.hedera.node.app.service.mono.state.merkle.MerkleNetworkContext(); + private MerkleNetworkContext getExpectedMerkleNetworkContext() { + final MerkleNetworkContext merkleNetworkContext = new MerkleNetworkContext(); merkleNetworkContext.setStakingRewardsActivated(true); merkleNetworkContext.setTotalStakedRewardStart(1L); diff --git a/hedera-node/hedera-token-service-impl/src/test/java/com/hedera/node/app/service/token/impl/test/fixtures/FakeCryptoCreateRecordBuilder.java b/hedera-node/hedera-token-service-impl/src/test/java/com/hedera/node/app/service/token/impl/test/fixtures/FakeCryptoCreateRecordBuilder.java index 418c75786271..67aa74cca3a1 100644 --- a/hedera-node/hedera-token-service-impl/src/test/java/com/hedera/node/app/service/token/impl/test/fixtures/FakeCryptoCreateRecordBuilder.java +++ b/hedera-node/hedera-token-service-impl/src/test/java/com/hedera/node/app/service/token/impl/test/fixtures/FakeCryptoCreateRecordBuilder.java @@ -24,9 +24,19 @@ import com.hedera.pbj.runtime.io.buffer.Bytes; import org.jetbrains.annotations.NotNull; +/** + * Fake Crypto Create Record Builder + */ public class FakeCryptoCreateRecordBuilder { + /** + * Constructs a {@link FakeCryptoCreateRecordBuilder} instance. + */ public FakeCryptoCreateRecordBuilder() {} + /** + * Creates a {@link CryptoCreateRecordBuilder} instance. + * @return a {@link CryptoCreateRecordBuilder} instance + */ public CryptoCreateRecordBuilder create() { return new CryptoCreateRecordBuilder() { diff --git a/hedera-node/hedera-token-service-impl/src/test/java/com/hedera/node/app/service/token/impl/test/fixtures/FakeCryptoTransferRecordBuilder.java b/hedera-node/hedera-token-service-impl/src/test/java/com/hedera/node/app/service/token/impl/test/fixtures/FakeCryptoTransferRecordBuilder.java index 5414439ace93..61983b371b86 100644 --- a/hedera-node/hedera-token-service-impl/src/test/java/com/hedera/node/app/service/token/impl/test/fixtures/FakeCryptoTransferRecordBuilder.java +++ b/hedera-node/hedera-token-service-impl/src/test/java/com/hedera/node/app/service/token/impl/test/fixtures/FakeCryptoTransferRecordBuilder.java @@ -31,10 +31,19 @@ import java.util.List; import org.jetbrains.annotations.NotNull; +/** + * Fake Crypto Transfer Record Builder + */ public class FakeCryptoTransferRecordBuilder { - + /** + * Constructs a {@link FakeCryptoTransferRecordBuilder} instance. + */ public FakeCryptoTransferRecordBuilder() {} + /** + * Creates a {@link CryptoTransferRecordBuilder} instance. + * @return a {@link CryptoTransferRecordBuilder} instance + */ public CryptoTransferRecordBuilder create() { return new CryptoTransferRecordBuilder() { @NotNull diff --git a/hedera-node/hedera-token-service-impl/src/test/java/com/hedera/node/app/service/token/impl/test/fixtures/FakeNodeStakeUpdateRecordBuilder.java b/hedera-node/hedera-token-service-impl/src/test/java/com/hedera/node/app/service/token/impl/test/fixtures/FakeNodeStakeUpdateRecordBuilder.java index 1b615e960d12..2440c2459f8a 100644 --- a/hedera-node/hedera-token-service-impl/src/test/java/com/hedera/node/app/service/token/impl/test/fixtures/FakeNodeStakeUpdateRecordBuilder.java +++ b/hedera-node/hedera-token-service-impl/src/test/java/com/hedera/node/app/service/token/impl/test/fixtures/FakeNodeStakeUpdateRecordBuilder.java @@ -21,8 +21,14 @@ import com.hedera.node.app.service.token.records.NodeStakeUpdateRecordBuilder; import org.jetbrains.annotations.NotNull; +/** + * Fake Node Stake Update Record Builder + */ public class FakeNodeStakeUpdateRecordBuilder { - + /** + * Constructs a {@link FakeNodeStakeUpdateRecordBuilder} instance. + * @return a {@link FakeNodeStakeUpdateRecordBuilder} instance + */ public NodeStakeUpdateRecordBuilder create() { return new NodeStakeUpdateRecordBuilder() { private String memo; diff --git a/hedera-node/hedera-token-service-impl/src/test/java/com/hedera/node/app/service/token/impl/test/handlers/BaseCryptoHandlerTest.java b/hedera-node/hedera-token-service-impl/src/test/java/com/hedera/node/app/service/token/impl/test/handlers/BaseCryptoHandlerTest.java new file mode 100644 index 000000000000..485aab5e8814 --- /dev/null +++ b/hedera-node/hedera-token-service-impl/src/test/java/com/hedera/node/app/service/token/impl/test/handlers/BaseCryptoHandlerTest.java @@ -0,0 +1,103 @@ +/* + * Copyright (C) 2024 Hedera Hashgraph, LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.hedera.node.app.service.token.impl.test.handlers; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.Mockito.*; + +import com.google.protobuf.ByteString; +import com.hedera.hapi.node.base.AccountID; +import com.hedera.node.app.service.token.impl.handlers.BaseCryptoHandler; +import com.hedera.node.config.data.AccountsConfig; +import com.hedera.pbj.runtime.io.buffer.Bytes; +import com.swirlds.config.api.Configuration; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +public class BaseCryptoHandlerTest { + @Mock + private Configuration configuration; + + @Mock + private AccountsConfig accountsConfig; + + @BeforeEach + void setUp() { + MockitoAnnotations.openMocks(this); + when(configuration.getConfigData(AccountsConfig.class)).thenReturn(accountsConfig); + } + + @Test + @DisplayName("isStakingAccount Check if account is a staking reward account") + void isStakingAccount_returnsTrue_whenAccountIsStakingRewardAccount() { + when(accountsConfig.stakingRewardAccount()).thenReturn(1L); + assertTrue(BaseCryptoHandler.isStakingAccount(configuration, BaseCryptoHandler.asAccount(1L))); + } + + @Test + @DisplayName("isStakingAccount Check if account is a node reward account") + void isStakingAccount_returnsTrue_whenAccountIsNodeRewardAccount() { + when(accountsConfig.nodeRewardAccount()).thenReturn(1L); + assertTrue(BaseCryptoHandler.isStakingAccount(configuration, BaseCryptoHandler.asAccount(1L))); + } + + @Test + @DisplayName("isStakingAccount Check if account is not a staking or node reward account") + void isStakingAccount_returnsFalse_whenAccountIsNotStakingOrNodeRewardAccount() { + when(accountsConfig.stakingRewardAccount()).thenReturn(1L); + when(accountsConfig.nodeRewardAccount()).thenReturn(2L); + assertFalse(BaseCryptoHandler.isStakingAccount(configuration, BaseCryptoHandler.asAccount(3L))); + } + + @DisplayName("asAccount Check if asAccount returns AccountID with given number") + @Test + void asAccountReturnsAccountIDWithGivenNumber() { + AccountID result = BaseCryptoHandler.asAccount(123); + assertEquals(123, result.accountNum()); + } + + @Test + @DisplayName("hasAccountNumOrAlias Null accountID is invalid") + void hasAccountNumOrAlias_returnsFalse_whenAccountIsNull() { + assertFalse(BaseCryptoHandler.hasAccountNumOrAlias(null)); + } + + @Test + @DisplayName("hasAccountNumOrAlias Account with number is valid") + void hasAccountNumOrAlias_returnsTrue_whenAccountHasNumber() { + AccountID accountID = AccountID.newBuilder().accountNum(1L).build(); + assertTrue(BaseCryptoHandler.hasAccountNumOrAlias(accountID)); + } + + @Test + @DisplayName("hasAccountNumOrAlias Account with alias is valid") + void hasAccountNumOrAlias_returnsTrue_whenAccountHasAlias() { + byte[] bytes = ByteString.copyFromUtf8("alias").toByteArray(); + AccountID accountID = AccountID.newBuilder().alias(Bytes.wrap(bytes)).build(); + assertTrue(BaseCryptoHandler.hasAccountNumOrAlias(accountID)); + } + + @Test + @DisplayName("hasAccountNumOrAlias Account neither number nor alias is invalid") + void hasAccountNumOrAlias_returnsFalse_whenAccountHasNeitherNumberNorAlias() { + AccountID accountID = AccountID.newBuilder().build(); + assertFalse(BaseCryptoHandler.hasAccountNumOrAlias(accountID)); + } +} diff --git a/hedera-node/hedera-token-service-impl/src/test/java/com/hedera/node/app/service/token/impl/test/handlers/CryptoAddLiveHashHandlerTest.java b/hedera-node/hedera-token-service-impl/src/test/java/com/hedera/node/app/service/token/impl/test/handlers/CryptoAddLiveHashHandlerTest.java index 3be7b88f4e1b..58ad0c63cb8b 100644 --- a/hedera-node/hedera-token-service-impl/src/test/java/com/hedera/node/app/service/token/impl/test/handlers/CryptoAddLiveHashHandlerTest.java +++ b/hedera-node/hedera-token-service-impl/src/test/java/com/hedera/node/app/service/token/impl/test/handlers/CryptoAddLiveHashHandlerTest.java @@ -18,14 +18,20 @@ import static com.hedera.hapi.node.base.ResponseCodeEnum.NOT_SUPPORTED; import static com.hedera.node.app.spi.fixtures.workflows.ExceptionConditions.responseCode; +import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.mockito.Mock.Strictness.LENIENT; +import static org.mockito.Mockito.mock; +import com.hedera.hapi.node.transaction.TransactionBody; import com.hedera.node.app.service.token.impl.handlers.CryptoAddLiveHashHandler; +import com.hedera.node.app.spi.fees.FeeContext; +import com.hedera.node.app.spi.fees.Fees; import com.hedera.node.app.spi.workflows.HandleContext; import com.hedera.node.app.spi.workflows.HandleException; import com.hedera.node.app.spi.workflows.PreCheckException; import com.hedera.node.app.spi.workflows.PreHandleContext; +import org.assertj.core.api.Assertions; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.Mock; @@ -41,6 +47,14 @@ class CryptoAddLiveHashHandlerTest { private final CryptoAddLiveHashHandler subject = new CryptoAddLiveHashHandler(); + @Test + void pureChecksDoesNothing() { + // Verify no exception is thrown + Assertions.assertThatNoException() + .isThrownBy( + () -> subject.pureChecks(TransactionBody.newBuilder().build())); + } + @Test void preHandleThrowsUnsupported() { assertThatThrownBy(() -> subject.preHandle(preHandleContext)) @@ -54,4 +68,11 @@ void handleThrowsUnsupported() { .isInstanceOf(HandleException.class) .has(responseCode(NOT_SUPPORTED)); } + + @Test + void calculateFeesFree() { + final var feeCtx = mock(FeeContext.class); + final var result = subject.calculateFees(feeCtx); + assertThat(result).isEqualTo(Fees.FREE); + } } diff --git a/hedera-node/hedera-token-service-impl/src/test/java/com/hedera/node/app/service/token/impl/test/handlers/CryptoApproveAllowanceHandlerTest.java b/hedera-node/hedera-token-service-impl/src/test/java/com/hedera/node/app/service/token/impl/test/handlers/CryptoApproveAllowanceHandlerTest.java index 59db20201997..00c3664360da 100644 --- a/hedera-node/hedera-token-service-impl/src/test/java/com/hedera/node/app/service/token/impl/test/handlers/CryptoApproveAllowanceHandlerTest.java +++ b/hedera-node/hedera-token-service-impl/src/test/java/com/hedera/node/app/service/token/impl/test/handlers/CryptoApproveAllowanceHandlerTest.java @@ -20,6 +20,7 @@ import static com.hedera.hapi.node.base.ResponseCodeEnum.INVALID_ALLOWANCE_OWNER_ID; import static com.hedera.hapi.node.base.ResponseCodeEnum.INVALID_ALLOWANCE_SPENDER_ID; import static com.hedera.hapi.node.base.ResponseCodeEnum.INVALID_DELEGATING_SPENDER; +import static com.hedera.hapi.node.base.ResponseCodeEnum.INVALID_PAYER_ACCOUNT_ID; import static com.hedera.hapi.node.base.ResponseCodeEnum.MAX_ALLOWANCES_EXCEEDED; import static com.hedera.hapi.node.base.ResponseCodeEnum.NEGATIVE_ALLOWANCE_AMOUNT; import static com.hedera.hapi.node.base.ResponseCodeEnum.OK; @@ -32,11 +33,14 @@ import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.ArgumentMatchers.anyLong; +import static org.mockito.ArgumentMatchers.notNull; import static org.mockito.BDDMockito.given; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; import com.hedera.hapi.node.base.AccountID; import com.hedera.hapi.node.base.NftID; +import com.hedera.hapi.node.base.Timestamp; import com.hedera.hapi.node.base.TransactionID; import com.hedera.hapi.node.state.token.Account; import com.hedera.hapi.node.state.token.Nft; @@ -45,12 +49,16 @@ import com.hedera.hapi.node.token.NftAllowance; import com.hedera.hapi.node.token.TokenAllowance; import com.hedera.hapi.node.transaction.TransactionBody; +import com.hedera.node.app.service.token.ReadableAccountStore; import com.hedera.node.app.service.token.impl.ReadableAccountStoreImpl; import com.hedera.node.app.service.token.impl.WritableAccountStore; import com.hedera.node.app.service.token.impl.WritableNftStore; import com.hedera.node.app.service.token.impl.handlers.CryptoApproveAllowanceHandler; import com.hedera.node.app.service.token.impl.test.handlers.util.CryptoTokenHandlerTestBase; import com.hedera.node.app.service.token.impl.validators.ApproveAllowanceValidator; +import com.hedera.node.app.spi.fees.FeeCalculator; +import com.hedera.node.app.spi.fees.FeeContext; +import com.hedera.node.app.spi.fees.Fees; import com.hedera.node.app.spi.fixtures.workflows.FakePreHandleContext; import com.hedera.node.app.spi.metrics.StoreMetricsService; import com.hedera.node.app.spi.validation.ExpiryValidator; @@ -58,7 +66,10 @@ import com.hedera.node.app.spi.workflows.HandleException; import com.hedera.node.app.spi.workflows.PreCheckException; import com.hedera.node.config.testfixtures.HederaTestConfigBuilder; +import com.hedera.pbj.runtime.io.buffer.Bytes; +import java.util.Collections; import java.util.List; +import org.assertj.core.api.Assertions; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; @@ -193,6 +204,20 @@ void cryptoApproveAllowanceFailsIfDelegatingSpenderMissing() throws PreCheckExce assertThrowsPreCheck(() -> subject.preHandle(context), INVALID_DELEGATING_SPENDER); } + @Test + void tokenAllowanceFailsIfOwnerHasAlias() throws PreCheckException { + final var tokenAllowance = TokenAllowance.newBuilder() + .tokenId(fungibleTokenId) + .owner(AccountID.newBuilder() + .alias(Bytes.wrap(new byte[] {0, 1, 2})) + .build()) + .build(); + final var txn = cryptoApproveAllowanceTransaction( + payerId, true, Collections.emptyList(), List.of(tokenAllowance), Collections.emptyList()); + final var context = new FakePreHandleContext(readableAccountStore, txn); + assertThrowsPreCheck(() -> subject.preHandle(context), INVALID_ALLOWANCE_OWNER_ID); + } + @Test void happyPathAddsAllowances() { writableAccountStore.put(ownerAccount @@ -502,6 +527,43 @@ void validateNegativeAmounts() { .has(responseCode(NEGATIVE_ALLOWANCE_AMOUNT)); } + @Test + void validateInvalidNftSpender() { + // NftAllowance with no spender + final var invalidNftAllowance = + NftAllowance.newBuilder().tokenId(nonFungibleTokenId).build(); + final var txn = cryptoApproveAllowanceTransaction( + payerId, false, Collections.emptyList(), Collections.emptyList(), List.of(invalidNftAllowance)); + + assertThrowsPreCheck(() -> subject.pureChecks(txn), INVALID_ALLOWANCE_SPENDER_ID); + } + + @Test + void validateInvalidTokenAllowanceAmount() { + // TokenAllowance with invalid allowance amount + final var invalidTokenAllowance = TokenAllowance.newBuilder() + .tokenId(fungibleTokenId) + .spender(spenderId) + .amount(-1) + .build(); + final var txn = cryptoApproveAllowanceTransaction( + payerId, false, Collections.emptyList(), List.of(invalidTokenAllowance), Collections.emptyList()); + + assertThrowsPreCheck(() -> subject.pureChecks(txn), NEGATIVE_ALLOWANCE_AMOUNT); + } + + @Test + void validateValidNftAllowance() throws PreCheckException { + final var validNftAllowance = NftAllowance.newBuilder() + .spender(spenderId) + .tokenId(nonFungibleTokenId) + .build(); + final var txn = cryptoApproveAllowanceTransaction( + payerId, false, Collections.emptyList(), Collections.emptyList(), List.of(validNftAllowance)); + + subject.pureChecks(txn); // No exception thrown + } + @Test void existingAllowancesDeletedWithAmountZero() { final var txn = cryptoApproveAllowanceTransaction( @@ -522,6 +584,79 @@ void existingAllowancesDeletedWithAmountZero() { assertThat(modifiedOwner.cryptoAllowances()).isEmpty(); assertThat(modifiedOwner.tokenAllowances()).isEmpty(); } + + @Test + void handlePayerAccountNotFound() { + given(handleContext.payer()).willReturn(AccountID.DEFAULT); + Assertions.assertThatThrownBy(() -> subject.handle(handleContext)) + .isInstanceOf(HandleException.class) + .has(responseCode(INVALID_PAYER_ACCOUNT_ID)); + } + + @Test + void calculateFeesHappyPath() { + final var txn = cryptoApproveAllowanceTransaction( + payerId, + Timestamp.newBuilder().seconds(account.expirationSecond() - 1).build(), + false, + List.of(cryptoAllowance), + List.of(tokenAllowance), + List.of(nftAllowance)); + final var feeCtx = mock(FeeContext.class); + given(feeCtx.body()).willReturn(txn); + given(feeCtx.readableStore(ReadableAccountStore.class)).willReturn(readableAccountStore); + given(feeCtx.payer()).willReturn(payerId); + + final var feeCalc = mock(FeeCalculator.class); + given(feeCtx.feeCalculator(notNull())).willReturn(feeCalc); + given(feeCalc.addBytesPerTransaction(anyLong())).willReturn(feeCalc); + given(feeCalc.addRamByteSeconds(anyLong())).willReturn(feeCalc); + // The fees wouldn't be free in this scenario, but we don't care about the actual return + // value here since we're using a mock calculator + given(feeCalc.calculate()).willReturn(Fees.FREE); + + subject.calculateFees(feeCtx); + + verify(feeCalc).addBytesPerTransaction(128); + verify(feeCalc).addRamByteSeconds(112); + } + + @Test + void calculateFeesAccountNotFound() { + final var txn = cryptoApproveAllowanceTransaction( + payerId, + Timestamp.newBuilder().seconds(account.expirationSecond() - 1).build(), + false, + List.of(cryptoAllowance), + List.of(tokenAllowance), + List.of(nftAllowance)); + final var feeCtx = mock(FeeContext.class); + given(feeCtx.body()).willReturn(txn); + given(feeCtx.readableStore(ReadableAccountStore.class)).willReturn(readableAccountStore); + given(feeCtx.payer()) + .willReturn(AccountID.newBuilder().accountNum(Long.MAX_VALUE).build()); + + final var feeCalc = mock(FeeCalculator.class); + given(feeCtx.feeCalculator(notNull())).willReturn(feeCalc); + given(feeCalc.addBytesPerTransaction(anyLong())).willReturn(feeCalc); + given(feeCalc.addRamByteSeconds(anyLong())).willReturn(feeCalc); + // The fees wouldn't be free in this scenario, but we don't care about the actual return + // value here since we're using a mock calculator + given(feeCalc.calculate()).willReturn(Fees.FREE); + + subject.calculateFees(feeCtx); + + verify(feeCalc).addBytesPerTransaction(128); + verify(feeCalc).addRamByteSeconds(0); + } + + @Test + void updateSpenderWithEmptySerialNumsDoesntUpdate() { + subject.updateSpender( + writableTokenStore, writableNftStore, ownerAccount, spenderId, nonFungibleTokenId, List.of()); + assertThat(writableTokenStore.modifiedTokens()).isEmpty(); + } + // // @Test // void failsToUpdateSpenderIfWrongOwner() { @@ -558,7 +693,18 @@ private TransactionBody cryptoApproveAllowanceTransaction( final List cryptoAllowance, final List tokenAllowance, final List nftAllowance) { - final var transactionID = TransactionID.newBuilder().accountID(id).transactionValidStart(consensusTimestamp); + return cryptoApproveAllowanceTransaction( + id, consensusTimestamp, isWithDelegatingSpender, cryptoAllowance, tokenAllowance, nftAllowance); + } + + private TransactionBody cryptoApproveAllowanceTransaction( + final AccountID id, + final Timestamp validStart, + final boolean isWithDelegatingSpender, + final List cryptoAllowance, + final List tokenAllowance, + final List nftAllowance) { + final var transactionID = TransactionID.newBuilder().accountID(id).transactionValidStart(validStart); final var allowanceTxnBody = CryptoApproveAllowanceTransactionBody.newBuilder() .cryptoAllowances(cryptoAllowance) .tokenAllowances(tokenAllowance) diff --git a/hedera-node/hedera-token-service-impl/src/test/java/com/hedera/node/app/service/token/impl/test/handlers/CryptoCreateHandlerTest.java b/hedera-node/hedera-token-service-impl/src/test/java/com/hedera/node/app/service/token/impl/test/handlers/CryptoCreateHandlerTest.java index 1cf0bef274aa..941aad86e074 100644 --- a/hedera-node/hedera-token-service-impl/src/test/java/com/hedera/node/app/service/token/impl/test/handlers/CryptoCreateHandlerTest.java +++ b/hedera-node/hedera-token-service-impl/src/test/java/com/hedera/node/app/service/token/impl/test/handlers/CryptoCreateHandlerTest.java @@ -26,12 +26,19 @@ import static com.hedera.hapi.node.base.ResponseCodeEnum.INVALID_RECEIVE_RECORD_THRESHOLD; import static com.hedera.hapi.node.base.ResponseCodeEnum.INVALID_RENEWAL_PERIOD; import static com.hedera.hapi.node.base.ResponseCodeEnum.INVALID_SEND_RECORD_THRESHOLD; +import static com.hedera.hapi.node.base.ResponseCodeEnum.INVALID_TRANSACTION_BODY; import static com.hedera.hapi.node.base.ResponseCodeEnum.KEY_REQUIRED; import static com.hedera.hapi.node.base.ResponseCodeEnum.MEMO_TOO_LONG; import static com.hedera.hapi.node.base.ResponseCodeEnum.NOT_SUPPORTED; import static com.hedera.hapi.node.base.ResponseCodeEnum.PROXY_ACCOUNT_ID_FIELD_IS_DEPRECATED; +import static com.hedera.hapi.node.base.SubType.DEFAULT; import static com.hedera.node.app.service.token.impl.handlers.BaseCryptoHandler.asAccount; +import static com.hedera.node.app.service.token.impl.test.handlers.util.StateBuilderUtil.ACCOUNTS; +import static com.hedera.node.app.service.token.impl.test.handlers.util.StateBuilderUtil.ALIASES; +import static com.hedera.node.app.spi.fixtures.workflows.ExceptionConditions.responseCode; +import static com.hedera.test.utils.KeyUtils.A_COMPLEX_KEY; import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.AssertionsForClassTypes.assertThatThrownBy; import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; @@ -70,7 +77,9 @@ import com.hedera.node.app.service.token.records.CryptoCreateRecordBuilder; import com.hedera.node.app.spi.fees.FeeAccumulator; import com.hedera.node.app.spi.fees.FeeCalculator; +import com.hedera.node.app.spi.fees.FeeContext; import com.hedera.node.app.spi.fees.Fees; +import com.hedera.node.app.spi.fixtures.fees.FakeFeeCalculator; import com.hedera.node.app.spi.fixtures.workflows.FakePreHandleContext; import com.hedera.node.app.spi.info.NetworkInfo; import com.hedera.node.app.spi.info.NodeInfo; @@ -91,7 +100,6 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.Mock; -import org.mockito.Mock.Strictness; import org.mockito.junit.jupiter.MockitoExtension; /** @@ -102,12 +110,15 @@ class CryptoCreateHandlerTest extends CryptoHandlerTestBase { @Mock(strictness = LENIENT) private HandleContext handleContext; - @Mock(strictness = Strictness.LENIENT) + @Mock(strictness = LENIENT) private LongSupplier consensusSecondNow; @Mock(strictness = LENIENT) private GlobalDynamicProperties dynamicProperties; + @Mock(strictness = LENIENT) + private FeeContext feeContext; + @Mock private PropertySource compositeProps; @@ -173,16 +184,30 @@ public void setUp() { subject = new CryptoCreateHandler(cryptoCreateValidator, stakingValidator); } + @Test + @DisplayName("test CalculateFees When Free") + void testCalculateFeesWhenFree() { + var transactionBody = new CryptoCreateBuilder() + .withStakedAccountId(3) + .withMemo("blank") + .withKey(A_COMPLEX_KEY) + .build(); + feeCalculator = new FakeFeeCalculator(); + given(feeContext.body()).willReturn(transactionBody); + given(feeContext.feeCalculator(DEFAULT)).willReturn(feeCalculator); + final var result = subject.calculateFees(feeContext); + assertThat(result).isEqualTo(Fees.FREE); + } + @Test @DisplayName("preHandle works when there is a receiverSigRequired") void preHandleCryptoCreateVanilla() throws PreCheckException { final var context = new FakePreHandleContext(readableStore, txn); subject.pureChecks(txn); subject.preHandle(context); - - assertEquals(txn, context.body()); + assertThat(txn).isEqualTo(context.body()); basicMetaAssertions(context, 1); - assertEquals(key, context.payerKey()); + assertThat(key).isEqualTo(context.payerKey()); } @Test @@ -190,7 +215,7 @@ void preHandleCryptoCreateVanilla() throws PreCheckException { void whenInitialBalanceIsNegative() throws PreCheckException { txn = new CryptoCreateBuilder().withInitialBalance(-1L).build(); final var msg = assertThrows(PreCheckException.class, () -> subject.pureChecks(txn)); - assertEquals(INVALID_INITIAL_BALANCE, msg.responseCode()); + assertThat(INVALID_INITIAL_BALANCE).isEqualTo(msg.responseCode()); } @Test @@ -198,7 +223,22 @@ void whenInitialBalanceIsNegative() throws PreCheckException { void whenNoAutoRenewPeriodSpecified() throws PreCheckException { txn = new CryptoCreateBuilder().withNoAutoRenewPeriod().build(); final var msg = assertThrows(PreCheckException.class, () -> subject.pureChecks(txn)); - assertEquals(INVALID_RENEWAL_PERIOD, msg.responseCode()); + assertThat(INVALID_RENEWAL_PERIOD).isEqualTo(msg.responseCode()); + } + + @Test + @DisplayName("pureChecks succeeds when expected shardId is specified") + void validateWhenZeroShardId() throws PreCheckException { + txn = new CryptoCreateBuilder().withShardId(0).build(); + assertDoesNotThrow(() -> subject.pureChecks(txn)); + } + + @Test + @DisplayName("pureChecks fail when invalid maxAutoAssociations is specified") + void failsWhenInvalidMaxAutoAssociations() throws PreCheckException { + txn = new CryptoCreateBuilder().withMaxAutoAssociations(-5).build(); + final var msg = assertThrows(PreCheckException.class, () -> subject.pureChecks(txn)); + assertThat(msg.responseCode()).isEqualTo(INVALID_TRANSACTION_BODY); } @Test @@ -206,7 +246,7 @@ void whenNoAutoRenewPeriodSpecified() throws PreCheckException { void sendRecordThresholdIsNegative() throws PreCheckException { txn = new CryptoCreateBuilder().withSendRecordThreshold(-1).build(); final var msg = assertThrows(PreCheckException.class, () -> subject.pureChecks(txn)); - assertEquals(INVALID_SEND_RECORD_THRESHOLD, msg.responseCode()); + assertThat(msg.responseCode()).isEqualTo(INVALID_SEND_RECORD_THRESHOLD); } @Test @@ -214,7 +254,7 @@ void sendRecordThresholdIsNegative() throws PreCheckException { void receiveRecordThresholdIsNegative() throws PreCheckException { txn = new CryptoCreateBuilder().withReceiveRecordThreshold(-1).build(); final var msg = assertThrows(PreCheckException.class, () -> subject.pureChecks(txn)); - assertEquals(INVALID_RECEIVE_RECORD_THRESHOLD, msg.responseCode()); + assertThat(msg.responseCode()).isEqualTo(INVALID_RECEIVE_RECORD_THRESHOLD); } @Test @@ -222,7 +262,7 @@ void receiveRecordThresholdIsNegative() throws PreCheckException { void whenProxyAccountIdIsSpecified() throws PreCheckException { txn = new CryptoCreateBuilder().withProxyAccountNum(1).build(); final var msg = assertThrows(PreCheckException.class, () -> subject.pureChecks(txn)); - assertEquals(PROXY_ACCOUNT_ID_FIELD_IS_DEPRECATED, msg.responseCode()); + assertThat(msg.responseCode()).isEqualTo(PROXY_ACCOUNT_ID_FIELD_IS_DEPRECATED); } @Test @@ -232,10 +272,39 @@ void preHandleWorksWhenInitialBalanceIsZero() throws PreCheckException { final var context = new FakePreHandleContext(readableStore, txn); subject.pureChecks(txn); subject.preHandle(context); + assertThat(txn).isEqualTo(context.body()); + basicMetaAssertions(context, 1); + assertThat(key).isEqualTo(context.payerKey()); + } - assertEquals(txn, context.body()); + @Test + @DisplayName("preHandle succeeds when has non zero evm alias") + void preHandleWorksWhenHasEvmAlias() throws PreCheckException { + final byte[] evmAddress = CommonUtils.unhex("6aeb3773ea468a814d954e6dec795bfee7d76e26"); + txn = new CryptoCreateBuilder() + .withAlias(Bytes.wrap(evmAddress)) + .withStakedAccountId(3) + .build(); + final var context = new FakePreHandleContext(readableStore, txn); + subject.preHandle(context); + assertThat(txn).isEqualTo(context.body()); basicMetaAssertions(context, 1); - assertEquals(key, context.payerKey()); + assertThat(key).isEqualTo(context.payerKey()); + } + + @Test + @DisplayName("preHandle fails when invalid alias key") + void preHandleWorksWhenHasAlias() throws PreCheckException { + final Bytes SENDER_ALIAS = + Bytes.fromHex("3a21030edcc130e13fb5102e7c883535af8c2b0a5a617231f77fd127ce5f3b9a620591"); + txn = new CryptoCreateBuilder() + .withAlias(SENDER_ALIAS) + .withStakedAccountId(3) + .build(); + final var context = new FakePreHandleContext(readableStore, txn); + assertThatThrownBy(() -> subject.preHandle(context)) + .isInstanceOf(PreCheckException.class) + .has(responseCode(INVALID_ALIAS_KEY)); } @Test @@ -247,12 +316,11 @@ void noReceiverSigRequiredPreHandleCryptoCreate() throws PreCheckException { final var context = new FakePreHandleContext(readableStore, noReceiverSigTxn); subject.preHandle(context); - - assertEquals(expected.body(), context.body()); + assertThat(expected.body()).isEqualTo(context.body()); assertFalse(context.requiredNonPayerKeys().contains(key)); basicMetaAssertions(context, 0); assertThat(context.requiredNonPayerKeys()).isEmpty(); - assertEquals(key, context.payerKey()); + assertThat(key).isEqualTo(context.payerKey()); } @Test @@ -717,6 +785,8 @@ private class CryptoCreateBuilder { private long receiveRecordThreshold = 0; private AccountID proxyAccountId = null; private long stakedAccountId = 0; + private long shardId = 0; + private int maxAutoAssociations = -1; private Key key = otherKey; @@ -754,6 +824,9 @@ public TransactionBody build() { if (memo != null) { createTxnBody.memo(memo); } + if (maxAutoAssociations != -1) { + createTxnBody.maxAutomaticTokenAssociations(maxAutoAssociations); + } return TransactionBody.newBuilder() .transactionID(transactionID) @@ -821,5 +894,15 @@ public CryptoCreateBuilder withKey(final Key key) { this.key = key; return this; } + + public CryptoCreateBuilder withShardId(final long id) { + this.shardId = id; + return this; + } + + public CryptoCreateBuilder withMaxAutoAssociations(final int maxAutoAssociations) { + this.maxAutoAssociations = maxAutoAssociations; + return this; + } } } diff --git a/hedera-node/hedera-token-service-impl/src/test/java/com/hedera/node/app/service/token/impl/test/handlers/CryptoDeleteHandlerTest.java b/hedera-node/hedera-token-service-impl/src/test/java/com/hedera/node/app/service/token/impl/test/handlers/CryptoDeleteHandlerTest.java index 70c920289eee..b2c27f10f340 100644 --- a/hedera-node/hedera-token-service-impl/src/test/java/com/hedera/node/app/service/token/impl/test/handlers/CryptoDeleteHandlerTest.java +++ b/hedera-node/hedera-token-service-impl/src/test/java/com/hedera/node/app/service/token/impl/test/handlers/CryptoDeleteHandlerTest.java @@ -25,6 +25,8 @@ import static com.hedera.hapi.node.base.ResponseCodeEnum.INVALID_TRANSFER_ACCOUNT_ID; import static com.hedera.hapi.node.base.ResponseCodeEnum.TRANSACTION_REQUIRES_ZERO_TOKEN_BALANCES; import static com.hedera.hapi.node.base.ResponseCodeEnum.TRANSFER_ACCOUNT_SAME_AS_DELETE_ACCOUNT; +import static com.hedera.node.app.service.token.impl.test.handlers.util.StateBuilderUtil.ACCOUNTS; +import static com.hedera.node.app.service.token.impl.test.handlers.util.StateBuilderUtil.ALIASES; import static com.hedera.node.app.spi.fixtures.Assertions.assertThrowsPreCheck; import static com.hedera.node.app.spi.fixtures.workflows.ExceptionConditions.responseCode; import static org.assertj.core.api.Assertions.assertThatNoException; @@ -55,7 +57,6 @@ import com.hedera.node.app.service.token.records.CryptoDeleteRecordBuilder; import com.hedera.node.app.spi.fixtures.workflows.FakePreHandleContext; import com.hedera.node.app.spi.metrics.StoreMetricsService; -import com.hedera.node.app.spi.state.WritableStates; import com.hedera.node.app.spi.validation.EntityType; import com.hedera.node.app.spi.validation.ExpiryValidator; import com.hedera.node.app.spi.workflows.HandleContext; @@ -63,6 +64,7 @@ import com.hedera.node.app.spi.workflows.PreCheckException; import com.hedera.node.config.testfixtures.HederaTestConfigBuilder; import com.swirlds.config.api.Configuration; +import com.swirlds.state.spi.WritableStates; import java.util.List; import java.util.Map; import org.junit.jupiter.api.BeforeEach; diff --git a/hedera-node/hedera-token-service-impl/src/test/java/com/hedera/node/app/service/token/impl/test/handlers/CryptoGetAccountBalanceHandlerTest.java b/hedera-node/hedera-token-service-impl/src/test/java/com/hedera/node/app/service/token/impl/test/handlers/CryptoGetAccountBalanceHandlerTest.java index dc6fd580af60..2a1e8c075cf9 100644 --- a/hedera-node/hedera-token-service-impl/src/test/java/com/hedera/node/app/service/token/impl/test/handlers/CryptoGetAccountBalanceHandlerTest.java +++ b/hedera-node/hedera-token-service-impl/src/test/java/com/hedera/node/app/service/token/impl/test/handlers/CryptoGetAccountBalanceHandlerTest.java @@ -17,6 +17,7 @@ package com.hedera.node.app.service.token.impl.test.handlers; import static com.hedera.node.app.service.token.impl.handlers.BaseTokenHandler.asToken; +import static com.hedera.node.app.service.token.impl.test.handlers.util.StateBuilderUtil.ACCOUNTS; import static com.hedera.node.app.service.token.impl.test.handlers.util.StateBuilderUtil.TOKENS; import static com.hedera.node.app.service.token.impl.test.handlers.util.StateBuilderUtil.TOKEN_RELS; import static com.hedera.node.app.spi.fixtures.workflows.ExceptionConditions.responseCode; @@ -54,11 +55,11 @@ import com.hedera.node.app.service.token.impl.ReadableTokenStoreImpl; import com.hedera.node.app.service.token.impl.handlers.CryptoGetAccountBalanceHandler; import com.hedera.node.app.service.token.impl.test.handlers.util.CryptoHandlerTestBase; -import com.hedera.node.app.spi.fixtures.state.MapReadableKVState; -import com.hedera.node.app.spi.state.ReadableStates; import com.hedera.node.app.spi.workflows.PreCheckException; import com.hedera.node.app.spi.workflows.QueryContext; import com.hedera.node.config.testfixtures.HederaTestConfigBuilder; +import com.swirlds.platform.test.fixtures.state.MapReadableKVState; +import com.swirlds.state.spi.ReadableStates; import java.util.ArrayList; import java.util.List; import org.junit.jupiter.api.BeforeEach; @@ -177,6 +178,25 @@ void validatesQueryIfInvalidAccount() throws Throwable { .has(responseCode(ResponseCodeEnum.INVALID_ACCOUNT_ID)); } + @Test + @DisplayName("Account Id with valid header is needed during validate") + void validatesQueryIfInvalidAccountHeader() throws Throwable { + final var state = + MapReadableKVState.builder(ACCOUNTS).build(); + given(readableStates.get(ACCOUNTS)).willReturn(state); + final var store = new ReadableAccountStoreImpl(readableStates); + final AccountID invalidRealmAccountId = + AccountID.newBuilder().accountNum(5).realmNum(-1L).build(); + + final var query = createGetAccountBalanceQueryWithInvalidHeader(invalidRealmAccountId.accountNumOrThrow()); + when(context.query()).thenReturn(query); + when(context.createStore(ReadableAccountStore.class)).thenReturn(store); + + assertThatThrownBy(() -> subject.validate(context)) + .isInstanceOf(PreCheckException.class) + .has(responseCode(ResponseCodeEnum.INVALID_ACCOUNT_ID)); + } + @Test @DisplayName("Contract Id is needed during validate") void validatesQueryIfInvalidContract() throws Throwable { @@ -466,6 +486,15 @@ private Query createGetAccountBalanceQuery(final long accountId) { return Query.newBuilder().cryptogetAccountBalance(data).build(); } + private Query createGetAccountBalanceQueryWithInvalidHeader(final long accountId) { + final var data = CryptoGetAccountBalanceQuery.newBuilder() + .accountID(AccountID.newBuilder().accountNum(accountId).build()) + .header((QueryHeader) null) + .build(); + + return Query.newBuilder().cryptogetAccountBalance(data).build(); + } + private Query createGetAccountBalanceQueryWithContract(final long contractId) { final var data = CryptoGetAccountBalanceQuery.newBuilder() .contractID(ContractID.newBuilder().contractNum(contractId).build()) diff --git a/hedera-node/hedera-token-service-impl/src/test/java/com/hedera/node/app/service/token/impl/test/handlers/CryptoGetAccountInfoHandlerTest.java b/hedera-node/hedera-token-service-impl/src/test/java/com/hedera/node/app/service/token/impl/test/handlers/CryptoGetAccountInfoHandlerTest.java index 6ab36cc60129..747c62557eac 100644 --- a/hedera-node/hedera-token-service-impl/src/test/java/com/hedera/node/app/service/token/impl/test/handlers/CryptoGetAccountInfoHandlerTest.java +++ b/hedera-node/hedera-token-service-impl/src/test/java/com/hedera/node/app/service/token/impl/test/handlers/CryptoGetAccountInfoHandlerTest.java @@ -68,16 +68,16 @@ import com.hedera.node.app.service.token.impl.ReadableTokenStoreImpl; import com.hedera.node.app.service.token.impl.handlers.CryptoGetAccountInfoHandler; import com.hedera.node.app.service.token.impl.test.handlers.util.CryptoHandlerTestBase; -import com.hedera.node.app.spi.fixtures.state.MapReadableKVState; -import com.hedera.node.app.spi.state.ReadableSingletonState; -import com.hedera.node.app.spi.state.ReadableSingletonStateBase; -import com.hedera.node.app.spi.state.ReadableStates; import com.hedera.node.app.spi.workflows.PreCheckException; import com.hedera.node.app.spi.workflows.QueryContext; import com.hedera.node.config.converter.BytesConverter; import com.hedera.node.config.testfixtures.HederaTestConfigBuilder; import com.hedera.pbj.runtime.io.buffer.Bytes; import com.swirlds.common.utility.CommonUtils; +import com.swirlds.platform.state.spi.ReadableSingletonStateBase; +import com.swirlds.platform.test.fixtures.state.MapReadableKVState; +import com.swirlds.state.spi.ReadableSingletonState; +import com.swirlds.state.spi.ReadableStates; import java.util.ArrayList; import java.util.List; import java.util.concurrent.atomic.AtomicReference; diff --git a/hedera-node/hedera-token-service-impl/src/test/java/com/hedera/node/app/service/token/impl/test/handlers/CryptoTransferHandlerTest.java b/hedera-node/hedera-token-service-impl/src/test/java/com/hedera/node/app/service/token/impl/test/handlers/CryptoTransferHandlerTest.java index f7f9ef8b2ed8..ec7d93128029 100644 --- a/hedera-node/hedera-token-service-impl/src/test/java/com/hedera/node/app/service/token/impl/test/handlers/CryptoTransferHandlerTest.java +++ b/hedera-node/hedera-token-service-impl/src/test/java/com/hedera/node/app/service/token/impl/test/handlers/CryptoTransferHandlerTest.java @@ -22,6 +22,8 @@ import static com.hedera.hapi.node.base.ResponseCodeEnum.NOT_SUPPORTED; import static com.hedera.hapi.node.base.ResponseCodeEnum.TOKEN_TRANSFER_LIST_SIZE_LIMIT_EXCEEDED; import static com.hedera.hapi.node.base.ResponseCodeEnum.TRANSFER_LIST_SIZE_LIMIT_EXCEEDED; +import static com.hedera.node.app.hapi.fees.usage.SingletonUsageProperties.USAGE_PROPERTIES; +import static com.hedera.node.app.service.mono.context.properties.PropertyNames.FEES_TOKEN_TRANSFER_USAGE_MULTIPLIER; import static com.hedera.node.app.service.mono.context.properties.PropertyNames.HEDERA_ALLOWANCES_IS_ENABLED; import static com.hedera.node.app.service.mono.context.properties.PropertyNames.LEDGER_NFT_TRANSFERS_MAX_LEN; import static com.hedera.node.app.service.mono.context.properties.PropertyNames.LEDGER_TOKEN_TRANSFERS_MAX_LEN; @@ -35,30 +37,54 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.BDDMockito.given; +import static org.mockito.Mockito.inOrder; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; +import com.hedera.hapi.node.base.AccountAmount; +import com.hedera.hapi.node.base.AccountID; import com.hedera.hapi.node.base.Key; +import com.hedera.hapi.node.base.SubType; import com.hedera.hapi.node.base.TokenID; import com.hedera.hapi.node.base.TokenTransferList; +import com.hedera.hapi.node.base.TransactionID; import com.hedera.hapi.node.base.TransferList; +import com.hedera.hapi.node.state.token.Account; +import com.hedera.hapi.node.state.token.Nft; import com.hedera.hapi.node.token.CryptoTransferTransactionBody; import com.hedera.hapi.node.transaction.TransactionBody; +import com.hedera.node.app.service.token.ReadableAccountStore; +import com.hedera.node.app.service.token.ReadableNftStore; +import com.hedera.node.app.service.token.ReadableTokenRelationStore; +import com.hedera.node.app.service.token.ReadableTokenStore; import com.hedera.node.app.service.token.impl.WritableAccountStore; import com.hedera.node.app.service.token.impl.handlers.CryptoTransferHandler; import com.hedera.node.app.service.token.records.CryptoCreateRecordBuilder; import com.hedera.node.app.service.token.records.CryptoTransferRecordBuilder; +import com.hedera.node.app.spi.fees.FeeCalculator; +import com.hedera.node.app.spi.fees.FeeContext; +import com.hedera.node.app.spi.fees.Fees; import com.hedera.node.app.spi.workflows.HandleContext; import com.hedera.node.app.spi.workflows.HandleException; +import com.hedera.node.app.spi.workflows.WarmupContext; +import com.hedera.node.app.workflows.dispatcher.ReadableStoreFactory; +import com.hedera.node.app.workflows.handle.HandleContextImpl; +import com.hedera.node.app.workflows.handle.WarmupContextImpl; import com.hedera.node.config.testfixtures.HederaTestConfigBuilder; import com.swirlds.config.api.Configuration; import com.swirlds.config.extensions.test.fixtures.TestConfigBuilder; +import java.util.ArrayList; import java.util.List; import org.assertj.core.api.Assertions; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InOrder; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; @@ -79,6 +105,197 @@ public void setUp() { given(handleContext.recordBuilder(CryptoTransferRecordBuilder.class)).willReturn(transferRecordBuilder); } + @Test + void warmTestNullContext() { + Assertions.assertThatThrownBy(() -> subject.warm(null)).isInstanceOf(NullPointerException.class); + } + + @Test + void warmTestAllAccountsTransferList() { + ReadableStoreFactory storeFactory = mock(ReadableStoreFactory.class); + ReadableAccountStore readableAccountStore = mock(ReadableAccountStore.class); + + TransactionBody txn = newCryptoTransfer(ACCT_3333_MINUS_10, ACCT_4444_PLUS_10); + + WarmupContext warmupContext = new WarmupContextImpl(txn, storeFactory); + when(storeFactory.getStore(ReadableAccountStore.class)).thenReturn(readableAccountStore); + + subject.warm(warmupContext); + + verify(readableAccountStore, times(1)).warm(ACCOUNT_3333); + verify(readableAccountStore, times(1)).warm(ACCOUNT_4444); + } + + @Test + void warmTokenDataTransferList() { + ReadableStoreFactory storeFactory = mock(ReadableStoreFactory.class); + ReadableAccountStore readableAccountStore = mock(ReadableAccountStore.class); + ReadableTokenStore readableTokenStore = mock(ReadableTokenStore.class); + ReadableNftStore readableNftStore = mock(ReadableNftStore.class); + ReadableTokenRelationStore readableTokenRelationStore = mock(ReadableTokenRelationStore.class); + Account account = mock(Account.class); + Nft nft = nftSl1; + + TransactionBody txn = newCryptoTransfer(TokenTransferList.newBuilder() + .token(TOKEN_2468) + .nftTransfers(SERIAL_1_FROM_3333_TO_4444 + .copyBuilder() + .receiverAccountID((AccountID) null) + .build()) + .build()); + + WarmupContext warmupContext = new WarmupContextImpl(txn, storeFactory); + when(storeFactory.getStore(ReadableAccountStore.class)).thenReturn(readableAccountStore); + when(storeFactory.getStore(ReadableTokenStore.class)).thenReturn(readableTokenStore); + when(storeFactory.getStore(ReadableNftStore.class)).thenReturn(readableNftStore); + when(storeFactory.getStore(ReadableTokenRelationStore.class)).thenReturn(readableTokenRelationStore); + when(readableNftStore.get(TOKEN_2468, 1L)).thenReturn(nft); + when(readableAccountStore.getAliasedAccountById(any(AccountID.class))).thenReturn(account); + + subject.warm(warmupContext); + + verify(readableTokenRelationStore, times(1)).warm(ACCOUNT_3333, TOKEN_2468); + verify(readableNftStore, times(1)).warm(any()); + } + + @Test + void calculateFeesHbarTransfer() { + config = defaultConfig() + .withValue(FEES_TOKEN_TRANSFER_USAGE_MULTIPLIER, 1) + .getOrCreateConfig(); + List acctAmounts = new ArrayList<>(); + List tokenTransferLists = new ArrayList<>(); + acctAmounts.add(aaWith(ACCOUNT_3333, -5)); + acctAmounts.add(aaWith(ACCOUNT_4444, 5)); + + CryptoTransferTransactionBody cryptoTransfer = CryptoTransferTransactionBody.newBuilder() + .transfers(TransferList.newBuilder().accountAmounts(acctAmounts)) + .tokenTransfers(tokenTransferLists) + .build(); + + FeeContext feeContext = mock(HandleContextImpl.class); + FeeCalculator feeCalculator = mock(FeeCalculator.class); + Fees fees = mock(Fees.class); + + when(feeContext.body()) + .thenReturn(TransactionBody.newBuilder() + .transactionID(TransactionID.newBuilder().accountID(ACCOUNT_3333)) + .cryptoTransfer(cryptoTransfer) + .build()); + when(feeContext.configuration()).thenReturn(config); + when(feeContext.feeCalculator(any())).thenReturn(feeCalculator); + when(feeCalculator.addBytesPerTransaction(anyLong())).thenReturn(feeCalculator); + when(feeCalculator.addRamByteSeconds(anyLong())).thenReturn(feeCalculator); + when(feeCalculator.calculate()).thenReturn(fees); + + subject.calculateFees(feeContext); + + // Not interested in return value from calculate, just that it was called and bpt and rbs were set approriately + InOrder inOrder = inOrder(feeContext, feeCalculator); + inOrder.verify(feeContext, times(1)).feeCalculator(SubType.DEFAULT); + inOrder.verify(feeCalculator, times(1)).addBytesPerTransaction(64L); + inOrder.verify(feeCalculator, times(1)).addRamByteSeconds(64L * USAGE_PROPERTIES.legacyReceiptStorageSecs()); + inOrder.verify(feeCalculator, times(1)).calculate(); + } + + @Test + void calculateFeesFtCustomFeesTransfer() { + config = defaultConfig() + .withValue(FEES_TOKEN_TRANSFER_USAGE_MULTIPLIER, 2) + .getOrCreateConfig(); + List acctAmounts = new ArrayList<>(); + List tokenTransferLists = new ArrayList<>(); + tokenTransferLists.add(TokenTransferList.newBuilder() + .token(fungibleTokenId) + .transfers( + AccountAmount.newBuilder() + .accountID(ACCOUNT_3333) + .amount(-5) + .build(), + AccountAmount.newBuilder() + .accountID(ACCOUNT_4444) + .amount(5) + .build()) + .build()); + + CryptoTransferTransactionBody cryptoTransfer = CryptoTransferTransactionBody.newBuilder() + .transfers(TransferList.newBuilder().accountAmounts(acctAmounts)) + .tokenTransfers(tokenTransferLists) + .build(); + + FeeContext feeContext = mock(HandleContextImpl.class); + FeeCalculator feeCalculator = mock(FeeCalculator.class); + Fees fees = mock(Fees.class); + + when(feeContext.body()) + .thenReturn(TransactionBody.newBuilder() + .transactionID(TransactionID.newBuilder().accountID(ACCOUNT_3333)) + .cryptoTransfer(cryptoTransfer) + .build()); + when(feeContext.configuration()).thenReturn(config); + when(feeContext.readableStore(ReadableTokenStore.class)).thenReturn(readableTokenStore); + when(feeContext.readableStore(ReadableTokenRelationStore.class)).thenReturn(readableTokenRelStore); + when(feeContext.readableStore(ReadableAccountStore.class)).thenReturn(readableAccountStore); + when(feeContext.feeCalculator(any())).thenReturn(feeCalculator); + when(feeCalculator.addBytesPerTransaction(anyLong())).thenReturn(feeCalculator); + when(feeCalculator.addRamByteSeconds(anyLong())).thenReturn(feeCalculator); + when(feeCalculator.calculate()).thenReturn(fees); + + subject.calculateFees(feeContext); + + // Not interested in return value from calculate, just that it was called and bpt and rbs were set approriately + InOrder inOrder = inOrder(feeContext, feeCalculator); + inOrder.verify(feeContext, times(1)).feeCalculator(SubType.TOKEN_FUNGIBLE_COMMON_WITH_CUSTOM_FEES); + inOrder.verify(feeCalculator, times(1)).addBytesPerTransaction(176L); + inOrder.verify(feeCalculator, times(1)).addRamByteSeconds(320L * USAGE_PROPERTIES.legacyReceiptStorageSecs()); + inOrder.verify(feeCalculator, times(1)).calculate(); + } + + @Test + void calculateFeesNftCustomFeesTransfer() { + config = defaultConfig() + .withValue(FEES_TOKEN_TRANSFER_USAGE_MULTIPLIER, 2) + .getOrCreateConfig(); + List acctAmounts = new ArrayList<>(); + List tokenTransferLists = new ArrayList<>(); + tokenTransferLists.add(TokenTransferList.newBuilder() + .token(nonFungibleTokenId) + .nftTransfers(SERIAL_1_FROM_3333_TO_4444) + .build()); + + CryptoTransferTransactionBody cryptoTransfer = CryptoTransferTransactionBody.newBuilder() + .transfers(TransferList.newBuilder().accountAmounts(acctAmounts)) + .tokenTransfers(tokenTransferLists) + .build(); + + FeeContext feeContext = mock(HandleContextImpl.class); + FeeCalculator feeCalculator = mock(FeeCalculator.class); + Fees fees = mock(Fees.class); + + when(feeContext.body()) + .thenReturn(TransactionBody.newBuilder() + .transactionID(TransactionID.newBuilder().accountID(ACCOUNT_3333)) + .cryptoTransfer(cryptoTransfer) + .build()); + when(feeContext.configuration()).thenReturn(config); + when(feeContext.readableStore(ReadableTokenStore.class)).thenReturn(readableTokenStore); + when(feeContext.readableStore(ReadableTokenRelationStore.class)).thenReturn(readableTokenRelStore); + when(feeContext.readableStore(ReadableAccountStore.class)).thenReturn(readableAccountStore); + when(feeContext.feeCalculator(any())).thenReturn(feeCalculator); + when(feeCalculator.addBytesPerTransaction(anyLong())).thenReturn(feeCalculator); + when(feeCalculator.addRamByteSeconds(anyLong())).thenReturn(feeCalculator); + when(feeCalculator.calculate()).thenReturn(fees); + + subject.calculateFees(feeContext); + + // Not interested in return value from calculate, just that it was called and bpt and rbs were set approriately + InOrder inOrder = inOrder(feeContext, feeCalculator); + inOrder.verify(feeContext, times(1)).feeCalculator(SubType.TOKEN_NON_FUNGIBLE_UNIQUE_WITH_CUSTOM_FEES); + inOrder.verify(feeCalculator, times(1)).addBytesPerTransaction(104L); + inOrder.verify(feeCalculator, times(1)).addRamByteSeconds(136L * USAGE_PROPERTIES.legacyReceiptStorageSecs()); + inOrder.verify(feeCalculator, times(1)).calculate(); + } + @Test void handleNullArgs() { Assertions.assertThatThrownBy(() -> subject.handle(null)).isInstanceOf(NullPointerException.class); diff --git a/hedera-node/hedera-token-service-impl/src/test/java/com/hedera/node/app/service/token/impl/test/handlers/CryptoUpdateHandlerTest.java b/hedera-node/hedera-token-service-impl/src/test/java/com/hedera/node/app/service/token/impl/test/handlers/CryptoUpdateHandlerTest.java index 3165da94e7d5..85e0a1261497 100644 --- a/hedera-node/hedera-token-service-impl/src/test/java/com/hedera/node/app/service/token/impl/test/handlers/CryptoUpdateHandlerTest.java +++ b/hedera-node/hedera-token-service-impl/src/test/java/com/hedera/node/app/service/token/impl/test/handlers/CryptoUpdateHandlerTest.java @@ -31,6 +31,7 @@ import static com.hedera.hapi.node.base.ResponseCodeEnum.REQUESTED_NUM_AUTOMATIC_ASSOCIATIONS_EXCEEDS_ASSOCIATION_LIMIT; import static com.hedera.hapi.node.base.ResponseCodeEnum.STAKING_NOT_ENABLED; import static com.hedera.node.app.service.mono.context.properties.PropertyNames.ENTITIES_MAX_LIFETIME; +import static com.hedera.node.app.service.token.impl.test.handlers.util.StateBuilderUtil.ACCOUNTS; import static com.hedera.node.app.spi.fixtures.Assertions.assertThrowsPreCheck; import static com.hedera.node.app.spi.fixtures.workflows.ExceptionConditions.responseCode; import static com.hedera.test.utils.KeyUtils.B_COMPLEX_KEY; @@ -43,7 +44,10 @@ import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.BDDMockito.given; +import static org.mockito.Mockito.inOrder; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.when; import com.hedera.hapi.node.base.AccountID; import com.hedera.hapi.node.base.Duration; @@ -58,12 +62,16 @@ import com.hedera.node.app.service.mono.config.HederaNumbers; import com.hedera.node.app.service.mono.context.properties.GlobalDynamicProperties; import com.hedera.node.app.service.mono.context.properties.PropertySource; +import com.hedera.node.app.service.token.ReadableAccountStore; import com.hedera.node.app.service.token.impl.CryptoSignatureWaiversImpl; import com.hedera.node.app.service.token.impl.ReadableAccountStoreImpl; import com.hedera.node.app.service.token.impl.WritableAccountStore; import com.hedera.node.app.service.token.impl.handlers.CryptoUpdateHandler; import com.hedera.node.app.service.token.impl.test.handlers.util.CryptoHandlerTestBase; import com.hedera.node.app.service.token.impl.validators.StakingValidator; +import com.hedera.node.app.spi.fees.FeeCalculator; +import com.hedera.node.app.spi.fees.FeeContext; +import com.hedera.node.app.spi.fees.Fees; import com.hedera.node.app.spi.fixtures.workflows.FakePreHandleContext; import com.hedera.node.app.spi.info.NetworkInfo; import com.hedera.node.app.spi.info.NodeInfo; @@ -85,6 +93,7 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InOrder; import org.mockito.Mock; import org.mockito.Mock.Strictness; import org.mockito.junit.jupiter.MockitoExtension; @@ -734,6 +743,44 @@ void failsIfAccountDeleted() { .has(responseCode(ACCOUNT_DELETED)); } + @Test + void testCalculateFeesHappyPath() { + FeeContext feeContext = mock(FeeContext.class); + FeeCalculator feeCalculator = mock(FeeCalculator.class); + + TransactionBody cryptoUpdateTransaction = new CryptoUpdateBuilder() + .withPayer(id) + .withAutoRenewPeriod(account.autoRenewSeconds()) + .withReceiverSigReq(account.receiverSigRequired()) + .withDeclineReward(account.declineReward()) + .withStakedNodeId(account.stakedNodeId()) + .withStakedAccountId( + account.stakedAccountId() == null + ? 0L + : account.stakedAccountId().accountNum()) + .withExpiration(account.expirationSecond()) + .withMaxAutoAssociations(account.maxAutoAssociations()) + .withMemo("") + .withKey(account.key()) + .withTarget(id) + .build(); + + when(feeContext.readableStore(ReadableAccountStore.class)).thenReturn(readableStore); + when(feeContext.body()).thenReturn(cryptoUpdateTransaction); + when(feeContext.configuration()).thenReturn(configuration); + when(feeContext.feeCalculator(any())).thenReturn(feeCalculator); + when(feeCalculator.addBytesPerTransaction(anyLong())).thenReturn(feeCalculator); + when(feeCalculator.addRamByteSeconds(anyLong())).thenReturn(feeCalculator); + when(feeCalculator.calculate()).thenReturn(Fees.FREE); + + subject.calculateFees(feeContext); + + InOrder inOrder = inOrder(feeCalculator); + inOrder.verify(feeCalculator, times(1)).addBytesPerTransaction(212L); + inOrder.verify(feeCalculator, times(2)).addRamByteSeconds(0L); + inOrder.verify(feeCalculator, times(1)).calculate(); + } + /** * A builder for {@link TransactionBody} instances. */ @@ -831,11 +878,21 @@ public CryptoUpdateHandlerTest.CryptoUpdateBuilder withStakedAccountId(final lon return this; } + public CryptoUpdateHandlerTest.CryptoUpdateBuilder withStakedAccountId(final Long id) { + this.stakedAccountId = id; + return this; + } + public CryptoUpdateHandlerTest.CryptoUpdateBuilder withStakedNodeId(final long id) { this.stakeNodeId = id; return this; } + public CryptoUpdateHandlerTest.CryptoUpdateBuilder withStakedNodeId(final Long id) { + this.stakeNodeId = id; + return this; + } + public CryptoUpdateHandlerTest.CryptoUpdateBuilder withReceiverSigReqWrapper(final boolean receiverSigReq) { this.receiverSigReqWrapper = Boolean.valueOf(receiverSigReq); ; @@ -871,6 +928,11 @@ public CryptoUpdateBuilder withKey(Key key) { this.opKey = key; return this; } + + public CryptoUpdateBuilder withPayer(AccountID payer) { + this.payer = payer; + return this; + } } private void updateReadableAccountStore(Map accountsToAdd) { diff --git a/hedera-node/hedera-token-service-impl/src/test/java/com/hedera/node/app/service/token/impl/test/handlers/FinalizeChildRecordHandlerTest.java b/hedera-node/hedera-token-service-impl/src/test/java/com/hedera/node/app/service/token/impl/test/handlers/FinalizeChildRecordHandlerTest.java index 146375ca2e71..432372d1fe1b 100644 --- a/hedera-node/hedera-token-service-impl/src/test/java/com/hedera/node/app/service/token/impl/test/handlers/FinalizeChildRecordHandlerTest.java +++ b/hedera-node/hedera-token-service-impl/src/test/java/com/hedera/node/app/service/token/impl/test/handlers/FinalizeChildRecordHandlerTest.java @@ -714,12 +714,17 @@ void handleCombinedHbarAndTokenTransfersSuccess() { .build()); // Make fungible token changes final var fungible321Change = token321Rel.balance() - 25; + final var fungible654Change = token654Rel.balance() - 1; writableTokenRelStore.put(token321Rel.copyBuilder().balance(25).build()); writableTokenRelStore.put(token321Rel .copyBuilder() .accountId(ACCOUNT_5656_ID) .balance(fungible321Change) .build()); + writableTokenRelStore.put( + token654Rel.copyBuilder().balance(fungible654Change).build()); + writableTokenRelStore.put( + token654Rel.copyBuilder().accountId(ACCOUNT_1212_ID).balance(1).build()); // Make NFT changes writableNftStore.put(nft.copyBuilder().ownerId(ACCOUNT_1212_ID).build()); diff --git a/hedera-node/hedera-token-service-impl/src/test/java/com/hedera/node/app/service/token/impl/test/handlers/FinalizeParentRecordHandlerTest.java b/hedera-node/hedera-token-service-impl/src/test/java/com/hedera/node/app/service/token/impl/test/handlers/FinalizeParentRecordHandlerTest.java index c0c8dc5f35d4..badb4742f097 100644 --- a/hedera-node/hedera-token-service-impl/src/test/java/com/hedera/node/app/service/token/impl/test/handlers/FinalizeParentRecordHandlerTest.java +++ b/hedera-node/hedera-token-service-impl/src/test/java/com/hedera/node/app/service/token/impl/test/handlers/FinalizeParentRecordHandlerTest.java @@ -929,12 +929,17 @@ void handleCombinedHbarAndTokenTransfersSuccess() { .build()); // Make fungible token changes final var fungible321Change = token321Rel.balance() - 25; + final var fungible654Change = token654Rel.balance() - 1; writableTokenRelStore.put(token321Rel.copyBuilder().balance(25).build()); writableTokenRelStore.put(token321Rel .copyBuilder() .accountId(ACCOUNT_5656_ID) .balance(fungible321Change) .build()); + writableTokenRelStore.put( + token654Rel.copyBuilder().balance(fungible654Change).build()); + writableTokenRelStore.put( + token654Rel.copyBuilder().accountId(ACCOUNT_1212_ID).balance(1).build()); // Make NFT changes writableNftStore.put(nft.copyBuilder().ownerId(ACCOUNT_1212_ID).build()); writableTokenStore = TestStoreFactory.newWritableStoreWithTokens(TOKEN_321_FUNGIBLE, token654); diff --git a/hedera-node/hedera-token-service-impl/src/test/java/com/hedera/node/app/service/token/impl/test/handlers/TokenBurnHandlerTest.java b/hedera-node/hedera-token-service-impl/src/test/java/com/hedera/node/app/service/token/impl/test/handlers/TokenBurnHandlerTest.java index 86b86319bea9..5af2216b37f1 100644 --- a/hedera-node/hedera-token-service-impl/src/test/java/com/hedera/node/app/service/token/impl/test/handlers/TokenBurnHandlerTest.java +++ b/hedera-node/hedera-token-service-impl/src/test/java/com/hedera/node/app/service/token/impl/test/handlers/TokenBurnHandlerTest.java @@ -74,7 +74,6 @@ import com.hedera.node.app.service.token.impl.test.handlers.util.ParityTestBase; import com.hedera.node.app.service.token.impl.validators.TokenSupplyChangeOpsValidator; import com.hedera.node.app.service.token.records.TokenBurnRecordBuilder; -import com.hedera.node.app.spi.fixtures.state.MapWritableKVState; import com.hedera.node.app.spi.fixtures.state.MapWritableStates; import com.hedera.node.app.spi.fixtures.workflows.FakePreHandleContext; import com.hedera.node.app.spi.metrics.StoreMetricsService; @@ -84,6 +83,7 @@ import com.hedera.node.app.workflows.handle.record.SingleTransactionRecordBuilderImpl; import com.hedera.node.config.testfixtures.HederaTestConfigBuilder; import com.swirlds.config.api.Configuration; +import com.swirlds.platform.test.fixtures.state.MapWritableKVState; import java.time.Instant; import java.util.Map; import org.assertj.core.api.Assertions; diff --git a/hedera-node/hedera-token-service-impl/src/test/java/com/hedera/node/app/service/token/impl/test/handlers/TokenFeeScheduleUpdateHandlerTest.java b/hedera-node/hedera-token-service-impl/src/test/java/com/hedera/node/app/service/token/impl/test/handlers/TokenFeeScheduleUpdateHandlerTest.java index 3fd44c75704d..f3d8eac08f7c 100644 --- a/hedera-node/hedera-token-service-impl/src/test/java/com/hedera/node/app/service/token/impl/test/handlers/TokenFeeScheduleUpdateHandlerTest.java +++ b/hedera-node/hedera-token-service-impl/src/test/java/com/hedera/node/app/service/token/impl/test/handlers/TokenFeeScheduleUpdateHandlerTest.java @@ -24,29 +24,46 @@ import static com.hedera.node.app.spi.fixtures.workflows.ExceptionConditions.responseCode; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.junit.jupiter.api.Assertions.assertEquals; import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyBoolean; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.BDDMockito.given; import static org.mockito.Mock.Strictness.LENIENT; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; import com.hedera.hapi.node.base.Key; +import com.hedera.hapi.node.base.SignatureMap; import com.hedera.hapi.node.base.TokenID; +import com.hedera.hapi.node.base.TransactionID; import com.hedera.hapi.node.state.token.Token; import com.hedera.hapi.node.token.TokenFeeScheduleUpdateTransactionBody; +import com.hedera.hapi.node.transaction.CustomFee; import com.hedera.hapi.node.transaction.TransactionBody; +import com.hedera.node.app.fees.FeeContextImpl; +import com.hedera.node.app.fees.FeeManager; import com.hedera.node.app.service.token.ReadableAccountStore; import com.hedera.node.app.service.token.ReadableTokenRelationStore; +import com.hedera.node.app.service.token.ReadableTokenStore; import com.hedera.node.app.service.token.impl.WritableTokenStore; import com.hedera.node.app.service.token.impl.handlers.TokenFeeScheduleUpdateHandler; import com.hedera.node.app.service.token.impl.test.handlers.util.CryptoTokenHandlerTestBase; import com.hedera.node.app.service.token.impl.validators.CustomFeesValidator; import com.hedera.node.app.service.token.records.TokenBaseRecordBuilder; -import com.hedera.node.app.spi.fixtures.state.MapWritableKVState; +import com.hedera.node.app.spi.fees.FeeCalculator; +import com.hedera.node.app.spi.fees.Fees; import com.hedera.node.app.spi.metrics.StoreMetricsService; import com.hedera.node.app.spi.workflows.HandleContext; import com.hedera.node.app.spi.workflows.HandleException; import com.hedera.node.app.spi.workflows.PreCheckException; import com.hedera.node.app.spi.workflows.PreHandleContext; +import com.hedera.node.app.workflows.TransactionInfo; +import com.hedera.node.app.workflows.dispatcher.ReadableStoreFactory; import com.hedera.node.config.testfixtures.HederaTestConfigBuilder; +import com.swirlds.platform.test.fixtures.state.MapWritableKVState; +import java.util.ArrayList; import java.util.List; import java.util.Set; import org.junit.jupiter.api.BeforeEach; @@ -233,6 +250,43 @@ void failsIfTxnHasNoTokenId() { .has(responseCode(INVALID_TOKEN_ID)); } + @Test + public void testCalculateFeesHappyPath() { + TransactionInfo txnInfo = mock(TransactionInfo.class); + FeeManager feeManager = mock(FeeManager.class); + FeeCalculator feeCalculator = mock(FeeCalculator.class); + ReadableStoreFactory storeFactory = mock(ReadableStoreFactory.class); + TransactionBody transactionBody = mock(TransactionBody.class); + TokenFeeScheduleUpdateTransactionBody tokenFeeScheduleUpdateTransactionBody = + mock(TokenFeeScheduleUpdateTransactionBody.class); + TransactionID transactionID = mock(TransactionID.class); + + List customFees = new ArrayList<>(); + customFees.add(withFixedFee(hbarFixedFee)); + customFees.add(withFractionalFee(fractionalFee)); + customFees.add(withRoyaltyFee(royaltyFee)); + + when(feeManager.createFeeCalculator(any(), any(), any(), anyInt(), anyInt(), any(), any(), anyBoolean(), any())) + .thenReturn(feeCalculator); + when(txnInfo.txBody()).thenReturn(transactionBody); + when(transactionBody.tokenFeeScheduleUpdateOrThrow()).thenReturn(tokenFeeScheduleUpdateTransactionBody); + when(tokenFeeScheduleUpdateTransactionBody.customFees()).thenReturn(customFees); + when(tokenFeeScheduleUpdateTransactionBody.tokenIdOrThrow()).thenReturn(fungibleTokenId); + when(storeFactory.getStore(ReadableTokenStore.class)).thenReturn(readableTokenStore); + when(transactionBody.transactionIDOrThrow()).thenReturn(transactionID); + when(transactionID.transactionValidStartOrThrow()).thenReturn(consensusTimestamp); + when(txnInfo.signatureMap()).thenReturn(SignatureMap.DEFAULT); + when(feeCalculator.addBytesPerTransaction(anyLong())).thenReturn(feeCalculator); + when(feeCalculator.addRamByteSeconds(anyLong())).thenReturn(feeCalculator); + when(feeCalculator.calculate()).thenReturn(Fees.FREE); + + final var feeContext = new FeeContextImpl( + consensusInstant, txnInfo, payerKey, payerId, feeManager, storeFactory, configuration, null, -1); + + final var calculateFees = subject.calculateFees(feeContext); + assertEquals(calculateFees, Fees.FREE); + } + private void givenTxn() { txn = TransactionBody.newBuilder() .tokenFeeScheduleUpdate(TokenFeeScheduleUpdateTransactionBody.newBuilder() diff --git a/hedera-node/hedera-token-service-impl/src/test/java/com/hedera/node/app/service/token/impl/test/handlers/TokenGetInfoHandlerTest.java b/hedera-node/hedera-token-service-impl/src/test/java/com/hedera/node/app/service/token/impl/test/handlers/TokenGetInfoHandlerTest.java index f1ce96b4fdc9..12b942a152a3 100644 --- a/hedera-node/hedera-token-service-impl/src/test/java/com/hedera/node/app/service/token/impl/test/handlers/TokenGetInfoHandlerTest.java +++ b/hedera-node/hedera-token-service-impl/src/test/java/com/hedera/node/app/service/token/impl/test/handlers/TokenGetInfoHandlerTest.java @@ -53,11 +53,11 @@ import com.hedera.node.app.service.token.impl.ReadableTokenStoreImpl; import com.hedera.node.app.service.token.impl.handlers.TokenGetInfoHandler; import com.hedera.node.app.service.token.impl.test.handlers.util.CryptoTokenHandlerTestBase; -import com.hedera.node.app.spi.fixtures.state.MapReadableKVState; import com.hedera.node.app.spi.workflows.PreCheckException; import com.hedera.node.app.spi.workflows.QueryContext; import com.hedera.node.config.converter.BytesConverter; import com.hedera.node.config.testfixtures.HederaTestConfigBuilder; +import com.swirlds.platform.test.fixtures.state.MapReadableKVState; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; diff --git a/hedera-node/hedera-token-service-impl/src/test/java/com/hedera/node/app/service/token/impl/test/handlers/TokenGetNftInfoHandlerTest.java b/hedera-node/hedera-token-service-impl/src/test/java/com/hedera/node/app/service/token/impl/test/handlers/TokenGetNftInfoHandlerTest.java index a09a96a2cce0..7e6c7b9adc79 100644 --- a/hedera-node/hedera-token-service-impl/src/test/java/com/hedera/node/app/service/token/impl/test/handlers/TokenGetNftInfoHandlerTest.java +++ b/hedera-node/hedera-token-service-impl/src/test/java/com/hedera/node/app/service/token/impl/test/handlers/TokenGetNftInfoHandlerTest.java @@ -44,12 +44,12 @@ import com.hedera.node.app.service.token.impl.ReadableTokenStoreImpl; import com.hedera.node.app.service.token.impl.handlers.TokenGetNftInfoHandler; import com.hedera.node.app.service.token.impl.test.handlers.util.CryptoTokenHandlerTestBase; -import com.hedera.node.app.spi.fixtures.state.MapReadableKVState; import com.hedera.node.app.spi.workflows.PreCheckException; import com.hedera.node.app.spi.workflows.QueryContext; import com.hedera.node.config.converter.BytesConverter; import com.hedera.node.config.testfixtures.HederaTestConfigBuilder; import com.hedera.pbj.runtime.io.buffer.Bytes; +import com.swirlds.platform.test.fixtures.state.MapReadableKVState; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; diff --git a/hedera-node/hedera-token-service-impl/src/test/java/com/hedera/node/app/service/token/impl/test/handlers/TokenGrantKycToAccountHandlerTest.java b/hedera-node/hedera-token-service-impl/src/test/java/com/hedera/node/app/service/token/impl/test/handlers/TokenGrantKycToAccountHandlerTest.java index b4c39eb19cf6..18a93de2dd6d 100644 --- a/hedera-node/hedera-token-service-impl/src/test/java/com/hedera/node/app/service/token/impl/test/handlers/TokenGrantKycToAccountHandlerTest.java +++ b/hedera-node/hedera-token-service-impl/src/test/java/com/hedera/node/app/service/token/impl/test/handlers/TokenGrantKycToAccountHandlerTest.java @@ -56,7 +56,6 @@ import com.hedera.node.app.service.token.impl.handlers.BaseTokenHandler; import com.hedera.node.app.service.token.impl.handlers.TokenGrantKycToAccountHandler; import com.hedera.node.app.service.token.impl.test.handlers.util.TokenHandlerTestBase; -import com.hedera.node.app.spi.fixtures.state.MapReadableKVState; import com.hedera.node.app.spi.fixtures.workflows.FakePreHandleContext; import com.hedera.node.app.spi.validation.EntityType; import com.hedera.node.app.spi.validation.ExpiryValidator; @@ -64,6 +63,7 @@ import com.hedera.node.app.spi.workflows.HandleException; import com.hedera.node.app.spi.workflows.PreCheckException; import com.hedera.pbj.runtime.io.buffer.Bytes; +import com.swirlds.platform.test.fixtures.state.MapReadableKVState; import java.util.Collections; import org.assertj.core.api.Assertions; import org.junit.jupiter.api.BeforeEach; diff --git a/hedera-node/hedera-token-service-impl/src/test/java/com/hedera/node/app/service/token/impl/test/handlers/TokenHandlersTest.java b/hedera-node/hedera-token-service-impl/src/test/java/com/hedera/node/app/service/token/impl/test/handlers/TokenHandlersTest.java new file mode 100644 index 000000000000..a809580a4f24 --- /dev/null +++ b/hedera-node/hedera-token-service-impl/src/test/java/com/hedera/node/app/service/token/impl/test/handlers/TokenHandlersTest.java @@ -0,0 +1,333 @@ +/* + * Copyright (C) 2024 Hedera Hashgraph, LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.hedera.node.app.service.token.impl.test.handlers; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.mockito.Mockito.mock; + +import com.hedera.node.app.service.token.impl.handlers.CryptoAddLiveHashHandler; +import com.hedera.node.app.service.token.impl.handlers.CryptoApproveAllowanceHandler; +import com.hedera.node.app.service.token.impl.handlers.CryptoCreateHandler; +import com.hedera.node.app.service.token.impl.handlers.CryptoDeleteAllowanceHandler; +import com.hedera.node.app.service.token.impl.handlers.CryptoDeleteHandler; +import com.hedera.node.app.service.token.impl.handlers.CryptoDeleteLiveHashHandler; +import com.hedera.node.app.service.token.impl.handlers.CryptoGetAccountBalanceHandler; +import com.hedera.node.app.service.token.impl.handlers.CryptoGetAccountInfoHandler; +import com.hedera.node.app.service.token.impl.handlers.CryptoGetAccountRecordsHandler; +import com.hedera.node.app.service.token.impl.handlers.CryptoGetLiveHashHandler; +import com.hedera.node.app.service.token.impl.handlers.CryptoGetStakersHandler; +import com.hedera.node.app.service.token.impl.handlers.CryptoTransferHandler; +import com.hedera.node.app.service.token.impl.handlers.CryptoUpdateHandler; +import com.hedera.node.app.service.token.impl.handlers.TokenAccountWipeHandler; +import com.hedera.node.app.service.token.impl.handlers.TokenAssociateToAccountHandler; +import com.hedera.node.app.service.token.impl.handlers.TokenBurnHandler; +import com.hedera.node.app.service.token.impl.handlers.TokenCreateHandler; +import com.hedera.node.app.service.token.impl.handlers.TokenDeleteHandler; +import com.hedera.node.app.service.token.impl.handlers.TokenDissociateFromAccountHandler; +import com.hedera.node.app.service.token.impl.handlers.TokenFeeScheduleUpdateHandler; +import com.hedera.node.app.service.token.impl.handlers.TokenFreezeAccountHandler; +import com.hedera.node.app.service.token.impl.handlers.TokenGetAccountNftInfosHandler; +import com.hedera.node.app.service.token.impl.handlers.TokenGetInfoHandler; +import com.hedera.node.app.service.token.impl.handlers.TokenGetNftInfoHandler; +import com.hedera.node.app.service.token.impl.handlers.TokenGetNftInfosHandler; +import com.hedera.node.app.service.token.impl.handlers.TokenGrantKycToAccountHandler; +import com.hedera.node.app.service.token.impl.handlers.TokenHandlers; +import com.hedera.node.app.service.token.impl.handlers.TokenMintHandler; +import com.hedera.node.app.service.token.impl.handlers.TokenPauseHandler; +import com.hedera.node.app.service.token.impl.handlers.TokenRevokeKycFromAccountHandler; +import com.hedera.node.app.service.token.impl.handlers.TokenUnfreezeAccountHandler; +import com.hedera.node.app.service.token.impl.handlers.TokenUnpauseHandler; +import com.hedera.node.app.service.token.impl.handlers.TokenUpdateHandler; +import com.hedera.node.app.service.token.impl.handlers.TokenUpdateNftsHandler; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +public class TokenHandlersTest { + + private CryptoCreateHandler cryptoCreateHandler; + private CryptoUpdateHandler cryptoUpdateHandler; + private CryptoTransferHandler cryptoTransferHandler; + private CryptoDeleteHandler cryptoDeleteHandler; + private CryptoApproveAllowanceHandler cryptoApproveAllowanceHandler; + private CryptoDeleteAllowanceHandler cryptoDeleteAllowanceHandler; + private CryptoAddLiveHashHandler cryptoAddLiveHashHandler; + private CryptoDeleteLiveHashHandler cryptoDeleteLiveHashHandler; + private TokenCreateHandler tokenCreateHandler; + private TokenUpdateHandler tokenUpdateHandler; + private TokenMintHandler tokenMintHandler; + private TokenBurnHandler tokenBurnHandler; + private TokenDeleteHandler tokenDeleteHandler; + private TokenAccountWipeHandler tokenAccountWipeHandler; + private TokenFreezeAccountHandler tokenFreezeAccountHandler; + private TokenUnfreezeAccountHandler tokenUnfreezeAccountHandler; + private TokenGrantKycToAccountHandler tokenGrantKycToAccountHandler; + private TokenRevokeKycFromAccountHandler tokenRevokeKycFromAccountHandler; + private TokenAssociateToAccountHandler tokenAssociateToAccountHandler; + private TokenDissociateFromAccountHandler tokenDissociateFromAccountHandler; + private TokenFeeScheduleUpdateHandler tokenFeeScheduleUpdateHandler; + private TokenPauseHandler tokenPauseHandler; + private TokenUnpauseHandler tokenUnpauseHandler; + private CryptoGetAccountBalanceHandler cryptoGetAccountBalanceHandler; + private CryptoGetAccountInfoHandler cryptoGetAccountInfoHandler; + private CryptoGetAccountRecordsHandler cryptoGetAccountRecordsHandler; + private CryptoGetLiveHashHandler cryptoGetLiveHashHandler; + private CryptoGetStakersHandler cryptoGetStakersHandler; + private TokenGetInfoHandler tokenGetInfoHandler; + private TokenGetAccountNftInfosHandler tokenGetAccountNftInfosHandler; + private TokenGetNftInfoHandler tokenGetNftInfoHandler; + private TokenGetNftInfosHandler tokenGetNftInfosHandler; + private TokenUpdateNftsHandler tokenUpdateNftsHandler; + + private TokenHandlers tokenHandlers; + + @BeforeEach + public void setUp() { + cryptoCreateHandler = mock(CryptoCreateHandler.class); + cryptoUpdateHandler = mock(CryptoUpdateHandler.class); + cryptoTransferHandler = mock(CryptoTransferHandler.class); + cryptoDeleteHandler = mock(CryptoDeleteHandler.class); + cryptoApproveAllowanceHandler = mock(CryptoApproveAllowanceHandler.class); + cryptoDeleteAllowanceHandler = mock(CryptoDeleteAllowanceHandler.class); + cryptoAddLiveHashHandler = mock(CryptoAddLiveHashHandler.class); + cryptoDeleteLiveHashHandler = mock(CryptoDeleteLiveHashHandler.class); + tokenCreateHandler = mock(TokenCreateHandler.class); + tokenUpdateHandler = mock(TokenUpdateHandler.class); + tokenMintHandler = mock(TokenMintHandler.class); + tokenBurnHandler = mock(TokenBurnHandler.class); + tokenDeleteHandler = mock(TokenDeleteHandler.class); + tokenAccountWipeHandler = mock(TokenAccountWipeHandler.class); + tokenFreezeAccountHandler = mock(TokenFreezeAccountHandler.class); + tokenUnfreezeAccountHandler = mock(TokenUnfreezeAccountHandler.class); + tokenGrantKycToAccountHandler = mock(TokenGrantKycToAccountHandler.class); + tokenRevokeKycFromAccountHandler = mock(TokenRevokeKycFromAccountHandler.class); + tokenAssociateToAccountHandler = mock(TokenAssociateToAccountHandler.class); + tokenDissociateFromAccountHandler = mock(TokenDissociateFromAccountHandler.class); + tokenFeeScheduleUpdateHandler = mock(TokenFeeScheduleUpdateHandler.class); + tokenPauseHandler = mock(TokenPauseHandler.class); + tokenUnpauseHandler = mock(TokenUnpauseHandler.class); + cryptoGetAccountBalanceHandler = mock(CryptoGetAccountBalanceHandler.class); + cryptoGetAccountInfoHandler = mock(CryptoGetAccountInfoHandler.class); + cryptoGetAccountRecordsHandler = mock(CryptoGetAccountRecordsHandler.class); + cryptoGetLiveHashHandler = mock(CryptoGetLiveHashHandler.class); + cryptoGetStakersHandler = mock(CryptoGetStakersHandler.class); + tokenGetInfoHandler = mock(TokenGetInfoHandler.class); + tokenGetAccountNftInfosHandler = mock(TokenGetAccountNftInfosHandler.class); + tokenGetNftInfoHandler = mock(TokenGetNftInfoHandler.class); + tokenGetNftInfosHandler = mock(TokenGetNftInfosHandler.class); + tokenUpdateNftsHandler = mock(TokenUpdateNftsHandler.class); + + tokenHandlers = new TokenHandlers( + cryptoCreateHandler, + cryptoUpdateHandler, + cryptoTransferHandler, + cryptoDeleteHandler, + cryptoApproveAllowanceHandler, + cryptoDeleteAllowanceHandler, + cryptoAddLiveHashHandler, + cryptoDeleteLiveHashHandler, + tokenCreateHandler, + tokenUpdateHandler, + tokenMintHandler, + tokenBurnHandler, + tokenDeleteHandler, + tokenAccountWipeHandler, + tokenFreezeAccountHandler, + tokenUnfreezeAccountHandler, + tokenGrantKycToAccountHandler, + tokenRevokeKycFromAccountHandler, + tokenAssociateToAccountHandler, + tokenDissociateFromAccountHandler, + tokenFeeScheduleUpdateHandler, + tokenPauseHandler, + tokenUnpauseHandler, + cryptoGetAccountBalanceHandler, + cryptoGetAccountInfoHandler, + cryptoGetAccountRecordsHandler, + cryptoGetLiveHashHandler, + cryptoGetStakersHandler, + tokenGetInfoHandler, + tokenGetAccountNftInfosHandler, + tokenGetNftInfoHandler, + tokenGetNftInfosHandler, + tokenUpdateNftsHandler); + } + + @Test + public void cryptoCreateHandlerReturnsCorrectInstance() { + assertEquals(cryptoCreateHandler, tokenHandlers.cryptoCreateHandler()); + } + + @Test + public void cryptoUpdateHandlerReturnsCorrectInstance() { + assertEquals(cryptoUpdateHandler, tokenHandlers.cryptoUpdateHandler()); + } + + @Test + public void cryptoTransferHandlerReturnsCorrectInstance() { + assertEquals(cryptoTransferHandler, tokenHandlers.cryptoTransferHandler()); + } + + @Test + public void cryptoDeleteHandlerReturnsCorrectInstance() { + assertEquals(cryptoDeleteHandler, tokenHandlers.cryptoDeleteHandler()); + } + + @Test + public void cryptoApproveAllowanceHandlerReturnsCorrectInstance() { + assertEquals(cryptoApproveAllowanceHandler, tokenHandlers.cryptoApproveAllowanceHandler()); + } + + @Test + public void cryptoDeleteAllowanceHandlerReturnsCorrectInstance() { + assertEquals(cryptoDeleteAllowanceHandler, tokenHandlers.cryptoDeleteAllowanceHandler()); + } + + @Test + public void cryptoAddLiveHashHandlerReturnsCorrectInstance() { + assertEquals(cryptoAddLiveHashHandler, tokenHandlers.cryptoAddLiveHashHandler()); + } + + @Test + public void cryptoDeleteLiveHashHandlerReturnsCorrectInstance() { + assertEquals(cryptoDeleteLiveHashHandler, tokenHandlers.cryptoDeleteLiveHashHandler()); + } + + @Test + public void tokenCreateHandlerReturnsCorrectInstance() { + assertEquals(tokenCreateHandler, tokenHandlers.tokenCreateHandler()); + } + + @Test + public void tokenUpdateHandlerReturnsCorrectInstance() { + assertEquals(tokenUpdateHandler, tokenHandlers.tokenUpdateHandler()); + } + + @Test + public void tokenMintHandlerReturnsCorrectInstance() { + assertEquals(tokenMintHandler, tokenHandlers.tokenMintHandler()); + } + + @Test + public void tokenBurnHandlerReturnsCorrectInstance() { + assertEquals(tokenBurnHandler, tokenHandlers.tokenBurnHandler()); + } + + @Test + public void tokenDeleteHandlerReturnsCorrectInstance() { + assertEquals(tokenDeleteHandler, tokenHandlers.tokenDeleteHandler()); + } + + @Test + public void tokenAccountWipeHandlerReturnsCorrectInstance() { + assertEquals(tokenAccountWipeHandler, tokenHandlers.tokenAccountWipeHandler()); + } + + @Test + public void tokenFreezeAccountHandlerReturnsCorrectInstance() { + assertEquals(tokenFreezeAccountHandler, tokenHandlers.tokenFreezeAccountHandler()); + } + + @Test + public void tokenUnfreezeAccountHandlerReturnsCorrectInstance() { + assertEquals(tokenUnfreezeAccountHandler, tokenHandlers.tokenUnfreezeAccountHandler()); + } + + @Test + public void tokenGrantKycToAccountHandlerReturnsCorrectInstance() { + assertEquals(tokenGrantKycToAccountHandler, tokenHandlers.tokenGrantKycToAccountHandler()); + } + + @Test + public void tokenRevokeKycFromAccountHandlerReturnsCorrectInstance() { + assertEquals(tokenRevokeKycFromAccountHandler, tokenHandlers.tokenRevokeKycFromAccountHandler()); + } + + @Test + public void tokenAssociateToAccountHandlerReturnsCorrectInstance() { + assertEquals(tokenAssociateToAccountHandler, tokenHandlers.tokenAssociateToAccountHandler()); + } + + @Test + public void tokenDissociateFromAccountHandlerReturnsCorrectInstance() { + assertEquals(tokenDissociateFromAccountHandler, tokenHandlers.tokenDissociateFromAccountHandler()); + } + + @Test + public void tokenFeeScheduleUpdateHandlerReturnsCorrectInstance() { + assertEquals(tokenFeeScheduleUpdateHandler, tokenHandlers.tokenFeeScheduleUpdateHandler()); + } + + @Test + public void tokenPauseHandlerReturnsCorrectInstance() { + assertEquals(tokenPauseHandler, tokenHandlers.tokenPauseHandler()); + } + + @Test + public void tokenUnpauseHandlerReturnsCorrectInstance() { + assertEquals(tokenUnpauseHandler, tokenHandlers.tokenUnpauseHandler()); + } + + @Test + public void cryptoGetAccountBalanceHandlerReturnsCorrectInstance() { + assertEquals(cryptoGetAccountBalanceHandler, tokenHandlers.cryptoGetAccountBalanceHandler()); + } + + @Test + public void cryptoGetAccountInfoHandlerReturnsCorrectInstance() { + assertEquals(cryptoGetAccountInfoHandler, tokenHandlers.cryptoGetAccountInfoHandler()); + } + + @Test + public void cryptoGetAccountRecordsHandlerReturnsCorrectInstance() { + assertEquals(cryptoGetAccountRecordsHandler, tokenHandlers.cryptoGetAccountRecordsHandler()); + } + + @Test + public void cryptoGetLiveHashHandlerReturnsCorrectInstance() { + assertEquals(cryptoGetLiveHashHandler, tokenHandlers.cryptoGetLiveHashHandler()); + } + + @Test + public void cryptoGetStakersHandlerReturnsCorrectInstance() { + assertEquals(cryptoGetStakersHandler, tokenHandlers.cryptoGetStakersHandler()); + } + + @Test + public void tokenGetInfoHandlerReturnsCorrectInstance() { + assertEquals(tokenGetInfoHandler, tokenHandlers.tokenGetInfoHandler()); + } + + @Test + public void tokenGetAccountNftInfosHandlerReturnsCorrectInstance() { + assertEquals(tokenGetAccountNftInfosHandler, tokenHandlers.tokenGetAccountNftInfosHandler()); + } + + @Test + public void tokenGetNftInfoHandlerReturnsCorrectInstance() { + assertEquals(tokenGetNftInfoHandler, tokenHandlers.tokenGetNftInfoHandler()); + } + + @Test + public void tokenGetNftInfosHandlerReturnsCorrectInstance() { + assertEquals(tokenGetNftInfosHandler, tokenHandlers.tokenGetNftInfosHandler()); + } + + @Test + public void tokenUpdateNftsHandlerReturnsCorrectInstance() { + assertEquals(tokenUpdateNftsHandler, tokenHandlers.tokenUpdateNftsHandler()); + } +} diff --git a/hedera-node/hedera-token-service-impl/src/test/java/com/hedera/node/app/service/token/impl/test/handlers/TokenPauseHandlerTest.java b/hedera-node/hedera-token-service-impl/src/test/java/com/hedera/node/app/service/token/impl/test/handlers/TokenPauseHandlerTest.java index 7d0bbcbcc288..70b2d78d8537 100644 --- a/hedera-node/hedera-token-service-impl/src/test/java/com/hedera/node/app/service/token/impl/test/handlers/TokenPauseHandlerTest.java +++ b/hedera-node/hedera-token-service-impl/src/test/java/com/hedera/node/app/service/token/impl/test/handlers/TokenPauseHandlerTest.java @@ -18,16 +18,23 @@ import static com.hedera.hapi.node.base.ResponseCodeEnum.INVALID_TOKEN_ID; import static com.hedera.node.app.spi.fixtures.Assertions.assertThrowsPreCheck; +import static org.assertj.core.api.AssertionsForClassTypes.assertThatCode; +import static org.assertj.core.api.AssertionsForClassTypes.assertThatThrownBy; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.BDDMockito.given; import static org.mockito.Mock.Strictness.LENIENT; +import static org.mockito.Mockito.inOrder; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; import com.hedera.hapi.node.base.AccountID; import com.hedera.hapi.node.base.Key; +import com.hedera.hapi.node.base.SubType; import com.hedera.hapi.node.base.TokenID; import com.hedera.hapi.node.base.TransactionID; import com.hedera.hapi.node.state.token.Account; @@ -41,14 +48,17 @@ import com.hedera.node.app.service.token.impl.handlers.TokenPauseHandler; import com.hedera.node.app.service.token.impl.test.handlers.util.TokenHandlerTestBase; import com.hedera.node.app.service.token.records.TokenBaseRecordBuilder; -import com.hedera.node.app.spi.fixtures.state.MapReadableKVState; +import com.hedera.node.app.spi.fees.FeeCalculator; +import com.hedera.node.app.spi.fees.FeeContext; import com.hedera.node.app.spi.fixtures.workflows.FakePreHandleContext; import com.hedera.node.app.spi.workflows.HandleContext; import com.hedera.node.app.spi.workflows.HandleException; import com.hedera.node.app.spi.workflows.PreCheckException; +import com.swirlds.platform.test.fixtures.state.MapReadableKVState; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InOrder; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; @@ -84,6 +94,43 @@ void setUp() throws PreCheckException { given(handleContext.recordBuilder(any())).willReturn(recordBuilder); } + @Test + public void testPureChecksThrowsExceptionWhenDoesNotHaveToken() { + TokenPauseTransactionBody transactionBody = mock(TokenPauseTransactionBody.class); + TransactionBody transaction = mock(TransactionBody.class); + given(handleContext.body()).willReturn(transaction); + given(transaction.tokenPauseOrThrow()).willReturn(transactionBody); + given(transactionBody.hasToken()).willReturn(false); + + assertThatThrownBy(() -> subject.pureChecks(handleContext.body())).isInstanceOf(PreCheckException.class); + } + + @Test + public void testPureChecksDoesNotThrowExceptionWhenHasToken() { + TokenPauseTransactionBody transactionBody = mock(TokenPauseTransactionBody.class); + TransactionBody transaction = mock(TransactionBody.class); + given(handleContext.body()).willReturn(transaction); + given(transaction.tokenPauseOrThrow()).willReturn(transactionBody); + given(transactionBody.hasToken()).willReturn(true); + + assertThatCode(() -> subject.pureChecks(handleContext.body())).doesNotThrowAnyException(); + } + + @Test + public void testCalculateFeesInvocations() { + FeeContext feeContext = mock(FeeContext.class); + FeeCalculator feeCalculator = mock(FeeCalculator.class); + when(feeContext.feeCalculator(SubType.DEFAULT)).thenReturn(feeCalculator); + when(feeCalculator.addBytesPerTransaction(anyLong())).thenReturn(feeCalculator); + + subject.calculateFees(feeContext); + + InOrder inOrder = inOrder(feeContext, feeCalculator); + inOrder.verify(feeContext).feeCalculator(SubType.DEFAULT); + inOrder.verify(feeCalculator).addBytesPerTransaction(anyLong()); + inOrder.verify(feeCalculator).calculate(); + } + @Test void pausesUnPausedToken() { unPauseKnownToken(); diff --git a/hedera-node/hedera-token-service-impl/src/test/java/com/hedera/node/app/service/token/impl/test/handlers/TokenUnpauseHandlerTest.java b/hedera-node/hedera-token-service-impl/src/test/java/com/hedera/node/app/service/token/impl/test/handlers/TokenUnpauseHandlerTest.java index 6a64cb3659cb..91cab57bb4a8 100644 --- a/hedera-node/hedera-token-service-impl/src/test/java/com/hedera/node/app/service/token/impl/test/handlers/TokenUnpauseHandlerTest.java +++ b/hedera-node/hedera-token-service-impl/src/test/java/com/hedera/node/app/service/token/impl/test/handlers/TokenUnpauseHandlerTest.java @@ -18,15 +18,22 @@ import static com.hedera.hapi.node.base.ResponseCodeEnum.INVALID_TOKEN_ID; import static com.hedera.node.app.spi.fixtures.Assertions.assertThrowsPreCheck; +import static org.assertj.core.api.AssertionsForClassTypes.assertThatCode; +import static org.assertj.core.api.AssertionsForClassTypes.assertThatThrownBy; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.BDDMockito.given; import static org.mockito.Mock.Strictness.LENIENT; +import static org.mockito.Mockito.inOrder; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; import com.hedera.hapi.node.base.Key; +import com.hedera.hapi.node.base.SubType; import com.hedera.hapi.node.base.TokenID; import com.hedera.hapi.node.base.TransactionID; import com.hedera.hapi.node.state.token.Account; @@ -40,14 +47,17 @@ import com.hedera.node.app.service.token.impl.handlers.TokenUnpauseHandler; import com.hedera.node.app.service.token.impl.test.handlers.util.TokenHandlerTestBase; import com.hedera.node.app.service.token.records.TokenBaseRecordBuilder; -import com.hedera.node.app.spi.fixtures.state.MapReadableKVState; +import com.hedera.node.app.spi.fees.FeeCalculator; +import com.hedera.node.app.spi.fees.FeeContext; import com.hedera.node.app.spi.fixtures.workflows.FakePreHandleContext; import com.hedera.node.app.spi.workflows.HandleContext; import com.hedera.node.app.spi.workflows.HandleException; import com.hedera.node.app.spi.workflows.PreCheckException; +import com.swirlds.platform.test.fixtures.state.MapReadableKVState; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InOrder; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; @@ -81,6 +91,43 @@ void setUp() throws PreCheckException { given(handleContext.recordBuilder(any())).willReturn(recordBuilder); } + @Test + public void testPureChecksThrowsExceptionWhenDoesNotHaveToken() { + TokenUnpauseTransactionBody transactionBody = mock(TokenUnpauseTransactionBody.class); + TransactionBody transaction = mock(TransactionBody.class); + given(handleContext.body()).willReturn(transaction); + given(transaction.tokenUnpauseOrThrow()).willReturn(transactionBody); + given(transactionBody.hasToken()).willReturn(false); + + assertThatThrownBy(() -> subject.pureChecks(handleContext.body())).isInstanceOf(PreCheckException.class); + } + + @Test + public void testPureChecksDoesNotThrowExceptionWhenHasToken() { + TokenUnpauseTransactionBody transactionBody = mock(TokenUnpauseTransactionBody.class); + TransactionBody transaction = mock(TransactionBody.class); + given(handleContext.body()).willReturn(transaction); + given(transaction.tokenUnpauseOrThrow()).willReturn(transactionBody); + given(transactionBody.hasToken()).willReturn(true); + + assertThatCode(() -> subject.pureChecks(handleContext.body())).doesNotThrowAnyException(); + } + + @Test + public void testCalculateFeesInvocations() { + FeeContext feeContext = mock(FeeContext.class); + FeeCalculator feeCalculator = mock(FeeCalculator.class); + when(feeContext.feeCalculator(SubType.DEFAULT)).thenReturn(feeCalculator); + when(feeCalculator.addBytesPerTransaction(anyLong())).thenReturn(feeCalculator); + + subject.calculateFees(feeContext); + + InOrder inOrder = inOrder(feeContext, feeCalculator); + inOrder.verify(feeContext).feeCalculator(SubType.DEFAULT); + inOrder.verify(feeCalculator).addBytesPerTransaction(anyLong()); + inOrder.verify(feeCalculator).calculate(); + } + @Test void unPausesToken() { pauseKnownToken(); diff --git a/hedera-node/hedera-token-service-impl/src/test/java/com/hedera/node/app/service/token/impl/test/handlers/TokenUpdateHandlerParityTest.java b/hedera-node/hedera-token-service-impl/src/test/java/com/hedera/node/app/service/token/impl/test/handlers/TokenUpdateHandlerParityTest.java index 009af1bd966f..129fbb1bea01 100644 --- a/hedera-node/hedera-token-service-impl/src/test/java/com/hedera/node/app/service/token/impl/test/handlers/TokenUpdateHandlerParityTest.java +++ b/hedera-node/hedera-token-service-impl/src/test/java/com/hedera/node/app/service/token/impl/test/handlers/TokenUpdateHandlerParityTest.java @@ -32,21 +32,30 @@ import static com.hedera.test.factories.scenarios.TokenUpdateScenarios.UPDATE_REPLACING_TREASURY_AS_CUSTOM_PAYER; import static com.hedera.test.factories.scenarios.TokenUpdateScenarios.UPDATE_REPLACING_TREASURY_AS_PAYER; import static com.hedera.test.factories.scenarios.TokenUpdateScenarios.UPDATE_REPLACING_WITH_MISSING_TREASURY; -import static com.hedera.test.factories.scenarios.TokenUpdateScenarios.UPDATE_WITH_FREEZE_KEYED_TOKEN; -import static com.hedera.test.factories.scenarios.TokenUpdateScenarios.UPDATE_WITH_KYC_KEYED_TOKEN; +import static com.hedera.test.factories.scenarios.TokenUpdateScenarios.UPDATE_WITH_FREEZE_KEYED_TOKEN_REPLACEMENT_KEY_NOT_REQUIRED; +import static com.hedera.test.factories.scenarios.TokenUpdateScenarios.UPDATE_WITH_KYC_KEYED_TOKEN_REPLACEMENT_KEY_NOT_REQUIRED; +import static com.hedera.test.factories.scenarios.TokenUpdateScenarios.UPDATE_WITH_KYC_KEYED_TOKEN_REPLACEMENT_KEY_REQUIRED; import static com.hedera.test.factories.scenarios.TokenUpdateScenarios.UPDATE_WITH_MISSING_TOKEN; import static com.hedera.test.factories.scenarios.TokenUpdateScenarios.UPDATE_WITH_MISSING_TOKEN_ADMIN_KEY; -import static com.hedera.test.factories.scenarios.TokenUpdateScenarios.UPDATE_WITH_NO_KEYS_AFFECTED; -import static com.hedera.test.factories.scenarios.TokenUpdateScenarios.UPDATE_WITH_SUPPLY_KEYED_TOKEN; -import static com.hedera.test.factories.scenarios.TokenUpdateScenarios.UPDATE_WITH_WIPE_KEYED_TOKEN; +import static com.hedera.test.factories.scenarios.TokenUpdateScenarios.UPDATE_WITH_NO_FIELDS_CHANGED; +import static com.hedera.test.factories.scenarios.TokenUpdateScenarios.UPDATE_WITH_SUPPLY_KEYED_TOKEN_REPLACEMENT_KEY_NOT_REQUIRED; +import static com.hedera.test.factories.scenarios.TokenUpdateScenarios.UPDATE_WITH_WIPE_KEYED_TOKEN_REPLACEMENT_KEY_NOT_REQUIRED; import static com.hedera.test.factories.scenarios.TxnHandlingScenario.TOKEN_ADMIN_KT; +import static com.hedera.test.factories.scenarios.TxnHandlingScenario.TOKEN_FREEZE_KT; +import static com.hedera.test.factories.scenarios.TxnHandlingScenario.TOKEN_KYC_KT; +import static com.hedera.test.factories.scenarios.TxnHandlingScenario.TOKEN_SUPPLY_KT; import static com.hedera.test.factories.scenarios.TxnHandlingScenario.TOKEN_TREASURY_KT; +import static com.hedera.test.factories.scenarios.TxnHandlingScenario.TOKEN_WIPE_KT; import static com.hedera.test.factories.txns.SignedTxnFactory.DEFAULT_PAYER_KT; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.contains; import static org.hamcrest.Matchers.containsInAnyOrder; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; +import com.hedera.hapi.node.base.Key; +import com.hedera.hapi.node.base.KeyList; +import com.hedera.hapi.node.base.ThresholdKey; import com.hedera.node.app.service.token.ReadableTokenStore; import com.hedera.node.app.service.token.impl.handlers.TokenUpdateHandler; import com.hedera.node.app.service.token.impl.test.handlers.util.ParityTestBase; @@ -68,15 +77,14 @@ public void setUp() { } @Test - void tokenUpdateWithoutAffectingKeys() throws PreCheckException { - final var txn = txnFrom(UPDATE_WITH_NO_KEYS_AFFECTED); + void tokenUpdateWithNoFieldsChanged() throws PreCheckException { + final var txn = txnFrom(UPDATE_WITH_NO_FIELDS_CHANGED); final var context = new FakePreHandleContext(readableAccountStore, txn); context.registerStore(ReadableTokenStore.class, readableTokenStore); subject.preHandle(context); assertEquals(context.payerKey(), DEFAULT_PAYER_KT.asPbjKey()); - assertEquals(1, context.requiredNonPayerKeys().size()); - assertThat(context.requiredNonPayerKeys(), contains(TOKEN_ADMIN_KT.asPbjKey())); + assertTrue(context.requiredNonPayerKeys().isEmpty()); } @Test @@ -143,50 +151,108 @@ void tokenUpdateReplacingAdminKey() throws PreCheckException { @Test void tokenUpdateWithSupplyKeyedToken() throws PreCheckException { - final var txn = txnFrom(UPDATE_WITH_SUPPLY_KEYED_TOKEN); + final var txn = txnFrom(UPDATE_WITH_SUPPLY_KEYED_TOKEN_REPLACEMENT_KEY_NOT_REQUIRED); final var context = new FakePreHandleContext(readableAccountStore, txn); + final var thresholdKey = Key.newBuilder() + .thresholdKey(ThresholdKey.newBuilder() + .keys(KeyList.newBuilder() + .keys(TOKEN_SUPPLY_KT.asPbjKey(), TOKEN_ADMIN_KT.asPbjKey()) + .build()) + .threshold(1) + .build()) + .build(); context.registerStore(ReadableTokenStore.class, readableTokenStore); subject.preHandle(context); assertEquals(context.payerKey(), DEFAULT_PAYER_KT.asPbjKey()); assertEquals(1, context.requiredNonPayerKeys().size()); - assertThat(context.requiredNonPayerKeys(), contains(TOKEN_ADMIN_KT.asPbjKey())); + assertThat(context.requiredNonPayerKeys(), contains(thresholdKey)); } @Test void tokenUpdateWithKYCKeyedToken() throws PreCheckException { - final var txn = txnFrom(UPDATE_WITH_KYC_KEYED_TOKEN); + final var txn = txnFrom(UPDATE_WITH_KYC_KEYED_TOKEN_REPLACEMENT_KEY_NOT_REQUIRED); final var context = new FakePreHandleContext(readableAccountStore, txn); context.registerStore(ReadableTokenStore.class, readableTokenStore); subject.preHandle(context); + final var thresholdKey = Key.newBuilder() + .thresholdKey(ThresholdKey.newBuilder() + .keys(KeyList.newBuilder() + .keys(TOKEN_KYC_KT.asPbjKey(), TOKEN_ADMIN_KT.asPbjKey()) + .build()) + .threshold(1) + .build()) + .build(); assertEquals(context.payerKey(), DEFAULT_PAYER_KT.asPbjKey()); assertEquals(1, context.requiredNonPayerKeys().size()); - assertThat(context.requiredNonPayerKeys(), contains(TOKEN_ADMIN_KT.asPbjKey())); + assertThat(context.requiredNonPayerKeys(), contains(thresholdKey)); + } + + @Test + void tokenUpdateWithKYCKeyedTokenAndFullValidation() throws PreCheckException { + final var txn = txnFrom(UPDATE_WITH_KYC_KEYED_TOKEN_REPLACEMENT_KEY_REQUIRED); + final var context = new FakePreHandleContext(readableAccountStore, txn); + context.registerStore(ReadableTokenStore.class, readableTokenStore); + subject.preHandle(context); + final var thresholdKey = Key.newBuilder() + .thresholdKey(ThresholdKey.newBuilder() + .keys(KeyList.newBuilder() + .keys( + Key.newBuilder() + .keyList(KeyList.newBuilder() + .keys(TOKEN_KYC_KT.asPbjKey(), TOKEN_REPLACE_KT.asPbjKey()) + .build()) + .build(), + TOKEN_ADMIN_KT.asPbjKey()) + .build()) + .threshold(1) + .build()) + .build(); + + assertEquals(context.payerKey(), DEFAULT_PAYER_KT.asPbjKey()); + assertEquals(1, context.requiredNonPayerKeys().size()); + assertThat(context.requiredNonPayerKeys(), contains(thresholdKey)); } @Test void tokenUpdateWithFreezeKeyedToken() throws PreCheckException { - final var txn = txnFrom(UPDATE_WITH_FREEZE_KEYED_TOKEN); + final var txn = txnFrom(UPDATE_WITH_FREEZE_KEYED_TOKEN_REPLACEMENT_KEY_NOT_REQUIRED); final var context = new FakePreHandleContext(readableAccountStore, txn); context.registerStore(ReadableTokenStore.class, readableTokenStore); subject.preHandle(context); + final var thresholdKey = Key.newBuilder() + .thresholdKey(ThresholdKey.newBuilder() + .keys(KeyList.newBuilder() + .keys(TOKEN_FREEZE_KT.asPbjKey(), TOKEN_ADMIN_KT.asPbjKey()) + .build()) + .threshold(1) + .build()) + .build(); assertEquals(context.payerKey(), DEFAULT_PAYER_KT.asPbjKey()); assertEquals(1, context.requiredNonPayerKeys().size()); - assertThat(context.requiredNonPayerKeys(), contains(TOKEN_ADMIN_KT.asPbjKey())); + assertThat(context.requiredNonPayerKeys(), contains(thresholdKey)); } @Test void tokenUpdateWithWipeKeyedToken() throws PreCheckException { - final var txn = txnFrom(UPDATE_WITH_WIPE_KEYED_TOKEN); + final var txn = txnFrom(UPDATE_WITH_WIPE_KEYED_TOKEN_REPLACEMENT_KEY_NOT_REQUIRED); final var context = new FakePreHandleContext(readableAccountStore, txn); context.registerStore(ReadableTokenStore.class, readableTokenStore); subject.preHandle(context); + final var thresholdKey = Key.newBuilder() + .thresholdKey(ThresholdKey.newBuilder() + .keys(KeyList.newBuilder() + .keys(TOKEN_WIPE_KT.asPbjKey(), TOKEN_ADMIN_KT.asPbjKey()) + .build()) + .threshold(1) + .build()) + .build(); assertEquals(context.payerKey(), DEFAULT_PAYER_KT.asPbjKey()); assertEquals(1, context.requiredNonPayerKeys().size()); - assertThat(context.requiredNonPayerKeys(), contains(TOKEN_ADMIN_KT.asPbjKey())); + assertThat(context.requiredNonPayerKeys(), contains(thresholdKey)); } @Test diff --git a/hedera-node/hedera-token-service-impl/src/test/java/com/hedera/node/app/service/token/impl/test/handlers/TokenUpdateHandlerTest.java b/hedera-node/hedera-token-service-impl/src/test/java/com/hedera/node/app/service/token/impl/test/handlers/TokenUpdateHandlerTest.java index 0223a8589697..4bc96b89739e 100644 --- a/hedera-node/hedera-token-service-impl/src/test/java/com/hedera/node/app/service/token/impl/test/handlers/TokenUpdateHandlerTest.java +++ b/hedera-node/hedera-token-service-impl/src/test/java/com/hedera/node/app/service/token/impl/test/handlers/TokenUpdateHandlerTest.java @@ -25,6 +25,8 @@ import static com.hedera.hapi.node.base.ResponseCodeEnum.INVALID_AUTORENEW_ACCOUNT; import static com.hedera.hapi.node.base.ResponseCodeEnum.INVALID_CUSTOM_FEE_SCHEDULE_KEY; import static com.hedera.hapi.node.base.ResponseCodeEnum.INVALID_EXPIRATION_TIME; +import static com.hedera.hapi.node.base.ResponseCodeEnum.INVALID_FREEZE_KEY; +import static com.hedera.hapi.node.base.ResponseCodeEnum.INVALID_KYC_KEY; import static com.hedera.hapi.node.base.ResponseCodeEnum.INVALID_METADATA_KEY; import static com.hedera.hapi.node.base.ResponseCodeEnum.INVALID_PAUSE_KEY; import static com.hedera.hapi.node.base.ResponseCodeEnum.INVALID_SUPPLY_KEY; @@ -47,6 +49,7 @@ import static com.hedera.node.app.service.token.impl.handlers.BaseTokenHandler.asToken; import static com.hedera.node.app.spi.fixtures.workflows.ExceptionConditions.responseCode; import static com.hedera.node.app.spi.workflows.HandleException.validateTrue; +import static com.hedera.test.utils.KeyUtils.A_KEY_LIST; import static com.hedera.test.utils.KeyUtils.B_COMPLEX_KEY; import static org.assertj.core.api.Assertions.assertThatNoException; import static org.assertj.core.api.AssertionsForClassTypes.assertThat; @@ -62,6 +65,7 @@ import com.hedera.hapi.node.base.Timestamp; import com.hedera.hapi.node.base.TokenAssociation; import com.hedera.hapi.node.base.TokenID; +import com.hedera.hapi.node.base.TokenKeyValidation; import com.hedera.hapi.node.base.TransactionID; import com.hedera.hapi.node.state.token.TokenRelation; import com.hedera.hapi.node.token.TokenUpdateTransactionBody; @@ -84,13 +88,14 @@ import com.hedera.node.app.spi.validation.ExpiryValidator; import com.hedera.node.app.spi.workflows.HandleContext; import com.hedera.node.app.spi.workflows.HandleException; +import com.hedera.node.app.spi.workflows.PreCheckException; +import com.hedera.node.app.spi.workflows.PreHandleContext; import com.hedera.node.app.workflows.handle.validation.StandardizedAttributeValidator; import com.hedera.node.app.workflows.handle.validation.StandardizedExpiryValidator; import com.hedera.node.config.ConfigProvider; import com.hedera.node.config.VersionedConfigImpl; import com.hedera.node.config.testfixtures.HederaTestConfigBuilder; import com.hedera.pbj.runtime.io.buffer.Bytes; -import java.time.Instant; import org.assertj.core.api.Assertions; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -103,6 +108,9 @@ class TokenUpdateHandlerTest extends CryptoTokenHandlerTestBase { @Mock(strictness = LENIENT) private HandleContext handleContext; + @Mock(strictness = LENIENT) + private PreHandleContext preHandleContext; + @Mock(strictness = LENIENT) private ConfigProvider configProvider; @@ -122,7 +130,6 @@ class TokenUpdateHandlerTest extends CryptoTokenHandlerTestBase { private ExpiryValidator expiryValidator; private AttributeValidator attributeValidator; private TokenUpdateHandler subject; - private final Instant consensusNow = Instant.ofEpochSecond(1_234_567L); @BeforeEach public void setUp() { @@ -244,11 +251,9 @@ void succeedsWithSupplyMetaDataAndKey() { @Test void failsForInvalidMetaDataKey() { - setUpTxnContext(); txn = new TokenUpdateBuilder().withMetadataKey(Key.DEFAULT).build(); - given(handleContext.body()).willReturn(txn); - assertThatThrownBy(() -> subject.handle(handleContext)) - .isInstanceOf(HandleException.class) + assertThatThrownBy(() -> subject.pureChecks(txn)) + .isInstanceOf(PreCheckException.class) .has(responseCode(INVALID_METADATA_KEY)); } @@ -269,14 +274,148 @@ void failsIfTokenImmutable() { .adminKey((Key) null) .build(); writableTokenStore.put(copyToken); - given(handleContext.readableStore(ReadableTokenStore.class)).willReturn(writableTokenStore); + given(preHandleContext.createStore(ReadableTokenStore.class)).willReturn(writableTokenStore); txn = new TokenUpdateBuilder().build(); - given(handleContext.body()).willReturn(txn); - assertThatThrownBy(() -> subject.handle(handleContext)) - .isInstanceOf(HandleException.class) + given(preHandleContext.body()).willReturn(txn); + assertThatThrownBy(() -> subject.preHandle(preHandleContext)) + .isInstanceOf(PreCheckException.class) .has(responseCode(TOKEN_IS_IMMUTABLE)); } + @Test + void invalidKeysForTokenFails() { + final Key invalidAllZeros = Key.newBuilder() + .ecdsaSecp256k1((Bytes.fromHex("0000000000000000000000000000000000000000"))) + .build(); + txn = new TokenUpdateBuilder().withAdminKey(invalidAllZeros).build(); + assertThatThrownBy(() -> subject.pureChecks(txn)) + .isInstanceOf(PreCheckException.class) + .has(responseCode(INVALID_ADMIN_KEY)); + + txn = new TokenUpdateBuilder().withFreezeKey(invalidAllZeros).build(); + assertThatThrownBy(() -> subject.pureChecks(txn)) + .isInstanceOf(PreCheckException.class) + .has(responseCode(INVALID_FREEZE_KEY)); + + txn = new TokenUpdateBuilder().withKycKey(invalidAllZeros).build(); + assertThatThrownBy(() -> subject.pureChecks(txn)) + .isInstanceOf(PreCheckException.class) + .has(responseCode(INVALID_KYC_KEY)); + + txn = new TokenUpdateBuilder().withWipeKey(invalidAllZeros).build(); + assertThatThrownBy(() -> subject.pureChecks(txn)) + .isInstanceOf(PreCheckException.class) + .has(responseCode(INVALID_WIPE_KEY)); + + txn = new TokenUpdateBuilder().withSupplyKey(invalidAllZeros).build(); + assertThatThrownBy(() -> subject.pureChecks(txn)) + .isInstanceOf(PreCheckException.class) + .has(responseCode(INVALID_SUPPLY_KEY)); + + txn = new TokenUpdateBuilder().withPauseKey(invalidAllZeros).build(); + assertThatThrownBy(() -> subject.pureChecks(txn)) + .isInstanceOf(PreCheckException.class) + .has(responseCode(INVALID_PAUSE_KEY)); + + txn = new TokenUpdateBuilder().withFeeScheduleKey(invalidAllZeros).build(); + assertThatThrownBy(() -> subject.pureChecks(txn)) + .isInstanceOf(PreCheckException.class) + .has(responseCode(INVALID_CUSTOM_FEE_SCHEDULE_KEY)); + + txn = new TokenUpdateBuilder().withMetadataKey(invalidAllZeros).build(); + assertThatThrownBy(() -> subject.pureChecks(txn)) + .isInstanceOf(PreCheckException.class) + .has(responseCode(INVALID_METADATA_KEY)); + } + + @Test + void invalidKeysForTokenSucceedsIfNoValidationIsApplied() { + final var copyToken = writableTokenStore + .get(fungibleTokenId) + .copyBuilder() + .adminKey(Key.DEFAULT) + .wipeKey(Key.DEFAULT) + .kycKey(Key.DEFAULT) + .supplyKey(Key.DEFAULT) + .freezeKey(Key.DEFAULT) + .feeScheduleKey(Key.DEFAULT) + .pauseKey(Key.DEFAULT) + .metadataKey(Key.DEFAULT) + .build(); + writableTokenStore.put(copyToken); + + given(handleContext.writableStore((WritableTokenStore.class))).willReturn(writableTokenStore); + + final Key invalidAllZeros = Key.newBuilder() + .ecdsaSecp256k1((Bytes.fromHex("0000000000000000000000000000000000000000"))) + .build(); + txn = new TokenUpdateBuilder() + .withKeyVerification(TokenKeyValidation.NO_VALIDATION) + .withAdminKey(invalidAllZeros) + .withFreezeKey(invalidAllZeros) + .withKycKey(invalidAllZeros) + .withWipeKey(invalidAllZeros) + .withSupplyKey(invalidAllZeros) + .withPauseKey(invalidAllZeros) + .withFeeScheduleKey(invalidAllZeros) + .withMetadataKey(invalidAllZeros) + .build(); + given(handleContext.body()).willReturn(txn); + assertThatNoException().isThrownBy(() -> subject.handle(handleContext)); + + final var modifiedToken = writableTokenStore.get(fungibleTokenId); + assertThat(modifiedToken.adminKey()).isEqualTo(invalidAllZeros); + assertThat(modifiedToken.freezeKey()).isEqualTo(invalidAllZeros); + assertThat(modifiedToken.kycKey()).isEqualTo(invalidAllZeros); + assertThat(modifiedToken.wipeKey()).isEqualTo(invalidAllZeros); + assertThat(modifiedToken.supplyKey()).isEqualTo(invalidAllZeros); + assertThat(modifiedToken.pauseKey()).isEqualTo(invalidAllZeros); + assertThat(modifiedToken.feeScheduleKey()).isEqualTo(invalidAllZeros); + assertThat(modifiedToken.metadataKey()).isEqualTo(invalidAllZeros); + } + + // previous to HIP-540 this should fail because the assumption was + // if an admin key is not set, the token is immutable + // after the HIP we expect other keys to be able to update themselves as well + @Test + void validTokenKeysSucceedsEvenIfAdminKeyIsNotSet() { + final var copyToken = writableTokenStore + .get(fungibleTokenId) + .copyBuilder() + .adminKey((Key) null) + .wipeKey(Key.DEFAULT) + .kycKey(Key.DEFAULT) + .supplyKey(Key.DEFAULT) + .freezeKey(Key.DEFAULT) + .feeScheduleKey(Key.DEFAULT) + .pauseKey(Key.DEFAULT) + .metadataKey(Key.DEFAULT) + .build(); + writableTokenStore.put(copyToken); + given(handleContext.writableStore((WritableTokenStore.class))).willReturn(writableTokenStore); + + txn = new TokenUpdateBuilder() + .withFreezeKey(A_KEY_LIST) + .withKycKey(A_KEY_LIST) + .withWipeKey(A_KEY_LIST) + .withSupplyKey(A_KEY_LIST) + .withPauseKey(A_KEY_LIST) + .withFeeScheduleKey(A_KEY_LIST) + .withMetadataKey(A_KEY_LIST) + .build(); + given(handleContext.body()).willReturn(txn); + assertThatNoException().isThrownBy(() -> subject.handle(handleContext)); + + final var modifiedToken = writableTokenStore.get(fungibleTokenId); + assertThat(modifiedToken.freezeKey()).isEqualTo(A_KEY_LIST); + assertThat(modifiedToken.kycKey()).isEqualTo(A_KEY_LIST); + assertThat(modifiedToken.wipeKey()).isEqualTo(A_KEY_LIST); + assertThat(modifiedToken.supplyKey()).isEqualTo(A_KEY_LIST); + assertThat(modifiedToken.pauseKey()).isEqualTo(A_KEY_LIST); + assertThat(modifiedToken.feeScheduleKey()).isEqualTo(A_KEY_LIST); + assertThat(modifiedToken.metadataKey()).isEqualTo(A_KEY_LIST); + } + @Test void failsIfTokenHasNoKycGrantedImmutable() { final var copyTokenRel = writableTokenRelStore @@ -911,33 +1050,28 @@ void doesntGrantKycOrUnfreezeNewTreasuryIfNoKeyIsPresent() { @Test void validatesUpdatingKeys() { txn = new TokenUpdateBuilder().withAdminKey(Key.DEFAULT).build(); - given(handleContext.body()).willReturn(txn); - assertThatThrownBy(() -> subject.handle(handleContext)) - .isInstanceOf(HandleException.class) + assertThatThrownBy(() -> subject.pureChecks(txn)) + .isInstanceOf(PreCheckException.class) .has(responseCode(INVALID_ADMIN_KEY)); txn = new TokenUpdateBuilder().withSupplyKey(Key.DEFAULT).build(); - given(handleContext.body()).willReturn(txn); - assertThatThrownBy(() -> subject.handle(handleContext)) - .isInstanceOf(HandleException.class) + assertThatThrownBy(() -> subject.pureChecks(txn)) + .isInstanceOf(PreCheckException.class) .has(responseCode(INVALID_SUPPLY_KEY)); txn = new TokenUpdateBuilder().withWipeKey(Key.DEFAULT).build(); - given(handleContext.body()).willReturn(txn); - assertThatThrownBy(() -> subject.handle(handleContext)) - .isInstanceOf(HandleException.class) + assertThatThrownBy(() -> subject.pureChecks(txn)) + .isInstanceOf(PreCheckException.class) .has(responseCode(INVALID_WIPE_KEY)); txn = new TokenUpdateBuilder().withFeeScheduleKey(Key.DEFAULT).build(); - given(handleContext.body()).willReturn(txn); - assertThatThrownBy(() -> subject.handle(handleContext)) - .isInstanceOf(HandleException.class) + assertThatThrownBy(() -> subject.pureChecks(txn)) + .isInstanceOf(PreCheckException.class) .has(responseCode(INVALID_CUSTOM_FEE_SCHEDULE_KEY)); txn = new TokenUpdateBuilder().withPauseKey(Key.DEFAULT).build(); - given(handleContext.body()).willReturn(txn); - assertThatThrownBy(() -> subject.handle(handleContext)) - .isInstanceOf(HandleException.class) + assertThatThrownBy(() -> subject.pureChecks(txn)) + .isInstanceOf(PreCheckException.class) .has(responseCode(INVALID_PAUSE_KEY)); } @@ -961,6 +1095,40 @@ void rejectsTreasuryUpdateIfNonzeroBalanceForNFTs() { .has(responseCode(CURRENT_TREASURY_STILL_OWNS_NFTS)); } + @Test + void validateZeroTreasuryNotUpdatedForContractCalls() { + // We don't set transaction ID so we simulate a contract call + txn = TransactionBody.newBuilder() + .tokenUpdate(TokenUpdateTransactionBody.newBuilder() + .token(fungibleTokenId) + .treasury(zeroAccountId) + .name("TestTokenUpdateDoesNotUpdateZeroTreasury") + .build()) + .build(); + given(handleContext.body()).willReturn(txn); + assertThatNoException().isThrownBy(() -> subject.handle(handleContext)); + + final var token = writableTokenState.get(fungibleTokenId); + assertThat(token.treasuryAccountId()).isEqualTo(fungibleToken.treasuryAccountId()); + assertThat(token.treasuryAccountId()).isNotEqualTo(zeroAccountId); + assertThat(token.name()).isEqualTo("TestTokenUpdateDoesNotUpdateZeroTreasury"); + } + + @Test + void validateZeroTreasuryIsUpdatedForHapiCalls() { + txn = new TokenUpdateBuilder() + .withToken(fungibleTokenId) + .withTreasury(zeroAccountId) + .withName("TestTokenUpdateDoesUpdateZeroTreasury") + .build(); + given(handleContext.body()).willReturn(txn); + assertThatNoException().isThrownBy(() -> subject.handle(handleContext)); + + final var token = writableTokenState.get(fungibleTokenId); + assertThat(token.treasuryAccountId()).isEqualTo(zeroAccountId); + assertThat(token.name()).isEqualTo("TestTokenUpdateDoesUpdateZeroTreasury"); + } + /* --------------------------------- Helpers --------------------------------- */ /** * A builder for {@link com.hedera.hapi.node.transaction.TransactionBody} instances. @@ -983,6 +1151,7 @@ private class TokenUpdateBuilder { private long autoRenewPeriod = autoRenewSecs; private String memo = "test token1"; private String metadata = "test metadata"; + private TokenKeyValidation keyVerification = TokenKeyValidation.FULL_VALIDATION; TokenID tokenId = fungibleTokenId; private TokenUpdateBuilder() {} @@ -1004,6 +1173,7 @@ public TransactionBody build() { .metadata(Bytes.wrap(metadata)) .feeScheduleKey(feeScheduleKey) .pauseKey(pauseKey) + .keyVerificationMode(keyVerification) .autoRenewAccount(autoRenewAccount) .expiry(expiry) .memo(memo); @@ -1106,6 +1276,11 @@ public TokenUpdateBuilder wthFreezeKey(final Key key) { this.freezeKey = key; return this; } + + public TokenUpdateBuilder withKeyVerification(final TokenKeyValidation keyVerification) { + this.keyVerification = keyVerification; + return this; + } } private void setUpTxnContext() { diff --git a/hedera-node/hedera-token-service-impl/src/test/java/com/hedera/node/app/service/token/impl/test/handlers/TokenUpdateNftsHandlerTest.java b/hedera-node/hedera-token-service-impl/src/test/java/com/hedera/node/app/service/token/impl/test/handlers/TokenUpdateNftsHandlerTest.java index d08c51ec56fd..1df8be099061 100644 --- a/hedera-node/hedera-token-service-impl/src/test/java/com/hedera/node/app/service/token/impl/test/handlers/TokenUpdateNftsHandlerTest.java +++ b/hedera-node/hedera-token-service-impl/src/test/java/com/hedera/node/app/service/token/impl/test/handlers/TokenUpdateNftsHandlerTest.java @@ -56,7 +56,6 @@ import com.hedera.node.app.service.token.impl.validators.TokenAttributesValidator; import com.hedera.node.app.spi.fees.FeeCalculator; import com.hedera.node.app.spi.fees.FeeContext; -import com.hedera.node.app.spi.fixtures.state.MapWritableKVState; import com.hedera.node.app.spi.fixtures.state.MapWritableStates; import com.hedera.node.app.spi.metrics.StoreMetricsService; import com.hedera.node.app.spi.validation.AttributeValidator; @@ -68,6 +67,7 @@ import com.hedera.node.config.testfixtures.HederaTestConfigBuilder; import com.hedera.pbj.runtime.io.buffer.Bytes; import com.swirlds.config.api.Configuration; +import com.swirlds.platform.test.fixtures.state.MapWritableKVState; import java.util.ArrayList; import java.util.Arrays; import java.util.List; diff --git a/hedera-node/hedera-token-service-impl/src/test/java/com/hedera/node/app/service/token/impl/test/handlers/staking/EndOfStakingPeriodUpdaterTest.java b/hedera-node/hedera-token-service-impl/src/test/java/com/hedera/node/app/service/token/impl/test/handlers/staking/EndOfStakingPeriodUpdaterTest.java index 39fa59d19b9f..561c3875ef42 100644 --- a/hedera-node/hedera-token-service-impl/src/test/java/com/hedera/node/app/service/token/impl/test/handlers/staking/EndOfStakingPeriodUpdaterTest.java +++ b/hedera-node/hedera-token-service-impl/src/test/java/com/hedera/node/app/service/token/impl/test/handlers/staking/EndOfStakingPeriodUpdaterTest.java @@ -22,6 +22,7 @@ import static com.hedera.node.app.service.token.impl.handlers.BaseCryptoHandler.asAccount; import static com.hedera.node.app.service.token.impl.handlers.staking.EndOfStakingPeriodUpdater.calculateWeightFromStake; import static com.hedera.node.app.service.token.impl.handlers.staking.EndOfStakingPeriodUpdater.scaleUpWeightToStake; +import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.BDDMockito.given; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verifyNoInteractions; @@ -32,7 +33,6 @@ import com.hedera.hapi.node.state.token.NetworkStakingRewards; import com.hedera.hapi.node.state.token.StakingNodeInfo; import com.hedera.node.app.service.token.ReadableAccountStore; -import com.hedera.node.app.service.token.ReadableStakingInfoStore; import com.hedera.node.app.service.token.impl.WritableNetworkStakingRewardsStore; import com.hedera.node.app.service.token.impl.WritableStakingInfoStore; import com.hedera.node.app.service.token.impl.handlers.staking.EndOfStakingPeriodUpdater; @@ -42,35 +42,50 @@ import com.hedera.node.app.service.token.records.NodeStakeUpdateRecordBuilder; import com.hedera.node.app.service.token.records.TokenContext; import com.hedera.node.app.spi.fixtures.numbers.FakeHederaNumbers; -import com.hedera.node.app.spi.fixtures.state.MapWritableKVState; import com.hedera.node.app.spi.fixtures.state.MapWritableStates; -import com.hedera.node.app.spi.state.WritableSingletonState; -import com.hedera.node.app.spi.state.WritableSingletonStateBase; -import com.hedera.node.app.spi.state.WritableStates; +import com.hedera.node.app.spi.fixtures.util.LogCaptor; +import com.hedera.node.app.spi.fixtures.util.LogCaptureExtension; +import com.hedera.node.app.spi.fixtures.util.LoggingSubject; +import com.hedera.node.app.spi.fixtures.util.LoggingTarget; import com.hedera.node.config.data.StakingConfig; import com.hedera.node.config.testfixtures.HederaTestConfigBuilder; import com.swirlds.config.extensions.test.fixtures.TestConfigBuilder; +import com.swirlds.platform.state.spi.WritableSingletonStateBase; +import com.swirlds.platform.test.fixtures.state.MapWritableKVState; +import com.swirlds.state.spi.WritableSingletonState; +import com.swirlds.state.spi.WritableStates; +import edu.umd.cs.findbugs.annotations.NonNull; import java.time.Instant; import java.util.List; import java.util.Map; import java.util.Set; import java.util.concurrent.atomic.AtomicReference; -import org.assertj.core.api.Assertions; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; -@ExtendWith(MockitoExtension.class) +/** + * Unit tests for {@link EndOfStakingPeriodUpdater}. + */ +@ExtendWith({MockitoExtension.class, LogCaptureExtension.class}) public class EndOfStakingPeriodUpdaterTest { + + @LoggingTarget + private LogCaptor logCaptor; + + @Mock + private TokenContext context; + private ReadableAccountStore accountStore; + @LoggingSubject private EndOfStakingPeriodUpdater subject; - private NodeStakeUpdateRecordBuilder nodeStakeUpdateRecordBuilder; - @Mock - private ReadableStakingInfoStore iterableStakingInfoStore; + private NodeStakeUpdateRecordBuilder nodeStakeUpdateRecordBuilder; + private WritableStakingInfoStore stakingInfoStore; + private WritableNetworkStakingRewardsStore stakingRewardsStore; @BeforeEach void setup() { @@ -84,8 +99,6 @@ void setup() { @Test void skipsEndOfStakingPeriodUpdatesIfStakingNotEnabled() { - final var consensusTime = Instant.now(); - // Set up the staking config final var context = mock(TokenContext.class); given(context.configuration()) @@ -114,12 +127,12 @@ void convertsStakeValueToWeightCorrectly() { final var updatedWeight4 = calculateWeightFromStake(stake4, totalStake, SUM_OF_CONSENSUS_WEIGHTS); final var updatedWeight5 = calculateWeightFromStake(stake5, totalStake, SUM_OF_CONSENSUS_WEIGHTS); final var totalWeight = updatedWeight1 + updatedWeight2 + updatedWeight3 + updatedWeight4 + updatedWeight5; - Assertions.assertThat(totalWeight).isLessThanOrEqualTo(SUM_OF_CONSENSUS_WEIGHTS); - Assertions.assertThat(updatedWeight1).isEqualTo(1); - Assertions.assertThat(updatedWeight2).isEqualTo((stake2 * SUM_OF_CONSENSUS_WEIGHTS) / totalStake); - Assertions.assertThat(updatedWeight3).isEqualTo((stake3 * SUM_OF_CONSENSUS_WEIGHTS) / totalStake); - Assertions.assertThat(updatedWeight4).isEqualTo((stake4 * SUM_OF_CONSENSUS_WEIGHTS) / totalStake); - Assertions.assertThat(updatedWeight5).isZero(); + assertThat(totalWeight).isLessThanOrEqualTo(SUM_OF_CONSENSUS_WEIGHTS); + assertThat(updatedWeight1).isEqualTo(1); + assertThat(updatedWeight2).isEqualTo((stake2 * SUM_OF_CONSENSUS_WEIGHTS) / totalStake); + assertThat(updatedWeight3).isEqualTo((stake3 * SUM_OF_CONSENSUS_WEIGHTS) / totalStake); + assertThat(updatedWeight4).isEqualTo((stake4 * SUM_OF_CONSENSUS_WEIGHTS) / totalStake); + assertThat(updatedWeight5).isZero(); } @Test @@ -142,12 +155,12 @@ void scalesBackWeightToStake() { final var totalWeight = weightForEqualsMin + weightInBetween1 + weightInBetween2 + weightForEqualsMax + weightForZeroStake; // total of all weights should be less than or equal to SUM_OF_CONSENSUS_WEIGHTS - Assertions.assertThat(totalWeight).isLessThanOrEqualTo(SUM_OF_CONSENSUS_WEIGHTS); - Assertions.assertThat(weightForEqualsMin).isEqualTo(1); - Assertions.assertThat(weightInBetween1).isEqualTo((stakeInBetween1 * SUM_OF_CONSENSUS_WEIGHTS) / totalStake); - Assertions.assertThat(weightInBetween2).isEqualTo((stakeInBetween2 * SUM_OF_CONSENSUS_WEIGHTS) / totalStake); - Assertions.assertThat(weightForEqualsMax).isEqualTo((stakeEqualsMax * SUM_OF_CONSENSUS_WEIGHTS) / totalStake); - Assertions.assertThat(weightForZeroStake).isZero(); + assertThat(totalWeight).isLessThanOrEqualTo(SUM_OF_CONSENSUS_WEIGHTS); + assertThat(weightForEqualsMin).isEqualTo(1); + assertThat(weightInBetween1).isEqualTo((stakeInBetween1 * SUM_OF_CONSENSUS_WEIGHTS) / totalStake); + assertThat(weightInBetween2).isEqualTo((stakeInBetween2 * SUM_OF_CONSENSUS_WEIGHTS) / totalStake); + assertThat(weightForEqualsMax).isEqualTo((stakeEqualsMax * SUM_OF_CONSENSUS_WEIGHTS) / totalStake); + assertThat(weightForZeroStake).isZero(); final var scaledStake1 = scaleUpWeightToStake(weightForEqualsMin, minStake, maxStake, totalStake, SUM_OF_CONSENSUS_WEIGHTS); @@ -166,89 +179,209 @@ void scalesBackWeightToStake() { final var expectedEqualScaledStake = ((maxStake - minStake) * (weightInBetween2 - 1)) / (maxWeight - 1) + minStake; // stake equals min stake - Assertions.assertThat(scaledStake1).isEqualTo(equalsMinStake); + assertThat(scaledStake1).isEqualTo(equalsMinStake); // Both these fall in the same bucket since their weight is the same. So, they get same scaled weight - Assertions.assertThat(scaledStake2).isEqualTo(expectedEqualScaledStake); - Assertions.assertThat(scaledStake3).isEqualTo(expectedEqualScaledStake); + assertThat(scaledStake2).isEqualTo(expectedEqualScaledStake); + assertThat(scaledStake3).isEqualTo(expectedEqualScaledStake); // stake equals max stake, will return max stake - Assertions.assertThat(scaledStake4).isEqualTo(stakeEqualsMax); + assertThat(scaledStake4).isEqualTo(stakeEqualsMax); // stake equals zero, will return zero - Assertions.assertThat(scaledStake5).isEqualTo(zeroStake); + assertThat(scaledStake5).isEqualTo(zeroStake); } @Test void deletedNodesGetsZeroPendingRewards() { - final var context = mock(TokenContext.class); - given(context.consensusTime()).willReturn(Instant.now()); + commonSetup( + 1_000_000_000L, + STAKING_INFO_1.copyBuilder().deleted(true).build(), + STAKING_INFO_2, + STAKING_INFO_3.copyBuilder().deleted(true).build()); + // Assert preconditions + assertThat(STAKING_INFO_1.weight()).isZero(); + assertThat(STAKING_INFO_2.weight()).isZero(); + assertThat(STAKING_INFO_3.weight()).isZero(); + assertThat(STAKING_INFO_1.pendingRewards()).isZero(); + assertThat(STAKING_INFO_2.pendingRewards()).isZero(); + assertThat(STAKING_INFO_3.pendingRewards()).isZero(); - // Create staking config - final var stakingConfig = newStakingConfig().getOrCreateConfig(); - given(context.configuration()).willReturn(stakingConfig); + subject.updateNodes(context); - // Create account store (with data) - given(context.readableStore(ReadableAccountStore.class)).willReturn(accountStore); + assertThat(stakingRewardsStore.totalStakeRewardStart()) + .isEqualTo(STAKE_TO_REWARD_1 + STAKE_TO_REWARD_2 + STAKE_TO_REWARD_3); + assertThat(stakingRewardsStore.totalStakedStart()).isEqualTo(130000000000L); + final var resultStakingInfo1 = stakingInfoStore.get(NODE_NUM_1.number()); + final var resultStakingInfo2 = stakingInfoStore.get(NODE_NUM_2.number()); + final var resultStakingInfo3 = stakingInfoStore.get(NODE_NUM_3.number()); + assertThat(resultStakingInfo1.stake()).isEqualTo(80000000000L); + assertThat(resultStakingInfo2.stake()).isEqualTo(50000000000L); + assertThat(resultStakingInfo3.stake()).isZero(); + assertThat(resultStakingInfo1.unclaimedStakeRewardStart()).isZero(); + assertThat(resultStakingInfo2.unclaimedStakeRewardStart()).isZero(); + assertThat(resultStakingInfo3.unclaimedStakeRewardStart()).isZero(); + assertThat(resultStakingInfo1.rewardSumHistory()).isEqualTo(List.of(86L, 6L, 5L)); + assertThat(resultStakingInfo2.rewardSumHistory()).isEqualTo(List.of(101L, 1L, 1L)); + assertThat(resultStakingInfo3.rewardSumHistory()).isEqualTo(List.of(11L, 3L, 1L)); + assertThat(resultStakingInfo1.weight()).isZero(); + assertThat(resultStakingInfo2.weight()).isEqualTo(192); + assertThat(resultStakingInfo3.weight()).isZero(); + assertThat(resultStakingInfo1.pendingRewards()).isZero(); + assertThat(resultStakingInfo2.pendingRewards()).isEqualTo(63000L); + assertThat(resultStakingInfo3.pendingRewards()).isZero(); + assertThat(resultStakingInfo1.weight() + resultStakingInfo2.weight() + resultStakingInfo3.weight()) + .isLessThanOrEqualTo(SUM_OF_CONSENSUS_WEIGHTS); - // Create staking info store (with data) - final var stakingInfosState = new MapWritableKVState.Builder(STAKING_INFO_KEY) - .value(NODE_NUM_1, STAKING_INFO_1.copyBuilder().deleted(true).build()) - .value(NODE_NUM_2, STAKING_INFO_2) - .value(NODE_NUM_3, STAKING_INFO_3.copyBuilder().deleted(true).build()) - .build(); - final var stakingInfoStore = - new WritableStakingInfoStore(new MapWritableStates(Map.of(STAKING_INFO_KEY, stakingInfosState))); - given(context.writableStore(WritableStakingInfoStore.class)).willReturn(stakingInfoStore); + assertThat(logCaptor.infoLogs()).contains("Non-zero reward sum history for node number 1 is now [86, 6, 5]"); + assertThat(logCaptor.infoLogs()).contains("Non-zero reward sum history for node number 2 is now [101, 1, 1]"); + assertThat(logCaptor.infoLogs()).contains("Non-zero reward sum history for node number 3 is now [11, 3, 1]"); + } - // Create staking reward store (with data) - final var backingValue = new AtomicReference<>(new NetworkStakingRewards(true, 1_000_000_000L, 0, 0)); - final var stakingRewardsState = - new WritableSingletonStateBase<>(STAKING_NETWORK_REWARDS_KEY, backingValue::get, backingValue::set); - final var states = mock(WritableStates.class); - given(states.getSingleton(STAKING_NETWORK_REWARDS_KEY)) - .willReturn((WritableSingletonState) stakingRewardsState); - final var stakingRewardsStore = new WritableNetworkStakingRewardsStore(states); - given(context.writableStore(WritableNetworkStakingRewardsStore.class)).willReturn(stakingRewardsStore); - given(context.addUncheckedPrecedingChildRecordBuilder(NodeStakeUpdateRecordBuilder.class)) - .willReturn(nodeStakeUpdateRecordBuilder); - given(context.knownNodeIds()).willReturn(Set.of(NODE_NUM_1.number(), NODE_NUM_2.number(), NODE_NUM_3.number())); + @Test + void doesNothingWhenStakingConfigIsNotEnabled() { + given(context.configuration()) + .willReturn( + newStakingConfig().withValue("staking.isEnabled", false).getOrCreateConfig()); + // Set up the relevant stores (and data) + final var stakingInfoStore = mock(WritableStakingInfoStore.class); + final var stakingRewardsStore = mock(WritableNetworkStakingRewardsStore.class); + + subject.updateNodes(context); + + verifyNoInteractions(stakingInfoStore, stakingRewardsStore); + assertThat(logCaptor.infoLogs()).contains("Staking not enabled, nothing to do"); + } + + @Test + void calculatesNewEndOfPeriodStakingFieldsAsExpected() { + commonSetup(1_000_000_000L, STAKING_INFO_1, STAKING_INFO_2, STAKING_INFO_3); // Assert preconditions - Assertions.assertThat(STAKING_INFO_1.weight()).isZero(); - Assertions.assertThat(STAKING_INFO_2.weight()).isZero(); - Assertions.assertThat(STAKING_INFO_3.weight()).isZero(); - Assertions.assertThat(STAKING_INFO_1.pendingRewards()).isZero(); - Assertions.assertThat(STAKING_INFO_2.pendingRewards()).isZero(); - Assertions.assertThat(STAKING_INFO_3.pendingRewards()).isZero(); + assertThat(STAKING_INFO_1.weight()).isZero(); + assertThat(STAKING_INFO_2.weight()).isZero(); + assertThat(STAKING_INFO_3.weight()).isZero(); + assertThat(STAKING_INFO_1.pendingRewards()).isZero(); + assertThat(STAKING_INFO_2.pendingRewards()).isZero(); + assertThat(STAKING_INFO_3.pendingRewards()).isZero(); subject.updateNodes(context); - Assertions.assertThat(stakingRewardsStore.totalStakeRewardStart()) + assertThat(stakingRewardsStore.totalStakeRewardStart()) .isEqualTo(STAKE_TO_REWARD_1 + STAKE_TO_REWARD_2 + STAKE_TO_REWARD_3); - Assertions.assertThat(stakingRewardsStore.totalStakedStart()).isEqualTo(130000000000L); + assertThat(stakingRewardsStore.totalStakedStart()).isEqualTo(130000000000L); final var resultStakingInfo1 = stakingInfoStore.get(NODE_NUM_1.number()); final var resultStakingInfo2 = stakingInfoStore.get(NODE_NUM_2.number()); final var resultStakingInfo3 = stakingInfoStore.get(NODE_NUM_3.number()); - Assertions.assertThat(resultStakingInfo1.stake()).isEqualTo(80000000000L); - Assertions.assertThat(resultStakingInfo2.stake()).isEqualTo(50000000000L); - Assertions.assertThat(resultStakingInfo3.stake()).isZero(); - Assertions.assertThat(resultStakingInfo1.unclaimedStakeRewardStart()).isZero(); - Assertions.assertThat(resultStakingInfo2.unclaimedStakeRewardStart()).isZero(); - Assertions.assertThat(resultStakingInfo3.unclaimedStakeRewardStart()).isZero(); - Assertions.assertThat(resultStakingInfo1.rewardSumHistory()).isEqualTo(List.of(86L, 6L, 5L)); - Assertions.assertThat(resultStakingInfo2.rewardSumHistory()).isEqualTo(List.of(101L, 1L, 1L)); - Assertions.assertThat(resultStakingInfo3.rewardSumHistory()).isEqualTo(List.of(11L, 3L, 1L)); - Assertions.assertThat(resultStakingInfo1.weight()).isZero(); - Assertions.assertThat(resultStakingInfo2.weight()).isEqualTo(192); - Assertions.assertThat(resultStakingInfo3.weight()).isZero(); - Assertions.assertThat(resultStakingInfo1.pendingRewards()).isEqualTo(0L); - Assertions.assertThat(resultStakingInfo2.pendingRewards()).isEqualTo(63000L); - Assertions.assertThat(resultStakingInfo3.pendingRewards()).isEqualTo(0L); - Assertions.assertThat(resultStakingInfo1.weight() + resultStakingInfo2.weight() + resultStakingInfo3.weight()) + assertThat(resultStakingInfo1.stake()).isEqualTo(80000000000L); + assertThat(resultStakingInfo2.stake()).isEqualTo(50000000000L); + assertThat(resultStakingInfo3.stake()).isZero(); + assertThat(resultStakingInfo1.unclaimedStakeRewardStart()).isZero(); + assertThat(resultStakingInfo2.unclaimedStakeRewardStart()).isZero(); + assertThat(resultStakingInfo3.unclaimedStakeRewardStart()).isZero(); + assertThat(resultStakingInfo1.rewardSumHistory()).isEqualTo(List.of(86L, 6L, 5L)); + assertThat(resultStakingInfo2.rewardSumHistory()).isEqualTo(List.of(101L, 1L, 1L)); + assertThat(resultStakingInfo3.rewardSumHistory()).isEqualTo(List.of(11L, 3L, 1L)); + assertThat(resultStakingInfo1.weight()).isEqualTo(307); + assertThat(resultStakingInfo2.weight()).isEqualTo(192); + assertThat(resultStakingInfo3.weight()).isZero(); + assertThat(resultStakingInfo1.pendingRewards()).isEqualTo(72000); + assertThat(resultStakingInfo2.pendingRewards()).isEqualTo(63000L); + assertThat(resultStakingInfo3.pendingRewards()).isEqualTo(72000L); + assertThat(resultStakingInfo1.weight() + resultStakingInfo2.weight() + resultStakingInfo3.weight()) .isLessThanOrEqualTo(SUM_OF_CONSENSUS_WEIGHTS); } @Test - void calculatesNewEndOfPeriodStakingFieldsAsExpected() { - final var context = mock(TokenContext.class); + void calculatesNewEndOfPeriodStakingFieldsAsExpectedWhenMaxStakeIsLessThanTotalStake() { + commonSetup(1_000_000_000L, STAKING_INFO_1, STAKING_INFO_2, STAKING_INFO_3); + given(context.configuration()) + .willReturn(newStakingConfig() + .withValue("staking.rewardBalanceThreshold", 100000) + .withValue("staking.maxStakeRewarded", 0L) + .getOrCreateConfig()); + + subject.updateNodes(context); + + assertThat(stakingRewardsStore.totalStakeRewardStart()) + .isEqualTo(STAKE_TO_REWARD_1 + STAKE_TO_REWARD_2 + STAKE_TO_REWARD_3); + assertThat(stakingRewardsStore.totalStakedStart()).isEqualTo(130000000000L); + final var resultStakingInfo1 = stakingInfoStore.get(NODE_NUM_1.number()); + final var resultStakingInfo2 = stakingInfoStore.get(NODE_NUM_2.number()); + final var resultStakingInfo3 = stakingInfoStore.get(NODE_NUM_3.number()); + assertThat(resultStakingInfo1.stake()).isEqualTo(80000000000L); + assertThat(resultStakingInfo2.stake()).isEqualTo(50000000000L); + assertThat(resultStakingInfo3.stake()).isZero(); + assertThat(resultStakingInfo1.unclaimedStakeRewardStart()).isZero(); + assertThat(resultStakingInfo2.unclaimedStakeRewardStart()).isZero(); + assertThat(resultStakingInfo3.unclaimedStakeRewardStart()).isZero(); + assertThat(resultStakingInfo1.rewardSumHistory()).isEqualTo(List.of(6L, 6L, 5L)); + assertThat(resultStakingInfo2.rewardSumHistory()).isEqualTo(List.of(1L, 1L, 1L)); + assertThat(resultStakingInfo3.rewardSumHistory()).isEqualTo(List.of(3L, 3L, 1L)); + assertThat(resultStakingInfo1.weight()).isEqualTo(307); + assertThat(resultStakingInfo2.weight()).isEqualTo(192); + assertThat(resultStakingInfo3.weight()).isZero(); + // Since max stake rewarded is 0, all pending rewards should be 0 + assertThat(resultStakingInfo1.pendingRewards()).isEqualTo(0L); + assertThat(resultStakingInfo2.pendingRewards()).isEqualTo(0L); + assertThat(resultStakingInfo3.pendingRewards()).isEqualTo(0L); + assertThat(resultStakingInfo1.weight() + resultStakingInfo2.weight() + resultStakingInfo3.weight()) + .isLessThanOrEqualTo(SUM_OF_CONSENSUS_WEIGHTS); + } + + @Test + void zeroWholeHbarsStakedCaseWorks() { + commonSetup( + 0L, + STAKING_INFO_1.copyBuilder().stakeRewardStart(0).build(), + STAKING_INFO_2.copyBuilder().stakeRewardStart(0).build(), + STAKING_INFO_3.copyBuilder().stakeRewardStart(0).build()); + assertThat(stakingRewardsStore.totalStakeRewardStart()).isZero(); + + subject.updateNodes(context); + + assertThat(stakingRewardsStore.totalStakeRewardStart()) + .isEqualTo(STAKE_TO_REWARD_1 + STAKE_TO_REWARD_2 + STAKE_TO_REWARD_3); + assertThat(stakingRewardsStore.totalStakedStart()).isEqualTo(130000000000L); + final var resultStakingInfo1 = stakingInfoStore.get(NODE_NUM_1.number()); + final var resultStakingInfo2 = stakingInfoStore.get(NODE_NUM_2.number()); + final var resultStakingInfo3 = stakingInfoStore.get(NODE_NUM_3.number()); + assertThat(resultStakingInfo1.rewardSumHistory()).isEqualTo(List.of(6L, 6L, 5L)); + assertThat(resultStakingInfo2.rewardSumHistory()).isEqualTo(List.of(1L, 1L, 1L)); + assertThat(resultStakingInfo3.rewardSumHistory()).isEqualTo(List.of(3L, 3L, 1L)); + } + + @Test + void returnsZeroWeightIfTotalStakeOfAllNodeIsZero() { + final var weight = calculateWeightFromStake(10, 0, 500); + assertThat(weight).isEqualTo(0); + assertThat(logCaptor.warnLogs()).contains("Total stake of all nodes should be greater than 0. But got 0"); + } + + @Test + void returnsZeroScaledUpWeightIfTotalStakeOfAllNodeIsZero() { + final var weight = scaleUpWeightToStake(10, 1000, 1000, 0, 500); + assertThat(weight).isEqualTo(0); + assertThat(logCaptor.warnLogs()) + .contains( + "Total stake of all nodes is 0, " + + "which shouldn't happen (weight=10, minStake=1000, maxStake=1000, sumOfConsensusWeights=500)"); + } + + @Test + void calculatesMidnightTimeCorrectly() { + final var consensusSecs = 1653660350L; + final var consensusNanos = 12345L; + final var expectedNanos = 999_999_999; + final var consensusTime = Instant.ofEpochSecond(consensusSecs, consensusNanos); + final var expectedMidnightTime = + Timestamp.newBuilder().seconds(1653609599L).nanos(expectedNanos).build(); + + assertThat(subject.lastInstantOfPreviousPeriodFor(consensusTime)).isEqualTo(expectedMidnightTime); + } + + private void commonSetup( + final long totalStakeRewardStart, + @NonNull final StakingNodeInfo info1, + @NonNull final StakingNodeInfo info2, + @NonNull final StakingNodeInfo info3) { given(context.consensusTime()).willReturn(Instant.now()); // Create staking config @@ -259,74 +392,28 @@ void calculatesNewEndOfPeriodStakingFieldsAsExpected() { given(context.readableStore(ReadableAccountStore.class)).willReturn(accountStore); // Create staking info store (with data) - final var stakingInfosState = new MapWritableKVState.Builder(STAKING_INFO_KEY) - .value(NODE_NUM_1, STAKING_INFO_1) - .value(NODE_NUM_2, STAKING_INFO_2) - .value(NODE_NUM_3, STAKING_INFO_3) + MapWritableKVState stakingInfosState = new MapWritableKVState.Builder< + EntityNumber, StakingNodeInfo>(STAKING_INFO_KEY) + .value(NODE_NUM_1, info1) + .value(NODE_NUM_2, info2) + .value(NODE_NUM_3, info3) .build(); - final var stakingInfoStore = + stakingInfoStore = new WritableStakingInfoStore(new MapWritableStates(Map.of(STAKING_INFO_KEY, stakingInfosState))); given(context.writableStore(WritableStakingInfoStore.class)).willReturn(stakingInfoStore); // Create staking reward store (with data) - final var backingValue = new AtomicReference<>(new NetworkStakingRewards(true, 1_000_000_000L, 0, 0)); - final var stakingRewardsState = + final var backingValue = new AtomicReference<>(new NetworkStakingRewards(true, totalStakeRewardStart, 0, 0)); + WritableSingletonState stakingRewardsState = new WritableSingletonStateBase<>(STAKING_NETWORK_REWARDS_KEY, backingValue::get, backingValue::set); final var states = mock(WritableStates.class); given(states.getSingleton(STAKING_NETWORK_REWARDS_KEY)) .willReturn((WritableSingletonState) stakingRewardsState); - final var stakingRewardsStore = new WritableNetworkStakingRewardsStore(states); + stakingRewardsStore = new WritableNetworkStakingRewardsStore(states); given(context.writableStore(WritableNetworkStakingRewardsStore.class)).willReturn(stakingRewardsStore); given(context.addUncheckedPrecedingChildRecordBuilder(NodeStakeUpdateRecordBuilder.class)) .willReturn(nodeStakeUpdateRecordBuilder); given(context.knownNodeIds()).willReturn(Set.of(NODE_NUM_1.number(), NODE_NUM_2.number(), NODE_NUM_3.number())); - - // Assert preconditions - Assertions.assertThat(STAKING_INFO_1.weight()).isZero(); - Assertions.assertThat(STAKING_INFO_2.weight()).isZero(); - Assertions.assertThat(STAKING_INFO_3.weight()).isZero(); - Assertions.assertThat(STAKING_INFO_1.pendingRewards()).isZero(); - Assertions.assertThat(STAKING_INFO_2.pendingRewards()).isZero(); - Assertions.assertThat(STAKING_INFO_3.pendingRewards()).isZero(); - - subject.updateNodes(context); - - Assertions.assertThat(stakingRewardsStore.totalStakeRewardStart()) - .isEqualTo(STAKE_TO_REWARD_1 + STAKE_TO_REWARD_2 + STAKE_TO_REWARD_3); - Assertions.assertThat(stakingRewardsStore.totalStakedStart()).isEqualTo(130000000000L); - final var resultStakingInfo1 = stakingInfoStore.get(NODE_NUM_1.number()); - final var resultStakingInfo2 = stakingInfoStore.get(NODE_NUM_2.number()); - final var resultStakingInfo3 = stakingInfoStore.get(NODE_NUM_3.number()); - Assertions.assertThat(resultStakingInfo1.stake()).isEqualTo(80000000000L); - Assertions.assertThat(resultStakingInfo2.stake()).isEqualTo(50000000000L); - Assertions.assertThat(resultStakingInfo3.stake()).isZero(); - Assertions.assertThat(resultStakingInfo1.unclaimedStakeRewardStart()).isZero(); - Assertions.assertThat(resultStakingInfo2.unclaimedStakeRewardStart()).isZero(); - Assertions.assertThat(resultStakingInfo3.unclaimedStakeRewardStart()).isZero(); - Assertions.assertThat(resultStakingInfo1.rewardSumHistory()).isEqualTo(List.of(86L, 6L, 5L)); - Assertions.assertThat(resultStakingInfo2.rewardSumHistory()).isEqualTo(List.of(101L, 1L, 1L)); - Assertions.assertThat(resultStakingInfo3.rewardSumHistory()).isEqualTo(List.of(11L, 3L, 1L)); - Assertions.assertThat(resultStakingInfo1.weight()).isEqualTo(307); - Assertions.assertThat(resultStakingInfo2.weight()).isEqualTo(192); - Assertions.assertThat(resultStakingInfo3.weight()).isZero(); - Assertions.assertThat(resultStakingInfo1.pendingRewards()).isEqualTo(72000); - Assertions.assertThat(resultStakingInfo2.pendingRewards()).isEqualTo(63000L); - Assertions.assertThat(resultStakingInfo3.pendingRewards()).isEqualTo(72000L); - Assertions.assertThat(resultStakingInfo1.weight() + resultStakingInfo2.weight() + resultStakingInfo3.weight()) - .isLessThanOrEqualTo(SUM_OF_CONSENSUS_WEIGHTS); - } - - @Test - void calculatesMidnightTimeCorrectly() { - final var consensusSecs = 1653660350L; - final var consensusNanos = 12345L; - final var expectedNanos = 999_999_999; - final var consensusTime = Instant.ofEpochSecond(consensusSecs, consensusNanos); - final var expectedMidnightTime = - Timestamp.newBuilder().seconds(1653609599L).nanos(expectedNanos).build(); - - Assertions.assertThat(subject.lastInstantOfPreviousPeriodFor(consensusTime)) - .isEqualTo(expectedMidnightTime); } private static final int SUM_OF_CONSENSUS_WEIGHTS = 500; @@ -350,16 +437,32 @@ void calculatesMidnightTimeCorrectly() { private static final List REWARD_SUM_HISTORY_1 = List.of(8L, 7L, 2L); private static final List REWARD_SUM_HISTORY_2 = List.of(5L, 5L, 4L); private static final List REWARD_SUM_HISTORY_3 = List.of(4L, 2L, 1L); + /** + * Node number 1 for the test nodes. + */ public static final EntityNumber NODE_NUM_1 = EntityNumber.newBuilder().number(1).build(); + /** + * Node number 2 for the test nodes. + */ public static final EntityNumber NODE_NUM_2 = EntityNumber.newBuilder().number(2).build(); + /** + * Node number 3 for the test nodes. + */ public static final EntityNumber NODE_NUM_3 = EntityNumber.newBuilder().number(3).build(); + /** + * Node number 4 for the test nodes. + */ public static final EntityNumber NODE_NUM_4 = EntityNumber.newBuilder().number(4).build(); + /** + * Node number 8 for the test nodes. + */ public static final EntityNumber NODE_NUM_8 = EntityNumber.newBuilder().number(8).build(); + /** Staking info for node 1. */ public static final StakingNodeInfo STAKING_INFO_1 = StakingNodeInfo.newBuilder() .nodeNumber(NODE_NUM_1.number()) .minStake(MIN_STAKE) @@ -373,6 +476,7 @@ void calculatesMidnightTimeCorrectly() { .deleted(false) .weight(0) .build(); + /** Staking info for node 2. */ public static final StakingNodeInfo STAKING_INFO_2 = StakingNodeInfo.newBuilder() .nodeNumber(NODE_NUM_2.number()) .minStake(MIN_STAKE) @@ -386,6 +490,7 @@ void calculatesMidnightTimeCorrectly() { .deleted(false) .weight(0) .build(); + /** Staking info for node 3. */ public static final StakingNodeInfo STAKING_INFO_3 = StakingNodeInfo.newBuilder() .nodeNumber(NODE_NUM_3.number()) .minStake(MIN_STAKE) diff --git a/hedera-node/hedera-token-service-impl/src/test/java/com/hedera/node/app/service/token/impl/test/handlers/staking/StakeInfoHelperTest.java b/hedera-node/hedera-token-service-impl/src/test/java/com/hedera/node/app/service/token/impl/test/handlers/staking/StakeInfoHelperTest.java index 868bbfae4294..7d0ac5742896 100644 --- a/hedera-node/hedera-token-service-impl/src/test/java/com/hedera/node/app/service/token/impl/test/handlers/staking/StakeInfoHelperTest.java +++ b/hedera-node/hedera-token-service-impl/src/test/java/com/hedera/node/app/service/token/impl/test/handlers/staking/StakeInfoHelperTest.java @@ -23,8 +23,8 @@ import com.hedera.node.app.service.token.impl.TokenServiceImpl; import com.hedera.node.app.service.token.impl.WritableStakingInfoStore; import com.hedera.node.app.service.token.impl.handlers.staking.StakeInfoHelper; -import com.hedera.node.app.spi.fixtures.state.MapWritableKVState; import com.hedera.node.app.spi.fixtures.state.MapWritableStates; +import com.swirlds.platform.test.fixtures.state.MapWritableKVState; import java.util.Map; import org.assertj.core.api.Assertions; import org.junit.jupiter.api.BeforeEach; diff --git a/hedera-node/hedera-token-service-impl/src/test/java/com/hedera/node/app/service/token/impl/test/handlers/staking/StakePeriodManagerTest.java b/hedera-node/hedera-token-service-impl/src/test/java/com/hedera/node/app/service/token/impl/test/handlers/staking/StakePeriodManagerTest.java index bc07f4e4457e..9ae8f1918ab3 100644 --- a/hedera-node/hedera-token-service-impl/src/test/java/com/hedera/node/app/service/token/impl/test/handlers/staking/StakePeriodManagerTest.java +++ b/hedera-node/hedera-token-service-impl/src/test/java/com/hedera/node/app/service/token/impl/test/handlers/staking/StakePeriodManagerTest.java @@ -28,13 +28,13 @@ import com.hedera.node.app.service.token.ReadableNetworkStakingRewardsStore; import com.hedera.node.app.service.token.impl.ReadableNetworkStakingRewardsStoreImpl; import com.hedera.node.app.service.token.impl.handlers.staking.StakePeriodManager; -import com.hedera.node.app.spi.state.WritableSingletonState; -import com.hedera.node.app.spi.state.WritableSingletonStateBase; -import com.hedera.node.app.spi.state.WritableStates; import com.hedera.node.config.ConfigProvider; import com.hedera.node.config.VersionedConfigImpl; import com.hedera.node.config.VersionedConfiguration; import com.hedera.node.config.testfixtures.HederaTestConfigBuilder; +import com.swirlds.platform.state.spi.WritableSingletonStateBase; +import com.swirlds.state.spi.WritableSingletonState; +import com.swirlds.state.spi.WritableStates; import java.time.Instant; import java.time.LocalDate; import java.time.ZoneOffset; diff --git a/hedera-node/hedera-token-service-impl/src/test/java/com/hedera/node/app/service/token/impl/test/handlers/staking/StakingRewardsHandlerImplTest.java b/hedera-node/hedera-token-service-impl/src/test/java/com/hedera/node/app/service/token/impl/test/handlers/staking/StakingRewardsHandlerImplTest.java index 7620493ce34a..5318f0831996 100644 --- a/hedera-node/hedera-token-service-impl/src/test/java/com/hedera/node/app/service/token/impl/test/handlers/staking/StakingRewardsHandlerImplTest.java +++ b/hedera-node/hedera-token-service-impl/src/test/java/com/hedera/node/app/service/token/impl/test/handlers/staking/StakingRewardsHandlerImplTest.java @@ -37,12 +37,15 @@ import com.hedera.node.app.service.token.impl.handlers.staking.StakingRewardsDistributor; import com.hedera.node.app.service.token.impl.handlers.staking.StakingRewardsHandlerImpl; import com.hedera.node.app.service.token.impl.handlers.staking.StakingRewardsHelper; +import com.hedera.node.app.service.token.impl.handlers.staking.StakingUtilities; import com.hedera.node.app.service.token.impl.test.handlers.util.CryptoTokenHandlerTestBase; import com.hedera.node.app.service.token.records.CryptoDeleteRecordBuilder; import com.hedera.node.app.service.token.records.FinalizeContext; import com.hedera.node.app.spi.metrics.StoreMetricsService; import com.hedera.node.app.spi.workflows.record.DeleteCapableTransactionRecordBuilder; import com.hedera.node.config.ConfigProvider; +import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationTargetException; import java.time.Instant; import java.time.LocalDate; import java.time.ZoneOffset; @@ -94,6 +97,28 @@ public void setUp() { subject = new StakingRewardsHandlerImpl(rewardsPayer, stakePeriodManager, stakeInfoHelper); } + @Test + void testCoverageForPrivateConstructor() + throws NoSuchMethodException, InstantiationException, IllegalAccessException { + final Constructor constructor = StakingUtilities.class.getDeclaredConstructor(); + constructor.setAccessible(true); + try { + constructor.newInstance(); + } catch (InvocationTargetException e) { + Throwable cause = e.getCause(); + assertThat(cause.getClass()).isEqualTo(UnsupportedOperationException.class); + } + } + + @Test + void testStakeMetaChangesForNullOriginalAccount() { + noStakeChanges(); + final var rewards = subject.applyStakingRewards(context, Collections.emptySet(), emptyMap()); + final var modifiedAccount = writableAccountStore.get(payerId); + assertThat(modifiedAccount).isNotNull(); + assertThat(StakingUtilities.hasStakeMetaChanges(null, modifiedAccount)).isTrue(); + } + @Test void changingKeyOnlyIsNotRewardSituation() { final var stakedToMeBefore = account.stakedToMe(); diff --git a/hedera-node/hedera-token-service-impl/src/test/java/com/hedera/node/app/service/token/impl/test/handlers/transfer/AccountAmountUtils.java b/hedera-node/hedera-token-service-impl/src/test/java/com/hedera/node/app/service/token/impl/test/handlers/transfer/AccountAmountUtils.java index a79d2079cea2..9a07b34a79ff 100644 --- a/hedera-node/hedera-token-service-impl/src/test/java/com/hedera/node/app/service/token/impl/test/handlers/transfer/AccountAmountUtils.java +++ b/hedera-node/hedera-token-service-impl/src/test/java/com/hedera/node/app/service/token/impl/test/handlers/transfer/AccountAmountUtils.java @@ -21,15 +21,30 @@ import com.hedera.hapi.node.base.NftTransfer; import com.hedera.pbj.runtime.io.buffer.Bytes; +/** + * Utility class for creating {@link AccountAmount} and {@link NftTransfer} objects. + */ public final class AccountAmountUtils { private AccountAmountUtils() { throw new UnsupportedOperationException("Utility class"); } + /** + * Create an {@link AccountAmount} object with the given account and amount. + * @param account the account + * @param amount the amount + * @return the {@link AccountAmount} object + */ public static AccountAmount aaWith(AccountID account, long amount) { return AccountAmount.newBuilder().accountID(account).amount(amount).build(); } + /** + * Create an {@link AccountAmount} object with the given account and amount, and set the approval flag. + * @param account the account + * @param amount the amount + * @return the {@link AccountAmount} object + */ public static AccountAmount aaWithAllowance(AccountID account, long amount) { return AccountAmount.newBuilder() .accountID(account) @@ -38,10 +53,22 @@ public static AccountAmount aaWithAllowance(AccountID account, long amount) { .build(); } + /** + * Create an {@link AccountID} object with the given alias. + * @param alias the alias + * @return the {@link AccountID} object + */ public static AccountID asAccountWithAlias(Bytes alias) { return AccountID.newBuilder().alias(alias).build(); } + /** + * Create an {@link NftTransfer} object with the given sender, receiver, and serial number. + * @param from the sender + * @param to the receiver + * @param serialNo the serial number + * @return the {@link NftTransfer} object + */ public static NftTransfer nftTransferWith(AccountID from, AccountID to, long serialNo) { return NftTransfer.newBuilder() .senderAccountID(from) @@ -50,6 +77,13 @@ public static NftTransfer nftTransferWith(AccountID from, AccountID to, long ser .build(); } + /** + * Create an {@link NftTransfer} object with the given sender, receiver, and serial number, and set the approval flag to true. + * @param from the sender + * @param to the receiver + * @param serialNo the serial number + * @return the {@link NftTransfer} object + */ public static NftTransfer nftTransferWithAllowance(AccountID from, AccountID to, long serialNo) { return NftTransfer.newBuilder() .senderAccountID(from) @@ -59,6 +93,12 @@ public static NftTransfer nftTransferWithAllowance(AccountID from, AccountID to, .build(); } + /** + * Create an {@link AccountAmount} object with the given aliased accountId with given alias and amount. + * @param alias the alias + * @param amount the amount + * @return the {@link AccountAmount} object + */ public static AccountAmount aaAlias(final Bytes alias, final long amount) { return AccountAmount.newBuilder() .amount(amount) diff --git a/hedera-node/hedera-token-service-impl/src/test/java/com/hedera/node/app/service/token/impl/test/handlers/transfer/AssociateTokenRecipientsStepTest.java b/hedera-node/hedera-token-service-impl/src/test/java/com/hedera/node/app/service/token/impl/test/handlers/transfer/AssociateTokenRecipientsStepTest.java index 1a1e94d3614e..26886b8cce53 100644 --- a/hedera-node/hedera-token-service-impl/src/test/java/com/hedera/node/app/service/token/impl/test/handlers/transfer/AssociateTokenRecipientsStepTest.java +++ b/hedera-node/hedera-token-service-impl/src/test/java/com/hedera/node/app/service/token/impl/test/handlers/transfer/AssociateTokenRecipientsStepTest.java @@ -34,7 +34,6 @@ import com.hedera.node.app.service.token.records.CryptoTransferRecordBuilder; import com.hedera.node.app.spi.validation.ExpiryValidator; import com.hedera.node.app.spi.workflows.HandleContext; -import com.hedera.pbj.runtime.io.buffer.Bytes; import java.util.List; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -42,6 +41,9 @@ import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; +/** + * Unit tests for {@link AssociateTokenRecipientsStep}. + */ @ExtendWith(MockitoExtension.class) public class AssociateTokenRecipientsStepTest extends StepsBase { @Mock(strictness = Mock.Strictness.LENIENT) @@ -103,23 +105,11 @@ void givenValidTxn() { given(expiryValidator.expirationStatus(any(), anyBoolean(), anyLong())).willReturn(ResponseCodeEnum.OK); } - public static AccountAmount adjustFrom(AccountID account, long amount) { + private AccountAmount adjustFrom(AccountID account, long amount) { return AccountAmount.newBuilder().accountID(account).amount(amount).build(); } - public static AccountAmount adjustFromWithAllowance(AccountID account, long amount) { - return AccountAmount.newBuilder() - .accountID(account) - .amount(amount) - .isApproval(true) - .build(); - } - - public static AccountID asAccountWithAlias(String alias) { - return AccountID.newBuilder().alias(Bytes.wrap(alias)).build(); - } - - public static NftTransfer nftTransferWith(AccountID from, AccountID to, long serialNo) { + private NftTransfer nftTransferWith(AccountID from, AccountID to, long serialNo) { return NftTransfer.newBuilder() .senderAccountID(from) .receiverAccountID(to) diff --git a/hedera-node/hedera-token-service-impl/src/test/java/com/hedera/node/app/service/token/impl/test/handlers/transfer/StepsBase.java b/hedera-node/hedera-token-service-impl/src/test/java/com/hedera/node/app/service/token/impl/test/handlers/transfer/StepsBase.java index 79e0ec2327c4..2e51948f36e3 100644 --- a/hedera-node/hedera-token-service-impl/src/test/java/com/hedera/node/app/service/token/impl/test/handlers/transfer/StepsBase.java +++ b/hedera-node/hedera-token-service-impl/src/test/java/com/hedera/node/app/service/token/impl/test/handlers/transfer/StepsBase.java @@ -71,6 +71,9 @@ import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; +/** + * Provides common setup for transfer handler tests. + */ @ExtendWith(MockitoExtension.class) public class StepsBase extends CryptoTokenHandlerTestBase { @Mock diff --git a/hedera-node/hedera-token-service-impl/src/test/java/com/hedera/node/app/service/token/impl/test/handlers/transfer/customfees/CustomFixedFeeAssessorTest.java b/hedera-node/hedera-token-service-impl/src/test/java/com/hedera/node/app/service/token/impl/test/handlers/transfer/customfees/CustomFixedFeeAssessorTest.java new file mode 100644 index 000000000000..2275a83d9b8a --- /dev/null +++ b/hedera-node/hedera-token-service-impl/src/test/java/com/hedera/node/app/service/token/impl/test/handlers/transfer/customfees/CustomFixedFeeAssessorTest.java @@ -0,0 +1,132 @@ +/* + * Copyright (C) 2024 Hedera Hashgraph, LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.hedera.node.app.service.token.impl.test.handlers.transfer.customfees; + +import static com.hedera.node.app.service.token.impl.handlers.BaseCryptoHandler.asAccount; +import static com.hedera.node.app.service.token.impl.handlers.BaseTokenHandler.asToken; +import static com.hedera.node.app.service.token.impl.test.handlers.util.CryptoTokenHandlerTestBase.withFixedFee; +import static com.hedera.node.app.service.token.impl.test.handlers.util.TransferUtil.asNftTransferList; +import static org.assertj.core.api.Assertions.assertThat; + +import com.hedera.hapi.node.base.AccountID; +import com.hedera.hapi.node.base.TokenID; +import com.hedera.hapi.node.base.TokenTransferList; +import com.hedera.hapi.node.base.TokenType; +import com.hedera.hapi.node.transaction.AssessedCustomFee; +import com.hedera.hapi.node.transaction.CustomFee; +import com.hedera.hapi.node.transaction.FixedFee; +import com.hedera.node.app.service.token.impl.handlers.transfer.customfees.AssessmentResult; +import com.hedera.node.app.service.token.impl.handlers.transfer.customfees.CustomFeeMeta; +import com.hedera.node.app.service.token.impl.handlers.transfer.customfees.CustomFixedFeeAssessor; +import java.util.List; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.junit.jupiter.MockitoExtension; + +@ExtendWith(MockitoExtension.class) +public class CustomFixedFeeAssessorTest { + + private CustomFixedFeeAssessor subject; + + private AssessmentResult result; + + private final AccountID payer = asAccount(4001); + private final AccountID otherCollector = asAccount(1001); + private final AccountID funding = asAccount(98); + private final TokenID firstFungibleTokenId = asToken(3000); + private final AccountID minter = asAccount(6000); + private final TokenID nonFungibleTokenId = asToken(70000); + private final TokenTransferList nftTransferList = asNftTransferList(nonFungibleTokenId, payer, funding, 1); + final FixedFee htsFixedFee = FixedFee.newBuilder() + .denominatingTokenId(firstFungibleTokenId) + .amount(1) + .build(); + final FixedFee hbarFixedFee = FixedFee.newBuilder().amount(1).build(); + private final AssessedCustomFee htsAssessedFee = AssessedCustomFee.newBuilder() + .amount(1) + .tokenId(firstFungibleTokenId) + .effectivePayerAccountId(payer) + .feeCollectorAccountId(otherCollector) + .build(); + private final AssessedCustomFee hbarAssessedFee = AssessedCustomFee.newBuilder() + .amount(1) + .effectivePayerAccountId(payer) + .feeCollectorAccountId(otherCollector) + .build(); + + @BeforeEach + void setUp() { + subject = new CustomFixedFeeAssessor(); + } + + @Test + void delegatesToHbarWhenDenomIsNull() { + result = new AssessmentResult(List.of(nftTransferList), List.of()); + final var hbarFee = withFixedFee(hbarFixedFee, otherCollector, false); + final var feeMeta = withCustomFeeMeta(List.of(hbarFee), TokenType.FUNGIBLE_COMMON); + + subject.assessFixedFees(feeMeta, payer, result); + assertThat(result.getAssessedCustomFees()).isNotEmpty(); + assertThat(result.getAssessedCustomFees()).contains(hbarAssessedFee); + } + + @Test + void delegatesToHtsWhenDenomIsNonNull() { + result = new AssessmentResult(List.of(nftTransferList), List.of()); + final var hbarFee = withFixedFee(htsFixedFee, otherCollector, false); + final var feeMeta = withCustomFeeMeta(List.of(hbarFee), TokenType.FUNGIBLE_COMMON); + + subject.assessFixedFees(feeMeta, payer, result); + assertThat(result.getAssessedCustomFees()).isNotEmpty(); + assertThat(result.getAssessedCustomFees()).contains(htsAssessedFee); + } + + @Test + void fixedCustomFeeExemptIsOk() { + result = new AssessmentResult(List.of(nftTransferList), List.of()); + final var hbarFee = withFixedFee(htsFixedFee, payer, false); + final var feeMeta = withCustomFeeMeta(List.of(hbarFee), TokenType.FUNGIBLE_COMMON); + + subject.assessFixedFees(feeMeta, payer, result); + assertThat(result.getAssessedCustomFees()).isEmpty(); + } + + @Test + void exemptsAssessmentWhenSenderSameAsCollector() { + result = new AssessmentResult(List.of(nftTransferList), List.of()); + final var hbarFee = withFixedFee(htsFixedFee, payer, false); + final var feeMeta = withCustomFeeMeta(List.of(hbarFee), TokenType.FUNGIBLE_COMMON); + + subject.assessFixedFees(feeMeta, payer, result); + assertThat(result.getAssessedCustomFees()).isEmpty(); + } + + @Test + void ignoresIfPayerExempt() { + result = new AssessmentResult(List.of(nftTransferList), List.of()); + final var hbarFee = withFixedFee(htsFixedFee, payer, false); + final var feeMeta = withCustomFeeMeta(List.of(hbarFee), TokenType.FUNGIBLE_COMMON); + + subject.assessFixedFee(feeMeta, payer, hbarFee, result); + assertThat(result.getAssessedCustomFees()).isEmpty(); + } + + private CustomFeeMeta withCustomFeeMeta(List customFees, TokenType tokenType) { + return new CustomFeeMeta(firstFungibleTokenId, minter, customFees, tokenType); + } +} diff --git a/hedera-node/hedera-token-service-impl/src/test/java/com/hedera/node/app/service/token/impl/test/handlers/transfer/customfees/CustomFractionalFeeAssessorTest.java b/hedera-node/hedera-token-service-impl/src/test/java/com/hedera/node/app/service/token/impl/test/handlers/transfer/customfees/CustomFractionalFeeAssessorTest.java new file mode 100644 index 000000000000..3a4948cbf695 --- /dev/null +++ b/hedera-node/hedera-token-service-impl/src/test/java/com/hedera/node/app/service/token/impl/test/handlers/transfer/customfees/CustomFractionalFeeAssessorTest.java @@ -0,0 +1,517 @@ +/* + * Copyright (C) 2024 Hedera Hashgraph, LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.hedera.node.app.service.token.impl.test.handlers.transfer.customfees; + +import static com.hedera.hapi.node.base.ResponseCodeEnum.CUSTOM_FEE_MUST_BE_POSITIVE; +import static com.hedera.hapi.node.base.ResponseCodeEnum.CUSTOM_FEE_OUTSIDE_NUMERIC_RANGE; +import static com.hedera.hapi.node.base.ResponseCodeEnum.INSUFFICIENT_SENDER_ACCOUNT_BALANCE_FOR_CUSTOM_FEE; +import static com.hedera.hapi.node.base.TokenType.FUNGIBLE_COMMON; +import static com.hedera.node.app.service.token.impl.handlers.BaseCryptoHandler.asAccount; +import static com.hedera.node.app.service.token.impl.handlers.BaseTokenHandler.asToken; +import static com.hedera.node.app.service.token.impl.handlers.transfer.customfees.AdjustmentUtils.safeFractionMultiply; +import static com.hedera.node.app.service.token.impl.test.handlers.util.CryptoTokenHandlerTestBase.withFixedFee; +import static com.hedera.node.app.service.token.impl.test.handlers.util.CryptoTokenHandlerTestBase.withFractionalFee; +import static com.hedera.node.app.service.token.impl.test.handlers.util.TransferUtil.asAccountAmount; +import static com.hedera.node.app.service.token.impl.test.handlers.util.TransferUtil.asTokenTransferList; +import static com.hedera.node.app.spi.fixtures.workflows.ExceptionConditions.responseCode; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; + +import com.hedera.hapi.node.base.AccountID; +import com.hedera.hapi.node.base.Fraction; +import com.hedera.hapi.node.base.TokenID; +import com.hedera.hapi.node.base.TokenTransferList; +import com.hedera.hapi.node.base.TokenType; +import com.hedera.hapi.node.transaction.AssessedCustomFee; +import com.hedera.hapi.node.transaction.CustomFee; +import com.hedera.hapi.node.transaction.FixedFee; +import com.hedera.hapi.node.transaction.FractionalFee; +import com.hedera.node.app.service.token.impl.handlers.transfer.customfees.AssessmentResult; +import com.hedera.node.app.service.token.impl.handlers.transfer.customfees.CustomFeeMeta; +import com.hedera.node.app.service.token.impl.handlers.transfer.customfees.CustomFixedFeeAssessor; +import com.hedera.node.app.service.token.impl.handlers.transfer.customfees.CustomFractionalFeeAssessor; +import com.hedera.node.app.spi.workflows.HandleException; +import java.math.BigInteger; +import java.util.List; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +@ExtendWith(MockitoExtension.class) +public class CustomFractionalFeeAssessorTest { + @Mock + private CustomFixedFeeAssessor fixedFeeAssessor; + + private CustomFractionalFeeAssessor subject; + private AssessmentResult result; + + private final AccountID payer = asAccount(4001); + private final TokenID tokenWithFractionalFee = asToken(3000); + private final AccountID minter = asAccount(6000); + private final AccountID firstReclaimedAcount = asAccount(8000); + private final AccountID secondReclaimedAcount = asAccount(9000); + private final long vanillaTriggerAmount = 5000L; + private final long firstCreditAmount = 4000L; + private final long secondCreditAmount = 1000L; + private final long minApplicableTriggerAmount = 50L; + private final long maxApplicableTriggerAmount = 50_000L; + private final long firstMinAmountOfFractionalFee = 2L; + private final long firstMaxAmountOfFractionalFee = 100L; + private final long firstNumerator = 1L; + private final long firstDenominator = 100L; + private final long netOfTransfersMinAmountOfFractionalFee = 3L; + private final long netOfTransfersMaxAmountOfFractionalFee = 101L; + private final long netOfTransfersNumerator = 2L; + private final long netOfTransfersDenominator = 101L; + private final long secondMinAmountOfFractionalFee = 10L; + private final long secondMaxAmountOfFractionalFee = 1000L; + private final long secondNumerator = 1L; + private final long secondDenominator = 10L; + private final long nonsenseNumerator = Long.MAX_VALUE; + private final long nonsenseDenominator = 1L; + private final boolean notNetOfTransfers = false; + private final AccountID firstFractionalFeeCollector = asAccount(6666L); + private final AccountID secondFractionalFeeCollector = asAccount(7777L); + private final AccountID netOfTransfersFeeCollector = asAccount(8888L); + + final FractionalFee netOfTransferFractionalFee = FractionalFee.newBuilder() + .maximumAmount(netOfTransfersMaxAmountOfFractionalFee) + .minimumAmount(netOfTransfersMinAmountOfFractionalFee) + .netOfTransfers(true) + .fractionalAmount(Fraction.newBuilder() + .numerator(netOfTransfersNumerator) + .denominator(netOfTransfersDenominator) + .build()) + .build(); + final FractionalFee firstFractionalFeeWithoutNetOfTransfers = FractionalFee.newBuilder() + .maximumAmount(firstMaxAmountOfFractionalFee) + .minimumAmount(firstMinAmountOfFractionalFee) + .netOfTransfers(false) + .fractionalAmount(Fraction.newBuilder() + .numerator(firstNumerator) + .denominator(firstDenominator) + .build()) + .build(); + final FractionalFee secondFractionalFeeWithoutNetOfTransfers = FractionalFee.newBuilder() + .maximumAmount(secondMaxAmountOfFractionalFee) + .minimumAmount(secondMinAmountOfFractionalFee) + .netOfTransfers(false) + .fractionalAmount(Fraction.newBuilder() + .numerator(secondNumerator) + .denominator(secondDenominator) + .build()) + .build(); + + final FractionalFee exemptFractionalFeeWithoutNetOfTransfers = FractionalFee.newBuilder() + .maximumAmount(secondMaxAmountOfFractionalFee) + .minimumAmount(firstMinAmountOfFractionalFee) + .netOfTransfers(false) + .fractionalAmount(Fraction.newBuilder() + .numerator(firstNumerator) + .denominator(firstDenominator) + .build()) + .build(); + + final FractionalFee nonsenseFractionalFee = FractionalFee.newBuilder() + .maximumAmount(1) + .minimumAmount(1) + .netOfTransfers(false) + .fractionalAmount(Fraction.newBuilder() + .numerator(nonsenseNumerator) + .denominator(nonsenseDenominator) + .build()) + .build(); + + private final CustomFee skippedFixedFee = + withFixedFee(FixedFee.newBuilder().amount(100L).build(), null, notNetOfTransfers); + private final CustomFee fractionalCustomFeeNetOfTransfers = + withFractionalFee(netOfTransferFractionalFee, netOfTransfersFeeCollector, true); + private final CustomFee firstFractionalCustomFee = + withFractionalFee(firstFractionalFeeWithoutNetOfTransfers, firstFractionalFeeCollector, false); + private final CustomFee secondFractionalCustomFee = + withFractionalFee(secondFractionalFeeWithoutNetOfTransfers, secondFractionalFeeCollector, false); + private final CustomFee exemptFractionalCustomFee = + withFractionalFee(exemptFractionalFeeWithoutNetOfTransfers, payer, false); + private final CustomFee nonsenseCustomFee = + withFractionalFee(nonsenseFractionalFee, secondFractionalFeeCollector, false); + private final TokenTransferList triggerTransferList = asTokenTransferList( + tokenWithFractionalFee, + List.of( + asAccountAmount(payer, -vanillaTriggerAmount), + asAccountAmount(firstReclaimedAcount, +firstCreditAmount), + asAccountAmount(secondReclaimedAcount, +secondCreditAmount))); + private final AccountID[] effPayerAccountNums = new AccountID[] {firstReclaimedAcount, secondReclaimedAcount}; + + @BeforeEach + void setUp() { + subject = new CustomFractionalFeeAssessor(fixedFeeAssessor); + } + + @Test + void appliesFeesAsExpected() { + result = new AssessmentResult(List.of(triggerTransferList), List.of()); + final CustomFeeMeta feeMeta = withCustomFeeMeta( + List.of( + firstFractionalCustomFee, + secondFractionalCustomFee, + exemptFractionalCustomFee, + skippedFixedFee, + fractionalCustomFeeNetOfTransfers), + FUNGIBLE_COMMON); + + subject.assessFractionalFees(feeMeta, payer, result); + + // firstExpectedFee = 5000 * 1 / 100 = 50 + final var firstExpectedFee = subject.amountOwed(vanillaTriggerAmount, firstFractionalCustomFee.fractionalFee()); + final var secondExpectedFee = + subject.amountOwed(vanillaTriggerAmount, secondFractionalCustomFee.fractionalFee()); + + final var netFee = subject.amountOwed(vanillaTriggerAmount, fractionalCustomFeeNetOfTransfers.fractionalFee()); + final var fixedFee = withFixedFee( + FixedFee.newBuilder() + .denominatingTokenId(tokenWithFractionalFee) + .amount(netFee) + .build(), + netOfTransfersFeeCollector, + true); + + final var expectedAssessedFee1 = AssessedCustomFee.newBuilder() + .feeCollectorAccountId(firstFractionalFeeCollector) + .effectivePayerAccountId(effPayerAccountNums) + .amount(firstExpectedFee) + .tokenId(tokenWithFractionalFee) + .build(); + final var expectedAssessedFee2 = AssessedCustomFee.newBuilder() + .feeCollectorAccountId(secondFractionalFeeCollector) + .effectivePayerAccountId(effPayerAccountNums) + .amount(secondExpectedFee) + .tokenId(tokenWithFractionalFee) + .build(); + + verify(fixedFeeAssessor).assessFixedFee(feeMeta, payer, fixedFee, result); + + assertThat(result.getAssessedCustomFees()).isNotEmpty(); + assertThat(result.getAssessedCustomFees()).contains(expectedAssessedFee1); + assertThat(result.getAssessedCustomFees()).contains(expectedAssessedFee2); + + assertThat(result.getRoyaltiesPaid()).isEmpty(); + } + + @Test + void nonNetOfTransfersWithoutExemptions() { + result = new AssessmentResult(List.of(triggerTransferList), List.of()); + final CustomFeeMeta feeMeta = withCustomFeeMeta(List.of(firstFractionalCustomFee), FUNGIBLE_COMMON); + + subject.assessFractionalFees(feeMeta, payer, result); + + // firstExpectedFee = 5000 * 1 / 100 = 50 + final var firstExpectedFee = subject.amountOwed(vanillaTriggerAmount, firstFractionalCustomFee.fractionalFee()); + // secondExpectedFee = 5000 * 1 / 10 = 500 + final var secondExpectedFee = + subject.amountOwed(vanillaTriggerAmount, secondFractionalCustomFee.fractionalFee()); + // netFee = 5000 * 2 / 101 = 99 + final var netFee = subject.amountOwed(vanillaTriggerAmount, fractionalCustomFeeNetOfTransfers.fractionalFee()); + final var totalReclaimedFees = firstExpectedFee + secondExpectedFee; + final var fixedFee = withFixedFee( + FixedFee.newBuilder() + .denominatingTokenId(tokenWithFractionalFee) + .amount(netFee) + .build(), + netOfTransfersFeeCollector, + true); + + // first assessed fee for fee collector is fraction of credit amounts 4000, 1000. So it is 50 + final var expectedAssessedFee1 = AssessedCustomFee.newBuilder() + .feeCollectorAccountId(firstFractionalFeeCollector) + .effectivePayerAccountId(effPayerAccountNums) + .amount(firstExpectedFee) + .tokenId(tokenWithFractionalFee) + .build(); + + // This is not Net of transfers fee, so it is not assessed + verify(fixedFeeAssessor, never()).assessFixedFee(feeMeta, payer, fixedFee, result); + assertThat(result.getAssessedCustomFees()).hasSize(1); + assertThat(result.getAssessedCustomFees()).contains(expectedAssessedFee1); + + assertThat(result.getRoyaltiesPaid()).isEmpty(); + } + + @Test + void nonNetOfTransfersRespectExemptionsIfPayerIsCollector() { + result = new AssessmentResult(List.of(triggerTransferList), List.of()); + // two custom fees set. First sender is exempt from fees + final CustomFeeMeta feeMeta = withCustomFeeMeta( + List.of( + firstFractionalCustomFee + .copyBuilder() + .feeCollectorAccountId(payer) + .build(), + secondFractionalCustomFee), + FUNGIBLE_COMMON); + + subject.assessFractionalFees(feeMeta, payer, result); + + // firstExpectedFee is ignored due to the fee collector being same as the payer + // secondExpectedFee = 5000 * 1 / 10 = 500 + final var secondExpectedFee = + subject.amountOwed(vanillaTriggerAmount, secondFractionalCustomFee.fractionalFee()); + + // first assessed fee for fee collector is fraction of credit amounts 4000, 1000. So it is 50 + final var expectedAssessedFee1 = AssessedCustomFee.newBuilder() + .feeCollectorAccountId(secondFractionalFeeCollector) + .effectivePayerAccountId(effPayerAccountNums) + .amount(secondExpectedFee) + .tokenId(tokenWithFractionalFee) + .build(); + + // This is not Net of transfers fee, so it is not assessed + verify(fixedFeeAssessor, never()).assessFixedFee(any(), any(), any(), any()); + assertThat(result.getAssessedCustomFees()).hasSize(1); + assertThat(result.getAssessedCustomFees()).contains(expectedAssessedFee1); + + assertThat(result.getRoyaltiesPaid()).isEmpty(); + } + + @Test + void ifNonNetOfTransfersFindsAllExemptThenNoFeesAssessed() { + result = new AssessmentResult(List.of(triggerTransferList), List.of()); + // two custom fees set. First sender is exempt from fees + final CustomFeeMeta feeMeta = withCustomFeeMeta( + List.of(secondFractionalCustomFee + .copyBuilder() + .feeCollectorAccountId(payer) + .allCollectorsAreExempt(true) + .build()), + FUNGIBLE_COMMON); + + subject.assessFractionalFees(feeMeta, payer, result); + + // This is not Net of transfers fee, so it is not assessed + verify(fixedFeeAssessor, never()).assessFixedFee(any(), any(), any(), any()); + assertThat(result.getAssessedCustomFees()).isEmpty(); + assertThat(result.getRoyaltiesPaid()).isEmpty(); + } + + @Test + void abortsOnCreditOverflow() { + final TokenTransferList triggerTransferList = asTokenTransferList( + tokenWithFractionalFee, + List.of( + asAccountAmount(payer, -vanillaTriggerAmount), + asAccountAmount(firstReclaimedAcount, Long.MAX_VALUE), + asAccountAmount(secondReclaimedAcount, Long.MAX_VALUE))); + result = new AssessmentResult(List.of(triggerTransferList), List.of()); + // two custom fees set. First sender is exempt from fees + final CustomFeeMeta feeMeta = + withCustomFeeMeta(List.of(firstFractionalCustomFee, secondFractionalCustomFee), FUNGIBLE_COMMON); + + assertThatThrownBy(() -> subject.assessFractionalFees(feeMeta, payer, result)) + .isInstanceOf(HandleException.class) + .has(responseCode(CUSTOM_FEE_OUTSIDE_NUMERIC_RANGE)); + + // This is not Net of transfers fee, so it is not assessed + verify(fixedFeeAssessor, never()).assessFixedFee(any(), any(), any(), any()); + assertThat(result.getAssessedCustomFees()).isEmpty(); + assertThat(result.getRoyaltiesPaid()).isEmpty(); + } + + @Test + void cannotOverflowWithCrazyFraction() { + final TokenTransferList triggerTransferList = asTokenTransferList( + tokenWithFractionalFee, + List.of( + asAccountAmount(payer, -vanillaTriggerAmount), + asAccountAmount(firstReclaimedAcount, firstCreditAmount), + asAccountAmount(secondReclaimedAcount, secondCreditAmount))); + result = new AssessmentResult(List.of(triggerTransferList), List.of()); + // two custom fees set. First sender is exempt from fees + final CustomFeeMeta feeMeta = + withCustomFeeMeta(List.of(nonsenseCustomFee, secondFractionalCustomFee), FUNGIBLE_COMMON); + + assertThatThrownBy(() -> subject.assessFractionalFees(feeMeta, payer, result)) + .isInstanceOf(HandleException.class) + .has(responseCode(CUSTOM_FEE_OUTSIDE_NUMERIC_RANGE)); + + // This is not Net of transfers fee, so it is not assessed + verify(fixedFeeAssessor, never()).assessFixedFee(any(), any(), any(), any()); + assertThat(result.getAssessedCustomFees()).isEmpty(); + assertThat(result.getRoyaltiesPaid()).isEmpty(); + } + + @Test + void failsWithInsufficientBalanceWhenAppropriate() { + final TokenTransferList triggerTransferList = asTokenTransferList( + tokenWithFractionalFee, List.of(asAccountAmount(payer, -1), asAccountAmount(firstReclaimedAcount, 1))); + result = new AssessmentResult(List.of(triggerTransferList), List.of()); + // two custom fees set. First sender is exempt from fees + final CustomFeeMeta feeMeta = + withCustomFeeMeta(List.of(nonsenseCustomFee, secondFractionalCustomFee), FUNGIBLE_COMMON); + + assertThatThrownBy(() -> subject.assessFractionalFees(feeMeta, payer, result)) + .isInstanceOf(HandleException.class) + .has(responseCode(INSUFFICIENT_SENDER_ACCOUNT_BALANCE_FOR_CUSTOM_FEE)); + } + + @Test + void failsFastOnJustPositiveAdjustment() { + final TokenTransferList triggerTransferList = + asTokenTransferList(tokenWithFractionalFee, List.of(asAccountAmount(payer, Long.MAX_VALUE))); + result = new AssessmentResult(List.of(triggerTransferList), List.of()); + // two custom fees set. First sender is exempt from fees + final CustomFeeMeta feeMeta = withCustomFeeMeta(List.of(), FUNGIBLE_COMMON); + + assertThatThrownBy(() -> subject.assessFractionalFees(feeMeta, payer, result)) + .isInstanceOf(HandleException.class) + .has(responseCode(CUSTOM_FEE_MUST_BE_POSITIVE)); + } + + @Test + void handlesEasyCase() { + long reasonable = 1_234_567L; + long n = 10; + long d = 9; + + final var expected = reasonable * n / d; + + assertThat(safeFractionMultiply(n, d, reasonable)).isEqualTo(expected); + } + + @Test + void fallsBackToArbitraryPrecisionIfNeeded() { + long huge = Long.MAX_VALUE / 2; + long n = 10; + long d = 9; + final var expected = BigInteger.valueOf(huge) + .multiply(BigInteger.valueOf(n)) + .divide(BigInteger.valueOf(d)) + .longValueExact(); + + assertThat(safeFractionMultiply(n, d, huge)).isEqualTo(expected); + } + + @Test + void propagatesArithmeticExceptionOnOverflow() { + long huge = Long.MAX_VALUE - 1; + long n = 10; + long d = 9; + + assertThatThrownBy(() -> safeFractionMultiply(n, d, huge)).isInstanceOf(ArithmeticException.class); + } + + @Test + void computesCheckingAmountOwned() { + assertThat(subject.amountOwed(vanillaTriggerAmount, firstFractionalCustomFee.fractionalFee())) + .isEqualTo(vanillaTriggerAmount / firstDenominator); + } + + @Test + void enforcesMax() { + assertThat(subject.amountOwed(maxApplicableTriggerAmount, firstFractionalCustomFee.fractionalFee())) + .isEqualTo(firstMaxAmountOfFractionalFee); + } + + @Test + void enforcesMin() { + assertThat(subject.amountOwed(minApplicableTriggerAmount, firstFractionalCustomFee.fractionalFee())) + .isEqualTo(firstMinAmountOfFractionalFee); + } + + @Test + void reclaimsRemainderAsExpected() { + final TokenTransferList triggerTransferList = asTokenTransferList( + tokenWithFractionalFee, + List.of( + asAccountAmount(payer, -vanillaTriggerAmount), + asAccountAmount(firstReclaimedAcount, firstCreditAmount + 12_345L), + asAccountAmount(secondReclaimedAcount, secondCreditAmount + 2_345L))); + result = new AssessmentResult(List.of(triggerTransferList), List.of()); + final CustomFeeMeta feeMeta = + withCustomFeeMeta(List.of(firstFractionalCustomFee, secondFractionalCustomFee), FUNGIBLE_COMMON); + + subject.assessFractionalFees(feeMeta, payer, result); + + // firstExpectedFee = 5000 * 1 / 100 = 50 + // Amount to reclaim is 50. The amount to reclaim from credits is 19690 (16345 + 3345). + // The claims happen from the first credit amount is 50 * (16345/19690) = 41 + // The claims happen from the second credit amount is 50 * (3345/19690) = 8 + // So 1 is left to reclaim, which is the remainder.So, we go through credits again and reclaim 1 from the first + final var firstExpectedFee = subject.amountOwed(vanillaTriggerAmount, firstFractionalCustomFee.fractionalFee()); + final var secondExpectedFee = + subject.amountOwed(vanillaTriggerAmount, secondFractionalCustomFee.fractionalFee()); + + final var netFee = subject.amountOwed(vanillaTriggerAmount, fractionalCustomFeeNetOfTransfers.fractionalFee()); + final var fixedFee = withFixedFee( + FixedFee.newBuilder() + .denominatingTokenId(tokenWithFractionalFee) + .amount(netFee) + .build(), + netOfTransfersFeeCollector, + true); + + final var expectedAssessedFee1 = AssessedCustomFee.newBuilder() + .feeCollectorAccountId(firstFractionalFeeCollector) + .effectivePayerAccountId(effPayerAccountNums) + .amount(firstExpectedFee) + .tokenId(tokenWithFractionalFee) + .build(); + final var expectedAssessedFee2 = AssessedCustomFee.newBuilder() + .feeCollectorAccountId(secondFractionalFeeCollector) + .effectivePayerAccountId(effPayerAccountNums) + .amount(secondExpectedFee) + .tokenId(tokenWithFractionalFee) + .build(); + + verify(fixedFeeAssessor, never()).assessFixedFee(any(), any(), any(), any()); + + assertThat(result.getAssessedCustomFees()).isNotEmpty(); + assertThat(result.getAssessedCustomFees()).contains(expectedAssessedFee1); + assertThat(result.getAssessedCustomFees()).contains(expectedAssessedFee2); + + assertThat(result.getRoyaltiesPaid()).isEmpty(); + } + + @Test + void doesNothingIfNoValueExchangedAndNoFallback() { + final var transfersList = + asTokenTransferList(tokenWithFractionalFee, List.of(asAccountAmount(payer, -vanillaTriggerAmount))); + result = new AssessmentResult(List.of(transfersList), List.of()); + + final CustomFeeMeta feeMeta = withCustomFeeMeta( + List.of( + firstFractionalCustomFee, + secondFractionalCustomFee, + exemptFractionalCustomFee, + skippedFixedFee, + fractionalCustomFeeNetOfTransfers), + FUNGIBLE_COMMON); + + subject.assessFractionalFees(feeMeta, payer, result); + + verify(fixedFeeAssessor, never()).assessFixedFee(any(), any(), any(), any()); + assertThat(result.getAssessedCustomFees()).isEmpty(); + assertThat(result.getRoyaltiesPaid()).isEmpty(); + } + + public CustomFeeMeta withCustomFeeMeta(List customFees, TokenType tokenType) { + return new CustomFeeMeta(tokenWithFractionalFee, minter, customFees, tokenType); + } +} diff --git a/hedera-node/hedera-token-service-impl/src/test/java/com/hedera/node/app/service/token/impl/test/handlers/transfer/customfees/CustomRoyaltyFeeAssessorTest.java b/hedera-node/hedera-token-service-impl/src/test/java/com/hedera/node/app/service/token/impl/test/handlers/transfer/customfees/CustomRoyaltyFeeAssessorTest.java new file mode 100644 index 000000000000..aca542e69586 --- /dev/null +++ b/hedera-node/hedera-token-service-impl/src/test/java/com/hedera/node/app/service/token/impl/test/handlers/transfer/customfees/CustomRoyaltyFeeAssessorTest.java @@ -0,0 +1,299 @@ +/* + * Copyright (C) 2024 Hedera Hashgraph, LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.hedera.node.app.service.token.impl.test.handlers.transfer.customfees; + +import static com.hedera.hapi.node.base.TokenType.NON_FUNGIBLE_UNIQUE; +import static com.hedera.node.app.service.token.impl.handlers.BaseCryptoHandler.asAccount; +import static com.hedera.node.app.service.token.impl.handlers.BaseTokenHandler.asToken; +import static com.hedera.node.app.service.token.impl.test.handlers.transfer.AccountAmountUtils.asAccountWithAlias; +import static com.hedera.node.app.service.token.impl.test.handlers.util.CryptoTokenHandlerTestBase.withFixedFee; +import static com.hedera.node.app.service.token.impl.test.handlers.util.CryptoTokenHandlerTestBase.withRoyaltyFee; +import static com.hedera.node.app.service.token.impl.test.handlers.util.TransferUtil.asNftTransferList; +import static com.hedera.node.app.service.token.impl.test.handlers.util.TransferUtil.asTokenTransferList; +import static com.hedera.node.app.service.token.impl.test.handlers.util.TransferUtil.asTransferList; +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; + +import com.hedera.hapi.node.base.AccountAmount; +import com.hedera.hapi.node.base.AccountID; +import com.hedera.hapi.node.base.Fraction; +import com.hedera.hapi.node.base.TokenID; +import com.hedera.hapi.node.base.TokenTransferList; +import com.hedera.hapi.node.base.TokenType; +import com.hedera.hapi.node.base.TransferList; +import com.hedera.hapi.node.transaction.AssessedCustomFee; +import com.hedera.hapi.node.transaction.CustomFee; +import com.hedera.hapi.node.transaction.FixedFee; +import com.hedera.hapi.node.transaction.RoyaltyFee; +import com.hedera.node.app.service.token.impl.handlers.transfer.customfees.AssessmentResult; +import com.hedera.node.app.service.token.impl.handlers.transfer.customfees.CustomFeeMeta; +import com.hedera.node.app.service.token.impl.handlers.transfer.customfees.CustomFixedFeeAssessor; +import com.hedera.node.app.service.token.impl.handlers.transfer.customfees.CustomRoyaltyFeeAssessor; +import com.hedera.pbj.runtime.io.buffer.Bytes; +import java.util.List; +import org.apache.commons.lang3.tuple.Pair; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +@ExtendWith(MockitoExtension.class) +public class CustomRoyaltyFeeAssessorTest { + @Mock + private CustomFixedFeeAssessor fixedFeeAssessor; + + private CustomRoyaltyFeeAssessor subject; + private AssessmentResult result; + + private final long originalUnits = 100; + private final AccountID payer = asAccount(4001); + private final AccountID otherCollector = asAccount(1001); + private final AccountID targetCollector = asAccount(2001); + private final AccountID funding = asAccount(98); + private final TokenID firstFungibleTokenId = asToken(3000); + private final AccountID minter = asAccount(6000); + private final AccountID alias = asAccountWithAlias(Bytes.wrap("01234567890123456789012345678901")); + private final TokenID nonFungibleTokenId = asToken(70000); + private final TransferList hbarPayerTransferList = asTransferList(originalUnits, payer); + private final TokenTransferList htsPayerTokenTransferList = + asTokenTransferList(firstFungibleTokenId, originalUnits, payer); + private final TokenTransferList nftTransferList = asNftTransferList(nonFungibleTokenId, payer, funding, 1); + private final TokenTransferList nftTransferListWithAlias = asNftTransferList(nonFungibleTokenId, payer, alias, 1); + private final AssessedCustomFee hbarAssessedFee = AssessedCustomFee.newBuilder() + .amount(originalUnits / 2) + .effectivePayerAccountId(payer) + .feeCollectorAccountId(targetCollector) + .build(); + + private final AssessedCustomFee htsAssessedFee = AssessedCustomFee.newBuilder() + .amount(originalUnits / 2) + .tokenId(firstFungibleTokenId) + .effectivePayerAccountId(payer) + .feeCollectorAccountId(targetCollector) + .build(); + + final FixedFee hbarFallbackFee = FixedFee.newBuilder().amount(33).build(); + final FixedFee htsFallbackFee = FixedFee.newBuilder() + .amount(33) + .denominatingTokenId(firstFungibleTokenId) + .build(); + final FixedFee fixedFee = FixedFee.newBuilder() + .denominatingTokenId(firstFungibleTokenId) + .amount(1) + .build(); + final RoyaltyFee royaltyFee = RoyaltyFee.newBuilder() + .exchangeValueFraction( + Fraction.newBuilder().numerator(1).denominator(2).build()) + .build(); + + @BeforeEach + void setUp() { + subject = new CustomRoyaltyFeeAssessor(fixedFeeAssessor); + } + + @Test + void doesNothingIfNoValueExchangedAndNoFallback() { + result = new AssessmentResult(List.of(nftTransferList), List.of()); + + final CustomFeeMeta feeMeta = withCustomFeeMeta( + List.of(withFixedFee(fixedFee, otherCollector, false), withRoyaltyFee(royaltyFee, targetCollector)), + NON_FUNGIBLE_UNIQUE); + + subject.assessRoyaltyFees(feeMeta, payer, funding, result); + + assertThat(result.getAssessedCustomFees()).isEmpty(); + verify(fixedFeeAssessor, never()).assessFixedFee(any(), any(), any(), any()); + + // We add to the set of royalties paid to track the royalties paid. + // Even though nothing is paid, once its analyzed it should be added to the set + assertThat(result.getRoyaltiesPaid()).contains(Pair.of(funding, feeMeta.tokenId())); + assertThat(result.getAssessedCustomFees()).isEmpty(); + } + + @Test + void chargesHbarFallbackAsExpected() { + result = new AssessmentResult(List.of(nftTransferList), List.of()); + + final var royaltyCustomFee = withRoyaltyFee( + royaltyFee.copyBuilder().fallbackFee(hbarFallbackFee).build(), targetCollector); + final var royaltyFixedFee = withFixedFee(fixedFee, otherCollector, false); + + final CustomFeeMeta feeMeta = + withCustomFeeMeta(List.of(royaltyFixedFee, royaltyCustomFee), NON_FUNGIBLE_UNIQUE); + + subject.assessRoyaltyFees(feeMeta, payer, funding, result); + + assertThat(result.getAssessedCustomFees()).isEmpty(); + // receiver will pay the fallback fee + verify(fixedFeeAssessor) + .assessFixedFee(feeMeta, funding, withFixedFee(hbarFallbackFee, targetCollector, false), result); + + // We add to the set of royalties paid to track the royalties paid. It should have an entry with receiver + assertThat(result.getRoyaltiesPaid()).contains(Pair.of(funding, feeMeta.tokenId())); + } + + @Test + void chargesHtsFallbackAsExpected() { + result = new AssessmentResult(List.of(nftTransferList), List.of()); + + final var royaltyCustomFee = withRoyaltyFee( + royaltyFee.copyBuilder().fallbackFee(htsFallbackFee).build(), targetCollector); + final var royaltyFixedFee = withFixedFee(fixedFee, otherCollector, false); + + final CustomFeeMeta feeMeta = + withCustomFeeMeta(List.of(royaltyFixedFee, royaltyCustomFee), NON_FUNGIBLE_UNIQUE); + + subject.assessRoyaltyFees(feeMeta, payer, funding, result); + + assertThat(result.getAssessedCustomFees()).isEmpty(); + // receiver will pay the fallback fee + verify(fixedFeeAssessor) + .assessFixedFee(feeMeta, funding, withFixedFee(htsFallbackFee, targetCollector, false), result); + + // We add to the set of royalties paid to track the royalties paid. It should have an entry with receiver + assertThat(result.getRoyaltiesPaid()).contains(Pair.of(funding, feeMeta.tokenId())); + } + + @Test + void doesntFailIfFallbackNftTransferredToUnknownAlias() { + result = new AssessmentResult(List.of(nftTransferListWithAlias), List.of()); + + final var royaltyCustomFee = withRoyaltyFee( + royaltyFee.copyBuilder().fallbackFee(htsFallbackFee).build(), targetCollector); + final var fixedFee = withFixedFee(this.fixedFee, otherCollector, false); + + final CustomFeeMeta feeMeta = withCustomFeeMeta(List.of(fixedFee, royaltyCustomFee), NON_FUNGIBLE_UNIQUE); + + subject.assessRoyaltyFees(feeMeta, payer, funding, result); + + assertThat(result.getAssessedCustomFees()).isEmpty(); + // receiver will pay the fallback fee + verify(fixedFeeAssessor) + .assessFixedFee(feeMeta, funding, withFixedFee(htsFallbackFee, targetCollector, false), result); + + // We add to the set of royalties paid to track the royalties paid. It should have an entry with receiver + assertThat(result.getRoyaltiesPaid()).contains(Pair.of(funding, feeMeta.tokenId())); + } + + @Test + void skipsIfRoyaltyAlreadyPaidByReceiver() { + result = new AssessmentResult(List.of(nftTransferListWithAlias), List.of()); + // Include royalty already paid + result.addToRoyaltiesPaid(Pair.of(funding, firstFungibleTokenId)); + + final var royaltyCustomFee = withRoyaltyFee( + royaltyFee.copyBuilder().fallbackFee(htsFallbackFee).build(), targetCollector); + final var fixedFee = withFixedFee(this.fixedFee, otherCollector, false); + + final CustomFeeMeta feeMeta = withCustomFeeMeta(List.of(fixedFee, royaltyCustomFee), NON_FUNGIBLE_UNIQUE); + + subject.assessRoyaltyFees(feeMeta, payer, funding, result); + + assertThat(result.getAssessedCustomFees()).isEmpty(); + + verify(fixedFeeAssessor, never()).assessFixedFee(any(), any(), any(), any()); + } + + @Test + void assessRoyaltyOnlyOncePerTokenType() { + result = new AssessmentResult(List.of(nftTransferListWithAlias), List.of()); + // Include royalty already paid by sender + result.addToRoyaltiesPaid(Pair.of(payer, firstFungibleTokenId)); + + final var royaltyCustomFee = withRoyaltyFee( + royaltyFee.copyBuilder().fallbackFee(htsFallbackFee).build(), targetCollector); + final var fixedFee = withFixedFee(this.fixedFee, otherCollector, false); + + final CustomFeeMeta feeMeta = withCustomFeeMeta(List.of(fixedFee, royaltyCustomFee), NON_FUNGIBLE_UNIQUE); + + subject.assessRoyaltyFees(feeMeta, payer, funding, result); + + assertThat(result.getAssessedCustomFees()).isEmpty(); + + verify(fixedFeeAssessor, never()).assessFixedFee(any(), any(), any(), any()); + } + + @Test + void reclaimsFromExchangeValueWhenAvailable() { + final var accountAmounts = List.of(AccountAmount.newBuilder() + .accountID(payer) + .amount(originalUnits) + .build()); + result = new AssessmentResult( + List.of( + nftTransferList, + htsPayerTokenTransferList + .copyBuilder() + .transfers(accountAmounts) + .build()), + accountAmounts); + + final var royaltyCustomFee = withRoyaltyFee( + royaltyFee.copyBuilder().fallbackFee(htsFallbackFee).build(), targetCollector); + final var royaltyFixedFee = withFixedFee(fixedFee, otherCollector, false); + + final CustomFeeMeta feeMeta = + withCustomFeeMeta(List.of(royaltyFixedFee, royaltyCustomFee), NON_FUNGIBLE_UNIQUE); + + subject.assessRoyaltyFees(feeMeta, payer, funding, result); + + assertThat(result.getAssessedCustomFees()).isNotEmpty(); + assertThat(result.getAssessedCustomFees()).contains(hbarAssessedFee); + assertThat(result.getAssessedCustomFees()).contains(htsAssessedFee); + // sender will pay from exchange credits + verify(fixedFeeAssessor, never()).assessFixedFees(any(), any(), any()); + + // We add to the set of royalties paid to track the royalties paid. It should have an entry with sender + assertThat(result.getRoyaltiesPaid()).contains(Pair.of(payer, feeMeta.tokenId())); + } + + @Test + void doesntCollectRoyaltyIfOriginalPayerIsExempt() { + final var accountAmounts = List.of(AccountAmount.newBuilder() + .accountID(payer) + .amount(originalUnits) + .build()); + result = new AssessmentResult( + List.of( + nftTransferList, + htsPayerTokenTransferList + .copyBuilder() + .transfers(accountAmounts) + .build()), + accountAmounts); + + final var royaltyCustomFee = withRoyaltyFee( + royaltyFee.copyBuilder().fallbackFee(htsFallbackFee).build(), payer); + final var royaltyFixedFee = withFixedFee(fixedFee, payer, false); + + final CustomFeeMeta feeMeta = + withCustomFeeMeta(List.of(royaltyFixedFee, royaltyCustomFee), NON_FUNGIBLE_UNIQUE); + + subject.assessRoyaltyFees(feeMeta, payer, funding, result); + + assertThat(result.getAssessedCustomFees()).isEmpty(); + // sender will pay from exchange credits + verify(fixedFeeAssessor, never()).assessFixedFees(any(), any(), any()); + } + + public CustomFeeMeta withCustomFeeMeta(List customFees, TokenType tokenType) { + return new CustomFeeMeta(firstFungibleTokenId, minter, customFees, tokenType); + } +} diff --git a/hedera-node/hedera-token-service-impl/src/test/java/com/hedera/node/app/service/token/impl/test/handlers/util/AdapterUtils.java b/hedera-node/hedera-token-service-impl/src/test/java/com/hedera/node/app/service/token/impl/test/handlers/util/AdapterUtils.java index 59ca837bf836..7600cff6df6f 100644 --- a/hedera-node/hedera-token-service-impl/src/test/java/com/hedera/node/app/service/token/impl/test/handlers/util/AdapterUtils.java +++ b/hedera-node/hedera-token-service-impl/src/test/java/com/hedera/node/app/service/token/impl/test/handlers/util/AdapterUtils.java @@ -34,19 +34,22 @@ import com.hedera.node.app.service.mono.state.migration.HederaAccount; import com.hedera.node.app.service.mono.utils.EntityNum; import com.hedera.node.app.service.token.ReadableAccountStore; -import com.hedera.node.app.spi.fixtures.state.MapReadableKVState; -import com.hedera.node.app.spi.fixtures.state.MapWritableKVState; -import com.hedera.node.app.spi.state.ReadableKVState; -import com.hedera.node.app.spi.state.ReadableStates; -import com.hedera.node.app.spi.state.WritableKVState; -import com.hedera.node.app.spi.state.WritableStates; import com.hedera.pbj.runtime.io.buffer.Bytes; import com.hedera.test.factories.scenarios.TxnHandlingScenario; import com.hedera.test.utils.StateKeyAdapter; import com.hedera.test.utils.TestFixturesKeyLookup; +import com.swirlds.platform.test.fixtures.state.MapReadableKVState; +import com.swirlds.platform.test.fixtures.state.MapWritableKVState; +import com.swirlds.state.spi.ReadableKVState; +import com.swirlds.state.spi.ReadableStates; +import com.swirlds.state.spi.WritableKVState; +import com.swirlds.state.spi.WritableStates; import java.util.Map; import org.mockito.Mockito; +/** + * Utility class for creating {@link ReadableAccountStore} objects. + */ public class AdapterUtils { private static final String ACCOUNTS_KEY = "ACCOUNTS"; private static final String ALIASES_KEY = "ALIASES"; @@ -68,18 +71,32 @@ ALIASES_KEY, wellKnownAliasState(), ACCOUNTS_KEY, wellKnownAccountsState()))); } + /** + * Returns a {@link ReadableStates} object that contains the given key-value pairs. + * @param keysToMock the key-value pairs + * @return the {@link ReadableStates} object + */ public static ReadableStates mockStates(final Map keysToMock) { final var mockStates = Mockito.mock(ReadableStates.class); keysToMock.forEach((key, state) -> given(mockStates.get(key)).willReturn(state)); return mockStates; } + /** + * Returns a {@link WritableStates} object that contains the given key-value pairs. + * @param keysToMock the key-value pairs + * @return the {@link WritableStates} object + */ public static WritableStates mockWritableStates(final Map keysToMock) { final var mockStates = Mockito.mock(WritableStates.class); keysToMock.forEach((key, state) -> given(mockStates.get(key)).willReturn(state)); return mockStates; } + /** + * Returns a {@link ReadableKVState} object that contains the well-known accounts used in a {@code SigRequirementsTest} + * @return + */ private static ReadableKVState wellKnownAccountsState() { final var wrappedState = new MapReadableKVState<>(ACCOUNTS_KEY, TxnHandlingScenario.wellKnownAccounts()); return new StateKeyAdapter<>( @@ -87,6 +104,10 @@ public static WritableStates mockWritableStates(final Map new EntityNum(id.accountNumOrThrow().intValue())); } + /** + * Returns a {@link WritableKVState} object that contains the well-known aliases used in a {@code SigRequirementsTest} + * @return the well-known aliases state + */ public static MapWritableKVState wellKnownAliasState() { final Map wellKnownAliases = Map.ofEntries( Map.entry(new ProtoBytes(Bytes.wrap(CURRENTLY_UNUSED_ALIAS)), asAccount(MISSING_NUM.longValue())), @@ -98,6 +119,11 @@ public static MapWritableKVState wellKnownAliasState() { return new MapWritableKVState<>(ALIASES_KEY, wellKnownAliases); } + /** + * Returns a {@link TransactionBody} object from a {@link TxnHandlingScenario} + * @param scenario the scenario + * @return the {@link TransactionBody} object + */ public static TransactionBody txnFrom(final TxnHandlingScenario scenario) { try { return toPbj(scenario.platformTxn().getTxn()); diff --git a/hedera-node/hedera-token-service-impl/src/test/java/com/hedera/node/app/service/token/impl/test/handlers/util/CryptoHandlerTestBase.java b/hedera-node/hedera-token-service-impl/src/test/java/com/hedera/node/app/service/token/impl/test/handlers/util/CryptoHandlerTestBase.java index ad9a4c4264e1..5553b783a3ea 100644 --- a/hedera-node/hedera-token-service-impl/src/test/java/com/hedera/node/app/service/token/impl/test/handlers/util/CryptoHandlerTestBase.java +++ b/hedera-node/hedera-token-service-impl/src/test/java/com/hedera/node/app/service/token/impl/test/handlers/util/CryptoHandlerTestBase.java @@ -19,6 +19,8 @@ import static com.hedera.node.app.service.mono.Utils.asHederaKey; import static com.hedera.node.app.service.mono.pbj.PbjConverter.asBytes; import static com.hedera.node.app.service.token.impl.handlers.BaseCryptoHandler.asAccount; +import static com.hedera.node.app.service.token.impl.test.handlers.util.StateBuilderUtil.ACCOUNTS; +import static com.hedera.node.app.service.token.impl.test.handlers.util.StateBuilderUtil.ALIASES; import static com.hedera.node.app.service.token.impl.test.util.SigReqAdapterUtils.UNSET_STAKED_ID; import static com.hedera.test.utils.KeyUtils.A_COMPLEX_KEY; import static com.hedera.test.utils.KeyUtils.B_COMPLEX_KEY; @@ -38,19 +40,18 @@ import com.hedera.hapi.node.token.CryptoAllowance; import com.hedera.hapi.node.token.TokenAllowance; import com.hedera.node.app.service.token.ReadableAccountStore; -import com.hedera.node.app.service.token.impl.CryptoSignatureWaiversImpl; import com.hedera.node.app.service.token.impl.ReadableAccountStoreImpl; import com.hedera.node.app.service.token.impl.WritableAccountStore; -import com.hedera.node.app.spi.fixtures.state.MapReadableKVState; -import com.hedera.node.app.spi.fixtures.state.MapWritableKVState; import com.hedera.node.app.spi.key.HederaKey; import com.hedera.node.app.spi.metrics.StoreMetricsService; -import com.hedera.node.app.spi.state.ReadableStates; -import com.hedera.node.app.spi.state.WritableStates; import com.hedera.node.app.spi.workflows.PreHandleContext; import com.hedera.node.config.testfixtures.HederaTestConfigBuilder; import com.hedera.pbj.runtime.io.buffer.Bytes; import com.swirlds.common.utility.CommonUtils; +import com.swirlds.platform.test.fixtures.state.MapReadableKVState; +import com.swirlds.platform.test.fixtures.state.MapWritableKVState; +import com.swirlds.state.spi.ReadableStates; +import com.swirlds.state.spi.WritableStates; import edu.umd.cs.findbugs.annotations.NonNull; import java.time.Instant; import java.util.Collections; @@ -60,10 +61,11 @@ import org.mockito.junit.jupiter.MockitoExtension; // FUTURE : Remove this and use CryptoTokenHandlerTestBase instead for all classes extending this class +/** + * Base class for testing Crypto handlers implementations. + */ @ExtendWith(MockitoExtension.class) public class CryptoHandlerTestBase { - public static final String ACCOUNTS = "ACCOUNTS"; - protected static final String ALIASES = "ALIASES"; protected final Key key = A_COMPLEX_KEY; protected final Key otherKey = C_COMPLEX_KEY; protected final AccountID id = AccountID.newBuilder().accountNum(3).build(); @@ -134,12 +136,12 @@ public class CryptoHandlerTestBase { @Mock(strictness = LENIENT) protected WritableStates writableStates; - @Mock - protected CryptoSignatureWaiversImpl waivers; - @Mock private StoreMetricsService storeMetricsService; + /** + * Set up the test environment. + */ @BeforeEach public void setUp() { account = givenValidAccount(accountNum); diff --git a/hedera-node/hedera-token-service-impl/src/test/java/com/hedera/node/app/service/token/impl/test/handlers/util/CryptoTokenHandlerTestBase.java b/hedera-node/hedera-token-service-impl/src/test/java/com/hedera/node/app/service/token/impl/test/handlers/util/CryptoTokenHandlerTestBase.java index c2b08bbc9a37..94584d200efd 100644 --- a/hedera-node/hedera-token-service-impl/src/test/java/com/hedera/node/app/service/token/impl/test/handlers/util/CryptoTokenHandlerTestBase.java +++ b/hedera-node/hedera-token-service-impl/src/test/java/com/hedera/node/app/service/token/impl/test/handlers/util/CryptoTokenHandlerTestBase.java @@ -78,15 +78,7 @@ import com.hedera.node.app.service.token.impl.WritableTokenStore; import com.hedera.node.app.service.token.records.FinalizeContext; import com.hedera.node.app.spi.fees.Fees; -import com.hedera.node.app.spi.fixtures.state.MapReadableKVState; -import com.hedera.node.app.spi.fixtures.state.MapWritableKVState; import com.hedera.node.app.spi.metrics.StoreMetricsService; -import com.hedera.node.app.spi.state.ReadableSingletonState; -import com.hedera.node.app.spi.state.ReadableSingletonStateBase; -import com.hedera.node.app.spi.state.ReadableStates; -import com.hedera.node.app.spi.state.WritableSingletonState; -import com.hedera.node.app.spi.state.WritableSingletonStateBase; -import com.hedera.node.app.spi.state.WritableStates; import com.hedera.node.app.spi.workflows.HandleContext; import com.hedera.node.app.spi.workflows.PreHandleContext; import com.hedera.node.config.VersionedConfigImpl; @@ -94,6 +86,14 @@ import com.hedera.pbj.runtime.io.buffer.Bytes; import com.swirlds.common.utility.CommonUtils; import com.swirlds.config.api.Configuration; +import com.swirlds.platform.state.spi.ReadableSingletonStateBase; +import com.swirlds.platform.state.spi.WritableSingletonStateBase; +import com.swirlds.platform.test.fixtures.state.MapReadableKVState; +import com.swirlds.platform.test.fixtures.state.MapWritableKVState; +import com.swirlds.state.spi.ReadableSingletonState; +import com.swirlds.state.spi.ReadableStates; +import com.swirlds.state.spi.WritableSingletonState; +import com.swirlds.state.spi.WritableStates; import edu.umd.cs.findbugs.annotations.NonNull; import java.time.Instant; import java.time.LocalDate; @@ -107,6 +107,9 @@ import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; +/** + * Base class for testing both Crypto and Token implementations. + */ @ExtendWith(MockitoExtension.class) public class CryptoTokenHandlerTestBase extends StateBuilderUtil { protected static final Instant originalInstant = Instant.ofEpochSecond(12345678910L); @@ -152,6 +155,8 @@ public class CryptoTokenHandlerTestBase extends StateBuilderUtil { protected final AccountID feeCollectorId = transferAccountId; protected final AccountID stakingRewardId = AccountID.newBuilder().accountNum(800).build(); + protected final AccountID zeroAccountId = + AccountID.newBuilder().accountNum(0).build(); /* ---------- Account Numbers ---------- */ protected final Long accountNum = payerId.accountNum(); @@ -282,8 +287,9 @@ public class CryptoTokenHandlerTestBase extends StateBuilderUtil { Fraction.newBuilder().numerator(1).denominator(2).build()) .fallbackFee(hbarFixedFee) .build(); - protected CustomFee customFractionalFee = withFractionalFee(fractionalFee); - protected List customFees = List.of(withFixedFee(hbarFixedFee), customFractionalFee); + protected CustomFee customFractionalFee = withFractionalFee(fractionalFee, feeCollectorId, false); + protected List customFees = + List.of(withFixedFee(hbarFixedFee, feeCollectorId, false), customFractionalFee); /* ---------- Misc ---------- */ protected final Timestamp consensusTimestamp = @@ -365,6 +371,7 @@ public class CryptoTokenHandlerTestBase extends StateBuilderUtil { protected Account stakingRewardAccount; protected Account tokenReceiverAccount; protected Account hbarReceiverAccount; + protected Account zeroAccount; /* ---------- Maps for updating both readable and writable stores ---------- */ private Map accountsMap; @@ -384,7 +391,9 @@ public class CryptoTokenHandlerTestBase extends StateBuilderUtil { protected Configuration configuration; protected VersionedConfigImpl versionedConfig; - + /** + * Sets up the test environment. + */ @BeforeEach public void setUp() { handlerTestBaseInternalSetUp(true); @@ -415,6 +424,7 @@ private void setUpAllEntities(final boolean prepopulateReceiverIds) { accountsMap.put(delegatingSpenderId, delegatingSpenderAccount); accountsMap.put(spenderId, spenderAccount); accountsMap.put(treasuryId, treasuryAccount); + accountsMap.put(zeroAccountId, zeroAccount); accountsMap.put(stakingRewardId, stakingRewardAccount); tokensMap = new HashMap<>(); @@ -724,18 +734,24 @@ private void givenValidTokens() { fungibleTokenB = givenValidFungibleToken() .copyBuilder() .tokenId(fungibleTokenIDB) - .customFees(withFixedFee(FixedFee.newBuilder() - .denominatingTokenId(fungibleTokenIDC) - .amount(1000) - .build())) + .customFees(withFixedFee( + FixedFee.newBuilder() + .denominatingTokenId(fungibleTokenIDC) + .amount(1000) + .build(), + feeCollectorId, + false)) .build(); fungibleTokenC = givenValidFungibleToken() .copyBuilder() .tokenId(fungibleTokenIDC) - .customFees(withFixedFee(FixedFee.newBuilder() - .denominatingTokenId(fungibleTokenId) - .amount(40) - .build())) + .customFees(withFixedFee( + FixedFee.newBuilder() + .denominatingTokenId(fungibleTokenId) + .amount(40) + .build(), + feeCollectorId, + false)) .build(); nonFungibleToken = givenValidNonFungibleToken(true); nftSl1 = givenNft(nftIdSl1).copyBuilder().ownerNextNftId(nftIdSl2).build(); @@ -791,6 +807,10 @@ private void givenValidAccounts() { .headNftId((NftID) null) .headNftSerialNumber(0L) .build(); + zeroAccount = givenValidAccountBuilder() + .accountId(zeroAccountId) + .key(EMPTY_KEYLIST) + .build(); } protected Token givenValidFungibleToken() { @@ -844,7 +864,7 @@ protected Token givenValidNonFungibleToken(boolean hasKyc) { .copyBuilder() .tokenId(nonFungibleTokenId) .treasuryAccountId(treasuryId) - .customFees(List.of(withRoyaltyFee(royaltyFee))) + .customFees(List.of(withRoyaltyFee(royaltyFee, feeCollectorId))) .tokenType(TokenType.NON_FUNGIBLE_UNIQUE) .kycKey(hasKyc ? kycKey : null) .build(); @@ -922,6 +942,31 @@ protected Nft givenNft(NftID tokenID) { .build(); } + public static CustomFee withFixedFee( + final FixedFee fixedFee, final AccountID feeCollectorId, final boolean allCollectorsExempt) { + return CustomFee.newBuilder() + .feeCollectorAccountId(feeCollectorId) + .allCollectorsAreExempt(allCollectorsExempt) + .fixedFee(fixedFee) + .build(); + } + + public static CustomFee withFractionalFee( + final FractionalFee fractionalFee, final AccountID feeCollectorId, final boolean allCollectorsExempt) { + return CustomFee.newBuilder() + .fractionalFee(fractionalFee) + .feeCollectorAccountId(feeCollectorId) + .allCollectorsAreExempt(allCollectorsExempt) + .build(); + } + + public static CustomFee withRoyaltyFee(final RoyaltyFee royaltyFee, final AccountID feeCollectorId) { + return CustomFee.newBuilder() + .royaltyFee(royaltyFee) + .feeCollectorAccountId(feeCollectorId) + .build(); + } + protected CustomFee withFixedFee(final FixedFee fixedFee) { return CustomFee.newBuilder() .feeCollectorAccountId(feeCollectorId) diff --git a/hedera-node/hedera-token-service-impl/src/test/java/com/hedera/node/app/service/token/impl/test/handlers/util/ParityTestBase.java b/hedera-node/hedera-token-service-impl/src/test/java/com/hedera/node/app/service/token/impl/test/handlers/util/ParityTestBase.java index c07b30d4ea57..01339b6125f1 100644 --- a/hedera-node/hedera-token-service-impl/src/test/java/com/hedera/node/app/service/token/impl/test/handlers/util/ParityTestBase.java +++ b/hedera-node/hedera-token-service-impl/src/test/java/com/hedera/node/app/service/token/impl/test/handlers/util/ParityTestBase.java @@ -30,6 +30,9 @@ import com.swirlds.config.api.Configuration; import org.junit.jupiter.api.BeforeEach; +/** + * Adds parity tests for Signature requirements from mono-service in mod-service. + */ public class ParityTestBase { protected ReadableAccountStore readableAccountStore; protected WritableAccountStore writableAccountStore; @@ -38,6 +41,9 @@ public class ParityTestBase { protected TokenID token = TokenID.newBuilder().tokenNum(1).build(); protected Configuration configuration; + /** + * Sets up the test environment. + */ @BeforeEach public void setUp() { readableAccountStore = SigReqAdapterUtils.wellKnownAccountStoreAt(); diff --git a/hedera-node/hedera-token-service-impl/src/test/java/com/hedera/node/app/service/token/impl/test/handlers/util/StateBuilderUtil.java b/hedera-node/hedera-token-service-impl/src/test/java/com/hedera/node/app/service/token/impl/test/handlers/util/StateBuilderUtil.java index 3767211100ad..0c7d01a44641 100644 --- a/hedera-node/hedera-token-service-impl/src/test/java/com/hedera/node/app/service/token/impl/test/handlers/util/StateBuilderUtil.java +++ b/hedera-node/hedera-token-service-impl/src/test/java/com/hedera/node/app/service/token/impl/test/handlers/util/StateBuilderUtil.java @@ -25,18 +25,42 @@ import com.hedera.hapi.node.state.token.Nft; import com.hedera.hapi.node.state.token.Token; import com.hedera.hapi.node.state.token.TokenRelation; -import com.hedera.node.app.spi.fixtures.state.MapReadableKVState; -import com.hedera.node.app.spi.fixtures.state.MapWritableKVState; +import com.swirlds.platform.test.fixtures.state.MapReadableKVState; +import com.swirlds.platform.test.fixtures.state.MapWritableKVState; import edu.umd.cs.findbugs.annotations.NonNull; +/** + * Utility class for building state for tests. + * + */ public class StateBuilderUtil { - + /** + * The state key for accounts. + */ public static final String ACCOUNTS = "ACCOUNTS"; + /** + * The state key for aliases. + */ public static final String ALIASES = "ALIASES"; + /** + * The state key for tokens. + */ public static final String TOKENS = "TOKENS"; + /** + * The state key for token relations. + */ public static final String TOKEN_RELS = "TOKEN_RELS"; + /** + * The state key for NFTs. + */ public static final String NFTS = "NFTS"; + /** + * The state key for staking infos. + */ public static final String STAKING_INFO = "STAKING_INFOS"; + /** + * The state key for network rewards. + */ public static final String NETWORK_REWARDS = "STAKING_NETWORK_REWARDS"; @NonNull diff --git a/hedera-node/hedera-token-service-impl/src/test/java/com/hedera/node/app/service/token/impl/test/handlers/util/TestStoreFactory.java b/hedera-node/hedera-token-service-impl/src/test/java/com/hedera/node/app/service/token/impl/test/handlers/util/TestStoreFactory.java index a779a1a5cdbd..99a10e59d746 100644 --- a/hedera-node/hedera-token-service-impl/src/test/java/com/hedera/node/app/service/token/impl/test/handlers/util/TestStoreFactory.java +++ b/hedera-node/hedera-token-service-impl/src/test/java/com/hedera/node/app/service/token/impl/test/handlers/util/TestStoreFactory.java @@ -42,15 +42,18 @@ import com.hedera.node.app.service.token.impl.WritableNftStore; import com.hedera.node.app.service.token.impl.WritableTokenRelationStore; import com.hedera.node.app.service.token.impl.WritableTokenStore; -import com.hedera.node.app.spi.fixtures.state.MapReadableStates; -import com.hedera.node.app.spi.fixtures.state.MapWritableKVState; import com.hedera.node.app.spi.fixtures.state.MapWritableStates; import com.hedera.node.app.spi.metrics.StoreMetricsService; import com.hedera.node.config.testfixtures.HederaTestConfigBuilder; import com.swirlds.config.api.Configuration; +import com.swirlds.platform.test.fixtures.state.MapReadableStates; +import com.swirlds.platform.test.fixtures.state.MapWritableKVState; import java.util.HashMap; import java.util.Map; +/** + * A factory for creating test stores. + */ public final class TestStoreFactory { private static final Configuration CONFIGURATION = HederaTestConfigBuilder.createConfig(); @@ -59,11 +62,21 @@ private TestStoreFactory() { throw new UnsupportedOperationException("Utility Class"); } + /** + * Creates a new {@link ReadableTokenStore} with the given {@link Token}s. + * @param tokens the tokens to add to the store + * @return the new store + */ public static ReadableTokenStore newReadableStoreWithTokens(Token... tokens) { final var wrappedState = newTokenStateFromTokens(tokens); return new ReadableTokenStoreImpl(new MapReadableStates(Map.of(TOKENS_KEY, wrappedState))); } + /** + * Creates a new {@link WritableTokenStore} with the given {@link Token}s. + * @param tokens the tokens to add to the store + * @return the new store + */ public static WritableTokenStore newWritableStoreWithTokens(Token... tokens) { final var wrappedState = newTokenStateFromTokens(tokens); return new WritableTokenStore( @@ -72,6 +85,11 @@ public static WritableTokenStore newWritableStoreWithTokens(Token... tokens) { mock(StoreMetricsService.class)); } + /** + * Creates a new {@link ReadableAccountStore} with the given {@link Account}s. + * @param accounts the accounts to add to the store + * @return the new store + */ public static ReadableAccountStore newReadableStoreWithAccounts(Account... accounts) { return new ReadableAccountStoreImpl(new MapReadableStates(writableAccountStates(accounts))); } @@ -90,11 +108,21 @@ private static MapWritableKVState newAccountStateFromAccount return new MapWritableKVState<>(ACCOUNTS_KEY, backingMap); } + /** + * Creates a new {@link WritableAccountStore} with the given {@link Account}s. + * @param accounts the accounts to add to the store + * @return the new store + */ public static WritableAccountStore newWritableStoreWithAccounts(Account... accounts) { return new WritableAccountStore( new MapWritableStates(writableAccountStates(accounts)), CONFIGURATION, mock(StoreMetricsService.class)); } + /** + * Creates a new {@link ReadableTokenRelationStore} with the given {@link TokenRelation}s. + * @param tokenRels the token relations to add to the store + * @return the new store + */ public static ReadableTokenRelationStore newReadableStoreWithTokenRels(final TokenRelation... tokenRels) { final var wrappedState = newTokenRelStateFromTokenRels(tokenRels); return new ReadableTokenRelationStoreImpl( @@ -116,6 +144,11 @@ private static MapWritableKVState newTokenRelStateF return new MapWritableKVState<>(ACCOUNTS_KEY, backingMap); } + /** + * Creates a new {@link WritableTokenRelationStore} with the given {@link TokenRelation}s. + * @param tokenRels the token relations to add to the store + * @return the new store + */ public static WritableTokenRelationStore newWritableStoreWithTokenRels(final TokenRelation... tokenRels) { final var wrappingState = newTokenRelStateFromTokenRels(tokenRels); return new WritableTokenRelationStore( @@ -124,11 +157,21 @@ public static WritableTokenRelationStore newWritableStoreWithTokenRels(final Tok mock(StoreMetricsService.class)); } + /** + * Creates a new {@link ReadableNftStore} with the given {@link Nft}s. + * @param nfts the nfts to add to the store + * @return the new store + */ public static ReadableNftStore newReadableStoreWithNfts(Nft... nfts) { final var wrappingState = newNftStateFromNfts(nfts); return new ReadableNftStoreImpl(new MapReadableStates(Map.of(TokenServiceImpl.NFTS_KEY, wrappingState))); } + /** + * Creates a new {@link WritableNftStore} with the given {@link Nft}s. + * @param nfts the nfts to add to the store + * @return the new store + */ public static WritableNftStore newWritableStoreWithNfts(Nft... nfts) { final var wrappingState = newNftStateFromNfts(nfts); return new WritableNftStore( diff --git a/hedera-node/hedera-token-service-impl/src/test/java/com/hedera/node/app/service/token/impl/test/handlers/util/TokenHandlerTestBase.java b/hedera-node/hedera-token-service-impl/src/test/java/com/hedera/node/app/service/token/impl/test/handlers/util/TokenHandlerTestBase.java index 9b948815df57..e2192d06380d 100644 --- a/hedera-node/hedera-token-service-impl/src/test/java/com/hedera/node/app/service/token/impl/test/handlers/util/TokenHandlerTestBase.java +++ b/hedera-node/hedera-token-service-impl/src/test/java/com/hedera/node/app/service/token/impl/test/handlers/util/TokenHandlerTestBase.java @@ -46,14 +46,14 @@ import com.hedera.node.app.service.token.impl.ReadableTokenStoreImpl; import com.hedera.node.app.service.token.impl.WritableTokenStore; import com.hedera.node.app.service.token.impl.handlers.BaseCryptoHandler; -import com.hedera.node.app.spi.fixtures.state.MapReadableKVState; -import com.hedera.node.app.spi.fixtures.state.MapWritableKVState; import com.hedera.node.app.spi.key.HederaKey; import com.hedera.node.app.spi.metrics.StoreMetricsService; -import com.hedera.node.app.spi.state.ReadableStates; -import com.hedera.node.app.spi.state.WritableStates; import com.hedera.node.config.testfixtures.HederaTestConfigBuilder; import com.hedera.pbj.runtime.io.buffer.Bytes; +import com.swirlds.platform.test.fixtures.state.MapReadableKVState; +import com.swirlds.platform.test.fixtures.state.MapWritableKVState; +import com.swirlds.state.spi.ReadableStates; +import com.swirlds.state.spi.WritableStates; import edu.umd.cs.findbugs.annotations.NonNull; import java.time.Instant; import java.util.Collections; @@ -64,6 +64,9 @@ import org.mockito.junit.jupiter.MockitoExtension; // FUTURE : Remove this and use CryptoTokenHandlerTestBase instead for all classes extending this class +/** + * Base class for token handler tests. + */ @ExtendWith(MockitoExtension.class) public class TokenHandlerTestBase { protected static final String TOKENS = "TOKENS"; @@ -139,6 +142,9 @@ public class TokenHandlerTestBase { protected ReadableTokenStore readableTokenStore; protected WritableTokenStore writableTokenStore; + /** + * Sets up the common test environment. + */ @BeforeEach public void commonSetUp() { givenValidToken(); diff --git a/hedera-node/hedera-token-service-impl/src/test/java/com/hedera/node/app/service/token/impl/test/handlers/util/TransferUtil.java b/hedera-node/hedera-token-service-impl/src/test/java/com/hedera/node/app/service/token/impl/test/handlers/util/TransferUtil.java new file mode 100644 index 000000000000..69424fbf8e7e --- /dev/null +++ b/hedera-node/hedera-token-service-impl/src/test/java/com/hedera/node/app/service/token/impl/test/handlers/util/TransferUtil.java @@ -0,0 +1,79 @@ +/* + * Copyright (C) 2024 Hedera Hashgraph, LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.hedera.node.app.service.token.impl.test.handlers.util; + +import com.hedera.hapi.node.base.AccountAmount; +import com.hedera.hapi.node.base.AccountID; +import com.hedera.hapi.node.base.NftTransfer; +import com.hedera.hapi.node.base.TokenID; +import com.hedera.hapi.node.base.TokenTransferList; +import com.hedera.hapi.node.base.TransferList; +import java.util.List; + +public class TransferUtil { + public static TransferList asTransferList(final long payerCredit, final AccountID payer) { + return TransferList.newBuilder() + .accountAmounts(AccountAmount.newBuilder() + .accountID(payer) + .amount(payerCredit) + .build()) + .build(); + } + + public static TokenTransferList asTokenTransferList( + final TokenID tokenId, final long payerCredit, final AccountID payer) { + return TokenTransferList.newBuilder() + .token(tokenId) + .transfers(AccountAmount.newBuilder() + .accountID(payer) + .amount(payerCredit) + .build()) + .build(); + } + + public static TokenTransferList asTokenTransferList(final TokenID tokenId, List accountAmounts) { + return TokenTransferList.newBuilder() + .token(tokenId) + .transfers(accountAmounts) + .build(); + } + + public static AccountAmount asAccountAmount(final AccountID account, final long amount) { + return AccountAmount.newBuilder().accountID(account).amount(amount).build(); + } + + public static TokenTransferList asNftTransferList( + final TokenID nonFungibleTokenId, + final AccountID sender, + final AccountID receiver, + final long serialNumber) { + final var nftTransfer = asNftTransfer(sender, receiver, serialNumber); + return TokenTransferList.newBuilder() + .token(nonFungibleTokenId) + .nftTransfers(nftTransfer) + .build(); + } + + private static NftTransfer asNftTransfer( + final AccountID sender, final AccountID receiver, final long serialNumber) { + return NftTransfer.newBuilder() + .senderAccountID(sender) + .receiverAccountID(receiver) + .serialNumber(serialNumber) + .build(); + } +} diff --git a/hedera-node/hedera-token-service-impl/src/test/java/com/hedera/node/app/service/token/impl/test/schemas/InitialModServiceTokenSchemaTest.java b/hedera-node/hedera-token-service-impl/src/test/java/com/hedera/node/app/service/token/impl/test/schemas/InitialModServiceTokenSchemaTest.java index a3b0d6b1ab2d..cd3fdae5af93 100644 --- a/hedera-node/hedera-token-service-impl/src/test/java/com/hedera/node/app/service/token/impl/test/schemas/InitialModServiceTokenSchemaTest.java +++ b/hedera-node/hedera-token-service-impl/src/test/java/com/hedera/node/app/service/token/impl/test/schemas/InitialModServiceTokenSchemaTest.java @@ -16,6 +16,8 @@ package com.hedera.node.app.service.token.impl.test.schemas; +import static com.hedera.node.app.service.mono.state.migration.ContractStateMigrator.bytesFromInts; +import static com.hedera.node.app.service.mono.state.virtual.KeyPackingUtils.asPackedInts; import static com.hedera.node.app.service.token.impl.TokenServiceImpl.ACCOUNTS_KEY; import static com.hedera.node.app.service.token.impl.TokenServiceImpl.ALIASES_KEY; import static com.hedera.node.app.service.token.impl.TokenServiceImpl.STAKING_INFO_KEY; @@ -44,7 +46,9 @@ import static com.hedera.node.app.service.token.impl.test.schemas.SyntheticAccountsData.buildConfig; import static com.hedera.node.app.service.token.impl.test.schemas.SyntheticAccountsData.configBuilder; import static com.hedera.node.app.spi.fixtures.state.TestSchema.CURRENT_VERSION; +import static com.swirlds.common.utility.CommonUtils.unhex; import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertEquals; import static org.mockito.Mockito.verify; import com.hedera.hapi.node.base.AccountID; @@ -53,21 +57,24 @@ import com.hedera.hapi.node.state.token.StakingNodeInfo; import com.hedera.node.app.ids.EntityIdService; import com.hedera.node.app.ids.WritableEntityIdStore; +import com.hedera.node.app.service.mono.state.migration.AccountStateTranslator; +import com.hedera.node.app.service.mono.state.virtual.entities.OnDiskAccount; import com.hedera.node.app.service.token.impl.TokenServiceImpl; import com.hedera.node.app.service.token.impl.schemas.InitialModServiceTokenSchema; import com.hedera.node.app.spi.fixtures.info.FakeNetworkInfo; -import com.hedera.node.app.spi.fixtures.state.MapWritableKVState; import com.hedera.node.app.spi.fixtures.state.MapWritableStates; import com.hedera.node.app.spi.info.NetworkInfo; import com.hedera.node.app.spi.info.NodeInfo; import com.hedera.node.app.spi.state.EmptyReadableStates; -import com.hedera.node.app.spi.state.WritableSingletonState; -import com.hedera.node.app.spi.state.WritableSingletonStateBase; -import com.hedera.node.app.spi.state.WritableStates; import com.hedera.node.app.spi.workflows.record.GenesisRecordsBuilder; import com.hedera.node.app.workflows.handle.record.MigrationContextImpl; import com.hedera.pbj.runtime.io.buffer.Bytes; import com.swirlds.config.api.Configuration; +import com.swirlds.platform.state.spi.WritableSingletonStateBase; +import com.swirlds.platform.test.fixtures.state.MapWritableKVState; +import com.swirlds.state.spi.WritableSingletonState; +import com.swirlds.state.spi.WritableStates; +import edu.umd.cs.findbugs.annotations.NonNull; import java.time.Instant; import java.util.Collections; import java.util.HashMap; @@ -80,6 +87,8 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.CsvSource; import org.mockito.ArgumentCaptor; import org.mockito.Captor; import org.mockito.Mock; @@ -133,6 +142,26 @@ void setUp() { config = buildConfig(DEFAULT_NUM_SYSTEM_ACCOUNTS, true); } + @CsvSource({ + "abababababababababababababababababababababababababababababababab", + "abcdef", + "0123456789", + }) + @ParameterizedTest + void keysAreMigratedIdentically(@NonNull final String hexedKey) { + var unhexedKey = unhex(hexedKey); + if (unhexedKey.length != 32) { + final var leftPadded = new byte[32]; + System.arraycopy(unhexedKey, 0, leftPadded, 32 - unhexedKey.length, unhexedKey.length); + unhexedKey = leftPadded; + } + final var onDiskAccount = new OnDiskAccount(); + onDiskAccount.setFirstStorageKey(asPackedInts(unhexedKey)); + final var account = AccountStateTranslator.accountFromOnDiskAccount(onDiskAccount); + final var expectedKey = bytesFromInts(onDiskAccount.getFirstStorageKey()); + assertEquals(expectedKey, account.firstContractStorageKey()); + } + @Test void nonGenesisDoesntCreate() { // To simulate a non-genesis case, we'll add a single account object to the `previousStates` param diff --git a/hedera-node/hedera-token-service-impl/src/test/java/com/hedera/node/app/service/token/impl/test/util/MetaAssertion.java b/hedera-node/hedera-token-service-impl/src/test/java/com/hedera/node/app/service/token/impl/test/util/MetaAssertion.java index 4cd2ba6958ab..5c21e8c5bb7e 100644 --- a/hedera-node/hedera-token-service-impl/src/test/java/com/hedera/node/app/service/token/impl/test/util/MetaAssertion.java +++ b/hedera-node/hedera-token-service-impl/src/test/java/com/hedera/node/app/service/token/impl/test/util/MetaAssertion.java @@ -20,8 +20,15 @@ import com.hedera.node.app.spi.workflows.PreHandleContext; +/** + * Basic assertions for the pre-handle context. + */ public class MetaAssertion { - + /** + * Basic pre-handle context assertions. + * @param context the context + * @param keysSize the size of the keys + */ public static void basicContextAssertions(final PreHandleContext context, final int keysSize) { assertEquals(keysSize, context.requiredNonPayerKeys().size()); } diff --git a/hedera-node/hedera-token-service-impl/src/test/java/com/hedera/node/app/service/token/impl/test/util/SigReqAdapterUtils.java b/hedera-node/hedera-token-service-impl/src/test/java/com/hedera/node/app/service/token/impl/test/util/SigReqAdapterUtils.java index d92f5b9c676e..e5401b1d2357 100644 --- a/hedera-node/hedera-token-service-impl/src/test/java/com/hedera/node/app/service/token/impl/test/util/SigReqAdapterUtils.java +++ b/hedera-node/hedera-token-service-impl/src/test/java/com/hedera/node/app/service/token/impl/test/util/SigReqAdapterUtils.java @@ -90,21 +90,22 @@ import com.hedera.hapi.node.transaction.TransactionBody; import com.hedera.node.app.service.mono.state.merkle.MerkleToken; import com.hedera.node.app.service.mono.utils.accessors.PlatformTxnAccessor; +import com.hedera.node.app.service.token.ReadableAccountStore; import com.hedera.node.app.service.token.ReadableTokenStore; import com.hedera.node.app.service.token.impl.ReadableAccountStoreImpl; import com.hedera.node.app.service.token.impl.ReadableTokenStoreImpl; import com.hedera.node.app.service.token.impl.WritableAccountStore; import com.hedera.node.app.service.token.impl.WritableTokenRelationStore; import com.hedera.node.app.service.token.impl.WritableTokenStore; -import com.hedera.node.app.spi.fixtures.state.MapWritableKVState; import com.hedera.node.app.spi.metrics.StoreMetricsService; -import com.hedera.node.app.spi.state.WritableKVState; import com.hedera.node.config.testfixtures.HederaTestConfigBuilder; import com.hedera.pbj.runtime.OneOf; import com.hedera.pbj.runtime.io.buffer.Bytes; import com.hedera.test.factories.scenarios.TxnHandlingScenario; import com.hedera.test.utils.StateKeyAdapter; import com.swirlds.config.api.Configuration; +import com.swirlds.platform.test.fixtures.state.MapWritableKVState; +import com.swirlds.state.spi.WritableKVState; import java.util.ArrayList; import java.util.HashMap; import java.util.List; @@ -114,6 +115,9 @@ import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.junit.jupiter.MockitoExtension; +/** + * Provides utility methods for testing signature requirements in the token service. + */ @ExtendWith(MockitoExtension.class) public class SigReqAdapterUtils { private static final String TOKENS_KEY = "TOKENS"; @@ -121,7 +125,9 @@ public class SigReqAdapterUtils { private static final Configuration CONFIGURATION = HederaTestConfigBuilder.createConfig(); protected final Bytes metadata = Bytes.wrap(new byte[] {1, 2, 3, 4}); - + /** + * Represents an unset staked ID. + */ public static final OneOf UNSET_STAKED_ID = new OneOf<>(Account.StakedIdOneOfType.UNSET, null); @@ -195,6 +201,11 @@ private static WritableKVState wellKnownTokenState() { return new MapWritableKVState<>("TOKENS", destination); } + /** + * Returns the {@link WritableTokenRelationStore} containing the "well-known" token relations that exist in a + * {@code SigRequirementsTest} scenario. + * @return the well-known token relation store + */ public static WritableTokenRelationStore wellKnownTokenRelStoreAt() { final var destination = new HashMap(); destination.put( @@ -235,11 +246,21 @@ public static WritableTokenRelationStore wellKnownTokenRelStoreAt() { mock(StoreMetricsService.class)); } + /** + * Returns the {@link ReadableAccountStore} containing the "well-known" accounts that exist in a + * {@code SigRequirementsTest} scenario. + * @return the well-known account store + */ public static ReadableAccountStoreImpl wellKnownAccountStoreAt() { return new ReadableAccountStoreImpl( mockStates(Map.of(ACCOUNTS_KEY, wrappedAccountState(), ALIASES_KEY, wellKnownAliasState()))); } + /** + * Returns the {@link WritableAccountStore} containing the "well-known" accounts that exist in a + * {@code SigRequirementsTest} scenario. + * @return the well-known account store + */ public static WritableAccountStore wellKnownWritableAccountStoreAt() { return new WritableAccountStore( mockWritableStates(Map.of(ACCOUNTS_KEY, wrappedAccountState(), ALIASES_KEY, wellKnownAliasState())), @@ -385,6 +406,11 @@ public PlatformTxnAccessor platformTxn() { return dummyScenario.tokenStore(); } + /** + * Returns a {@link TransactionBody} from a {@link TxnHandlingScenario}. + * @param scenario the scenario + * @return the transaction body + */ public static TransactionBody txnFrom(final TxnHandlingScenario scenario) { try { return toPbj(scenario.platformTxn().getTxn()); diff --git a/hedera-node/hedera-token-service-impl/src/test/java/com/hedera/node/app/service/token/impl/test/util/TokenRelListCalculatorTest.java b/hedera-node/hedera-token-service-impl/src/test/java/com/hedera/node/app/service/token/impl/test/util/TokenRelListCalculatorTest.java index 3cb888ebd0df..ec3e1094aaeb 100644 --- a/hedera-node/hedera-token-service-impl/src/test/java/com/hedera/node/app/service/token/impl/test/util/TokenRelListCalculatorTest.java +++ b/hedera-node/hedera-token-service-impl/src/test/java/com/hedera/node/app/service/token/impl/test/util/TokenRelListCalculatorTest.java @@ -30,7 +30,7 @@ import com.hedera.node.app.service.token.ReadableTokenRelationStore; import com.hedera.node.app.service.token.impl.ReadableTokenRelationStoreImpl; import com.hedera.node.app.service.token.impl.util.TokenRelListCalculator; -import com.hedera.node.app.spi.fixtures.state.MapReadableKVState; +import com.swirlds.platform.test.fixtures.state.MapReadableKVState; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; diff --git a/hedera-node/hedera-token-service/build.gradle.kts b/hedera-node/hedera-token-service/build.gradle.kts index d26b1c916e25..5e25f1d8970c 100644 --- a/hedera-node/hedera-token-service/build.gradle.kts +++ b/hedera-node/hedera-token-service/build.gradle.kts @@ -15,8 +15,9 @@ */ plugins { - id("com.hedera.hashgraph.conventions") - id("com.hedera.hashgraph.java-test-fixtures") + id("com.hedera.gradle.services") + id("com.hedera.gradle.services-publish") + id("com.hedera.gradle.java-test-fixtures") } description = "Hedera Token Service API" diff --git a/hedera-node/hedera-token-service/src/main/java/com/hedera/node/app/service/token/AliasUtils.java b/hedera-node/hedera-token-service/src/main/java/com/hedera/node/app/service/token/AliasUtils.java index 8ef527856d74..fee9196129d5 100644 --- a/hedera-node/hedera-token-service/src/main/java/com/hedera/node/app/service/token/AliasUtils.java +++ b/hedera-node/hedera-token-service/src/main/java/com/hedera/node/app/service/token/AliasUtils.java @@ -80,11 +80,19 @@ public static boolean isOfEvmAddressSize(@NonNull final Bytes alias) { @Nullable public static Bytes extractEvmAddress(@NonNull final Bytes alias) { requireNonNull(alias); - if (isOfEvmAddressSize(alias)) return alias; + if (isOfEvmAddressSize(alias)) { + return alias; + } final var key = asKeyFromAliasOrElse(alias, null); return (key != null && key.hasEcdsaSecp256k1()) ? recoverAddressFromPubKey(key.ecdsaSecp256k1OrThrow()) : null; } + /** + * Given a key, attempts to extract from it an EVM address. If the key is an ECDSA_SECP256K1 key, return the EVM + * address derived from the public key. Otherwise, return null. + * @param key The key to extract an EVM address from. + * @return The EVM address, or null if the key is not an ECDSA_SECP256K1 key. + */ @Nullable public static Bytes extractEvmAddress(@Nullable final Key key) { return key != null && key.hasEcdsaSecp256k1() ? recoverAddressFromPubKey(key.ecdsaSecp256k1OrThrow()) : null; @@ -107,6 +115,13 @@ public static boolean isEntityNumAlias(final Bytes alias) { return isOfEvmAddressSize(alias) && alias.matchesPrefix(ENTITY_NUM_ALIAS_PREFIX); } + /** + * Given some alias, determine whether it is a key alias. If the alias is a valid protobuf-encoded key, then it is a + * key alias. This method does not check whether the key is valid, only whether the alias is a valid protobuf-encoded + * key. + * @param alias The alias to check + * @return True if the alias is a key alias + */ public static boolean isKeyAlias(@NonNull final Bytes alias) { final var key = asKeyFromAliasOrElse(alias, null); if (key == null) return false; @@ -120,6 +135,12 @@ public static boolean isKeyAlias(@NonNull final Bytes alias) { return false; } + /** + * Given a public key, recover the address from it. This method is used to extract an EVM address from an ECDSA + * public key. + * @param alias The public key to recover the address from + * @return The address recovered from the public key + */ @NonNull public static Bytes recoverAddressFromPubKey(@NonNull final Bytes alias) { return EthSigsUtils.recoverAddressFromPubKey(alias); @@ -165,16 +186,16 @@ public static Long extractIdFromAddressAlias(final Bytes addressAlias) { return addressAlias.getLong(12); } + /** + * A utility method that checks if account is in aliased form + * @param idOrAlias account id or alias + * @return true if account is in aliased form + */ public static boolean isAlias(@NonNull final AccountID idOrAlias) { requireNonNull(idOrAlias); return !idOrAlias.hasAccountNum() && idOrAlias.hasAlias(); } - public static boolean isNotAlias(@NonNull final AccountID idOrAlias) { - requireNonNull(idOrAlias); - return idOrAlias.hasAccountNum() && !idOrAlias.hasAlias(); - } - /** * Parse a {@code Key} from given alias {@code Bytes}. If there is a parse error, throws a * {@code HandleException} with {@code INVALID_ALIAS_KEY} response code. @@ -194,6 +215,7 @@ public static Key asKeyFromAlias(@NonNull final Bytes alias) { * {@code HandleException} with {@code INVALID_ALIAS_KEY} response code. * @param alias given alias bytes * @return the parsed key + * @throws PreCheckException if the alias is not a valid key */ @NonNull public static Key asKeyFromAliasPreCheck(@NonNull final Bytes alias) throws PreCheckException { @@ -203,6 +225,13 @@ public static Key asKeyFromAliasPreCheck(@NonNull final Bytes alias) throws PreC return key; } + /** + * Parse a {@code Key} from given alias {@code Bytes}. If there is a parse error, returns the given default key. + * @param alias given alias bytes. If the alias is an evmAddress we don't need to parse with Key.PROTOBUF. + * This will cause BufferUnderflowException. So, we return the default key. + * @param def default key + * @return the parsed key or the default key if there is a parse error + */ @Nullable public static Key asKeyFromAliasOrElse(@NonNull final Bytes alias, @Nullable final Key def) { requireNonNull(alias); @@ -219,6 +248,11 @@ public static Key asKeyFromAliasOrElse(@NonNull final Bytes alias, @Nullable fin } } + /** + * Check if the given alias is greater than the size of an EVM address. + * @param alias The alias to check + * @return True if the alias is greater than the size of an EVM address + */ public static boolean isAliasSizeGreaterThanEvmAddress(@NonNull final Bytes alias) { requireNonNull(alias); return alias.length() > EVM_ADDRESS_SIZE; diff --git a/hedera-node/hedera-token-service/src/main/java/com/hedera/node/app/service/token/CryptoServiceDefinition.java b/hedera-node/hedera-token-service/src/main/java/com/hedera/node/app/service/token/CryptoServiceDefinition.java index 566f8b1782f1..222323e1aa86 100644 --- a/hedera-node/hedera-token-service/src/main/java/com/hedera/node/app/service/token/CryptoServiceDefinition.java +++ b/hedera-node/hedera-token-service/src/main/java/com/hedera/node/app/service/token/CryptoServiceDefinition.java @@ -30,6 +30,9 @@ */ @SuppressWarnings("java:S6548") public final class CryptoServiceDefinition implements RpcServiceDefinition { + /** + * The singleton instance of this class + */ public static final CryptoServiceDefinition INSTANCE = new CryptoServiceDefinition(); private static final Set> methods = Set.of( diff --git a/hedera-node/hedera-token-service/src/main/java/com/hedera/node/app/service/token/CryptoSignatureWaivers.java b/hedera-node/hedera-token-service/src/main/java/com/hedera/node/app/service/token/CryptoSignatureWaivers.java index 9acefbdda704..82e2fbe6f086 100644 --- a/hedera-node/hedera-token-service/src/main/java/com/hedera/node/app/service/token/CryptoSignatureWaivers.java +++ b/hedera-node/hedera-token-service/src/main/java/com/hedera/node/app/service/token/CryptoSignatureWaivers.java @@ -28,6 +28,7 @@ public interface CryptoSignatureWaivers extends SigWaivers { * target account being updated by the above accounts. * * @param cryptoUpdateTxn a crypto update transaction + * @param payer the account paying for the transaction * @return whether the target account's key must sign */ boolean isTargetAccountSignatureWaived(TransactionBody cryptoUpdateTxn, AccountID payer); @@ -40,6 +41,7 @@ public interface CryptoSignatureWaivers extends SigWaivers { * treasury account. * * @param cryptoUpdateTxn a crypto update transaction + * @param payer the account paying for the transaction * @return whether the new key from the transaction must sign */ boolean isNewKeySignatureWaived(TransactionBody cryptoUpdateTxn, AccountID payer); diff --git a/hedera-node/hedera-token-service/src/main/java/com/hedera/node/app/service/token/ReadableAccountStore.java b/hedera-node/hedera-token-service/src/main/java/com/hedera/node/app/service/token/ReadableAccountStore.java index b938e22aa09b..3d11d353c6c5 100644 --- a/hedera-node/hedera-token-service/src/main/java/com/hedera/node/app/service/token/ReadableAccountStore.java +++ b/hedera-node/hedera-token-service/src/main/java/com/hedera/node/app/service/token/ReadableAccountStore.java @@ -73,6 +73,7 @@ public interface ReadableAccountStore { /** * Returns true if the given account ID exists in state. * @param accountID the ID to check + * @return true if the account exists in state */ boolean contains(@NonNull final AccountID accountID); @@ -109,6 +110,10 @@ default Account getContractById(@NonNull final ContractID contractID) { return account == null || !account.smartContract() ? null : account; } + /** + * Returns the number of entities in the account state. + * @return the size of the account state + */ long sizeOfAccountState(); /** diff --git a/hedera-node/hedera-token-service/src/main/java/com/hedera/node/app/service/token/ReadableTokenRelationStore.java b/hedera-node/hedera-token-service/src/main/java/com/hedera/node/app/service/token/ReadableTokenRelationStore.java index 74a1f464f8a5..35fedd1035b6 100644 --- a/hedera-node/hedera-token-service/src/main/java/com/hedera/node/app/service/token/ReadableTokenRelationStore.java +++ b/hedera-node/hedera-token-service/src/main/java/com/hedera/node/app/service/token/ReadableTokenRelationStore.java @@ -34,6 +34,7 @@ public interface ReadableTokenRelationStore { * * @param accountId - the id of the account in the token-relation to be retrieved * @param tokenId - the id of the token in the token-relation to be retrieved + * @return the token-relation with the given IDs, or {@code null} if no such token-relation exists */ @Nullable TokenRelation get(@NonNull final AccountID accountId, @NonNull final TokenID tokenId); @@ -50,6 +51,7 @@ public interface ReadableTokenRelationStore { *

    The default implementation is empty because preloading data into memory is only used for some implementations. * * @param accountID the account id + * @param tokenId the token id */ default void warm(@NonNull final AccountID accountID, @NonNull final TokenID tokenId) {} } diff --git a/hedera-node/hedera-token-service/src/main/java/com/hedera/node/app/service/token/ReadableTokenStore.java b/hedera-node/hedera-token-service/src/main/java/com/hedera/node/app/service/token/ReadableTokenStore.java index 277e26d24097..d5dbbb53a17e 100644 --- a/hedera-node/hedera-token-service/src/main/java/com/hedera/node/app/service/token/ReadableTokenStore.java +++ b/hedera-node/hedera-token-service/src/main/java/com/hedera/node/app/service/token/ReadableTokenStore.java @@ -39,6 +39,19 @@ public interface ReadableTokenStore { @Nullable TokenMetadata getTokenMeta(@NonNull TokenID id); + /** Represents the metadata for a token. This should be deprecated and use Token instead in the FUTURE. + * @param adminKey admin key of the token + * @param kycKey kyc key of the token + * @param wipeKey wipe key of the token + * @param freezeKey freeze key of the token + * @param supplyKey supply key of the token + * @param feeScheduleKey fee schedule key of the token + * @param pauseKey pause key of the token + * @param symbol symbol of the token + * @param hasRoyaltyWithFallback whether the token has royalty with fallback + * @param treasuryAccountId treasury account id of the token + * @param decimals decimals of the token + */ record TokenMetadata( @Nullable Key adminKey, @Nullable Key kycKey, @@ -51,30 +64,52 @@ record TokenMetadata( boolean hasRoyaltyWithFallback, AccountID treasuryAccountId, int decimals) { + /** + * Returns whether the token has an admin key. + * @return whether the token has an admin key. + */ public boolean hasAdminKey() { return adminKey != null && !adminKey.key().kind().equals(KeyOneOfType.UNSET); } - + /** + * Returns whether the token has a kyc key. + * @return whether the token has a kyc key. + */ public boolean hasKycKey() { return kycKey != null && !kycKey.key().kind().equals(KeyOneOfType.UNSET); } - + /** + * Returns whether the token has a wipe key. + * @return whether the token has a wipe key. + */ public boolean hasWipeKey() { return wipeKey != null && !wipeKey.key().kind().equals(KeyOneOfType.UNSET); } - + /** + * Returns whether the token has a freeze key. + * @return whether the token has a freeze key. + */ public boolean hasFreezeKey() { return freezeKey != null && !freezeKey.key().kind().equals(KeyOneOfType.UNSET); } - + /** + * Returns whether the token has a supply key. + * @return whether the token has a supply key. + */ public boolean hasSupplyKey() { return supplyKey != null && !supplyKey.key().kind().equals(KeyOneOfType.UNSET); } - + /** + * Returns whether the token has a fee schedule key. + * @return whether the token has a fee schedule key. + */ public boolean hasFeeScheduleKey() { return feeScheduleKey != null && !feeScheduleKey.key().kind().equals(KeyOneOfType.UNSET); } - + /** + * Returns whether the token has a pause key. + * @return whether the token has a pause key. + */ public boolean hasPauseKey() { return pauseKey != null && !pauseKey.key().kind().equals(KeyOneOfType.UNSET); } @@ -84,6 +119,7 @@ public boolean hasPauseKey() { * Returns all the data for a token * * @param id the token id to look up + * @return the token */ @Nullable Token get(@NonNull TokenID id); diff --git a/hedera-node/hedera-token-service/src/main/java/com/hedera/node/app/service/token/TokenService.java b/hedera-node/hedera-token-service/src/main/java/com/hedera/node/app/service/token/TokenService.java index 97fb1c000977..3202b99d8fc4 100644 --- a/hedera-node/hedera-token-service/src/main/java/com/hedera/node/app/service/token/TokenService.java +++ b/hedera-node/hedera-token-service/src/main/java/com/hedera/node/app/service/token/TokenService.java @@ -29,7 +29,9 @@ * Service. */ public interface TokenService extends Service { - + /** + * The name of the service + */ String NAME = "TokenService"; @NonNull diff --git a/hedera-node/hedera-token-service/src/main/java/com/hedera/node/app/service/token/TokenServiceDefinition.java b/hedera-node/hedera-token-service/src/main/java/com/hedera/node/app/service/token/TokenServiceDefinition.java index d34b3a2eab8d..3721ff3d7e06 100644 --- a/hedera-node/hedera-token-service/src/main/java/com/hedera/node/app/service/token/TokenServiceDefinition.java +++ b/hedera-node/hedera-token-service/src/main/java/com/hedera/node/app/service/token/TokenServiceDefinition.java @@ -30,6 +30,9 @@ */ @SuppressWarnings("java:S6548") public final class TokenServiceDefinition implements RpcServiceDefinition { + /** + * Singleton instance of the Token Service + */ public static final TokenServiceDefinition INSTANCE = new TokenServiceDefinition(); private static final Set> methods = Set.of( diff --git a/hedera-node/hedera-token-service/src/main/java/com/hedera/node/app/service/token/Units.java b/hedera-node/hedera-token-service/src/main/java/com/hedera/node/app/service/token/Units.java index 7d74c1c984c1..f8d5a909c005 100644 --- a/hedera-node/hedera-token-service/src/main/java/com/hedera/node/app/service/token/Units.java +++ b/hedera-node/hedera-token-service/src/main/java/com/hedera/node/app/service/token/Units.java @@ -16,6 +16,9 @@ package com.hedera.node.app.service.token; +/** + * Utility class for units + */ public final class Units { private Units() { throw new UnsupportedOperationException("Utility Class"); diff --git a/hedera-node/hedera-token-service/src/main/java/com/hedera/node/app/service/token/api/AccountSummariesApi.java b/hedera-node/hedera-token-service/src/main/java/com/hedera/node/app/service/token/api/AccountSummariesApi.java index 5d114b0c2622..53e6cdc95fc6 100644 --- a/hedera-node/hedera-token-service/src/main/java/com/hedera/node/app/service/token/api/AccountSummariesApi.java +++ b/hedera-node/hedera-token-service/src/main/java/com/hedera/node/app/service/token/api/AccountSummariesApi.java @@ -56,8 +56,18 @@ * Methods for summarizing an account's relationships and attributes to HAPI clients. */ public interface AccountSummariesApi { + /** + * The size of an EVM address. + */ int EVM_ADDRESS_SIZE = 20; + /** + * The sentinel node id to represent stakingNodeId is absent on account. + * It is -1 because nodeId 0 is allowed for network. + */ long SENTINEL_NODE_ID = -1L; + /** + * The sentinel account id to represent stakedAccountId is absent on account. + */ AccountID SENTINEL_ACCOUNT_ID = AccountID.newBuilder().accountNum(0).build(); /** @@ -205,6 +215,16 @@ static StakingInfo summarizeStakingInfo( return stakingInfo.build(); } + /** + * Adds the node stake meta to the given staking info builder, given the account and some information about the + * network. + * @param numStoredPeriods the number of periods for which the rewards are stored + * @param stakePeriodMins the duration of a stake period + * @param areRewardsActive whether the rewards are active + * @param account the account + * @param readableStakingInfoStore the readable staking info store + * @param stakingInfo the staking info builder + */ static void addNodeStakeMeta( final int numStoredPeriods, final long stakePeriodMins, diff --git a/hedera-node/hedera-token-service/src/main/java/com/hedera/node/app/service/token/api/ContractChangeSummary.java b/hedera-node/hedera-token-service/src/main/java/com/hedera/node/app/service/token/api/ContractChangeSummary.java index 701d9b42e022..43e50a203dd7 100644 --- a/hedera-node/hedera-token-service/src/main/java/com/hedera/node/app/service/token/api/ContractChangeSummary.java +++ b/hedera-node/hedera-token-service/src/main/java/com/hedera/node/app/service/token/api/ContractChangeSummary.java @@ -34,6 +34,11 @@ public record ContractChangeSummary(List newContractIds, List NONCE_INFO_CONTRACT_ID_COMPARATOR = Comparator.comparing(ContractNonceInfo::contractIdOrThrow, CONTRACT_ID_NUM_COMPARATOR); + /** + * Constructs a new instance of the contract changes summary. + * @param newContractIds the list of new contract IDs, ordered by contract number + * @param updatedContractNonces the list of updated contract nonces, ordered by contract number + */ public ContractChangeSummary { Objects.requireNonNull(newContractIds).sort(CONTRACT_ID_NUM_COMPARATOR); Objects.requireNonNull(updatedContractNonces).sort(NONCE_INFO_CONTRACT_ID_COMPARATOR); diff --git a/hedera-node/hedera-token-service/src/main/java/com/hedera/node/app/service/token/api/StakingRewardsApi.java b/hedera-node/hedera-token-service/src/main/java/com/hedera/node/app/service/token/api/StakingRewardsApi.java index ed5582ef0f32..d1a37b807022 100644 --- a/hedera-node/hedera-token-service/src/main/java/com/hedera/node/app/service/token/api/StakingRewardsApi.java +++ b/hedera-node/hedera-token-service/src/main/java/com/hedera/node/app/service/token/api/StakingRewardsApi.java @@ -37,10 +37,25 @@ * Methods for computing an account's pending staking rewards. */ public interface StakingRewardsApi { + /** + * Logger for this interface + */ Logger log = LogManager.getLogger(StakingRewardsApi.class); + /** + * Constant for time conversion from minutes to seconds + */ int MINUTES_TO_SECONDS = 60; + /** + * Constant for time conversion from minutes to milliseconds + */ long MINUTES_TO_MILLISECONDS = 60_000L; + /** + * Constant for daily staking period in minutes + */ long DAILY_STAKING_PERIOD_MINS = 1440L; + /** + * Constant for UTC time zone + */ ZoneId ZONE_UTC = ZoneId.of("UTC"); /** diff --git a/hedera-node/hedera-token-service/src/main/java/com/hedera/node/app/service/token/api/TokenServiceApi.java b/hedera-node/hedera-token-service/src/main/java/com/hedera/node/app/service/token/api/TokenServiceApi.java index 14fa7e360418..131bb028b7c4 100644 --- a/hedera-node/hedera-token-service/src/main/java/com/hedera/node/app/service/token/api/TokenServiceApi.java +++ b/hedera-node/hedera-token-service/src/main/java/com/hedera/node/app/service/token/api/TokenServiceApi.java @@ -45,8 +45,20 @@ public interface TokenServiceApi { */ boolean checkForCustomFees(@NonNull CryptoTransferTransactionBody op); + /** + * Whether to free the alias when account is deleted or not. + * Aliases are always freed when we use ContractDeleteTransactionBody. + * Aliases are freed when we use CryptoDeleteTransactionBody based on the value of the + * accountsConfig#releaseAliasAfterDeletion + */ enum FreeAliasOnDeletion { + /** + * Free the alias on deletion. + */ YES, + /** + * Do not free the alias on deletion. + */ NO } @@ -76,6 +88,7 @@ void deleteAndTransfer( * @param stakedAccountIdInOp staked account id * @param stakedNodeIdInOp staked node id * @param accountStore readable account store + * @param networkInfo network info * @throws HandleException if the staking election is invalid */ void assertValidStakingElectionForCreation( @@ -96,6 +109,7 @@ void assertValidStakingElectionForCreation( * @param stakedAccountIdInOp staked account id * @param stakedNodeIdInOp staked node id * @param accountStore readable account store + * @param networkInfo network info * @throws HandleException if the staking election is invalid */ void assertValidStakingElectionForUpdate( @@ -110,12 +124,15 @@ void assertValidStakingElectionForUpdate( /** * Marks an account as a contract. * + * @param accountId the id of the account to mark as a contract + * @param autoRenewAccountId the id of the account to use for auto-renewing the contract */ void markAsContract(@NonNull AccountID accountId, @Nullable AccountID autoRenewAccountId); /** * Finalizes a hollow account as a contract. * + * @param hollowAccountId the id of the hollow account to finalize as a contract */ void finalizeHollowAccountAsContract(@NonNull AccountID hollowAccountId); diff --git a/hedera-node/hedera-token-service/src/main/java/com/hedera/node/app/service/token/records/ChildRecordFinalizer.java b/hedera-node/hedera-token-service/src/main/java/com/hedera/node/app/service/token/records/ChildRecordFinalizer.java index d74843c3684a..0e40434eeb4f 100644 --- a/hedera-node/hedera-token-service/src/main/java/com/hedera/node/app/service/token/records/ChildRecordFinalizer.java +++ b/hedera-node/hedera-token-service/src/main/java/com/hedera/node/app/service/token/records/ChildRecordFinalizer.java @@ -35,5 +35,13 @@ * for the child record */ public interface ChildRecordFinalizer { + /** + * This class is used to "finalize" hbar and token transfers for the child transaction record. + * It determines the net hbar transfers and token transfers based on the original value from writable state, + * and based on changes made during this transaction. It then constructs a TransferList and TokenTransferList + * for the child record. + * @param context the context + * @param function the functionality + */ void finalizeChildRecord(@NonNull ChildFinalizeContext context, final HederaFunctionality function); } diff --git a/hedera-node/hedera-token-service/src/main/java/com/hedera/node/app/service/token/records/CryptoTransferRecordBuilder.java b/hedera-node/hedera-token-service/src/main/java/com/hedera/node/app/service/token/records/CryptoTransferRecordBuilder.java index c55d74430959..6928c9c6e0cb 100644 --- a/hedera-node/hedera-token-service/src/main/java/com/hedera/node/app/service/token/records/CryptoTransferRecordBuilder.java +++ b/hedera-node/hedera-token-service/src/main/java/com/hedera/node/app/service/token/records/CryptoTransferRecordBuilder.java @@ -76,6 +76,11 @@ public interface CryptoTransferRecordBuilder extends SingleTransactionRecordBuil */ CryptoTransferRecordBuilder addAutomaticTokenAssociation(@NonNull final TokenAssociation tokenAssociation); + /** + * Tracks the result of a contract call, if any. It is used to update the transaction record. + * @param result the result of a contract call + * @return this builder + */ @NonNull CryptoTransferRecordBuilder contractCallResult(@Nullable ContractFunctionResult result); } diff --git a/hedera-node/hedera-token-service/src/main/java/com/hedera/node/app/service/token/records/GenesisAccountRecordBuilder.java b/hedera-node/hedera-token-service/src/main/java/com/hedera/node/app/service/token/records/GenesisAccountRecordBuilder.java index b55e6affdd80..94991873b05c 100644 --- a/hedera-node/hedera-token-service/src/main/java/com/hedera/node/app/service/token/records/GenesisAccountRecordBuilder.java +++ b/hedera-node/hedera-token-service/src/main/java/com/hedera/node/app/service/token/records/GenesisAccountRecordBuilder.java @@ -30,24 +30,32 @@ public interface GenesisAccountRecordBuilder { /** * Tracks the created account ID for the system account + * @param accountID the account ID of the system account + * @return this builder */ @NonNull GenesisAccountRecordBuilder accountID(@NonNull final AccountID accountID); /** * Tracks the synthetic transaction that represents the created system account + * @param txn the synthetic transaction that represents the created system account + * @return this builder */ @NonNull GenesisAccountRecordBuilder transaction(@NonNull final Transaction txn); /** * Tracks the synthetic transaction that represents the created system account + * @param status the status of the synthetic transaction that represents the created system account + * @return this builder */ @NonNull GenesisAccountRecordBuilder status(@NonNull ResponseCodeEnum status); /** * Tracks the memo for the synthetic record + * @param memo the memo for the synthetic record + * @return this builder */ @NonNull GenesisAccountRecordBuilder memo(@NonNull final String memo); diff --git a/hedera-node/hedera-token-service/src/main/java/com/hedera/node/app/service/token/records/NodeStakeUpdateRecordBuilder.java b/hedera-node/hedera-token-service/src/main/java/com/hedera/node/app/service/token/records/NodeStakeUpdateRecordBuilder.java index 9441eb63b49a..0c19e874708a 100644 --- a/hedera-node/hedera-token-service/src/main/java/com/hedera/node/app/service/token/records/NodeStakeUpdateRecordBuilder.java +++ b/hedera-node/hedera-token-service/src/main/java/com/hedera/node/app/service/token/records/NodeStakeUpdateRecordBuilder.java @@ -28,6 +28,7 @@ public interface NodeStakeUpdateRecordBuilder { * Sets the status. * * @param status the status + * @return the builder */ NodeStakeUpdateRecordBuilder status(@NonNull ResponseCodeEnum status); diff --git a/hedera-node/hedera-token-service/src/main/java/com/hedera/node/app/service/token/records/ParentRecordFinalizer.java b/hedera-node/hedera-token-service/src/main/java/com/hedera/node/app/service/token/records/ParentRecordFinalizer.java index 7a0703d2fe49..35a442d51af1 100644 --- a/hedera-node/hedera-token-service/src/main/java/com/hedera/node/app/service/token/records/ParentRecordFinalizer.java +++ b/hedera-node/hedera-token-service/src/main/java/com/hedera/node/app/service/token/records/ParentRecordFinalizer.java @@ -46,6 +46,15 @@ * for the parent record (excluding changes from child transaction records) */ public interface ParentRecordFinalizer { + /** This class is used to "finalize" hbar and token transfers for the parent transaction record. + * It determines the net hbar transfers and token transfers based on the original value from readable state, + * and based on changes made during this transaction. It then constructs a TransferList and TokenTransferList + * for the parent record. It also calculates staking rewards for the parent record. + * @param payer the account that will pay for the transaction + * @param context the context + * @param functionality the functionality + * @param explicitRewardReceivers the explicit reward receivers + */ default void finalizeParentRecord( @NonNull AccountID payer, @NonNull FinalizeContext context, @@ -54,6 +63,17 @@ default void finalizeParentRecord( finalizeParentRecord(payer, context, functionality, explicitRewardReceivers, emptyMap()); } + /** + * This class is used to "finalize" hbar and token transfers for the parent transaction record. + * It determines the net hbar transfers and token transfers based on the original value from readable state, + * and based on changes made during this transaction. It then constructs a TransferList and TokenTransferList + * for the parent record. It also calculates staking rewards for the parent record. + * @param payer the account that will pay for the transaction + * @param context the context + * @param functionality the functionality + * @param explicitRewardReceivers the explicit reward receivers + * @param prePaidRewards a map of account id to the amount of rewards paid out + */ void finalizeParentRecord( @Nullable AccountID payer, @NonNull FinalizeContext context, diff --git a/hedera-node/hedera-token-service/src/main/java/com/hedera/node/app/service/token/records/TokenAccountWipeRecordBuilder.java b/hedera-node/hedera-token-service/src/main/java/com/hedera/node/app/service/token/records/TokenAccountWipeRecordBuilder.java index fe2eae6c6d9f..0ce3a6e278f8 100644 --- a/hedera-node/hedera-token-service/src/main/java/com/hedera/node/app/service/token/records/TokenAccountWipeRecordBuilder.java +++ b/hedera-node/hedera-token-service/src/main/java/com/hedera/node/app/service/token/records/TokenAccountWipeRecordBuilder.java @@ -33,6 +33,7 @@ public interface TokenAccountWipeRecordBuilder extends TokenBaseRecordBuilder { /** * Sets the new total supply of a token * @param newTotalSupply the new total supply of a token + * @return this builder */ @NonNull TokenAccountWipeRecordBuilder newTotalSupply(final long newTotalSupply); diff --git a/hedera-node/hedera-token-service/src/main/java/com/hedera/node/app/service/token/records/TokenBaseRecordBuilder.java b/hedera-node/hedera-token-service/src/main/java/com/hedera/node/app/service/token/records/TokenBaseRecordBuilder.java index 55b603449312..bfe60b214eb6 100644 --- a/hedera-node/hedera-token-service/src/main/java/com/hedera/node/app/service/token/records/TokenBaseRecordBuilder.java +++ b/hedera-node/hedera-token-service/src/main/java/com/hedera/node/app/service/token/records/TokenBaseRecordBuilder.java @@ -26,6 +26,8 @@ public interface TokenBaseRecordBuilder extends SingleTransactionRecordBuilder { /** * Sets the {@link TokenType} of the token the recorded transaction created or modified. + * @param tokenType the token type + * @return this builder */ TokenBaseRecordBuilder tokenType(final @NonNull TokenType tokenType); } diff --git a/hedera-node/hedera-token-service/src/main/java/com/hedera/node/app/service/token/records/TokenBurnRecordBuilder.java b/hedera-node/hedera-token-service/src/main/java/com/hedera/node/app/service/token/records/TokenBurnRecordBuilder.java index 75d06fe2ecbb..d315e9e51729 100644 --- a/hedera-node/hedera-token-service/src/main/java/com/hedera/node/app/service/token/records/TokenBurnRecordBuilder.java +++ b/hedera-node/hedera-token-service/src/main/java/com/hedera/node/app/service/token/records/TokenBurnRecordBuilder.java @@ -34,6 +34,7 @@ public interface TokenBurnRecordBuilder extends TokenBaseRecordBuilder { /** * Sets the new total supply of a token * @param newTotalSupply the new total supply of a token + * @return this builder */ @NonNull TokenBurnRecordBuilder newTotalSupply(final long newTotalSupply); @@ -41,6 +42,7 @@ public interface TokenBurnRecordBuilder extends TokenBaseRecordBuilder { /** * Sets the list of serial numbers burned * @param serialNumbers list of serial numbers burned + * @return this builder */ @NonNull TokenBurnRecordBuilder serialNumbers(@NonNull List serialNumbers); diff --git a/hedera-node/hedera-token-service/src/main/java/com/hedera/node/app/service/token/records/TokenContext.java b/hedera-node/hedera-token-service/src/main/java/com/hedera/node/app/service/token/records/TokenContext.java index e4155b3a111c..8216a6e0750b 100644 --- a/hedera-node/hedera-token-service/src/main/java/com/hedera/node/app/service/token/records/TokenContext.java +++ b/hedera-node/hedera-token-service/src/main/java/com/hedera/node/app/service/token/records/TokenContext.java @@ -100,6 +100,7 @@ public interface TokenContext { /** * Indicate whether this is the first transaction since node startup + * @return true if this is the first transaction since node startup */ boolean isFirstTransaction(); diff --git a/hedera-node/hedera-token-service/src/main/java/com/hedera/node/app/service/token/records/TokenCreateRecordBuilder.java b/hedera-node/hedera-token-service/src/main/java/com/hedera/node/app/service/token/records/TokenCreateRecordBuilder.java index 9cbc59fa3c62..84d095ea0eb3 100644 --- a/hedera-node/hedera-token-service/src/main/java/com/hedera/node/app/service/token/records/TokenCreateRecordBuilder.java +++ b/hedera-node/hedera-token-service/src/main/java/com/hedera/node/app/service/token/records/TokenCreateRecordBuilder.java @@ -37,6 +37,10 @@ public interface TokenCreateRecordBuilder extends TokenBaseRecordBuilder { @NonNull TokenCreateRecordBuilder tokenID(@NonNull TokenID tokenID); + /** + * Gets the token ID of the token created + * @return the token ID of the token created + */ TokenID tokenID(); /** diff --git a/hedera-node/hedera-token-service/src/main/java/com/hedera/node/app/service/token/records/TokenMintRecordBuilder.java b/hedera-node/hedera-token-service/src/main/java/com/hedera/node/app/service/token/records/TokenMintRecordBuilder.java index 0d4b68787e4c..9ee4064d502a 100644 --- a/hedera-node/hedera-token-service/src/main/java/com/hedera/node/app/service/token/records/TokenMintRecordBuilder.java +++ b/hedera-node/hedera-token-service/src/main/java/com/hedera/node/app/service/token/records/TokenMintRecordBuilder.java @@ -38,6 +38,7 @@ public interface TokenMintRecordBuilder extends TokenBaseRecordBuilder { /** * Sets the new total supply of a token * @param newTotalSupply the new total supply of a token + * @return this builder */ TokenMintRecordBuilder newTotalSupply(final long newTotalSupply); diff --git a/hedera-node/hedera-token-service/src/main/java/module-info.java b/hedera-node/hedera-token-service/src/main/java/module-info.java index c43dc0820ad5..78dd9ce66018 100644 --- a/hedera-node/hedera-token-service/src/main/java/module-info.java +++ b/hedera-node/hedera-token-service/src/main/java/module-info.java @@ -1,3 +1,6 @@ +/** + * Provides the classes necessary to manage Hedera Token Service. + */ module com.hedera.node.app.service.token { exports com.hedera.node.app.service.token; exports com.hedera.node.app.service.token.api to @@ -16,9 +19,9 @@ requires transitive com.hedera.node.hapi; requires transitive com.hedera.pbj.runtime; requires transitive com.swirlds.config.api; + requires transitive org.apache.logging.log4j; requires com.hedera.node.app.hapi.utils; - requires com.hedera.node.app.service.evm; requires com.github.spotbugs.annotations; + requires com.hedera.evm; requires com.swirlds.common; - requires transitive org.apache.logging.log4j; } diff --git a/hedera-node/hedera-token-service/src/testFixtures/java/com/hedera/node/app/service/token/fixtures/FakeFeeRecordBuilder.java b/hedera-node/hedera-token-service/src/testFixtures/java/com/hedera/node/app/service/token/fixtures/FakeFeeRecordBuilder.java index 3abb0e000de1..97275d4483bc 100644 --- a/hedera-node/hedera-token-service/src/testFixtures/java/com/hedera/node/app/service/token/fixtures/FakeFeeRecordBuilder.java +++ b/hedera-node/hedera-token-service/src/testFixtures/java/com/hedera/node/app/service/token/fixtures/FakeFeeRecordBuilder.java @@ -19,6 +19,9 @@ import com.hedera.node.app.service.token.api.FeeRecordBuilder; import edu.umd.cs.findbugs.annotations.NonNull; +/** + * A fake implementation of {@link FeeRecordBuilder} for testing purposes. + */ public class FakeFeeRecordBuilder implements FeeRecordBuilder { private long transactionFee; diff --git a/hedera-node/hedera-token-service/src/testFixtures/java/module-info.java b/hedera-node/hedera-token-service/src/testFixtures/java/module-info.java index b5719788f546..e45b3eb7a559 100644 --- a/hedera-node/hedera-token-service/src/testFixtures/java/module-info.java +++ b/hedera-node/hedera-token-service/src/testFixtures/java/module-info.java @@ -1,3 +1,6 @@ +/** + * Provides fixtures for testing the token service. + */ module com.hedera.node.app.service.token.test.fixtures { exports com.hedera.node.app.service.token.fixtures; diff --git a/hedera-node/hedera-util-service-impl/build.gradle.kts b/hedera-node/hedera-util-service-impl/build.gradle.kts index d92df33eff04..8be5e5d0506a 100644 --- a/hedera-node/hedera-util-service-impl/build.gradle.kts +++ b/hedera-node/hedera-util-service-impl/build.gradle.kts @@ -14,7 +14,10 @@ * limitations under the License. */ -plugins { id("com.hedera.hashgraph.conventions") } +plugins { + id("com.hedera.gradle.services") + id("com.hedera.gradle.services-publish") +} description = "Default Hedera Util Service Implementation" @@ -23,7 +26,7 @@ mainModuleInfo { annotationProcessor("dagger.compiler") } testModuleInfo { requires("com.hedera.node.app.spi.test.fixtures") requires("com.hedera.node.config.test.fixtures") - requires("com.swirlds.config.api") + requires("com.swirlds.platform.core.test.fixtures") requires("com.swirlds.config.extensions.test.fixtures") requires("org.assertj.core") requires("org.junit.jupiter.api") diff --git a/hedera-node/hedera-util-service-impl/src/main/java/com/hedera/node/app/service/util/impl/UtilServiceImpl.java b/hedera-node/hedera-util-service-impl/src/main/java/com/hedera/node/app/service/util/impl/UtilServiceImpl.java index 5c25995e9808..c3a8a00b2de0 100644 --- a/hedera-node/hedera-util-service-impl/src/main/java/com/hedera/node/app/service/util/impl/UtilServiceImpl.java +++ b/hedera-node/hedera-util-service-impl/src/main/java/com/hedera/node/app/service/util/impl/UtilServiceImpl.java @@ -17,6 +17,7 @@ package com.hedera.node.app.service.util.impl; import com.hedera.node.app.service.util.UtilService; +import com.hedera.node.app.spi.Service; -/** Standard implementation of the {@link UtilService} {@link com.hedera.node.app.spi.Service}. */ +/** Standard implementation of the {@link UtilService} {@link Service}. */ public final class UtilServiceImpl implements UtilService {} diff --git a/hedera-node/hedera-util-service-impl/src/main/java/com/hedera/node/app/service/util/impl/handlers/UtilPrngHandler.java b/hedera-node/hedera-util-service-impl/src/main/java/com/hedera/node/app/service/util/impl/handlers/UtilPrngHandler.java index 96704d2db750..b56765968cb0 100644 --- a/hedera-node/hedera-util-service-impl/src/main/java/com/hedera/node/app/service/util/impl/handlers/UtilPrngHandler.java +++ b/hedera-node/hedera-util-service-impl/src/main/java/com/hedera/node/app/service/util/impl/handlers/UtilPrngHandler.java @@ -27,6 +27,7 @@ import com.hedera.node.app.spi.workflows.PreCheckException; import com.hedera.node.app.spi.workflows.PreHandleContext; import com.hedera.node.app.spi.workflows.TransactionHandler; +import com.hedera.node.config.data.UtilPrngConfig; import com.hedera.pbj.runtime.io.buffer.Bytes; import edu.umd.cs.findbugs.annotations.NonNull; import java.nio.ByteBuffer; @@ -91,6 +92,10 @@ public Fees calculateFees(@NonNull FeeContext feeContext) { */ @Override public void handle(@NonNull final HandleContext context) { + if (!context.configuration().getConfigData(UtilPrngConfig.class).isEnabled()) { + // (FUTURE) Should this throw NOT_SUPPORTED instead? As written is the legacy behavior. + return; + } final var op = context.body().utilPrngOrThrow(); final var range = op.range(); diff --git a/hedera-node/hedera-util-service-impl/src/main/java/module-info.java b/hedera-node/hedera-util-service-impl/src/main/java/module-info.java index 971ea817fec9..d90be662a9e7 100644 --- a/hedera-node/hedera-util-service-impl/src/main/java/module-info.java +++ b/hedera-node/hedera-util-service-impl/src/main/java/module-info.java @@ -7,6 +7,8 @@ requires transitive com.hedera.pbj.runtime; requires transitive dagger; requires transitive javax.inject; + requires com.hedera.node.config; + requires com.swirlds.config.api; requires org.apache.logging.log4j; requires static com.github.spotbugs.annotations; requires static java.compiler; // javax.annotation.processing.Generated diff --git a/hedera-node/hedera-util-service-impl/src/test/java/com/hedera/node/app/service/util/impl/test/handlers/UtilHandlersTest.java b/hedera-node/hedera-util-service-impl/src/test/java/com/hedera/node/app/service/util/impl/test/handlers/UtilHandlersTest.java new file mode 100644 index 000000000000..6fd72b7614ee --- /dev/null +++ b/hedera-node/hedera-util-service-impl/src/test/java/com/hedera/node/app/service/util/impl/test/handlers/UtilHandlersTest.java @@ -0,0 +1,42 @@ +/* + * Copyright (C) 2024 Hedera Hashgraph, LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.hedera.node.app.service.util.impl.test.handlers; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.mockito.Mockito.mock; + +import com.hedera.node.app.service.util.impl.handlers.UtilHandlers; +import com.hedera.node.app.service.util.impl.handlers.UtilPrngHandler; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +class UtilHandlersTest { + private UtilPrngHandler prngHandler; + + private UtilHandlers utilHandlers; + + @BeforeEach + public void setUp() { + prngHandler = mock(UtilPrngHandler.class); + utilHandlers = new UtilHandlers(prngHandler); + } + + @Test + void prngHandlerReturnsCorrectInstance() { + assertEquals(prngHandler, utilHandlers.prngHandler(), "prngHandler does not return correct instance"); + } +} diff --git a/hedera-node/hedera-util-service-impl/src/test/java/com/hedera/node/app/service/util/impl/test/handlers/UtilPrngHandlerTest.java b/hedera-node/hedera-util-service-impl/src/test/java/com/hedera/node/app/service/util/impl/test/handlers/UtilPrngHandlerTest.java index c8b64a74ab86..f402e3001620 100644 --- a/hedera-node/hedera-util-service-impl/src/test/java/com/hedera/node/app/service/util/impl/test/handlers/UtilPrngHandlerTest.java +++ b/hedera-node/hedera-util-service-impl/src/test/java/com/hedera/node/app/service/util/impl/test/handlers/UtilPrngHandlerTest.java @@ -21,8 +21,12 @@ import static org.assertj.core.api.AssertionsForClassTypes.assertThat; import static org.assertj.core.api.AssertionsForClassTypes.assertThatCode; import static org.assertj.core.api.AssertionsForClassTypes.assertThatThrownBy; +import static org.mockito.ArgumentMatchers.anyLong; +import static org.mockito.ArgumentMatchers.notNull; import static org.mockito.BDDMockito.given; import static org.mockito.Mock.Strictness.LENIENT; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; import com.hedera.hapi.node.base.SubType; import com.hedera.hapi.node.transaction.TransactionBody; @@ -31,7 +35,8 @@ import com.hedera.node.app.service.util.impl.records.PrngRecordBuilder; import com.hedera.node.app.spi.fees.FeeAccumulator; import com.hedera.node.app.spi.fees.FeeCalculator; -import com.hedera.node.app.spi.fixtures.TestBase; +import com.hedera.node.app.spi.fees.FeeContext; +import com.hedera.node.app.spi.fees.Fees; import com.hedera.node.app.spi.fixtures.fees.FakeFeeAccumulator; import com.hedera.node.app.spi.fixtures.fees.FakeFeeCalculator; import com.hedera.node.app.spi.records.BlockRecordInfo; @@ -41,6 +46,7 @@ import com.hedera.node.config.testfixtures.HederaTestConfigBuilder; import com.hedera.pbj.runtime.io.buffer.BufferedData; import com.hedera.pbj.runtime.io.buffer.Bytes; +import com.swirlds.platform.test.fixtures.state.TestBase; import java.util.Random; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -137,6 +143,26 @@ void followsHappyPathWithNoRange() { assertThat(recordBuilder.entropyBytes).isEqualTo(hash); } + @Test + void calculateFeesHappyPath() { + givenTxnWithoutRange(); + final var body = TransactionBody.newBuilder().utilPrng(txn).build(); + + final var feeCtx = mock(FeeContext.class); + given(feeCtx.body()).willReturn(body); + + final var feeCalc = mock(FeeCalculator.class); + given(feeCtx.feeCalculator(notNull())).willReturn(feeCalc); + given(feeCalc.addBytesPerTransaction(anyLong())).willReturn(feeCalc); + // The fees wouldn't be free in this scenario, but we don't care about the actual return + // value here since we're using a mock calculator + given(feeCalc.calculate()).willReturn(Fees.FREE); + + subject.calculateFees(feeCtx); + + verify(feeCalc).addBytesPerTransaction(0); + } + @ParameterizedTest @ValueSource( ints = { @@ -301,6 +327,18 @@ void emptyHashFromRunningHashReturnsAllZeros() { assertThat(recordBuilder.entropyBytes).isEqualTo(Bytes.wrap(new byte[48])); } + @Test + void verifyModThrowException() { + assertThatThrownBy(() -> UtilPrngHandler.mod(0, 0)).isInstanceOf(ArithmeticException.class); + } + + @Test + void verifyModHappyPath() { + assertThatCode(() -> UtilPrngHandler.mod(2, 4)).doesNotThrowAnyException(); + + assertThat(UtilPrngHandler.mod(2, 4)).isEqualTo(2); + } + private void givenTxnWithRange(int range) { txn = UtilPrngTransactionBody.newBuilder().range(range).build(); given(handleContext.body()) diff --git a/hedera-node/hedera-util-service/build.gradle.kts b/hedera-node/hedera-util-service/build.gradle.kts index 4d69d077da35..b122b93b1d9b 100644 --- a/hedera-node/hedera-util-service/build.gradle.kts +++ b/hedera-node/hedera-util-service/build.gradle.kts @@ -14,6 +14,9 @@ * limitations under the License. */ -plugins { id("com.hedera.hashgraph.conventions") } +plugins { + id("com.hedera.gradle.services") + id("com.hedera.gradle.services-publish") +} description = "Hedera Util Service API" diff --git a/hedera-node/hedera-util-service/src/main/java/module-info.java b/hedera-node/hedera-util-service/src/main/java/module-info.java index 48d2cd50dcad..9f0ad4b00130 100644 --- a/hedera-node/hedera-util-service/src/main/java/module-info.java +++ b/hedera-node/hedera-util-service/src/main/java/module-info.java @@ -3,8 +3,8 @@ uses com.hedera.node.app.service.util.UtilService; - requires com.hedera.node.hapi; requires transitive com.hedera.node.app.spi; + requires com.hedera.node.hapi; requires transitive com.hedera.pbj.runtime; requires static com.github.spotbugs.annotations; } diff --git a/hedera-node/infrastructure/docker/containers/production-next/consensus-node/entrypoint.sh b/hedera-node/infrastructure/docker/containers/production-next/consensus-node/entrypoint.sh index f42a5b1a435c..dc374cc9b6ab 100644 --- a/hedera-node/infrastructure/docker/containers/production-next/consensus-node/entrypoint.sh +++ b/hedera-node/infrastructure/docker/containers/production-next/consensus-node/entrypoint.sh @@ -1,4 +1,4 @@ -#!/usr/bin/env bash +#!/command/with-contenv bash ######################################################################################################################## # Copyright 2016-2022 Hedera Hashgraph, LLC # diff --git a/hedera-node/log4j2.xml b/hedera-node/log4j2.xml index 86634f8fed4b..fcffc6e46d69 100644 --- a/hedera-node/log4j2.xml +++ b/hedera-node/log4j2.xml @@ -7,7 +7,7 @@ + filePattern="output/hgcaa.log-%i.log"> %d{yyyy-MM-dd HH:mm:ss.SSS} %-5p %-4L %c{1} - %m{nolookups}%n @@ -18,7 +18,7 @@ + filePattern="output/queries.log-%i.log"> %d{yyyy-MM-dd HH:mm:ss.SSS} %-5p %-4L %c{1} - %m{nolookups}%n @@ -30,7 +30,7 @@ + filePattern="output/swirlds.log-%i.log"> %d{yyyy-MM-dd HH:mm:ss.SSS} %-8sn %-5p %-16marker <%t> %c{1}: %msg{nolookups}%n @@ -42,26 +42,29 @@ + filePattern="output/swirlds-hashstream/swirlds-hashstream-%i.log"> %d{yyyy-MM-dd HH:mm:ss.SSS} %-8sn %-5p %-16marker <%t> %c{1}: %msg{nolookups}%n - - - - - - - - + + filePattern="output/transaction-state/state-changes-%i.log"> + + %d{yyyy-MM-dd HH:mm:ss.SSS} - %m{nolookups}%n + + + + + + + %d{yyyy-MM-dd HH:mm:ss.SSS} - %m{nolookups}%n diff --git a/hedera-node/test-clients/build.gradle.kts b/hedera-node/test-clients/build.gradle.kts index a5788651e44b..bef57516562b 100644 --- a/hedera-node/test-clients/build.gradle.kts +++ b/hedera-node/test-clients/build.gradle.kts @@ -17,17 +17,19 @@ import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar plugins { - id("com.hedera.hashgraph.conventions") - id("com.hedera.hashgraph.shadow-jar") + id("com.hedera.gradle.services") + id("com.hedera.gradle.shadow-jar") } description = "Hedera Services Test Clients for End to End Tests (EET)" -mainModuleInfo { runtimeOnly("org.junit.platform.launcher") } +mainModuleInfo { + runtimeOnly("org.junit.jupiter.engine") + runtimeOnly("org.junit.platform.launcher") +} itestModuleInfo { requires("com.hedera.node.test.clients") - requires("com.hedera.node.hapi") requires("org.apache.commons.lang3") requires("org.junit.jupiter.api") requires("org.testcontainers") @@ -51,145 +53,67 @@ sourceSets { create("yahcli") } -// The following tasks run the 'HapiTestEngine' tests (residing in src/main/java). -// IntelliJ picks up this task when running tests through in the IDE. - -// Runs all tests -tasks.register("hapiTest") { - testClassesDirs = sourceSets.main.get().output.classesDirs - classpath = sourceSets.main.get().runtimeClasspath - - useJUnitPlatform() +val ciCheckTagExpressions = + mapOf( + "hapiTestCrypto" to "CRYPTO", + "hapiTestToken" to "TOKEN", + "hapiTestRestart" to "RESTART", + "hapiTestSmartContract" to "SMART_CONTRACT", + "hapiTestNDReconnect" to "ND_RECONNECT", + "hapiTestTimeConsuming" to "LONG_RUNNING", + "hapiTestMisc" to "!(CRYPTO|TOKEN|SMART_CONTRACT|LONG_RUNNING|RESTART|ND_RECONNECT)" + ) - // Limit heap and number of processors - maxHeapSize = "8g" - jvmArgs("-XX:ActiveProcessorCount=6") - - // Do not yet run things on the '--module-path' - modularity.inferModulePath.set(false) +tasks { + ciCheckTagExpressions.forEach { (taskName, _) -> register(taskName) { dependsOn("test") } } } -// Runs all tests that are not part of other test tasks -tasks.register("hapiTestMisc") { +tasks.test { testClassesDirs = sourceSets.main.get().output.classesDirs classpath = sourceSets.main.get().runtimeClasspath + val ciTagExpression = + gradle.startParameter.taskNames + .stream() + .map { ciCheckTagExpressions[it] ?: "" } + .filter { it.isNotBlank() } + .toList() + .joinToString("|") useJUnitPlatform { - excludeTags( - "CRYPTO", - "TOKEN", - "SMART_CONTRACT", - "TIME_CONSUMING", - "RESTART", - "ND_RECONNECT" + includeTags( + if (ciTagExpression.isBlank()) "any()|none()" + else "${ciTagExpression}|STREAM_VALIDATION" ) } - // Limit heap and number of processors - maxHeapSize = "8g" - jvmArgs("-XX:ActiveProcessorCount=6") - - // Do not yet run things on the '--module-path' - modularity.inferModulePath.set(false) -} - -// Runs all tests of CryptoService -tasks.register("hapiTestCrypto") { - testClassesDirs = sourceSets.main.get().output.classesDirs - classpath = sourceSets.main.get().runtimeClasspath - - useJUnitPlatform { includeTags("CRYPTO", "RECORD_STREAM_VALIDATION") } - - // Limit heap and number of processors - maxHeapSize = "8g" - jvmArgs("-XX:ActiveProcessorCount=6") - - // Do not yet run things on the '--module-path' - modularity.inferModulePath.set(false) -} - -// Runs all tests of TokenService -tasks.register("hapiTestToken") { - testClassesDirs = sourceSets.main.get().output.classesDirs - classpath = sourceSets.main.get().runtimeClasspath - - useJUnitPlatform { includeTags("TOKEN", "RECORD_STREAM_VALIDATION") } - - // Limit heap and number of processors - maxHeapSize = "8g" - jvmArgs("-XX:ActiveProcessorCount=6") - - // Do not yet run things on the '--module-path' - modularity.inferModulePath.set(false) -} - -// Runs all tests of SmartContractService -tasks.register("hapiTestSmartContract") { - testClassesDirs = sourceSets.main.get().output.classesDirs - classpath = sourceSets.main.get().runtimeClasspath - - useJUnitPlatform { includeTags("SMART_CONTRACT", "RECORD_STREAM_VALIDATION") } - - // Limit heap and number of processors - maxHeapSize = "8g" - jvmArgs("-XX:ActiveProcessorCount=6") - - // Do not yet run things on the '--module-path' - modularity.inferModulePath.set(false) -} - -// Runs a handful of test-suites that are extremely time-consuming (10+ minutes) -tasks.register("hapiTestTimeConsuming") { - testClassesDirs = sourceSets.main.get().output.classesDirs - classpath = sourceSets.main.get().runtimeClasspath - - useJUnitPlatform { includeTags("TIME_CONSUMING", "RECORD_STREAM_VALIDATION") } + // Default quiet mode is "false" unless we are running in CI or set it explicitly to "true" + systemProperty( + "hapi.spec.quiet.mode", + System.getProperty("hapi.spec.quiet.mode") + ?: if (ciTagExpression.isNotBlank()) "true" else "false" + ) + systemProperty("junit.jupiter.execution.parallel.enabled", true) + systemProperty("junit.jupiter.execution.parallel.mode.default", "concurrent") + // Surprisingly, the Gradle JUnitPlatformTestExecutionListener fails to gather result + // correctly if test classes run in parallel (concurrent execution WITHIN a test class + // is fine). So we need to force the test classes to run in the same thread. Luckily this + // is not a huge limitation, as our test classes generally have enough non-leaky tests to + // get a material speed up. See https://github.com/gradle/gradle/issues/6453. + systemProperty("junit.jupiter.execution.parallel.mode.classes.default", "same_thread") + systemProperty( + "junit.jupiter.testclass.order.default", + "org.junit.jupiter.api.ClassOrderer\$OrderAnnotation" + ) // Limit heap and number of processors maxHeapSize = "8g" jvmArgs("-XX:ActiveProcessorCount=6") + maxParallelForks = 1 // Do not yet run things on the '--module-path' modularity.inferModulePath.set(false) } -// Runs a handful of test-suites that are extremely time-consuming (10+ minutes) -tasks.register("hapiTestRestart") { - testClassesDirs = sourceSets.main.get().output.classesDirs - classpath = sourceSets.main.get().runtimeClasspath - - useJUnitPlatform { includeTags("RESTART", "RECORD_STREAM_VALIDATION") } - - // Limit heap and number of processors - maxHeapSize = "8g" - jvmArgs("-XX:ActiveProcessorCount=6") - - // Do not yet run things on the '--module-path' - modularity.inferModulePath.set(false) -} - -tasks.register("hapiTestNDReconnect") { - testClassesDirs = sourceSets.main.get().output.classesDirs - classpath = sourceSets.main.get().runtimeClasspath - - useJUnitPlatform { includeTags("ND_RECONNECT", "RECORD_STREAM_VALIDATION") } - - // Limit heap and number of processors - maxHeapSize = "8g" - jvmArgs("-XX:ActiveProcessorCount=6") - - // Do not yet run things on the '--module-path' - modularity.inferModulePath.set(false) -} - -tasks.test { - // Disable these EET tests from being executed as part of the gradle "test" task. - // We should maybe remove them from src/test into src/eet, - // so it can be part of an eet test task instead. See issue #3412 - // (https://github.com/hashgraph/hedera-services/issues/3412). - exclude("**/*") -} - tasks.itest { systemProperty("itests", System.getProperty("itests")) systemProperty("junit.jupiter.execution.parallel.enabled", false) diff --git a/hedera-node/test-clients/record-snapshots/ContractCall.json b/hedera-node/test-clients/record-snapshots/ContractCall.json index 5f4bf3f0b4d7..3bf5f08071fb 100644 --- a/hedera-node/test-clients/record-snapshots/ContractCall.json +++ b/hedera-node/test-clients/record-snapshots/ContractCall.json @@ -1 +1 @@ -{"specSnapshots":{"ConsTimeManagementWorksWithRevertedInternalCreations":{"placeholderNum":1016,"encodedItems":[{"b64Body":"Cg8KCQjE/P6sBhC/BxICGAISAhgDGP7LzSwiAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjooBjwESDAiAy9mwBhDUiuiEAhptCiISICOWph9j8lOo5q/tQLgLolDeKnYE3XmD2Oj4NftacZ1YCiM6IQOXBtQRyaNn/w5IYJgVJ5qTiRcRlsD7IS6ypZEpSPx2igoiEiA0beZ/xd1vyAOwZdnvE/TXWk/1h+aP2AkWvkX1y6IQHCIMSGVsbG8gV29ybGQhKgAyAA==","b64Record":"CiUIFhoDGPkHKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjAgXDbciouS1iV8HgoEUB7CLn0BS4Og+Cv9V4+NwYWez51tn8ApiK0XnZlGyjn6I3QaDAiA/f6sBhD+wumgAiIPCgkIxPz+rAYQvwcSAhgCKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjlIA"},{"b64Body":"Cg8KCQjF/P6sBhDDBxICGAISAhgDGI322zkiAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjpoBmBgKAxj5ByKQGDYwODA2MDQwNTIzNDgwMTU2MTAwMTA1NzYwMDA4MGZkNWI1MDYxMDVlODgwNjEwMDIwNjAwMDM5NjAwMGYzZmU2MDgwNjA0MDUyMzQ4MDE1NjEwMDEwNTc2MDAwODBmZDViNTA2MDA0MzYxMDYxMDAzNjU3NjAwMDM1NjBlMDFjODA2MzYyNTM4YTE2MTQ2MTAwM2I1NzgwNjM3YzQxYWQyYzE0NjEwMDU3NTc1YjYwMDA4MGZkNWI2MTAwNTU2MDA0ODAzNjAzODEwMTkwNjEwMDUwOTE5MDYxMDM3NDU2NWI2MTAwODc1NjViMDA1YjYxMDA3MTYwMDQ4MDM2MDM4MTAxOTA2MTAwNmM5MTkwNjEwM2I0NTY1YjYxMDBkODU2NWI2MDQwNTE2MTAwN2U5MTkwNjEwM2ZhNTY1YjYwNDA1MTgwOTEwMzkwZjM1YjYwMDA2MDQwNTE2MTAwOTU5MDYxMDMwNTU2NWI2MDQwNTE4MDkxMDM5MDYwMDBmMDgwMTU4MDE1NjEwMGIxNTczZDYwMDA4MDNlM2Q2MDAwZmQ1YjUwOTA1MDYwMDA2MTAwYzA4NDg0NjEwMWVkNTY1YjkwNTA2MDE2NjAwMzBiODExNDYxMDBkMjU3NjAwMDgwZmQ1YjUwNTA1MDUwNTY1YjYwMDA4MDYwMDA2MTAxNjc3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNjYzN2M0MWFkMmM2MGUwMWI4NTYwNDA1MTYwMjQwMTYxMDEwZjkxOTA2MTA0MjQ1NjViNjA0MDUxNjAyMDgxODMwMzAzODE1MjkwNjA0MDUyOTA3YmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmMTkxNjYwMjA4MjAxODA1MTdiZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmY4MzgxODMxNjE3ODM1MjUwNTA1MDUwNjA0MDUxNjEwMTc5OTE5MDYxMDRiMDU2NWI2MDAwNjA0MDUxODA4MzAzODE2MDAwODY1YWYxOTE1MDUwM2Q4MDYwMDA4MTE0NjEwMWI2NTc2MDQwNTE5MTUwNjAxZjE5NjAzZjNkMDExNjgyMDE2MDQwNTIzZDgyNTIzZDYwMDA2MDIwODQwMTNlNjEwMWJiNTY1YjYwNjA5MTUwNWI1MDkxNTA5MTUwODE2MTAxY2M1NzYwMTU2MTAxZTE1NjViODA4MDYwMjAwMTkwNTE4MTAxOTA2MTAxZTA5MTkwNjEwNTAwNTY1YjViNjAwMzBiOTI1MDUwNTA5MTkwNTA1NjViNjAwMDgwNjAwMDYxMDE2NzczZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjE2NjM0OTE0NmJkZTYwZTAxYjg2ODY2MDQwNTE2MDI0MDE2MTAyMjY5MjkxOTA2MTA1MmQ1NjViNjA0MDUxNjAyMDgxODMwMzAzODE1MjkwNjA0MDUyOTA3YmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmMTkxNjYwMjA4MjAxODA1MTdiZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmY4MzgxODMxNjE3ODM1MjUwNTA1MDUwNjA0MDUxNjEwMjkwOTE5MDYxMDRiMDU2NWI2MDAwNjA0MDUxODA4MzAzODE2MDAwODY1YWYxOTE1MDUwM2Q4MDYwMDA4MTE0NjEwMmNkNTc2MDQwNTE5MTUwNjAxZjE5NjAzZjNkMDExNjgyMDE2MDQwNTIzZDgyNTIzZDYwMDA2MDIwODQwMTNlNjEwMmQyNTY1YjYwNjA5MTUwNWI1MDkxNTA5MTUwODE2MTAyZTM1NzYwMTU2MTAyZjg1NjViODA4MDYwMjAwMTkwNTE4MTAxOTA2MTAyZjc5MTkwNjEwNTAwNTY1YjViNjAwMzBiOTI1MDUwNTA5MjkxNTA1MDU2NWI2MDVjODA2MTA1NTc4MzM5MDE5MDU2NWI2MDAwODBmZDViNjAwMDczZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjgyMTY5MDUwOTE5MDUwNTY1YjYwMDA2MTAzNDE4MjYxMDMxNjU2NWI5MDUwOTE5MDUwNTY1YjYxMDM1MTgxNjEwMzM2NTY1YjgxMTQ2MTAzNWM1NzYwMDA4MGZkNWI1MDU2NWI2MDAwODEzNTkwNTA2MTAzNmU4MTYxMDM0ODU2NWI5MjkxNTA1MDU2NWI2MDAwODA2MDQwODM4NTAzMTIxNTYxMDM4YjU3NjEwMzhhNjEwMzExNTY1YjViNjAwMDYxMDM5OTg1ODI4NjAxNjEwMzVmNTY1YjkyNTA1MDYwMjA2MTAzYWE4NTgyODYwMTYxMDM1ZjU2NWI5MTUwNTA5MjUwOTI5MDUwNTY1YjYwMDA2MDIwODI4NDAzMTIxNTYxMDNjYTU3NjEwM2M5NjEwMzExNTY1YjViNjAwMDYxMDNkODg0ODI4NTAxNjEwMzVmNTY1YjkxNTA1MDkyOTE1MDUwNTY1YjYwMDA4MTkwNTA5MTkwNTA1NjViNjEwM2Y0ODE2MTAzZTE1NjViODI1MjUwNTA1NjViNjAwMDYwMjA4MjAxOTA1MDYxMDQwZjYwMDA4MzAxODQ2MTAzZWI1NjViOTI5MTUwNTA1NjViNjEwNDFlODE2MTAzMzY1NjViODI1MjUwNTA1NjViNjAwMDYwMjA4MjAxOTA1MDYxMDQzOTYwMDA4MzAxODQ2MTA0MTU1NjViOTI5MTUwNTA1NjViNjAwMDgxNTE5MDUwOTE5MDUwNTY1YjYwMDA4MTkwNTA5MjkxNTA1MDU2NWI2MDAwNWI4MzgxMTAxNTYxMDQ3MzU3ODA4MjAxNTE4MTg0MDE1MjYwMjA4MTAxOTA1MDYxMDQ1ODU2NWI2MDAwODQ4NDAxNTI1MDUwNTA1MDU2NWI2MDAwNjEwNDhhODI2MTA0M2Y1NjViNjEwNDk0ODE4NTYxMDQ0YTU2NWI5MzUwNjEwNGE0ODE4NTYwMjA4NjAxNjEwNDU1NTY1YjgwODQwMTkxNTA1MDkyOTE1MDUwNTY1YjYwMDA2MTA0YmM4Mjg0NjEwNDdmNTY1YjkxNTA4MTkwNTA5MjkxNTA1MDU2NWI2MDAwODE2MDAzMGI5MDUwOTE5MDUwNTY1YjYxMDRkZDgxNjEwNGM3NTY1YjgxMTQ2MTA0ZTg1NzYwMDA4MGZkNWI1MDU2NWI2MDAwODE1MTkwNTA2MTA0ZmE4MTYxMDRkNDU2NWI5MjkxNTA1MDU2NWI2MDAwNjAyMDgyODQwMzEyMTU2MTA1MTY1NzYxMDUxNTYxMDMxMTU2NWI1YjYwMDA2MTA1MjQ4NDgyODUwMTYxMDRlYjU2NWI5MTUwNTA5MjkxNTA1MDU2NWI2MDAwNjA0MDgyMDE5MDUwNjEwNTQyNjAwMDgzMDE4NTYxMDQxNTU2NWI2MTA1NGY2MDIwODMwMTg0NjEwNDE1NTY1YjkzOTI1MDUwNTA1NmZlNjA4MDYwNDA1MjM0ODAxNTYwMGY1NzYwMDA4MGZkNWI1MDYwM2Y4MDYwMWQ2MDAwMzk2MDAwZjNmZTYwODA2MDQwNTI2MDAwODBmZGZlYTI2NDY5NzA2NjczNTgyMjEyMjAzZWM4ZGUwZGJhZjQ0ZDJkYmYyNGZkM2E1YTI1OGRlM2NkNmJmMzJiMjE2OGU2ODhhNDRlYmE3ZTc5YWI4YzU5NjQ3MzZmNmM2MzQzMDAwODEwMDAzM2EyNjQ2OTcwNjY3MzU4MjIxMjIwZTMyY2EyNzUxM2FhODgwY2IwMWZmODc0MjEwNGZlMTZlZTY4ZDYyNjE1MDYzNjE4YmI3NGE0MWRmN2FlMTE1NjY0NzM2ZjZjNjM0MzAwMDgxMDAwMzM=","b64Record":"CiAIFiocCgwIARAMGgYIgK6ZpA8SDAgBEA8aBgiArpmkDxIwRSwOJFwZ5Y/TCdYXuaAG7bn8fCMPpfFegVklS2DxKVKAw+YyXLhsp566iJH/lZkUGgsIgf3+rAYQorP6YSIPCgkIxfz+rAYQwwcSAhgCKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjlIA"},{"b64Body":"Cg8KCQjF/P6sBhDFBxICGAISAhgDGJb7rp0CIgIIeDIgw4PCrsOCwrfDg8K5dEY4w4LCrkrDg8KLw4PCkMODwo5CRQoDGPkHGiISIDMDdFNZ0zLVX0qp427Aw84Au2oh8Sj8ueF2H600vszmIJChD0IFCIDO2gNSAFoAagtjZWxsYXIgZG9vcg==","b64Record":"CiUIFiIDGPoHKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjDuU4WpTCQrK8BHFAWBboJxMnRx9+w857QdeO0mEXCp0lvsk3GhdgqCt3vh4QuvekoaDAiB/f6sBhCq8u7GAiIPCgkIxfz+rAYQxQcSAhgCKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjjDA2eIGQp0OCgMY+gcS6AtggGBAUjSAFWEAEFdgAID9W1BgBDYQYQA2V2AANWDgHIBjYlOKFhRhADtXgGN8Qa0sFGEAV1dbYACA/VthAFVgBIA2A4EBkGEAUJGQYQN0VlthAIdWWwBbYQBxYASANgOBAZBhAGyRkGEDtFZbYQDYVltgQFFhAH6RkGED+lZbYEBRgJEDkPNbYABgQFFhAJWQYQMFVltgQFGAkQOQYADwgBWAFWEAsVc9YACAPj1gAP1bUJBQYABhAMCEhGEB7VZbkFBgFmADC4EUYQDSV2AAgP1bUFBQUFZbYACAYABhAWdz//////////////////////////8WY3xBrSxg4BuFYEBRYCQBYQEPkZBhBCRWW2BAUWAggYMDA4FSkGBAUpB7/////////////////////////////////////xkWYCCCAYBRe/////////////////////////////////////+DgYMWF4NSUFBQUGBAUWEBeZGQYQSwVltgAGBAUYCDA4FgAIZa8ZFQUD2AYACBFGEBtldgQFGRUGAfGWA/PQEWggFgQFI9glI9YABgIIQBPmEBu1ZbYGCRUFtQkVCRUIFhAcxXYBVhAeFWW4CAYCABkFGBAZBhAeCRkGEFAFZbW2ADC5JQUFCRkFBWW2AAgGAAYQFnc///////////////////////////FmNJFGveYOAbhoZgQFFgJAFhAiaSkZBhBS1WW2BAUWAggYMDA4FSkGBAUpB7/////////////////////////////////////xkWYCCCAYBRe/////////////////////////////////////+DgYMWF4NSUFBQUGBAUWECkJGQYQSwVltgAGBAUYCDA4FgAIZa8ZFQUD2AYACBFGECzVdgQFGRUGAfGWA/PQEWggFgQFI9glI9YABgIIQBPmEC0lZbYGCRUFtQkVCRUIFhAuNXYBVhAvhWW4CAYCABkFGBAZBhAveRkGEFAFZbW2ADC5JQUFCSkVBQVltgXIBhBVeDOQGQVltgAID9W2AAc///////////////////////////ghaQUJGQUFZbYABhA0GCYQMWVluQUJGQUFZbYQNRgWEDNlZbgRRhA1xXYACA/VtQVltgAIE1kFBhA26BYQNIVluSkVBQVltgAIBgQIOFAxIVYQOLV2EDimEDEVZbW2AAYQOZhYKGAWEDX1ZbklBQYCBhA6qFgoYBYQNfVluRUFCSUJKQUFZbYABgIIKEAxIVYQPKV2EDyWEDEVZbW2AAYQPYhIKFAWEDX1ZbkVBQkpFQUFZbYACBkFCRkFBWW2ED9IFhA+FWW4JSUFBWW2AAYCCCAZBQYQQPYACDAYRhA+tWW5KRUFBWW2EEHoFhAzZWW4JSUFBWW2AAYCCCAZBQYQQ5YACDAYRhBBVWW5KRUFBWW2AAgVGQUJGQUFZbYACBkFCSkVBQVltgAFuDgRAVYQRzV4CCAVGBhAFSYCCBAZBQYQRYVltgAISEAVJQUFBQVltgAGEEioJhBD9WW2EElIGFYQRKVluTUGEEpIGFYCCGAWEEVVZbgIQBkVBQkpFQUFZbYABhBLyChGEEf1ZbkVCBkFCSkVBQVltgAIFgAwuQUJGQUFZbYQTdgWEEx1ZbgRRhBOhXYACA/VtQVltgAIFRkFBhBPqBYQTUVluSkVBQVltgAGAggoQDEhVhBRZXYQUVYQMRVltbYABhBSSEgoUBYQTrVluRUFCSkVBQVltgAGBAggGQUGEFQmAAgwGFYQQVVlthBU9gIIMBhGEEFVZbk5JQUFBW/mCAYEBSNIAVYA9XYACA/VtQYD+AYB1gADlgAPP+YIBgQFJgAID9/qJkaXBmc1giEiA+yN4NuvRNLb8k/TpaJY3jzWvzKyFo5oikTrp+eauMWWRzb2xjQwAIEAAzomRpcGZzWCISIOMsonUTqogMsB/4dCEE/hbuaNYmFQY2GLt0pB33rhFWZHNvbGNDAAgQADMigAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKMCaDDoDGPoHShYKFAAAAAAAAAAAAAAAAAAAAAAAAAP6cgcKAxj6BxABUhYKCQoCGAIQ/7LFDQoJCgIYYhCAs8UN"},{"b64Body":"Cg8KCQjG/P6sBhDHBxICGAISAhgDGOCssQMiAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjjpPCgMY+gcQoI0GIkRiU4oWAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==","b64Record":"CiUIISIDGPoHKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjAF0zxP2TtaojXAfZ9trOFg7xse34nBIcFUObWjPllVxDeMeKZjhX962no/HLVWg9UaCwiC/f6sBhCd1s5rIg8KCQjG/P6sBhDHBxICGAIqIMODwq7DgsK3w4PCuXRGOMOCwq5Kw4PCi8ODwpDDg8KOMN6arwM6CBoCMHgosokGUhYKCQoCGAIQu7XeBgoJCgIYYhC8td4G"},{"b64Body":"ChEKCQjG/P6sBhDHBxICGAIgAcICBgoCGAASAA==","b64Record":"CgIIHhIwJf/H4tAZMVk5hEmN8lkhigg/HZpYNSj/k1Cg2H2cOQFbhSAF1WRTb20MjHrpgXMlGgsIgv3+rAYQntbOayIRCgkIxvz+rAYQxwcSAhgCIAE6jAEKAxjnAhIgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB4aEElOU1VGRklDSUVOVF9HQVMo4v0qUJLEAmJESRRr3gAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABqAxj6B1IAegsIgv3+rAYQndbOaw=="}]},"PayableSuccess":{"placeholderNum":1019,"encodedItems":[{"b64Body":"Cg8KCQjL/P6sBhDjBxICGAISAhgDGP7LzSwiAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjooBjgESCwiHy9mwBhCyj4Q2Gm0KIhIgEDp5opPQRd+kyPoPKVSzEx7N0wlAFc9yvwRKgaWELVEKIzohAgsrh0IQ9+xF71qP+H3dlmvcGZnufsNojcnr70DQsO3dCiISIK2cErWvxy1g0s0aT0virBpJploduoKlpH2uUfhkeiC9IgxIZWxsbyBXb3JsZCEqADIA","b64Record":"CiUIFhoDGPwHKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjASEJig/eiAFIBHx/FGdpzaMOS6XBske+Y4BiZ74nx0PawuixTC/LfK2AHYdwChAL4aCwiH/f6sBhCl3qNFIg8KCQjL/P6sBhDjBxICGAIqIMODwq7DgsK3w4PCuXRGOMOCwq5Kw4PCi8ODwpDDg8KOUgA="},{"b64Body":"Cg8KCQjL/P6sBhDnBxICGAISAhgDGPa4sjEiAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjpoBogkKAxj8ByKaCTYwODA2MDQwNTI2MTAyM2E4MDYxMDAxMzYwMDAzOTYwMDBmM2ZlNjA4MDYwNDA1MjYwMDQzNjEwNjEwMDNmNTc2MDAwMzU2MGUwMWM4MDYzMTIwNjVmZTAxNDYxMDA4ZjU3ODA2MzNjY2ZkNjBiMTQ2MTAwYmE1NzgwNjM2ZjY0MjM0ZTE0NjEwMGQxNTc4MDYzYjZiNTVmMjUxNDYxMDEyYzU3NWIzMzczZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjE2N2ZmMWIwM2Y3MDhiOWMzOWY0NTNmZTNmMGNlZjg0MTY0YzdkNmY3ZGY4MzZkZjA3OTZlMWU5YzJiY2U2ZWUzOTdlMzQ2MDQwNTE4MDgyODE1MjYwMjAwMTkxNTA1MDYwNDA1MTgwOTEwMzkwYTIwMDViMzQ4MDE1NjEwMDliNTc2MDAwODBmZDViNTA2MTAwYTQ2MTAxNWE1NjViNjA0MDUxODA4MjgxNTI2MDIwMDE5MTUwNTA2MDQwNTE4MDkxMDM5MGYzNWIzNDgwMTU2MTAwYzY1NzYwMDA4MGZkNWI1MDYxMDBjZjYxMDE2MjU2NWIwMDViMzQ4MDE1NjEwMGRkNTc2MDAwODBmZDViNTA2MTAxMmE2MDA0ODAzNjAzNjA0MDgxMTAxNTYxMDBmNDU3NjAwMDgwZmQ1YjgxMDE5MDgwODAzNTczZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjE2OTA2MDIwMDE5MDkyOTE5MDgwMzU5MDYwMjAwMTkwOTI5MTkwNTA1MDUwNjEwMWFiNTY1YjAwNWI2MTAxNTg2MDA0ODAzNjAzNjAyMDgxMTAxNTYxMDE0MjU3NjAwMDgwZmQ1YjgxMDE5MDgwODAzNTkwNjAyMDAxOTA5MjkxOTA1MDUwNTA2MTAxZjY1NjViMDA1YjYwMDA0NzkwNTA5MDU2NWIzMzczZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjE2NjEwOGZjNDc5MDgxMTUwMjkwNjA0MDUxNjAwMDYwNDA1MTgwODMwMzgxODU4ODg4ZjE5MzUwNTA1MDUwMTU4MDE1NjEwMWE4NTczZDYwMDA4MDNlM2Q2MDAwZmQ1YjUwNTY1YjgxNzNmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmMTY2MTA4ZmM4MjkwODExNTAyOTA2MDQwNTE2MDAwNjA0MDUxODA4MzAzODE4NTg4ODhmMTkzNTA1MDUwNTAxNTgwMTU2MTAxZjE1NzNkNjAwMDgwM2UzZDYwMDBmZDViNTA1MDUwNTY1YjgwMzQxNDYxMDIwMjU3NjAwMDgwZmQ1YjUwNTZmZWEyNjU2MjdhN2E3MjMxNTgyMGY4Zjg0ZmMzMWE4NDUwNjRiNTc4MWU5MDgzMTZmM2M1OTExNTc5NjJkZWFiYjBmZDQyNGVkNTRmMjU2NDAwZjk2NDczNmY2YzYzNDMwMDA1MTEwMDMy","b64Record":"CiAIFiocCgwIARAMGgYIgK6ZpA8SDAgBEA8aBgiArpmkDxIw1LrpsBPieMQHpMH0VgiDABJXI/5yLA2KdrW+yHbtJt8e8F7us0HhafBE/0sIxW+BGgwIh/3+rAYQ8pz4xgIiDwoJCMv8/qwGEOcHEgIYAiogw4PCrsOCwrfDg8K5dEY4w4LCrkrDg8KLw4PCkMODwo5SAA=="},{"b64Body":"Cg8KCQjM/P6sBhDpBxICGAISAhgDGK/Dto0EIgIIeDIgw4PCrsOCwrfDg8K5dEY4w4LCrkrDg8KLw4PCkMODwo5ClgEKAxj8BxpzKnEIAhJtCiISIIUDdW/SuLoui8DAGLBRL6AMOZWtMbTdL93RWCqddbbcCiM6IQNq1ziP0CNzoxTWvpYSSoYH7reJn6WYK2y7zrkVnPd+zwoiEiCod48C6osZIM/h9xo+gmd9lWxnEprn8pD8NMBbgIBKEiDAhD1CBQiAztoDUgBaAGoLY2VsbGFyIGRvb3I=","b64Record":"CiUIFiIDGP0HKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjADYn2oi9H62zoxaUNaaZlec8axN1r9vbg2oevzle9tw0iQ5dHZ50P61HGVkKuGhbUaCwiI/f6sBhDSn+1OIg8KCQjM/P6sBhDpBxICGAIqIMODwq7DgsK3w4PCuXRGOMOCwq5Kw4PCi8ODwpDDg8KOMIDmihtC7wYKAxj9BxK6BGCAYEBSYAQ2EGEAP1dgADVg4ByAYxIGX+AUYQCPV4BjPM/WCxRhALpXgGNvZCNOFGEA0VeAY7a1XyUUYQEsV1szc///////////////////////////Fn/xsD9wi5w59FP+PwzvhBZMfW99+DbfB5bh6cK85u45fjRgQFGAgoFSYCABkVBQYEBRgJEDkKIAWzSAFWEAm1dgAID9W1BhAKRhAVpWW2BAUYCCgVJgIAGRUFBgQFGAkQOQ81s0gBVhAMZXYACA/VtQYQDPYQFiVlsAWzSAFWEA3VdgAID9W1BhASpgBIA2A2BAgRAVYQD0V2AAgP1bgQGQgIA1c///////////////////////////FpBgIAGQkpGQgDWQYCABkJKRkFBQUGEBq1ZbAFthAVhgBIA2A2AggRAVYQFCV2AAgP1bgQGQgIA1kGAgAZCSkZBQUFBhAfZWWwBbYABHkFCQVlszc///////////////////////////FmEI/EeQgRUCkGBAUWAAYEBRgIMDgYWIiPGTUFBQUBWAFWEBqFc9YACAPj1gAP1bUFZbgXP//////////////////////////xZhCPyCkIEVApBgQFFgAGBAUYCDA4GFiIjxk1BQUFAVgBVhAfFXPWAAgD49YAD9W1BQUFZbgDQUYQICV2AAgP1bUFb+omVienpyMVgg+PhPwxqEUGS1eB6QgxbzxZEVeWLeq7D9Qk7VTyVkAPlkc29sY0MABREAMiKAAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAogOowOgMY/QdKFgoUAAAAAAAAAAAAAAAAAAAAAAAAA/1yBwoDGP0HEAFSFgoJCgIYAhD/y5U2CgkKAhhiEIDMlTY="},{"b64Body":"Cg8KCQjM/P6sBhDrBxICGAISAhgDGOCssQMiAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjjoMCgMY/QcQoI0GGOgH","b64Record":"CiUIFiIDGP0HKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjCBj+DMrsPPyVn+6ZpI769E2YQ1pXHifIv/lgsp9sR5Ml+TUzTsS7SmpwsQlro3HPsaDAiI/f6sBhDNjczQAiIPCgkIzPz+rAYQ6wcSAhgCKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjjCA19oCOv0ECgMY/QcigAIEAAAAAAAAAACAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEAAAQAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIAAAAAAAAAAAAAAKIDxBDLuAgoDGP0HEoACBAAAAAAAAAAAgAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAAEAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAAAAAAAAAAAAAABog8bA/cIucOfRT/j8M74QWTH1vffg23weW4enCvObuOX4aIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACIiAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD6FIgCgkKAhgCEM+9tQUKCQoCGGIQgK61BQoICgMY/QcQ0A8="}]},"DepositSuccess":{"placeholderNum":1022,"encodedItems":[{"b64Body":"Cg8KCQjR/P6sBhD/BxICGAISAhgDGP7LzSwiAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjooBjwESDAiNy9mwBhDI4vjEARptCiISIAYAP6ns5XNJSv1ivZiXOcacuL0lq7DNzNZMim4KXLwRCiM6IQL+NW0cqcsHsxxYEjh7UNnrwX+OMv0K5jrtHmIG1X59UwoiEiC+ogkpFCk7SEgmyzlBxUyo9d4dE8myhaWpCPUzxBAG4iIMSGVsbG8gV29ybGQhKgAyAA==","b64Record":"CiUIFhoDGP8HKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjCoqhAc4Rsv1GNlHs66SsBn72SANG9KbyvcUTRMG0TWeO/7k1bEIsF+zDYu3BqxPZgaDAiN/f6sBhCiwLzUASIPCgkI0fz+rAYQ/wcSAhgCKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjlIA"},{"b64Body":"Cg8KCQjR/P6sBhCDCBICGAISAhgDGPa4sjEiAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjpoBogkKAxj/ByKaCTYwODA2MDQwNTI2MTAyM2E4MDYxMDAxMzYwMDAzOTYwMDBmM2ZlNjA4MDYwNDA1MjYwMDQzNjEwNjEwMDNmNTc2MDAwMzU2MGUwMWM4MDYzMTIwNjVmZTAxNDYxMDA4ZjU3ODA2MzNjY2ZkNjBiMTQ2MTAwYmE1NzgwNjM2ZjY0MjM0ZTE0NjEwMGQxNTc4MDYzYjZiNTVmMjUxNDYxMDEyYzU3NWIzMzczZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjE2N2ZmMWIwM2Y3MDhiOWMzOWY0NTNmZTNmMGNlZjg0MTY0YzdkNmY3ZGY4MzZkZjA3OTZlMWU5YzJiY2U2ZWUzOTdlMzQ2MDQwNTE4MDgyODE1MjYwMjAwMTkxNTA1MDYwNDA1MTgwOTEwMzkwYTIwMDViMzQ4MDE1NjEwMDliNTc2MDAwODBmZDViNTA2MTAwYTQ2MTAxNWE1NjViNjA0MDUxODA4MjgxNTI2MDIwMDE5MTUwNTA2MDQwNTE4MDkxMDM5MGYzNWIzNDgwMTU2MTAwYzY1NzYwMDA4MGZkNWI1MDYxMDBjZjYxMDE2MjU2NWIwMDViMzQ4MDE1NjEwMGRkNTc2MDAwODBmZDViNTA2MTAxMmE2MDA0ODAzNjAzNjA0MDgxMTAxNTYxMDBmNDU3NjAwMDgwZmQ1YjgxMDE5MDgwODAzNTczZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjE2OTA2MDIwMDE5MDkyOTE5MDgwMzU5MDYwMjAwMTkwOTI5MTkwNTA1MDUwNjEwMWFiNTY1YjAwNWI2MTAxNTg2MDA0ODAzNjAzNjAyMDgxMTAxNTYxMDE0MjU3NjAwMDgwZmQ1YjgxMDE5MDgwODAzNTkwNjAyMDAxOTA5MjkxOTA1MDUwNTA2MTAxZjY1NjViMDA1YjYwMDA0NzkwNTA5MDU2NWIzMzczZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjE2NjEwOGZjNDc5MDgxMTUwMjkwNjA0MDUxNjAwMDYwNDA1MTgwODMwMzgxODU4ODg4ZjE5MzUwNTA1MDUwMTU4MDE1NjEwMWE4NTczZDYwMDA4MDNlM2Q2MDAwZmQ1YjUwNTY1YjgxNzNmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmMTY2MTA4ZmM4MjkwODExNTAyOTA2MDQwNTE2MDAwNjA0MDUxODA4MzAzODE4NTg4ODhmMTkzNTA1MDUwNTAxNTgwMTU2MTAxZjE1NzNkNjAwMDgwM2UzZDYwMDBmZDViNTA1MDUwNTY1YjgwMzQxNDYxMDIwMjU3NjAwMDgwZmQ1YjUwNTZmZWEyNjU2MjdhN2E3MjMxNTgyMGY4Zjg0ZmMzMWE4NDUwNjRiNTc4MWU5MDgzMTZmM2M1OTExNTc5NjJkZWFiYjBmZDQyNGVkNTRmMjU2NDAwZjk2NDczNmY2YzYzNDMwMDA1MTEwMDMy","b64Record":"CiAIFiocCgwIARAMGgYIgK6ZpA8SDAgBEA8aBgiArpmkDxIw6OBKpIvlSofeU/I7SYO4aJUmqiEqAKaYVlG+14KEFbCBUf6AQCtXQvTTrvwg018/GgwIjf3+rAYQkZuE1gMiDwoJCNH8/qwGEIMIEgIYAiogw4PCrsOCwrfDg8K5dEY4w4LCrkrDg8KLw4PCkMODwo5SAA=="},{"b64Body":"Cg8KCQjS/P6sBhCFCBICGAISAhgDGK/Dto0EIgIIeDIgw4PCrsOCwrfDg8K5dEY4w4LCrkrDg8KLw4PCkMODwo5ClgEKAxj/BxpzKnEIAhJtCiISIBPzEbpn5/+irTzkz8wlR5ZAYS29KczyadpTFLTFc1CRCiM6IQPyAvlbUz5tpjDo/Th9cRPur9Kqcqy152zwZ+czoAP+SwoiEiBmz+k1btmzjL8Uon+Jn4+IUv5VisWN5WR8epeJQmGKliCQoQ9CBQiAztoDUgBaAGoLY2VsbGFyIGRvb3I=","b64Record":"CiUIFiIDGIAIKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjCdCNkMeHS54zftenI1NUBjz9hgfXCgBEf0u2ifdqeSY8H1mkHin2CSuQ7CJn43NiAaDAiO/f6sBhDK/4LeASIPCgkI0vz+rAYQhQgSAhgCKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjjDA2eIGQu8GCgMYgAgSugRggGBAUmAENhBhAD9XYAA1YOAcgGMSBl/gFGEAj1eAYzzP1gsUYQC6V4Bjb2QjThRhANFXgGO2tV8lFGEBLFdbM3P//////////////////////////xZ/8bA/cIucOfRT/j8M74QWTH1vffg23weW4enCvObuOX40YEBRgIKBUmAgAZFQUGBAUYCRA5CiAFs0gBVhAJtXYACA/VtQYQCkYQFaVltgQFGAgoFSYCABkVBQYEBRgJEDkPNbNIAVYQDGV2AAgP1bUGEAz2EBYlZbAFs0gBVhAN1XYACA/VtQYQEqYASANgNgQIEQFWEA9FdgAID9W4EBkICANXP//////////////////////////xaQYCABkJKRkIA1kGAgAZCSkZBQUFBhAatWWwBbYQFYYASANgNgIIEQFWEBQldgAID9W4EBkICANZBgIAGQkpGQUFBQYQH2VlsAW2AAR5BQkFZbM3P//////////////////////////xZhCPxHkIEVApBgQFFgAGBAUYCDA4GFiIjxk1BQUFAVgBVhAahXPWAAgD49YAD9W1BWW4Fz//////////////////////////8WYQj8gpCBFQKQYEBRYABgQFGAgwOBhYiI8ZNQUFBQFYAVYQHxVz1gAIA+PWAA/VtQUFBWW4A0FGECAldgAID9W1BW/qJlYnp6cjFYIPj4T8MahFBktXgekIMW88WRFXli3quw/UJO1U8lZAD5ZHNvbGNDAAURADIigAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKMCaDDoDGIAIShYKFAAAAAAAAAAAAAAAAAAAAAAAAAQAcgcKAxiACBABUhYKCQoCGAIQ/7LFDQoJCgIYYhCAs8UN"},{"b64Body":"Cg8KCQjS/P6sBhCHCBICGAISAhgDGOCssQMiAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjjoyCgMYgAgQoI0GGOgHIiS2tV8lAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA+g=","b64Record":"CiUIFiIDGIAIKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjDrn8Cj3xs/FgX1gexpw8wEO68SF6yManCvidYya74vIfKqxcLndZNdEvuE05q0LPYaCwiP/f6sBhCe3cACIg8KCQjS/P6sBhCHCBICGAIqIMODwq7DgsK3w4PCuXRGOMOCwq5Kw4PCi8ODwpDDg8KOMIDX2gI6jAIKAxiACCKAAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAogPEEUiAKCQoCGAIQz721BQoJCgIYYhCArrUFCggKAxiACBDQDw=="}]},"DepositDeleteSuccess":{"placeholderNum":1025,"encodedItems":[{"b64Body":"Cg8KCQjX/P6sBhCbCBICGAISAhgDGPuV9hQiAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjlouCiISILqlEt7vpKVphgUUqG34jiLSpodCkI7bZ6LnneC7b1TLENI9SgUIgM7aAw==","b64Record":"CiUIFhIDGIIIKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjCSU82s6//kLvbf74s4Gc9W4oREC4IZpnJiXnKGyGES4qrBTdJkizW3SFy1g9qDvSIaDAiT/f6sBhDn4qKAAyIPCgkI1/z+rAYQmwgSAhgCKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjlITCgcKAhgCEKN7CggKAxiCCBCkew=="},{"b64Body":"Cg8KCQjY/P6sBhCdCBICGAISAhgDGP7LzSwiAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjooBjgESCwiUy9mwBhD12et4Gm0KIhIgTIzpl8Zr4eMakwHQdPNq6fPUCZ3CU92/Y5wMKufjFesKIzohAh8ahCjBzJIkHvWGLA4/ANsKq5YoWyg6/qWMEvSwh66hCiISIFO1+cV/EsNltNqkklmpGP+JW+IzPvphQIvY4119ni8nIgxIZWxsbyBXb3JsZCEqADIA","b64Record":"CiUIFhoDGIMIKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjCyT80bpJ+UotLlaJ45ncyimIH22DLkRzSIUTAXCdpq4dGxEWSoMe9AWkQDH8ektFwaDAiU/f6sBhDw+6mIASIPCgkI2Pz+rAYQnQgSAhgCKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjlIA"},{"b64Body":"Cg8KCQjY/P6sBhChCBICGAISAhgDGPa4sjEiAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjpoBogkKAxiDCCKaCTYwODA2MDQwNTI2MTAyM2E4MDYxMDAxMzYwMDAzOTYwMDBmM2ZlNjA4MDYwNDA1MjYwMDQzNjEwNjEwMDNmNTc2MDAwMzU2MGUwMWM4MDYzMTIwNjVmZTAxNDYxMDA4ZjU3ODA2MzNjY2ZkNjBiMTQ2MTAwYmE1NzgwNjM2ZjY0MjM0ZTE0NjEwMGQxNTc4MDYzYjZiNTVmMjUxNDYxMDEyYzU3NWIzMzczZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjE2N2ZmMWIwM2Y3MDhiOWMzOWY0NTNmZTNmMGNlZjg0MTY0YzdkNmY3ZGY4MzZkZjA3OTZlMWU5YzJiY2U2ZWUzOTdlMzQ2MDQwNTE4MDgyODE1MjYwMjAwMTkxNTA1MDYwNDA1MTgwOTEwMzkwYTIwMDViMzQ4MDE1NjEwMDliNTc2MDAwODBmZDViNTA2MTAwYTQ2MTAxNWE1NjViNjA0MDUxODA4MjgxNTI2MDIwMDE5MTUwNTA2MDQwNTE4MDkxMDM5MGYzNWIzNDgwMTU2MTAwYzY1NzYwMDA4MGZkNWI1MDYxMDBjZjYxMDE2MjU2NWIwMDViMzQ4MDE1NjEwMGRkNTc2MDAwODBmZDViNTA2MTAxMmE2MDA0ODAzNjAzNjA0MDgxMTAxNTYxMDBmNDU3NjAwMDgwZmQ1YjgxMDE5MDgwODAzNTczZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjE2OTA2MDIwMDE5MDkyOTE5MDgwMzU5MDYwMjAwMTkwOTI5MTkwNTA1MDUwNjEwMWFiNTY1YjAwNWI2MTAxNTg2MDA0ODAzNjAzNjAyMDgxMTAxNTYxMDE0MjU3NjAwMDgwZmQ1YjgxMDE5MDgwODAzNTkwNjAyMDAxOTA5MjkxOTA1MDUwNTA2MTAxZjY1NjViMDA1YjYwMDA0NzkwNTA5MDU2NWIzMzczZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjE2NjEwOGZjNDc5MDgxMTUwMjkwNjA0MDUxNjAwMDYwNDA1MTgwODMwMzgxODU4ODg4ZjE5MzUwNTA1MDUwMTU4MDE1NjEwMWE4NTczZDYwMDA4MDNlM2Q2MDAwZmQ1YjUwNTY1YjgxNzNmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmMTY2MTA4ZmM4MjkwODExNTAyOTA2MDQwNTE2MDAwNjA0MDUxODA4MzAzODE4NTg4ODhmMTkzNTA1MDUwNTAxNTgwMTU2MTAxZjE1NzNkNjAwMDgwM2UzZDYwMDBmZDViNTA1MDUwNTY1YjgwMzQxNDYxMDIwMjU3NjAwMDgwZmQ1YjUwNTZmZWEyNjU2MjdhN2E3MjMxNTgyMGY4Zjg0ZmMzMWE4NDUwNjRiNTc4MWU5MDgzMTZmM2M1OTExNTc5NjJkZWFiYjBmZDQyNGVkNTRmMjU2NDAwZjk2NDczNmY2YzYzNDMwMDA1MTEwMDMy","b64Record":"CiAIFiocCgwIARAMGgYIgK6ZpA8SDAgBEA8aBgiArpmkDxIwzaeyEK3ytZ97BOcbyBjGSXCxPbxSDfKSMyafGDGoUijcqWaKoSQrI83kieq7pTssGgwIlP3+rAYQ0InbiQMiDwoJCNj8/qwGEKEIEgIYAiogw4PCrsOCwrfDg8K5dEY4w4LCrkrDg8KLw4PCkMODwo5SAA=="},{"b64Body":"Cg8KCQjZ/P6sBhCjCBICGAISAhgDGK/Dto0EIgIIeDIgw4PCrsOCwrfDg8K5dEY4w4LCrkrDg8KLw4PCkMODwo5ClgEKAxiDCBpzKnEIAhJtCiISILbiz9Ic0fNIHfxp7H+Hjb16A2WSeOoaMDsIAU1KaYtECiM6IQIWjrf1QOFQKrN9GGfcnBOqOkwvzHzTMUijNxsYt2b9/QoiEiBqfKDtumOh8ZWcCnF5wC+vyLZXlWnO/nllBP0/x8Pf1SCQoQ9CBQiAztoDUgBaAGoLY2VsbGFyIGRvb3I=","b64Record":"CiUIFiIDGIQIKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjBk1iNTqLpOpVSK9/C9PENMK4V9BLcoEnMIuHiFEwOctFV6rMIjQbrTwEtMiNxxwlgaDAiV/f6sBhCVqeGRASIPCgkI2fz+rAYQowgSAhgCKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjjDA2eIGQu8GCgMYhAgSugRggGBAUmAENhBhAD9XYAA1YOAcgGMSBl/gFGEAj1eAYzzP1gsUYQC6V4Bjb2QjThRhANFXgGO2tV8lFGEBLFdbM3P//////////////////////////xZ/8bA/cIucOfRT/j8M74QWTH1vffg23weW4enCvObuOX40YEBRgIKBUmAgAZFQUGBAUYCRA5CiAFs0gBVhAJtXYACA/VtQYQCkYQFaVltgQFGAgoFSYCABkVBQYEBRgJEDkPNbNIAVYQDGV2AAgP1bUGEAz2EBYlZbAFs0gBVhAN1XYACA/VtQYQEqYASANgNgQIEQFWEA9FdgAID9W4EBkICANXP//////////////////////////xaQYCABkJKRkIA1kGAgAZCSkZBQUFBhAatWWwBbYQFYYASANgNgIIEQFWEBQldgAID9W4EBkICANZBgIAGQkpGQUFBQYQH2VlsAW2AAR5BQkFZbM3P//////////////////////////xZhCPxHkIEVApBgQFFgAGBAUYCDA4GFiIjxk1BQUFAVgBVhAahXPWAAgD49YAD9W1BWW4Fz//////////////////////////8WYQj8gpCBFQKQYEBRYABgQFGAgwOBhYiI8ZNQUFBQFYAVYQHxVz1gAIA+PWAA/VtQUFBWW4A0FGECAldgAID9W1BW/qJlYnp6cjFYIPj4T8MahFBktXgekIMW88WRFXli3quw/UJO1U8lZAD5ZHNvbGNDAAURADIigAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKMCaDDoDGIQIShYKFAAAAAAAAAAAAAAAAAAAAAAAAAQEcgcKAxiECBABUhYKCQoCGAIQ/7LFDQoJCgIYYhCAs8UN"},{"b64Body":"Cg8KCQjZ/P6sBhClCBICGAISAhgDGOCssQMiAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjjoyCgMYhAgQoI0GGOgHIiS2tV8lAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA+g=","b64Record":"CiUIFiIDGIQIKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjC7AdX8mydRLUyvND7uG6UENqUK4YSl8a3pJvLgWxv0xX/CfwwARGCmRGMrWAlATU0aDAiV/f6sBhCihbGTAyIPCgkI2fz+rAYQpQgSAhgCKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjjCA19oCOowCCgMYhAgigAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKIDxBFIgCgkKAhgCEM+9tQUKCQoCGGIQgK61BQoICgMYhAgQ0A8="},{"b64Body":"Cg8KCQja/P6sBhCnCBICGAISAhgDGPyuhQgiAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjrIBCgoDGIQIEgMYggg=","b64Record":"CiUIFiIDGIQIKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjDeCcVtFR1MBDSQUtTNnkJsYGq/Y6ZQHRyC6d+zE7ll2G1QiHcT3yDCUhFwsQmoDPYaDAiW/f6sBhCxnoSbASIPCgkI2vz+rAYQpwgSAhgCKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjlIUCggKAxiCCBDQDwoICgMYhAgQzw8="}]},"MultipleDepositSuccess":{"placeholderNum":1033,"encodedItems":[{"b64Body":"Cg8KCQji/P6sBhDPCBICGAISAhgDGP7LzSwiAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjooBjwESDAiey9mwBhCH6uqZAhptCiISIGASthiNpw1mrEAN2R55r+0b4bOCb3R/U3bAdMUMw7sqCiM6IQM0P+kzRvbkvNWKPLgFIb6h0JOb4t2svXtRsUGSVXMFjgoiEiCFcMrehbbxfdt6Nw+P+0hiGFZsWYGH8OyGqD4UhEYPpiIMSGVsbG8gV29ybGQhKgAyAA==","b64Record":"CiUIFhoDGIoIKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjAzy0YCHK4Jg85rdZDrrCSOfWfHwYJIQYNMpMynQvMjNF2X5ZViVa4qoLFPyH0L/HkaDAie/f6sBhCfmcSgAiIPCgkI4vz+rAYQzwgSAhgCKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjlIA"},{"b64Body":"Cg8KCQjj/P6sBhDTCBICGAISAhgDGPa4sjEiAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjpoBogkKAxiKCCKaCTYwODA2MDQwNTI2MTAyM2E4MDYxMDAxMzYwMDAzOTYwMDBmM2ZlNjA4MDYwNDA1MjYwMDQzNjEwNjEwMDNmNTc2MDAwMzU2MGUwMWM4MDYzMTIwNjVmZTAxNDYxMDA4ZjU3ODA2MzNjY2ZkNjBiMTQ2MTAwYmE1NzgwNjM2ZjY0MjM0ZTE0NjEwMGQxNTc4MDYzYjZiNTVmMjUxNDYxMDEyYzU3NWIzMzczZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjE2N2ZmMWIwM2Y3MDhiOWMzOWY0NTNmZTNmMGNlZjg0MTY0YzdkNmY3ZGY4MzZkZjA3OTZlMWU5YzJiY2U2ZWUzOTdlMzQ2MDQwNTE4MDgyODE1MjYwMjAwMTkxNTA1MDYwNDA1MTgwOTEwMzkwYTIwMDViMzQ4MDE1NjEwMDliNTc2MDAwODBmZDViNTA2MTAwYTQ2MTAxNWE1NjViNjA0MDUxODA4MjgxNTI2MDIwMDE5MTUwNTA2MDQwNTE4MDkxMDM5MGYzNWIzNDgwMTU2MTAwYzY1NzYwMDA4MGZkNWI1MDYxMDBjZjYxMDE2MjU2NWIwMDViMzQ4MDE1NjEwMGRkNTc2MDAwODBmZDViNTA2MTAxMmE2MDA0ODAzNjAzNjA0MDgxMTAxNTYxMDBmNDU3NjAwMDgwZmQ1YjgxMDE5MDgwODAzNTczZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjE2OTA2MDIwMDE5MDkyOTE5MDgwMzU5MDYwMjAwMTkwOTI5MTkwNTA1MDUwNjEwMWFiNTY1YjAwNWI2MTAxNTg2MDA0ODAzNjAzNjAyMDgxMTAxNTYxMDE0MjU3NjAwMDgwZmQ1YjgxMDE5MDgwODAzNTkwNjAyMDAxOTA5MjkxOTA1MDUwNTA2MTAxZjY1NjViMDA1YjYwMDA0NzkwNTA5MDU2NWIzMzczZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjE2NjEwOGZjNDc5MDgxMTUwMjkwNjA0MDUxNjAwMDYwNDA1MTgwODMwMzgxODU4ODg4ZjE5MzUwNTA1MDUwMTU4MDE1NjEwMWE4NTczZDYwMDA4MDNlM2Q2MDAwZmQ1YjUwNTY1YjgxNzNmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmMTY2MTA4ZmM4MjkwODExNTAyOTA2MDQwNTE2MDAwNjA0MDUxODA4MzAzODE4NTg4ODhmMTkzNTA1MDUwNTAxNTgwMTU2MTAxZjE1NzNkNjAwMDgwM2UzZDYwMDBmZDViNTA1MDUwNTY1YjgwMzQxNDYxMDIwMjU3NjAwMDgwZmQ1YjUwNTZmZWEyNjU2MjdhN2E3MjMxNTgyMGY4Zjg0ZmMzMWE4NDUwNjRiNTc4MWU5MDgzMTZmM2M1OTExNTc5NjJkZWFiYjBmZDQyNGVkNTRmMjU2NDAwZjk2NDczNmY2YzYzNDMwMDA1MTEwMDMy","b64Record":"CiAIFiocCgwIARAMGgYIgK6ZpA8SDAgBEA8aBgiArpmkDxIwn6aHgLtjzyI/AB4dcz51O5bSCU/j5A6Ub85UfOmc0xoIzptniaU/87Sp+CFb9cgMGgsIn/3+rAYQ7JK3RSIPCgkI4/z+rAYQ0wgSAhgCKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjlIA"},{"b64Body":"Cg8KCQjj/P6sBhDVCBICGAISAhgDGK/Dto0EIgIIeDIgw4PCrsOCwrfDg8K5dEY4w4LCrkrDg8KLw4PCkMODwo5ClgEKAxiKCBpzKnEIAhJtCiISIHJVm5Qs/bTKJUpOj7t0KkXO7T9vxSbPQFeCrLj1alATCiM6IQNA90mY1LCdl86IJepYxvy+wjf3OVBJYA/pnKlz6PwyewoiEiD7YKJ3sjy8Q5X+nUfJQR8Tu4wdXK1V1U/1viU11MXQdyCQoQ9CBQiAztoDUgBaAGoLY2VsbGFyIGRvb3I=","b64Record":"CiUIFiIDGIsIKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjASRDIfLq1Ax7uy3U5xcIJhcZRC7DhjxCT3hok14su/3BG1C0PjASPQPsHlzO1/n/MaDAif/f6sBhCl8ouqAiIPCgkI4/z+rAYQ1QgSAhgCKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjjDA2eIGQu8GCgMYiwgSugRggGBAUmAENhBhAD9XYAA1YOAcgGMSBl/gFGEAj1eAYzzP1gsUYQC6V4Bjb2QjThRhANFXgGO2tV8lFGEBLFdbM3P//////////////////////////xZ/8bA/cIucOfRT/j8M74QWTH1vffg23weW4enCvObuOX40YEBRgIKBUmAgAZFQUGBAUYCRA5CiAFs0gBVhAJtXYACA/VtQYQCkYQFaVltgQFGAgoFSYCABkVBQYEBRgJEDkPNbNIAVYQDGV2AAgP1bUGEAz2EBYlZbAFs0gBVhAN1XYACA/VtQYQEqYASANgNgQIEQFWEA9FdgAID9W4EBkICANXP//////////////////////////xaQYCABkJKRkIA1kGAgAZCSkZBQUFBhAatWWwBbYQFYYASANgNgIIEQFWEBQldgAID9W4EBkICANZBgIAGQkpGQUFBQYQH2VlsAW2AAR5BQkFZbM3P//////////////////////////xZhCPxHkIEVApBgQFFgAGBAUYCDA4GFiIjxk1BQUFAVgBVhAahXPWAAgD49YAD9W1BWW4Fz//////////////////////////8WYQj8gpCBFQKQYEBRYABgQFGAgwOBhYiI8ZNQUFBQFYAVYQHxVz1gAIA+PWAA/VtQUFBWW4A0FGECAldgAID9W1BW/qJlYnp6cjFYIPj4T8MahFBktXgekIMW88WRFXli3quw/UJO1U8lZAD5ZHNvbGNDAAURADIigAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKMCaDDoDGIsIShYKFAAAAAAAAAAAAAAAAAAAAAAAAAQLcgcKAxiLCBABUhYKCQoCGAIQ/7LFDQoJCgIYYhCAs8UN"},{"b64Body":"Cg8KCQjk/P6sBhDXCBICGAISAhgDGOCssQMiAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjjoyCgMYiwgQoI0GGOgHIiS2tV8lAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA+g=","b64Record":"CiUIFiIDGIsIKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjCZSE74DbfZ7bgJ3Kr+AMOz0ynQqrzWO4jsgl7p6gU1MVFcqikv1Pj5fg9TKEsR+jkaCwig/f6sBhCPm4xPIg8KCQjk/P6sBhDXCBICGAIqIMODwq7DgsK3w4PCuXRGOMOCwq5Kw4PCi8ODwpDDg8KOMIDX2gI6jAIKAxiLCCKAAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAogPEEUiAKCQoCGAIQz721BQoJCgIYYhCArrUFCggKAxiLCBDQDw=="},{"b64Body":"Cg8KCQjk/P6sBhDZCBICGAISAhgDGOCssQMiAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjjoyCgMYiwgQoI0GGOgHIiS2tV8lAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA+g=","b64Record":"CiUIFiIDGIsIKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjDlFuB3/d39SOEzuTD8AXKlBTn+c4eFo5RRTyhWww0cGxr7AL07iEB12vhkFuzNw0gaDAig/f6sBhDQqsTQAiIPCgkI5Pz+rAYQ2QgSAhgCKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjjCA19oCOowCCgMYiwgigAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKIDxBFIgCgkKAhgCEM+9tQUKCQoCGGIQgK61BQoICgMYiwgQ0A8="},{"b64Body":"Cg8KCQjl/P6sBhDbCBICGAISAhgDGOCssQMiAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjjoyCgMYiwgQoI0GGOgHIiS2tV8lAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA+g=","b64Record":"CiUIFiIDGIsIKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjDsy3HZA5KV5QM8hx4LDL2GXxH/MN2idp9FJSvuI8YfFuZOugl3dbtj8K640BZjnVkaCwih/f6sBhCfudJYIg8KCQjl/P6sBhDbCBICGAIqIMODwq7DgsK3w4PCuXRGOMOCwq5Kw4PCi8ODwpDDg8KOMIDX2gI6jAIKAxiLCCKAAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAogPEEUiAKCQoCGAIQz721BQoJCgIYYhCArrUFCggKAxiLCBDQDw=="},{"b64Body":"Cg8KCQjl/P6sBhDdCBICGAISAhgDGOCssQMiAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjjoyCgMYiwgQoI0GGOgHIiS2tV8lAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA+g=","b64Record":"CiUIFiIDGIsIKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjCt7MbgVWDmOY3cTUmN9c6e3QtWNJsv9WX3MMAkTzirr94Vj0O5QcETka6z1X3OFEIaDAih/f6sBhCNpYXaAiIPCgkI5fz+rAYQ3QgSAhgCKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjjCA19oCOowCCgMYiwgigAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKIDxBFIgCgkKAhgCEM+9tQUKCQoCGGIQgK61BQoICgMYiwgQ0A8="},{"b64Body":"Cg8KCQjm/P6sBhDfCBICGAISAhgDGOCssQMiAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjjoyCgMYiwgQoI0GGOgHIiS2tV8lAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA+g=","b64Record":"CiUIFiIDGIsIKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjA1E5WAaj7pGzciqjkHfQ85ryNwbCt15AzzRIECGgHt3x8x+CyQ5KMr5Ea/3ORrQaAaCwii/f6sBhDc/P1hIg8KCQjm/P6sBhDfCBICGAIqIMODwq7DgsK3w4PCuXRGOMOCwq5Kw4PCi8ODwpDDg8KOMIDX2gI6jAIKAxiLCCKAAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAogPEEUiAKCQoCGAIQz721BQoJCgIYYhCArrUFCggKAxiLCBDQDw=="},{"b64Body":"Cg8KCQjm/P6sBhDhCBICGAISAhgDGOCssQMiAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjjoyCgMYiwgQoI0GGOgHIiS2tV8lAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA+g=","b64Record":"CiUIFiIDGIsIKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjBY19mdGtF3OV92JYNA7rXFvvvCSny5dpUd0gMjyM62CKkoLoRKR6DncdjVZxHyx3MaDAii/f6sBhCBqbDjAiIPCgkI5vz+rAYQ4QgSAhgCKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjjCA19oCOowCCgMYiwgigAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKIDxBFIgCgkKAhgCEM+9tQUKCQoCGGIQgK61BQoICgMYiwgQ0A8="},{"b64Body":"Cg8KCQjn/P6sBhDjCBICGAISAhgDGOCssQMiAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjjoyCgMYiwgQoI0GGOgHIiS2tV8lAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA+g=","b64Record":"CiUIFiIDGIsIKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjA2qCq8qoiTu/EO301cCDXowS/7BkYi1GUBISoR8BKSsTNk2Q1gvLF+cnPm2CYYokUaCwij/f6sBhCs6rZrIg8KCQjn/P6sBhDjCBICGAIqIMODwq7DgsK3w4PCuXRGOMOCwq5Kw4PCi8ODwpDDg8KOMIDX2gI6jAIKAxiLCCKAAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAogPEEUiAKCQoCGAIQz721BQoJCgIYYhCArrUFCggKAxiLCBDQDw=="},{"b64Body":"Cg8KCQjn/P6sBhDlCBICGAISAhgDGOCssQMiAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjjoyCgMYiwgQoI0GGOgHIiS2tV8lAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA+g=","b64Record":"CiUIFiIDGIsIKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjBIs6eBduRFO5XDV/Sz9vFOQE/tEuE4gnx+x6FF235xSbjW7YvX7GHKV2hRQzt0/9waDAij/f6sBhCig4LtAiIPCgkI5/z+rAYQ5QgSAhgCKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjjCA19oCOowCCgMYiwgigAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKIDxBFIgCgkKAhgCEM+9tQUKCQoCGGIQgK61BQoICgMYiwgQ0A8="},{"b64Body":"Cg8KCQjo/P6sBhDnCBICGAISAhgDGOCssQMiAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjjoyCgMYiwgQoI0GGOgHIiS2tV8lAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA+g=","b64Record":"CiUIFiIDGIsIKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjCa3+yYg/jMfW+5nVu2Rtgz6aL/NOOal2Ochx2xh8s6apygHudU2pheDo9TdYT4TSIaCwik/f6sBhDo7oJ1Ig8KCQjo/P6sBhDnCBICGAIqIMODwq7DgsK3w4PCuXRGOMOCwq5Kw4PCi8ODwpDDg8KOMIDX2gI6jAIKAxiLCCKAAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAogPEEUiAKCQoCGAIQz721BQoJCgIYYhCArrUFCggKAxiLCBDQDw=="},{"b64Body":"Cg8KCQjo/P6sBhDpCBICGAISAhgDGOCssQMiAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjjoyCgMYiwgQoI0GGOgHIiS2tV8lAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA+g=","b64Record":"CiUIFiIDGIsIKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjCKMTQgaoOA9THza6z+fgm5dXPsZ0PqN9U9BMP1MZOcP/GU5JtjCYqut14tF12KYKkaDAik/f6sBhCGltv2AiIPCgkI6Pz+rAYQ6QgSAhgCKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjjCA19oCOowCCgMYiwgigAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKIDxBFIgCgkKAhgCEM+9tQUKCQoCGGIQgK61BQoICgMYiwgQ0A8="}]},"payTestSelfDestructCall":{"placeholderNum":1036,"encodedItems":[{"b64Body":"Cg8KCQjt/P6sBhD5CBICGAISAhgDGPuV9hQiAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjloyCiISINruqmOQrxyDRCIoHMX+TQ5/64bru7824N+tdpnIzobJEICglKWNHUoFCIDO2gM=","b64Record":"CiUIFhIDGI0IKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjDS/K4bwlZp49OukfIu2HYAetdPHLUiBr3ZRl60G/RhIWgvpO1gipTlu2dTHZnLLjEaDAip/f6sBhDarpreASIPCgkI7fz+rAYQ+QgSAhgCKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjlIbCgsKAhgCEP+/qMqaOgoMCgMYjQgQgMCoypo6"},{"b64Body":"Cg8KCQjt/P6sBhD7CBICGAISAhgDGPuV9hQiAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjlouCiISIIPFcWOlu7AXHFQtdgBR4WGiHr7D/dfpuCK86o/29aQSEOgHSgUIgM7aAw==","b64Record":"CiUIFhIDGI4IKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjD0Sr4MeKBrcMgG/gAKicjZV8PCJUsmuXYub+AiI1UuxLqYRfNjvfZcJvb75OUsqtkaDAip/f6sBhDPtfDCAyIPCgkI7fz+rAYQ+wgSAhgCKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjlITCgcKAhgCEM8PCggKAxiOCBDQDw=="},{"b64Body":"Cg8KCQju/P6sBhD9CBICGAISAhgDGP7LzSwiAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjooBjwESDAiqy9mwBhCl26fQARptCiISIIrsAUu9T0Aj2OCrera3Gb9pduNj+rubrLlDiM0XA3eACiM6IQM0zmnaQaWUMK7lZ44nwQAe28nYwT9c94CLl25+yjcBDAoiEiB+7Z48w9AuxdoT2XowVZNzlJ2lPlKcKSALl2qi3uzLzyIMSGVsbG8gV29ybGQhKgAyAA==","b64Record":"CiUIFhoDGI8IKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjBhO5z4VpHeEw8mIcuT36o7OtHtbGx211hC+92gmr6AueoXKCrh89Ksb2//TNjK+7IaDAiq/f6sBhCmvdbnASIPCgkI7vz+rAYQ/QgSAhgCKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjlIA"},{"b64Body":"Cg8KCQju/P6sBhCBCRICGAISAhgDGKu53TMiAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjpoBug0KAxiPCCKyDTYwODA2MDQwNTIzNDgwMTU2MTAwMTA1NzYwMDA4MGZkNWI1MDYxMDMzOTgwNjEwMDIwNjAwMDM5NjAwMGYzZmU2MDgwNjA0MDUyNjAwNDM2MTA2MTAwNzI1NzYwMDAzNTdjMDEwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDkwMDQ4MDYzMTIwNjVmZTAxNDYxMDA3NzU3ODA2MzNjY2ZkNjBiMTQ2MTAwYTI1NzgwNjM2ZjY0MjM0ZTE0NjEwMGI5NTc4MDYzODE0MmU4ZjYxNDYxMDExNDU3ODA2MzliOTZlZWNlMTQ2MTAxNjU1NzgwNjNiNmI1NWYyNTE0NjEwMWNhNTc1YjYwMDA4MGZkNWIzNDgwMTU2MTAwODM1NzYwMDA4MGZkNWI1MDYxMDA4YzYxMDFmODU2NWI2MDQwNTE4MDgyODE1MjYwMjAwMTkxNTA1MDYwNDA1MTgwOTEwMzkwZjM1YjM0ODAxNTYxMDBhZTU3NjAwMDgwZmQ1YjUwNjEwMGI3NjEwMjE3NTY1YjAwNWIzNDgwMTU2MTAwYzU1NzYwMDA4MGZkNWI1MDYxMDExMjYwMDQ4MDM2MDM2MDQwODExMDE1NjEwMGRjNTc2MDAwODBmZDViODEwMTkwODA4MDM1NzNmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmMTY5MDYwMjAwMTkwOTI5MTkwODAzNTkwNjAyMDAxOTA5MjkxOTA1MDUwNTA2MTAyNzc1NjViMDA1YjM0ODAxNTYxMDEyMDU3NjAwMDgwZmQ1YjUwNjEwMTYzNjAwNDgwMzYwMzYwMjA4MTEwMTU2MTAxMzc1NzYwMDA4MGZkNWI4MTAxOTA4MDgwMzU3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNjkwNjAyMDAxOTA5MjkxOTA1MDUwNTA2MTAyYzI1NjViMDA1YjM0ODAxNTYxMDE3MTU3NjAwMDgwZmQ1YjUwNjEwMWI0NjAwNDgwMzYwMzYwMjA4MTEwMTU2MTAxODg1NzYwMDA4MGZkNWI4MTAxOTA4MDgwMzU3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNjkwNjAyMDAxOTA5MjkxOTA1MDUwNTA2MTAyZGI1NjViNjA0MDUxODA4MjgxNTI2MDIwMDE5MTUwNTA2MDQwNTE4MDkxMDM5MGYzNWI2MTAxZjY2MDA0ODAzNjAzNjAyMDgxMTAxNTYxMDFlMDU3NjAwMDgwZmQ1YjgxMDE5MDgwODAzNTkwNjAyMDAxOTA5MjkxOTA1MDUwNTA2MTAyZmM1NjViMDA1YjYwMDAzMDczZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjE2MzE5MDUwOTA1NjViMzM3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNjYxMDhmYzMwNzNmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmMTYzMTkwODExNTAyOTA2MDQwNTE2MDAwNjA0MDUxODA4MzAzODE4NTg4ODhmMTkzNTA1MDUwNTAxNTgwMTU2MTAyNzQ1NzNkNjAwMDgwM2UzZDYwMDBmZDViNTA1NjViODE3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNjYxMDhmYzgyOTA4MTE1MDI5MDYwNDA1MTYwMDA2MDQwNTE4MDgzMDM4MTg1ODg4OGYxOTM1MDUwNTA1MDE1ODAxNTYxMDJiZDU3M2Q2MDAwODAzZTNkNjAwMGZkNWI1MDUwNTA1NjViODA3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNmZmNWI2MDAwODE3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNjMxOTA1MDkxOTA1MDU2NWI4MDM0MTQxNTE1NjEwMzBhNTc2MDAwODBmZDViNTA1NmZlYTE2NTYyN2E3YTcyMzA1ODIwMjFjNGI1NzE5NTI2OGZmMGNlMWU5MGIwOGM3ZTkxYmU0MTg0MjA5OTMzZmY0ZTcwYjYyZTY5ZDM4OTU2OGQ5YTAwMjk=","b64Record":"CiAIFiocCgwIARAMGgYIgK6ZpA8SDAgBEA8aBgiArpmkDxIwnWa4ObzH/SPcUxDuOI7HT9QLiRdtgD4iOFnhrOVXx/a5oQtMySpua7QS93NzunbMGgwIqv3+rAYQy/iYzAMiDwoJCO78/qwGEIEJEgIYAiogw4PCrsOCwrfDg8K5dEY4w4LCrkrDg8KLw4PCkMODwo5SAA=="},{"b64Body":"Cg8KCQjv/P6sBhCDCRICGAISAhgDGJb7rp0CIgIIeDIgw4PCrsOCwrfDg8K5dEY4w4LCrkrDg8KLw4PCkMODwo5CRQoDGI8IGiISIODAfZfeLSoW/zIAu41WJfDHVSMgDWtTeiERc3az2CS1IJChD0IFCIDO2gNSAFoAagtjZWxsYXIgZG9vcg==","b64Record":"CiUIFiIDGJAIKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjBqg7t79KBuT5/VAy/JFJ23sXgVuVjAfT5GXNUuQX2ek7PTm6c5Mbocbjviv4UzU94aDAir/f6sBhDyqPTwASIPCgkI7/z+rAYQgwkSAhgCKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjjDA2eIGQu4ICgMYkAgSuQZggGBAUmAENhBhAHJXYAA1fAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAkASAYxIGX+AUYQB3V4BjPM/WCxRhAKJXgGNvZCNOFGEAuVeAY4FC6PYUYQEUV4Bjm5buzhRhAWVXgGO2tV8lFGEByldbYACA/Vs0gBVhAINXYACA/VtQYQCMYQH4VltgQFGAgoFSYCABkVBQYEBRgJEDkPNbNIAVYQCuV2AAgP1bUGEAt2ECF1ZbAFs0gBVhAMVXYACA/VtQYQESYASANgNgQIEQFWEA3FdgAID9W4EBkICANXP//////////////////////////xaQYCABkJKRkIA1kGAgAZCSkZBQUFBhAndWWwBbNIAVYQEgV2AAgP1bUGEBY2AEgDYDYCCBEBVhATdXYACA/VuBAZCAgDVz//////////////////////////8WkGAgAZCSkZBQUFBhAsJWWwBbNIAVYQFxV2AAgP1bUGEBtGAEgDYDYCCBEBVhAYhXYACA/VuBAZCAgDVz//////////////////////////8WkGAgAZCSkZBQUFBhAttWW2BAUYCCgVJgIAGRUFBgQFGAkQOQ81thAfZgBIA2A2AggRAVYQHgV2AAgP1bgQGQgIA1kGAgAZCSkZBQUFBhAvxWWwBbYAAwc///////////////////////////FjGQUJBWWzNz//////////////////////////8WYQj8MHP//////////////////////////xYxkIEVApBgQFFgAGBAUYCDA4GFiIjxk1BQUFAVgBVhAnRXPWAAgD49YAD9W1BWW4Fz//////////////////////////8WYQj8gpCBFQKQYEBRYABgQFGAgwOBhYiI8ZNQUFBQFYAVYQK9Vz1gAIA+PWAA/VtQUFBWW4Bz//////////////////////////8W/1tgAIFz//////////////////////////8WMZBQkZBQVluANBQVFWEDCldgAID9W1BW/qFlYnp6cjBYICHEtXGVJo/wzh6QsIx+kb5BhCCZM/9OcLYuadOJVo2aACkigAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKMCaDDoDGJAIShYKFAAAAAAAAAAAAAAAAAAAAAAAAAQQcgcKAxiQCBABUhYKCQoCGAIQ/7LFDQoJCgIYYhCAs8UN"},{"b64Body":"ChAKCQjv/P6sBhCFCRIDGI0IEgIYAxighpQKIgIIeDIgw4PCrsOCwrfDg8K5dEY4w4LCrkrDg8KLw4PCkMODwo46MgoDGJAIEOCnEhjoByIktrVfJQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPo","b64Record":"CiUIFiIDGJAIKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjCfQ7aFYNqGlX4739iiCihog6AEoMfP/16aU7QbqM5s8PcqZ/bR3qQyrDZO6zQht98aDAir/f6sBhCPsefVAyIQCgkI7/z+rAYQhQkSAxiNCCogw4PCrsOCwrfDg8K5dEY4w4LCrkrDg8KLw4PCkMODwo4wgIWQCDqMAgoDGJAIIoACAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACiA0w5SIQoJCgIYYhCAiqAQCgoKAxiNCBDPmaAQCggKAxiQCBDQDw=="},{"b64Body":"ChAKCQjw/P6sBhCHCRIDGI0IEgIYAxighpQKIgIIeDIgw4PCrsOCwrfDg8K5dEY4w4LCrkrDg8KLw4PCkMODwo46DwoDGJAIEOCnEiIEEgZf4A==","b64Record":"CiUIFiIDGJAIKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjDGnZx19e5dpyalQwovTShnWATyyBWu05pgH0UgyBDN0pShf7Etl1Q0OxtlgYra+tgaDAis/f6sBhDjjtP6ASIQCgkI8Pz+rAYQhwkSAxiNCCogw4PCrsOCwrfDg8K5dEY4w4LCrkrDg8KLw4PCkMODwo4wgIWQCDquAgoDGJAIEiAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD6CKAAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAogNMOUhcKCQoCGGIQgIqgEAoKCgMYjQgQ/4mgEA=="},{"b64Body":"ChAKCQjw/P6sBhCJCRIDGI0IEgIYAxighpQKIgIIeDIgw4PCrsOCwrfDg8K5dEY4w4LCrkrDg8KLw4PCkMODwo46LwoDGJAIEOCnEiIkgULo9gAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQQ","b64Record":"CiUITSIDGJAIKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjBLVph7uxqvM8RPcGqDTpCZ+jT+4jXIsGwB8Q/4bSNPQ9NyxbD8jLa7ZRJKxWREgrYaCwit/f6sBhCb/NQCIhAKCQjw/P6sBhCJCRIDGI0IKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjjCghpQKOhsaFVNFTEZfREVTVFJVQ1RfVE9fU0VMRijgpxJSFwoJCgIYYhDAjKgUCgoKAxiNCBC/jKgU"},{"b64Body":"ChAKCQjx/P6sBhCLCRIDGI0IEgIYAxighpQKIgIIeDIgw4PCrsOCwrfDg8K5dEY4w4LCrkrDg8KLw4PCkMODwo46LwoDGJAIEOCnEiIkgULo9gAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA","b64Record":"CiUIHSIDGJAIKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjBDQKH/G/9HkbjQo68W+GJ+7BqesEVkfNwRaLkxncdWhA9vXgZGts5c3ZnBgYQSX5UaDAit/f6sBhCBxZ+EAiIQCgkI8fz+rAYQiwkSAxiNCCogw4PCrsOCwrfDg8K5dEY4w4LCrkrDg8KLw4PCkMODwo4woIaUCjoeGhhJTlZBTElEX1NPTElESVRZX0FERFJFU1Mo4KcSUhcKCQoCGGIQwIyoFAoKCgMYjQgQv4yoFA=="},{"b64Body":"ChAKCQjy/P6sBhCNCRIDGI0IEgIYAxighpQKIgIIeDIgw4PCrsOCwrfDg8K5dEY4w4LCrkrDg8KLw4PCkMODwo46LwoDGJAIEOCnEiIkgULo9gAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQO","b64Record":"CiUIFiIDGJAIKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjA6x9RFsbqvHPezyYKc8wZD6WLWQuYzgGYQ7vDe6aeXHmfYtKz/udqBjm3pOFTAwM4aCwiu/f6sBhDgzpYMIhAKCQjy/P6sBhCNCRIDGI0IKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjjCAhZAIOowCCgMYkAgigAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKIDTDlIrCgkKAhhiEICKoBAKCgoDGI0IEP+JoBAKCAoDGI4IENAPCggKAxiQCBDPDw=="}]},"MultipleSelfDestructsAreSafe":{"placeholderNum":1041,"encodedItems":[{"b64Body":"Cg8KCQj2/P6sBhClCRICGAISAhgDGP7LzSwiAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjooBjwESDAiyy9mwBhCukNvkAhptCiISICF7W4ycBpdQCr8sSUe3cQAlx3DOoVTUBEO7rrgD2ZBcCiM6IQMwiDCkUcg5HS5E2oi0SIFAXjHa4cqk5+h5fDJI6C1QzgoiEiBfTS3TX+YO5t60JoWG8aHnuAovkHtoieoEsRlbEB5qMCIMSGVsbG8gV29ybGQhKgAyAA==","b64Record":"CiUIFhoDGJIIKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjAUul5dQ+Pe3XdIePKetojPXpf6DO2Z8PWPla7lbgjxz/je/ZrY/jZEHLj2jBJ37VwaDAiy/f6sBhCa7JLtAiIPCgkI9vz+rAYQpQkSAhgCKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjlIA"},{"b64Body":"Cg8KCQj3/P6sBhCpCRICGAISAhgDGILlpzoiAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjpoBoBkKAxiSCCKYGTYwODA2MDQwNTIzNDgwMTU2MTAwMTA1NzYwMDA4MGZkNWI1MDMwNjAwMDYwNDA1MTYxMDAyMDkwNjEwMjViNTY1YjgwODM3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNjczZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjE2ODE1MjYwMjAwMTgyNjNmZmZmZmZmZjE2NjNmZmZmZmZmZjE2ODE1MjYwMjAwMTkyNTA1MDUwNjA0MDUxODA5MTAzOTA2MDAwZjA4MDE1ODAxNTYxMDA4NTU3M2Q2MDAwODAzZTNkNjAwMGZkNWI1MDYwMDA4MDYwMDM4MTEwNjEwMDkzNTdmZTViMDE2MDAwNjEwMTAwMGE4MTU0ODE3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYwMjE5MTY5MDgzNzNmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmMTYwMjE3OTA1NTUwMzA2MDAxNjA0MDUxNjEwMGUxOTA2MTAyNWI1NjViODA4MzczZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjE2NzNmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmMTY4MTUyNjAyMDAxODI2M2ZmZmZmZmZmMTY2M2ZmZmZmZmZmMTY4MTUyNjAyMDAxOTI1MDUwNTA2MDQwNTE4MDkxMDM5MDYwMDBmMDgwMTU4MDE1NjEwMTQ2NTczZDYwMDA4MDNlM2Q2MDAwZmQ1YjUwNjAwMDYwMDE2MDAzODExMDYxMDE1NTU3ZmU1YjAxNjAwMDYxMDEwMDBhODE1NDgxNzNmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmMDIxOTE2OTA4MzczZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjE2MDIxNzkwNTU1MDMwNjAwMjYwNDA1MTYxMDFhMzkwNjEwMjViNTY1YjgwODM3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNjczZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjE2ODE1MjYwMjAwMTgyNjNmZmZmZmZmZjE2NjNmZmZmZmZmZjE2ODE1MjYwMjAwMTkyNTA1MDUwNjA0MDUxODA5MTAzOTA2MDAwZjA4MDE1ODAxNTYxMDIwODU3M2Q2MDAwODAzZTNkNjAwMGZkNWI1MDYwMDA2MDAyNjAwMzgxMTA2MTAyMTc1N2ZlNWIwMTYwMDA2MTAxMDAwYTgxNTQ4MTczZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjAyMTkxNjkwODM3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNjAyMTc5MDU1NTA2MTAyNjg1NjViNjEwMWI4ODA2MTA0OTQ4MzM5MDE5MDU2NWI2MTAyMWQ4MDYxMDI3NzYwMDAzOTYwMDBmM2ZlNjA4MDYwNDA1MjM0ODAxNTYxMDAxMDU3NjAwMDgwZmQ1YjUwNjAwNDM2MTA2MTAwMmI1NzYwMDAzNTYwZTAxYzgwNjM2NGRiZTc4YzE0NjEwMDMwNTc1YjYwMDA4MGZkNWI2MTAwMzg2MTAwM2E1NjViMDA1YjYwMDA4MDYwMDM4MTEwNjEwMDQ3NTdmZTViMDE2MDAwOTA1NDkwNjEwMTAwMGE5MDA0NzNmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmMTY3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNjYzYjhiM2RiYzY2MDQwNTE4MTYzZmZmZmZmZmYxNjYwZTAxYjgxNTI2MDA0MDE2MDAwNjA0MDUxODA4MzAzODE2MDAwODc4MDNiMTU4MDE1NjEwMGIwNTc2MDAwODBmZDViNTA1YWYxMTU4MDE1NjEwMGM0NTczZDYwMDA4MDNlM2Q2MDAwZmQ1YjUwNTA1MDUwNjAwMDYwMDE2MDAzODExMDYxMDBkNjU3ZmU1YjAxNjAwMDkwNTQ5MDYxMDEwMDBhOTAwNDczZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjE2NzNmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmMTY2M2I4YjNkYmM2NjA0MDUxODE2M2ZmZmZmZmZmMTY2MGUwMWI4MTUyNjAwNDAxNjAwMDYwNDA1MTgwODMwMzgxNjAwMDg3ODAzYjE1ODAxNTYxMDEzZjU3NjAwMDgwZmQ1YjUwNWFmMTE1ODAxNTYxMDE1MzU3M2Q2MDAwODAzZTNkNjAwMGZkNWI1MDUwNTA1MDYwMDA2MDAyNjAwMzgxMTA2MTAxNjU1N2ZlNWIwMTYwMDA5MDU0OTA2MTAxMDAwYTkwMDQ3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNjczZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjE2NjNiOGIzZGJjNjYwNDA1MTgxNjNmZmZmZmZmZjE2NjBlMDFiODE1MjYwMDQwMTYwMDA2MDQwNTE4MDgzMDM4MTYwMDA4NzgwM2IxNTgwMTU2MTAxY2U1NzYwMDA4MGZkNWI1MDVhZjExNTgwMTU2MTAxZTI1NzNkNjAwMDgwM2UzZDYwMDBmZDViNTA1MDUwNTA1NmZlYTI2NTYyN2E3YTcyMzE1ODIwNmI1ODg3NjljODIzYmQ0MjMyMzRjYTY3MjI2MTdjOTE1NTkzNzc4NzczMjg5ZTVlYTY5NTdmNmIzMDgzYTcyNjY0NzM2ZjZjNjM0MzAwMDUxMTAwMzI2MDgwNjA0MDUyMzQ4MDE1NjEwMDEwNTc2MDAwODBmZDViNTA2MDQwNTE2MTAxYjgzODAzODA2MTAxYjg4MzM5ODE4MTAxNjA0MDUyNjA0MDgxMTAxNTYxMDAzMzU3NjAwMDgwZmQ1YjgxMDE5MDgwODA1MTkwNjAyMDAxOTA5MjkxOTA4MDUxOTA2MDIwMDE5MDkyOTE5MDUwNTA1MDgxNjAwMDgwNjEwMTAwMGE4MTU0ODE3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYwMjE5MTY5MDgzNzNmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmMTYwMjE3OTA1NTUwODA2MDAwNjAxNDYxMDEwMDBhODE1NDgxNjNmZmZmZmZmZjAyMTkxNjkwODM2M2ZmZmZmZmZmMTYwMjE3OTA1NTUwNTA1MDYwZjk4MDYxMDBiZjYwMDAzOTYwMDBmM2ZlNjA4MDYwNDA1MjM0ODAxNTYwMGY1NzYwMDA4MGZkNWI1MDYwMDQzNjEwNjAyODU3NjAwMDM1NjBlMDFjODA2M2I4YjNkYmM2MTQ2MDJkNTc1YjYwMDA4MGZkNWI2MDMzNjAzNTU2NWIwMDViN2ZlZmVkZTI2ODc2NDE2MzM5YjBhNzg5ZGEzNTE3Njk0N2JkZDMwMmI4ZWE5ZDQ5NWRjZDZjMDg0ZWMzZDFjYzllNjAwMDYwMTQ5MDU0OTA2MTAxMDAwYTkwMDQ2M2ZmZmZmZmZmMTY2MDQwNTE4MDgyNjNmZmZmZmZmZjE2NjNmZmZmZmZmZjE2ODE1MjYwMjAwMTkxNTA1MDYwNDA1MTgwOTEwMzkwYTE2MDAwODA5MDU0OTA2MTAxMDAwYTkwMDQ3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNjczZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjE2ZmZmZWEyNjU2MjdhN2E3MjMxNTgyMGJiNGExZTEzMzZjMzRkNDc2NTQ3MWU5ODkzNDgxMDM3MTVhYzI0Nzk5NmU5YWM2ODYwZjRlYTQ1OWE5YjJiMDg2NDczNmY2YzYzNDMwMDA1MTEwMDMy","b64Record":"CiAIFiocCgwIARAMGgYIgK6ZpA8SDAgBEA8aBgiArpmkDxIwEgNEDG1K9OQIg/377F1PYHeFW+fSjcBYPut1LAK1ZpBytf05ju1Gx4r9o9DaKcVnGgwIs/3+rAYQkKjvkQEiDwoJCPf8/qwGEKkJEgIYAiogw4PCrsOCwrfDg8K5dEY4w4LCrkrDg8KLw4PCkMODwo5SAA=="},{"b64Body":"Cg8KCQj3/P6sBhCrCRICGAISAhgDGJb7rp0CIgIIeDIgw4PCrsOCwrfDg8K5dEY4w4LCrkrDg8KLw4PCkMODwo5CRQoDGJIIGiISIJFCFo5PTUCeYZhbbmhwviuwySk/4DdpHTn88GJQrpYXIMDPJEIFCIDO2gNSAFoAagtjZWxsYXIgZG9vcg==","b64Record":"CiUIFiIDGJMIKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjDILGgKiU+jXyl1a9ILUbgMY6PxT5KS2P6pXbsSbvtcW3fkllitCgT+xWZpWniVvUkaDAiz/f6sBhDM1tP2AiIPCgkI9/z+rAYQqwkSAhgCKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjjCAiqAQQvwGCgMYkwgSnQRggGBAUjSAFWEAEFdgAID9W1BgBDYQYQArV2AANWDgHIBjZNvnjBRhADBXW2AAgP1bYQA4YQA6VlsAW2AAgGADgRBhAEdX/lsBYACQVJBhAQAKkARz//////////////////////////8Wc///////////////////////////FmO4s9vGYEBRgWP/////FmDgG4FSYAQBYABgQFGAgwOBYACHgDsVgBVhALBXYACA/VtQWvEVgBVhAMRXPWAAgD49YAD9W1BQUFBgAGABYAOBEGEA1lf+WwFgAJBUkGEBAAqQBHP//////////////////////////xZz//////////////////////////8WY7iz28ZgQFGBY/////8WYOAbgVJgBAFgAGBAUYCDA4FgAIeAOxWAFWEBP1dgAID9W1Ba8RWAFWEBU1c9YACAPj1gAP1bUFBQUGAAYAJgA4EQYQFlV/5bAWAAkFSQYQEACpAEc///////////////////////////FnP//////////////////////////xZjuLPbxmBAUYFj/////xZg4BuBUmAEAWAAYEBRgIMDgWAAh4A7FYAVYQHOV2AAgP1bUFrxFYAVYQHiVz1gAIA+PWAA/VtQUFBQVv6iZWJ6enIxWCBrWIdpyCO9QjI0ymciYXyRVZN3h3Monl6mlX9rMIOnJmRzb2xjQwAFEQAyIoACAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACiAph06AxiTCDoDGJQIOgMYlQg6AxiWCEoWChQAAAAAAAAAAAAAAAAAAAAAAAAEE3IHCgMYkwgQBHIHCgMYlAgQAXIHCgMYlQgQAXIHCgMYlggQAVIWCgkKAhgCEP+TwCAKCQoCGGIQgJTAIA=="},{"b64Body":"ChEKCQj3/P6sBhCrCRICGAIgAUI4GiISIJFCFo5PTUCeYZhbbmhwviuwySk/4DdpHTn88GJQrpYXQgUIgM7aA2oLY2VsbGFyIGRvb3I=","b64Record":"CgcIFiIDGJQIEjCwU4yvcctZBZYl0IyWFF+pExYJzlMbE8X5izaUqnzhp0w5QXYcRQEhJfIbIiBZO80aDAiz/f6sBhDN1tP2AiIRCgkI9/z+rAYQqwkSAhgCIAFCHQoDGJQIShYKFK1F9n0CA4WiHZ2y2TOU8ZsOPJyxUgB6DAiz/f6sBhDM1tP2Ag=="},{"b64Body":"ChEKCQj3/P6sBhCrCRICGAIgAkI4GiISIJFCFo5PTUCeYZhbbmhwviuwySk/4DdpHTn88GJQrpYXQgUIgM7aA2oLY2VsbGFyIGRvb3I=","b64Record":"CgcIFiIDGJUIEjB7vlOYBYuT8wQZclPI20YDS8pwZLBocEqfd5ob3GWtZfGiq0nnTOYwvMZKL9KUWi0aDAiz/f6sBhDO1tP2AiIRCgkI9/z+rAYQqwkSAhgCIAJCHQoDGJUIShYKFFAclXKVzmaHxj05dnr8vcvYeP5HUgB6DAiz/f6sBhDM1tP2Ag=="},{"b64Body":"ChEKCQj3/P6sBhCrCRICGAIgA0I4GiISIJFCFo5PTUCeYZhbbmhwviuwySk/4DdpHTn88GJQrpYXQgUIgM7aA2oLY2VsbGFyIGRvb3I=","b64Record":"CgcIFiIDGJYIEjC9wOMGgKhWwifGUej7RjZksAXRpCirYPnlKCAJNqpx1Ni2HpijfFdoCH+m8Z21RPkaDAiz/f6sBhDP1tP2AiIRCgkI9/z+rAYQqwkSAhgCIANCHQoDGJYIShYKFK3lJKuCKlZ6yirQUpZ5uLY8anyyUgB6DAiz/f6sBhDM1tP2Ag=="},{"b64Body":"Cg8KCQj4/P6sBhCtCRICGAISAhgDGOCssQMiAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjjoPCgMYkwgQoI0GIgRk2+eM","b64Record":"CiUIFiIDGJMIKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjCu11NtpgJO8WSB0BRDI3cOXAG+s110Fg4Jxm4yov1NOvAjc3/fk4LT3YrSNYqWB6saDAi0/f6sBhCt17abASIPCgkI+Pz+rAYQrQkSAhgCKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjjCA19oCOvkJCgMYkwgigAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAAAAAAAAAEAAAAIAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAABAAAAAAAAAgAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAgAAAAAAAAAAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKIDxBDLMAgoDGJQIEoACAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgAAAAAAAABAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABog7+3iaHZBYzmwp4naNRdpR73TArjqnUldzWwITsPRzJ4iIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMswCCgMYlQgSgAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAAAAAAAAAAAAAAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgAAAAAAAAAAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGiDv7eJodkFjObCnido1F2lHvdMCuOqdSV3NbAhOw9HMniIgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEyzAIKAxiWCBKAAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEAAAAAAAACAAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAACAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAaIO/t4mh2QWM5sKeJ2jUXaUe90wK46p1JXc1sCE7D0cyeIiAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAlIWCgkKAhgCEP+ttQUKCQoCGGIQgK61BQ=="}]},"smartContractInlineAssemblyCheck":{"placeholderNum":1047,"encodedItems":[{"b64Body":"Cg8KCQj9/P6sBhDBCRICGAISAhgDGPuV9hQiAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjlozCiISIIy6Le62LSErrYq5D38B7w1KuxywAgoC2s9Q+r7dHAo4EIDAyvOEowJKBQiAztoD","b64Record":"CiUIFhIDGJgIKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjCAoYg4g6UN/r30Vwl2z4U0wJw2qprpVB/+Gll8xlDGjBLzYV78v5HjGmx6WgRscoEaCwi5/f6sBhCv+/ojIg8KCQj9/P6sBhDBCRICGAIqIMODwq7DgsK3w4PCuXRGOMOCwq5Kw4PCi8ODwpDDg8KOUh0KDAoCGAIQ//+U54nGBAoNCgMYmAgQgICV54nGBA=="},{"b64Body":"Cg8KCQj9/P6sBhDDCRICGAISAhgDGP7LzSwiAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjooBjwESDAi5y9mwBhCGy52TAhptCiISIEELz0z8/OOcTK9BAOBdFzfvQSDsxfwkArn4GkXFttq5CiM6IQLf+FCRjCg9hgtundTYdvvBCUk541DbXci6IY6SjSNtqAoiEiAxZ90mpIv7d9Fu3LT7OG3TYK3l06oDuphk40FCPIL1qyIMSGVsbG8gV29ybGQhKgAyAA==","b64Record":"CiUIFhoDGJkIKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjAfhLEG1LMqc46MVDxLsT+wwO35uBa0XcVLudlo5In9b+eiShKqWbrsz8bHCtuKbrwaDAi5/f6sBhDI49mlAiIPCgkI/fz+rAYQwwkSAhgCKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjlIA"},{"b64Body":"Cg8KCQj+/P6sBhDHCRICGAISAhgDGKGwpi4iAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjpoB3AMKAxiZCCLUAzYwODA2MDQwNTI2MDBmNjAwMDU1MzQ4MDE1NjEwMDE1NTc2MDAwODBmZDViNTA2MGM2ODA2MTAwMjQ2MDAwMzk2MDAwZjNmZTYwODA2MDQwNTIzNDgwMTU2MDBmNTc2MDAwODBmZDViNTA2MDA0MzYxMDYwMzI1NzYwMDAzNTYwZTAxYzgwNjM2MGZlNDdiMTE0NjAzNzU3ODA2MzZkNGNlNjNjMTQ2MDYyNTc1YjYwMDA4MGZkNWI2MDYwNjAwNDgwMzYwMzYwMjA4MTEwMTU2MDRiNTc2MDAwODBmZDViODEwMTkwODA4MDM1OTA2MDIwMDE5MDkyOTE5MDUwNTA1MDYwN2U1NjViMDA1YjYwNjg2MDg4NTY1YjYwNDA1MTgwODI4MTUyNjAyMDAxOTE1MDUwNjA0MDUxODA5MTAzOTBmMzViODA2MDAwODE5MDU1NTA1MDU2NWI2MDAwODA1NDkwNTA5MDU2ZmVhMjY1NjI3YTdhNzIzMTU4MjAyODIzMzhiZmVmZDNmOTk5ZGYxYzA3MjBjODViN2M3OTMzNTFlNTk3NWJiNDk1OTkyZjEyMTY5MjZlZWE4OTE1NjQ3MzZmNmM2MzQzMDAwNTBiMDAzMg==","b64Record":"CiAIFiocCgwIARAMGgYIgK6ZpA8SDAgBEA8aBgiArpmkDxIw5oCt1Frfplmlx52hguv4OC4+6K6upkAc5g4JMWmx0Dxle0U+1808GooMhFG67xvNGgsIuv3+rAYQ/8TcLSIPCgkI/vz+rAYQxwkSAhgCKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjlIA"},{"b64Body":"Cg8KCQj+/P6sBhDJCRICGAISAhgDGP7LzSwiAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjooBjwESDAi6y9mwBhCr++OcAhptCiISIKNN+UTxdf2itrU+/GX4ervMcSBKJD4ebs2ZxmQB3CzvCiM6IQLsnlGVnw54sSAyl2tJ3qx7oG0T7mFNBbXkFH/Bq1p5CQoiEiAW8yuNyiAKs9tKeGUEbcQ/dURSLmj7f7Hyr3FGr4NvoiIMSGVsbG8gV29ybGQhKgAyAA==","b64Record":"CiUIFhoDGJoIKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjB7TSgvKXF6aXZDTUjUXBPHCYIzOPHerjmkAnDkTMmwkYInp3pmfMasXycpUqXwJ5kaDAi6/f6sBhCbz/2uAiIPCgkI/vz+rAYQyQkSAhgCKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjlIA"},{"b64Body":"Cg8KCQj//P6sBhDNCRICGAISAhgDGM+WmC8iAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjpoBqAUKAxiaCCKgBTYwODA2MDQwNTIzNDgwMTU2MTAwMTA1NzYwMDA4MGZkNWI1MDYxMDEzMDgwNjEwMDIwNjAwMDM5NjAwMGYzZmU2MDgwNjA0MDUyMzQ4MDE1NjAwZjU3NjAwMDgwZmQ1YjUwNjAwNDM2MTA2MDNjNTc2MDAwMzU2MGUwMWM4MDYzYjUxYzRmOTYxNDYwNDE1NzgwNjNjMjcyMmVjYzE0NjA5NjU3ODA2M2U3MTQ4Y2MzMTQ2MGIyNTc1YjYwMDA4MGZkNWI2MDgwNjAwNDgwMzYwMzYwMjA4MTEwMTU2MDU1NTc2MDAwODBmZDViODEwMTkwODA4MDM1NzNmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmMTY5MDYwMjAwMTkwOTI5MTkwNTA1MDUwNjBkZDU2NWI2MDQwNTE4MDgyODE1MjYwMjAwMTkxNTA1MDYwNDA1MTgwOTEwMzkwZjM1YjYwOWM2MGU4NTY1YjYwNDA1MTgwODI4MTUyNjAyMDAxOTE1MDUwNjA0MDUxODA5MTAzOTBmMzViNjBkYjYwMDQ4MDM2MDM2MDIwODExMDE1NjBjNjU3NjAwMDgwZmQ1YjgxMDE5MDgwODAzNTkwNjAyMDAxOTA5MjkxOTA1MDUwNTA2MGYxNTY1YjAwNWI2MDAwODEzYjkwNTA5MTkwNTA1NjViNjAwMDgwNTQ5MDUwOTA1NjViODA2MDAwODE5MDU1NTA1MDU2ZmVhMjY1NjI3YTdhNzIzMTU4MjA0ZWFkOGM3NzBiYjRiYmY0MDBhM2I0NmEyMTYyMWU3ZWU1MmJmNThlYjkxMDJkM2E0NmY2NjEyZTRlMTA3Y2I4NjQ3MzZmNmM2MzQzMDAwNTBiMDAzMg==","b64Record":"CiAIFiocCgwIARAMGgYIgK6ZpA8SDAgBEA8aBgiArpmkDxIwcGeiiXPT8PBIXSVjaXofYgp3TaQOxbU7+zJ1iE/Ycj60mwRgj1RZOynUXViqXvOnGgsIu/3+rAYQnqiINyIPCgkI//z+rAYQzQkSAhgCKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjlIA"},{"b64Body":"Cg8KCQj//P6sBhDPCRICGAISAhgDGJb7rp0CIgIIeDIgw4PCrsOCwrfDg8K5dEY4w4LCrkrDg8KLw4PCkMODwo5CRQoDGJkIGiISICHTT7QdjDsrgrlo3b+AceH71lhTp5l3UJ8gCQAGM/fwIJChD0IFCIDO2gNSAFoAagtjZWxsYXIgZG9vcg==","b64Record":"CiUIFiIDGJsIKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjAFG1kCI+Udm/QyndzD5l9untMCz4AvLtUWL7QTIvNxIqcwuO4/vwtsc5YKOzjIH7AaDAi7/f6sBhDEueC4AiIPCgkI//z+rAYQzwkSAhgCKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjjDA2eIGQvsDCgMYmwgSxgFggGBAUjSAFWAPV2AAgP1bUGAENhBgMldgADVg4ByAY2D+R7EUYDdXgGNtTOY8FGBiV1tgAID9W2BgYASANgNgIIEQFWBLV2AAgP1bgQGQgIA1kGAgAZCSkZBQUFBgflZbAFtgaGCIVltgQFGAgoFSYCABkVBQYEBRgJEDkPNbgGAAgZBVUFBWW2AAgFSQUJBW/qJlYnp6cjFYICgjOL/v0/mZ3xwHIMhbfHkzUeWXW7SVmS8SFpJu6okVZHNvbGNDAAULADIigAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKMCaDDoDGJsIShYKFAAAAAAAAAAAAAAAAAAAAAAAAAQbcgcKAxibCBABUhYKCQoCGAIQ/7LFDQoJCgIYYhCAs8UN"},{"b64Body":"Cg8KCQiA/f6sBhDRCRICGAISAhgDGJb7rp0CIgIIeDIgw4PCrsOCwrfDg8K5dEY4w4LCrkrDg8KLw4PCkMODwo5CRQoDGJoIGiISIIo5cgMvigWsSKB9oMxETrHQNvx6RFnBtii8kgfZRUrLIJChD0IFCIDO2gNSAFoAagtjZWxsYXIgZG9vcg==","b64Record":"CiUIFiIDGJwIKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjARWz4uDXQYcl+06PMxH5gzgIzU9wfmtcA/+GvSajwVyPhtK/crbjp17+2W58afZFEaCwi8/f6sBhDV/99AIg8KCQiA/f6sBhDRCRICGAIqIMODwq7DgsK3w4PCuXRGOMOCwq5Kw4PCi8ODwpDDg8KOMMDZ4gZC5QQKAxicCBKwAmCAYEBSNIAVYA9XYACA/VtQYAQ2EGA8V2AANWDgHIBjtRxPlhRgQVeAY8JyLswUYJZXgGPnFIzDFGCyV1tgAID9W2CAYASANgNgIIEQFWBVV2AAgP1bgQGQgIA1c///////////////////////////FpBgIAGQkpGQUFBQYN1WW2BAUYCCgVJgIAGRUFBgQFGAkQOQ81tgnGDoVltgQFGAgoFSYCABkVBQYEBRgJEDkPNbYNtgBIA2A2AggRAVYMZXYACA/VuBAZCAgDWQYCABkJKRkFBQUGDxVlsAW2AAgTuQUJGQUFZbYACAVJBQkFZbgGAAgZBVUFBW/qJlYnp6cjFYIE6tjHcLtLv0AKO0aiFiHn7lK/WOuRAtOkb2YS5OEHy4ZHNvbGNDAAULADIigAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKMCaDDoDGJwIShYKFAAAAAAAAAAAAAAAAAAAAAAAAAQccgcKAxicCBABUhYKCQoCGAIQ/7LFDQoJCgIYYhCAs8UN"}]},"ocToken":{"placeholderNum":1053,"encodedItems":[{"b64Body":"Cg8KCQiE/f6sBhDzCRICGAISAhgDGPuV9hQiAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjloyCiISIC7Q45jtfsCLflF9PfqgqX3S4uPsJlV+I+A1iyXz83QAEICglKWNHUoFCIDO2gM=","b64Record":"CiUIFhIDGJ4IKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjBP/G9+O9dHbAa6AOSKW5ha20Wf8yod8u5zaTjTsGW+znTE+QBYCQ6uCA7bU9/XIaAaDAjA/f6sBhCxgZu+AyIPCgkIhP3+rAYQ8wkSAhgCKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjlIbCgsKAhgCEP+/qMqaOgoMCgMYnggQgMCoypo6"},{"b64Body":"ChAKCQiF/f6sBhD1CRIDGJ4IEgIYAxj7lfYUIgIIeDIgw4PCrsOCwrfDg8K5dEY4w4LCrkrDg8KLw4PCkMODwo5aMQoiEiDaIH5TTu8vj7wtYDJ0HsGLuGfRTYVg6qDgnDH16HekpRCAyK+gJUoFCIDO2gM=","b64Record":"CiUIFhIDGJ8IKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjDzFmRhLzxesVvN6Whdc0UIJj66+gk3o39lLmGazyRkuVnvZElpqvtKUqZzHW5kljIaDAjB/f6sBhDTtpjGASIQCgkIhf3+rAYQ9QkSAxieCCogw4PCrsOCwrfDg8K5dEY4w4LCrkrDg8KLw4PCkMODwo4w+5X2FFJICgkKAhgDEKyPgwEKCQoCGGIQ8pbUIAoKCgMYoAYQ7MKKBAoKCgMYoQYQ7MKKBAoLCgMYnggQ9bvL6koKCwoDGJ8IEICQ38BK"},{"b64Body":"ChAKCQiF/f6sBhD3CRIDGJ4IEgIYAxj7lfYUIgIIeDIgw4PCrsOCwrfDg8K5dEY4w4LCrkrDg8KLw4PCkMODwo5aMQoiEiAxYE976UQSdQkh5KPhjOw1O/rl8V7pmFzP3BBlVydBqhCAyK+gJUoFCIDO2gM=","b64Record":"CiUIFhIDGKAIKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjCJeIbl5lCu/t8ggxur378BG3zKTl+7JVeTlz4Esen0fOakLEC7VMsCmuoMNLbI7swaDAjB/f6sBhCcrMfHAyIQCgkIhf3+rAYQ9wkSAxieCCogw4PCrsOCwrfDg8K5dEY4w4LCrkrDg8KLw4PCkMODwo4w+5X2FFJICgkKAhgDEKyPgwEKCQoCGGIQ8pbUIAoKCgMYoAYQ7MKKBAoKCgMYoQYQ7MKKBAoLCgMYnggQ9bvL6koKCwoDGKAIEICQ38BK"},{"b64Body":"ChAKCQiG/f6sBhD5CRIDGJ4IEgIYAxj7lfYUIgIIeDIgw4PCrsOCwrfDg8K5dEY4w4LCrkrDg8KLw4PCkMODwo5aMQoiEiACuM/hocQNG7MvCB0Ml49iJEHSqdnH0gNslFjfWsTvGRCAyK+gJUoFCIDO2gM=","b64Record":"CiUIFhIDGKEIKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjBvFHCxxtBFR8wQrhPn9c5LRXt/3Ck0dvZl6N+P2n6xzjDWceTITr6RRZsJS6wh4A4aDAjC/f6sBhCQ9MXPASIQCgkIhv3+rAYQ+QkSAxieCCogw4PCrsOCwrfDg8K5dEY4w4LCrkrDg8KLw4PCkMODwo4w+5X2FFJICgkKAhgDEKyPgwEKCQoCGGIQ8pbUIAoKCgMYoAYQ7MKKBAoKCgMYoQYQ7MKKBAoLCgMYnggQ9bvL6koKCwoDGKEIEICQ38BK"},{"b64Body":"ChAKCQiG/f6sBhD7CRIDGJ4IEgIYAxj7lfYUIgIIeDIgw4PCrsOCwrfDg8K5dEY4w4LCrkrDg8KLw4PCkMODwo5aMQoiEiCKM73ZIL/7ehBaSjE1pyUXgEmnglOX5Whl0bl4lUkZ8xCAyK+gJUoFCIDO2gM=","b64Record":"CiUIFhIDGKIIKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjC4S8O+X2cvVCrv4INVH1Kui5iLWiTKztrxJ2XAmFiwK0ebPoLnVm7wvdITY5Q4COQaDAjC/f6sBhDvmanRAyIQCgkIhv3+rAYQ+wkSAxieCCogw4PCrsOCwrfDg8K5dEY4w4LCrkrDg8KLw4PCkMODwo4w+5X2FFJICgkKAhgDEKyPgwEKCQoCGGIQ8pbUIAoKCgMYoAYQ7MKKBAoKCgMYoQYQ7MKKBAoLCgMYnggQ9bvL6koKCwoDGKIIEICQ38BK"},{"b64Body":"Cg8KCQiH/f6sBhCbChICGAISAhgDGP7LzSwiAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjooBjwESDAjDy9mwBhD96d3lARptCiISIKJwBl6PPBfoTg2D7qy2cr998impdJ7mlKi9YHSqsCa5CiM6IQIo8ITM3fhHSsW/zZqgNExaMrR7LdrYlfhRGs/oymnpqwoiEiChG2v7ZFbnoQjpw4aGSiImKeCFOpfjN/fYzEyj6PvSeCIMSGVsbG8gV29ybGQhKgAyAA==","b64Record":"CiUIFhoDGKMIKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjDfbB83XM+ulI755rp7eNsiiDUb+YZp7cWfYoA9rKYL97wyBuAgThtRAQTaisFp9e0aDAjD/f6sBhCx+eD1ASIPCgkIh/3+rAYQmwoSAhgCKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjlIA"},{"b64Body":"Cg8KCQiH/f6sBhCfChICGAISAhgDGIydjj4iAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjpoBiCAKAxijCCKAIDYwNjA2MDQwNTI2MDAyODA1NDYwZmYxOTE2NjAwMzE3OTA1NTM0MTU2MTAwMWM1NzYwMDA4MGZkNWI2MDQwNTE2MTBhMWYzODAzODA2MTBhMWY4MzM5ODEwMTYwNDA1MjgwODA1MTkxOTA2MDIwMDE4MDUxODIwMTkxOTA2MDIwMDE4MDUxNjAwMjU0NjBmZjE2NjAwYTBhODUwMjYwMDM4MTkwNTU2MDAxNjBhMDYwMDIwYTAzMzMxNjYwMDA5MDgxNTI2MDA0NjAyMDUyNjA0MDgxMjA5MTkwOTE1NTkyMDE5MTkwNTA4MjgwNTE2MTAwODQ5MjkxNjAyMDAxOTA2MTAwYTE1NjViNTA2MDAxODE4MDUxNjEwMDk4OTI5MTYwMjAwMTkwNjEwMGExNTY1YjUwNTA1MDUwNjEwMTNjNTY1YjgyODA1NDYwMDE4MTYwMDExNjE1NjEwMTAwMDIwMzE2NjAwMjkwMDQ5MDYwMDA1MjYwMjA2MDAwMjA5MDYwMWYwMTYwMjA5MDA0ODEwMTkyODI2MDFmMTA2MTAwZTI1NzgwNTE2MGZmMTkxNjgzODAwMTE3ODU1NTYxMDEwZjU2NWI4MjgwMDE2MDAxMDE4NTU1ODIxNTYxMDEwZjU3OTE4MjAxNWI4MjgxMTExNTYxMDEwZjU3ODI1MTgyNTU5MTYwMjAwMTkxOTA2MDAxMDE5MDYxMDBmNDU2NWI1MDYxMDExYjkyOTE1MDYxMDExZjU2NWI1MDkwNTY1YjYxMDEzOTkxOTA1YjgwODIxMTE1NjEwMTFiNTc2MDAwODE1NTYwMDEwMTYxMDEyNTU2NWI5MDU2NWI2MTA4ZDQ4MDYxMDE0YjYwMDAzOTYwMDBmMzAwNjA2MDYwNDA1MjYwMDQzNjEwNjEwMGI5NTc2M2ZmZmZmZmZmN2MwMTAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwNjAwMDM1MDQxNjYzMDZmZGRlMDM4MTE0NjEwMGJlNTc4MDYzMDk1ZWE3YjMxNDYxMDE0ODU3ODA2MzE4MTYwZGRkMTQ2MTAxN2U1NzgwNjMyM2I4NzJkZDE0NjEwMWEzNTc4MDYzMzEzY2U1NjcxNDYxMDFjYjU3ODA2MzQyOTY2YzY4MTQ2MTAxZjQ1NzgwNjM3MGEwODIzMTE0NjEwMjBhNTc4MDYzNzljYzY3OTAxNDYxMDIyOTU3ODA2Mzk1ZDg5YjQxMTQ2MTAyNGI1NzgwNjNhOTA1OWNiYjE0NjEwMjVlNTc4MDYzY2FlOWNhNTExNDYxMDI4MjU3ODA2M2RkNjJlZDNlMTQ2MTAyZTc1NzViNjAwMDgwZmQ1YjM0MTU2MTAwYzk1NzYwMDA4MGZkNWI2MTAwZDE2MTAzMGM1NjViNjA0MDUxNjAyMDgwODI1MjgxOTA4MTAxODM4MTgxNTE4MTUyNjAyMDAxOTE1MDgwNTE5MDYwMjAwMTkwODA4MzgzNjAwMDViODM4MTEwMTU2MTAxMGQ1NzgwODIwMTUxODM4MjAxNTI2MDIwMDE2MTAwZjU1NjViNTA1MDUwNTA5MDUwOTA4MTAxOTA2MDFmMTY4MDE1NjEwMTNhNTc4MDgyMDM4MDUxNjAwMTgzNjAyMDAzNjEwMTAwMGEwMzE5MTY4MTUyNjAyMDAxOTE1MDViNTA5MjUwNTA1MDYwNDA1MTgwOTEwMzkwZjM1YjM0MTU2MTAxNTM1NzYwMDA4MGZkNWI2MTAxNmE2MDAxNjBhMDYwMDIwYTAzNjAwNDM1MTY2MDI0MzU2MTAzYWE1NjViNjA0MDUxOTAxNTE1ODE1MjYwMjAwMTYwNDA1MTgwOTEwMzkwZjM1YjM0MTU2MTAxODk1NzYwMDA4MGZkNWI2MTAxOTE2MTAzZGE1NjViNjA0MDUxOTA4MTUyNjAyMDAxNjA0MDUxODA5MTAzOTBmMzViMzQxNTYxMDFhZTU3NjAwMDgwZmQ1YjYxMDE2YTYwMDE2MGEwNjAwMjBhMDM2MDA0MzU4MTE2OTA2MDI0MzUxNjYwNDQzNTYxMDNlMDU2NWIzNDE1NjEwMWQ2NTc2MDAwODBmZDViNjEwMWRlNjEwNDU3NTY1YjYwNDA1MTYwZmY5MDkxMTY4MTUyNjAyMDAxNjA0MDUxODA5MTAzOTBmMzViMzQxNTYxMDFmZjU3NjAwMDgwZmQ1YjYxMDE2YTYwMDQzNTYxMDQ2MDU2NWIzNDE1NjEwMjE1NTc2MDAwODBmZDViNjEwMTkxNjAwMTYwYTA2MDAyMGEwMzYwMDQzNTE2NjEwNGViNTY1YjM0MTU2MTAyMzQ1NzYwMDA4MGZkNWI2MTAxNmE2MDAxNjBhMDYwMDIwYTAzNjAwNDM1MTY2MDI0MzU2MTA0ZmQ1NjViMzQxNTYxMDI1NjU3NjAwMDgwZmQ1YjYxMDBkMTYxMDVkOTU2NWIzNDE1NjEwMjY5NTc2MDAwODBmZDViNjEwMjgwNjAwMTYwYTA2MDAyMGEwMzYwMDQzNTE2NjAyNDM1NjEwNjQ0NTY1YjAwNWIzNDE1NjEwMjhkNTc2MDAwODBmZDViNjEwMTZhNjAwNDgwMzU2MDAxNjBhMDYwMDIwYTAzMTY5MDYwMjQ4MDM1OTE5MDYwNjQ5MDYwNDQzNTkwODEwMTkwODMwMTM1ODA2MDIwNjAxZjgyMDE4MTkwMDQ4MTAyMDE2MDQwNTE5MDgxMDE2MDQwNTI4MTgxNTI5MjkxOTA2MDIwODQwMTgzODM4MDgyODQzNzUwOTQ5NjUwNjEwNjUzOTU1MDUwNTA1MDUwNTA1NjViMzQxNTYxMDJmMjU3NjAwMDgwZmQ1YjYxMDE5MTYwMDE2MGEwNjAwMjBhMDM2MDA0MzU4MTE2OTA2MDI0MzUxNjYxMDc4NTU2NWI2MDAwODA1NDYwMDE4MTYwMDExNjE1NjEwMTAwMDIwMzE2NjAwMjkwMDQ4MDYwMWYwMTYwMjA4MDkxMDQwMjYwMjAwMTYwNDA1MTkwODEwMTYwNDA1MjgwOTI5MTkwODE4MTUyNjAyMDAxODI4MDU0NjAwMTgxNjAwMTE2MTU2MTAxMDAwMjAzMTY2MDAyOTAwNDgwMTU2MTAzYTI1NzgwNjAxZjEwNjEwMzc3NTc2MTAxMDA4MDgzNTQwNDAyODM1MjkxNjAyMDAxOTE2MTAzYTI1NjViODIwMTkxOTA2MDAwNTI2MDIwNjAwMDIwOTA1YjgxNTQ4MTUyOTA2MDAxMDE5MDYwMjAwMTgwODMxMTYxMDM4NTU3ODI5MDAzNjAxZjE2ODIwMTkxNWI1MDUwNTA1MDUwODE1NjViNjAwMTYwYTA2MDAyMGEwMzMzODExNjYwMDA5MDgxNTI2MDA1NjAyMDkwODE1MjYwNDA4MDgzMjA5Mzg2MTY4MzUyOTI5MDUyMjA4MTkwNTU2MDAxOTI5MTUwNTA1NjViNjAwMzU0ODE1NjViNjAwMTYwYTA2MDAyMGEwMzgwODQxNjYwMDA5MDgxNTI2MDA1NjAyMDkwODE1MjYwNDA4MDgzMjAzMzkwOTQxNjgzNTI5MjkwNTI5MDgxMjA1NDgyMTExNTYxMDQxNTU3NjAwMDgwZmQ1YjYwMDE2MGEwNjAwMjBhMDM4MDg1MTY2MDAwOTA4MTUyNjAwNTYwMjA5MDgxNTI2MDQwODA4MzIwMzM5MDk0MTY4MzUyOTI5MDUyMjA4MDU0ODM5MDAzOTA1NTYxMDQ0ZDg0ODQ4NDYxMDdhMjU2NWI1MDYwMDE5MzkyNTA1MDUwNTY1YjYwMDI1NDYwZmYxNjgxNTY1YjYwMDE2MGEwNjAwMjBhMDMzMzE2NjAwMDkwODE1MjYwMDQ2MDIwNTI2MDQwODEyMDU0ODI5MDEwMTU2MTA0ODY1NzYwMDA4MGZkNWI2MDAxNjBhMDYwMDIwYTAzMzMxNjYwMDA4MTgxNTI2MDA0NjAyMDUyNjA0MDkwODE5MDIwODA1NDg1OTAwMzkwNTU2MDAzODA1NDg1OTAwMzkwNTU3ZmNjMTZmNWRiYjQ4NzMyODA4MTVjMWVlMDlkYmQwNjczNmNmZmNjMTg0NDEyY2Y3YTcxYTBmZGI3NWQzOTdjYTU5MDg0OTA1MTkwODE1MjYwMjAwMTYwNDA1MTgwOTEwMzkwYTI1MDYwMDE5MTkwNTA1NjViNjAwNDYwMjA1MjYwMDA5MDgxNTI2MDQwOTAyMDU0ODE1NjViNjAwMTYwYTA2MDAyMGEwMzgyMTY2MDAwOTA4MTUyNjAwNDYwMjA1MjYwNDA4MTIwNTQ4MjkwMTAxNTYxMDUyMzU3NjAwMDgwZmQ1YjYwMDE2MGEwNjAwMjBhMDM4MDg0MTY2MDAwOTA4MTUyNjAwNTYwMjA5MDgxNTI2MDQwODA4MzIwMzM5MDk0MTY4MzUyOTI5MDUyMjA1NDgyMTExNTYxMDU1NjU3NjAwMDgwZmQ1YjYwMDE2MGEwNjAwMjBhMDM4MDg0MTY2MDAwODE4MTUyNjAwNDYwMjA5MDgxNTI2MDQwODA4MzIwODA1NDg4OTAwMzkwNTU2MDA1ODI1MjgwODMyMDMzOTA5NTE2ODM1MjkzOTA1MjgyOTAyMDgwNTQ4NTkwMDM5MDU1NjAwMzgwNTQ4NTkwMDM5MDU1OTA3ZmNjMTZmNWRiYjQ4NzMyODA4MTVjMWVlMDlkYmQwNjczNmNmZmNjMTg0NDEyY2Y3YTcxYTBmZGI3NWQzOTdjYTU5MDg0OTA1MTkwODE1MjYwMjAwMTYwNDA1MTgwOTEwMzkwYTI1MDYwMDE5MjkxNTA1MDU2NWI2MDAxODA1NDYwMDE4MTYwMDExNjE1NjEwMTAwMDIwMzE2NjAwMjkwMDQ4MDYwMWYwMTYwMjA4MDkxMDQwMjYwMjAwMTYwNDA1MTkwODEwMTYwNDA1MjgwOTI5MTkwODE4MTUyNjAyMDAxODI4MDU0NjAwMTgxNjAwMTE2MTU2MTAxMDAwMjAzMTY2MDAyOTAwNDgwMTU2MTAzYTI1NzgwNjAxZjEwNjEwMzc3NTc2MTAxMDA4MDgzNTQwNDAyODM1MjkxNjAyMDAxOTE2MTAzYTI1NjViNjEwNjRmMzM4MzgzNjEwN2EyNTY1YjUwNTA1NjViNjAwMDgzNjEwNjYwODE4NTYxMDNhYTU2NWIxNTYxMDc3ZDU3ODA2MDAxNjBhMDYwMDIwYTAzMTY2MzhmNGZmY2IxMzM4NjMwODc2MDQwNTE4NTYzZmZmZmZmZmYxNjdjMDEwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAyODE1MjYwMDQwMTgwODU2MDAxNjBhMDYwMDIwYTAzMTY2MDAxNjA=","b64Record":"CiAIFiocCgwIARAMGgYIgK6ZpA8SDAgBEA8aBgiArpmkDxIwwbZhCULESuzHPGvcMdnmzAVGWHhAI2FUdrAPMA3D6UsDG+X8qdFh1htOBjOXku5KGgsIxP3+rAYQpf3XGiIPCgkIh/3+rAYQnwoSAhgCKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjlIA"},{"b64Body":"Cg8KCQiI/f6sBhClChICGAISAhgDGMnP0DEiAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjoIBxggSAxijCCK+CGEwNjAwMjBhMDMxNjgxNTI2MDIwMDE4NDgxNTI2MDIwMDE4MzYwMDE2MGEwNjAwMjBhMDMxNjYwMDE2MGEwNjAwMjBhMDMxNjgxNTI2MDIwMDE4MDYwMjAwMTgyODEwMzgyNTI4MzgxODE1MTgxNTI2MDIwMDE5MTUwODA1MTkwNjAyMDAxOTA4MDgzODM2MDAwNWI4MzgxMTAxNTYxMDcxNjU3ODA4MjAxNTE4MzgyMDE1MjYwMjAwMTYxMDZmZTU2NWI1MDUwNTA1MDkwNTA5MDgxMDE5MDYwMWYxNjgwMTU2MTA3NDM1NzgwODIwMzgwNTE2MDAxODM2MDIwMDM2MTAxMDAwYTAzMTkxNjgxNTI2MDIwMDE5MTUwNWI1MDk1NTA1MDUwNTA1MDUwNjAwMDYwNDA1MTgwODMwMzgxNjAwMDg3ODAzYjE1MTU2MTA3NjQ1NzYwMDA4MGZkNWI2MTAyYzY1YTAzZjExNTE1NjEwNzc1NTc2MDAwODBmZDViNTA1MDUwNjAwMTkxNTA1YjUwOTM5MjUwNTA1MDU2NWI2MDA1NjAyMDkwODE1MjYwMDA5MjgzNTI2MDQwODA4NDIwOTA5MTUyOTA4MjUyOTAyMDU0ODE1NjViNjAwMDYwMDE2MGEwNjAwMjBhMDM4MzE2MTUxNTYxMDdiOTU3NjAwMDgwZmQ1YjYwMDE2MGEwNjAwMjBhMDM4NDE2NjAwMDkwODE1MjYwMDQ2MDIwNTI2MDQwOTAyMDU0ODI5MDEwMTU2MTA3ZGY1NzYwMDA4MGZkNWI2MDAxNjBhMDYwMDIwYTAzODMxNjYwMDA5MDgxNTI2MDA0NjAyMDUyNjA0MDkwMjA1NDgyODEwMTExNjEwODA1NTc2MDAwODBmZDViNTA2MDAxNjBhMDYwMDIwYTAzODA4MzE2NjAwMDgxODE1MjYwMDQ2MDIwNTI2MDQwODA4MjIwODA1NDk0ODgxNjgwODQ1MjgyODQyMDgwNTQ4ODgxMDM5MDkxNTU5Mzg1OTA1MjgxNTQ4NzAxOTA5MTU1OTE5MDkzMDE5MjdmZGRmMjUyYWQxYmUyYzg5YjY5YzJiMDY4ZmMzNzhkYWE5NTJiYTdmMTYzYzRhMTE2MjhmNTVhNGRmNTIzYjNlZjkwODU5MDUxOTA4MTUyNjAyMDAxNjA0MDUxODA5MTAzOTBhMzYwMDE2MGEwNjAwMjBhMDM4MDg0MTY2MDAwOTA4MTUyNjAwNDYwMjA1MjYwNDA4MDgyMjA1NDkyODcxNjgyNTI5MDIwNTQwMTgxMTQ2MTA4YTI1N2ZlNWI1MDUwNTA1MDU2MDBhMTY1NjI3YTdhNzIzMDU4MjAxNTE4MzhmNTZlNTZiNTBmNjNlNTQyODU2MmQ2MmI1M2Q1N2U0Njk3ZGQwZTFmZjdkNTNiNGViMWQ4ZDEwOGZmMDAyOQ==","b64Record":"CiAIFiocCgwIARAMGgYIgK6ZpA8SDAgBEA8aBgiArpmkDxIwxuW1xcuqRYkJGZsyo1ElAfCPTDs92PVFZNYp2Qpgdv581AG3fUtm9SyjD0xjBGptGgwIxP3+rAYQn+PH/wEiDwoJCIj9/qwGEKUKEgIYAiogw4PCrsOCwrfDg8K5dEY4w4LCrkrDg8KLw4PCkMODwo5SAA=="},{"b64Body":"ChAKCQiJ/f6sBhCnChIDGJ4IEgIYAxi1jqeoAiICCHgyIMODwq7DgsK3w4PCuXRGOMOCwq5Kw4PCi8ODwpDDg8KOQqgCCgMYowgaIhIg+GVqo3Q0vV3wWSqqrfo1KAahaWd5SL68u6eWneqRrjggkKEPQgUIgM7aA0rgAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD0JAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPT3BlbkNyb3dkIFRva2VuAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA09DVAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAUgBaAGoLY2VsbGFyIGRvb3I=","b64Record":"CiUIFiIDGKQIKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjDF342mpHdiyUuGRVaMEqMCzN4hjmcUU+4ue4OH62Wb4PwSvBKVl16GN581vdaQuVYaCwjF/f6sBhDXi5EkIhAKCQiJ/f6sBhCnChIDGJ4IKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjjC30aSvAkKJFAoDGKQIEtQRYGBgQFJgBDYQYQC5V2P/////fAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAYAA1BBZjBv3eA4EUYQC+V4BjCV6nsxRhAUhXgGMYFg3dFGEBfleAYyO4ct0UYQGjV4BjMTzlZxRhActXgGNClmxoFGEB9FeAY3CggjEUYQIKV4BjecxnkBRhAilXgGOV2JtBFGECS1eAY6kFnLsUYQJeV4BjyunKURRhAoJXgGPdYu0+FGEC51dbYACA/Vs0FWEAyVdgAID9W2EA0WEDDFZbYEBRYCCAglKBkIEBg4GBUYFSYCABkVCAUZBgIAGQgIODYABbg4EQFWEBDVeAggFRg4IBUmAgAWEA9VZbUFBQUJBQkIEBkGAfFoAVYQE6V4CCA4BRYAGDYCADYQEACgMZFoFSYCABkVBbUJJQUFBgQFGAkQOQ81s0FWEBU1dgAID9W2EBamABYKBgAgoDYAQ1FmAkNWEDqlZbYEBRkBUVgVJgIAFgQFGAkQOQ81s0FWEBiVdgAID9W2EBkWED2lZbYEBRkIFSYCABYEBRgJEDkPNbNBVhAa5XYACA/VthAWpgAWCgYAIKA2AENYEWkGAkNRZgRDVhA+BWWzQVYQHWV2AAgP1bYQHeYQRXVltgQFFg/5CRFoFSYCABYEBRgJEDkPNbNBVhAf9XYACA/VthAWpgBDVhBGBWWzQVYQIVV2AAgP1bYQGRYAFgoGACCgNgBDUWYQTrVls0FWECNFdgAID9W2EBamABYKBgAgoDYAQ1FmAkNWEE/VZbNBVhAlZXYACA/VthANFhBdlWWzQVYQJpV2AAgP1bYQKAYAFgoGACCgNgBDUWYCQ1YQZEVlsAWzQVYQKNV2AAgP1bYQFqYASANWABYKBgAgoDFpBgJIA1kZBgZJBgRDWQgQGQgwE1gGAgYB+CAYGQBIECAWBAUZCBAWBAUoGBUpKRkGAghAGDg4CChDdQlJZQYQZTlVBQUFBQUFZbNBVhAvJXYACA/VthAZFgAWCgYAIKA2AENYEWkGAkNRZhB4VWW2AAgFRgAYFgARYVYQEAAgMWYAKQBIBgHwFgIICRBAJgIAFgQFGQgQFgQFKAkpGQgYFSYCABgoBUYAGBYAEWFWEBAAIDFmACkASAFWEDoleAYB8QYQN3V2EBAICDVAQCg1KRYCABkWEDolZbggGRkGAAUmAgYAAgkFuBVIFSkGABAZBgIAGAgxFhA4VXgpADYB8WggGRW1BQUFBQgVZbYAFgoGACCgMzgRZgAJCBUmAFYCCQgVJgQICDIJOGFoNSkpBSIIGQVWABkpFQUFZbYANUgVZbYAFgoGACCgOAhBZgAJCBUmAFYCCQgVJgQICDIDOQlBaDUpKQUpCBIFSCERVhBBVXYACA/VtgAWCgYAIKA4CFFmAAkIFSYAVgIJCBUmBAgIMgM5CUFoNSkpBSIIBUg5ADkFVhBE2EhIRhB6JWW1BgAZOSUFBQVltgAlRg/xaBVltgAWCgYAIKAzMWYACQgVJgBGAgUmBAgSBUgpAQFWEEhldgAID9W2ABYKBgAgoDMxZgAIGBUmAEYCBSYECQgZAggFSFkAOQVWADgFSFkAOQVX/MFvXbtIcygIFcHuCdvQZzbP/MGEQSz3pxoP23XTl8pZCEkFGQgVJgIAFgQFGAkQOQolBgAZGQUFZbYARgIFJgAJCBUmBAkCBUgVZbYAFgoGACCgOCFmAAkIFSYARgIFJgQIEgVIKQEBVhBSNXYACA/VtgAWCgYAIKA4CEFmAAkIFSYAVgIJCBUmBAgIMgM5CUFoNSkpBSIFSCERVhBVZXYACA/VtgAWCgYAIKA4CEFmAAgYFSYARgIJCBUmBAgIMggFSIkAOQVWAFglKAgyAzkJUWg1KTkFKCkCCAVIWQA5BVYAOAVIWQA5BVkH/MFvXbtIcygIFcHuCdvQZzbP/MGEQSz3pxoP23XTl8pZCEkFGQgVJgIAFgQFGAkQOQolBgAZKRUFBWW2ABgFRgAYFgARYVYQEAAgMWYAKQBIBgHwFgIICRBAJgIAFgQFGQgQFgQFKAkpGQgYFSYCABgoBUYAGBYAEWFWEBAAIDFmACkASAFWEDoleAYB8QYQN3V2EBAICDVAQCg1KRYCABkWEDolZbYQZPM4ODYQeiVltQUFZbYACDYQZggYVhA6pWWxVhB31XgGABYKBgAgoDFmOPT/yxM4Ywh2BAUYVj/////xZ8AQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACgVJgBAGAhWABYKBgAgoDFmABYKBgAgoDFoFSYCABhIFSYCABg2ABYKBgAgoDFmABYKBgAgoDFoFSYCABgGAgAYKBA4JSg4GBUYFSYCABkVCAUZBgIAGQgIODYABbg4EQFWEHFleAggFRg4IBUmAgAWEG/lZbUFBQUJBQkIEBkGAfFoAVYQdDV4CCA4BRYAGDYCADYQEACgMZFoFSYCABkVBbUJVQUFBQUFBgAGBAUYCDA4FgAIeAOxUVYQdkV2AAgP1bYQLGWgPxFRVhB3VXYACA/VtQUFBgAZFQW1CTklBQUFZbYAVgIJCBUmAAkoNSYECAhCCQkVKQglKQIFSBVltgAGABYKBgAgoDgxYVFWEHuVdgAID9W2ABYKBgAgoDhBZgAJCBUmAEYCBSYECQIFSCkBAVYQffV2AAgP1bYAFgoGACCgODFmAAkIFSYARgIFJgQJAgVIKBARFhCAVXYACA/VtQYAFgoGACCgOAgxZgAIGBUmAEYCBSYECAgiCAVJSIFoCEUoKEIIBUiIEDkJFVk4WQUoFUhwGQkVWRkJMBkn/d8lKtG+LIm2nCsGj8N42qlSun8WPEoRYo9VpN9SOz75CFkFGQgVJgIAFgQFGAkQOQo2ABYKBgAgoDgIQWYACQgVJgBGAgUmBAgIIgVJKHFoJSkCBUAYEUYQiiV/5bUFBQUFYAoWVienpyMFggFRg49W5WtQ9j5UKFYtYrU9V+RpfdDh/31TtOsdjRCP8AKSKAAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAo7soMOgMYpAhKFgoUAAAAAAAAAAAAAAAAAAAAAAAABCRyBwoDGKQIEAFSPAoJCgIYAxDovq4VCgoKAhhiEO7q+tYDCgoKAxigBhDM/I85CgoKAxihBhDM/I85CgsKAxieCBDtosneBA=="},{"b64Body":"ChAKCQiJ/f6sBhCvChIDGJ4IEgIYAxiqkAUiAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjnIZChcKCgoDGJ4IEM+WjQcKCQoCGAMQ0JaNBw==","b64Record":"CiAIFiocCgwIARAMGgYIgK6ZpA8SDAgBEA8aBgiArpmkDxIws+CeUDnCUPOLJ0huzvdyJchWfxhZiRgNHtJh/IeqJH7Rv8607V3l8bYCc2iDntrvGgwIxf3+rAYQu4ThiAIiEAoJCIn9/qwGEK8KEgMYnggqIMODwq7DgsK3w4PCuXRGOMOCwq5Kw4PCi8ODwpDDg8KOMKqQBVI1CgkKAhgDEObLjQcKCAoCGGIQzu8HCggKAxigBhD4fQoICgMYoQYQ+H0KCgoDGJ4IEKO3lwc="},{"b64Body":"ChAKCQiJ/f6sBhCzChIDGJ4IEgIYAxiqkAUiAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjnIZChcKCgoDGJ4IEM+WjQcKCQoCGAMQ0JaNBw==","b64Record":"CiAIFiocCgwIARAMGgYIgK6ZpA8SDAgBEA8aBgiArpmkDxIwLthDAoKnPNdculosZ8pGZIft13kak6mhbtNZrZ3uPqQvf5fFBMnRWi8F22Tlw+NYGgwIxf3+rAYQo4zhiAIiEAoJCIn9/qwGELMKEgMYnggqIMODwq7DgsK3w4PCuXRGOMOCwq5Kw4PCi8ODwpDDg8KOMKqQBVI1CgkKAhgDEObLjQcKCAoCGGIQzu8HCggKAxigBhD4fQoICgMYoQYQ+H0KCgoDGJ4IEKO3lwc="},{"b64Body":"ChAKCQiJ/f6sBhC5ChIDGJ4IEgIYAxjwr7sIIgIIeDIgw4PCrsOCwrfDg8K5dEY4w4LCrkrDg8KLw4PCkMODwo46TwoDGKQIEJChDyJEqQWcuwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQfAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPQkA=","b64Record":"CiUIFiIDGKQIKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjA/qnQBXQPTs8Umv3XL78BQYJPgUpdhDHfGbK+RnC0GeFMmnWFxpraq7sLpg1mYac4aDAjF/f6sBhDkj76lAiIQCgkIif3+rAYQuQoSAxieCCogw4PCrsOCwrfDg8K5dEY4w4LCrkrDg8KLw4PCkMODwo4wwNniBjqfBQoDGKQIIoACAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAACAAAAAAAAAAgAAAAAAAAABAAAAAAASAAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEAAAACAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACjAmgwykAMKAxikCBKAAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAgAAAAAAAAAIAAAAAAAAAAQAAAAAAEgAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAACAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAAAAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAaIN3yUq0b4sibacKwaPw3jaqVK6fxY8ShFij1Wk31I7PvGiAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEHhogAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABB8iIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD0JAUhcKCQoCGGIQgLPFDQoKCgMYnggQ/7LFDQ=="},{"b64Body":"ChAKCQiK/f6sBhC7ChIDGJ4IEgIYAxjwr7sIIgIIeDIgw4PCrsOCwrfDg8K5dEY4w4LCrkrDg8KLw4PCkMODwo46TwoDGKQIEJChDyJEqQWcuwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAehIA=","b64Record":"CiUIFiIDGKQIKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjBQotcz36eIoIa7Iox+C0oZ2Fs2QEZ9fFS155GqGL1bzyQtqufL4WCq5hs73GXFI2AaCwjG/f6sBhDri9MtIhAKCQiK/f6sBhC7ChIDGJ4IKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjjDA2eIGOp8FCgMYpAgigAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAAAAAEAAAAAAAAAAABIAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAQAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAAACAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKMCaDDKQAwoDGKQIEoACAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgAAAABAAAAAAAAAAAASAAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEAAEAAAAAAAAAAAAAAAAAAAAAAAAAAEAAAAAAAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABog3fJSrRviyJtpwrBo/DeNqpUrp/FjxKEWKPVaTfUjs+8aIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQeGiAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEICIgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAehIBSFwoJCgIYYhCAs8UNCgoKAxieCBD/ssUN"},{"b64Body":"ChAKCQiK/f6sBhC9ChIDGKAIEgIYAxjwr7sIIgIIeDIgw4PCrsOCwrfDg8K5dEY4w4LCrkrDg8KLw4PCkMODwo46TwoDGKQIEJChDyJEqQWcuwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQhAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHoSA=","b64Record":"CiUIFiIDGKQIKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjAECVw/Y8fyIsuFxw6/P/CITRzB+QT3yDC88PhDjxi5yhHpGqt6yLmhPdr7wJTgOI0aDAjG/f6sBhCKzsSSAiIQCgkIiv3+rAYQvQoSAxigCCogw4PCrsOCwrfDg8K5dEY4w4LCrkrDg8KLw4PCkMODwo4wwNniBjqfBQoDGKQIIoACAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAAASAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEAAEAAAAAAAAAAAAAAAAIAAAAAAAAAEAAAAAAAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAACjAmgwykAMKAxikCBKAAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAEgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAABAAAAAAAAAAAAAAAACAAAAAAAAABAAAAAAAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAaIN3yUq0b4sibacKwaPw3jaqVK6fxY8ShFij1Wk31I7PvGiAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEIBogAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABCEiIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB6EgUhcKCQoCGGIQgLPFDQoKCgMYoAgQ/7LFDQ=="},{"b64Body":"ChAKCQiL/f6sBhDLChIDGJ8IEgIYAxjwr7sIIgIIeDIgw4PCrsOCwrfDg8K5dEY4w4LCrkrDg8KLw4PCkMODwo46TwoDGKQIEJChDyJECV6nswAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQiAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADDUA=","b64Record":"CiUIFiIDGKQIKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjD+q4+l7linqfQE1SQOVDbH4UApOYzWxzdJp/ScF7bUkzfcCWaqCBmALRm2wCTaI8EaCwjH/f6sBhDZw/g2IhAKCQiL/f6sBhDLChIDGJ8IKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjjDA2eIGOq4CCgMYpAgSIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABIoACAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACjAmgxSFwoJCgIYYhCAs8UNCgoKAxifCBD/ssUN"},{"b64Body":"ChAKCQiL/f6sBhDNChIDGKIIEgIYAxjwr7sIIgIIeDIgw4PCrsOCwrfDg8K5dEY4w4LCrkrDg8KLw4PCkMODwo46bwoDGKQIEJChDyJkI7hy3QAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQfAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABCAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGGoA==","b64Record":"CiUIFiIDGKQIKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjCeNdVySEJTf7hB2bAzWhZQniatFSRhgYnMW4s/++o0yQldY7+ahp9RzuLEh0wlCf8aDAjH/f6sBhCxwOe4AiIQCgkIi/3+rAYQzQoSAxiiCCogw4PCrsOCwrfDg8K5dEY4w4LCrkrDg8KLw4PCkMODwo4wwNniBjrBBQoDGKQIEiAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAASKAAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAgAAAAAAAAAAAAAAAQAAAAQAAAAAAEgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAABAAAAAgAAAAAAAAAAAAAAAAAAAABAAAAAAAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAowJoMMpADCgMYpAgSgAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAIAAAAAAAAAAAAAAAEAAAAEAAAAAABIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAQAAAAIAAAAAAAAAAAAAAAAAAAAAQAAAAAACAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGiDd8lKtG+LIm2nCsGj8N42qlSun8WPEoRYo9VpN9SOz7xogAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABB8aIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQgIiAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGGoFIXCgkKAhhiEICzxQ0KCgoDGKIIEP+yxQ0="}]},"erc721TokenUriAndHtsNftInfoTreatNonUtf8BytesDifferently":{"placeholderNum":1061,"encodedItems":[{"b64Body":"Cg8KCQiQ/f6sBhD1ChICGAISAhgDGP7LzSwiAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjooBjgESCwjMy9mwBhCEt4ppGm0KIhIgly7NuGwER017X6MxqiUDRQNDXSq9atLlYCPOnfcuOS4KIzohA1k45xDmSPddQzc+RODI4OdNkpzM60m6lPzy0vtbTyucCiISIKmVMaFp8Xjyc6gmFZISJSKMHP9VhqGQrNAYPdLrxjDgIgxIZWxsbyBXb3JsZCEqADIA","b64Record":"CiUIFhoDGKYIKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjBczXJqVX+x10LJMChWJKaHiqDbRTlI7qgg2EDt+29LD5zhGZfgrgtR7QGe0fT/wwcaDAjM/f6sBhDS1qqDASIPCgkIkP3+rAYQ9QoSAhgCKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjlIA"},{"b64Body":"Cg8KCQiQ/f6sBhD5ChICGAISAhgDGIydjj4iAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjpoBiCAKAximCCKAIDYwODA2MDQwNTIzNDgwMTU2MTAwMTA1NzYwMDA4MGZkNWI1MDYxMTI5MTgwNjEwMDIwNjAwMDM5NjAwMGYzZmU2MDgwNjA0MDUyMzQ4MDE1NjEwMDEwNTc2MDAwODBmZDViNTA2MDA0MzYxMDYxMDAzNjU3NjAwMDM1NjBlMDFjODA2M2JhNDkxNjU1MTQ2MTAwM2I1NzgwNjNlNTMxMGI5MTE0NjEwMDZiNTc1YjYwMDA4MGZkNWI2MTAwNTU2MDA0ODAzNjAzODEwMTkwNjEwMDUwOTE5MDYxMDJmYzU2NWI2MTAwOWI1NjViNjA0MDUxNjEwMDYyOTE5MDYxMDNjYzU2NWI2MDQwNTE4MDkxMDM5MGYzNWI2MTAwODU2MDA0ODAzNjAzODEwMTkwNjEwMDgwOTE5MDYxMDJmYzU2NWI2MTAxMjQ1NjViNjA0MDUxNjEwMDkyOTE5MDYxMDNjYzU2NWI2MDQwNTE4MDkxMDM5MGYzNWI2MDYwODI3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNjYzYzg3YjU2ZGQ4MzYwNDA1MTgyNjNmZmZmZmZmZjE2NjBlMDFiODE1MjYwMDQwMTYxMDBkNjkxOTA2MTAzZmQ1NjViNjAwMDYwNDA1MTgwODMwMzgxODY1YWZhMTU4MDE1NjEwMGYzNTczZDYwMDA4MDNlM2Q2MDAwZmQ1YjUwNTA1MDUwNjA0MDUxM2Q2MDAwODIzZTNkNjAxZjE5NjAxZjgyMDExNjgyMDE4MDYwNDA1MjUwODEwMTkwNjEwMTFjOTE5MDYxMDUzZTU2NWI5MDUwOTI5MTUwNTA1NjViNjA2MDYwMDA4MDYxMDE2NzczZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjE2NjMyODdlMWRhODYwZTAxYjg2ODY2MDQwNTE2MDI0MDE2MTAxNWQ5MjkxOTA2MTA1YjI1NjViNjA0MDUxNjAyMDgxODMwMzAzODE1MjkwNjA0MDUyOTA3YmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmMTkxNjYwMjA4MjAxODA1MTdiZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmY4MzgxODMxNjE3ODM1MjUwNTA1MDUwNjA0MDUxNjEwMWM3OTE5MDYxMDYxNzU2NWI2MDAwNjA0MDUxODA4MzAzODE2MDAwODY1YWYxOTE1MDUwM2Q4MDYwMDA4MTE0NjEwMjA0NTc2MDQwNTE5MTUwNjAxZjE5NjAzZjNkMDExNjgyMDE2MDQwNTIzZDgyNTIzZDYwMDA2MDIwODQwMTNlNjEwMjA5NTY1YjYwNjA5MTUwNWI1MDkxNTA5MTUwODE2MTAyMTg1NzYwMDA4MGZkNWI2MDAwODA4MjgwNjAyMDAxOTA1MTgxMDE5MDYxMDIyZjkxOTA2MTExZmY1NjViOTE1MDkxNTA2MDE2ODI2MDAzMGIxNDYxMDI0MzU3NjAwMDgwZmQ1YjgwNjA4MDAxNTE5NDUwNTA1MDUwNTA5MjkxNTA1MDU2NWI2MDAwNjA0MDUxOTA1MDkwNTY1YjYwMDA4MGZkNWI2MDAwODBmZDViNjAwMDczZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjgyMTY5MDUwOTE5MDUwNTY1YjYwMDA2MTAyOTM4MjYxMDI2ODU2NWI5MDUwOTE5MDUwNTY1YjYxMDJhMzgxNjEwMjg4NTY1YjgxMTQ2MTAyYWU1NzYwMDA4MGZkNWI1MDU2NWI2MDAwODEzNTkwNTA2MTAyYzA4MTYxMDI5YTU2NWI5MjkxNTA1MDU2NWI2MDAwODE5MDUwOTE5MDUwNTY1YjYxMDJkOTgxNjEwMmM2NTY1YjgxMTQ2MTAyZTQ1NzYwMDA4MGZkNWI1MDU2NWI2MDAwODEzNTkwNTA2MTAyZjY4MTYxMDJkMDU2NWI5MjkxNTA1MDU2NWI2MDAwODA2MDQwODM4NTAzMTIxNTYxMDMxMzU3NjEwMzEyNjEwMjVlNTY1YjViNjAwMDYxMDMyMTg1ODI4NjAxNjEwMmIxNTY1YjkyNTA1MDYwMjA2MTAzMzI4NTgyODYwMTYxMDJlNzU2NWI5MTUwNTA5MjUwOTI5MDUwNTY1YjYwMDA4MTUxOTA1MDkxOTA1MDU2NWI2MDAwODI4MjUyNjAyMDgyMDE5MDUwOTI5MTUwNTA1NjViNjAwMDViODM4MTEwMTU2MTAzNzY1NzgwODIwMTUxODE4NDAxNTI2MDIwODEwMTkwNTA2MTAzNWI1NjViNjAwMDg0ODQwMTUyNTA1MDUwNTA1NjViNjAwMDYwMWYxOTYwMWY4MzAxMTY5MDUwOTE5MDUwNTY1YjYwMDA2MTAzOWU4MjYxMDMzYzU2NWI2MTAzYTg4MTg1NjEwMzQ3NTY1YjkzNTA2MTAzYjg4MTg1NjAyMDg2MDE2MTAzNTg1NjViNjEwM2MxODE2MTAzODI1NjViODQwMTkxNTA1MDkyOTE1MDUwNTY1YjYwMDA2MDIwODIwMTkwNTA4MTgxMDM2MDAwODMwMTUyNjEwM2U2ODE4NDYxMDM5MzU2NWI5MDUwOTI5MTUwNTA1NjViNjEwM2Y3ODE2MTAyYzY1NjViODI1MjUwNTA1NjViNjAwMDYwMjA4MjAxOTA1MDYxMDQxMjYwMDA4MzAxODQ2MTAzZWU1NjViOTI5MTUwNTA1NjViNjAwMDgwZmQ1YjYwMDA4MGZkNWI3ZjRlNDg3YjcxMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDA2MDAwNTI2MDQxNjAwNDUyNjAyNDYwMDBmZDViNjEwNDVhODI2MTAzODI1NjViODEwMTgxODExMDY3ZmZmZmZmZmZmZmZmZmZmZjgyMTExNzE1NjEwNDc5NTc2MTA0Nzg2MTA0MjI1NjViNWI4MDYwNDA1MjUwNTA1MDU2NWI2MDAwNjEwNDhjNjEwMjU0NTY1YjkwNTA2MTA0OTg4MjgyNjEwNDUxNTY1YjkxOTA1MDU2NWI2MDAwNjdmZmZmZmZmZmZmZmZmZmZmODIxMTE1NjEwNGI4NTc2MTA0Yjc2MTA0MjI1NjViNWI2MTA0YzE4MjYxMDM4MjU2NWI5MDUwNjAyMDgxMDE5MDUwOTE5MDUwNTY1YjYwMDA2MTA0ZTE2MTA0ZGM4NDYxMDQ5ZDU2NWI2MTA0ODI1NjViOTA1MDgyODE1MjYwMjA4MTAxODQ4NDg0MDExMTE1NjEwNGZkNTc2MTA0ZmM2MTA0MWQ1NjViNWI2MTA1MDg4NDgyODU2MTAzNTg1NjViNTA5MzkyNTA1MDUwNTY1YjYwMDA4MjYwMWY4MzAxMTI2MTA1MjU1NzYxMDUyNDYxMDQxODU2NWI1YjgxNTE2MTA1MzU4NDgyNjAyMDg2MDE2MTA0Y2U1NjViOTE1MDUwOTI5MTUwNTA1NjViNjAwMDYwMjA4Mjg0MDMxMjE1NjEwNTU0NTc2MTA1NTM2MTAyNWU1NjViNWI2MDAwODIwMTUxNjdmZmZmZmZmZmZmZmZmZmZmODExMTE1NjEwNTcyNTc2MTA1NzE2MTAyNjM1NjViNWI2MTA1N2U4NDgyODUwMTYxMDUxMDU2NWI5MTUwNTA5MjkxNTA1MDU2NWI2MTA1OTA4MTYxMDI4ODU2NWI4MjUyNTA1MDU2NWI2MDAwODE2MDA3MGI5MDUwOTE5MDUwNTY1YjYxMDVhYzgxNjEwNTk2NTY1YjgyNTI1MDUwNTY1YjYwMDA2MDQwODIwMTkwNTA2MTA1Yzc2MDAwODMwMTg1NjEwNTg3NTY1YjYxMDVkNDYwMjA4MzAxODQ2MTA1YTM1NjViOTM5MjUwNTA1MDU2NWI2MDAwODE5MDUwOTI5MTUwNTA1NjViNjAwMDYxMDVmMTgyNjEwMzNjNTY1YjYxMDVmYjgxODU2MTA1ZGI1NjViOTM1MDYxMDYwYjgxODU2MDIwODYwMTYxMDM1ODU2NWI4MDg0MDE5MTUwNTA5MjkxNTA1MDU2NWI2MDAwNjEwNjIzODI4NDYxMDVlNjU2NWI5MTUwODE5MDUwOTI5MTUwNTA1NjViNjAwMDgxNjAwMzBiOTA1MDkxOTA1MDU2NWI2MTA2NDQ4MTYxMDYyZTU2NWI4MTE0NjEwNjRmNTc2MDAwODBmZDViNTA1NjViNjAwMDgxNTE5MDUwNjEwNjYxODE2MTA2M2I1NjViOTI5MTUwNTA1NjViNjAwMDgwZmQ1YjYwMDA4MGZkNWI2MDAwODE1MTkwNTA2MTA2ODA4MTYxMDI5YTU2NWI5MjkxNTA1MDU2NWI2MDAwODExNTE1OTA1MDkxOTA1MDU2NWI2MTA2OWI4MTYxMDY4NjU2NWI4MTE0NjEwNmE2NTc2MDAwODBmZDViNTA1NjViNjAwMDgxNTE5MDUwNjEwNmI4ODE2MTA2OTI1NjViOTI5MTUwNTA1NjViNjAwMDYzZmZmZmZmZmY4MjE2OTA1MDkxOTA1MDU2NWI2MTA2ZDc4MTYxMDZiZTU2NWI4MTE0NjEwNmUyNTc2MDAwODBmZDViNTA1NjViNjAwMDgxNTE5MDUwNjEwNmY0ODE2MTA2Y2U1NjViOTI5MTUwNTA1NjViNjAwMDY3ZmZmZmZmZmZmZmZmZmZmZjgyMTExNTYxMDcxNTU3NjEwNzE0NjEwNDIyNTY1YjViNjAyMDgyMDI5MDUwNjAyMDgxMDE5MDUwOTE5MDUwNTY1YjYwMDA4MGZkNWI2MDAwODE1MTkwNTA2MTA3M2E4MTYxMDJkMDU2NWI5MjkxNTA1MDU2NWI2MDAwNjdmZmZmZmZmZmZmZmZmZmZmODIxMTE1NjEwNzViNTc2MTA3NWE2MTA0MjI1NjViNWI2MTA3NjQ4MjYxMDM4MjU2NWI5MDUwNjAyMDgxMDE5MDUwOTE5MDUwNTY1YjYwMDA2MTA3ODQ2MTA3N2Y4NDYxMDc0MDU2NWI2MTA0ODI1NjViOTA1MDgyODE1MjYwMjA4MTAxODQ4NDg0MDExMTE1NjEwN2EwNTc2MTA3OWY2MTA0MWQ1NjViNWI2MTA3YWI4NDgyODU2MTAzNTg1NjViNTA5MzkyNTA1MDUwNTY1YjYwMDA4MjYwMWY4MzAxMTI2MTA3Yzg1NzYxMDdjNzYxMDQxODU2NWI1YjgxNTE2MTA3ZDg4NDgyNjAyMDg2MDE2MTA3NzE1NjViOTE1MDUwOTI5MTUwNTA=","b64Record":"CiAIFiocCgwIARAMGgYIgK6ZpA8SDAgBEA8aBgiArpmkDxIwlSoiTd7tSz/lhaWGSb1qKRwyKmpkFxDe78bLepGZumXgqpr/SZKVmxdyIFoyXYBGGgwIzP3+rAYQxeXC6AIiDwoJCJD9/qwGEPkKEgIYAiogw4PCrsOCwrfDg8K5dEY4w4LCrkrDg8KLw4PCkMODwo5SAA=="},{"b64Body":"Cg8KCQiR/f6sBhD/ChICGAISAhgDGPjR6j4iAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjoIBiCASAximCCKAIDU2NWI2MDAwNjBhMDgyODQwMzEyMTU2MTA3Zjc1NzYxMDdmNjYxMDY2NzU2NWI1YjYxMDgwMTYwYTA2MTA0ODI1NjViOTA1MDYwMDA2MTA4MTE4NDgyODUwMTYxMDZhOTU2NWI2MDAwODMwMTUyNTA2MDIwNjEwODI1ODQ4Mjg1MDE2MTA2NzE1NjViNjAyMDgzMDE1MjUwNjA0MDgyMDE1MTY3ZmZmZmZmZmZmZmZmZmZmZjgxMTExNTYxMDg0OTU3NjEwODQ4NjEwNjZjNTY1YjViNjEwODU1ODQ4Mjg1MDE2MTA3YjM1NjViNjA0MDgzMDE1MjUwNjA2MDgyMDE1MTY3ZmZmZmZmZmZmZmZmZmZmZjgxMTExNTYxMDg3OTU3NjEwODc4NjEwNjZjNTY1YjViNjEwODg1ODQ4Mjg1MDE2MTA3YjM1NjViNjA2MDgzMDE1MjUwNjA4MDYxMDg5OTg0ODI4NTAxNjEwNjcxNTY1YjYwODA4MzAxNTI1MDkyOTE1MDUwNTY1YjYwMDA2MDQwODI4NDAzMTIxNTYxMDhiYjU3NjEwOGJhNjEwNjY3NTY1YjViNjEwOGM1NjA0MDYxMDQ4MjU2NWI5MDUwNjAwMDYxMDhkNTg0ODI4NTAxNjEwNzJiNTY1YjYwMDA4MzAxNTI1MDYwMjA4MjAxNTE2N2ZmZmZmZmZmZmZmZmZmZmY4MTExMTU2MTA4Zjk1NzYxMDhmODYxMDY2YzU2NWI1YjYxMDkwNTg0ODI4NTAxNjEwN2UxNTY1YjYwMjA4MzAxNTI1MDkyOTE1MDUwNTY1YjYwMDA2MTA5MjQ2MTA5MWY4NDYxMDZmYTU2NWI2MTA0ODI1NjViOTA1MDgwODM4MjUyNjAyMDgyMDE5MDUwNjAyMDg0MDI4MzAxODU4MTExMTU2MTA5NDc1NzYxMDk0NjYxMDcyNjU2NWI1YjgzNWI4MTgxMTAxNTYxMDk4ZTU3ODA1MTY3ZmZmZmZmZmZmZmZmZmZmZjgxMTExNTYxMDk2YzU3NjEwOTZiNjEwNDE4NTY1YjViODA4NjAxNjEwOTc5ODk4MjYxMDhhNTU2NWI4NTUyNjAyMDg1MDE5NDUwNTA1MDYwMjA4MTAxOTA1MDYxMDk0OTU2NWI1MDUwNTA5MzkyNTA1MDUwNTY1YjYwMDA4MjYwMWY4MzAxMTI2MTA5YWQ1NzYxMDlhYzYxMDQxODU2NWI1YjgxNTE2MTA5YmQ4NDgyNjAyMDg2MDE2MTA5MTE1NjViOTE1MDUwOTI5MTUwNTA1NjViNjAwMDYwNjA4Mjg0MDMxMjE1NjEwOWRjNTc2MTA5ZGI2MTA2Njc1NjViNWI2MTA5ZTY2MDYwNjEwNDgyNTY1YjkwNTA2MDAwNjEwOWY2ODQ4Mjg1MDE2MTA2ZTU1NjViNjAwMDgzMDE1MjUwNjAyMDYxMGEwYTg0ODI4NTAxNjEwNjcxNTY1YjYwMjA4MzAxNTI1MDYwNDA2MTBhMWU4NDgyODUwMTYxMDZlNTU2NWI2MDQwODMwMTUyNTA5MjkxNTA1MDU2NWI2MDAwNjEwMTYwODI4NDAzMTIxNTYxMGE0MTU3NjEwYTQwNjEwNjY3NTY1YjViNjEwYTRjNjEwMTIwNjEwNDgyNTY1YjkwNTA2MDAwODIwMTUxNjdmZmZmZmZmZmZmZmZmZmZmODExMTE1NjEwYTZjNTc2MTBhNmI2MTA2NmM1NjViNWI2MTBhNzg4NDgyODUwMTYxMDUxMDU2NWI2MDAwODMwMTUyNTA2MDIwODIwMTUxNjdmZmZmZmZmZmZmZmZmZmZmODExMTE1NjEwYTljNTc2MTBhOWI2MTA2NmM1NjViNWI2MTBhYTg4NDgyODUwMTYxMDUxMDU2NWI2MDIwODMwMTUyNTA2MDQwNjEwYWJjODQ4Mjg1MDE2MTA2NzE1NjViNjA0MDgzMDE1MjUwNjA2MDgyMDE1MTY3ZmZmZmZmZmZmZmZmZmZmZjgxMTExNTYxMGFlMDU3NjEwYWRmNjEwNjZjNTY1YjViNjEwYWVjODQ4Mjg1MDE2MTA1MTA1NjViNjA2MDgzMDE1MjUwNjA4MDYxMGIwMDg0ODI4NTAxNjEwNmE5NTY1YjYwODA4MzAxNTI1MDYwYTA2MTBiMTQ4NDgyODUwMTYxMDZlNTU2NWI2MGEwODMwMTUyNTA2MGMwNjEwYjI4ODQ4Mjg1MDE2MTA2YTk1NjViNjBjMDgzMDE1MjUwNjBlMDgyMDE1MTY3ZmZmZmZmZmZmZmZmZmZmZjgxMTExNTYxMGI0YzU3NjEwYjRiNjEwNjZjNTY1YjViNjEwYjU4ODQ4Mjg1MDE2MTA5OTg1NjViNjBlMDgzMDE1MjUwNjEwMTAwNjEwYjZkODQ4Mjg1MDE2MTA5YzY1NjViNjEwMTAwODMwMTUyNTA5MjkxNTA1MDU2NWI2MTBiODM4MTYxMDU5NjU2NWI4MTE0NjEwYjhlNTc2MDAwODBmZDViNTA1NjViNjAwMDgxNTE5MDUwNjEwYmEwODE2MTBiN2E1NjViOTI5MTUwNTA1NjViNjAwMDY3ZmZmZmZmZmZmZmZmZmZmZjgyMTExNTYxMGJjMTU3NjEwYmMwNjEwNDIyNTY1YjViNjAyMDgyMDI5MDUwNjAyMDgxMDE5MDUwOTE5MDUwNTY1YjYwMDA2MGEwODI4NDAzMTIxNTYxMGJlODU3NjEwYmU3NjEwNjY3NTY1YjViNjEwYmYyNjBhMDYxMDQ4MjU2NWI5MDUwNjAwMDYxMGMwMjg0ODI4NTAxNjEwNmU1NTY1YjYwMDA4MzAxNTI1MDYwMjA2MTBjMTY4NDgyODUwMTYxMDY3MTU2NWI2MDIwODMwMTUyNTA2MDQwNjEwYzJhODQ4Mjg1MDE2MTA2YTk1NjViNjA0MDgzMDE1MjUwNjA2MDYxMGMzZTg0ODI4NTAxNjEwNmE5NTY1YjYwNjA4MzAxNTI1MDYwODA2MTBjNTI4NDgyODUwMTYxMDY3MTU2NWI2MDgwODMwMTUyNTA5MjkxNTA1MDU2NWI2MDAwNjEwYzcxNjEwYzZjODQ2MTBiYTY1NjViNjEwNDgyNTY1YjkwNTA4MDgzODI1MjYwMjA4MjAxOTA1MDYwYTA4NDAyODMwMTg1ODExMTE1NjEwYzk0NTc2MTBjOTM2MTA3MjY1NjViNWI4MzViODE4MTEwMTU2MTBjYmQ1NzgwNjEwY2E5ODg4MjYxMGJkMjU2NWI4NDUyNjAyMDg0MDE5MzUwNTA2MGEwODEwMTkwNTA2MTBjOTY1NjViNTA1MDUwOTM5MjUwNTA1MDU2NWI2MDAwODI2MDFmODMwMTEyNjEwY2RjNTc2MTBjZGI2MTA0MTg1NjViNWI4MTUxNjEwY2VjODQ4MjYwMjA4NjAxNjEwYzVlNTY1YjkxNTA1MDkyOTE1MDUwNTY1YjYwMDA2N2ZmZmZmZmZmZmZmZmZmZmY4MjExMTU2MTBkMTA1NzYxMGQwZjYxMDQyMjU2NWI1YjYwMjA4MjAyOTA1MDYwMjA4MTAxOTA1MDkxOTA1MDU2NWI2MDAwNjBjMDgyODQwMzEyMTU2MTBkMzc1NzYxMGQzNjYxMDY2NzU2NWI1YjYxMGQ0MTYwYzA2MTA0ODI1NjViOTA1MDYwMDA2MTBkNTE4NDgyODUwMTYxMDZlNTU2NWI2MDAwODMwMTUyNTA2MDIwNjEwZDY1ODQ4Mjg1MDE2MTA2ZTU1NjViNjAyMDgzMDE1MjUwNjA0MDYxMGQ3OTg0ODI4NTAxNjEwNmU1NTY1YjYwNDA4MzAxNTI1MDYwNjA2MTBkOGQ4NDgyODUwMTYxMDZlNTU2NWI2MDYwODMwMTUyNTA2MDgwNjEwZGExODQ4Mjg1MDE2MTA2YTk1NjViNjA4MDgzMDE1MjUwNjBhMDYxMGRiNTg0ODI4NTAxNjEwNjcxNTY1YjYwYTA4MzAxNTI1MDkyOTE1MDUwNTY1YjYwMDA2MTBkZDQ2MTBkY2Y4NDYxMGNmNTU2NWI2MTA0ODI1NjViOTA1MDgwODM4MjUyNjAyMDgyMDE5MDUwNjBjMDg0MDI4MzAxODU4MTExMTU2MTBkZjc1NzYxMGRmNjYxMDcyNjU2NWI1YjgzNWI4MTgxMTAxNTYxMGUyMDU3ODA2MTBlMGM4ODgyNjEwZDIxNTY1Yjg0NTI2MDIwODQwMTkzNTA1MDYwYzA4MTAxOTA1MDYxMGRmOTU2NWI1MDUwNTA5MzkyNTA1MDUwNTY1YjYwMDA4MjYwMWY4MzAxMTI2MTBlM2Y1NzYxMGUzZTYxMDQxODU2NWI1YjgxNTE2MTBlNGY4NDgyNjAyMDg2MDE2MTBkYzE1NjViOTE1MDUwOTI5MTUwNTA1NjViNjAwMDY3ZmZmZmZmZmZmZmZmZmZmZjgyMTExNTYxMGU3MzU3NjEwZTcyNjEwNDIyNTY1YjViNjAyMDgyMDI5MDUwNjAyMDgxMDE5MDUwOTE5MDUwNTY1YjYwMDA2MGMwODI4NDAzMTIxNTYxMGU5YTU3NjEwZTk5NjEwNjY3NTY1YjViNjEwZWE0NjBjMDYxMDQ4MjU2NWI5MDUwNjAwMDYxMGViNDg0ODI4NTAxNjEwNmU1NTY1YjYwMDA4MzAxNTI1MDYwMjA2MTBlYzg4NDgyODUwMTYxMDZlNTU2NWI2MDIwODMwMTUyNTA2MDQwNjEwZWRjODQ4Mjg1MDE2MTA2ZTU1NjViNjA0MDgzMDE1MjUwNjA2MDYxMGVmMDg0ODI4NTAxNjEwNjcxNTY1YjYwNjA4MzAxNTI1MDYwODA2MTBmMDQ4NDgyODUwMTYxMDZhOTU2NWI2MDgwODMwMTUyNTA2MGEwNjEwZjE4ODQ4Mjg1MDE2MTA2NzE1NjViNjBhMDgzMDE1MjUwOTI5MTUwNTA1NjViNjAwMDYxMGYzNzYxMGYzMjg0NjEwZTU4NTY1YjYxMDQ4MjU2NWI5MDUwODA4MzgyNTI2MDIwODIwMTkwNTA2MGMwODQwMjgzMDE4NTgxMTExNTYxMGY1YTU3NjEwZjU5NjEwNzI2NTY1YjViODM1YjgxODExMDE1NjEwZjgzNTc4MDYxMGY2Zjg4ODI2MTBlODQ1NjViODQ1MjYwMjA4NDAxOTM1MDUwNjBjMDgxMDE5MDUwNjEwZjVjNTY1YjUwNTA1MDkzOTI1MDUwNTA1NjViNjAwMDgyNjAxZjgzMDExMjYxMGZhMjU3NjEwZmExNjEwNDE4NTY1YjViODE1MTYxMGZiMjg0ODI2MDIwODYwMTYxMGYyNDU2NWI5MTUwNTA5MjkxNTA1MDU2NWI2MDAwNjEwMTIwODI4NDAzMTIxNTYxMGZkMjU3NjEwZmQxNjEwNjY3NTY1YjViNjEwZmRkNjEwMTIwNjEwNDgyNTY1YjkwNTA=","b64Record":"CiAIFiocCgwIARAMGgYIgK6ZpA8SDAgBEA8aBgiArpmkDxIwOvp+gjpl7Cw8vRSswQ4pTSzkpjlj/lEQ21TBud9vdxS2M/Tx2BILAd/heDXbJsKIGgwIzf3+rAYQl5eJjQEiDwoJCJH9/qwGEP8KEgIYAiogw4PCrsOCwrfDg8K5dEY4w4LCrkrDg8KLw4PCkMODwo5SAA=="},{"b64Body":"Cg8KCQiR/f6sBhCFCxICGAISAhgDGIDJ9DIiAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjoIB6goSAximCCLiCjYwMDA4MjAxNTE2N2ZmZmZmZmZmZmZmZmZmZmY4MTExMTU2MTBmZmQ1NzYxMGZmYzYxMDY2YzU2NWI1YjYxMTAwOTg0ODI4NTAxNjEwYTJhNTY1YjYwMDA4MzAxNTI1MDYwMjA2MTEwMWQ4NDgyODUwMTYxMGI5MTU2NWI2MDIwODMwMTUyNTA2MDQwNjExMDMxODQ4Mjg1MDE2MTA2YTk1NjViNjA0MDgzMDE1MjUwNjA2MDYxMTA0NTg0ODI4NTAxNjEwNmE5NTY1YjYwNjA4MzAxNTI1MDYwODA2MTEwNTk4NDgyODUwMTYxMDZhOTU2NWI2MDgwODMwMTUyNTA2MGEwODIwMTUxNjdmZmZmZmZmZmZmZmZmZmZmODExMTE1NjExMDdkNTc2MTEwN2M2MTA2NmM1NjViNWI2MTEwODk4NDgyODUwMTYxMGNjNzU2NWI2MGEwODMwMTUyNTA2MGMwODIwMTUxNjdmZmZmZmZmZmZmZmZmZmZmODExMTE1NjExMGFkNTc2MTEwYWM2MTA2NmM1NjViNWI2MTEwYjk4NDgyODUwMTYxMGUyYTU2NWI2MGMwODMwMTUyNTA2MGUwODIwMTUxNjdmZmZmZmZmZmZmZmZmZmZmODExMTE1NjExMGRkNTc2MTEwZGM2MTA2NmM1NjViNWI2MTEwZTk4NDgyODUwMTYxMGY4ZDU2NWI2MGUwODMwMTUyNTA2MTAxMDA4MjAxNTE2N2ZmZmZmZmZmZmZmZmZmZmY4MTExMTU2MTExMGU1NzYxMTEwZDYxMDY2YzU2NWI1YjYxMTExYTg0ODI4NTAxNjEwNTEwNTY1YjYxMDEwMDgzMDE1MjUwOTI5MTUwNTA1NjViNjAwMDYwYzA4Mjg0MDMxMjE1NjExMTNkNTc2MTExM2M2MTA2Njc1NjViNWI2MTExNDc2MGMwNjEwNDgyNTY1YjkwNTA2MDAwODIwMTUxNjdmZmZmZmZmZmZmZmZmZmZmODExMTE1NjExMTY3NTc2MTExNjY2MTA2NmM1NjViNWI2MTExNzM4NDgyODUwMTYxMGZiYjU2NWI2MDAwODMwMTUyNTA2MDIwNjExMTg3ODQ4Mjg1MDE2MTBiOTE1NjViNjAyMDgzMDE1MjUwNjA0MDYxMTE5Yjg0ODI4NTAxNjEwNjcxNTY1YjYwNDA4MzAxNTI1MDYwNjA2MTExYWY4NDgyODUwMTYxMGI5MTU2NWI2MDYwODMwMTUyNTA2MDgwODIwMTUxNjdmZmZmZmZmZmZmZmZmZmZmODExMTE1NjExMWQzNTc2MTExZDI2MTA2NmM1NjViNWI2MTExZGY4NDgyODUwMTYxMDdiMzU2NWI2MDgwODMwMTUyNTA2MGEwNjExMWYzODQ4Mjg1MDE2MTA2NzE1NjViNjBhMDgzMDE1MjUwOTI5MTUwNTA1NjViNjAwMDgwNjA0MDgzODUwMzEyMTU2MTEyMTY1NzYxMTIxNTYxMDI1ZTU2NWI1YjYwMDA2MTEyMjQ4NTgyODYwMTYxMDY1MjU2NWI5MjUwNTA2MDIwODMwMTUxNjdmZmZmZmZmZmZmZmZmZmZmODExMTE1NjExMjQ1NTc2MTEyNDQ2MTAyNjM1NjViNWI2MTEyNTE4NTgyODYwMTYxMTEyNzU2NWI5MTUwNTA5MjUwOTI5MDUwNTZmZWEyNjQ2OTcwNjY3MzU4MjIxMjIwMGJiNWI4OWQyNGFlNjIyMzdiYTJiNTEyZjc0YTg5NTdkY2JlNDcwOTVhMDZiNTJhMTA4YjhhNWJhMmQ5Nzk3YjY0NzM2ZjZjNjM0MzAwMDgxMDAwMzM=","b64Record":"CiAIFiocCgwIARAMGgYIgK6ZpA8SDAgBEA8aBgiArpmkDxIwMZ0a2VrDnCuoMkSVFwMJP7KqPCNeSQX5IxQ1+uUv3V+9iXdvcF3K3UdFIVgmRSAGGgwIzf3+rAYQqd/i8QIiDwoJCJH9/qwGEIULEgIYAiogw4PCrsOCwrfDg8K5dEY4w4LCrkrDg8KLw4PCkMODwo5SAA=="},{"b64Body":"Cg8KCQiS/f6sBhCHCxICGAISAhgDGJb7rp0CIgIIeDIgw4PCrsOCwrfDg8K5dEY4w4LCrkrDg8KLw4PCkMODwo5CRQoDGKYIGiISIPcgremqoxsIoP1ObBHii/srG6zNmF35TAhoDa6MSU0cIJChD0IFCIDO2gNSAFoAagtjZWxsYXIgZG9vcg==","b64Record":"CiUIFiIDGKcIKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjB3Xtjt85DOKqaeOlD4Ro8e8rq5cJnUgyFCNpB8QN/1J160hz4PWnUkrxyj2eLSx+gaDAjO/f6sBhCkpbmWASIPCgkIkv3+rAYQhwsSAhgCKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjjDA2eIGQsYnCgMYpwgSkSVggGBAUjSAFWEAEFdgAID9W1BgBDYQYQA2V2AANWDgHIBjukkWVRRhADtXgGPlMQuRFGEAa1dbYACA/VthAFVgBIA2A4EBkGEAUJGQYQL8VlthAJtWW2BAUWEAYpGQYQPMVltgQFGAkQOQ81thAIVgBIA2A4EBkGEAgJGQYQL8VlthASRWW2BAUWEAkpGQYQPMVltgQFGAkQOQ81tgYIJz//////////////////////////8WY8h7Vt2DYEBRgmP/////FmDgG4FSYAQBYQDWkZBhA/1WW2AAYEBRgIMDgYZa+hWAFWEA81c9YACAPj1gAP1bUFBQUGBAUT1gAII+PWAfGWAfggEWggGAYEBSUIEBkGEBHJGQYQU+VluQUJKRUFBWW2BgYACAYQFnc///////////////////////////FmMofh2oYOAbhoZgQFFgJAFhAV2SkZBhBbJWW2BAUWAggYMDA4FSkGBAUpB7/////////////////////////////////////xkWYCCCAYBRe/////////////////////////////////////+DgYMWF4NSUFBQUGBAUWEBx5GQYQYXVltgAGBAUYCDA4FgAIZa8ZFQUD2AYACBFGECBFdgQFGRUGAfGWA/PQEWggFgQFI9glI9YABgIIQBPmECCVZbYGCRUFtQkVCRUIFhAhhXYACA/VtgAICCgGAgAZBRgQGQYQIvkZBhEf9WW5FQkVBgFoJgAwsUYQJDV2AAgP1bgGCAAVGUUFBQUFCSkVBQVltgAGBAUZBQkFZbYACA/VtgAID9W2AAc///////////////////////////ghaQUJGQUFZbYABhApOCYQJoVluQUJGQUFZbYQKjgWECiFZbgRRhAq5XYACA/VtQVltgAIE1kFBhAsCBYQKaVluSkVBQVltgAIGQUJGQUFZbYQLZgWECxlZbgRRhAuRXYACA/VtQVltgAIE1kFBhAvaBYQLQVluSkVBQVltgAIBgQIOFAxIVYQMTV2EDEmECXlZbW2AAYQMhhYKGAWECsVZbklBQYCBhAzKFgoYBYQLnVluRUFCSUJKQUFZbYACBUZBQkZBQVltgAIKCUmAgggGQUJKRUFBWW2AAW4OBEBVhA3ZXgIIBUYGEAVJgIIEBkFBhA1tWW2AAhIQBUlBQUFBWW2AAYB8ZYB+DARaQUJGQUFZbYABhA56CYQM8VlthA6iBhWEDR1Zbk1BhA7iBhWAghgFhA1hWW2EDwYFhA4JWW4QBkVBQkpFQUFZbYABgIIIBkFCBgQNgAIMBUmED5oGEYQOTVluQUJKRUFBWW2ED94FhAsZWW4JSUFBWW2AAYCCCAZBQYQQSYACDAYRhA+5WW5KRUFBWW2AAgP1bYACA/Vt/Tkh7cQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABgAFJgQWAEUmAkYAD9W2EEWoJhA4JWW4EBgYEQZ///////////ghEXFWEEeVdhBHhhBCJWW1uAYEBSUFBQVltgAGEEjGECVFZbkFBhBJiCgmEEUVZbkZBQVltgAGf//////////4IRFWEEuFdhBLdhBCJWW1thBMGCYQOCVluQUGAggQGQUJGQUFZbYABhBOFhBNyEYQSdVlthBIJWW5BQgoFSYCCBAYSEhAERFWEE/VdhBPxhBB1WW1thBQiEgoVhA1hWW1CTklBQUFZbYACCYB+DARJhBSVXYQUkYQQYVltbgVFhBTWEgmAghgFhBM5WW5FQUJKRUFBWW2AAYCCChAMSFWEFVFdhBVNhAl5WW1tgAIIBUWf//////////4ERFWEFcldhBXFhAmNWW1thBX6EgoUBYQUQVluRUFCSkVBQVlthBZCBYQKIVluCUlBQVltgAIFgBwuQUJGQUFZbYQWsgWEFllZbglJQUFZbYABgQIIBkFBhBcdgAIMBhWEFh1ZbYQXUYCCDAYRhBaNWW5OSUFBQVltgAIGQUJKRUFBWW2AAYQXxgmEDPFZbYQX7gYVhBdtWW5NQYQYLgYVgIIYBYQNYVluAhAGRUFCSkVBQVltgAGEGI4KEYQXmVluRUIGQUJKRUFBWW2AAgWADC5BQkZBQVlthBkSBYQYuVluBFGEGT1dgAID9W1BWW2AAgVGQUGEGYYFhBjtWW5KRUFBWW2AAgP1bYACA/VtgAIFRkFBhBoCBYQKaVluSkVBQVltgAIEVFZBQkZBQVlthBpuBYQaGVluBFGEGpldgAID9W1BWW2AAgVGQUGEGuIFhBpJWW5KRUFBWW2AAY/////+CFpBQkZBQVlthBteBYQa+VluBFGEG4ldgAID9W1BWW2AAgVGQUGEG9IFhBs5WW5KRUFBWW2AAZ///////////ghEVYQcVV2EHFGEEIlZbW2AgggKQUGAggQGQUJGQUFZbYACA/VtgAIFRkFBhBzqBYQLQVluSkVBQVltgAGf//////////4IRFWEHW1dhB1phBCJWW1thB2SCYQOCVluQUGAggQGQUJGQUFZbYABhB4RhB3+EYQdAVlthBIJWW5BQgoFSYCCBAYSEhAERFWEHoFdhB59hBB1WW1thB6uEgoVhA1hWW1CTklBQUFZbYACCYB+DARJhB8hXYQfHYQQYVltbgVFhB9iEgmAghgFhB3FWW5FQUJKRUFBWW2AAYKCChAMSFWEH91dhB/ZhBmdWW1thCAFgoGEEglZbkFBgAGEIEYSChQFhBqlWW2AAgwFSUGAgYQglhIKFAWEGcVZbYCCDAVJQYECCAVFn//////////+BERVhCElXYQhIYQZsVltbYQhVhIKFAWEHs1ZbYECDAVJQYGCCAVFn//////////+BERVhCHlXYQh4YQZsVltbYQiFhIKFAWEHs1ZbYGCDAVJQYIBhCJmEgoUBYQZxVltggIMBUlCSkVBQVltgAGBAgoQDEhVhCLtXYQi6YQZnVltbYQjFYEBhBIJWW5BQYABhCNWEgoUBYQcrVltgAIMBUlBgIIIBUWf//////////4ERFWEI+VdhCPhhBmxWW1thCQWEgoUBYQfhVltgIIMBUlCSkVBQVltgAGEJJGEJH4RhBvpWW2EEglZbkFCAg4JSYCCCAZBQYCCEAoMBhYERFWEJR1dhCUZhByZWW1uDW4GBEBVhCY5XgFFn//////////+BERVhCWxXYQlrYQQYVltbgIYBYQl5iYJhCKVWW4VSYCCFAZRQUFBgIIEBkFBhCUlWW1BQUJOSUFBQVltgAIJgH4MBEmEJrVdhCaxhBBhWW1uBUWEJvYSCYCCGAWEJEVZbkVBQkpFQUFZbYABgYIKEAxIVYQncV2EJ22EGZ1ZbW2EJ5mBgYQSCVluQUGAAYQn2hIKFAWEG5VZbYACDAVJQYCBhCgqEgoUBYQZxVltgIIMBUlBgQGEKHoSChQFhBuVWW2BAgwFSUJKRUFBWW2AAYQFggoQDEhVhCkFXYQpAYQZnVltbYQpMYQEgYQSCVluQUGAAggFRZ///////////gREVYQpsV2EKa2EGbFZbW2EKeISChQFhBRBWW2AAgwFSUGAgggFRZ///////////gREVYQqcV2EKm2EGbFZbW2EKqISChQFhBRBWW2AggwFSUGBAYQq8hIKFAWEGcVZbYECDAVJQYGCCAVFn//////////+BERVhCuBXYQrfYQZsVltbYQrshIKFAWEFEFZbYGCDAVJQYIBhCwCEgoUBYQapVltggIMBUlBgoGELFISChQFhBuVWW2CggwFSUGDAYQsohIKFAWEGqVZbYMCDAVJQYOCCAVFn//////////+BERVhC0xXYQtLYQZsVltbYQtYhIKFAWEJmFZbYOCDAVJQYQEAYQtthIKFAWEJxlZbYQEAgwFSUJKRUFBWW2ELg4FhBZZWW4EUYQuOV2AAgP1bUFZbYACBUZBQYQuggWELelZbkpFQUFZbYABn//////////+CERVhC8FXYQvAYQQiVltbYCCCApBQYCCBAZBQkZBQVltgAGCggoQDEhVhC+hXYQvnYQZnVltbYQvyYKBhBIJWW5BQYABhDAKEgoUBYQblVltgAIMBUlBgIGEMFoSChQFhBnFWW2AggwFSUGBAYQwqhIKFAWEGqVZbYECDAVJQYGBhDD6EgoUBYQapVltgYIMBUlBggGEMUoSChQFhBnFWW2CAgwFSUJKRUFBWW2AAYQxxYQxshGELplZbYQSCVluQUICDglJgIIIBkFBgoIQCgwGFgREVYQyUV2EMk2EHJlZbW4NbgYEQFWEMvVeAYQypiIJhC9JWW4RSYCCEAZNQUGCggQGQUGEMllZbUFBQk5JQUFBWW2AAgmAfgwESYQzcV2EM22EEGFZbW4FRYQzshIJgIIYBYQxeVluRUFCSkVBQVltgAGf//////////4IRFWENEFdhDQ9hBCJWW1tgIIICkFBgIIEBkFCRkFBWW2AAYMCChAMSFWENN1dhDTZhBmdWW1thDUFgwGEEglZbkFBgAGENUYSChQFhBuVWW2AAgwFSUGAgYQ1lhIKFAWEG5VZbYCCDAVJQYEBhDXmEgoUBYQblVltgQIMBUlBgYGENjYSChQFhBuVWW2BggwFSUGCAYQ2hhIKFAWEGqVZbYICDAVJQYKBhDbWEgoUBYQZxVltgoIMBUlCSkVBQVltgAGEN1GENz4RhDPVWW2EEglZbkFCAg4JSYCCCAZBQYMCEAoMBhYERFWEN91dhDfZhByZWW1uDW4GBEBVhDiBXgGEODIiCYQ0hVluEUmAghAGTUFBgwIEBkFBhDflWW1BQUJOSUFBQVltgAIJgH4MBEmEOP1dhDj5hBBhWW1uBUWEOT4SCYCCGAWENwVZbkVBQkpFQUFZbYABn//////////+CERVhDnNXYQ5yYQQiVltbYCCCApBQYCCBAZBQkZBQVltgAGDAgoQDEhVhDppXYQ6ZYQZnVltbYQ6kYMBhBIJWW5BQYABhDrSEgoUBYQblVltgAIMBUlBgIGEOyISChQFhBuVWW2AggwFSUGBAYQ7chIKFAWEG5VZbYECDAVJQYGBhDvCEgoUBYQZxVltgYIMBUlBggGEPBISChQFhBqlWW2CAgwFSUGCgYQ8YhIKFAWEGcVZbYKCDAVJQkpFQUFZbYABhDzdhDzKEYQ5YVlthBIJWW5BQgIOCUmAgggGQUGDAhAKDAYWBERVhD1pXYQ9ZYQcmVltbg1uBgRAVYQ+DV4BhD2+IgmEOhFZbhFJgIIQBk1BQYMCBAZBQYQ9cVltQUFCTklBQUFZbYACCYB+DARJhD6JXYQ+hYQQYVltbgVFhD7KEgmAghgFhDyRWW5FQUJKRUFBWW2AAYQEggoQDEhVhD9JXYQ/RYQZnVltbYQ/dYQEgYQSCVluQUGAAggFRZ///////////gREVYQ/9V2EP/GEGbFZbW2EQCYSChQFhCipWW2AAgwFSUGAgYRAdhIKFAWELkVZbYCCDAVJQYEBhEDGEgoUBYQapVltgQIMBUlBgYGEQRYSChQFhBqlWW2BggwFSUGCAYRBZhIKFAWEGqVZbYICDAVJQYKCCAVFn//////////+BERVhEH1XYRB8YQZsVltbYRCJhIKFAWEMx1ZbYKCDAVJQYMCCAVFn//////////+BERVhEK1XYRCsYQZsVltbYRC5hIKFAWEOKlZbYMCDAVJQYOCCAVFn//////////+BERVhEN1XYRDcYQZsVltbYRDphIKFAWEPjVZbYOCDAVJQYQEAggFRZ///////////gREVYREOV2ERDWEGbFZbW2ERGoSChQFhBRBWW2EBAIMBUlCSkVBQVltgAGDAgoQDEhVhET1XYRE8YQZnVltbYRFHYMBhBIJWW5BQYACCAVFn//////////+BERVhEWdXYRFmYQZsVltbYRFzhIKFAWEPu1ZbYACDAVJQYCBhEYeEgoUBYQuRVltgIIMBUlBgQGERm4SChQFhBnFWW2BAgwFSUGBgYRGvhIKFAWELkVZbYGCDAVJQYICCAVFn//////////+BERVhEdNXYRHSYQZsVltbYRHfhIKFAWEHs1ZbYICDAVJQYKBhEfOEgoUBYQZxVltgoIMBUlCSkVBQVltgAIBgQIOFAxIVYRIWV2ESFWECXlZbW2AAYRIkhYKGAWEGUlZbklBQYCCDAVFn//////////+BERVhEkVXYRJEYQJjVltbYRJRhYKGAWERJ1ZbkVBQklCSkFBW/qJkaXBmc1giEiALtbidJK5iI3uitRL3SolX3L5HCVoGtSoQi4pbotl5e2Rzb2xjQwAIEAAzIoACAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACjAmgw6AxinCEoWChQAAAAAAAAAAAAAAAAAAAAAAAAEJ3IHCgMYpwgQAVIWCgkKAhgCEP+yxQ0KCQoCGGIQgLPFDQ=="},{"b64Body":"Cg8KCQiS/f6sBhCJCxICGAISAhgDGKbV6tYCIgIIeDIgw4PCrsOCwrfDg8K5dEY4w4LCrkrDg8KLw4PCkMODwo7qAUwKA25mdBIIRU5GWllGUEcqAhgCUiYyJAoiEiAKqOIQZMYeq4biqcFkVltOeppBRhBuCmzQOow5WhEOkmoMCM7L2bAGEOq87+0CiAEB","b64Record":"CiUIFiocCgwIARAMGgYIgK6ZpA8SDAgBEA8aBgiArpmkD1IDGKgIEjC462OFVVSwzeCD9Aaj0p72cCYj3tx1GH4lN6IXFC60BOdZDJTZ/tVVnYbN7xfg1PAaDAjO/f6sBhDF5pP7AiIPCgkIkv3+rAYQiQsSAhgCKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjlIAcgkKAxioCBICGAI="},{"b64Body":"Cg8KCQiT/f6sBhCPCxICGAISAhgDGKqg+QciAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjqoCCAoDGKgIGgH/","b64Record":"CiUIFiocCgwIARAMGgYIgK6ZpA8SDAgBEA8aBgiArpmkD1gBcgEBEjAmqLs+fI22u2mmbvyMAeOiHURZHzfVuHY9WjItccClyCvJ5B69kAM5TG4ODRnELKUaDAjP/f6sBhDk1fyfASIPCgkIk/3+rAYQjwsSAhgCKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjlIAWhEKAxioCBoKCgIYABICGAIYAQ=="},{"b64Body":"Cg8KCQiT/f6sBhCTCxICGAISAhgDGOCssQMiAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjjpPCgMYpwgQoI0GIkS6SRZVAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABCgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQ==","b64Record":"CiUIFiIDGKcIKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjB4PyeAXlaDCBWYmBzDCEhjP+x8HE7/KJwpsmGZ2o6FBzen9TP8LETPioqtPYj+E3EaDAjP/f6sBhDmo+CEAyIPCgkIk/3+rAYQkwsSAhgCKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjjCA19oCOu4CCgMYpwgSYAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPvv70AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACKAAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAogPEEUhYKCQoCGAIQ/621BQoJCgIYYhCArrUF"},{"b64Body":"ChEKCQiT/f6sBhCTCxICGAIgATpFCgMY5wIQASI8YY3GXgAAAAAAAAAAAAAAAAAAAAAAAAQoyHtW3QAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB","b64Record":"CgIIFhIwWHOeg5xVuqyqM1OIdMhcnuRGfylgfB75RS0Ul8f1n0Xw8LQKQWTcZEq4I1/hGg1fGgwIz/3+rAYQ56PghAMiEQoJCJP9/qwGEJMLEgIYAiABOm4KAxjnAhJgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA++/vQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKGRqAxinCFIAegwIz/3+rAYQ5qPghAM="},{"b64Body":"Cg8KCQiU/f6sBhCVCxICGAISAhgDGMC/7SEiAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjjpPCgMYpwgQwIQ9IkTlMQuRAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABCgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQ==","b64Record":"CiUIFiIDGKcIKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjD5sm3Y2JI2NU/EI2oTJLh0Cqj0AyyiI23cQe8I0pHU7lk1H1ywXwEj7RsiAWp3hncaDAjQ/f6sBhDvk66pASIPCgkIlP3+rAYQlQsSAhgCKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjjCA5oobOu4CCgMYpwgSYAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAH/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACKAAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAogOowUhYKCQoCGAIQ/8uVNgoJCgIYYhCAzJU2"},{"b64Body":"ChEKCQiU/f6sBhCVCxICGAIgATpNCgMY5wIQASJEKH4dqAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAE=","b64Record":"CgIIFhIwyQtQF0i+EB9nmXON6AYX6fsh15THNamuWilgWcozi8Ii9F3CPKLtm33oiABSAG8zGgwI0P3+rAYQ8JOuqQEiEQoJCJT9/qwGEJULEgIYAiABOu8bCgMY5wIS4BsAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGWfvs8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAANYAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAASAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADCAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAxgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAWAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAeAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABmFmXOAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADbmZ0AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACEVORlpZRlBHAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAARAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABWAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAegAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQweDAwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB/wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAoZGoDGKcIUgB6DAjQ/f6sBhDvk66pAQ=="}]},"MinChargeIsTXGasUsedByContractCall":{"placeholderNum":1065,"encodedItems":[{"b64Body":"Cg8KCQiZ/f6sBhCtCxICGAISAhgDGP7LzSwiAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjooBjgESCwjVy9mwBhCv/JYUGm0KIhIgnvXuho7J8/d4AcWfDjKOyGFkvcmLMY28/ivUfYk4wzMKIzohA9ywQxssH+8Wwo9MEJRhun4yVYkaM2dmxTV8SkXgi45oCiISIPyyV/EB0oT0ibVDw3rXTyG7yj4WxEr8vkIEn/P/b5qjIgxIZWxsbyBXb3JsZCEqADIA","b64Record":"CiUIFhoDGKoIKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjAoT2VeSfSnR1CoBNao7q+eHoFk17of5XYDV3X+GQZc7t1rKSKTUh//thoIDuUVr4EaCwjV/f6sBhCJ+9UVIg8KCQiZ/f6sBhCtCxICGAIqIMODwq7DgsK3w4PCuXRGOMOCwq5Kw4PCi8ODwpDDg8KOUgA="},{"b64Body":"Cg8KCQiZ/f6sBhCxCxICGAISAhgDGNzJ7TEiAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjpoBjAoKAxiqCCKECjYwODA2MDQwNTIzNDgwMTU2MTAwMTA1NzYwMDA4MGZkNWI1MDYxMDI2MjgwNjEwMDIwNjAwMDM5NjAwMGYzZmU2MDgwNjA0MDUyMzQ4MDE1NjEwMDEwNTc2MDAwODBmZDViNTA2MDA0MzYxMDYxMDA0YzU3NjAwMDM1NjBlMDFjODA2MzFhYjA2ZWU1MTQ2MTAwNTE1NzgwNjNiOWNhZWJmNDE0NjEwMDZkNTc4MDYzYzMwMzY1ZDIxNDYxMDA4OTU3ODA2M2VkZWQ5N2ZhMTQ2MTAwYTc1NzViNjAwMDgwZmQ1YjYxMDA2YjYwMDQ4MDM2MDM4MTAxOTA2MTAwNjY5MTkwNjEwMTM3NTY1YjYxMDBjNTU2NWIwMDViNjEwMDg3NjAwNDgwMzYwMzgxMDE5MDYxMDA4MjkxOTA2MTAxZDU1NjViNjEwMGQ3NTY1YjAwNWI2MTAwOTE2MTAwZjA1NjViNjA0MDUxNjEwMDllOTE5MDYxMDIxMTU2NWI2MDQwNTE4MDkxMDM5MGYzNWI2MTAwYWY2MTAwZjY1NjViNjA0MDUxNjEwMGJjOTE5MDYxMDIxMTU2NWI2MDQwNTE4MDkxMDM5MGYzNWI4MTYwMDA4MTkwNTU1MDgwNjAwMTgxOTA1NTUwNTA1MDU2NWI4MDczZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjE2ZmY1YjYwMDE1NDgxNTY1YjYwMDA1NDgxNTY1YjYwMDA4MGZkNWI2MDAwODE5MDUwOTE5MDUwNTY1YjYxMDExNDgxNjEwMTAxNTY1YjgxMTQ2MTAxMWY1NzYwMDA4MGZkNWI1MDU2NWI2MDAwODEzNTkwNTA2MTAxMzE4MTYxMDEwYjU2NWI5MjkxNTA1MDU2NWI2MDAwODA2MDQwODM4NTAzMTIxNTYxMDE0ZTU3NjEwMTRkNjEwMGZjNTY1YjViNjAwMDYxMDE1Yzg1ODI4NjAxNjEwMTIyNTY1YjkyNTA1MDYwMjA2MTAxNmQ4NTgyODYwMTYxMDEyMjU2NWI5MTUwNTA5MjUwOTI5MDUwNTY1YjYwMDA3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmY4MjE2OTA1MDkxOTA1MDU2NWI2MDAwNjEwMWEyODI2MTAxNzc1NjViOTA1MDkxOTA1MDU2NWI2MTAxYjI4MTYxMDE5NzU2NWI4MTE0NjEwMWJkNTc2MDAwODBmZDViNTA1NjViNjAwMDgxMzU5MDUwNjEwMWNmODE2MTAxYTk1NjViOTI5MTUwNTA1NjViNjAwMDYwMjA4Mjg0MDMxMjE1NjEwMWViNTc2MTAxZWE2MTAwZmM1NjViNWI2MDAwNjEwMWY5ODQ4Mjg1MDE2MTAxYzA1NjViOTE1MDUwOTI5MTUwNTA1NjViNjEwMjBiODE2MTAxMDE1NjViODI1MjUwNTA1NjViNjAwMDYwMjA4MjAxOTA1MDYxMDIyNjYwMDA4MzAxODQ2MTAyMDI1NjViOTI5MTUwNTA1NmZlYTI2NDY5NzA2NjczNTgyMjEyMjA0NjJmNjM0OGJlN2RjNWQ0Y2IyZmRkMjE0MGFmOTQ3NzYzM2Y0MjJlMDg1ZmJmMWU1M2QxODlkMGUzYWQ1ODE5NjQ3MzZmNmM2MzQzMDAwODBjMDAzMw==","b64Record":"CiAIFiocCgwIARAMGgYIgK6ZpA8SDAgBEA8aBgiArpmkDxIw7Q/K6IY/u0j/YvNt1/RCpy1SodF4rVCXaWlnpSJa3EmpyZydTNrZGKlIn8qIZiw1GgwI1f3+rAYQhem1lwIiDwoJCJn9/qwGELELEgIYAiogw4PCrsOCwrfDg8K5dEY4w4LCrkrDg8KLw4PCkMODwo5SAA=="},{"b64Body":"Cg8KCQia/f6sBhCzCxICGAISAhgDGJb7rp0CIgIIeDIgw4PCrsOCwrfDg8K5dEY4w4LCrkrDg8KLw4PCkMODwo5CRQoDGKoIGiISINDQrTlsltb7ADTK6Mf0Kk00hbip90OATVTLdZrQHSwjIOCnEkIFCIDO2gNSAFoAagtjZWxsYXIgZG9vcg==","b64Record":"CiUIFiIDGKsIKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjBwG1dki3gH8BVmpWgIztoLklrRldbAffx7i1E3xvaC3K39592NNki9CEvqYjv6uiMaCwjW/f6sBhDD0IY8Ig8KCQia/f6sBhCzCxICGAIqIMODwq7DgsK3w4PCuXRGOMOCwq5Kw4PCi8ODwpDDg8KOMICFkAhClwcKAxirCBLiBGCAYEBSNIAVYQAQV2AAgP1bUGAENhBhAExXYAA1YOAcgGMasG7lFGEAUVeAY7nK6/QUYQBtV4BjwwNl0hRhAIlXgGPt7Zf6FGEAp1dbYACA/VthAGtgBIA2A4EBkGEAZpGQYQE3VlthAMVWWwBbYQCHYASANgOBAZBhAIKRkGEB1VZbYQDXVlsAW2EAkWEA8FZbYEBRYQCekZBhAhFWW2BAUYCRA5DzW2EAr2EA9lZbYEBRYQC8kZBhAhFWW2BAUYCRA5DzW4FgAIGQVVCAYAGBkFVQUFBWW4Bz//////////////////////////8W/1tgAVSBVltgAFSBVltgAID9W2AAgZBQkZBQVlthARSBYQEBVluBFGEBH1dgAID9W1BWW2AAgTWQUGEBMYFhAQtWW5KRUFBWW2AAgGBAg4UDEhVhAU5XYQFNYQD8VltbYABhAVyFgoYBYQEiVluSUFBgIGEBbYWChgFhASJWW5FQUJJQkpBQVltgAHP//////////////////////////4IWkFCRkFBWW2AAYQGigmEBd1ZbkFCRkFBWW2EBsoFhAZdWW4EUYQG9V2AAgP1bUFZbYACBNZBQYQHPgWEBqVZbkpFQUFZbYABgIIKEAxIVYQHrV2EB6mEA/FZbW2AAYQH5hIKFAWEBwFZbkVBQkpFQUFZbYQILgWEBAVZbglJQUFZbYABgIIIBkFBhAiZgAIMBhGECAlZbkpFQUFb+omRpcGZzWCISIEYvY0i+fcXUyy/dIUCvlHdjP0IuCF+/HlPRidDjrVgZZHNvbGNDAAgMADMigAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKIDTDjoDGKsIShYKFAAAAAAAAAAAAAAAAAAAAAAAAAQrcgcKAxirCBABUhYKCQoCGAIQ/4mgEAoJCgIYYhCAiqAQ"},{"b64Body":"Cg8KCQia/f6sBhC1CxICGAISAhgDGKCGlAoiAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjjpPCgMYqwgQ4KcSIkQasG7lAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKg==","b64Record":"CiUIFiIDGKsIKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjDOZ74j17DjGA2dE8kV9R/BXaEWg4zbiN5dgPwuDUNnKzDheZvEGQR5OjwikzRusNoaDAjW/f6sBhC2mcOgAiIPCgkImv3+rAYQtQsSAhgCKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjjCAhZAIOowCCgMYqwgigAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKIDTDlIWCgkKAhgCEP+JoBAKCQoCGGIQgIqgEA=="}]},"hscsEvm005TransferOfHBarsWorksBetweenContracts":{"placeholderNum":1068,"encodedItems":[{"b64Body":"Cg8KCQif/f6sBhDJCxICGAISAhgDGPuV9hQiAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjloxCiISIK+88/negFJShddfUeiBZ/wyDfMERB1GfcEgTrtgwqj0EIDIr6AlSgUIgM7aAw==","b64Record":"CiUIFhIDGK0IKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjDVsSXB8zWShheLCp69onjY9v5I8sJUPVaiPBoiwxZ9DJ1+4fq2RPLFAD1l49VdT6oaDAjb/f6sBhDx6L3BASIPCgkIn/3+rAYQyQsSAhgCKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjlIZCgoKAhgCEP+P38BKCgsKAxitCBCAkN/ASg=="},{"b64Body":"Cg8KCQif/f6sBhDLCxICGAISAhgDGP7LzSwiAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjooBjwESDAjby9mwBhCrq+KfAxptCiISIO98HF01ywERzNwNPtVjprPbbczHwQYhBIWmw6DbyhogCiM6IQJvuJsATTS9qz6BNYlTHQzrMVfu+gg9yAHCcSivQ45J1woiEiBP7DysLw7qU7t+D2BDuj5dGVYWTurj8uHddCRRcdRnnCIMSGVsbG8gV29ybGQhKgAyAA==","b64Record":"CiUIFhoDGK4IKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjC/lxZuGLJCBuFqQNClQxZRn5QKSpp79vFiSdM9svvurQhVaKjolhWUg3tttuumBNYaDAjb/f6sBhDS5L6mAyIPCgkIn/3+rAYQywsSAhgCKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjlIA"},{"b64Body":"Cg8KCQig/f6sBhDPCxICGAISAhgDGPOGlzoiAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjpoBghkKAxiuCCL6GDYwODA2MDQwNTI2MTA2MmE4MDYxMDAxMzYwMDAzOTYwMDBmM2ZlNjA4MDYwNDA1MjYwMDQzNjEwNjEwMDRhNTc2MDAwMzU2MGUwMWM4MDYzMjZhYjk3ZWExNDYxMDA0YzU3ODA2MzhkZDk4MDQzMTQ2MTAwOWE1NzgwNjNhY2VmNjAzNzE0NjEwMGM4NTc4MDYzZDJhMGZlMjYxNDYxMDExNjU3ODA2M2UxMmUzMjM4MTQ2MTAxYTQ1NzViMDA1YjYxMDA5ODYwMDQ4MDM2MDM2MDQwODExMDE1NjEwMDYyNTc2MDAwODBmZDViODEwMTkwODA4MDM1NzNmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmMTY5MDYwMjAwMTkwOTI5MTkwODAzNTkwNjAyMDAxOTA5MjkxOTA1MDUwNTA2MTAxZjI1NjViMDA1YjYxMDBjNjYwMDQ4MDM2MDM2MDIwODExMDE1NjEwMGIwNTc2MDAwODBmZDViODEwMTkwODA4MDM1OTA2MDIwMDE5MDkyOTE5MDUwNTA1MDYxMDQyMzU2NWIwMDViNjEwMTE0NjAwNDgwMzYwMzYwNDA4MTEwMTU2MTAwZGU1NzYwMDA4MGZkNWI4MTAxOTA4MDgwMzU3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNjkwNjAyMDAxOTA5MjkxOTA4MDM1OTA2MDIwMDE5MDkyOTE5MDUwNTA1MDYxMDQ2ZDU2NWIwMDViNjEwMWEyNjAwNDgwMzYwMzYwODA4MTEwMTU2MTAxMmM1NzYwMDA4MGZkNWI4MTAxOTA4MDgwMzU3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNjkwNjAyMDAxOTA5MjkxOTA4MDM1NzNmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmMTY5MDYwMjAwMTkwOTI5MTkwODAzNTczZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjE2OTA2MDIwMDE5MDkyOTE5MDgwMzU5MDYwMjAwMTkwOTI5MTkwNTA1MDUwNjEwNGI4NTY1YjAwNWI2MTAxZjA2MDA0ODAzNjAzNjA0MDgxMTAxNTYxMDFiYTU3NjAwMDgwZmQ1YjgxMDE5MDgwODAzNTczZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjE2OTA2MDIwMDE5MDkyOTE5MDgwMzU5MDYwMjAwMTkwOTI5MTkwNTA1MDUwNjEwNWE3NTY1YjAwNWI4MTczZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjE2NjEwOGZjODI5MDgxMTUwMjkwNjA0MDUxNjAwMDYwNDA1MTgwODMwMzgxODU4ODg4ZjE5MzUwNTA1MDUwMTU4MDE1NjEwMjM4NTczZDYwMDA4MDNlM2Q2MDAwZmQ1YjUwODE3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNjYxMDhmYzYwMDI4MzgxNjEwMjVkNTdmZTViMDQ5MDgxMTUwMjkwNjA0MDUxNjAwMDYwNDA1MTgwODMwMzgxODU4ODg4ZjE5MzUwNTA1MDUwMTU4MDE1NjEwMjg5NTczZDYwMDA4MDNlM2Q2MDAwZmQ1YjUwODE3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNjYxMDhmYzYwMDQ4MzgxNjEwMmFlNTdmZTViMDQ5MDgxMTUwMjkwNjA0MDUxNjAwMDYwNDA1MTgwODMwMzgxODU4ODg4ZjE5MzUwNTA1MDUwMTU4MDE1NjEwMmRhNTczZDYwMDA4MDNlM2Q2MDAwZmQ1YjUwODE3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNjYxMDhmYzYwMDg4MzgxNjEwMmZmNTdmZTViMDQ5MDgxMTUwMjkwNjA0MDUxNjAwMDYwNDA1MTgwODMwMzgxODU4ODg4ZjE5MzUwNTA1MDUwMTU4MDE1NjEwMzJiNTczZDYwMDA4MDNlM2Q2MDAwZmQ1YjUwODE3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNjYxMDhmYzYwMTA4MzgxNjEwMzUwNTdmZTViMDQ5MDgxMTUwMjkwNjA0MDUxNjAwMDYwNDA1MTgwODMwMzgxODU4ODg4ZjE5MzUwNTA1MDUwMTU4MDE1NjEwMzdjNTczZDYwMDA4MDNlM2Q2MDAwZmQ1YjUwODE3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNjYxMDhmYzYwMjA4MzgxNjEwM2ExNTdmZTViMDQ5MDgxMTUwMjkwNjA0MDUxNjAwMDYwNDA1MTgwODMwMzgxODU4ODg4ZjE5MzUwNTA1MDUwMTU4MDE1NjEwM2NkNTczZDYwMDA4MDNlM2Q2MDAwZmQ1YjUwODE3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNjYxMDhmYzYwNDA4MzgxNjEwM2YyNTdmZTViMDQ5MDgxMTUwMjkwNjA0MDUxNjAwMDYwNDA1MTgwODMwMzgxODU4ODg4ZjE5MzUwNTA1MDUwMTU4MDE1NjEwNDFlNTczZDYwMDA4MDNlM2Q2MDAwZmQ1YjUwNTA1MDU2NWIzMzczZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjE2NjEwOGZjODI5MDgxMTUwMjkwNjA0MDUxNjAwMDYwNDA1MTgwODMwMzgxODU4ODg4ZjE5MzUwNTA1MDUwMTU4MDE1NjEwNDY5NTczZDYwMDA4MDNlM2Q2MDAwZmQ1YjUwNTA1NjViODE3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNjYxMDhmYzgyOTA4MTE1MDI5MDYwNDA1MTYwMDA2MDQwNTE4MDgzMDM4MTg1ODg4OGYxOTM1MDUwNTA1MDE1ODAxNTYxMDRiMzU3M2Q2MDAwODAzZTNkNjAwMGZkNWI1MDUwNTA1NjViODM3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNjYxMDhmYzgyOTA4MTE1MDI5MDYwNDA1MTYwMDA2MDQwNTE4MDgzMDM4MTg1ODg4OGYxOTM1MDUwNTA1MDE1ODAxNTYxMDRmZTU3M2Q2MDAwODAzZTNkNjAwMGZkNWI1MDgyNzNmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmMTY2MTA4ZmM2MDAyODM4MTYxMDUyMzU3ZmU1YjA0OTA4MTE1MDI5MDYwNDA1MTYwMDA2MDQwNTE4MDgzMDM4MTg1ODg4OGYxOTM1MDUwNTA1MDE1ODAxNTYxMDU0ZjU3M2Q2MDAwODAzZTNkNjAwMGZkNWI1MDgxNzNmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmMTY2MTA4ZmM2MDA0ODM4MTYxMDU3NDU3ZmU1YjA0OTA4MTE1MDI5MDYwNDA1MTYwMDA2MDQwNTE4MDgzMDM4MTg1ODg4OGYxOTM1MDUwNTA1MDE1ODAxNTYxMDVhMDU3M2Q2MDAwODAzZTNkNjAwMGZkNWI1MDUwNTA1MDUwNTY1YjgxNzNmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmMTY2MTA4ZmM4MjYwMDAwMzkwODExNTAyOTA2MDQwNTE2MDAwNjA0MDUxODA4MzAzODE4NTg4ODhmMTkzNTA1MDUwNTAxNTgwMTU2MTA1ZjA1NzNkNjAwMDgwM2UzZDYwMDBmZDViNTA1MDUwNTZmZWEyNjU2MjdhN2E3MjMxNTgyMGNmNzkxYmJmNDdkY2I2MTM5Mzc0NDA1MmE2MjBmNzcxYjQyOWYwNzFmMDJlMzY1N2NlM2U1ZjgwYWIzZjk3YmQ2NDczNmY2YzYzNDMwMDA1MTEwMDMy","b64Record":"CiAIFiocCgwIARAMGgYIgK6ZpA8SDAgBEA8aBgiArpmkDxIwKTMzoXeRO2pg0o4ZJaUtOngezMn+5ugFC5z2ByEdrjFxwMfpeCPC7ruqE+A72NA4GgwI3P3+rAYQvvraygEiDwoJCKD9/qwGEM8LEgIYAiogw4PCrsOCwrfDg8K5dEY4w4LCrkrDg8KLw4PCkMODwo5SAA=="},{"b64Body":"ChAKCQig/f6sBhDRCxIDGK0IEgIYAxiW+66dAiICCHgyIMODwq7DgsK3w4PCuXRGOMOCwq5Kw4PCi8ODwpDDg8KOQkgKAxiuCBoiEiBzULg6Bd2we7aeP+iN3IMJz5FEn2R298slxe+5y3ms+yCQoQ8okE5CBQiAztoDUgBaAGoLY2VsbGFyIGRvb3I=","b64Record":"CiUIFiIDGK8IKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjBcXks92mXQY8Kt47+3jvnVUcIEjbelLeNEdvTMf/QwaWvVrbFq34D4vF9WayTRqoUaDAjc/f6sBhCLp4WwAyIQCgkIoP3+rAYQ0QsSAxitCCogw4PCrsOCwrfDg8K5dEY4w4LCrkrDg8KLw4PCkMODwo4w1tSRpAJC3w4KAxivCBKqDGCAYEBSYAQ2EGEASldgADVg4ByAYyarl+oUYQBMV4BjjdmAQxRhAJpXgGOs72A3FGEAyFeAY9Kg/iYUYQEWV4Bj4S4yOBRhAaRXWwBbYQCYYASANgNgQIEQFWEAYldgAID9W4EBkICANXP//////////////////////////xaQYCABkJKRkIA1kGAgAZCSkZBQUFBhAfJWWwBbYQDGYASANgNgIIEQFWEAsFdgAID9W4EBkICANZBgIAGQkpGQUFBQYQQjVlsAW2EBFGAEgDYDYECBEBVhAN5XYACA/VuBAZCAgDVz//////////////////////////8WkGAgAZCSkZCANZBgIAGQkpGQUFBQYQRtVlsAW2EBomAEgDYDYICBEBVhASxXYACA/VuBAZCAgDVz//////////////////////////8WkGAgAZCSkZCANXP//////////////////////////xaQYCABkJKRkIA1c///////////////////////////FpBgIAGQkpGQgDWQYCABkJKRkFBQUGEEuFZbAFthAfBgBIA2A2BAgRAVYQG6V2AAgP1bgQGQgIA1c///////////////////////////FpBgIAGQkpGQgDWQYCABkJKRkFBQUGEFp1ZbAFuBc///////////////////////////FmEI/IKQgRUCkGBAUWAAYEBRgIMDgYWIiPGTUFBQUBWAFWECOFc9YACAPj1gAP1bUIFz//////////////////////////8WYQj8YAKDgWECXVf+WwSQgRUCkGBAUWAAYEBRgIMDgYWIiPGTUFBQUBWAFWECiVc9YACAPj1gAP1bUIFz//////////////////////////8WYQj8YASDgWECrlf+WwSQgRUCkGBAUWAAYEBRgIMDgYWIiPGTUFBQUBWAFWEC2lc9YACAPj1gAP1bUIFz//////////////////////////8WYQj8YAiDgWEC/1f+WwSQgRUCkGBAUWAAYEBRgIMDgYWIiPGTUFBQUBWAFWEDK1c9YACAPj1gAP1bUIFz//////////////////////////8WYQj8YBCDgWEDUFf+WwSQgRUCkGBAUWAAYEBRgIMDgYWIiPGTUFBQUBWAFWEDfFc9YACAPj1gAP1bUIFz//////////////////////////8WYQj8YCCDgWEDoVf+WwSQgRUCkGBAUWAAYEBRgIMDgYWIiPGTUFBQUBWAFWEDzVc9YACAPj1gAP1bUIFz//////////////////////////8WYQj8YECDgWED8lf+WwSQgRUCkGBAUWAAYEBRgIMDgYWIiPGTUFBQUBWAFWEEHlc9YACAPj1gAP1bUFBQVlszc///////////////////////////FmEI/IKQgRUCkGBAUWAAYEBRgIMDgYWIiPGTUFBQUBWAFWEEaVc9YACAPj1gAP1bUFBWW4Fz//////////////////////////8WYQj8gpCBFQKQYEBRYABgQFGAgwOBhYiI8ZNQUFBQFYAVYQSzVz1gAIA+PWAA/VtQUFBWW4Nz//////////////////////////8WYQj8gpCBFQKQYEBRYABgQFGAgwOBhYiI8ZNQUFBQFYAVYQT+Vz1gAIA+PWAA/VtQgnP//////////////////////////xZhCPxgAoOBYQUjV/5bBJCBFQKQYEBRYABgQFGAgwOBhYiI8ZNQUFBQFYAVYQVPVz1gAIA+PWAA/VtQgXP//////////////////////////xZhCPxgBIOBYQV0V/5bBJCBFQKQYEBRYABgQFGAgwOBhYiI8ZNQUFBQFYAVYQWgVz1gAIA+PWAA/VtQUFBQUFZbgXP//////////////////////////xZhCPyCYAADkIEVApBgQFFgAGBAUYCDA4GFiIjxk1BQUFAVgBVhBfBXPWAAgD49YAD9W1BQUFb+omVienpyMVggz3kbv0fcthOTdEBSpiD3cbQp8HHwLjZXzj5fgKs/l71kc29sY0MABREAMiKAAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAowJoMOgMYrwhKFgoUAAAAAAAAAAAAAAAAAAAAAAAABC9yBwoDGK8IEAFSRwoJCgIYAxCY4okUCgoKAhhiEODciMYDCgoKAxigBhCatYg3CgoKAxihBhCatYg3CgsKAxitCBDLxaTIBAoJCgMYrwgQoJwB"},{"b64Body":"ChAKCQih/f6sBhDTCxIDGK0IEgIYAxiW+66dAiICCHgyIMODwq7DgsK3w4PCuXRGOMOCwq5Kw4PCi8ODwpDDg8KOQkgKAxiuCBoiEiAfpTxN7LmBWDOBBZV/fC/Y4sY/m40cvx/wUPQGBDc4iSCQoQ8okE5CBQiAztoDUgBaAGoLY2VsbGFyIGRvb3I=","b64Record":"CiUIFiIDGLAIKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjBuaiyZb8u9xo/RnHpF9zvmG6UzmWEysF9qpMhPPK7IMt0PKVTXzdI5Yi+4Eu7hj6saDAjd/f6sBhDdjavUASIQCgkIof3+rAYQ0wsSAxitCCogw4PCrsOCwrfDg8K5dEY4w4LCrkrDg8KLw4PCkMODwo4w1tSRpAJC3w4KAxiwCBKqDGCAYEBSYAQ2EGEASldgADVg4ByAYyarl+oUYQBMV4BjjdmAQxRhAJpXgGOs72A3FGEAyFeAY9Kg/iYUYQEWV4Bj4S4yOBRhAaRXWwBbYQCYYASANgNgQIEQFWEAYldgAID9W4EBkICANXP//////////////////////////xaQYCABkJKRkIA1kGAgAZCSkZBQUFBhAfJWWwBbYQDGYASANgNgIIEQFWEAsFdgAID9W4EBkICANZBgIAGQkpGQUFBQYQQjVlsAW2EBFGAEgDYDYECBEBVhAN5XYACA/VuBAZCAgDVz//////////////////////////8WkGAgAZCSkZCANZBgIAGQkpGQUFBQYQRtVlsAW2EBomAEgDYDYICBEBVhASxXYACA/VuBAZCAgDVz//////////////////////////8WkGAgAZCSkZCANXP//////////////////////////xaQYCABkJKRkIA1c///////////////////////////FpBgIAGQkpGQgDWQYCABkJKRkFBQUGEEuFZbAFthAfBgBIA2A2BAgRAVYQG6V2AAgP1bgQGQgIA1c///////////////////////////FpBgIAGQkpGQgDWQYCABkJKRkFBQUGEFp1ZbAFuBc///////////////////////////FmEI/IKQgRUCkGBAUWAAYEBRgIMDgYWIiPGTUFBQUBWAFWECOFc9YACAPj1gAP1bUIFz//////////////////////////8WYQj8YAKDgWECXVf+WwSQgRUCkGBAUWAAYEBRgIMDgYWIiPGTUFBQUBWAFWECiVc9YACAPj1gAP1bUIFz//////////////////////////8WYQj8YASDgWECrlf+WwSQgRUCkGBAUWAAYEBRgIMDgYWIiPGTUFBQUBWAFWEC2lc9YACAPj1gAP1bUIFz//////////////////////////8WYQj8YAiDgWEC/1f+WwSQgRUCkGBAUWAAYEBRgIMDgYWIiPGTUFBQUBWAFWEDK1c9YACAPj1gAP1bUIFz//////////////////////////8WYQj8YBCDgWEDUFf+WwSQgRUCkGBAUWAAYEBRgIMDgYWIiPGTUFBQUBWAFWEDfFc9YACAPj1gAP1bUIFz//////////////////////////8WYQj8YCCDgWEDoVf+WwSQgRUCkGBAUWAAYEBRgIMDgYWIiPGTUFBQUBWAFWEDzVc9YACAPj1gAP1bUIFz//////////////////////////8WYQj8YECDgWED8lf+WwSQgRUCkGBAUWAAYEBRgIMDgYWIiPGTUFBQUBWAFWEEHlc9YACAPj1gAP1bUFBQVlszc///////////////////////////FmEI/IKQgRUCkGBAUWAAYEBRgIMDgYWIiPGTUFBQUBWAFWEEaVc9YACAPj1gAP1bUFBWW4Fz//////////////////////////8WYQj8gpCBFQKQYEBRYABgQFGAgwOBhYiI8ZNQUFBQFYAVYQSzVz1gAIA+PWAA/VtQUFBWW4Nz//////////////////////////8WYQj8gpCBFQKQYEBRYABgQFGAgwOBhYiI8ZNQUFBQFYAVYQT+Vz1gAIA+PWAA/VtQgnP//////////////////////////xZhCPxgAoOBYQUjV/5bBJCBFQKQYEBRYABgQFGAgwOBhYiI8ZNQUFBQFYAVYQVPVz1gAIA+PWAA/VtQgXP//////////////////////////xZhCPxgBIOBYQV0V/5bBJCBFQKQYEBRYABgQFGAgwOBhYiI8ZNQUFBQFYAVYQWgVz1gAIA+PWAA/VtQUFBQUFZbgXP//////////////////////////xZhCPyCYAADkIEVApBgQFFgAGBAUYCDA4GFiIjxk1BQUFAVgBVhBfBXPWAAgD49YAD9W1BQUFb+omVienpyMVggz3kbv0fcthOTdEBSpiD3cbQp8HHwLjZXzj5fgKs/l71kc29sY0MABREAMiKAAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAowJoMOgMYsAhKFgoUAAAAAAAAAAAAAAAAAAAAAAAABDByBwoDGLAIEAFSRwoJCgIYAxCY4okUCgoKAhhiEODciMYDCgoKAxigBhCatYg3CgoKAxihBhCatYg3CgsKAxitCBDLxaTIBAoJCgMYsAgQoJwB"},{"b64Body":"ChAKCQih/f6sBhDnCxIDGK0IEgIYAxjgrLEDIgIIeDIgw4PCrsOCwrfDg8K5dEY4w4LCrkrDg8KLw4PCkMODwo46TwoDGK8IEKCNBiJErO9gNwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAo=","b64Record":"CiUIFiIDGK8IKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjDT2jUHpezsKb2DzareDLQpmy1f2q5gij0KUn+GCUbfUdzh+qanWJt3QA8Xa7iVSl0aDAjd/f6sBhCTs/bVAyIQCgkIof3+rAYQ5wsSAxitCCogw4PCrsOCwrfDg8K5dEY4w4LCrkrDg8KLw4PCkMODwo4wgNfaAjqMAgoDGK8IIoACAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACiA8QRSKQoJCgIYYhCArrUFCgoKAxitCBD/rbUFCgcKAxivCBATCgcKAxiwCBAU"}]},"hscsEvm006ContractHBarTransferToAccount":{"placeholderNum":1073,"encodedItems":[{"b64Body":"Cg8KCQim/f6sBhD3CxICGAISAhgDGPuV9hQiAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjloxCiISIE3xbitgOUtvr9NUI8zUDe3AtPXIshNgRLYBsb0Wqf6BEIDIr6AlSgUIgM7aAw==","b64Record":"CiUIFhIDGLIIKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjBqB+WBCzuXg81kofGESjBOW8VCZnDDg1om3iZr9go03a20sxTulTyOV+urldS76AAaDAji/f6sBhCgnePZAiIPCgkIpv3+rAYQ9wsSAhgCKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjlIZCgoKAhgCEP+P38BKCgsKAxiyCBCAkN/ASg=="},{"b64Body":"Cg8KCQin/f6sBhD5CxICGAISAhgDGPuV9hQiAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjlouCiISIFq8rGhwAwy08pRyFNRKPw8I+xrMSYuv23YuSvRVVhx2EJBOSgUIgM7aAw==","b64Record":"CiUIFhIDGLMIKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjBlyDOgi5N1KqBF7uG76uKrYGYADMvinI3ExcX6RDGPWFvCBm7W1Q4VrubAJmiaGOIaCwjj/f6sBhD3gaJiIg8KCQin/f6sBhD5CxICGAIqIMODwq7DgsK3w4PCuXRGOMOCwq5Kw4PCi8ODwpDDg8KOUhUKCAoCGAIQn5wBCgkKAxizCBCgnAE="},{"b64Body":"Cg8KCQin/f6sBhD7CxICGAISAhgDGP7LzSwiAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjooBjwESDAjjy9mwBhDCqvLNAhptCiISINCJLtZz0nkVQA55TpP2K/dh3kzq+LKTYKJiu+i+QZVUCiM6IQNvZ0nWg32yuy2q5x4sSDSqWYj/dOoJOkN+A0bU8W/l/woiEiBaXBeAYVd7Ce/UbZ8la4bpjO9AL3WUDjz3s4srqyzWmCIMSGVsbG8gV29ybGQhKgAyAA==","b64Record":"CiUIFhoDGLQIKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjAPKzpFea5ARn3nvyobeavgonLVxWdGipchJioQuuOk4Mfi7jkt0/7Zll4wzt214REaDAjj/f6sBhC1ubPjAiIPCgkIp/3+rAYQ+wsSAhgCKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjlIA"},{"b64Body":"Cg8KCQio/f6sBhD/CxICGAISAhgDGPOGlzoiAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjpoBghkKAxi0CCL6GDYwODA2MDQwNTI2MTA2MmE4MDYxMDAxMzYwMDAzOTYwMDBmM2ZlNjA4MDYwNDA1MjYwMDQzNjEwNjEwMDRhNTc2MDAwMzU2MGUwMWM4MDYzMjZhYjk3ZWExNDYxMDA0YzU3ODA2MzhkZDk4MDQzMTQ2MTAwOWE1NzgwNjNhY2VmNjAzNzE0NjEwMGM4NTc4MDYzZDJhMGZlMjYxNDYxMDExNjU3ODA2M2UxMmUzMjM4MTQ2MTAxYTQ1NzViMDA1YjYxMDA5ODYwMDQ4MDM2MDM2MDQwODExMDE1NjEwMDYyNTc2MDAwODBmZDViODEwMTkwODA4MDM1NzNmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmMTY5MDYwMjAwMTkwOTI5MTkwODAzNTkwNjAyMDAxOTA5MjkxOTA1MDUwNTA2MTAxZjI1NjViMDA1YjYxMDBjNjYwMDQ4MDM2MDM2MDIwODExMDE1NjEwMGIwNTc2MDAwODBmZDViODEwMTkwODA4MDM1OTA2MDIwMDE5MDkyOTE5MDUwNTA1MDYxMDQyMzU2NWIwMDViNjEwMTE0NjAwNDgwMzYwMzYwNDA4MTEwMTU2MTAwZGU1NzYwMDA4MGZkNWI4MTAxOTA4MDgwMzU3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNjkwNjAyMDAxOTA5MjkxOTA4MDM1OTA2MDIwMDE5MDkyOTE5MDUwNTA1MDYxMDQ2ZDU2NWIwMDViNjEwMWEyNjAwNDgwMzYwMzYwODA4MTEwMTU2MTAxMmM1NzYwMDA4MGZkNWI4MTAxOTA4MDgwMzU3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNjkwNjAyMDAxOTA5MjkxOTA4MDM1NzNmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmMTY5MDYwMjAwMTkwOTI5MTkwODAzNTczZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjE2OTA2MDIwMDE5MDkyOTE5MDgwMzU5MDYwMjAwMTkwOTI5MTkwNTA1MDUwNjEwNGI4NTY1YjAwNWI2MTAxZjA2MDA0ODAzNjAzNjA0MDgxMTAxNTYxMDFiYTU3NjAwMDgwZmQ1YjgxMDE5MDgwODAzNTczZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjE2OTA2MDIwMDE5MDkyOTE5MDgwMzU5MDYwMjAwMTkwOTI5MTkwNTA1MDUwNjEwNWE3NTY1YjAwNWI4MTczZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjE2NjEwOGZjODI5MDgxMTUwMjkwNjA0MDUxNjAwMDYwNDA1MTgwODMwMzgxODU4ODg4ZjE5MzUwNTA1MDUwMTU4MDE1NjEwMjM4NTczZDYwMDA4MDNlM2Q2MDAwZmQ1YjUwODE3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNjYxMDhmYzYwMDI4MzgxNjEwMjVkNTdmZTViMDQ5MDgxMTUwMjkwNjA0MDUxNjAwMDYwNDA1MTgwODMwMzgxODU4ODg4ZjE5MzUwNTA1MDUwMTU4MDE1NjEwMjg5NTczZDYwMDA4MDNlM2Q2MDAwZmQ1YjUwODE3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNjYxMDhmYzYwMDQ4MzgxNjEwMmFlNTdmZTViMDQ5MDgxMTUwMjkwNjA0MDUxNjAwMDYwNDA1MTgwODMwMzgxODU4ODg4ZjE5MzUwNTA1MDUwMTU4MDE1NjEwMmRhNTczZDYwMDA4MDNlM2Q2MDAwZmQ1YjUwODE3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNjYxMDhmYzYwMDg4MzgxNjEwMmZmNTdmZTViMDQ5MDgxMTUwMjkwNjA0MDUxNjAwMDYwNDA1MTgwODMwMzgxODU4ODg4ZjE5MzUwNTA1MDUwMTU4MDE1NjEwMzJiNTczZDYwMDA4MDNlM2Q2MDAwZmQ1YjUwODE3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNjYxMDhmYzYwMTA4MzgxNjEwMzUwNTdmZTViMDQ5MDgxMTUwMjkwNjA0MDUxNjAwMDYwNDA1MTgwODMwMzgxODU4ODg4ZjE5MzUwNTA1MDUwMTU4MDE1NjEwMzdjNTczZDYwMDA4MDNlM2Q2MDAwZmQ1YjUwODE3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNjYxMDhmYzYwMjA4MzgxNjEwM2ExNTdmZTViMDQ5MDgxMTUwMjkwNjA0MDUxNjAwMDYwNDA1MTgwODMwMzgxODU4ODg4ZjE5MzUwNTA1MDUwMTU4MDE1NjEwM2NkNTczZDYwMDA4MDNlM2Q2MDAwZmQ1YjUwODE3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNjYxMDhmYzYwNDA4MzgxNjEwM2YyNTdmZTViMDQ5MDgxMTUwMjkwNjA0MDUxNjAwMDYwNDA1MTgwODMwMzgxODU4ODg4ZjE5MzUwNTA1MDUwMTU4MDE1NjEwNDFlNTczZDYwMDA4MDNlM2Q2MDAwZmQ1YjUwNTA1MDU2NWIzMzczZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjE2NjEwOGZjODI5MDgxMTUwMjkwNjA0MDUxNjAwMDYwNDA1MTgwODMwMzgxODU4ODg4ZjE5MzUwNTA1MDUwMTU4MDE1NjEwNDY5NTczZDYwMDA4MDNlM2Q2MDAwZmQ1YjUwNTA1NjViODE3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNjYxMDhmYzgyOTA4MTE1MDI5MDYwNDA1MTYwMDA2MDQwNTE4MDgzMDM4MTg1ODg4OGYxOTM1MDUwNTA1MDE1ODAxNTYxMDRiMzU3M2Q2MDAwODAzZTNkNjAwMGZkNWI1MDUwNTA1NjViODM3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNjYxMDhmYzgyOTA4MTE1MDI5MDYwNDA1MTYwMDA2MDQwNTE4MDgzMDM4MTg1ODg4OGYxOTM1MDUwNTA1MDE1ODAxNTYxMDRmZTU3M2Q2MDAwODAzZTNkNjAwMGZkNWI1MDgyNzNmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmMTY2MTA4ZmM2MDAyODM4MTYxMDUyMzU3ZmU1YjA0OTA4MTE1MDI5MDYwNDA1MTYwMDA2MDQwNTE4MDgzMDM4MTg1ODg4OGYxOTM1MDUwNTA1MDE1ODAxNTYxMDU0ZjU3M2Q2MDAwODAzZTNkNjAwMGZkNWI1MDgxNzNmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmMTY2MTA4ZmM2MDA0ODM4MTYxMDU3NDU3ZmU1YjA0OTA4MTE1MDI5MDYwNDA1MTYwMDA2MDQwNTE4MDgzMDM4MTg1ODg4OGYxOTM1MDUwNTA1MDE1ODAxNTYxMDVhMDU3M2Q2MDAwODAzZTNkNjAwMGZkNWI1MDUwNTA1MDUwNTY1YjgxNzNmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmMTY2MTA4ZmM4MjYwMDAwMzkwODExNTAyOTA2MDQwNTE2MDAwNjA0MDUxODA4MzAzODE4NTg4ODhmMTkzNTA1MDUwNTAxNTgwMTU2MTA1ZjA1NzNkNjAwMDgwM2UzZDYwMDBmZDViNTA1MDUwNTZmZWEyNjU2MjdhN2E3MjMxNTgyMGNmNzkxYmJmNDdkY2I2MTM5Mzc0NDA1MmE2MjBmNzcxYjQyOWYwNzFmMDJlMzY1N2NlM2U1ZjgwYWIzZjk3YmQ2NDczNmY2YzYzNDMwMDA1MTEwMDMy","b64Record":"CiAIFiocCgwIARAMGgYIgK6ZpA8SDAgBEA8aBgiArpmkDxIwyzQpPYlZorVeqm8eYiqRxaEdfj4XZuUfmNga9Wr1mbJy121/mj5Pv+NcR3OuZCt+GgsI5P3+rAYQ4s+zayIPCgkIqP3+rAYQ/wsSAhgCKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjlIA"},{"b64Body":"ChAKCQio/f6sBhCBDBIDGLIIEgIYAxiW+66dAiICCHgyIMODwq7DgsK3w4PCuXRGOMOCwq5Kw4PCi8ODwpDDg8KOQkgKAxi0CBoiEiDftM443T5Bd+xzLaiHMF5kN2h4FqoyVTD4z9RkKQ1YdCCQoQ8okE5CBQiAztoDUgBaAGoLY2VsbGFyIGRvb3I=","b64Record":"CiUIFiIDGLUIKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjBQfP9rji0oG1h2pjZa8qnra9rh/juxN+UAjx7E36/CZRuV76VTjyncqqexDoIe+30aDAjk/f6sBhDOooftAiIQCgkIqP3+rAYQgQwSAxiyCCogw4PCrsOCwrfDg8K5dEY4w4LCrkrDg8KLw4PCkMODwo4w1tSRpAJC3w4KAxi1CBKqDGCAYEBSYAQ2EGEASldgADVg4ByAYyarl+oUYQBMV4BjjdmAQxRhAJpXgGOs72A3FGEAyFeAY9Kg/iYUYQEWV4Bj4S4yOBRhAaRXWwBbYQCYYASANgNgQIEQFWEAYldgAID9W4EBkICANXP//////////////////////////xaQYCABkJKRkIA1kGAgAZCSkZBQUFBhAfJWWwBbYQDGYASANgNgIIEQFWEAsFdgAID9W4EBkICANZBgIAGQkpGQUFBQYQQjVlsAW2EBFGAEgDYDYECBEBVhAN5XYACA/VuBAZCAgDVz//////////////////////////8WkGAgAZCSkZCANZBgIAGQkpGQUFBQYQRtVlsAW2EBomAEgDYDYICBEBVhASxXYACA/VuBAZCAgDVz//////////////////////////8WkGAgAZCSkZCANXP//////////////////////////xaQYCABkJKRkIA1c///////////////////////////FpBgIAGQkpGQgDWQYCABkJKRkFBQUGEEuFZbAFthAfBgBIA2A2BAgRAVYQG6V2AAgP1bgQGQgIA1c///////////////////////////FpBgIAGQkpGQgDWQYCABkJKRkFBQUGEFp1ZbAFuBc///////////////////////////FmEI/IKQgRUCkGBAUWAAYEBRgIMDgYWIiPGTUFBQUBWAFWECOFc9YACAPj1gAP1bUIFz//////////////////////////8WYQj8YAKDgWECXVf+WwSQgRUCkGBAUWAAYEBRgIMDgYWIiPGTUFBQUBWAFWECiVc9YACAPj1gAP1bUIFz//////////////////////////8WYQj8YASDgWECrlf+WwSQgRUCkGBAUWAAYEBRgIMDgYWIiPGTUFBQUBWAFWEC2lc9YACAPj1gAP1bUIFz//////////////////////////8WYQj8YAiDgWEC/1f+WwSQgRUCkGBAUWAAYEBRgIMDgYWIiPGTUFBQUBWAFWEDK1c9YACAPj1gAP1bUIFz//////////////////////////8WYQj8YBCDgWEDUFf+WwSQgRUCkGBAUWAAYEBRgIMDgYWIiPGTUFBQUBWAFWEDfFc9YACAPj1gAP1bUIFz//////////////////////////8WYQj8YCCDgWEDoVf+WwSQgRUCkGBAUWAAYEBRgIMDgYWIiPGTUFBQUBWAFWEDzVc9YACAPj1gAP1bUIFz//////////////////////////8WYQj8YECDgWED8lf+WwSQgRUCkGBAUWAAYEBRgIMDgYWIiPGTUFBQUBWAFWEEHlc9YACAPj1gAP1bUFBQVlszc///////////////////////////FmEI/IKQgRUCkGBAUWAAYEBRgIMDgYWIiPGTUFBQUBWAFWEEaVc9YACAPj1gAP1bUFBWW4Fz//////////////////////////8WYQj8gpCBFQKQYEBRYABgQFGAgwOBhYiI8ZNQUFBQFYAVYQSzVz1gAIA+PWAA/VtQUFBWW4Nz//////////////////////////8WYQj8gpCBFQKQYEBRYABgQFGAgwOBhYiI8ZNQUFBQFYAVYQT+Vz1gAIA+PWAA/VtQgnP//////////////////////////xZhCPxgAoOBYQUjV/5bBJCBFQKQYEBRYABgQFGAgwOBhYiI8ZNQUFBQFYAVYQVPVz1gAIA+PWAA/VtQgXP//////////////////////////xZhCPxgBIOBYQV0V/5bBJCBFQKQYEBRYABgQFGAgwOBhYiI8ZNQUFBQFYAVYQWgVz1gAIA+PWAA/VtQUFBQUFZbgXP//////////////////////////xZhCPyCYAADkIEVApBgQFFgAGBAUYCDA4GFiIjxk1BQUFAVgBVhBfBXPWAAgD49YAD9W1BQUFb+omVienpyMVggz3kbv0fcthOTdEBSpiD3cbQp8HHwLjZXzj5fgKs/l71kc29sY0MABREAMiKAAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAowJoMOgMYtQhKFgoUAAAAAAAAAAAAAAAAAAAAAAAABDVyBwoDGLUIEAFSRwoJCgIYAxCY4okUCgoKAhhiEODciMYDCgoKAxigBhCatYg3CgoKAxihBhCatYg3CgsKAxiyCBDLxaTIBAoJCgMYtQgQoJwB"},{"b64Body":"ChAKCQip/f6sBhCVDBIDGLIIEgIYAxjgrLEDIgIIeDIgw4PCrsOCwrfDg8K5dEY4w4LCrkrDg8KLw4PCkMODwo46TwoDGLUIEKCNBiJErO9gNwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQzAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAo=","b64Record":"CiUIFiIDGLUIKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjBN2OEPrgrTG21G3o+11B41J3tpQ4aicZ53B4csrVE2BKtL7wDeq+gdtXCAwyv1oOwaDAjl/f6sBhDZ2suRASIQCgkIqf3+rAYQlQwSAxiyCCogw4PCrsOCwrfDg8K5dEY4w4LCrkrDg8KLw4PCkMODwo4wgNfaAjqMAgoDGLUIIoACAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACiA8QRSKQoJCgIYYhCArrUFCgoKAxiyCBD/rbUFCgcKAxizCBAUCgcKAxi1CBAT"}]},"hscsEvm005TransfersWithSubLevelCallsBetweenContracts":{"placeholderNum":1078,"encodedItems":[{"b64Body":"Cg8KCQiu/f6sBhClDBICGAISAhgDGPuV9hQiAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjloxCiISINNsINDLW9vDRCwDOtC1z1tVezUUBBtvFENsgRHEkv89EIDIr6AlSgUIgM7aAw==","b64Record":"CiUIFhIDGLcIKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjCraGU/sScgaPUR5yqnwcF8Nf99ZD8BHnPt1Yx34ZxP8xXiw4JkSuIatPOR0/LS4CEaCwjq/f6sBhC9m88aIg8KCQiu/f6sBhClDBICGAIqIMODwq7DgsK3w4PCuXRGOMOCwq5Kw4PCi8ODwpDDg8KOUhkKCgoCGAIQ/4/fwEoKCwoDGLcIEICQ38BK"},{"b64Body":"Cg8KCQiu/f6sBhCnDBICGAISAhgDGP7LzSwiAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjooBjwESDAjqy9mwBhCo05L0ARptCiISIPQRfnBOcYQ0BgotiIlo5gKx8X6NegYSJjqxL568E734CiM6IQLgwM0ySpR5yBV6NWU5Iw424dpa/86t6ybv5EtzPYxLWAoiEiCxll93a4PpzkM+4wiS6r6hJUPMlbnnV9LdvgeYssFhtSIMSGVsbG8gV29ybGQhKgAyAA==","b64Record":"CiUIFhoDGLgIKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjDObX4FzILY8SKSTYoy7pd6jnK8hBr2CjF9gPxUkgDuVfiEauaNQ4tszeYWgMbKrfQaDAjq/f6sBhC/0sr/ASIPCgkIrv3+rAYQpwwSAhgCKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjlIA"},{"b64Body":"Cg8KCQiv/f6sBhCrDBICGAISAhgDGMGu0DQiAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjpoBiA8KAxi4CCKADzYwODA2MDQwNTI2MTAzYWQ4MDYxMDAxMzYwMDAzOTYwMDBmM2ZlNjA4MDYwNDA1MjYwMDQzNjEwNjEwMDQzNTc2MDAwMzU2MGUwMWM4MDYzMTBjN2NmOWExNDYxMDA0ZjU3ODA2M2JmNTM1MTI1MTQ2MTAwNzg1NzgwNjNjMzA2ZjliMTE0NjEwMDk2NTc4MDYzZGI4YTViZjYxNDYxMDBiZjU3NjEwMDRhNTY1YjM2NjEwMDRhNTcwMDViNjAwMDgwZmQ1YjM0ODAxNTYxMDA1YjU3NjAwMDgwZmQ1YjUwNjEwMDc2NjAwNDgwMzYwMzgxMDE5MDYxMDA3MTkxOTA2MTAyN2U1NjViNjEwMGVhNTY1YjAwNWI2MTAwODA2MTAxNjE1NjViNjA0MDUxNjEwMDhkOTE5MDYxMDJjZDU2NWI2MDQwNTE4MDkxMDM5MGYzNWIzNDgwMTU2MTAwYTI1NzYwMDA4MGZkNWI1MDYxMDBiZDYwMDQ4MDM2MDM4MTAxOTA2MTAwYjg5MTkwNjEwMjdlNTY1YjYxMDE2OTU2NWIwMDViMzQ4MDE1NjEwMGNiNTc2MDAwODBmZDViNTA2MTAwZDQ2MTAxZTA1NjViNjA0MDUxNjEwMGUxOTE5MDYxMDMwMzU2NWI2MDQwNTE4MDkxMDM5MGYzNWI4MTczZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjE2NjM1OThmZGViODgyNjA0MDUxODI2M2ZmZmZmZmZmMTY2MGUwMWI4MTUyNjAwNDAxNjAyMDYwNDA1MTgwODMwMzgxODU4ODVhZjExNTgwMTU2MTAxMzc1NzNkNjAwMDgwM2UzZDYwMDBmZDViNTA1MDUwNTA1MDYwNDA1MTNkNjAxZjE5NjAxZjgyMDExNjgyMDE4MDYwNDA1MjUwODEwMTkwNjEwMTVjOTE5MDYxMDM0YTU2NWI1MDUwNTA1NjViNjAwMDM0OTA1MDkwNTY1YjgxNzNmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmMTY2MzU5OGZkZWI4ODI2MDQwNTE4MjYzZmZmZmZmZmYxNjYwZTAxYjgxNTI2MDA0MDE2MDIwNjA0MDUxODA4MzAzODE4NTg4NWFmMTE1ODAxNTYxMDFiNjU3M2Q2MDAwODAzZTNkNjAwMGZkNWI1MDUwNTA1MDUwNjA0MDUxM2Q2MDFmMTk2MDFmODIwMTE2ODIwMTgwNjA0MDUyNTA4MTAxOTA2MTAxZGI5MTkwNjEwMzRhNTY1YjUwNTA1MDU2NWI2MDAwOTA1NjViNjAwMDgwZmQ1YjYwMDA3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmY4MjE2OTA1MDkxOTA1MDU2NWI2MDAwNjEwMjE1ODI2MTAxZWE1NjViOTA1MDkxOTA1MDU2NWI2MTAyMjU4MTYxMDIwYTU2NWI4MTE0NjEwMjMwNTc2MDAwODBmZDViNTA1NjViNjAwMDgxMzU5MDUwNjEwMjQyODE2MTAyMWM1NjViOTI5MTUwNTA1NjViNjAwMDgxOTA1MDkxOTA1MDU2NWI2MTAyNWI4MTYxMDI0ODU2NWI4MTE0NjEwMjY2NTc2MDAwODBmZDViNTA1NjViNjAwMDgxMzU5MDUwNjEwMjc4ODE2MTAyNTI1NjViOTI5MTUwNTA1NjViNjAwMDgwNjA0MDgzODUwMzEyMTU2MTAyOTU1NzYxMDI5NDYxMDFlNTU2NWI1YjYwMDA2MTAyYTM4NTgyODYwMTYxMDIzMzU2NWI5MjUwNTA2MDIwNjEwMmI0ODU4Mjg2MDE2MTAyNjk1NjViOTE1MDUwOTI1MDkyOTA1MDU2NWI2MTAyYzc4MTYxMDI0ODU2NWI4MjUyNTA1MDU2NWI2MDAwNjAyMDgyMDE5MDUwNjEwMmUyNjAwMDgzMDE4NDYxMDJiZTU2NWI5MjkxNTA1MDU2NWI2MDAwODExNTE1OTA1MDkxOTA1MDU2NWI2MTAyZmQ4MTYxMDJlODU2NWI4MjUyNTA1MDU2NWI2MDAwNjAyMDgyMDE5MDUwNjEwMzE4NjAwMDgzMDE4NDYxMDJmNDU2NWI5MjkxNTA1MDU2NWI2MTAzMjc4MTYxMDJlODU2NWI4MTE0NjEwMzMyNTc2MDAwODBmZDViNTA1NjViNjAwMDgxNTE5MDUwNjEwMzQ0ODE2MTAzMWU1NjViOTI5MTUwNTA1NjViNjAwMDYwMjA4Mjg0MDMxMjE1NjEwMzYwNTc2MTAzNWY2MTAxZTU1NjViNWI2MDAwNjEwMzZlODQ4Mjg1MDE2MTAzMzU1NjViOTE1MDUwOTI5MTUwNTA1NmZlYTI2NDY5NzA2NjczNTgyMjEyMjA1ZTRiYTVlNjlmODM0Y2Y5Yzc0ZDZhMjU4YTA2NGRlNWU1OGJmNTNhZDE5MGRkMWQ4YTFiMGUzYjUzMDE1MWIyNjQ3MzZmNmM2MzQzMDAwODBjMDAzMw==","b64Record":"CiAIFiocCgwIARAMGgYIgK6ZpA8SDAgBEA8aBgiArpmkDxIwIzlSLV+L2CdqzXYt7fSr0pY+6tTMmvb/gu41tTJRPEh2BwvZXjrYlPzKLnk6D9QHGgsI6/3+rAYQ0O2CJCIPCgkIr/3+rAYQqwwSAhgCKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjlIA"},{"b64Body":"Cg8KCQiv/f6sBhCtDBICGAISAhgDGP7LzSwiAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjooBjwESDAjry9mwBhDT5J7+ARptCiISIAWgQD7xmTzjAOQiOEAEU/6cgqR2O8S90Ns+rcsUzR+gCiM6IQPYkmI087f7NBKvZLD7Iyrqr35oWCa49QFtj93a4R6ZEAoiEiAhewwKmPtbSWKDq9MVMgxzX0SFeMyNu60c7R6EBzP+0iIMSGVsbG8gV29ybGQhKgAyAA==","b64Record":"CiUIFhoDGLkIKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjAXhm6yLo/N2ZAG0lym+v7n9n/yCzZmh3fx5nZJUC7J7HI9Toz7qHokXY2tA6LOUGgaDAjr/f6sBhCwm/WIAiIPCgkIr/3+rAYQrQwSAhgCKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjlIA"},{"b64Body":"Cg8KCQiw/f6sBhCxDBICGAISAhgDGNKPzS0iAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjpoBvAIKAxi5CCK0AjYwODA2MDQwNTI2MDg5ODA2MDExNjAwMDM5NjAwMGYzZmU2MDgwNjA0MDUyNjAwNDM2MTA2MDI1NTc2MDAwMzU2MGUwMWM4MDYyMzAxNDZlMTQ2MDJhNTc4MDYzNTk4ZmRlYjgxNDYwNGQ1NzViNjAwMDgwZmQ1YjM0ODAxNTYwMzU1NzYwMDA4MGZkNWI1MDYwMDE1YjYwNDA1MTkwMTUxNTgxNTI2MDIwMDE2MDQwNTE4MDkxMDM5MGYzNWI2MDAxNjAzOTU2ZmVhMjY0Njk3MDY2NzM1ODIyMTIyMGM0YjY3OTY0MmYzYjFmN2UxNGJlNzY2ZmVhNTlhNWQ4ZmY3MWM1YWEyZjg2MjMzYWRhY2ZmNWM1YmFjNDI4ZDg2NDczNmY2YzYzNDMwMDA4MDcwMDMz","b64Record":"CiAIFiocCgwIARAMGgYIgK6ZpA8SDAgBEA8aBgiArpmkDxIwAttWkIax7qesxq9MDKjosX3tkZCPFrTMO4Sp/zhhIxq/jhWqA5eskMWe790zWWOZGgsI7P3+rAYQssSsLSIPCgkIsP3+rAYQsQwSAhgCKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjlIA"},{"b64Body":"ChAKCQiw/f6sBhCzDBIDGLcIEgIYAxiW+66dAiICCHgyIMODwq7DgsK3w4PCuXRGOMOCwq5Kw4PCi8ODwpDDg8KOQkcKAxi4CBoiEiBywi9BwYlRGUHWMVaJl2f6tDSZVs0H9ZW9gN5qaKEnBiCQoQ8oZEIFCIDO2gNSAFoAagtjZWxsYXIgZG9vcg==","b64Record":"CiUIFiIDGLoIKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjDsufndYtyEtKEXaoBOzV6Y5mpxPlL8e3IlnGpefud/VTLgKzCSdO/L2+5z816CkcUaDAjs/f6sBhDavqeSAiIQCgkIsP3+rAYQswwSAxi3CCogw4PCrsOCwrfDg8K5dEY4w4LCrkrDg8KLw4PCkMODwo4w1tSRpAJC4gkKAxi6CBKtB2CAYEBSYAQ2EGEAQ1dgADVg4ByAYxDHz5oUYQBPV4Bjv1NRJRRhAHhXgGPDBvmxFGEAlleAY9uKW/YUYQC/V2EASlZbNmEASlcAW2AAgP1bNIAVYQBbV2AAgP1bUGEAdmAEgDYDgQGQYQBxkZBhAn5WW2EA6lZbAFthAIBhAWFWW2BAUWEAjZGQYQLNVltgQFGAkQOQ81s0gBVhAKJXYACA/VtQYQC9YASANgOBAZBhALiRkGECflZbYQFpVlsAWzSAFWEAy1dgAID9W1BhANRhAeBWW2BAUWEA4ZGQYQMDVltgQFGAkQOQ81uBc///////////////////////////FmNZj964gmBAUYJj/////xZg4BuBUmAEAWAgYEBRgIMDgYWIWvEVgBVhATdXPWAAgD49YAD9W1BQUFBQYEBRPWAfGWAfggEWggGAYEBSUIEBkGEBXJGQYQNKVltQUFBWW2AANJBQkFZbgXP//////////////////////////xZjWY/euIJgQFGCY/////8WYOAbgVJgBAFgIGBAUYCDA4GFiFrxFYAVYQG2Vz1gAIA+PWAA/VtQUFBQUGBAUT1gHxlgH4IBFoIBgGBAUlCBAZBhAduRkGEDSlZbUFBQVltgAJBWW2AAgP1bYABz//////////////////////////+CFpBQkZBQVltgAGECFYJhAepWW5BQkZBQVlthAiWBYQIKVluBFGECMFdgAID9W1BWW2AAgTWQUGECQoFhAhxWW5KRUFBWW2AAgZBQkZBQVlthAluBYQJIVluBFGECZldgAID9W1BWW2AAgTWQUGECeIFhAlJWW5KRUFBWW2AAgGBAg4UDEhVhApVXYQKUYQHlVltbYABhAqOFgoYBYQIzVluSUFBgIGECtIWChgFhAmlWW5FQUJJQkpBQVlthAseBYQJIVluCUlBQVltgAGAgggGQUGEC4mAAgwGEYQK+VluSkVBQVltgAIEVFZBQkZBQVlthAv2BYQLoVluCUlBQVltgAGAgggGQUGEDGGAAgwGEYQL0VluSkVBQVlthAyeBYQLoVluBFGEDMldgAID9W1BWW2AAgVGQUGEDRIFhAx5WW5KRUFBWW2AAYCCChAMSFWEDYFdhA19hAeVWW1tgAGEDboSChQFhAzVWW5FQUJKRUFBW/qJkaXBmc1giEiBeS6Xmn4NM+cdNaiWKBk3l5Yv1OtGQ3R2KGw47UwFRsmRzb2xjQwAIDAAzIoACAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACjAmgw6Axi6CEoWChQAAAAAAAAAAAAAAAAAAAAAAAAEOnIHCgMYuggQAVJGCgkKAhgDEJjiiRQKCgoCGGIQ4NyIxgMKCgoDGKAGEJq1iDcKCgoDGKEGEJq1iDcKCwoDGLcIEPOqo8gECggKAxi6CBDIAQ=="},{"b64Body":"ChAKCQix/f6sBhC1DBIDGLcIEgIYAxiW+66dAiICCHgyIMODwq7DgsK3w4PCuXRGOMOCwq5Kw4PCi8ODwpDDg8KOQkcKAxi5CBoiEiDrkEs3q57WiG7QePD1aURaQSABIgGb8pYMPL6SWcD2wSCQoQ8oZEIFCIDO2gNSAFoAagtjZWxsYXIgZG9vcg==","b64Record":"CiUIFiIDGLsIKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjAI+44oZjKHAA95Fdlf2BxVjPTapNEohXUlhEMPY0Bk8t7HKswOk0CxhPxUupRW8C4aCwjt/f6sBhCA+4o3IhAKCQix/f6sBhC1DBIDGLcIKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjjDW1JGkAkK+AwoDGLsIEokBYIBgQFJgBDYQYCVXYAA1YOAcgGIwFG4UYCpXgGNZj964FGBNV1tgAID9WzSAFWA1V2AAgP1bUGABW2BAUZAVFYFSYCABYEBRgJEDkPNbYAFgOVb+omRpcGZzWCISIMS2eWQvOx9+FL52b+pZpdj/ccWqL4YjOtrP9cW6xCjYZHNvbGNDAAgHADMigAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKMCaDDoDGLsIShYKFAAAAAAAAAAAAAAAAAAAAAAAAAQ7cgcKAxi7CBABUkYKCQoCGAMQmOKJFAoKCgIYYhDg3IjGAwoKCgMYoAYQmrWINwoKCgMYoQYQmrWINwoLCgMYtwgQ86qjyAQKCAoDGLsIEMgB"},{"b64Body":"ChAKCQix/f6sBhC3DBIDGLcIEgIYAxjgrLEDIgIIeDIgw4PCrsOCwrfDg8K5dEY4w4LCrkrDg8KLw4PCkMODwo46CwoDGLoIEKCNBhgK","b64Record":"CiUIFiIDGLoIKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjDMVkqHY7TOZXt18dwdrl9299MXDiFt76buAq6345km5BdJcUQ+TtpCNw3Rm2AzXhAaDAjt/f6sBhDP/IucAiIQCgkIsf3+rAYQtwwSAxi3CCogw4PCrsOCwrfDg8K5dEY4w4LCrkrDg8KLw4PCkMODwo4wgNfaAjqMAgoDGLoIIoACAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACiA8QRSIAoJCgIYYhCArrUFCgoKAxi3CBCTrrUFCgcKAxi6CBAU"},{"b64Body":"ChAKCQiy/f6sBhC5DBIDGLcIEgIYAxjgrLEDIgIIeDIgw4PCrsOCwrfDg8K5dEY4w4LCrkrDg8KLw4PCkMODwo46EQoDGLoIEKCNBhgKIgS/U1El","b64Record":"CiUIFiIDGLoIKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjDbBkAYyAZ2xpoCkrJ4XJ5nhtvnDk0p0TvRINBYyqiUY4t5cUGW3cZ4zIrRU4fAia0aCwju/f6sBhCdoNhAIhAKCQiy/f6sBhC5DBIDGLcIKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjjCA19oCOq4CCgMYuggSIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKIoACAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACiA8QRSIAoJCgIYYhCArrUFCgoKAxi3CBCTrrUFCgcKAxi6CBAU"},{"b64Body":"ChAKCQiy/f6sBhC7DBIDGLcIEgIYAxjgrLEDIgIIeDIgw4PCrsOCwrfDg8K5dEY4w4LCrkrDg8KLw4PCkMODwo46EQoDGLoIEKCNBhgKIgTbilv2","b64Record":"CiUIISIDGLoIKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjBv870O7T0QUVWzhfyFwznq0iEyo25G5DKiA7JbZicEQR4oKwjaQrpK3QwtOyvPPIwaDAju/f6sBhDEt9ClAiIQCgkIsv3+rAYQuwwSAxi3CCogw4PCrsOCwrfDg8K5dEY4w4LCrkrDg8KLw4PCkMODwo4wgNfaAjoIGgIweCiA8QRSFwoJCgIYYhCArrUFCgoKAxi3CBD/rbUF"},{"b64Body":"Cg8KCQiz/f6sBhDJDBICGAISAhgDGOCssQMiAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjjpPCgMYuwgQoI0GIkQQx8+aAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABDsAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFA==","b64Record":"CiUIISIDGLsIKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjCMPyhWethMtrZ1zR6DNihd/refxVB79rP1QVV8b3T/5DkNuAZ/PXiYLW3nLEPwChEaCwjv/f6sBhCW8ZZKIg8KCQiz/f6sBhDJDBICGAIqIMODwq7DgsK3w4PCuXRGOMOCwq5Kw4PCi8ODwpDDg8KOMIDX2gI6CBoCMHgogPEEUhYKCQoCGAIQ/621BQoJCgIYYhCArrUF"},{"b64Body":"Cg8KCQiz/f6sBhDLDBICGAISAhgDGOCssQMiAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjjpPCgMYuggQoI0GIkTDBvmxAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABDsAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFA==","b64Record":"CiUIFiIDGLoIKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjBY6sXtae14Q289KpP5s/xiqNKG1coIKahXg7hXwREFhSOADeBrfuri2tCNFVIG3oIaDAjv/f6sBhCsnYevAiIPCgkIs/3+rAYQywwSAhgCKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjjCA19oCOowCCgMYuggigAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKIDxBFIoCgkKAhgCEP+ttQUKCQoCGGIQgK61BQoHCgMYuggQJwoHCgMYuwgQKA=="}]},"hscsEvm010MultiSignatureAccounts":{"placeholderNum":1084,"encodedItems":[{"b64Body":"Cg8KCQi4/f6sBhDbDBICGAISAhgDGMPM/xUiAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjlpZCkoySAoiEiA8qVYjJEmAGwZhuYzdMhaC7r0qPULCtjw0K+12beYKwQoiEiDVAyv+4aglsIW8UC5/xMklzo6kNW2GsRaZsJP2kDVLfxCAyK+gJUoFCIDO2gM=","b64Record":"CiUIFhIDGL0IKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjAZs4LKyjoLsEvyx7SIBolCVI2iNZHmQ6vz/z0GWAbD1DafmVHFFQr00ctec9372CMaDAj0/f6sBhDQvZizASIPCgkIuP3+rAYQ2wwSAhgCKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjlIZCgoKAhgCEP+P38BKCgsKAxi9CBCAkN/ASg=="},{"b64Body":"Cg8KCQi4/f6sBhDdDBICGAISAhgDGP7LzSwiAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjooBjwESDAj0y9mwBhCp8s6MAxptCiISILYLVTc3RFAg72XgtZtjBQ8fKrhbMphHpC5q2bXtIJXoCiM6IQPTzkemanZNEQqtHVC09XB7DpXOVF7a7bACrVMU+ZbLvgoiEiBdQ317UmangY0vFnMl6jpJP+Roifoxpwest7ZKTjxRgyIMSGVsbG8gV29ybGQhKgAyAA==","b64Record":"CiUIFhoDGL4IKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjAUhLSSP7c/7k/aZBqUJsu1UR6anZ70gZyrejrARno4LRHQ5qZDkCgExpzWderZPL4aDAj0/f6sBhDqnYGYAyIPCgkIuP3+rAYQ3QwSAhgCKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjlIA"},{"b64Body":"Cg8KCQi5/f6sBhDhDBICGAISAhgDGPOGlzoiAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjpoBghkKAxi+CCL6GDYwODA2MDQwNTI2MTA2MmE4MDYxMDAxMzYwMDAzOTYwMDBmM2ZlNjA4MDYwNDA1MjYwMDQzNjEwNjEwMDRhNTc2MDAwMzU2MGUwMWM4MDYzMjZhYjk3ZWExNDYxMDA0YzU3ODA2MzhkZDk4MDQzMTQ2MTAwOWE1NzgwNjNhY2VmNjAzNzE0NjEwMGM4NTc4MDYzZDJhMGZlMjYxNDYxMDExNjU3ODA2M2UxMmUzMjM4MTQ2MTAxYTQ1NzViMDA1YjYxMDA5ODYwMDQ4MDM2MDM2MDQwODExMDE1NjEwMDYyNTc2MDAwODBmZDViODEwMTkwODA4MDM1NzNmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmMTY5MDYwMjAwMTkwOTI5MTkwODAzNTkwNjAyMDAxOTA5MjkxOTA1MDUwNTA2MTAxZjI1NjViMDA1YjYxMDBjNjYwMDQ4MDM2MDM2MDIwODExMDE1NjEwMGIwNTc2MDAwODBmZDViODEwMTkwODA4MDM1OTA2MDIwMDE5MDkyOTE5MDUwNTA1MDYxMDQyMzU2NWIwMDViNjEwMTE0NjAwNDgwMzYwMzYwNDA4MTEwMTU2MTAwZGU1NzYwMDA4MGZkNWI4MTAxOTA4MDgwMzU3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNjkwNjAyMDAxOTA5MjkxOTA4MDM1OTA2MDIwMDE5MDkyOTE5MDUwNTA1MDYxMDQ2ZDU2NWIwMDViNjEwMWEyNjAwNDgwMzYwMzYwODA4MTEwMTU2MTAxMmM1NzYwMDA4MGZkNWI4MTAxOTA4MDgwMzU3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNjkwNjAyMDAxOTA5MjkxOTA4MDM1NzNmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmMTY5MDYwMjAwMTkwOTI5MTkwODAzNTczZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjE2OTA2MDIwMDE5MDkyOTE5MDgwMzU5MDYwMjAwMTkwOTI5MTkwNTA1MDUwNjEwNGI4NTY1YjAwNWI2MTAxZjA2MDA0ODAzNjAzNjA0MDgxMTAxNTYxMDFiYTU3NjAwMDgwZmQ1YjgxMDE5MDgwODAzNTczZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjE2OTA2MDIwMDE5MDkyOTE5MDgwMzU5MDYwMjAwMTkwOTI5MTkwNTA1MDUwNjEwNWE3NTY1YjAwNWI4MTczZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjE2NjEwOGZjODI5MDgxMTUwMjkwNjA0MDUxNjAwMDYwNDA1MTgwODMwMzgxODU4ODg4ZjE5MzUwNTA1MDUwMTU4MDE1NjEwMjM4NTczZDYwMDA4MDNlM2Q2MDAwZmQ1YjUwODE3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNjYxMDhmYzYwMDI4MzgxNjEwMjVkNTdmZTViMDQ5MDgxMTUwMjkwNjA0MDUxNjAwMDYwNDA1MTgwODMwMzgxODU4ODg4ZjE5MzUwNTA1MDUwMTU4MDE1NjEwMjg5NTczZDYwMDA4MDNlM2Q2MDAwZmQ1YjUwODE3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNjYxMDhmYzYwMDQ4MzgxNjEwMmFlNTdmZTViMDQ5MDgxMTUwMjkwNjA0MDUxNjAwMDYwNDA1MTgwODMwMzgxODU4ODg4ZjE5MzUwNTA1MDUwMTU4MDE1NjEwMmRhNTczZDYwMDA4MDNlM2Q2MDAwZmQ1YjUwODE3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNjYxMDhmYzYwMDg4MzgxNjEwMmZmNTdmZTViMDQ5MDgxMTUwMjkwNjA0MDUxNjAwMDYwNDA1MTgwODMwMzgxODU4ODg4ZjE5MzUwNTA1MDUwMTU4MDE1NjEwMzJiNTczZDYwMDA4MDNlM2Q2MDAwZmQ1YjUwODE3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNjYxMDhmYzYwMTA4MzgxNjEwMzUwNTdmZTViMDQ5MDgxMTUwMjkwNjA0MDUxNjAwMDYwNDA1MTgwODMwMzgxODU4ODg4ZjE5MzUwNTA1MDUwMTU4MDE1NjEwMzdjNTczZDYwMDA4MDNlM2Q2MDAwZmQ1YjUwODE3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNjYxMDhmYzYwMjA4MzgxNjEwM2ExNTdmZTViMDQ5MDgxMTUwMjkwNjA0MDUxNjAwMDYwNDA1MTgwODMwMzgxODU4ODg4ZjE5MzUwNTA1MDUwMTU4MDE1NjEwM2NkNTczZDYwMDA4MDNlM2Q2MDAwZmQ1YjUwODE3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNjYxMDhmYzYwNDA4MzgxNjEwM2YyNTdmZTViMDQ5MDgxMTUwMjkwNjA0MDUxNjAwMDYwNDA1MTgwODMwMzgxODU4ODg4ZjE5MzUwNTA1MDUwMTU4MDE1NjEwNDFlNTczZDYwMDA4MDNlM2Q2MDAwZmQ1YjUwNTA1MDU2NWIzMzczZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjE2NjEwOGZjODI5MDgxMTUwMjkwNjA0MDUxNjAwMDYwNDA1MTgwODMwMzgxODU4ODg4ZjE5MzUwNTA1MDUwMTU4MDE1NjEwNDY5NTczZDYwMDA4MDNlM2Q2MDAwZmQ1YjUwNTA1NjViODE3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNjYxMDhmYzgyOTA4MTE1MDI5MDYwNDA1MTYwMDA2MDQwNTE4MDgzMDM4MTg1ODg4OGYxOTM1MDUwNTA1MDE1ODAxNTYxMDRiMzU3M2Q2MDAwODAzZTNkNjAwMGZkNWI1MDUwNTA1NjViODM3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNjYxMDhmYzgyOTA4MTE1MDI5MDYwNDA1MTYwMDA2MDQwNTE4MDgzMDM4MTg1ODg4OGYxOTM1MDUwNTA1MDE1ODAxNTYxMDRmZTU3M2Q2MDAwODAzZTNkNjAwMGZkNWI1MDgyNzNmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmMTY2MTA4ZmM2MDAyODM4MTYxMDUyMzU3ZmU1YjA0OTA4MTE1MDI5MDYwNDA1MTYwMDA2MDQwNTE4MDgzMDM4MTg1ODg4OGYxOTM1MDUwNTA1MDE1ODAxNTYxMDU0ZjU3M2Q2MDAwODAzZTNkNjAwMGZkNWI1MDgxNzNmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmMTY2MTA4ZmM2MDA0ODM4MTYxMDU3NDU3ZmU1YjA0OTA4MTE1MDI5MDYwNDA1MTYwMDA2MDQwNTE4MDgzMDM4MTg1ODg4OGYxOTM1MDUwNTA1MDE1ODAxNTYxMDVhMDU3M2Q2MDAwODAzZTNkNjAwMGZkNWI1MDUwNTA1MDUwNTY1YjgxNzNmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmMTY2MTA4ZmM4MjYwMDAwMzkwODExNTAyOTA2MDQwNTE2MDAwNjA0MDUxODA4MzAzODE4NTg4ODhmMTkzNTA1MDUwNTAxNTgwMTU2MTA1ZjA1NzNkNjAwMDgwM2UzZDYwMDBmZDViNTA1MDUwNTZmZWEyNjU2MjdhN2E3MjMxNTgyMGNmNzkxYmJmNDdkY2I2MTM5Mzc0NDA1MmE2MjBmNzcxYjQyOWYwNzFmMDJlMzY1N2NlM2U1ZjgwYWIzZjk3YmQ2NDczNmY2YzYzNDMwMDA1MTEwMDMy","b64Record":"CiAIFiocCgwIARAMGgYIgK6ZpA8SDAgBEA8aBgiArpmkDxIwZp+2wlbPRUWY2KaA2tF82axifhGka2SSy3Jm1klsuZfUZCXw84hkeiFLCKGrKYSGGgwI9f3+rAYQg7fhvAEiDwoJCLn9/qwGEOEMEgIYAiogw4PCrsOCwrfDg8K5dEY4w4LCrkrDg8KLw4PCkMODwo5SAA=="},{"b64Body":"ChAKCQi5/f6sBhDrDBIDGL0IEgIYAxjOmY6mAiICCHgyIMODwq7DgsK3w4PCuXRGOMOCwq5Kw4PCi8ODwpDDg8KOQm8KAxi+CBpKMkgKIhIgPKlWIyRJgBsGYbmM3TIWgu69Kj1CwrY8NCvtdm3mCsEKIhIg1QMr/uGoJbCFvFAuf8TJJc6OpDVthrEWmbCT9pA1S38gkKEPKApCBQiAztoDUgBaAGoLY2VsbGFyIGRvb3I=","b64Record":"CiUIFiIDGL8IKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjDoLrIjzf53dKWfRP6BDvl5kppy2w+GyKFPx7fK8ZMfTC2haD0mYbKlFmvqI4c3qrcaDAj1/f6sBhDR1b6hAyIQCgkIuf3+rAYQ6wwSAxi9CCogw4PCrsOCwrfDg8K5dEY4w4LCrkrDg8KLw4PCkMODwo4wjvPwrAJC3w4KAxi/CBKqDGCAYEBSYAQ2EGEASldgADVg4ByAYyarl+oUYQBMV4BjjdmAQxRhAJpXgGOs72A3FGEAyFeAY9Kg/iYUYQEWV4Bj4S4yOBRhAaRXWwBbYQCYYASANgNgQIEQFWEAYldgAID9W4EBkICANXP//////////////////////////xaQYCABkJKRkIA1kGAgAZCSkZBQUFBhAfJWWwBbYQDGYASANgNgIIEQFWEAsFdgAID9W4EBkICANZBgIAGQkpGQUFBQYQQjVlsAW2EBFGAEgDYDYECBEBVhAN5XYACA/VuBAZCAgDVz//////////////////////////8WkGAgAZCSkZCANZBgIAGQkpGQUFBQYQRtVlsAW2EBomAEgDYDYICBEBVhASxXYACA/VuBAZCAgDVz//////////////////////////8WkGAgAZCSkZCANXP//////////////////////////xaQYCABkJKRkIA1c///////////////////////////FpBgIAGQkpGQgDWQYCABkJKRkFBQUGEEuFZbAFthAfBgBIA2A2BAgRAVYQG6V2AAgP1bgQGQgIA1c///////////////////////////FpBgIAGQkpGQgDWQYCABkJKRkFBQUGEFp1ZbAFuBc///////////////////////////FmEI/IKQgRUCkGBAUWAAYEBRgIMDgYWIiPGTUFBQUBWAFWECOFc9YACAPj1gAP1bUIFz//////////////////////////8WYQj8YAKDgWECXVf+WwSQgRUCkGBAUWAAYEBRgIMDgYWIiPGTUFBQUBWAFWECiVc9YACAPj1gAP1bUIFz//////////////////////////8WYQj8YASDgWECrlf+WwSQgRUCkGBAUWAAYEBRgIMDgYWIiPGTUFBQUBWAFWEC2lc9YACAPj1gAP1bUIFz//////////////////////////8WYQj8YAiDgWEC/1f+WwSQgRUCkGBAUWAAYEBRgIMDgYWIiPGTUFBQUBWAFWEDK1c9YACAPj1gAP1bUIFz//////////////////////////8WYQj8YBCDgWEDUFf+WwSQgRUCkGBAUWAAYEBRgIMDgYWIiPGTUFBQUBWAFWEDfFc9YACAPj1gAP1bUIFz//////////////////////////8WYQj8YCCDgWEDoVf+WwSQgRUCkGBAUWAAYEBRgIMDgYWIiPGTUFBQUBWAFWEDzVc9YACAPj1gAP1bUIFz//////////////////////////8WYQj8YECDgWED8lf+WwSQgRUCkGBAUWAAYEBRgIMDgYWIiPGTUFBQUBWAFWEEHlc9YACAPj1gAP1bUFBQVlszc///////////////////////////FmEI/IKQgRUCkGBAUWAAYEBRgIMDgYWIiPGTUFBQUBWAFWEEaVc9YACAPj1gAP1bUFBWW4Fz//////////////////////////8WYQj8gpCBFQKQYEBRYABgQFGAgwOBhYiI8ZNQUFBQFYAVYQSzVz1gAIA+PWAA/VtQUFBWW4Nz//////////////////////////8WYQj8gpCBFQKQYEBRYABgQFGAgwOBhYiI8ZNQUFBQFYAVYQT+Vz1gAIA+PWAA/VtQgnP//////////////////////////xZhCPxgAoOBYQUjV/5bBJCBFQKQYEBRYABgQFGAgwOBhYiI8ZNQUFBQFYAVYQVPVz1gAIA+PWAA/VtQgXP//////////////////////////xZhCPxgBIOBYQV0V/5bBJCBFQKQYEBRYABgQFGAgwOBhYiI8ZNQUFBQFYAVYQWgVz1gAIA+PWAA/VtQUFBQUFZbgXP//////////////////////////xZhCPyCYAADkIEVApBgQFFgAGBAUYCDA4GFiIjxk1BQUFAVgBVhBfBXPWAAgD49YAD9W1BQUFb+omVienpyMVggz3kbv0fcthOTdEBSpiD3cbQp8HHwLjZXzj5fgKs/l71kc29sY0MABREAMiKAAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAowJoMOgMYvwhKFgoUAAAAAAAAAAAAAAAAAAAAAAAABD9yBwoDGL8IEAFSRQoJCgIYAxDU3c8iCgoKAhhiELz3tcgDCgoKAxigBhDGiK43CgoKAxihBhDGiK43CgsKAxi9CBCv5uHZBAoHCgMYvwgQFA=="},{"b64Body":"ChAKCQi6/f6sBhDvDBIDGL0IEgIYAxjgrLEDIgIIeDIgw4PCrsOCwrfDg8K5dEY4w4LCrkrDg8KLw4PCkMODwo46TwoDGL8IEKCNBiJErO9gNwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQ9AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAo=","b64Record":"CiUIFiIDGL8IKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjDiQHw4vrlf0FlijvBRl0Wcpa7jBVNJPRV+YK2aLLnU0NdaTb8iZ1LIQe1Aug04j5QaDAj2/f6sBhDSkafGASIQCgkIuv3+rAYQ7wwSAxi9CCogw4PCrsOCwrfDg8K5dEY4w4LCrkrDg8KLw4PCkMODwo4wgNfaAjqMAgoDGL8IIoACAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACiA8QRSIAoJCgIYYhCArrUFCgoKAxi9CBDrrbUFCgcKAxi/CBAT"}]},"hscsEvm010ReceiverMustSignContractTx":{"placeholderNum":1088,"encodedItems":[{"b64Body":"Cg8KCQi//f6sBhD/DBICGAISAhgDGPuV9hQiAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjlo0CiISII7/xuc9tQnklLimHYQad+7h1v+n9fAXx6VN6BP3sPDKEIDo7aG6AUABSgUIgM7aAw==","b64Record":"CiUIFhIDGMEIKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjDbGyAw6dPETfBf1I9uoLlcG2UA4Vx9mhg+qnNKFCXoCiFIh8VZ75ESFb7Jdk5JeRAaCwj7/f6sBhDCuoRKIg8KCQi//f6sBhD/DBICGAIqIMODwq7DgsK3w4PCuXRGOMOCwq5Kw4PCi8ODwpDDg8KOUhsKCwoCGAIQ/8/bw/QCCgwKAxjBCBCA0NvD9AI="},{"b64Body":"Cg8KCQi//f6sBhCHDRICGAISAhgDGP7LzSwiAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjooBjwESDAj7y9mwBhDh4Oy6AhptCiISICpZm41UjvGR20o+HUS25RaMrHsZSpv8SeHK8ApLin/dCiM6IQJGlX5JcBc7+EKXx//R2sMWRmUhVmDKX0qGCU7D566vRgoiEiCSLhot2AesT4eURO/u58plLPt1pvs7oFO2lmvdaUuSLyIMSGVsbG8gV29ybGQhKgAyAA==","b64Record":"CiUIFhoDGMIIKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjCiV1f7kDJ+C/smWp8sHHJ22SANQ7czAe0GXmFYQt82bQlWzira7v7VTRSLxGNUvfcaDAj7/f6sBhC+9NjLAiIPCgkIv/3+rAYQhw0SAhgCKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjlIA"},{"b64Body":"Cg8KCQjA/f6sBhCLDRICGAISAhgDGPOGlzoiAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjpoBghkKAxjCCCL6GDYwODA2MDQwNTI2MTA2MmE4MDYxMDAxMzYwMDAzOTYwMDBmM2ZlNjA4MDYwNDA1MjYwMDQzNjEwNjEwMDRhNTc2MDAwMzU2MGUwMWM4MDYzMjZhYjk3ZWExNDYxMDA0YzU3ODA2MzhkZDk4MDQzMTQ2MTAwOWE1NzgwNjNhY2VmNjAzNzE0NjEwMGM4NTc4MDYzZDJhMGZlMjYxNDYxMDExNjU3ODA2M2UxMmUzMjM4MTQ2MTAxYTQ1NzViMDA1YjYxMDA5ODYwMDQ4MDM2MDM2MDQwODExMDE1NjEwMDYyNTc2MDAwODBmZDViODEwMTkwODA4MDM1NzNmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmMTY5MDYwMjAwMTkwOTI5MTkwODAzNTkwNjAyMDAxOTA5MjkxOTA1MDUwNTA2MTAxZjI1NjViMDA1YjYxMDBjNjYwMDQ4MDM2MDM2MDIwODExMDE1NjEwMGIwNTc2MDAwODBmZDViODEwMTkwODA4MDM1OTA2MDIwMDE5MDkyOTE5MDUwNTA1MDYxMDQyMzU2NWIwMDViNjEwMTE0NjAwNDgwMzYwMzYwNDA4MTEwMTU2MTAwZGU1NzYwMDA4MGZkNWI4MTAxOTA4MDgwMzU3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNjkwNjAyMDAxOTA5MjkxOTA4MDM1OTA2MDIwMDE5MDkyOTE5MDUwNTA1MDYxMDQ2ZDU2NWIwMDViNjEwMWEyNjAwNDgwMzYwMzYwODA4MTEwMTU2MTAxMmM1NzYwMDA4MGZkNWI4MTAxOTA4MDgwMzU3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNjkwNjAyMDAxOTA5MjkxOTA4MDM1NzNmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmMTY5MDYwMjAwMTkwOTI5MTkwODAzNTczZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjE2OTA2MDIwMDE5MDkyOTE5MDgwMzU5MDYwMjAwMTkwOTI5MTkwNTA1MDUwNjEwNGI4NTY1YjAwNWI2MTAxZjA2MDA0ODAzNjAzNjA0MDgxMTAxNTYxMDFiYTU3NjAwMDgwZmQ1YjgxMDE5MDgwODAzNTczZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjE2OTA2MDIwMDE5MDkyOTE5MDgwMzU5MDYwMjAwMTkwOTI5MTkwNTA1MDUwNjEwNWE3NTY1YjAwNWI4MTczZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjE2NjEwOGZjODI5MDgxMTUwMjkwNjA0MDUxNjAwMDYwNDA1MTgwODMwMzgxODU4ODg4ZjE5MzUwNTA1MDUwMTU4MDE1NjEwMjM4NTczZDYwMDA4MDNlM2Q2MDAwZmQ1YjUwODE3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNjYxMDhmYzYwMDI4MzgxNjEwMjVkNTdmZTViMDQ5MDgxMTUwMjkwNjA0MDUxNjAwMDYwNDA1MTgwODMwMzgxODU4ODg4ZjE5MzUwNTA1MDUwMTU4MDE1NjEwMjg5NTczZDYwMDA4MDNlM2Q2MDAwZmQ1YjUwODE3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNjYxMDhmYzYwMDQ4MzgxNjEwMmFlNTdmZTViMDQ5MDgxMTUwMjkwNjA0MDUxNjAwMDYwNDA1MTgwODMwMzgxODU4ODg4ZjE5MzUwNTA1MDUwMTU4MDE1NjEwMmRhNTczZDYwMDA4MDNlM2Q2MDAwZmQ1YjUwODE3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNjYxMDhmYzYwMDg4MzgxNjEwMmZmNTdmZTViMDQ5MDgxMTUwMjkwNjA0MDUxNjAwMDYwNDA1MTgwODMwMzgxODU4ODg4ZjE5MzUwNTA1MDUwMTU4MDE1NjEwMzJiNTczZDYwMDA4MDNlM2Q2MDAwZmQ1YjUwODE3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNjYxMDhmYzYwMTA4MzgxNjEwMzUwNTdmZTViMDQ5MDgxMTUwMjkwNjA0MDUxNjAwMDYwNDA1MTgwODMwMzgxODU4ODg4ZjE5MzUwNTA1MDUwMTU4MDE1NjEwMzdjNTczZDYwMDA4MDNlM2Q2MDAwZmQ1YjUwODE3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNjYxMDhmYzYwMjA4MzgxNjEwM2ExNTdmZTViMDQ5MDgxMTUwMjkwNjA0MDUxNjAwMDYwNDA1MTgwODMwMzgxODU4ODg4ZjE5MzUwNTA1MDUwMTU4MDE1NjEwM2NkNTczZDYwMDA4MDNlM2Q2MDAwZmQ1YjUwODE3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNjYxMDhmYzYwNDA4MzgxNjEwM2YyNTdmZTViMDQ5MDgxMTUwMjkwNjA0MDUxNjAwMDYwNDA1MTgwODMwMzgxODU4ODg4ZjE5MzUwNTA1MDUwMTU4MDE1NjEwNDFlNTczZDYwMDA4MDNlM2Q2MDAwZmQ1YjUwNTA1MDU2NWIzMzczZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjE2NjEwOGZjODI5MDgxMTUwMjkwNjA0MDUxNjAwMDYwNDA1MTgwODMwMzgxODU4ODg4ZjE5MzUwNTA1MDUwMTU4MDE1NjEwNDY5NTczZDYwMDA4MDNlM2Q2MDAwZmQ1YjUwNTA1NjViODE3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNjYxMDhmYzgyOTA4MTE1MDI5MDYwNDA1MTYwMDA2MDQwNTE4MDgzMDM4MTg1ODg4OGYxOTM1MDUwNTA1MDE1ODAxNTYxMDRiMzU3M2Q2MDAwODAzZTNkNjAwMGZkNWI1MDUwNTA1NjViODM3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNjYxMDhmYzgyOTA4MTE1MDI5MDYwNDA1MTYwMDA2MDQwNTE4MDgzMDM4MTg1ODg4OGYxOTM1MDUwNTA1MDE1ODAxNTYxMDRmZTU3M2Q2MDAwODAzZTNkNjAwMGZkNWI1MDgyNzNmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmMTY2MTA4ZmM2MDAyODM4MTYxMDUyMzU3ZmU1YjA0OTA4MTE1MDI5MDYwNDA1MTYwMDA2MDQwNTE4MDgzMDM4MTg1ODg4OGYxOTM1MDUwNTA1MDE1ODAxNTYxMDU0ZjU3M2Q2MDAwODAzZTNkNjAwMGZkNWI1MDgxNzNmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmMTY2MTA4ZmM2MDA0ODM4MTYxMDU3NDU3ZmU1YjA0OTA4MTE1MDI5MDYwNDA1MTYwMDA2MDQwNTE4MDgzMDM4MTg1ODg4OGYxOTM1MDUwNTA1MDE1ODAxNTYxMDVhMDU3M2Q2MDAwODAzZTNkNjAwMGZkNWI1MDUwNTA1MDUwNTY1YjgxNzNmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmMTY2MTA4ZmM4MjYwMDAwMzkwODExNTAyOTA2MDQwNTE2MDAwNjA0MDUxODA4MzAzODE4NTg4ODhmMTkzNTA1MDUwNTAxNTgwMTU2MTA1ZjA1NzNkNjAwMDgwM2UzZDYwMDBmZDViNTA1MDUwNTZmZWEyNjU2MjdhN2E3MjMxNTgyMGNmNzkxYmJmNDdkY2I2MTM5Mzc0NDA1MmE2MjBmNzcxYjQyOWYwNzFmMDJlMzY1N2NlM2U1ZjgwYWIzZjk3YmQ2NDczNmY2YzYzNDMwMDA1MTEwMDMy","b64Record":"CiAIFiocCgwIARAMGgYIgK6ZpA8SDAgBEA8aBgiArpmkDxIwaYui3Wklr5CtizSoGGSAJ6+T2Wn/cwJDe/AyrLBI6SJBShyyLOIP2nSgp35o2IhEGgsI/P3+rAYQy4nEUyIPCgkIwP3+rAYQiw0SAhgCKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjlIA"},{"b64Body":"ChAKCQjA/f6sBhCNDRIDGMEIEgIYAxiW+66dAiICCHgyIMODwq7DgsK3w4PCuXRGOMOCwq5Kw4PCi8ODwpDDg8KOQksKAxjCCBoiEiD6NUgVkIZhyPXKDGw51h/0QK2K/vzPwkCxj2iggOS7/yCQoQ8ogMivoCVCBQiAztoDUgBaAGoLY2VsbGFyIGRvb3I=","b64Record":"CiUIFiIDGMMIKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjB06HOdUspOerZH4W3Ewn3oVv4O47oytX691C4CQlqH9FrnS9Z0iZXObN24roWIJzUaDAj8/f6sBhC/wIHVAiIQCgkIwP3+rAYQjQ0SAxjBCCogw4PCrsOCwrfDg8K5dEY4w4LCrkrDg8KLw4PCkMODwo4w1tSRpAJC3w4KAxjDCBKqDGCAYEBSYAQ2EGEASldgADVg4ByAYyarl+oUYQBMV4BjjdmAQxRhAJpXgGOs72A3FGEAyFeAY9Kg/iYUYQEWV4Bj4S4yOBRhAaRXWwBbYQCYYASANgNgQIEQFWEAYldgAID9W4EBkICANXP//////////////////////////xaQYCABkJKRkIA1kGAgAZCSkZBQUFBhAfJWWwBbYQDGYASANgNgIIEQFWEAsFdgAID9W4EBkICANZBgIAGQkpGQUFBQYQQjVlsAW2EBFGAEgDYDYECBEBVhAN5XYACA/VuBAZCAgDVz//////////////////////////8WkGAgAZCSkZCANZBgIAGQkpGQUFBQYQRtVlsAW2EBomAEgDYDYICBEBVhASxXYACA/VuBAZCAgDVz//////////////////////////8WkGAgAZCSkZCANXP//////////////////////////xaQYCABkJKRkIA1c///////////////////////////FpBgIAGQkpGQgDWQYCABkJKRkFBQUGEEuFZbAFthAfBgBIA2A2BAgRAVYQG6V2AAgP1bgQGQgIA1c///////////////////////////FpBgIAGQkpGQgDWQYCABkJKRkFBQUGEFp1ZbAFuBc///////////////////////////FmEI/IKQgRUCkGBAUWAAYEBRgIMDgYWIiPGTUFBQUBWAFWECOFc9YACAPj1gAP1bUIFz//////////////////////////8WYQj8YAKDgWECXVf+WwSQgRUCkGBAUWAAYEBRgIMDgYWIiPGTUFBQUBWAFWECiVc9YACAPj1gAP1bUIFz//////////////////////////8WYQj8YASDgWECrlf+WwSQgRUCkGBAUWAAYEBRgIMDgYWIiPGTUFBQUBWAFWEC2lc9YACAPj1gAP1bUIFz//////////////////////////8WYQj8YAiDgWEC/1f+WwSQgRUCkGBAUWAAYEBRgIMDgYWIiPGTUFBQUBWAFWEDK1c9YACAPj1gAP1bUIFz//////////////////////////8WYQj8YBCDgWEDUFf+WwSQgRUCkGBAUWAAYEBRgIMDgYWIiPGTUFBQUBWAFWEDfFc9YACAPj1gAP1bUIFz//////////////////////////8WYQj8YCCDgWEDoVf+WwSQgRUCkGBAUWAAYEBRgIMDgYWIiPGTUFBQUBWAFWEDzVc9YACAPj1gAP1bUIFz//////////////////////////8WYQj8YECDgWED8lf+WwSQgRUCkGBAUWAAYEBRgIMDgYWIiPGTUFBQUBWAFWEEHlc9YACAPj1gAP1bUFBQVlszc///////////////////////////FmEI/IKQgRUCkGBAUWAAYEBRgIMDgYWIiPGTUFBQUBWAFWEEaVc9YACAPj1gAP1bUFBWW4Fz//////////////////////////8WYQj8gpCBFQKQYEBRYABgQFGAgwOBhYiI8ZNQUFBQFYAVYQSzVz1gAIA+PWAA/VtQUFBWW4Nz//////////////////////////8WYQj8gpCBFQKQYEBRYABgQFGAgwOBhYiI8ZNQUFBQFYAVYQT+Vz1gAIA+PWAA/VtQgnP//////////////////////////xZhCPxgAoOBYQUjV/5bBJCBFQKQYEBRYABgQFGAgwOBhYiI8ZNQUFBQFYAVYQVPVz1gAIA+PWAA/VtQgXP//////////////////////////xZhCPxgBIOBYQV0V/5bBJCBFQKQYEBRYABgQFGAgwOBhYiI8ZNQUFBQFYAVYQWgVz1gAIA+PWAA/VtQUFBQUFZbgXP//////////////////////////xZhCPyCYAADkIEVApBgQFFgAGBAUYCDA4GFiIjxk1BQUFAVgBVhBfBXPWAAgD49YAD9W1BQUFb+omVienpyMVggz3kbv0fcthOTdEBSpiD3cbQp8HHwLjZXzj5fgKs/l71kc29sY0MABREAMiKAAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAowJoMOgMYwwhKFgoUAAAAAAAAAAAAAAAAAAAAAAAABENyBwoDGMMIEAFSSQoJCgIYAxCY4okUCgoKAhhiEODciMYDCgoKAxigBhCatYg3CgoKAxihBhCatYg3CgsKAxjBCBCruYKJTwoLCgMYwwgQgJDfwEo="},{"b64Body":"ChAKCQjB/f6sBhCRDRIDGMEIEgIYAxjgrLEDIgIIeDIgw4PCrsOCwrfDg8K5dEY4w4LCrkrDg8KLw4PCkMODwo46TwoDGMMIEKCNBiJErO9gNwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAARBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAASoF8gA=","b64Record":"CiUIFiIDGMMIKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjDgt32WmGVPX8bBGTnSuIpZ2idhHksAmLyBUMetuib7ieO+IUIR1WrLbbQycZ/cPP8aDAj9/f6sBhDlqrzjAiIQCgkIwf3+rAYQkQ0SAxjBCCogw4PCrsOCwrfDg8K5dEY4w4LCrkrDg8KLw4PCkMODwo4wgNfaAjqMAgoDGMMIIoACAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACiA8QRSJQoJCgIYYhCArrUFCgsKAxjBCBCAmvqaJQoLCgMYwwgQ/8evoCU="}]},"InsufficientGas":{"placeholderNum":1092,"encodedItems":[{"b64Body":"Cg8KCQjG/f6sBhChDRICGAISAhgDGP7LzSwiAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjooBjwESDAiCzNmwBhDXsZLdARptCiISIK7wwRuE+ORvx+H2N6/W9yvXyqkGCdsJ00e5OSv/TTajCiM6IQKovpJa+tFXkSNx06GUhL/3Z3gEUKd3TsvSbu/NN2cWnwoiEiCeQiXDbf6PKMxYDyKr+uF5n7BZZawTu7x54tbiOZPUtyIMSGVsbG8gV29ybGQhKgAyAA==","b64Record":"CiUIFhoDGMUIKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjC6aymcSuO/sAut10sWVzVbZ2pmDj/9IoNSGiYb8jlJh+3FucoRP1RLbdTI4Y/6vLYaDAiC/v6sBhD219LnASIPCgkIxv3+rAYQoQ0SAhgCKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjlIA"},{"b64Body":"Cg8KCQjG/f6sBhClDRICGAISAhgDGKGwpi4iAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjpoB3AMKAxjFCCLUAzYwODA2MDQwNTI2MDBmNjAwMDU1MzQ4MDE1NjEwMDE1NTc2MDAwODBmZDViNTA2MGM2ODA2MTAwMjQ2MDAwMzk2MDAwZjNmZTYwODA2MDQwNTIzNDgwMTU2MDBmNTc2MDAwODBmZDViNTA2MDA0MzYxMDYwMzI1NzYwMDAzNTYwZTAxYzgwNjM2MGZlNDdiMTE0NjAzNzU3ODA2MzZkNGNlNjNjMTQ2MDYyNTc1YjYwMDA4MGZkNWI2MDYwNjAwNDgwMzYwMzYwMjA4MTEwMTU2MDRiNTc2MDAwODBmZDViODEwMTkwODA4MDM1OTA2MDIwMDE5MDkyOTE5MDUwNTA1MDYwN2U1NjViMDA1YjYwNjg2MDg4NTY1YjYwNDA1MTgwODI4MTUyNjAyMDAxOTE1MDUwNjA0MDUxODA5MTAzOTBmMzViODA2MDAwODE5MDU1NTA1MDU2NWI2MDAwODA1NDkwNTA5MDU2ZmVhMjY1NjI3YTdhNzIzMTU4MjAyODIzMzhiZmVmZDNmOTk5ZGYxYzA3MjBjODViN2M3OTMzNTFlNTk3NWJiNDk1OTkyZjEyMTY5MjZlZWE4OTE1NjQ3MzZmNmM2MzQzMDAwNTBiMDAzMg==","b64Record":"CiAIFiocCgwIARAMGgYIgK6ZpA8SDAgBEA8aBgiArpmkDxIw4Rixn9lqiti50iPYUtq3dLg4qE52rEUPMLT2FmiOw0p3i+D33fLCBKevgnUyhOD5GgsIg/7+rAYQqoHiECIPCgkIxv3+rAYQpQ0SAhgCKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjlIA"},{"b64Body":"Cg8KCQjH/f6sBhCnDRICGAISAhgDGK/Dto0EIgIIeDIgw4PCrsOCwrfDg8K5dEY4w4LCrkrDg8KLw4PCkMODwo5ClgEKAxjFCBpzKnEIAhJtCiISIFrbkDBHLyqXMnRoROIj8G3s3ga1xudjcvs0B1wZPEMeCiM6IQL6Y57ESGXJ1quTcdDnELOYMpTI9snY9JqIVzM2JsmbeQoiEiAdz5VnNR7kqxrIOZVQOzq+CeD2YhA3jgFh27PJUPVQriCQoQ9CBQiAztoDUgBaAGoLY2VsbGFyIGRvb3I=","b64Record":"CiUIFiIDGMYIKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjDCaPQeERiIViopIYt2fUiL+c8SsiYQ9CGlIeSi1d20BXERNB+YnzX5GrCf0Q0nY8saDAiD/v6sBhCIutL1ASIPCgkIx/3+rAYQpw0SAhgCKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjjDA2eIGQvsDCgMYxggSxgFggGBAUjSAFWAPV2AAgP1bUGAENhBgMldgADVg4ByAY2D+R7EUYDdXgGNtTOY8FGBiV1tgAID9W2BgYASANgNgIIEQFWBLV2AAgP1bgQGQgIA1kGAgAZCSkZBQUFBgflZbAFtgaGCIVltgQFGAgoFSYCABkVBQYEBRgJEDkPNbgGAAgZBVUFBWW2AAgFSQUJBW/qJlYnp6cjFYICgjOL/v0/mZ3xwHIMhbfHkzUeWXW7SVmS8SFpJu6okVZHNvbGNDAAULADIigAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKMCaDDoDGMYIShYKFAAAAAAAAAAAAAAAAAAAAAAAAARGcgcKAxjGCBABUhYKCQoCGAIQ/7LFDQoJCgIYYhCAs8UN"}]},"NonPayable":{"placeholderNum":1100,"encodedItems":[{"b64Body":"Cg8KCQjP/f6sBhDWDRICGAISAhgDGP7LzSwiAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjooBjwESDAiLzNmwBhCZma74AhptCiISIOJL3nYCDc8RmZyXn9U8AUfgZ14G1BLmue1aH80XKD2WCiM6IQIZ2p1smmrbX5LqAPyTfXeyWcq3ar06uN08qMm4PycwogoiEiB6/HDMRnkcVwS+E1CA0Cs2DK5Fu4DVG33HbLf9oAcgNCIMSGVsbG8gV29ybGQhKgAyAA==","b64Record":"CiUIFhoDGM0IKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjB4vS1H+pDpjSgcAyXM24+DfJ/TiyHb3GGe3Ta1SCiVPVU+ipxYVf8S1WmClw2B0r8aDAiL/v6sBhD13av7AiIPCgkIz/3+rAYQ1g0SAhgCKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjlIA"},{"b64Body":"Cg8KCQjQ/f6sBhDaDRICGAISAhgDGIi18DMiAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjpoB3A0KAxjNCCLUDTYwODA2MDQwNTIzNDgwMTU2MTAwMTA1NzYwMDA4MGZkNWI1MDYxMDM0YTgwNjEwMDIwNjAwMDM5NjAwMGYzMDA2MDgwNjA0MDUyNjAwNDM2MTA2MTAwNTc1NzYwMDAzNTdjMDEwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDkwMDQ2M2ZmZmZmZmZmMTY4MDYzMmYxOWMwNGExNDYxMDA1YzU3ODA2MzM4Y2M0ODMxMTQ2MTAwODc1NzgwNjNlZmM4MWE4YzE0NjEwMGRlNTc1YjYwMDA4MGZkNWIzNDgwMTU2MTAwNjg1NzYwMDA4MGZkNWI1MDYxMDA3MTYxMDBmNTU2NWI2MDQwNTE4MDgyODE1MjYwMjAwMTkxNTA1MDYwNDA1MTgwOTEwMzkwZjM1YjM0ODAxNTYxMDA5MzU3NjAwMDgwZmQ1YjUwNjEwMDljNjEwMWJjNTY1YjYwNDA1MTgwODI3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNjczZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjE2ODE1MjYwMjAwMTkxNTA1MDYwNDA1MTgwOTEwMzkwZjM1YjM0ODAxNTYxMDBlYTU3NjAwMDgwZmQ1YjUwNjEwMGYzNjEwMWU1NTY1YjAwNWI2MDAwODA2MDAwOTA1NDkwNjEwMTAwMGE5MDA0NzNmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmMTY3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNjYzMDg2OTQ5Yjc2MDQwNTE4MTYzZmZmZmZmZmYxNjdjMDEwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAyODE1MjYwMDQwMTYwMjA2MDQwNTE4MDgzMDM4MTYwMDA4NzgwM2IxNTgwMTU2MTAxN2M1NzYwMDA4MGZkNWI1MDVhZjExNTgwMTU2MTAxOTA1NzNkNjAwMDgwM2UzZDYwMDBmZDViNTA1MDUwNTA2MDQwNTEzZDYwMjA4MTEwMTU2MTAxYTY1NzYwMDA4MGZkNWI4MTAxOTA4MDgwNTE5MDYwMjAwMTkwOTI5MTkwNTA1MDUwOTA1MDkwNTY1YjYwMDA4MDYwMDA5MDU0OTA2MTAxMDAwYTkwMDQ3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNjkwNTA5MDU2NWI2MTAxZWQ2MTAyNGI1NjViNjA0MDUxODA5MTAzOTA2MDAwZjA4MDE1ODAxNTYxMDIwOTU3M2Q2MDAwODAzZTNkNjAwMGZkNWI1MDYwMDA4MDYxMDEwMDBhODE1NDgxNzNmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmMDIxOTE2OTA4MzczZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjE2MDIxNzkwNTU1MDU2NWI2MDQwNTE2MGM0ODA2MTAyNWI4MzM5MDE5MDU2MDA2MDgwNjA0MDUyNjAwODYwMDA1NTM0ODAxNTYwMTQ1NzYwMDA4MGZkNWI1MDYwYTE4MDYxMDAyMzYwMDAzOTYwMDBmMzAwNjA4MDYwNDA1MjYwMDQzNjEwNjAzZjU3NjAwMDM1N2MwMTAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwOTAwNDYzZmZmZmZmZmYxNjgwNjMwODY5NDliNzE0NjA0NDU3NWI2MDAwODBmZDViMzQ4MDE1NjA0ZjU3NjAwMDgwZmQ1YjUwNjA1NjYwNmM1NjViNjA0MDUxODA4MjgxNTI2MDIwMDE5MTUwNTA2MDQwNTE4MDkxMDM5MGYzNWI2MDAwNjAwNzkwNTA5MDU2MDBhMTY1NjI3YTdhNzIzMDU4MjAyZTA5N2JiZTEyMmFkNWQ4NmU4NDBiZTYwYWFiNDFkMTYwYWQ1Yjg2NzQ1YWE3YWEwMDk5YTZiYmZjMjY1MjE4MDAyOWExNjU2MjdhN2E3MjMwNTgyMDZjZjdlYTlkNGU1MDY4ODZiNjAyZmY3YTYyODQwMTYxMTQzN2NiZmQwZGZjYmQ1YmVlYzM3NzU3MDcwZGE1YjMwMDI5","b64Record":"CiAIFiocCgwIARAMGgYIgK6ZpA8SDAgBEA8aBgiArpmkDxIw+1fplWFGEXL8ychS1YHC+2UajlsdNh8bYUJUi53lnMh9inwmc/ZJIy8VjprayZahGgwIjP7+rAYQ5KLmnwEiDwoJCND9/qwGENoNEgIYAiogw4PCrsOCwrfDg8K5dEY4w4LCrkrDg8KLw4PCkMODwo5SAA=="},{"b64Body":"Cg8KCQjQ/f6sBhDcDRICGAISAhgDGJb7rp0CIgIIeDIgw4PCrsOCwrfDg8K5dEY4w4LCrkrDg8KLw4PCkMODwo5CRQoDGM0IGiISIKBoHYp4ez7/Yk4PEdv60StLAbm0H16b0/70vN0EIPgVIJChD0IFCIDO2gNSAFoAagtjZWxsYXIgZG9vcg==","b64Record":"CiUIFiIDGM4IKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjBo167il5HVlOlAJKyzUXcF0T+UDII4lQaZpxuvx0rTb6Hp00pgi48vxLuDm6xMgGIaDAiM/v6sBhCrsOKEAyIPCgkI0P3+rAYQ3A0SAhgCKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjjDA2eIGQv8ICgMYzggSygZggGBAUmAENhBhAFdXYAA1fAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAkARj/////xaAYy8ZwEoUYQBcV4BjOMxIMRRhAIdXgGPvyBqMFGEA3ldbYACA/Vs0gBVhAGhXYACA/VtQYQBxYQD1VltgQFGAgoFSYCABkVBQYEBRgJEDkPNbNIAVYQCTV2AAgP1bUGEAnGEBvFZbYEBRgIJz//////////////////////////8Wc///////////////////////////FoFSYCABkVBQYEBRgJEDkPNbNIAVYQDqV2AAgP1bUGEA82EB5VZbAFtgAIBgAJBUkGEBAAqQBHP//////////////////////////xZz//////////////////////////8WYwhpSbdgQFGBY/////8WfAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAoFSYAQBYCBgQFGAgwOBYACHgDsVgBVhAXxXYACA/VtQWvEVgBVhAZBXPWAAgD49YAD9W1BQUFBgQFE9YCCBEBVhAaZXYACA/VuBAZCAgFGQYCABkJKRkFBQUJBQkFZbYACAYACQVJBhAQAKkARz//////////////////////////8WkFCQVlthAe1hAktWW2BAUYCRA5BgAPCAFYAVYQIJVz1gAIA+PWAA/VtQYACAYQEACoFUgXP//////////////////////////wIZFpCDc///////////////////////////FgIXkFVQVltgQFFgxIBhAluDOQGQVgBggGBAUmAIYABVNIAVYBRXYACA/VtQYKGAYQAjYAA5YADzAGCAYEBSYAQ2EGA/V2AANXwBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAJAEY/////8WgGMIaUm3FGBEV1tgAID9WzSAFWBPV2AAgP1bUGBWYGxWW2BAUYCCgVJgIAGRUFBgQFGAkQOQ81tgAGAHkFCQVgChZWJ6enIwWCAuCXu+EirV2G6EC+YKq0HRYK1bhnRap6oAmaa7/CZSGAApoWVienpyMFggbPfqnU5QaIa2Av96YoQBYRQ3y/0N/L1b7sN3VwcNpbMAKSKAAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAowJoMOgMYzghKFgoUAAAAAAAAAAAAAAAAAAAAAAAABE5yBwoDGM4IEAFSFgoJCgIYAhD/ssUNCgkKAhhiEICzxQ0="},{"b64Body":"Cg8KCQjR/f6sBhDeDRICGAISAhgDGOCssQMiAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjjoSCgMYzggQoI0GGOgHIgTvyBqM","b64Record":"CiUIISIDGM4IKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjCngeGkbM/4GFL1/SqHxJMhPWOVtq/d+x5E25UhxlgGrxrTku3jYMh2AqDKaaviTg4aDAiN/v6sBhCMx8+pASIPCgkI0f3+rAYQ3g0SAhgCKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjjCA19oCOggaAjB4KIDxBFIWCgkKAhgCEP+ttQUKCQoCGGIQgK61BQ=="}]},"ContractTransferToSigReqAccountWithoutKeyFails":{"placeholderNum":1106,"encodedItems":[{"b64Body":"Cg8KCQja/f6sBhCsDhICGAISAhgDGPuV9hQiAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjlo0CiISIH5bK40qxu6mIJ3UhIrQOieYI6gmGlodtjhMyDX9s18mEICglKWNHUABSgUIgM7aAw==","b64Record":"CiUIFhIDGNMIKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjDHM2sTlBog/eHhJvy7f58JOyaG4cg7XETF6Dblp1Ya2HVFZx0FoHLhPyKyZsn36WIaDAiW/v6sBhDh98y4AiIPCgkI2v3+rAYQrA4SAhgCKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjlIbCgsKAhgCEP+/qMqaOgoMCgMY0wgQgMCoypo6"},{"b64Body":"Cg8KCQjb/f6sBhC0DhICGAISAhgDGP7LzSwiAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjooBjgESCwiXzNmwBhDB6sY+Gm0KIhIg5DI+Jph9c844gQJJfl7Umfyn6JpkcjLFS2EHgIbL0Q0KIzohA6NmgICEl4eHNpsqd73cJ2awsk16Ra5AKaUAyKiOMw+kCiISIC95gWDEV31N2PVkFYYPm0mW8r80+u92xjlVKbPVYtsfIgxIZWxsbyBXb3JsZCEqADIA","b64Record":"CiUIFhoDGNQIKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjA0EU3jrYdg/SbQKHAX7FM52fWOQVOEFTpTL8HxfVwJotgcNRW51yJ9ObZ8//pCAM4aCwiX/v6sBhC/vdlAIg8KCQjb/f6sBhC0DhICGAIqIMODwq7DgsK3w4PCuXRGOMOCwq5Kw4PCi8ODwpDDg8KOUgA="},{"b64Body":"Cg8KCQjb/f6sBhC4DhICGAISAhgDGPOGlzoiAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjpoBghkKAxjUCCL6GDYwODA2MDQwNTI2MTA2MmE4MDYxMDAxMzYwMDAzOTYwMDBmM2ZlNjA4MDYwNDA1MjYwMDQzNjEwNjEwMDRhNTc2MDAwMzU2MGUwMWM4MDYzMjZhYjk3ZWExNDYxMDA0YzU3ODA2MzhkZDk4MDQzMTQ2MTAwOWE1NzgwNjNhY2VmNjAzNzE0NjEwMGM4NTc4MDYzZDJhMGZlMjYxNDYxMDExNjU3ODA2M2UxMmUzMjM4MTQ2MTAxYTQ1NzViMDA1YjYxMDA5ODYwMDQ4MDM2MDM2MDQwODExMDE1NjEwMDYyNTc2MDAwODBmZDViODEwMTkwODA4MDM1NzNmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmMTY5MDYwMjAwMTkwOTI5MTkwODAzNTkwNjAyMDAxOTA5MjkxOTA1MDUwNTA2MTAxZjI1NjViMDA1YjYxMDBjNjYwMDQ4MDM2MDM2MDIwODExMDE1NjEwMGIwNTc2MDAwODBmZDViODEwMTkwODA4MDM1OTA2MDIwMDE5MDkyOTE5MDUwNTA1MDYxMDQyMzU2NWIwMDViNjEwMTE0NjAwNDgwMzYwMzYwNDA4MTEwMTU2MTAwZGU1NzYwMDA4MGZkNWI4MTAxOTA4MDgwMzU3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNjkwNjAyMDAxOTA5MjkxOTA4MDM1OTA2MDIwMDE5MDkyOTE5MDUwNTA1MDYxMDQ2ZDU2NWIwMDViNjEwMWEyNjAwNDgwMzYwMzYwODA4MTEwMTU2MTAxMmM1NzYwMDA4MGZkNWI4MTAxOTA4MDgwMzU3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNjkwNjAyMDAxOTA5MjkxOTA4MDM1NzNmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmMTY5MDYwMjAwMTkwOTI5MTkwODAzNTczZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjE2OTA2MDIwMDE5MDkyOTE5MDgwMzU5MDYwMjAwMTkwOTI5MTkwNTA1MDUwNjEwNGI4NTY1YjAwNWI2MTAxZjA2MDA0ODAzNjAzNjA0MDgxMTAxNTYxMDFiYTU3NjAwMDgwZmQ1YjgxMDE5MDgwODAzNTczZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjE2OTA2MDIwMDE5MDkyOTE5MDgwMzU5MDYwMjAwMTkwOTI5MTkwNTA1MDUwNjEwNWE3NTY1YjAwNWI4MTczZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjE2NjEwOGZjODI5MDgxMTUwMjkwNjA0MDUxNjAwMDYwNDA1MTgwODMwMzgxODU4ODg4ZjE5MzUwNTA1MDUwMTU4MDE1NjEwMjM4NTczZDYwMDA4MDNlM2Q2MDAwZmQ1YjUwODE3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNjYxMDhmYzYwMDI4MzgxNjEwMjVkNTdmZTViMDQ5MDgxMTUwMjkwNjA0MDUxNjAwMDYwNDA1MTgwODMwMzgxODU4ODg4ZjE5MzUwNTA1MDUwMTU4MDE1NjEwMjg5NTczZDYwMDA4MDNlM2Q2MDAwZmQ1YjUwODE3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNjYxMDhmYzYwMDQ4MzgxNjEwMmFlNTdmZTViMDQ5MDgxMTUwMjkwNjA0MDUxNjAwMDYwNDA1MTgwODMwMzgxODU4ODg4ZjE5MzUwNTA1MDUwMTU4MDE1NjEwMmRhNTczZDYwMDA4MDNlM2Q2MDAwZmQ1YjUwODE3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNjYxMDhmYzYwMDg4MzgxNjEwMmZmNTdmZTViMDQ5MDgxMTUwMjkwNjA0MDUxNjAwMDYwNDA1MTgwODMwMzgxODU4ODg4ZjE5MzUwNTA1MDUwMTU4MDE1NjEwMzJiNTczZDYwMDA4MDNlM2Q2MDAwZmQ1YjUwODE3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNjYxMDhmYzYwMTA4MzgxNjEwMzUwNTdmZTViMDQ5MDgxMTUwMjkwNjA0MDUxNjAwMDYwNDA1MTgwODMwMzgxODU4ODg4ZjE5MzUwNTA1MDUwMTU4MDE1NjEwMzdjNTczZDYwMDA4MDNlM2Q2MDAwZmQ1YjUwODE3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNjYxMDhmYzYwMjA4MzgxNjEwM2ExNTdmZTViMDQ5MDgxMTUwMjkwNjA0MDUxNjAwMDYwNDA1MTgwODMwMzgxODU4ODg4ZjE5MzUwNTA1MDUwMTU4MDE1NjEwM2NkNTczZDYwMDA4MDNlM2Q2MDAwZmQ1YjUwODE3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNjYxMDhmYzYwNDA4MzgxNjEwM2YyNTdmZTViMDQ5MDgxMTUwMjkwNjA0MDUxNjAwMDYwNDA1MTgwODMwMzgxODU4ODg4ZjE5MzUwNTA1MDUwMTU4MDE1NjEwNDFlNTczZDYwMDA4MDNlM2Q2MDAwZmQ1YjUwNTA1MDU2NWIzMzczZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjE2NjEwOGZjODI5MDgxMTUwMjkwNjA0MDUxNjAwMDYwNDA1MTgwODMwMzgxODU4ODg4ZjE5MzUwNTA1MDUwMTU4MDE1NjEwNDY5NTczZDYwMDA4MDNlM2Q2MDAwZmQ1YjUwNTA1NjViODE3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNjYxMDhmYzgyOTA4MTE1MDI5MDYwNDA1MTYwMDA2MDQwNTE4MDgzMDM4MTg1ODg4OGYxOTM1MDUwNTA1MDE1ODAxNTYxMDRiMzU3M2Q2MDAwODAzZTNkNjAwMGZkNWI1MDUwNTA1NjViODM3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNjYxMDhmYzgyOTA4MTE1MDI5MDYwNDA1MTYwMDA2MDQwNTE4MDgzMDM4MTg1ODg4OGYxOTM1MDUwNTA1MDE1ODAxNTYxMDRmZTU3M2Q2MDAwODAzZTNkNjAwMGZkNWI1MDgyNzNmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmMTY2MTA4ZmM2MDAyODM4MTYxMDUyMzU3ZmU1YjA0OTA4MTE1MDI5MDYwNDA1MTYwMDA2MDQwNTE4MDgzMDM4MTg1ODg4OGYxOTM1MDUwNTA1MDE1ODAxNTYxMDU0ZjU3M2Q2MDAwODAzZTNkNjAwMGZkNWI1MDgxNzNmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmMTY2MTA4ZmM2MDA0ODM4MTYxMDU3NDU3ZmU1YjA0OTA4MTE1MDI5MDYwNDA1MTYwMDA2MDQwNTE4MDgzMDM4MTg1ODg4OGYxOTM1MDUwNTA1MDE1ODAxNTYxMDVhMDU3M2Q2MDAwODAzZTNkNjAwMGZkNWI1MDUwNTA1MDUwNTY1YjgxNzNmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmMTY2MTA4ZmM4MjYwMDAwMzkwODExNTAyOTA2MDQwNTE2MDAwNjA0MDUxODA4MzAzODE4NTg4ODhmMTkzNTA1MDUwNTAxNTgwMTU2MTA1ZjA1NzNkNjAwMDgwM2UzZDYwMDBmZDViNTA1MDUwNTZmZWEyNjU2MjdhN2E3MjMxNTgyMGNmNzkxYmJmNDdkY2I2MTM5Mzc0NDA1MmE2MjBmNzcxYjQyOWYwNzFmMDJlMzY1N2NlM2U1ZjgwYWIzZjk3YmQ2NDczNmY2YzYzNDMwMDA1MTEwMDMy","b64Record":"CiAIFiocCgwIARAMGgYIgK6ZpA8SDAgBEA8aBgiArpmkDxIwYJXgfYJpnpn9dGJjU+Tzg1R/aYDF03SmbqaVGyWWd1oYAhEvq2Ue06EO8OcqKdGtGgwIl/7+rAYQ0If/wQIiDwoJCNv9/qwGELgOEgIYAiogw4PCrsOCwrfDg8K5dEY4w4LCrkrDg8KLw4PCkMODwo5SAA=="},{"b64Body":"Cg8KCQjc/f6sBhC6DhICGAISAhgDGJb7rp0CIgIIeDIgw4PCrsOCwrfDg8K5dEY4w4LCrkrDg8KLw4PCkMODwo5CSAoDGNQIGiISIIZOnPN1ul/dZ8sX+ufK8uqtIZ4aiTRbxR4tzB+JIGuPIOCnEiiIJ0IFCIDO2gNSAFoAagtjZWxsYXIgZG9vcg==","b64Record":"CiUIFiIDGNUIKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjCEQ2zg5KO0cFXRFvYBi7928TeC6Z1P+9fQE3hglAtjMME1OJPIqlk0qyAHXA5hKB8aCwiY/v6sBhDk4odKIg8KCQjc/f6sBhC6DhICGAIqIMODwq7DgsK3w4PCuXRGOMOCwq5Kw4PCi8ODwpDDg8KOMICFkAhC3w4KAxjVCBKqDGCAYEBSYAQ2EGEASldgADVg4ByAYyarl+oUYQBMV4BjjdmAQxRhAJpXgGOs72A3FGEAyFeAY9Kg/iYUYQEWV4Bj4S4yOBRhAaRXWwBbYQCYYASANgNgQIEQFWEAYldgAID9W4EBkICANXP//////////////////////////xaQYCABkJKRkIA1kGAgAZCSkZBQUFBhAfJWWwBbYQDGYASANgNgIIEQFWEAsFdgAID9W4EBkICANZBgIAGQkpGQUFBQYQQjVlsAW2EBFGAEgDYDYECBEBVhAN5XYACA/VuBAZCAgDVz//////////////////////////8WkGAgAZCSkZCANZBgIAGQkpGQUFBQYQRtVlsAW2EBomAEgDYDYICBEBVhASxXYACA/VuBAZCAgDVz//////////////////////////8WkGAgAZCSkZCANXP//////////////////////////xaQYCABkJKRkIA1c///////////////////////////FpBgIAGQkpGQgDWQYCABkJKRkFBQUGEEuFZbAFthAfBgBIA2A2BAgRAVYQG6V2AAgP1bgQGQgIA1c///////////////////////////FpBgIAGQkpGQgDWQYCABkJKRkFBQUGEFp1ZbAFuBc///////////////////////////FmEI/IKQgRUCkGBAUWAAYEBRgIMDgYWIiPGTUFBQUBWAFWECOFc9YACAPj1gAP1bUIFz//////////////////////////8WYQj8YAKDgWECXVf+WwSQgRUCkGBAUWAAYEBRgIMDgYWIiPGTUFBQUBWAFWECiVc9YACAPj1gAP1bUIFz//////////////////////////8WYQj8YASDgWECrlf+WwSQgRUCkGBAUWAAYEBRgIMDgYWIiPGTUFBQUBWAFWEC2lc9YACAPj1gAP1bUIFz//////////////////////////8WYQj8YAiDgWEC/1f+WwSQgRUCkGBAUWAAYEBRgIMDgYWIiPGTUFBQUBWAFWEDK1c9YACAPj1gAP1bUIFz//////////////////////////8WYQj8YBCDgWEDUFf+WwSQgRUCkGBAUWAAYEBRgIMDgYWIiPGTUFBQUBWAFWEDfFc9YACAPj1gAP1bUIFz//////////////////////////8WYQj8YCCDgWEDoVf+WwSQgRUCkGBAUWAAYEBRgIMDgYWIiPGTUFBQUBWAFWEDzVc9YACAPj1gAP1bUIFz//////////////////////////8WYQj8YECDgWED8lf+WwSQgRUCkGBAUWAAYEBRgIMDgYWIiPGTUFBQUBWAFWEEHlc9YACAPj1gAP1bUFBQVlszc///////////////////////////FmEI/IKQgRUCkGBAUWAAYEBRgIMDgYWIiPGTUFBQUBWAFWEEaVc9YACAPj1gAP1bUFBWW4Fz//////////////////////////8WYQj8gpCBFQKQYEBRYABgQFGAgwOBhYiI8ZNQUFBQFYAVYQSzVz1gAIA+PWAA/VtQUFBWW4Nz//////////////////////////8WYQj8gpCBFQKQYEBRYABgQFGAgwOBhYiI8ZNQUFBQFYAVYQT+Vz1gAIA+PWAA/VtQgnP//////////////////////////xZhCPxgAoOBYQUjV/5bBJCBFQKQYEBRYABgQFGAgwOBhYiI8ZNQUFBQFYAVYQVPVz1gAIA+PWAA/VtQgXP//////////////////////////xZhCPxgBIOBYQV0V/5bBJCBFQKQYEBRYABgQFGAgwOBhYiI8ZNQUFBQFYAVYQWgVz1gAIA+PWAA/VtQUFBQUFZbgXP//////////////////////////xZhCPyCYAADkIEVApBgQFFgAGBAUYCDA4GFiIjxk1BQUFAVgBVhBfBXPWAAgD49YAD9W1BQUFb+omVienpyMVggz3kbv0fcthOTdEBSpiD3cbQp8HHwLjZXzj5fgKs/l71kc29sY0MABREAMiKAAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAogNMOOgMY1QhKFgoUAAAAAAAAAAAAAAAAAAAAAAAABFVyBwoDGNUIEAFSIAoJCgIYAhCP2KAQCgkKAhhiEICKoBAKCAoDGNUIEJBO"}]},"SpecialQueriesXTest":{"placeholderNum":1009,"encodedItems":[]},"sendHbarsToAddressesMultipleTimes":{"placeholderNum":1120,"encodedItems":[{"b64Body":"Cg8KCQjx/f6sBhDoDxICGAISAhgDGPuV9hQiAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjloxCiISIGbxRA94chGPNplK+KghOF9OQpc8M9I2dAzQR72UC3C1EIDIr6AlSgUIgM7aAw==","b64Record":"CiUIFhIDGOEIKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjDwuJjEAal2CvV9j32waCQEr0GHsaMQ9MILdV+0GnZ5OOcEX2JsDmtawZHkvuzf34saDAit/v6sBhCs5eaEAyIPCgkI8f3+rAYQ6A8SAhgCKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjlIZCgoKAhgCEP+P38BKCgsKAxjhCBCAkN/ASg=="},{"b64Body":"Cg8KCQjy/f6sBhDqDxICGAISAhgDGPuV9hQiAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjlouCiISIIUzeX1EaPeUCRwUZaJJKDFSC1NngBZ+g8cbGcXVdi9REJBOSgUIgM7aAw==","b64Record":"CiUIFhIDGOIIKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjDA2WJHyTVfB6oyF3UjBaVAoO9Puu4tSiP3UF4ZHqwkMS6Kq1TkcpyNRSTX/ZGYS9kaDAiu/v6sBhDj9+yMASIPCgkI8v3+rAYQ6g8SAhgCKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjlIVCggKAhgCEJ+cAQoJCgMY4ggQoJwB"},{"b64Body":"Cg8KCQjy/f6sBhDsDxICGAISAhgDGP7LzSwiAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjooBjwESDAiuzNmwBhCFuu3wAhptCiISIPrUO02Sr+xYGBzQOyV23uPS/WpIpgALD/Fbencg+amUCiM6IQN/Q+EDPJ2hRrPXW9RrDvLhsd8n0RrBV70P1gfCNNjFaAoiEiBBlvRmxFuZbxKQhOQXat967MYtbMcRZ2A3cKTxx9bP8yIMSGVsbG8gV29ybGQhKgAyAA==","b64Record":"CiUIFhoDGOMIKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjATypjj3Z9g8hlTYmtUjVlCAdrqXwafNwANX+IiP/7GrM7PpvXLo5SNzGNzaWZiAkcaDAiu/v6sBhDxiLKOAyIPCgkI8v3+rAYQ7A8SAhgCKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjlIA"},{"b64Body":"Cg8KCQjz/f6sBhDwDxICGAISAhgDGPOGlzoiAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjpoBghkKAxjjCCL6GDYwODA2MDQwNTI2MTA2MmE4MDYxMDAxMzYwMDAzOTYwMDBmM2ZlNjA4MDYwNDA1MjYwMDQzNjEwNjEwMDRhNTc2MDAwMzU2MGUwMWM4MDYzMjZhYjk3ZWExNDYxMDA0YzU3ODA2MzhkZDk4MDQzMTQ2MTAwOWE1NzgwNjNhY2VmNjAzNzE0NjEwMGM4NTc4MDYzZDJhMGZlMjYxNDYxMDExNjU3ODA2M2UxMmUzMjM4MTQ2MTAxYTQ1NzViMDA1YjYxMDA5ODYwMDQ4MDM2MDM2MDQwODExMDE1NjEwMDYyNTc2MDAwODBmZDViODEwMTkwODA4MDM1NzNmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmMTY5MDYwMjAwMTkwOTI5MTkwODAzNTkwNjAyMDAxOTA5MjkxOTA1MDUwNTA2MTAxZjI1NjViMDA1YjYxMDBjNjYwMDQ4MDM2MDM2MDIwODExMDE1NjEwMGIwNTc2MDAwODBmZDViODEwMTkwODA4MDM1OTA2MDIwMDE5MDkyOTE5MDUwNTA1MDYxMDQyMzU2NWIwMDViNjEwMTE0NjAwNDgwMzYwMzYwNDA4MTEwMTU2MTAwZGU1NzYwMDA4MGZkNWI4MTAxOTA4MDgwMzU3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNjkwNjAyMDAxOTA5MjkxOTA4MDM1OTA2MDIwMDE5MDkyOTE5MDUwNTA1MDYxMDQ2ZDU2NWIwMDViNjEwMWEyNjAwNDgwMzYwMzYwODA4MTEwMTU2MTAxMmM1NzYwMDA4MGZkNWI4MTAxOTA4MDgwMzU3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNjkwNjAyMDAxOTA5MjkxOTA4MDM1NzNmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmMTY5MDYwMjAwMTkwOTI5MTkwODAzNTczZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjE2OTA2MDIwMDE5MDkyOTE5MDgwMzU5MDYwMjAwMTkwOTI5MTkwNTA1MDUwNjEwNGI4NTY1YjAwNWI2MTAxZjA2MDA0ODAzNjAzNjA0MDgxMTAxNTYxMDFiYTU3NjAwMDgwZmQ1YjgxMDE5MDgwODAzNTczZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjE2OTA2MDIwMDE5MDkyOTE5MDgwMzU5MDYwMjAwMTkwOTI5MTkwNTA1MDUwNjEwNWE3NTY1YjAwNWI4MTczZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjE2NjEwOGZjODI5MDgxMTUwMjkwNjA0MDUxNjAwMDYwNDA1MTgwODMwMzgxODU4ODg4ZjE5MzUwNTA1MDUwMTU4MDE1NjEwMjM4NTczZDYwMDA4MDNlM2Q2MDAwZmQ1YjUwODE3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNjYxMDhmYzYwMDI4MzgxNjEwMjVkNTdmZTViMDQ5MDgxMTUwMjkwNjA0MDUxNjAwMDYwNDA1MTgwODMwMzgxODU4ODg4ZjE5MzUwNTA1MDUwMTU4MDE1NjEwMjg5NTczZDYwMDA4MDNlM2Q2MDAwZmQ1YjUwODE3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNjYxMDhmYzYwMDQ4MzgxNjEwMmFlNTdmZTViMDQ5MDgxMTUwMjkwNjA0MDUxNjAwMDYwNDA1MTgwODMwMzgxODU4ODg4ZjE5MzUwNTA1MDUwMTU4MDE1NjEwMmRhNTczZDYwMDA4MDNlM2Q2MDAwZmQ1YjUwODE3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNjYxMDhmYzYwMDg4MzgxNjEwMmZmNTdmZTViMDQ5MDgxMTUwMjkwNjA0MDUxNjAwMDYwNDA1MTgwODMwMzgxODU4ODg4ZjE5MzUwNTA1MDUwMTU4MDE1NjEwMzJiNTczZDYwMDA4MDNlM2Q2MDAwZmQ1YjUwODE3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNjYxMDhmYzYwMTA4MzgxNjEwMzUwNTdmZTViMDQ5MDgxMTUwMjkwNjA0MDUxNjAwMDYwNDA1MTgwODMwMzgxODU4ODg4ZjE5MzUwNTA1MDUwMTU4MDE1NjEwMzdjNTczZDYwMDA4MDNlM2Q2MDAwZmQ1YjUwODE3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNjYxMDhmYzYwMjA4MzgxNjEwM2ExNTdmZTViMDQ5MDgxMTUwMjkwNjA0MDUxNjAwMDYwNDA1MTgwODMwMzgxODU4ODg4ZjE5MzUwNTA1MDUwMTU4MDE1NjEwM2NkNTczZDYwMDA4MDNlM2Q2MDAwZmQ1YjUwODE3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNjYxMDhmYzYwNDA4MzgxNjEwM2YyNTdmZTViMDQ5MDgxMTUwMjkwNjA0MDUxNjAwMDYwNDA1MTgwODMwMzgxODU4ODg4ZjE5MzUwNTA1MDUwMTU4MDE1NjEwNDFlNTczZDYwMDA4MDNlM2Q2MDAwZmQ1YjUwNTA1MDU2NWIzMzczZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjE2NjEwOGZjODI5MDgxMTUwMjkwNjA0MDUxNjAwMDYwNDA1MTgwODMwMzgxODU4ODg4ZjE5MzUwNTA1MDUwMTU4MDE1NjEwNDY5NTczZDYwMDA4MDNlM2Q2MDAwZmQ1YjUwNTA1NjViODE3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNjYxMDhmYzgyOTA4MTE1MDI5MDYwNDA1MTYwMDA2MDQwNTE4MDgzMDM4MTg1ODg4OGYxOTM1MDUwNTA1MDE1ODAxNTYxMDRiMzU3M2Q2MDAwODAzZTNkNjAwMGZkNWI1MDUwNTA1NjViODM3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNjYxMDhmYzgyOTA4MTE1MDI5MDYwNDA1MTYwMDA2MDQwNTE4MDgzMDM4MTg1ODg4OGYxOTM1MDUwNTA1MDE1ODAxNTYxMDRmZTU3M2Q2MDAwODAzZTNkNjAwMGZkNWI1MDgyNzNmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmMTY2MTA4ZmM2MDAyODM4MTYxMDUyMzU3ZmU1YjA0OTA4MTE1MDI5MDYwNDA1MTYwMDA2MDQwNTE4MDgzMDM4MTg1ODg4OGYxOTM1MDUwNTA1MDE1ODAxNTYxMDU0ZjU3M2Q2MDAwODAzZTNkNjAwMGZkNWI1MDgxNzNmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmMTY2MTA4ZmM2MDA0ODM4MTYxMDU3NDU3ZmU1YjA0OTA4MTE1MDI5MDYwNDA1MTYwMDA2MDQwNTE4MDgzMDM4MTg1ODg4OGYxOTM1MDUwNTA1MDE1ODAxNTYxMDVhMDU3M2Q2MDAwODAzZTNkNjAwMGZkNWI1MDUwNTA1MDUwNTY1YjgxNzNmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmMTY2MTA4ZmM4MjYwMDAwMzkwODExNTAyOTA2MDQwNTE2MDAwNjA0MDUxODA4MzAzODE4NTg4ODhmMTkzNTA1MDUwNTAxNTgwMTU2MTA1ZjA1NzNkNjAwMDgwM2UzZDYwMDBmZDViNTA1MDUwNTZmZWEyNjU2MjdhN2E3MjMxNTgyMGNmNzkxYmJmNDdkY2I2MTM5Mzc0NDA1MmE2MjBmNzcxYjQyOWYwNzFmMDJlMzY1N2NlM2U1ZjgwYWIzZjk3YmQ2NDczNmY2YzYzNDMwMDA1MTEwMDMy","b64Record":"CiAIFiocCgwIARAMGgYIgK6ZpA8SDAgBEA8aBgiArpmkDxIwKpl+ck8gGxN1UyOt74WY7wpTFwz7EsOvPC8RL7Y77xh3Kh7462DJaBxUOWJVr27KGgwIr/7+rAYQt/jDlgEiDwoJCPP9/qwGEPAPEgIYAiogw4PCrsOCwrfDg8K5dEY4w4LCrkrDg8KLw4PCkMODwo5SAA=="},{"b64Body":"ChAKCQjz/f6sBhDyDxIDGOEIEgIYAxiW+66dAiICCHgyIMODwq7DgsK3w4PCuXRGOMOCwq5Kw4PCi8ODwpDDg8KOQkgKAxjjCBoiEiBoGTU6IEzkcSx7AatFqn5rl1Xst+IWsOkmNaJUhniM7yCQoQ8okE5CBQiAztoDUgBaAGoLY2VsbGFyIGRvb3I=","b64Record":"CiUIFiIDGOQIKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjAWYBYk457AyB8gOjSA2bIrVMehJ0Wu+Kg/hkDS60XICgH7iNalgLrCH5NE7LCHgVwaDAiv/v6sBhCchqj7AiIQCgkI8/3+rAYQ8g8SAxjhCCogw4PCrsOCwrfDg8K5dEY4w4LCrkrDg8KLw4PCkMODwo4w1tSRpAJC3w4KAxjkCBKqDGCAYEBSYAQ2EGEASldgADVg4ByAYyarl+oUYQBMV4BjjdmAQxRhAJpXgGOs72A3FGEAyFeAY9Kg/iYUYQEWV4Bj4S4yOBRhAaRXWwBbYQCYYASANgNgQIEQFWEAYldgAID9W4EBkICANXP//////////////////////////xaQYCABkJKRkIA1kGAgAZCSkZBQUFBhAfJWWwBbYQDGYASANgNgIIEQFWEAsFdgAID9W4EBkICANZBgIAGQkpGQUFBQYQQjVlsAW2EBFGAEgDYDYECBEBVhAN5XYACA/VuBAZCAgDVz//////////////////////////8WkGAgAZCSkZCANZBgIAGQkpGQUFBQYQRtVlsAW2EBomAEgDYDYICBEBVhASxXYACA/VuBAZCAgDVz//////////////////////////8WkGAgAZCSkZCANXP//////////////////////////xaQYCABkJKRkIA1c///////////////////////////FpBgIAGQkpGQgDWQYCABkJKRkFBQUGEEuFZbAFthAfBgBIA2A2BAgRAVYQG6V2AAgP1bgQGQgIA1c///////////////////////////FpBgIAGQkpGQgDWQYCABkJKRkFBQUGEFp1ZbAFuBc///////////////////////////FmEI/IKQgRUCkGBAUWAAYEBRgIMDgYWIiPGTUFBQUBWAFWECOFc9YACAPj1gAP1bUIFz//////////////////////////8WYQj8YAKDgWECXVf+WwSQgRUCkGBAUWAAYEBRgIMDgYWIiPGTUFBQUBWAFWECiVc9YACAPj1gAP1bUIFz//////////////////////////8WYQj8YASDgWECrlf+WwSQgRUCkGBAUWAAYEBRgIMDgYWIiPGTUFBQUBWAFWEC2lc9YACAPj1gAP1bUIFz//////////////////////////8WYQj8YAiDgWEC/1f+WwSQgRUCkGBAUWAAYEBRgIMDgYWIiPGTUFBQUBWAFWEDK1c9YACAPj1gAP1bUIFz//////////////////////////8WYQj8YBCDgWEDUFf+WwSQgRUCkGBAUWAAYEBRgIMDgYWIiPGTUFBQUBWAFWEDfFc9YACAPj1gAP1bUIFz//////////////////////////8WYQj8YCCDgWEDoVf+WwSQgRUCkGBAUWAAYEBRgIMDgYWIiPGTUFBQUBWAFWEDzVc9YACAPj1gAP1bUIFz//////////////////////////8WYQj8YECDgWED8lf+WwSQgRUCkGBAUWAAYEBRgIMDgYWIiPGTUFBQUBWAFWEEHlc9YACAPj1gAP1bUFBQVlszc///////////////////////////FmEI/IKQgRUCkGBAUWAAYEBRgIMDgYWIiPGTUFBQUBWAFWEEaVc9YACAPj1gAP1bUFBWW4Fz//////////////////////////8WYQj8gpCBFQKQYEBRYABgQFGAgwOBhYiI8ZNQUFBQFYAVYQSzVz1gAIA+PWAA/VtQUFBWW4Nz//////////////////////////8WYQj8gpCBFQKQYEBRYABgQFGAgwOBhYiI8ZNQUFBQFYAVYQT+Vz1gAIA+PWAA/VtQgnP//////////////////////////xZhCPxgAoOBYQUjV/5bBJCBFQKQYEBRYABgQFGAgwOBhYiI8ZNQUFBQFYAVYQVPVz1gAIA+PWAA/VtQgXP//////////////////////////xZhCPxgBIOBYQV0V/5bBJCBFQKQYEBRYABgQFGAgwOBhYiI8ZNQUFBQFYAVYQWgVz1gAIA+PWAA/VtQUFBQUFZbgXP//////////////////////////xZhCPyCYAADkIEVApBgQFFgAGBAUYCDA4GFiIjxk1BQUFAVgBVhBfBXPWAAgD49YAD9W1BQUFb+omVienpyMVggz3kbv0fcthOTdEBSpiD3cbQp8HHwLjZXzj5fgKs/l71kc29sY0MABREAMiKAAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAowJoMOgMY5AhKFgoUAAAAAAAAAAAAAAAAAAAAAAAABGRyBwoDGOQIEAFSRwoJCgIYAxCY4okUCgoKAhhiEODciMYDCgoKAxigBhCatYg3CgoKAxihBhCatYg3CgsKAxjhCBDLxaTIBAoJCgMY5AgQoJwB"},{"b64Body":"ChAKCQj0/f6sBhD6DxIDGOEIEgIYAxjgrLEDIgIIeDIgw4PCrsOCwrfDg8K5dEY4w4LCrkrDg8KLw4PCkMODwo46TwoDGOQIEKCNBiJEJquX6gAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAARiAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEA=","b64Record":"CiUIFiIDGOQIKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjAZGLyiQqVoOtMaYY73vrlgFtEUrPI2dKKGrUh6vMH22BPTwnH7rA9fFLKo9yO3JQ8aDAiw/v6sBhCMzoegASIQCgkI9P3+rAYQ+g8SAxjhCCogw4PCrsOCwrfDg8K5dEY4w4LCrkrDg8KLw4PCkMODwo4wgNfaAjqMAgoDGOQIIoACAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACiA8QRSKwoJCgIYYhCArrUFCgoKAxjhCBD/rbUFCggKAxjiCBD+AQoICgMY5AgQ/QE="}]},"sendHbarsToDifferentAddresses":{"placeholderNum":1125,"encodedItems":[{"b64Body":"Cg8KCQj5/f6sBhCUEBICGAISAhgDGPuV9hQiAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjloxCiISICTf+CaEoDHPHUFVesWtpg0sk7I5zVsOt67vkHkpjc6BEIDIr6AlSgUIgM7aAw==","b64Record":"CiUIFhIDGOYIKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjCJjrQ9zw0Xc8NoN4JWKuvGhoZpxAchumIgC2Lx5hafGrJVcjVdTk3aqugxSaEdUpoaCwi1/v6sBhC0hO9AIg8KCQj5/f6sBhCUEBICGAIqIMODwq7DgsK3w4PCuXRGOMOCwq5Kw4PCi8ODwpDDg8KOUhkKCgoCGAIQ/4/fwEoKCwoDGOYIEICQ38BK"},{"b64Body":"Cg8KCQj5/f6sBhCWEBICGAISAhgDGPuV9hQiAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjlouCiISIA4d2WHDNsm3OqCWFr0cNzZZnxOrMXOQljpXpg6ooeZbEJBOSgUIgM7aAw==","b64Record":"CiUIFhIDGOcIKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjCiRqIedEFYtTU5CnVSbGEeZRp7y5fuWFg7HWhZe7nR8tdc6iUwgZ/yWTRv368zCMsaDAi1/v6sBhDZttulAiIPCgkI+f3+rAYQlhASAhgCKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjlIVCggKAhgCEJ+cAQoJCgMY5wgQoJwB"},{"b64Body":"Cg8KCQj6/f6sBhCYEBICGAISAhgDGPuV9hQiAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjlouCiISIN/l62J7Hs+P52g8dKF10lK5K9LvKl8rorJDzwikV0K1EJBOSgUIgM7aAw==","b64Record":"CiUIFhIDGOgIKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjBt1hkXbYy74yhCxV1+dktPKT4qknJru8CAqwKDg5ojR9GfBxCEoRdaGtd73NOmowkaCwi2/v6sBhDx4tItIg8KCQj6/f6sBhCYEBICGAIqIMODwq7DgsK3w4PCuXRGOMOCwq5Kw4PCi8ODwpDDg8KOUhUKCAoCGAIQn5wBCgkKAxjoCBCgnAE="},{"b64Body":"Cg8KCQj6/f6sBhCaEBICGAISAhgDGPuV9hQiAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjlouCiISIJlINE6qkubBourz4u0PkqYxwtop5kNjSMy3nCTWxzMqEJBOSgUIgM7aAw==","b64Record":"CiUIFhIDGOkIKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjBgLTDCtj+B5rLLlWFVeCq6lyzrsHi52KdIFHT1M0o0x+tQ62clI8v26qHRlcrDHGYaDAi2/v6sBhDejpGvAiIPCgkI+v3+rAYQmhASAhgCKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjlIVCggKAhgCEJ+cAQoJCgMY6QgQoJwB"},{"b64Body":"Cg8KCQj7/f6sBhCcEBICGAISAhgDGP7LzSwiAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjooBjgESCwi3zNmwBhCWp9kwGm0KIhIgzKse5mR6kOH7KDn+8MP9JCe3iHNANdCWIeXtl7rKCSsKIzohA3ZhsXhoeUaVIt+QLev9OUbZyDlvTkMQEPx7zy/839jFCiISIIU1dbcyXoEvnTFtAqBhoiQoKlUXtm8/t+ta3Fxbc62YIgxIZWxsbyBXb3JsZCEqADIA","b64Record":"CiUIFhoDGOoIKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjBnS3+FnNbns701mQxXyyKHX1it/gbsE6NAyltwlq3uCyXc8uwMrgOGI9WA9m++Ch0aCwi3/v6sBhCwsJs3Ig8KCQj7/f6sBhCcEBICGAIqIMODwq7DgsK3w4PCuXRGOMOCwq5Kw4PCi8ODwpDDg8KOUgA="},{"b64Body":"Cg8KCQj7/f6sBhCgEBICGAISAhgDGPOGlzoiAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjpoBghkKAxjqCCL6GDYwODA2MDQwNTI2MTA2MmE4MDYxMDAxMzYwMDAzOTYwMDBmM2ZlNjA4MDYwNDA1MjYwMDQzNjEwNjEwMDRhNTc2MDAwMzU2MGUwMWM4MDYzMjZhYjk3ZWExNDYxMDA0YzU3ODA2MzhkZDk4MDQzMTQ2MTAwOWE1NzgwNjNhY2VmNjAzNzE0NjEwMGM4NTc4MDYzZDJhMGZlMjYxNDYxMDExNjU3ODA2M2UxMmUzMjM4MTQ2MTAxYTQ1NzViMDA1YjYxMDA5ODYwMDQ4MDM2MDM2MDQwODExMDE1NjEwMDYyNTc2MDAwODBmZDViODEwMTkwODA4MDM1NzNmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmMTY5MDYwMjAwMTkwOTI5MTkwODAzNTkwNjAyMDAxOTA5MjkxOTA1MDUwNTA2MTAxZjI1NjViMDA1YjYxMDBjNjYwMDQ4MDM2MDM2MDIwODExMDE1NjEwMGIwNTc2MDAwODBmZDViODEwMTkwODA4MDM1OTA2MDIwMDE5MDkyOTE5MDUwNTA1MDYxMDQyMzU2NWIwMDViNjEwMTE0NjAwNDgwMzYwMzYwNDA4MTEwMTU2MTAwZGU1NzYwMDA4MGZkNWI4MTAxOTA4MDgwMzU3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNjkwNjAyMDAxOTA5MjkxOTA4MDM1OTA2MDIwMDE5MDkyOTE5MDUwNTA1MDYxMDQ2ZDU2NWIwMDViNjEwMWEyNjAwNDgwMzYwMzYwODA4MTEwMTU2MTAxMmM1NzYwMDA4MGZkNWI4MTAxOTA4MDgwMzU3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNjkwNjAyMDAxOTA5MjkxOTA4MDM1NzNmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmMTY5MDYwMjAwMTkwOTI5MTkwODAzNTczZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjE2OTA2MDIwMDE5MDkyOTE5MDgwMzU5MDYwMjAwMTkwOTI5MTkwNTA1MDUwNjEwNGI4NTY1YjAwNWI2MTAxZjA2MDA0ODAzNjAzNjA0MDgxMTAxNTYxMDFiYTU3NjAwMDgwZmQ1YjgxMDE5MDgwODAzNTczZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjE2OTA2MDIwMDE5MDkyOTE5MDgwMzU5MDYwMjAwMTkwOTI5MTkwNTA1MDUwNjEwNWE3NTY1YjAwNWI4MTczZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjE2NjEwOGZjODI5MDgxMTUwMjkwNjA0MDUxNjAwMDYwNDA1MTgwODMwMzgxODU4ODg4ZjE5MzUwNTA1MDUwMTU4MDE1NjEwMjM4NTczZDYwMDA4MDNlM2Q2MDAwZmQ1YjUwODE3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNjYxMDhmYzYwMDI4MzgxNjEwMjVkNTdmZTViMDQ5MDgxMTUwMjkwNjA0MDUxNjAwMDYwNDA1MTgwODMwMzgxODU4ODg4ZjE5MzUwNTA1MDUwMTU4MDE1NjEwMjg5NTczZDYwMDA4MDNlM2Q2MDAwZmQ1YjUwODE3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNjYxMDhmYzYwMDQ4MzgxNjEwMmFlNTdmZTViMDQ5MDgxMTUwMjkwNjA0MDUxNjAwMDYwNDA1MTgwODMwMzgxODU4ODg4ZjE5MzUwNTA1MDUwMTU4MDE1NjEwMmRhNTczZDYwMDA4MDNlM2Q2MDAwZmQ1YjUwODE3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNjYxMDhmYzYwMDg4MzgxNjEwMmZmNTdmZTViMDQ5MDgxMTUwMjkwNjA0MDUxNjAwMDYwNDA1MTgwODMwMzgxODU4ODg4ZjE5MzUwNTA1MDUwMTU4MDE1NjEwMzJiNTczZDYwMDA4MDNlM2Q2MDAwZmQ1YjUwODE3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNjYxMDhmYzYwMTA4MzgxNjEwMzUwNTdmZTViMDQ5MDgxMTUwMjkwNjA0MDUxNjAwMDYwNDA1MTgwODMwMzgxODU4ODg4ZjE5MzUwNTA1MDUwMTU4MDE1NjEwMzdjNTczZDYwMDA4MDNlM2Q2MDAwZmQ1YjUwODE3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNjYxMDhmYzYwMjA4MzgxNjEwM2ExNTdmZTViMDQ5MDgxMTUwMjkwNjA0MDUxNjAwMDYwNDA1MTgwODMwMzgxODU4ODg4ZjE5MzUwNTA1MDUwMTU4MDE1NjEwM2NkNTczZDYwMDA4MDNlM2Q2MDAwZmQ1YjUwODE3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNjYxMDhmYzYwNDA4MzgxNjEwM2YyNTdmZTViMDQ5MDgxMTUwMjkwNjA0MDUxNjAwMDYwNDA1MTgwODMwMzgxODU4ODg4ZjE5MzUwNTA1MDUwMTU4MDE1NjEwNDFlNTczZDYwMDA4MDNlM2Q2MDAwZmQ1YjUwNTA1MDU2NWIzMzczZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjE2NjEwOGZjODI5MDgxMTUwMjkwNjA0MDUxNjAwMDYwNDA1MTgwODMwMzgxODU4ODg4ZjE5MzUwNTA1MDUwMTU4MDE1NjEwNDY5NTczZDYwMDA4MDNlM2Q2MDAwZmQ1YjUwNTA1NjViODE3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNjYxMDhmYzgyOTA4MTE1MDI5MDYwNDA1MTYwMDA2MDQwNTE4MDgzMDM4MTg1ODg4OGYxOTM1MDUwNTA1MDE1ODAxNTYxMDRiMzU3M2Q2MDAwODAzZTNkNjAwMGZkNWI1MDUwNTA1NjViODM3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNjYxMDhmYzgyOTA4MTE1MDI5MDYwNDA1MTYwMDA2MDQwNTE4MDgzMDM4MTg1ODg4OGYxOTM1MDUwNTA1MDE1ODAxNTYxMDRmZTU3M2Q2MDAwODAzZTNkNjAwMGZkNWI1MDgyNzNmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmMTY2MTA4ZmM2MDAyODM4MTYxMDUyMzU3ZmU1YjA0OTA4MTE1MDI5MDYwNDA1MTYwMDA2MDQwNTE4MDgzMDM4MTg1ODg4OGYxOTM1MDUwNTA1MDE1ODAxNTYxMDU0ZjU3M2Q2MDAwODAzZTNkNjAwMGZkNWI1MDgxNzNmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmMTY2MTA4ZmM2MDA0ODM4MTYxMDU3NDU3ZmU1YjA0OTA4MTE1MDI5MDYwNDA1MTYwMDA2MDQwNTE4MDgzMDM4MTg1ODg4OGYxOTM1MDUwNTA1MDE1ODAxNTYxMDVhMDU3M2Q2MDAwODAzZTNkNjAwMGZkNWI1MDUwNTA1MDUwNTY1YjgxNzNmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmMTY2MTA4ZmM4MjYwMDAwMzkwODExNTAyOTA2MDQwNTE2MDAwNjA0MDUxODA4MzAzODE4NTg4ODhmMTkzNTA1MDUwNTAxNTgwMTU2MTA1ZjA1NzNkNjAwMDgwM2UzZDYwMDBmZDViNTA1MDUwNTZmZWEyNjU2MjdhN2E3MjMxNTgyMGNmNzkxYmJmNDdkY2I2MTM5Mzc0NDA1MmE2MjBmNzcxYjQyOWYwNzFmMDJlMzY1N2NlM2U1ZjgwYWIzZjk3YmQ2NDczNmY2YzYzNDMwMDA1MTEwMDMy","b64Record":"CiAIFiocCgwIARAMGgYIgK6ZpA8SDAgBEA8aBgiArpmkDxIwSEmVq/r6l/AgopnMMyre0s5N7+OQR8PJ6o6lh2eG7F3BmSkyQ6gM49cuavUMNcsqGgwIt/7+rAYQxO3CuAIiDwoJCPv9/qwGEKAQEgIYAiogw4PCrsOCwrfDg8K5dEY4w4LCrkrDg8KLw4PCkMODwo5SAA=="},{"b64Body":"ChAKCQj8/f6sBhCiEBIDGOYIEgIYAxiW+66dAiICCHgyIMODwq7DgsK3w4PCuXRGOMOCwq5Kw4PCi8ODwpDDg8KOQkgKAxjqCBoiEiB6+qNyaJgg3X4ft9qPP4iD4srJwCiKYkWJqsxop8B1fiCQoQ8okE5CBQiAztoDUgBaAGoLY2VsbGFyIGRvb3I=","b64Record":"CiUIFiIDGOsIKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjC+SwYI6ubO18uLgbeLcNBd5d3vaKKuBJwARo066RD+hM4iDmH/XVlr9jSvC+ByjKMaCwi4/v6sBhCP58tAIhAKCQj8/f6sBhCiEBIDGOYIKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjjDW1JGkAkLfDgoDGOsIEqoMYIBgQFJgBDYQYQBKV2AANWDgHIBjJquX6hRhAExXgGON2YBDFGEAmleAY6zvYDcUYQDIV4Bj0qD+JhRhARZXgGPhLjI4FGEBpFdbAFthAJhgBIA2A2BAgRAVYQBiV2AAgP1bgQGQgIA1c///////////////////////////FpBgIAGQkpGQgDWQYCABkJKRkFBQUGEB8lZbAFthAMZgBIA2A2AggRAVYQCwV2AAgP1bgQGQgIA1kGAgAZCSkZBQUFBhBCNWWwBbYQEUYASANgNgQIEQFWEA3ldgAID9W4EBkICANXP//////////////////////////xaQYCABkJKRkIA1kGAgAZCSkZBQUFBhBG1WWwBbYQGiYASANgNggIEQFWEBLFdgAID9W4EBkICANXP//////////////////////////xaQYCABkJKRkIA1c///////////////////////////FpBgIAGQkpGQgDVz//////////////////////////8WkGAgAZCSkZCANZBgIAGQkpGQUFBQYQS4VlsAW2EB8GAEgDYDYECBEBVhAbpXYACA/VuBAZCAgDVz//////////////////////////8WkGAgAZCSkZCANZBgIAGQkpGQUFBQYQWnVlsAW4Fz//////////////////////////8WYQj8gpCBFQKQYEBRYABgQFGAgwOBhYiI8ZNQUFBQFYAVYQI4Vz1gAIA+PWAA/VtQgXP//////////////////////////xZhCPxgAoOBYQJdV/5bBJCBFQKQYEBRYABgQFGAgwOBhYiI8ZNQUFBQFYAVYQKJVz1gAIA+PWAA/VtQgXP//////////////////////////xZhCPxgBIOBYQKuV/5bBJCBFQKQYEBRYABgQFGAgwOBhYiI8ZNQUFBQFYAVYQLaVz1gAIA+PWAA/VtQgXP//////////////////////////xZhCPxgCIOBYQL/V/5bBJCBFQKQYEBRYABgQFGAgwOBhYiI8ZNQUFBQFYAVYQMrVz1gAIA+PWAA/VtQgXP//////////////////////////xZhCPxgEIOBYQNQV/5bBJCBFQKQYEBRYABgQFGAgwOBhYiI8ZNQUFBQFYAVYQN8Vz1gAIA+PWAA/VtQgXP//////////////////////////xZhCPxgIIOBYQOhV/5bBJCBFQKQYEBRYABgQFGAgwOBhYiI8ZNQUFBQFYAVYQPNVz1gAIA+PWAA/VtQgXP//////////////////////////xZhCPxgQIOBYQPyV/5bBJCBFQKQYEBRYABgQFGAgwOBhYiI8ZNQUFBQFYAVYQQeVz1gAIA+PWAA/VtQUFBWWzNz//////////////////////////8WYQj8gpCBFQKQYEBRYABgQFGAgwOBhYiI8ZNQUFBQFYAVYQRpVz1gAIA+PWAA/VtQUFZbgXP//////////////////////////xZhCPyCkIEVApBgQFFgAGBAUYCDA4GFiIjxk1BQUFAVgBVhBLNXPWAAgD49YAD9W1BQUFZbg3P//////////////////////////xZhCPyCkIEVApBgQFFgAGBAUYCDA4GFiIjxk1BQUFAVgBVhBP5XPWAAgD49YAD9W1CCc///////////////////////////FmEI/GACg4FhBSNX/lsEkIEVApBgQFFgAGBAUYCDA4GFiIjxk1BQUFAVgBVhBU9XPWAAgD49YAD9W1CBc///////////////////////////FmEI/GAEg4FhBXRX/lsEkIEVApBgQFFgAGBAUYCDA4GFiIjxk1BQUFAVgBVhBaBXPWAAgD49YAD9W1BQUFBQVluBc///////////////////////////FmEI/IJgAAOQgRUCkGBAUWAAYEBRgIMDgYWIiPGTUFBQUBWAFWEF8Fc9YACAPj1gAP1bUFBQVv6iZWJ6enIxWCDPeRu/R9y2E5N0QFKmIPdxtCnwcfAuNlfOPl+Aqz+XvWRzb2xjQwAFEQAyIoACAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACjAmgw6AxjrCEoWChQAAAAAAAAAAAAAAAAAAAAAAAAEa3IHCgMY6wgQAVJHCgkKAhgDEJjiiRQKCgoCGGIQ4NyIxgMKCgoDGKAGEJq1iDcKCgoDGKEGEJq1iDcKCwoDGOYIEMvFpMgECgkKAxjrCBCgnAE="},{"b64Body":"ChAKCQj8/f6sBhC2EBIDGOYIEgIYAxjgrLEDIgIIeDIgw4PCrsOCwrfDg8K5dEY4w4LCrkrDg8KLw4PCkMODwo46kAEKAxjrCBCgjQYihAHSoP4mAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABGcAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEaAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAARpAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABQ=","b64Record":"CiUIFiIDGOsIKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjD7yBwbdeYC8xSGXqeCdXcwA8fywZ0bIqUzDcauoklDPzAVxosDmHw9XvDKnjg1YNcaDAi4/v6sBhDhraLCAiIQCgkI/P3+rAYQthASAxjmCCogw4PCrsOCwrfDg8K5dEY4w4LCrkrDg8KLw4PCkMODwo4wgNfaAjqMAgoDGOsIIoACAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACiA8QRSOwoJCgIYYhCArrUFCgoKAxjmCBD/rbUFCgcKAxjnCBAoCgcKAxjoCBAUCgcKAxjpCBAKCgcKAxjrCBBF"}]},"sendHbarsFromDifferentAddressessToAddress":{"placeholderNum":1132,"encodedItems":[{"b64Body":"Cg8KCQiB/v6sBhDMEBICGAISAhgDGPuV9hQiAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjloxCiISIA132D7y/dPDfSRNhWraeDTwhH5lEQkHlhOXXXwc/4UPEIDIr6AlSgUIgM7aAw==","b64Record":"CiUIFhIDGO0IKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjBXiIVhvh6wbrn9mpwWLbzczRYoGzb+ACtlIwnlbVU09xpK+BBsZXxbiwNBuV3Gbu4aDAi9/v6sBhCi4P7FASIPCgkIgf7+rAYQzBASAhgCKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjlIZCgoKAhgCEP+P38BKCgsKAxjtCBCAkN/ASg=="},{"b64Body":"Cg8KCQiB/v6sBhDOEBICGAISAhgDGPuV9hQiAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjlouCiISIB7PpCmGHp5C8eRRbKsxlBcG4J9uhuJsn7qTyikMYMExEJBOSgUIgM7aAw==","b64Record":"CiUIFhIDGO4IKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjCZ718/VC42wEQhvEzp1iXLv3eFb/mF+SjW6+hjDSjlD0PVBmXpKtLohe3CJuEmZLkaDAi9/v6sBhD588jHAyIPCgkIgf7+rAYQzhASAhgCKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjlIVCggKAhgCEJ+cAQoJCgMY7ggQoJwB"},{"b64Body":"Cg8KCQiC/v6sBhDQEBICGAISAhgDGP7LzSwiAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjooBjwESDAi+zNmwBhCGx83DARptCiISIP6EXjYdkk3KMa/oSVbJIdLHJh5UitI/P7EC0lHJAl0aCiM6IQPPfykw+C+aVN5JJgQWfkyhdjOmeo2ujDunIXUjKhPmqQoiEiB5oAPxVxhA/nFM0/JUogtdkdDGkrRbhgXc8J6seVJ3fiIMSGVsbG8gV29ybGQhKgAyAA==","b64Record":"CiUIFhoDGO8IKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjAh6n1zgFv/XTCghEE7xrSk3oRtOjqr85A87axbALpR2aiy/ho+JijeZBsdINCj4oUaDAi+/v6sBhD8/MPPASIPCgkIgv7+rAYQ0BASAhgCKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjlIA"},{"b64Body":"Cg8KCQiC/v6sBhDUEBICGAISAhgDGIydjj4iAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjpoBiCAKAxjvCCKAIDYwODA2MDQwNTI2MDQwNTE2MTBlNmIzODAzODA2MTBlNmI4MzM5ODE4MTAxNjA0MDUyNjA0MDgxMTAxNTYxMDAyNjU3NjAwMDgwZmQ1YjgxMDE5MDgwODA1MTkwNjAyMDAxOTA5MjkxOTA4MDUxOTA2MDIwMDE5MDkyOTE5MDUwNTA1MDgxNjAwMDgwNjEwMTAwMGE4MTU0ODE3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYwMjE5MTY5MDgzNzNmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmMTYwMjE3OTA1NTUwODA2MDAxNjAwMDYxMDEwMDBhODE1NDgxNzNmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmMDIxOTE2OTA4MzczZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjE2MDIxNzkwNTU1MDUwNTA2MTBkOTg4MDYxMDBkMzYwMDAzOTYwMDBmM2ZlNjA4MDYwNDA1MjYwMDQzNjEwNjEwMDNmNTc2MDAwMzU2MGUwMWM4MDYzNTU1NWQ3ZTQxNDYxMDA0MTU3ODA2MzVlY2YyOThhMTQ2MTAwY2Y1NzgwNjM5ZGM3ZGIxMDE0NjEwMGZkNTc4MDYzYTlhM2E2MzUxNDYxMDE0YjU3NWIwMDViNjEwMGNkNjAwNDgwMzYwMzYwODA4MTEwMTU2MTAwNTc1NzYwMDA4MGZkNWI4MTAxOTA4MDgwMzU3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNjkwNjAyMDAxOTA5MjkxOTA4MDM1NzNmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmMTY5MDYwMjAwMTkwOTI5MTkwODAzNTczZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjE2OTA2MDIwMDE5MDkyOTE5MDgwMzU5MDYwMjAwMTkwOTI5MTkwNTA1MDUwNjEwMTc5NTY1YjAwNWI2MTAwZmI2MDA0ODAzNjAzNjAyMDgxMTAxNTYxMDBlNTU3NjAwMDgwZmQ1YjgxMDE5MDgwODAzNTkwNjAyMDAxOTA5MjkxOTA1MDUwNTA2MTA3MTM1NjViMDA1YjYxMDE0OTYwMDQ4MDM2MDM2MDQwODExMDE1NjEwMTEzNTc2MDAwODBmZDViODEwMTkwODA4MDM1NzNmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmMTY5MDYwMjAwMTkwOTI5MTkwODAzNTkwNjAyMDAxOTA5MjkxOTA1MDUwNTA2MTA4MmY1NjViMDA1YjYxMDE3NzYwMDQ4MDM2MDM2MDIwODExMDE1NjEwMTYxNTc2MDAwODBmZDViODEwMTkwODA4MDM1OTA2MDIwMDE5MDkyOTE5MDUwNTA1MDYxMGEwZjU2NWIwMDViODM3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNjYxMDhmYzgyOTA4MTE1MDI5MDYwNDA1MTYwMDA2MDQwNTE4MDgzMDM4MTg1ODg4OGYxOTM1MDUwNTA1MDE1ODAxNTYxMDFiZjU3M2Q2MDAwODAzZTNkNjAwMGZkNWI1MDYwMDA4MDkwNTQ5MDYxMDEwMDBhOTAwNDczZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjE2NzNmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmMTY2M2FjZWY2MDM3ODU2MDAyODQ4MTYxMDIwODU3ZmU1YjA0NjA0MDUxODM2M2ZmZmZmZmZmMTY2MGUwMWI4MTUyNjAwNDAxODA4MzczZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjE2NzNmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmMTY4MTUyNjAyMDAxODI4MTUyNjAyMDAxOTI1MDUwNTA2MDAwNjA0MDUxODA4MzAzODE2MDAwODc4MDNiMTU4MDE1NjEwMjcyNTc2MDAwODBmZDViNTA1YWYxMTU4MDE1NjEwMjg2NTczZDYwMDA4MDNlM2Q2MDAwZmQ1YjUwNTA1MDUwNjAwMTYwMDA5MDU0OTA2MTAxMDAwYTkwMDQ3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNjczZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjE2NjNhY2VmNjAzNzg1NjAwMjg0ODE2MTAyZDM1N2ZlNWIwNDYwNDA1MTgzNjNmZmZmZmZmZjE2NjBlMDFiODE1MjYwMDQwMTgwODM3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNjczZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjE2ODE1MjYwMjAwMTgyODE1MjYwMjAwMTkyNTA1MDUwNjAwMDYwNDA1MTgwODMwMzgxNjAwMDg3ODAzYjE1ODAxNTYxMDMzZDU3NjAwMDgwZmQ1YjUwNWFmMTE1ODAxNTYxMDM1MTU3M2Q2MDAwODAzZTNkNjAwMGZkNWI1MDUwNTA1MDgyNzNmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmMTY2MTA4ZmM4MjkwODExNTAyOTA2MDQwNTE2MDAwNjA0MDUxODA4MzAzODE4NTg4ODhmMTkzNTA1MDUwNTAxNTgwMTU2MTAzOWI1NzNkNjAwMDgwM2UzZDYwMDBmZDViNTA2MDAwODA5MDU0OTA2MTAxMDAwYTkwMDQ3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNjczZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjE2NjNhY2VmNjAzNzg0NjAwMjg0ODE2MTAzZTQ1N2ZlNWIwNDYwNDA1MTgzNjNmZmZmZmZmZjE2NjBlMDFiODE1MjYwMDQwMTgwODM3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNjczZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjE2ODE1MjYwMjAwMTgyODE1MjYwMjAwMTkyNTA1MDUwNjAwMDYwNDA1MTgwODMwMzgxNjAwMDg3ODAzYjE1ODAxNTYxMDQ0ZTU3NjAwMDgwZmQ1YjUwNWFmMTE1ODAxNTYxMDQ2MjU3M2Q2MDAwODAzZTNkNjAwMGZkNWI1MDUwNTA1MDYwMDE2MDAwOTA1NDkwNjEwMTAwMGE5MDA0NzNmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmMTY3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNjYzYWNlZjYwMzc4NDYwMDI4NDgxNjEwNGFmNTdmZTViMDQ2MDQwNTE4MzYzZmZmZmZmZmYxNjYwZTAxYjgxNTI2MDA0MDE4MDgzNzNmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmMTY3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNjgxNTI2MDIwMDE4MjgxNTI2MDIwMDE5MjUwNTA1MDYwMDA2MDQwNTE4MDgzMDM4MTYwMDA4NzgwM2IxNTgwMTU2MTA1MTk1NzYwMDA4MGZkNWI1MDVhZjExNTgwMTU2MTA1MmQ1NzNkNjAwMDgwM2UzZDYwMDBmZDViNTA1MDUwNTA4MTczZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjE2NjEwOGZjODI5MDgxMTUwMjkwNjA0MDUxNjAwMDYwNDA1MTgwODMwMzgxODU4ODg4ZjE5MzUwNTA1MDUwMTU4MDE1NjEwNTc3NTczZDYwMDA4MDNlM2Q2MDAwZmQ1YjUwNjAwMDgwOTA1NDkwNjEwMTAwMGE5MDA0NzNmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmMTY3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNjYzYWNlZjYwMzc4MzYwMDI4NDgxNjEwNWMwNTdmZTViMDQ2MDQwNTE4MzYzZmZmZmZmZmYxNjYwZTAxYjgxNTI2MDA0MDE4MDgzNzNmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmMTY3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNjgxNTI2MDIwMDE4MjgxNTI2MDIwMDE5MjUwNTA1MDYwMDA2MDQwNTE4MDgzMDM4MTYwMDA4NzgwM2IxNTgwMTU2MTA2MmE1NzYwMDA4MGZkNWI1MDVhZjExNTgwMTU2MTA2M2U1NzNkNjAwMDgwM2UzZDYwMDBmZDViNTA1MDUwNTA2MDAxNjAwMDkwNTQ5MDYxMDEwMDBhOTAwNDczZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjE2NzNmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmMTY2M2FjZWY2MDM3ODM2MDAyODQ4MTYxMDY4YjU3ZmU1YjA0NjA0MDUxODM2M2ZmZmZmZmZmMTY2MGUwMWI4MTUyNjAwNDAxODA4MzczZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjE2NzNmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmMTY4MTUyNjAyMDAxODI4MTUyNjAyMDAxOTI1MDUwNTA2MDAwNjA0MDUxODA4MzAzODE2MDAwODc4MDNiMTU4MDE1NjEwNmY1NTc2MDAwODBmZDViNTA1YWYxMTU4MDE1NjEwNzA5NTczZDYwMDA4MDNlM2Q2MDAwZmQ1YjUwNTA1MDUwNTA1MDUwNTA1NjViNjAwMDgwOTA1NDkwNjEwMTAwMGE5MDA0NzNmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmY=","b64Record":"CiAIFiocCgwIARAMGgYIgK6ZpA8SDAgBEA8aBgiArpmkDxIw2CetI8FIdrR6PniaRCzVo7jzX9JFRm5oraYabZRjkiU85Mwx5IVvoMkABlgl91UYGgwIvv7+rAYQtfuX0QMiDwoJCIL+/qwGENQQEgIYAiogw4PCrsOCwrfDg8K5dEY4w4LCrkrDg8KLw4PCkMODwo5SAA=="},{"b64Body":"Cg8KCQiD/v6sBhDaEBICGAISAhgDGPjsozsiAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjoIB3hkSAxjvCCLWGWZmZmZmZmZmZmZmZmZmZmYxNjczZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjE2NjM4ZGQ5ODA0MzgyNjA0MDUxODI2M2ZmZmZmZmZmMTY2MGUwMWI4MTUyNjAwNDAxODA4MjgxNTI2MDIwMDE5MTUwNTA2MDAwNjA0MDUxODA4MzAzODE2MDAwODc4MDNiMTU4MDE1NjEwNzg3NTc2MDAwODBmZDViNTA1YWYxMTU4MDE1NjEwNzliNTczZDYwMDA4MDNlM2Q2MDAwZmQ1YjUwNTA1MDUwNjAwMTYwMDA5MDU0OTA2MTAxMDAwYTkwMDQ3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNjczZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjE2NjM4ZGQ5ODA0MzgyNjA0MDUxODI2M2ZmZmZmZmZmMTY2MGUwMWI4MTUyNjAwNDAxODA4MjgxNTI2MDIwMDE5MTUwNTA2MDAwNjA0MDUxODA4MzAzODE2MDAwODc4MDNiMTU4MDE1NjEwODE0NTc2MDAwODBmZDViNTA1YWYxMTU4MDE1NjEwODI4NTczZDYwMDA4MDNlM2Q2MDAwZmQ1YjUwNTA1MDUwNTA1NjViODE3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNjYxMDhmYzgyOTA4MTE1MDI5MDYwNDA1MTYwMDA2MDQwNTE4MDgzMDM4MTg1ODg4OGYxOTM1MDUwNTA1MDE1ODAxNTYxMDg3NTU3M2Q2MDAwODAzZTNkNjAwMGZkNWI1MDYwMDA4MDkwNTQ5MDYxMDEwMDBhOTAwNDczZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjE2NzNmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmMTY2M2FjZWY2MDM3ODM2MDAyODQ4MTYxMDhiZTU3ZmU1YjA0NjA0MDUxODM2M2ZmZmZmZmZmMTY2MGUwMWI4MTUyNjAwNDAxODA4MzczZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjE2NzNmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmMTY4MTUyNjAyMDAxODI4MTUyNjAyMDAxOTI1MDUwNTA2MDAwNjA0MDUxODA4MzAzODE2MDAwODc4MDNiMTU4MDE1NjEwOTI4NTc2MDAwODBmZDViNTA1YWYxMTU4MDE1NjEwOTNjNTczZDYwMDA4MDNlM2Q2MDAwZmQ1YjUwNTA1MDUwNjAwMTYwMDA5MDU0OTA2MTAxMDAwYTkwMDQ3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNjczZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjE2NjNhY2VmNjAzNzgzNjAwMjg0ODE2MTA5ODk1N2ZlNWIwNDYwNDA1MTgzNjNmZmZmZmZmZjE2NjBlMDFiODE1MjYwMDQwMTgwODM3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNjczZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjE2ODE1MjYwMjAwMTgyODE1MjYwMjAwMTkyNTA1MDUwNjAwMDYwNDA1MTgwODMwMzgxNjAwMDg3ODAzYjE1ODAxNTYxMDlmMzU3NjAwMDgwZmQ1YjUwNWFmMTE1ODAxNTYxMGEwNzU3M2Q2MDAwODAzZTNkNjAwMGZkNWI1MDUwNTA1MDUwNTA1NjViNjAwMDYwNjA2MDAwODA5MDU0OTA2MTAxMDAwYTkwMDQ3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNjczZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjE2ODM2MDQwNTE2MDI0MDE4MDgyODE1MjYwMjAwMTkxNTA1MDYwNDA1MTYwMjA4MTgzMDMwMzgxNTI5MDYwNDA1MjdmOGRkOTgwNDMwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDdiZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxOTE2NjAyMDgyMDE4MDUxN2JmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjgzODE4MzE2MTc4MzUyNTA1MDUwNTA2MDQwNTE4MDgyODA1MTkwNjAyMDAxOTA4MDgzODM1YjYwMjA4MzEwNjEwYjBjNTc4MDUxODI1MjYwMjA4MjAxOTE1MDYwMjA4MTAxOTA1MDYwMjA4MzAzOTI1MDYxMGFlOTU2NWI2MDAxODM2MDIwMDM2MTAxMDAwYTAzODAxOTgyNTExNjgxODQ1MTE2ODA4MjE3ODU1MjUwNTA1MDUwNTA1MDkwNTAwMTkxNTA1MDYwMDA2MDQwNTE4MDgzMDM4MTg1NWFmNDkxNTA1MDNkODA2MDAwODExNDYxMGI2YzU3NjA0MDUxOTE1MDYwMWYxOTYwM2YzZDAxMTY4MjAxNjA0MDUyM2Q4MjUyM2Q2MDAwNjAyMDg0MDEzZTYxMGI3MTU2NWI2MDYwOTE1MDViNTA5MTUwOTE1MDYwMDA2MDYwNjAwMTYwMDA5MDU0OTA2MTAxMDAwYTkwMDQ3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNjczZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjE2ODU2MDQwNTE2MDI0MDE4MDgyODE1MjYwMjAwMTkxNTA1MDYwNDA1MTYwMjA4MTgzMDMwMzgxNTI5MDYwNDA1MjdmOGRkOTgwNDMwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDdiZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxOTE2NjAyMDgyMDE4MDUxN2JmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjgzODE4MzE2MTc4MzUyNTA1MDUwNTA2MDQwNTE4MDgyODA1MTkwNjAyMDAxOTA4MDgzODM1YjYwMjA4MzEwNjEwYzc0NTc4MDUxODI1MjYwMjA4MjAxOTE1MDYwMjA4MTAxOTA1MDYwMjA4MzAzOTI1MDYxMGM1MTU2NWI2MDAxODM2MDIwMDM2MTAxMDAwYTAzODAxOTgyNTExNjgxODQ1MTE2ODA4MjE3ODU1MjUwNTA1MDUwNTA1MDkwNTAwMTkxNTA1MDYwMDA2MDQwNTE4MDgzMDM4MTg1NWFmNDkxNTA1MDNkODA2MDAwODExNDYxMGNkNDU3NjA0MDUxOTE1MDYwMWYxOTYwM2YzZDAxMTY4MjAxNjA0MDUyM2Q4MjUyM2Q2MDAwNjAyMDg0MDEzZTYxMGNkOTU2NWI2MDYwOTE1MDViNTA5MTUwOTE1MDgzMTU4MDYxMGNlOTU3NTA4MTE1NWIxNTYxMGQ1YzU3NjA0MDUxN2YwOGMzNzlhMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwODE1MjYwMDQwMTgwODA2MDIwMDE4MjgxMDM4MjUyNjAxZTgxNTI2MDIwMDE4MDdmNDQ2NTZjNjU2NzYxNzQ2NTIwNzQ3MjYxNmU3MzY2NjU3MjIwNjM2MTZjNmMyMDY2NjE2OTZjNjU2NDIxMDAwMDgxNTI1MDYwMjAwMTkxNTA1MDYwNDA1MTgwOTEwMzkwZmQ1YjUwNTA1MDUwNTA1NmZlYTI2NTYyN2E3YTcyMzE1ODIwNjE5ZDU1NTI1Y2QxN2Y3MzliMGNiMjZkODZmN2QxOGYxNTVlYWM0NWY5NTM3MGQ5NjRkZWEzYzcwZGIxNGQ2ZTY0NzM2ZjZjNjM0MzAwMDUxMTAwMzI=","b64Record":"CiAIFiocCgwIARAMGgYIgK6ZpA8SDAgBEA8aBgiArpmkDxIwcJGwH87+lRtrto8un6SzTvFGa0uukhKYv4hNJJG208MH/HUh48oKLwy/eMHCfnW2GgwIv/7+rAYQ3t6O2QEiDwoJCIP+/qwGENoQEgIYAiogw4PCrsOCwrfDg8K5dEY4w4LCrkrDg8KLw4PCkMODwo5SAA=="},{"b64Body":"Cg8KCQiD/v6sBhDcEBICGAISAhgDGP7LzSwiAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjooBjwESDAi/zNmwBhDL5Ka+AxptCiISIKAiRxoqow/HvaIBfa/UL4p4fIkxOTEzFhDvneYqT2JfCiM6IQK/k0xjAwwaKRw3O38pzHgKlC+KUq01FDubNWsfobsRtQoiEiDJ4uyEZCzgHd8rY1aVce4Y5Z+svWRGhD+nKl1Ghq+QcCIMSGVsbG8gV29ybGQhKgAyAA==","b64Record":"CiUIFhoDGPAIKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjDgWUjjFMmnDriwWOr9P7iRU5j/CvkwjmjYTfwB3sL+r1q1TFh/M1L/vBkxQEylbHYaDAi//v6sBhCls8PaAyIPCgkIg/7+rAYQ3BASAhgCKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjlIA"},{"b64Body":"Cg8KCQiE/v6sBhDgEBICGAISAhgDGM6Y0i8iAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjpoBkAYKAxjwCCKIBjYwODA2MDQwNTI2MTAxNzE4MDYxMDAxMzYwMDAzOTYwMDBmM2ZlNjA4MDYwNDA1MjYwMDQzNjEwNjEwMDI5NTc2MDAwMzU2MGUwMWM4MDYzOGRkOTgwNDMxNDYxMDAyYjU3ODA2M2FjZWY2MDM3MTQ2MTAwNTk1NzViMDA1YjYxMDA1NzYwMDQ4MDM2MDM2MDIwODExMDE1NjEwMDQxNTc2MDAwODBmZDViODEwMTkwODA4MDM1OTA2MDIwMDE5MDkyOTE5MDUwNTA1MDYxMDBhNzU2NWIwMDViNjEwMGE1NjAwNDgwMzYwMzYwNDA4MTEwMTU2MTAwNmY1NzYwMDA4MGZkNWI4MTAxOTA4MDgwMzU3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNjkwNjAyMDAxOTA5MjkxOTA4MDM1OTA2MDIwMDE5MDkyOTE5MDUwNTA1MDYxMDBmMTU2NWIwMDViMzM3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNjYxMDhmYzgyOTA4MTE1MDI5MDYwNDA1MTYwMDA2MDQwNTE4MDgzMDM4MTg1ODg4OGYxOTM1MDUwNTA1MDE1ODAxNTYxMDBlZDU3M2Q2MDAwODAzZTNkNjAwMGZkNWI1MDUwNTY1YjgxNzNmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmMTY2MTA4ZmM4MjkwODExNTAyOTA2MDQwNTE2MDAwNjA0MDUxODA4MzAzODE4NTg4ODhmMTkzNTA1MDUwNTAxNTgwMTU2MTAxMzc1NzNkNjAwMDgwM2UzZDYwMDBmZDViNTA1MDUwNTZmZWEyNjU2MjdhN2E3MjMxNTgyMDVlYjQwYmI2MjA4MzVmYjc5NjljZGQ3NWRlZDE4ODkyN2Q1YzkyN2Y5M2JhNjc1ZDY1YjljNTIzNDQ0MjQyODc2NDczNmY2YzYzNDMwMDA1MTEwMDMy","b64Record":"CiAIFiocCgwIARAMGgYIgK6ZpA8SDAgBEA8aBgiArpmkDxIwas5TIiYGCHockrYPLb1h/boxBOot9LKhdJ+Q279TBnqzCOkFdH3CEQQyUZwTYoYvGgwIwP7+rAYQ4vLK4gEiDwoJCIT+/qwGEOAQEgIYAiogw4PCrsOCwrfDg8K5dEY4w4LCrkrDg8KLw4PCkMODwo5SAA=="},{"b64Body":"ChAKCQiE/v6sBhDiEBIDGO0IEgIYAxiW+66dAiICCHgyIMODwq7DgsK3w4PCuXRGOMOCwq5Kw4PCi8ODwpDDg8KOQkgKAxjwCBoiEiBOFADUbvDGHhlNMnfEXmMFqg1X2D++USQaqT3sawzqsSCQoQ8okE5CBQiAztoDUgBaAGoLY2VsbGFyIGRvb3I=","b64Record":"CiUIFiIDGPEIKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjBXf1hn0oY04asB6zTPLWqToBCJlO+6g6WeTUmIKj0+zSuWICOEk3eF5KRhrrCGXmEaCwjB/v6sBhCGraUHIhAKCQiE/v6sBhDiEBIDGO0IKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjjDW1JGkAkKmBQoDGPEIEvECYIBgQFJgBDYQYQApV2AANWDgHIBjjdmAQxRhACtXgGOs72A3FGEAWVdbAFthAFdgBIA2A2AggRAVYQBBV2AAgP1bgQGQgIA1kGAgAZCSkZBQUFBhAKdWWwBbYQClYASANgNgQIEQFWEAb1dgAID9W4EBkICANXP//////////////////////////xaQYCABkJKRkIA1kGAgAZCSkZBQUFBhAPFWWwBbM3P//////////////////////////xZhCPyCkIEVApBgQFFgAGBAUYCDA4GFiIjxk1BQUFAVgBVhAO1XPWAAgD49YAD9W1BQVluBc///////////////////////////FmEI/IKQgRUCkGBAUWAAYEBRgIMDgYWIiPGTUFBQUBWAFWEBN1c9YACAPj1gAP1bUFBQVv6iZWJ6enIxWCBetAu2IINft5ac3XXe0YiSfVySf5O6Z11lucUjREJCh2Rzb2xjQwAFEQAyIoACAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACjAmgw6AxjxCEoWChQAAAAAAAAAAAAAAAAAAAAAAAAEcXIHCgMY8QgQAVJHCgkKAhgDEJjiiRQKCgoCGGIQ4NyIxgMKCgoDGKAGEJq1iDcKCgoDGKEGEJq1iDcKCwoDGO0IEMvFpMgECgkKAxjxCBCgnAE="},{"b64Body":"ChAKCQiF/v6sBhDkEBIDGO0IEgIYAxiW+66dAiICCHgyIMODwq7DgsK3w4PCuXRGOMOCwq5Kw4PCi8ODwpDDg8KOQkgKAxjwCBoiEiCkv5DWz4L2s9mNokDo7Wk42ylnW71ntG8ieQILoRHMsyCQoQ8okE5CBQiAztoDUgBaAGoLY2VsbGFyIGRvb3I=","b64Record":"CiUIFiIDGPIIKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjCJ0YcQcpP+PtcnHvsQL8tPPYRYA+a2OavJ03UZ97sM+92M0tmUWE+owZAIf1Ct2NQaDAjB/v6sBhC6hLPsASIQCgkIhf7+rAYQ5BASAxjtCCogw4PCrsOCwrfDg8K5dEY4w4LCrkrDg8KLw4PCkMODwo4w1tSRpAJCpgUKAxjyCBLxAmCAYEBSYAQ2EGEAKVdgADVg4ByAY43ZgEMUYQArV4BjrO9gNxRhAFlXWwBbYQBXYASANgNgIIEQFWEAQVdgAID9W4EBkICANZBgIAGQkpGQUFBQYQCnVlsAW2EApWAEgDYDYECBEBVhAG9XYACA/VuBAZCAgDVz//////////////////////////8WkGAgAZCSkZCANZBgIAGQkpGQUFBQYQDxVlsAWzNz//////////////////////////8WYQj8gpCBFQKQYEBRYABgQFGAgwOBhYiI8ZNQUFBQFYAVYQDtVz1gAIA+PWAA/VtQUFZbgXP//////////////////////////xZhCPyCkIEVApBgQFFgAGBAUYCDA4GFiIjxk1BQUFAVgBVhATdXPWAAgD49YAD9W1BQUFb+omVienpyMVggXrQLtiCDX7eWnN113tGIkn1ckn+TumddZbnFI0RCQodkc29sY0MABREAMiKAAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAowJoMOgMY8ghKFgoUAAAAAAAAAAAAAAAAAAAAAAAABHJyBwoDGPIIEAFSRwoJCgIYAxCY4okUCgoKAhhiEODciMYDCgoKAxigBhCatYg3CgoKAxihBhCatYg3CgsKAxjtCBDLxaTIBAoJCgMY8ggQoJwB"},{"b64Body":"ChAKCQiF/v6sBhDsEBIDGO0IEgIYAxjVgL+gAiICCHgyIMODwq7DgsK3w4PCuXRGOMOCwq5Kw4PCi8ODwpDDg8KOQooBCgMY7wgaIhIg/yp3zexbwSRnbNBj1mOSCBCR1sC/LQhbDuqFrWWzr1wgkKEPKJBOQgUIgM7aA0pAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABHEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEclIAWgBqC2NlbGxhciBkb29y","b64Record":"CiUIFiIDGPMIKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjB8FH04ILNNbGgPP3uCRjUtHwq6eotU6K3csD1ECUQWnkdeXINmBjmeGXuBWX0R6fUaCwjC/v6sBhDD2N4QIhAKCQiF/v6sBhDsEBIDGO0IKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjjCV2qGnAkLNHQoDGPMIEpgbYIBgQFJgBDYQYQA/V2AANWDgHIBjVVXX5BRhAEFXgGNezymKFGEAz1eAY53H2xAUYQD9V4BjqaOmNRRhAUtXWwBbYQDNYASANgNggIEQFWEAV1dgAID9W4EBkICANXP//////////////////////////xaQYCABkJKRkIA1c///////////////////////////FpBgIAGQkpGQgDVz//////////////////////////8WkGAgAZCSkZCANZBgIAGQkpGQUFBQYQF5VlsAW2EA+2AEgDYDYCCBEBVhAOVXYACA/VuBAZCAgDWQYCABkJKRkFBQUGEHE1ZbAFthAUlgBIA2A2BAgRAVYQETV2AAgP1bgQGQgIA1c///////////////////////////FpBgIAGQkpGQgDWQYCABkJKRkFBQUGEIL1ZbAFthAXdgBIA2A2AggRAVYQFhV2AAgP1bgQGQgIA1kGAgAZCSkZBQUFBhCg9WWwBbg3P//////////////////////////xZhCPyCkIEVApBgQFFgAGBAUYCDA4GFiIjxk1BQUFAVgBVhAb9XPWAAgD49YAD9W1BgAICQVJBhAQAKkARz//////////////////////////8Wc///////////////////////////FmOs72A3hWAChIFhAghX/lsEYEBRg2P/////FmDgG4FSYAQBgINz//////////////////////////8Wc///////////////////////////FoFSYCABgoFSYCABklBQUGAAYEBRgIMDgWAAh4A7FYAVYQJyV2AAgP1bUFrxFYAVYQKGVz1gAIA+PWAA/VtQUFBQYAFgAJBUkGEBAAqQBHP//////////////////////////xZz//////////////////////////8WY6zvYDeFYAKEgWEC01f+WwRgQFGDY/////8WYOAbgVJgBAGAg3P//////////////////////////xZz//////////////////////////8WgVJgIAGCgVJgIAGSUFBQYABgQFGAgwOBYACHgDsVgBVhAz1XYACA/VtQWvEVgBVhA1FXPWAAgD49YAD9W1BQUFCCc///////////////////////////FmEI/IKQgRUCkGBAUWAAYEBRgIMDgYWIiPGTUFBQUBWAFWEDm1c9YACAPj1gAP1bUGAAgJBUkGEBAAqQBHP//////////////////////////xZz//////////////////////////8WY6zvYDeEYAKEgWED5Ff+WwRgQFGDY/////8WYOAbgVJgBAGAg3P//////////////////////////xZz//////////////////////////8WgVJgIAGCgVJgIAGSUFBQYABgQFGAgwOBYACHgDsVgBVhBE5XYACA/VtQWvEVgBVhBGJXPWAAgD49YAD9W1BQUFBgAWAAkFSQYQEACpAEc///////////////////////////FnP//////////////////////////xZjrO9gN4RgAoSBYQSvV/5bBGBAUYNj/////xZg4BuBUmAEAYCDc///////////////////////////FnP//////////////////////////xaBUmAgAYKBUmAgAZJQUFBgAGBAUYCDA4FgAIeAOxWAFWEFGVdgAID9W1Ba8RWAFWEFLVc9YACAPj1gAP1bUFBQUIFz//////////////////////////8WYQj8gpCBFQKQYEBRYABgQFGAgwOBhYiI8ZNQUFBQFYAVYQV3Vz1gAIA+PWAA/VtQYACAkFSQYQEACpAEc///////////////////////////FnP//////////////////////////xZjrO9gN4NgAoSBYQXAV/5bBGBAUYNj/////xZg4BuBUmAEAYCDc///////////////////////////FnP//////////////////////////xaBUmAgAYKBUmAgAZJQUFBgAGBAUYCDA4FgAIeAOxWAFWEGKldgAID9W1Ba8RWAFWEGPlc9YACAPj1gAP1bUFBQUGABYACQVJBhAQAKkARz//////////////////////////8Wc///////////////////////////FmOs72A3g2AChIFhBotX/lsEYEBRg2P/////FmDgG4FSYAQBgINz//////////////////////////8Wc///////////////////////////FoFSYCABgoFSYCABklBQUGAAYEBRgIMDgWAAh4A7FYAVYQb1V2AAgP1bUFrxFYAVYQcJVz1gAIA+PWAA/VtQUFBQUFBQUFZbYACAkFSQYQEACpAEc///////////////////////////FnP//////////////////////////xZjjdmAQ4JgQFGCY/////8WYOAbgVJgBAGAgoFSYCABkVBQYABgQFGAgwOBYACHgDsVgBVhB4dXYACA/VtQWvEVgBVhB5tXPWAAgD49YAD9W1BQUFBgAWAAkFSQYQEACpAEc///////////////////////////FnP//////////////////////////xZjjdmAQ4JgQFGCY/////8WYOAbgVJgBAGAgoFSYCABkVBQYABgQFGAgwOBYACHgDsVgBVhCBRXYACA/VtQWvEVgBVhCChXPWAAgD49YAD9W1BQUFBQVluBc///////////////////////////FmEI/IKQgRUCkGBAUWAAYEBRgIMDgYWIiPGTUFBQUBWAFWEIdVc9YACAPj1gAP1bUGAAgJBUkGEBAAqQBHP//////////////////////////xZz//////////////////////////8WY6zvYDeDYAKEgWEIvlf+WwRgQFGDY/////8WYOAbgVJgBAGAg3P//////////////////////////xZz//////////////////////////8WgVJgIAGCgVJgIAGSUFBQYABgQFGAgwOBYACHgDsVgBVhCShXYACA/VtQWvEVgBVhCTxXPWAAgD49YAD9W1BQUFBgAWAAkFSQYQEACpAEc///////////////////////////FnP//////////////////////////xZjrO9gN4NgAoSBYQmJV/5bBGBAUYNj/////xZg4BuBUmAEAYCDc///////////////////////////FnP//////////////////////////xaBUmAgAYKBUmAgAZJQUFBgAGBAUYCDA4FgAIeAOxWAFWEJ81dgAID9W1Ba8RWAFWEKB1c9YACAPj1gAP1bUFBQUFBQVltgAGBgYACAkFSQYQEACpAEc///////////////////////////FnP//////////////////////////xaDYEBRYCQBgIKBUmAgAZFQUGBAUWAggYMDA4FSkGBAUn+N2YBDAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHv/////////////////////////////////////GRZgIIIBgFF7/////////////////////////////////////4OBgxYXg1JQUFBQYEBRgIKAUZBgIAGQgIODW2AggxBhCwxXgFGCUmAgggGRUGAggQGQUGAggwOSUGEK6VZbYAGDYCADYQEACgOAGYJRFoGEURaAgheFUlBQUFBQUJBQAZFQUGAAYEBRgIMDgYVa9JFQUD2AYACBFGELbFdgQFGRUGAfGWA/PQEWggFgQFI9glI9YABgIIQBPmELcVZbYGCRUFtQkVCRUGAAYGBgAWAAkFSQYQEACpAEc///////////////////////////FnP//////////////////////////xaFYEBRYCQBgIKBUmAgAZFQUGBAUWAggYMDA4FSkGBAUn+N2YBDAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHv/////////////////////////////////////GRZgIIIBgFF7/////////////////////////////////////4OBgxYXg1JQUFBQYEBRgIKAUZBgIAGQgIODW2AggxBhDHRXgFGCUmAgggGRUGAggQGQUGAggwOSUGEMUVZbYAGDYCADYQEACgOAGYJRFoGEURaAgheFUlBQUFBQUJBQAZFQUGAAYEBRgIMDgYVa9JFQUD2AYACBFGEM1FdgQFGRUGAfGWA/PQEWggFgQFI9glI9YABgIIQBPmEM2VZbYGCRUFtQkVCRUIMVgGEM6VdQgRVbFWENXFdgQFF/CMN5oAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACBUmAEAYCAYCABgoEDglJgHoFSYCABgH9EZWxlZ2F0ZSB0cmFuc2ZlciBjYWxsIGZhaWxlZCEAAIFSUGAgAZFQUGBAUYCRA5D9W1BQUFBQVv6iZWJ6enIxWCBhnVVSXNF/c5sMsm2G99GPFV6sRflTcNlk3qPHDbFNbmRzb2xjQwAFEQAyIoACAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACjAmgw6AxjzCEoWChQAAAAAAAAAAAAAAAAAAAAAAAAEc3IHCgMY8wgQAVJHCgkKAhgDEK7quBQKCgoCGGIQmJLjygMKCgoDGKAGEPLb0zcKCgoDGKEGEPLb0zcKCwoDGO0IEMnQxM4ECgkKAxjzCBCgnAE="},{"b64Body":"ChAKCQiG/v6sBhDuEBIDGO0IEgIYAxjgrLEDIgIIeDIgw4PCrsOCwrfDg8K5dEY4w4LCrkrDg8KLw4PCkMODwo46TwoDGPMIEKCNBiJEncfbEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAARuAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACg=","b64Record":"CiUIFiIDGPMIKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjCh0st2CTIOnJ3tVOZMTvgYz5Y5Fo+02vWWwXr5rm/H3rTE6LtUNsV5kdOztKrUxUIaDAjC/v6sBhDvhdr1ASIQCgkIhv7+rAYQ7hASAxjtCCogw4PCrsOCwrfDg8K5dEY4w4LCrkrDg8KLw4PCkMODwo4wgNfaAjqMAgoDGPMIIoACAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACiA8QRSPAoJCgIYYhCArrUFCgoKAxjtCBD/rbUFCggKAxjuCBCgAQoHCgMY8QgQJwoHCgMY8ggQJwoHCgMY8wgQTw=="}]},"sendHbarsFromAndToDifferentAddressess":{"placeholderNum":1140,"encodedItems":[{"b64Body":"Cg8KCQiL/v6sBhCKERICGAISAhgDGPuV9hQiAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjloyCiISIO+qO89LRbNcLl2QjXlab3CyAKgrG1ofUOLN1EB6JOoIEIDAqMqaOkoFCIDO2gM=","b64Record":"CiUIFhIDGPUIKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjCoHho1iFCEXWjiCuzJiL6yfIX7pft5yYe100V9VT+iMIRtY2F/oNWB7RZOLkMEMDkaCwjH/v6sBhCUm4p6Ig8KCQiL/v6sBhCKERICGAIqIMODwq7DgsK3w4PCuXRGOMOCwq5Kw4PCi8ODwpDDg8KOUhsKCwoCGAIQ///QlLV0CgwKAxj1CBCAgNGUtXQ="},{"b64Body":"Cg8KCQiL/v6sBhCMERICGAISAhgDGPuV9hQiAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjlouCiISIPfrq5QWrAvxf6HC2idvEhB1WSIsj9UCZIv9cp0jmE61EJBOSgUIgM7aAw==","b64Record":"CiUIFhIDGPYIKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjD36Q0LkkQaGzB7YtoGo+O+pl5rHZGCXeSAtMNox4ICgd5LFfv5HRDck3MY4zHUy9AaDAjH/v6sBhDJq8beAiIPCgkIi/7+rAYQjBESAhgCKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjlIVCggKAhgCEJ+cAQoJCgMY9ggQoJwB"},{"b64Body":"Cg8KCQiM/v6sBhCOERICGAISAhgDGPuV9hQiAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjlouCiISIP0R8/MVlQrTO0NShrPCnXIDe0XMYAlyhW92t9TsB8OgEJBOSgUIgM7aAw==","b64Record":"CiUIFhIDGPcIKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjDLiH7bqyp7bmehuiDTJYXfZNjbDpNP1XLJgxZFuvr7RkfRenXYiJDYPbVEtE5kVUYaDAjI/v6sBhCBzpWDASIPCgkIjP7+rAYQjhESAhgCKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjlIVCggKAhgCEJ+cAQoJCgMY9wgQoJwB"},{"b64Body":"Cg8KCQiM/v6sBhCQERICGAISAhgDGPuV9hQiAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjlouCiISIJsrJPM9JzNTHITAj3kKLNodq7L9oJzwXk88raM/0oS9EJBOSgUIgM7aAw==","b64Record":"CiUIFhIDGPgIKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjDytMLiy04dc+3l6w3AEmZ0PInmUBWUVYWbtZ3WSY8ahLJL6Fwcpx6oqrhMEWKS2L4aDAjI/v6sBhDi96XoAiIPCgkIjP7+rAYQkBESAhgCKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjlIVCggKAhgCEJ+cAQoJCgMY+AgQoJwB"},{"b64Body":"Cg8KCQiN/v6sBhCSERICGAISAhgDGP7LzSwiAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjooBjgESCwjJzNmwBhCXg5FzGm0KIhIgMn23655AWQ8q0hHYa3scbdK+ebNbQr55pthFOgvmV7wKIzohA3YI13kPCTubafKgM30LE490Bj2B1CF3eyFLPyiYhWNPCiISIFHTpmYk7nl3oHNiSClcbCBTBrkitceXKQhLjKViXZaKIgxIZWxsbyBXb3JsZCEqADIA","b64Record":"CiUIFhoDGPkIKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjD7Kh/YQTiX1iN83fePijoEI/R1Msa8j9K2G/ZnNYFeA0vY5v7ayh+Kllv/67B+CosaDAjJ/v6sBhCFvMGWASIPCgkIjf7+rAYQkhESAhgCKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjlIA"},{"b64Body":"Cg8KCQiN/v6sBhCWERICGAISAhgDGIydjj4iAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjpoBiCAKAxj5CCKAIDYwODA2MDQwNTI2MDQwNTE2MTBlNmIzODAzODA2MTBlNmI4MzM5ODE4MTAxNjA0MDUyNjA0MDgxMTAxNTYxMDAyNjU3NjAwMDgwZmQ1YjgxMDE5MDgwODA1MTkwNjAyMDAxOTA5MjkxOTA4MDUxOTA2MDIwMDE5MDkyOTE5MDUwNTA1MDgxNjAwMDgwNjEwMTAwMGE4MTU0ODE3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYwMjE5MTY5MDgzNzNmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmMTYwMjE3OTA1NTUwODA2MDAxNjAwMDYxMDEwMDBhODE1NDgxNzNmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmMDIxOTE2OTA4MzczZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjE2MDIxNzkwNTU1MDUwNTA2MTBkOTg4MDYxMDBkMzYwMDAzOTYwMDBmM2ZlNjA4MDYwNDA1MjYwMDQzNjEwNjEwMDNmNTc2MDAwMzU2MGUwMWM4MDYzNTU1NWQ3ZTQxNDYxMDA0MTU3ODA2MzVlY2YyOThhMTQ2MTAwY2Y1NzgwNjM5ZGM3ZGIxMDE0NjEwMGZkNTc4MDYzYTlhM2E2MzUxNDYxMDE0YjU3NWIwMDViNjEwMGNkNjAwNDgwMzYwMzYwODA4MTEwMTU2MTAwNTc1NzYwMDA4MGZkNWI4MTAxOTA4MDgwMzU3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNjkwNjAyMDAxOTA5MjkxOTA4MDM1NzNmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmMTY5MDYwMjAwMTkwOTI5MTkwODAzNTczZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjE2OTA2MDIwMDE5MDkyOTE5MDgwMzU5MDYwMjAwMTkwOTI5MTkwNTA1MDUwNjEwMTc5NTY1YjAwNWI2MTAwZmI2MDA0ODAzNjAzNjAyMDgxMTAxNTYxMDBlNTU3NjAwMDgwZmQ1YjgxMDE5MDgwODAzNTkwNjAyMDAxOTA5MjkxOTA1MDUwNTA2MTA3MTM1NjViMDA1YjYxMDE0OTYwMDQ4MDM2MDM2MDQwODExMDE1NjEwMTEzNTc2MDAwODBmZDViODEwMTkwODA4MDM1NzNmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmMTY5MDYwMjAwMTkwOTI5MTkwODAzNTkwNjAyMDAxOTA5MjkxOTA1MDUwNTA2MTA4MmY1NjViMDA1YjYxMDE3NzYwMDQ4MDM2MDM2MDIwODExMDE1NjEwMTYxNTc2MDAwODBmZDViODEwMTkwODA4MDM1OTA2MDIwMDE5MDkyOTE5MDUwNTA1MDYxMGEwZjU2NWIwMDViODM3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNjYxMDhmYzgyOTA4MTE1MDI5MDYwNDA1MTYwMDA2MDQwNTE4MDgzMDM4MTg1ODg4OGYxOTM1MDUwNTA1MDE1ODAxNTYxMDFiZjU3M2Q2MDAwODAzZTNkNjAwMGZkNWI1MDYwMDA4MDkwNTQ5MDYxMDEwMDBhOTAwNDczZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjE2NzNmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmMTY2M2FjZWY2MDM3ODU2MDAyODQ4MTYxMDIwODU3ZmU1YjA0NjA0MDUxODM2M2ZmZmZmZmZmMTY2MGUwMWI4MTUyNjAwNDAxODA4MzczZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjE2NzNmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmMTY4MTUyNjAyMDAxODI4MTUyNjAyMDAxOTI1MDUwNTA2MDAwNjA0MDUxODA4MzAzODE2MDAwODc4MDNiMTU4MDE1NjEwMjcyNTc2MDAwODBmZDViNTA1YWYxMTU4MDE1NjEwMjg2NTczZDYwMDA4MDNlM2Q2MDAwZmQ1YjUwNTA1MDUwNjAwMTYwMDA5MDU0OTA2MTAxMDAwYTkwMDQ3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNjczZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjE2NjNhY2VmNjAzNzg1NjAwMjg0ODE2MTAyZDM1N2ZlNWIwNDYwNDA1MTgzNjNmZmZmZmZmZjE2NjBlMDFiODE1MjYwMDQwMTgwODM3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNjczZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjE2ODE1MjYwMjAwMTgyODE1MjYwMjAwMTkyNTA1MDUwNjAwMDYwNDA1MTgwODMwMzgxNjAwMDg3ODAzYjE1ODAxNTYxMDMzZDU3NjAwMDgwZmQ1YjUwNWFmMTE1ODAxNTYxMDM1MTU3M2Q2MDAwODAzZTNkNjAwMGZkNWI1MDUwNTA1MDgyNzNmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmMTY2MTA4ZmM4MjkwODExNTAyOTA2MDQwNTE2MDAwNjA0MDUxODA4MzAzODE4NTg4ODhmMTkzNTA1MDUwNTAxNTgwMTU2MTAzOWI1NzNkNjAwMDgwM2UzZDYwMDBmZDViNTA2MDAwODA5MDU0OTA2MTAxMDAwYTkwMDQ3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNjczZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjE2NjNhY2VmNjAzNzg0NjAwMjg0ODE2MTAzZTQ1N2ZlNWIwNDYwNDA1MTgzNjNmZmZmZmZmZjE2NjBlMDFiODE1MjYwMDQwMTgwODM3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNjczZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjE2ODE1MjYwMjAwMTgyODE1MjYwMjAwMTkyNTA1MDUwNjAwMDYwNDA1MTgwODMwMzgxNjAwMDg3ODAzYjE1ODAxNTYxMDQ0ZTU3NjAwMDgwZmQ1YjUwNWFmMTE1ODAxNTYxMDQ2MjU3M2Q2MDAwODAzZTNkNjAwMGZkNWI1MDUwNTA1MDYwMDE2MDAwOTA1NDkwNjEwMTAwMGE5MDA0NzNmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmMTY3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNjYzYWNlZjYwMzc4NDYwMDI4NDgxNjEwNGFmNTdmZTViMDQ2MDQwNTE4MzYzZmZmZmZmZmYxNjYwZTAxYjgxNTI2MDA0MDE4MDgzNzNmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmMTY3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNjgxNTI2MDIwMDE4MjgxNTI2MDIwMDE5MjUwNTA1MDYwMDA2MDQwNTE4MDgzMDM4MTYwMDA4NzgwM2IxNTgwMTU2MTA1MTk1NzYwMDA4MGZkNWI1MDVhZjExNTgwMTU2MTA1MmQ1NzNkNjAwMDgwM2UzZDYwMDBmZDViNTA1MDUwNTA4MTczZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjE2NjEwOGZjODI5MDgxMTUwMjkwNjA0MDUxNjAwMDYwNDA1MTgwODMwMzgxODU4ODg4ZjE5MzUwNTA1MDUwMTU4MDE1NjEwNTc3NTczZDYwMDA4MDNlM2Q2MDAwZmQ1YjUwNjAwMDgwOTA1NDkwNjEwMTAwMGE5MDA0NzNmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmMTY3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNjYzYWNlZjYwMzc4MzYwMDI4NDgxNjEwNWMwNTdmZTViMDQ2MDQwNTE4MzYzZmZmZmZmZmYxNjYwZTAxYjgxNTI2MDA0MDE4MDgzNzNmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmMTY3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNjgxNTI2MDIwMDE4MjgxNTI2MDIwMDE5MjUwNTA1MDYwMDA2MDQwNTE4MDgzMDM4MTYwMDA4NzgwM2IxNTgwMTU2MTA2MmE1NzYwMDA4MGZkNWI1MDVhZjExNTgwMTU2MTA2M2U1NzNkNjAwMDgwM2UzZDYwMDBmZDViNTA1MDUwNTA2MDAxNjAwMDkwNTQ5MDYxMDEwMDBhOTAwNDczZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjE2NzNmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmMTY2M2FjZWY2MDM3ODM2MDAyODQ4MTYxMDY4YjU3ZmU1YjA0NjA0MDUxODM2M2ZmZmZmZmZmMTY2MGUwMWI4MTUyNjAwNDAxODA4MzczZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjE2NzNmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmMTY4MTUyNjAyMDAxODI4MTUyNjAyMDAxOTI1MDUwNTA2MDAwNjA0MDUxODA4MzAzODE2MDAwODc4MDNiMTU4MDE1NjEwNmY1NTc2MDAwODBmZDViNTA1YWYxMTU4MDE1NjEwNzA5NTczZDYwMDA4MDNlM2Q2MDAwZmQ1YjUwNTA1MDUwNTA1MDUwNTA1NjViNjAwMDgwOTA1NDkwNjEwMTAwMGE5MDA0NzNmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmY=","b64Record":"CiAIFiocCgwIARAMGgYIgK6ZpA8SDAgBEA8aBgiArpmkDxIw/h28eoq0i31zDmiKFRfCA3+cHc/MlrpJaYW2WaOkhhdtQi6IZD2BVuhjpLWSE9OwGgwIyf7+rAYQ17XM+wIiDwoJCI3+/qwGEJYREgIYAiogw4PCrsOCwrfDg8K5dEY4w4LCrkrDg8KLw4PCkMODwo5SAA=="},{"b64Body":"Cg8KCQiO/v6sBhCcERICGAISAhgDGPjsozsiAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjoIB3hkSAxj5CCLWGWZmZmZmZmZmZmZmZmZmZmYxNjczZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjE2NjM4ZGQ5ODA0MzgyNjA0MDUxODI2M2ZmZmZmZmZmMTY2MGUwMWI4MTUyNjAwNDAxODA4MjgxNTI2MDIwMDE5MTUwNTA2MDAwNjA0MDUxODA4MzAzODE2MDAwODc4MDNiMTU4MDE1NjEwNzg3NTc2MDAwODBmZDViNTA1YWYxMTU4MDE1NjEwNzliNTczZDYwMDA4MDNlM2Q2MDAwZmQ1YjUwNTA1MDUwNjAwMTYwMDA5MDU0OTA2MTAxMDAwYTkwMDQ3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNjczZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjE2NjM4ZGQ5ODA0MzgyNjA0MDUxODI2M2ZmZmZmZmZmMTY2MGUwMWI4MTUyNjAwNDAxODA4MjgxNTI2MDIwMDE5MTUwNTA2MDAwNjA0MDUxODA4MzAzODE2MDAwODc4MDNiMTU4MDE1NjEwODE0NTc2MDAwODBmZDViNTA1YWYxMTU4MDE1NjEwODI4NTczZDYwMDA4MDNlM2Q2MDAwZmQ1YjUwNTA1MDUwNTA1NjViODE3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNjYxMDhmYzgyOTA4MTE1MDI5MDYwNDA1MTYwMDA2MDQwNTE4MDgzMDM4MTg1ODg4OGYxOTM1MDUwNTA1MDE1ODAxNTYxMDg3NTU3M2Q2MDAwODAzZTNkNjAwMGZkNWI1MDYwMDA4MDkwNTQ5MDYxMDEwMDBhOTAwNDczZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjE2NzNmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmMTY2M2FjZWY2MDM3ODM2MDAyODQ4MTYxMDhiZTU3ZmU1YjA0NjA0MDUxODM2M2ZmZmZmZmZmMTY2MGUwMWI4MTUyNjAwNDAxODA4MzczZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjE2NzNmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmMTY4MTUyNjAyMDAxODI4MTUyNjAyMDAxOTI1MDUwNTA2MDAwNjA0MDUxODA4MzAzODE2MDAwODc4MDNiMTU4MDE1NjEwOTI4NTc2MDAwODBmZDViNTA1YWYxMTU4MDE1NjEwOTNjNTczZDYwMDA4MDNlM2Q2MDAwZmQ1YjUwNTA1MDUwNjAwMTYwMDA5MDU0OTA2MTAxMDAwYTkwMDQ3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNjczZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjE2NjNhY2VmNjAzNzgzNjAwMjg0ODE2MTA5ODk1N2ZlNWIwNDYwNDA1MTgzNjNmZmZmZmZmZjE2NjBlMDFiODE1MjYwMDQwMTgwODM3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNjczZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjE2ODE1MjYwMjAwMTgyODE1MjYwMjAwMTkyNTA1MDUwNjAwMDYwNDA1MTgwODMwMzgxNjAwMDg3ODAzYjE1ODAxNTYxMDlmMzU3NjAwMDgwZmQ1YjUwNWFmMTE1ODAxNTYxMGEwNzU3M2Q2MDAwODAzZTNkNjAwMGZkNWI1MDUwNTA1MDUwNTA1NjViNjAwMDYwNjA2MDAwODA5MDU0OTA2MTAxMDAwYTkwMDQ3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNjczZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjE2ODM2MDQwNTE2MDI0MDE4MDgyODE1MjYwMjAwMTkxNTA1MDYwNDA1MTYwMjA4MTgzMDMwMzgxNTI5MDYwNDA1MjdmOGRkOTgwNDMwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDdiZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxOTE2NjAyMDgyMDE4MDUxN2JmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjgzODE4MzE2MTc4MzUyNTA1MDUwNTA2MDQwNTE4MDgyODA1MTkwNjAyMDAxOTA4MDgzODM1YjYwMjA4MzEwNjEwYjBjNTc4MDUxODI1MjYwMjA4MjAxOTE1MDYwMjA4MTAxOTA1MDYwMjA4MzAzOTI1MDYxMGFlOTU2NWI2MDAxODM2MDIwMDM2MTAxMDAwYTAzODAxOTgyNTExNjgxODQ1MTE2ODA4MjE3ODU1MjUwNTA1MDUwNTA1MDkwNTAwMTkxNTA1MDYwMDA2MDQwNTE4MDgzMDM4MTg1NWFmNDkxNTA1MDNkODA2MDAwODExNDYxMGI2YzU3NjA0MDUxOTE1MDYwMWYxOTYwM2YzZDAxMTY4MjAxNjA0MDUyM2Q4MjUyM2Q2MDAwNjAyMDg0MDEzZTYxMGI3MTU2NWI2MDYwOTE1MDViNTA5MTUwOTE1MDYwMDA2MDYwNjAwMTYwMDA5MDU0OTA2MTAxMDAwYTkwMDQ3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNjczZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjE2ODU2MDQwNTE2MDI0MDE4MDgyODE1MjYwMjAwMTkxNTA1MDYwNDA1MTYwMjA4MTgzMDMwMzgxNTI5MDYwNDA1MjdmOGRkOTgwNDMwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDdiZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxOTE2NjAyMDgyMDE4MDUxN2JmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjgzODE4MzE2MTc4MzUyNTA1MDUwNTA2MDQwNTE4MDgyODA1MTkwNjAyMDAxOTA4MDgzODM1YjYwMjA4MzEwNjEwYzc0NTc4MDUxODI1MjYwMjA4MjAxOTE1MDYwMjA4MTAxOTA1MDYwMjA4MzAzOTI1MDYxMGM1MTU2NWI2MDAxODM2MDIwMDM2MTAxMDAwYTAzODAxOTgyNTExNjgxODQ1MTE2ODA4MjE3ODU1MjUwNTA1MDUwNTA1MDkwNTAwMTkxNTA1MDYwMDA2MDQwNTE4MDgzMDM4MTg1NWFmNDkxNTA1MDNkODA2MDAwODExNDYxMGNkNDU3NjA0MDUxOTE1MDYwMWYxOTYwM2YzZDAxMTY4MjAxNjA0MDUyM2Q4MjUyM2Q2MDAwNjAyMDg0MDEzZTYxMGNkOTU2NWI2MDYwOTE1MDViNTA5MTUwOTE1MDgzMTU4MDYxMGNlOTU3NTA4MTE1NWIxNTYxMGQ1YzU3NjA0MDUxN2YwOGMzNzlhMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwODE1MjYwMDQwMTgwODA2MDIwMDE4MjgxMDM4MjUyNjAxZTgxNTI2MDIwMDE4MDdmNDQ2NTZjNjU2NzYxNzQ2NTIwNzQ3MjYxNmU3MzY2NjU3MjIwNjM2MTZjNmMyMDY2NjE2OTZjNjU2NDIxMDAwMDgxNTI1MDYwMjAwMTkxNTA1MDYwNDA1MTgwOTEwMzkwZmQ1YjUwNTA1MDUwNTA1NmZlYTI2NTYyN2E3YTcyMzE1ODIwNjE5ZDU1NTI1Y2QxN2Y3MzliMGNiMjZkODZmN2QxOGYxNTVlYWM0NWY5NTM3MGQ5NjRkZWEzYzcwZGIxNGQ2ZTY0NzM2ZjZjNjM0MzAwMDUxMTAwMzI=","b64Record":"CiAIFiocCgwIARAMGgYIgK6ZpA8SDAgBEA8aBgiArpmkDxIwsvFS3Nf6wmviPStdGFuO7Sci8KU/M7m2PFhPDlT1wD/3vNdiX4ZHeoEuv6iAa3LJGgwIyv7+rAYQjJSigwEiDwoJCI7+/qwGEJwREgIYAiogw4PCrsOCwrfDg8K5dEY4w4LCrkrDg8KLw4PCkMODwo5SAA=="},{"b64Body":"Cg8KCQiO/v6sBhCeERICGAISAhgDGP7LzSwiAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjooBjwESDAjKzNmwBhDSiZ/wAhptCiISIGMLarCrM86prcPKlwqk1taGtWKFMLRg3nuIrO/2ObH5CiM6IQKDn99q2egsv7eOkp5FIKqaj098aYeekwjt0h/acp2w1AoiEiCB7mfXpqbcgqx/M7dFN0fgeIn6qURzbthDPRP2NlN5JSIMSGVsbG8gV29ybGQhKgAyAA==","b64Record":"CiUIFhoDGPoIKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjDwpOvbuLA+SiAhjFh4+zSNspmn0th4JKj+d4NEMA9tQ4ffylAJ2hwd4gQ2i6tRRPoaDAjK/v6sBhD08aqOAyIPCgkIjv7+rAYQnhESAhgCKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjlIA"},{"b64Body":"Cg8KCQiP/v6sBhCiERICGAISAhgDGM6Y0i8iAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjpoBkAYKAxj6CCKIBjYwODA2MDQwNTI2MTAxNzE4MDYxMDAxMzYwMDAzOTYwMDBmM2ZlNjA4MDYwNDA1MjYwMDQzNjEwNjEwMDI5NTc2MDAwMzU2MGUwMWM4MDYzOGRkOTgwNDMxNDYxMDAyYjU3ODA2M2FjZWY2MDM3MTQ2MTAwNTk1NzViMDA1YjYxMDA1NzYwMDQ4MDM2MDM2MDIwODExMDE1NjEwMDQxNTc2MDAwODBmZDViODEwMTkwODA4MDM1OTA2MDIwMDE5MDkyOTE5MDUwNTA1MDYxMDBhNzU2NWIwMDViNjEwMGE1NjAwNDgwMzYwMzYwNDA4MTEwMTU2MTAwNmY1NzYwMDA4MGZkNWI4MTAxOTA4MDgwMzU3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNjkwNjAyMDAxOTA5MjkxOTA4MDM1OTA2MDIwMDE5MDkyOTE5MDUwNTA1MDYxMDBmMTU2NWIwMDViMzM3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNjYxMDhmYzgyOTA4MTE1MDI5MDYwNDA1MTYwMDA2MDQwNTE4MDgzMDM4MTg1ODg4OGYxOTM1MDUwNTA1MDE1ODAxNTYxMDBlZDU3M2Q2MDAwODAzZTNkNjAwMGZkNWI1MDUwNTY1YjgxNzNmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmMTY2MTA4ZmM4MjkwODExNTAyOTA2MDQwNTE2MDAwNjA0MDUxODA4MzAzODE4NTg4ODhmMTkzNTA1MDUwNTAxNTgwMTU2MTAxMzc1NzNkNjAwMDgwM2UzZDYwMDBmZDViNTA1MDUwNTZmZWEyNjU2MjdhN2E3MjMxNTgyMDVlYjQwYmI2MjA4MzVmYjc5NjljZGQ3NWRlZDE4ODkyN2Q1YzkyN2Y5M2JhNjc1ZDY1YjljNTIzNDQ0MjQyODc2NDczNmY2YzYzNDMwMDA1MTEwMDMy","b64Record":"CiAIFiocCgwIARAMGgYIgK6ZpA8SDAgBEA8aBgiArpmkDxIwySEeGnJksNt6u8MlGdZB/Bk1mIwIHKXfuIADDyGp56HAQ1GKDk7rlBk/R5ZdhTLXGgwIy/7+rAYQ58TFlgEiDwoJCI/+/qwGEKIREgIYAiogw4PCrsOCwrfDg8K5dEY4w4LCrkrDg8KLw4PCkMODwo5SAA=="},{"b64Body":"ChAKCQiP/v6sBhCkERIDGPUIEgIYAxiW+66dAiICCHgyIMODwq7DgsK3w4PCuXRGOMOCwq5Kw4PCi8ODwpDDg8KOQkgKAxj6CBoiEiDGMTbjqjIxEdDd6nBEY25HLUDZP5iPMmGHp+2UlXohBSCQoQ8okE5CBQiAztoDUgBaAGoLY2VsbGFyIGRvb3I=","b64Record":"CiUIFiIDGPsIKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjBZyU+28MAzECPwVjwe1MNHTFpDmWYcGxYrhWtY8iSQ/5FyHA/ls5upV0xMnOpV0H0aDAjL/v6sBhD5kpv7AiIQCgkIj/7+rAYQpBESAxj1CCogw4PCrsOCwrfDg8K5dEY4w4LCrkrDg8KLw4PCkMODwo4w1tSRpAJCpgUKAxj7CBLxAmCAYEBSYAQ2EGEAKVdgADVg4ByAY43ZgEMUYQArV4BjrO9gNxRhAFlXWwBbYQBXYASANgNgIIEQFWEAQVdgAID9W4EBkICANZBgIAGQkpGQUFBQYQCnVlsAW2EApWAEgDYDYECBEBVhAG9XYACA/VuBAZCAgDVz//////////////////////////8WkGAgAZCSkZCANZBgIAGQkpGQUFBQYQDxVlsAWzNz//////////////////////////8WYQj8gpCBFQKQYEBRYABgQFGAgwOBhYiI8ZNQUFBQFYAVYQDtVz1gAIA+PWAA/VtQUFZbgXP//////////////////////////xZhCPyCkIEVApBgQFFgAGBAUYCDA4GFiIjxk1BQUFAVgBVhATdXPWAAgD49YAD9W1BQUFb+omVienpyMVggXrQLtiCDX7eWnN113tGIkn1ckn+TumddZbnFI0RCQodkc29sY0MABREAMiKAAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAowJoMOgMY+whKFgoUAAAAAAAAAAAAAAAAAAAAAAAABHtyBwoDGPsIEAFSRwoJCgIYAxCY4okUCgoKAhhiEODciMYDCgoKAxigBhCatYg3CgoKAxihBhCatYg3CgsKAxj1CBDLxaTIBAoJCgMY+wgQoJwB"},{"b64Body":"ChAKCQiQ/v6sBhCmERIDGPUIEgIYAxiW+66dAiICCHgyIMODwq7DgsK3w4PCuXRGOMOCwq5Kw4PCi8ODwpDDg8KOQkgKAxj6CBoiEiDBqQHCzU01HFFfiQRJ2IUJpzb2V6F2G7I+Ue4cYBP8lyCQoQ8okE5CBQiAztoDUgBaAGoLY2VsbGFyIGRvb3I=","b64Record":"CiUIFiIDGPwIKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjBBzWHC8Q8PoKidi3PGcq4ryqqyfyGhrHKwNr63OJjno9MiFZ8IT3NIk1XugQzod9EaDAjM/v6sBhCMq4ugASIQCgkIkP7+rAYQphESAxj1CCogw4PCrsOCwrfDg8K5dEY4w4LCrkrDg8KLw4PCkMODwo4w1tSRpAJCpgUKAxj8CBLxAmCAYEBSYAQ2EGEAKVdgADVg4ByAY43ZgEMUYQArV4BjrO9gNxRhAFlXWwBbYQBXYASANgNgIIEQFWEAQVdgAID9W4EBkICANZBgIAGQkpGQUFBQYQCnVlsAW2EApWAEgDYDYECBEBVhAG9XYACA/VuBAZCAgDVz//////////////////////////8WkGAgAZCSkZCANZBgIAGQkpGQUFBQYQDxVlsAWzNz//////////////////////////8WYQj8gpCBFQKQYEBRYABgQFGAgwOBhYiI8ZNQUFBQFYAVYQDtVz1gAIA+PWAA/VtQUFZbgXP//////////////////////////xZhCPyCkIEVApBgQFFgAGBAUYCDA4GFiIjxk1BQUFAVgBVhATdXPWAAgD49YAD9W1BQUFb+omVienpyMVggXrQLtiCDX7eWnN113tGIkn1ckn+TumddZbnFI0RCQodkc29sY0MABREAMiKAAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAowJoMOgMY/AhKFgoUAAAAAAAAAAAAAAAAAAAAAAAABHxyBwoDGPwIEAFSRwoJCgIYAxCY4okUCgoKAhhiEODciMYDCgoKAxigBhCatYg3CgoKAxihBhCatYg3CgsKAxj1CBDLxaTIBAoJCgMY/AgQoJwB"},{"b64Body":"ChAKCQiQ/v6sBhC6ERIDGPUIEgIYAxjVgL+gAiICCHgyIMODwq7DgsK3w4PCuXRGOMOCwq5Kw4PCi8ODwpDDg8KOQooBCgMY+QgaIhIgeHFRVBqmamnL5gk2MN7alrarbaXhUmM7F4hMKCrvvAwgkKEPKJBOQgUIgM7aA0pAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABHsAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEfFIAWgBqC2NlbGxhciBkb29y","b64Record":"CiUIFiIDGP0IKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjAxws7AHnCIytDnAdg2j83Wgj/Ksl6gyrwVmazUm95uotk78HBHbwbOPFQAk1OWINEaDAjM/v6sBhChzauhAyIQCgkIkP7+rAYQuhESAxj1CCogw4PCrsOCwrfDg8K5dEY4w4LCrkrDg8KLw4PCkMODwo4wldqhpwJCzR0KAxj9CBKYG2CAYEBSYAQ2EGEAP1dgADVg4ByAY1VV1+QUYQBBV4BjXs8pihRhAM9XgGOdx9sQFGEA/VeAY6mjpjUUYQFLV1sAW2EAzWAEgDYDYICBEBVhAFdXYACA/VuBAZCAgDVz//////////////////////////8WkGAgAZCSkZCANXP//////////////////////////xaQYCABkJKRkIA1c///////////////////////////FpBgIAGQkpGQgDWQYCABkJKRkFBQUGEBeVZbAFthAPtgBIA2A2AggRAVYQDlV2AAgP1bgQGQgIA1kGAgAZCSkZBQUFBhBxNWWwBbYQFJYASANgNgQIEQFWEBE1dgAID9W4EBkICANXP//////////////////////////xaQYCABkJKRkIA1kGAgAZCSkZBQUFBhCC9WWwBbYQF3YASANgNgIIEQFWEBYVdgAID9W4EBkICANZBgIAGQkpGQUFBQYQoPVlsAW4Nz//////////////////////////8WYQj8gpCBFQKQYEBRYABgQFGAgwOBhYiI8ZNQUFBQFYAVYQG/Vz1gAIA+PWAA/VtQYACAkFSQYQEACpAEc///////////////////////////FnP//////////////////////////xZjrO9gN4VgAoSBYQIIV/5bBGBAUYNj/////xZg4BuBUmAEAYCDc///////////////////////////FnP//////////////////////////xaBUmAgAYKBUmAgAZJQUFBgAGBAUYCDA4FgAIeAOxWAFWECcldgAID9W1Ba8RWAFWEChlc9YACAPj1gAP1bUFBQUGABYACQVJBhAQAKkARz//////////////////////////8Wc///////////////////////////FmOs72A3hWAChIFhAtNX/lsEYEBRg2P/////FmDgG4FSYAQBgINz//////////////////////////8Wc///////////////////////////FoFSYCABgoFSYCABklBQUGAAYEBRgIMDgWAAh4A7FYAVYQM9V2AAgP1bUFrxFYAVYQNRVz1gAIA+PWAA/VtQUFBQgnP//////////////////////////xZhCPyCkIEVApBgQFFgAGBAUYCDA4GFiIjxk1BQUFAVgBVhA5tXPWAAgD49YAD9W1BgAICQVJBhAQAKkARz//////////////////////////8Wc///////////////////////////FmOs72A3hGAChIFhA+RX/lsEYEBRg2P/////FmDgG4FSYAQBgINz//////////////////////////8Wc///////////////////////////FoFSYCABgoFSYCABklBQUGAAYEBRgIMDgWAAh4A7FYAVYQROV2AAgP1bUFrxFYAVYQRiVz1gAIA+PWAA/VtQUFBQYAFgAJBUkGEBAAqQBHP//////////////////////////xZz//////////////////////////8WY6zvYDeEYAKEgWEEr1f+WwRgQFGDY/////8WYOAbgVJgBAGAg3P//////////////////////////xZz//////////////////////////8WgVJgIAGCgVJgIAGSUFBQYABgQFGAgwOBYACHgDsVgBVhBRlXYACA/VtQWvEVgBVhBS1XPWAAgD49YAD9W1BQUFCBc///////////////////////////FmEI/IKQgRUCkGBAUWAAYEBRgIMDgYWIiPGTUFBQUBWAFWEFd1c9YACAPj1gAP1bUGAAgJBUkGEBAAqQBHP//////////////////////////xZz//////////////////////////8WY6zvYDeDYAKEgWEFwFf+WwRgQFGDY/////8WYOAbgVJgBAGAg3P//////////////////////////xZz//////////////////////////8WgVJgIAGCgVJgIAGSUFBQYABgQFGAgwOBYACHgDsVgBVhBipXYACA/VtQWvEVgBVhBj5XPWAAgD49YAD9W1BQUFBgAWAAkFSQYQEACpAEc///////////////////////////FnP//////////////////////////xZjrO9gN4NgAoSBYQaLV/5bBGBAUYNj/////xZg4BuBUmAEAYCDc///////////////////////////FnP//////////////////////////xaBUmAgAYKBUmAgAZJQUFBgAGBAUYCDA4FgAIeAOxWAFWEG9VdgAID9W1Ba8RWAFWEHCVc9YACAPj1gAP1bUFBQUFBQUFBWW2AAgJBUkGEBAAqQBHP//////////////////////////xZz//////////////////////////8WY43ZgEOCYEBRgmP/////FmDgG4FSYAQBgIKBUmAgAZFQUGAAYEBRgIMDgWAAh4A7FYAVYQeHV2AAgP1bUFrxFYAVYQebVz1gAIA+PWAA/VtQUFBQYAFgAJBUkGEBAAqQBHP//////////////////////////xZz//////////////////////////8WY43ZgEOCYEBRgmP/////FmDgG4FSYAQBgIKBUmAgAZFQUGAAYEBRgIMDgWAAh4A7FYAVYQgUV2AAgP1bUFrxFYAVYQgoVz1gAIA+PWAA/VtQUFBQUFZbgXP//////////////////////////xZhCPyCkIEVApBgQFFgAGBAUYCDA4GFiIjxk1BQUFAVgBVhCHVXPWAAgD49YAD9W1BgAICQVJBhAQAKkARz//////////////////////////8Wc///////////////////////////FmOs72A3g2AChIFhCL5X/lsEYEBRg2P/////FmDgG4FSYAQBgINz//////////////////////////8Wc///////////////////////////FoFSYCABgoFSYCABklBQUGAAYEBRgIMDgWAAh4A7FYAVYQkoV2AAgP1bUFrxFYAVYQk8Vz1gAIA+PWAA/VtQUFBQYAFgAJBUkGEBAAqQBHP//////////////////////////xZz//////////////////////////8WY6zvYDeDYAKEgWEJiVf+WwRgQFGDY/////8WYOAbgVJgBAGAg3P//////////////////////////xZz//////////////////////////8WgVJgIAGCgVJgIAGSUFBQYABgQFGAgwOBYACHgDsVgBVhCfNXYACA/VtQWvEVgBVhCgdXPWAAgD49YAD9W1BQUFBQUFZbYABgYGAAgJBUkGEBAAqQBHP//////////////////////////xZz//////////////////////////8Wg2BAUWAkAYCCgVJgIAGRUFBgQFFgIIGDAwOBUpBgQFJ/jdmAQwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB7/////////////////////////////////////xkWYCCCAYBRe/////////////////////////////////////+DgYMWF4NSUFBQUGBAUYCCgFGQYCABkICDg1tgIIMQYQsMV4BRglJgIIIBkVBgIIEBkFBgIIMDklBhCulWW2ABg2AgA2EBAAoDgBmCURaBhFEWgIIXhVJQUFBQUFCQUAGRUFBgAGBAUYCDA4GFWvSRUFA9gGAAgRRhC2xXYEBRkVBgHxlgPz0BFoIBYEBSPYJSPWAAYCCEAT5hC3FWW2BgkVBbUJFQkVBgAGBgYAFgAJBUkGEBAAqQBHP//////////////////////////xZz//////////////////////////8WhWBAUWAkAYCCgVJgIAGRUFBgQFFgIIGDAwOBUpBgQFJ/jdmAQwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB7/////////////////////////////////////xkWYCCCAYBRe/////////////////////////////////////+DgYMWF4NSUFBQUGBAUYCCgFGQYCABkICDg1tgIIMQYQx0V4BRglJgIIIBkVBgIIEBkFBgIIMDklBhDFFWW2ABg2AgA2EBAAoDgBmCURaBhFEWgIIXhVJQUFBQUFCQUAGRUFBgAGBAUYCDA4GFWvSRUFA9gGAAgRRhDNRXYEBRkVBgHxlgPz0BFoIBYEBSPYJSPWAAYCCEAT5hDNlWW2BgkVBbUJFQkVCDFYBhDOlXUIEVWxVhDVxXYEBRfwjDeaAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgVJgBAGAgGAgAYKBA4JSYB6BUmAgAYB/RGVsZWdhdGUgdHJhbnNmZXIgY2FsbCBmYWlsZWQhAACBUlBgIAGRUFBgQFGAkQOQ/VtQUFBQUFb+omVienpyMVggYZ1VUlzRf3ObDLJthvfRjxVerEX5U3DZZN6jxw2xTW5kc29sY0MABREAMiKAAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAowJoMOgMY/QhKFgoUAAAAAAAAAAAAAAAAAAAAAAAABH1yBwoDGP0IEAFSRwoJCgIYAxCu6rgUCgoKAhhiEJiS48oDCgoKAxigBhDy29M3CgoKAxihBhDy29M3CgsKAxj1CBDJ0MTOBAoJCgMY/QgQoJwB"},{"b64Body":"ChAKCQiR/v6sBhC8ERIDGPUIEgIYAxjAv+0hIgIIeDIgw4PCrsOCwrfDg8K5dEY4w4LCrkrDg8KLw4PCkMODwo46kAEKAxj9CBDAhD0ihAFVVdfkAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABHYAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEdwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAR4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACg=","b64Record":"CiUIFiIDGP0IKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjB6acNAyOPgVqLCoxKSXNaee688kZ8lIyRPmVZ36JDAPBulpyr+Tx7IABslbvAsm0UaDAjN/v6sBhDjq66pASIQCgkIkf7+rAYQvBESAxj1CCogw4PCrsOCwrfDg8K5dEY4w4LCrkrDg8KLw4PCkMODwo4wgOaKGzqMAgoDGP0IIoACAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACiA6jBSUQoJCgIYYhCAzJU2CgoKAxj1CBD/y5U2CggKAxj2CBCgAQoICgMY9wgQoAEKCAoDGPgIEKABCgcKAxj7CBB3CgcKAxj8CBB3CggKAxj9CBDvAQ=="}]},"transferNegativeAmountOfHbarsFails":{"placeholderNum":1150,"encodedItems":[{"b64Body":"Cg8KCQiW/v6sBhDYERICGAISAhgDGPuV9hQiAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjloxCiISIMnM8RhcMCKONH1X2j+gspRb7CQjvrbrCceOs3NcPR0FEIDIr6AlSgUIgM7aAw==","b64Record":"CiUIFhIDGP8IKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjDgVcAUPeyhmElI05ZaVHJrosSoqH6nk80mCxlpHA9eDUAR5ZM+ER6MLfgLzOfnrJgaCwjS/v6sBhD2qs8tIg8KCQiW/v6sBhDYERICGAIqIMODwq7DgsK3w4PCuXRGOMOCwq5Kw4PCi8ODwpDDg8KOUhkKCgoCGAIQ/4/fwEoKCwoDGP8IEICQ38BK"},{"b64Body":"Cg8KCQiW/v6sBhDaERICGAISAhgDGPuV9hQiAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjlouCiISILU+LBr1NmT5H3ROYK4p+F5euAFkdRV6+vNjv2CIRyWXEJBOSgUIgM7aAw==","b64Record":"CiUIFhIDGIAJKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjC1pano88o/ALlWHArZAQD/Nm/mhyN+nUEvafR1HVYDaW3Yf3zqvoJCh9Fpt9qoF5kaDAjS/v6sBhD/5bqSAiIPCgkIlv7+rAYQ2hESAhgCKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjlIVCggKAhgCEJ+cAQoJCgMYgAkQoJwB"},{"b64Body":"Cg8KCQiX/v6sBhDcERICGAISAhgDGP7LzSwiAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjooBjgESCwjTzNmwBhCU9OMYGm0KIhIgR52RKApJucoI+mdFENhYKUw8CgTF5gTmP2Zo/WWoG6sKIzohA3the98W6l34jAueuPxMaPg6H7Qlx55PyYi7zSq+q8mGCiISILAHZemSKqowStGFY3UGlgxg3nxjojwApQ0G9S70AFS3IgxIZWxsbyBXb3JsZCEqADIA","b64Record":"CiUIFhoDGIEJKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjBbXwmYc49/L+OZoUBMAWPr+SuTD92440+hs4bEpewsRZ77W3O+rXSaTfE2hMhzeNIaCwjT/v6sBhDUk8kaIg8KCQiX/v6sBhDcERICGAIqIMODwq7DgsK3w4PCuXRGOMOCwq5Kw4PCi8ODwpDDg8KOUgA="},{"b64Body":"Cg8KCQiX/v6sBhDgERICGAISAhgDGPOGlzoiAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjpoBghkKAxiBCSL6GDYwODA2MDQwNTI2MTA2MmE4MDYxMDAxMzYwMDAzOTYwMDBmM2ZlNjA4MDYwNDA1MjYwMDQzNjEwNjEwMDRhNTc2MDAwMzU2MGUwMWM4MDYzMjZhYjk3ZWExNDYxMDA0YzU3ODA2MzhkZDk4MDQzMTQ2MTAwOWE1NzgwNjNhY2VmNjAzNzE0NjEwMGM4NTc4MDYzZDJhMGZlMjYxNDYxMDExNjU3ODA2M2UxMmUzMjM4MTQ2MTAxYTQ1NzViMDA1YjYxMDA5ODYwMDQ4MDM2MDM2MDQwODExMDE1NjEwMDYyNTc2MDAwODBmZDViODEwMTkwODA4MDM1NzNmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmMTY5MDYwMjAwMTkwOTI5MTkwODAzNTkwNjAyMDAxOTA5MjkxOTA1MDUwNTA2MTAxZjI1NjViMDA1YjYxMDBjNjYwMDQ4MDM2MDM2MDIwODExMDE1NjEwMGIwNTc2MDAwODBmZDViODEwMTkwODA4MDM1OTA2MDIwMDE5MDkyOTE5MDUwNTA1MDYxMDQyMzU2NWIwMDViNjEwMTE0NjAwNDgwMzYwMzYwNDA4MTEwMTU2MTAwZGU1NzYwMDA4MGZkNWI4MTAxOTA4MDgwMzU3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNjkwNjAyMDAxOTA5MjkxOTA4MDM1OTA2MDIwMDE5MDkyOTE5MDUwNTA1MDYxMDQ2ZDU2NWIwMDViNjEwMWEyNjAwNDgwMzYwMzYwODA4MTEwMTU2MTAxMmM1NzYwMDA4MGZkNWI4MTAxOTA4MDgwMzU3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNjkwNjAyMDAxOTA5MjkxOTA4MDM1NzNmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmMTY5MDYwMjAwMTkwOTI5MTkwODAzNTczZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjE2OTA2MDIwMDE5MDkyOTE5MDgwMzU5MDYwMjAwMTkwOTI5MTkwNTA1MDUwNjEwNGI4NTY1YjAwNWI2MTAxZjA2MDA0ODAzNjAzNjA0MDgxMTAxNTYxMDFiYTU3NjAwMDgwZmQ1YjgxMDE5MDgwODAzNTczZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjE2OTA2MDIwMDE5MDkyOTE5MDgwMzU5MDYwMjAwMTkwOTI5MTkwNTA1MDUwNjEwNWE3NTY1YjAwNWI4MTczZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjE2NjEwOGZjODI5MDgxMTUwMjkwNjA0MDUxNjAwMDYwNDA1MTgwODMwMzgxODU4ODg4ZjE5MzUwNTA1MDUwMTU4MDE1NjEwMjM4NTczZDYwMDA4MDNlM2Q2MDAwZmQ1YjUwODE3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNjYxMDhmYzYwMDI4MzgxNjEwMjVkNTdmZTViMDQ5MDgxMTUwMjkwNjA0MDUxNjAwMDYwNDA1MTgwODMwMzgxODU4ODg4ZjE5MzUwNTA1MDUwMTU4MDE1NjEwMjg5NTczZDYwMDA4MDNlM2Q2MDAwZmQ1YjUwODE3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNjYxMDhmYzYwMDQ4MzgxNjEwMmFlNTdmZTViMDQ5MDgxMTUwMjkwNjA0MDUxNjAwMDYwNDA1MTgwODMwMzgxODU4ODg4ZjE5MzUwNTA1MDUwMTU4MDE1NjEwMmRhNTczZDYwMDA4MDNlM2Q2MDAwZmQ1YjUwODE3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNjYxMDhmYzYwMDg4MzgxNjEwMmZmNTdmZTViMDQ5MDgxMTUwMjkwNjA0MDUxNjAwMDYwNDA1MTgwODMwMzgxODU4ODg4ZjE5MzUwNTA1MDUwMTU4MDE1NjEwMzJiNTczZDYwMDA4MDNlM2Q2MDAwZmQ1YjUwODE3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNjYxMDhmYzYwMTA4MzgxNjEwMzUwNTdmZTViMDQ5MDgxMTUwMjkwNjA0MDUxNjAwMDYwNDA1MTgwODMwMzgxODU4ODg4ZjE5MzUwNTA1MDUwMTU4MDE1NjEwMzdjNTczZDYwMDA4MDNlM2Q2MDAwZmQ1YjUwODE3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNjYxMDhmYzYwMjA4MzgxNjEwM2ExNTdmZTViMDQ5MDgxMTUwMjkwNjA0MDUxNjAwMDYwNDA1MTgwODMwMzgxODU4ODg4ZjE5MzUwNTA1MDUwMTU4MDE1NjEwM2NkNTczZDYwMDA4MDNlM2Q2MDAwZmQ1YjUwODE3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNjYxMDhmYzYwNDA4MzgxNjEwM2YyNTdmZTViMDQ5MDgxMTUwMjkwNjA0MDUxNjAwMDYwNDA1MTgwODMwMzgxODU4ODg4ZjE5MzUwNTA1MDUwMTU4MDE1NjEwNDFlNTczZDYwMDA4MDNlM2Q2MDAwZmQ1YjUwNTA1MDU2NWIzMzczZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjE2NjEwOGZjODI5MDgxMTUwMjkwNjA0MDUxNjAwMDYwNDA1MTgwODMwMzgxODU4ODg4ZjE5MzUwNTA1MDUwMTU4MDE1NjEwNDY5NTczZDYwMDA4MDNlM2Q2MDAwZmQ1YjUwNTA1NjViODE3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNjYxMDhmYzgyOTA4MTE1MDI5MDYwNDA1MTYwMDA2MDQwNTE4MDgzMDM4MTg1ODg4OGYxOTM1MDUwNTA1MDE1ODAxNTYxMDRiMzU3M2Q2MDAwODAzZTNkNjAwMGZkNWI1MDUwNTA1NjViODM3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNjYxMDhmYzgyOTA4MTE1MDI5MDYwNDA1MTYwMDA2MDQwNTE4MDgzMDM4MTg1ODg4OGYxOTM1MDUwNTA1MDE1ODAxNTYxMDRmZTU3M2Q2MDAwODAzZTNkNjAwMGZkNWI1MDgyNzNmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmMTY2MTA4ZmM2MDAyODM4MTYxMDUyMzU3ZmU1YjA0OTA4MTE1MDI5MDYwNDA1MTYwMDA2MDQwNTE4MDgzMDM4MTg1ODg4OGYxOTM1MDUwNTA1MDE1ODAxNTYxMDU0ZjU3M2Q2MDAwODAzZTNkNjAwMGZkNWI1MDgxNzNmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmMTY2MTA4ZmM2MDA0ODM4MTYxMDU3NDU3ZmU1YjA0OTA4MTE1MDI5MDYwNDA1MTYwMDA2MDQwNTE4MDgzMDM4MTg1ODg4OGYxOTM1MDUwNTA1MDE1ODAxNTYxMDVhMDU3M2Q2MDAwODAzZTNkNjAwMGZkNWI1MDUwNTA1MDUwNTY1YjgxNzNmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmMTY2MTA4ZmM4MjYwMDAwMzkwODExNTAyOTA2MDQwNTE2MDAwNjA0MDUxODA4MzAzODE4NTg4ODhmMTkzNTA1MDUwNTAxNTgwMTU2MTA1ZjA1NzNkNjAwMDgwM2UzZDYwMDBmZDViNTA1MDUwNTZmZWEyNjU2MjdhN2E3MjMxNTgyMGNmNzkxYmJmNDdkY2I2MTM5Mzc0NDA1MmE2MjBmNzcxYjQyOWYwNzFmMDJlMzY1N2NlM2U1ZjgwYWIzZjk3YmQ2NDczNmY2YzYzNDMwMDA1MTEwMDMy","b64Record":"CiAIFiocCgwIARAMGgYIgK6ZpA8SDAgBEA8aBgiArpmkDxIwv1CNFzTBe9jzBTGe9MHFvy3KvvTZzd1KoCETGjzNjzNIhf8lPcvok/yXhpU+uyzjGgwI0/7+rAYQs9+bnAIiDwoJCJf+/qwGEOAREgIYAiogw4PCrsOCwrfDg8K5dEY4w4LCrkrDg8KLw4PCkMODwo5SAA=="},{"b64Body":"ChAKCQiY/v6sBhDiERIDGP8IEgIYAxiW+66dAiICCHgyIMODwq7DgsK3w4PCuXRGOMOCwq5Kw4PCi8ODwpDDg8KOQkgKAxiBCRoiEiCuXwuBvAJvtQIwMTG8GpUnHgtcEvwSfI20uQ9ucYQM3iCQoQ8okE5CBQiAztoDUgBaAGoLY2VsbGFyIGRvb3I=","b64Record":"CiUIFiIDGIIJKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjAAjh+fPpJVGsyeXIqRsClgnO0x9aUh+nJef+L2rw3cAiDDJKnBwitfxjt6s7yNObkaCwjU/v6sBhDg/Y0kIhAKCQiY/v6sBhDiERIDGP8IKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjjDW1JGkAkLfDgoDGIIJEqoMYIBgQFJgBDYQYQBKV2AANWDgHIBjJquX6hRhAExXgGON2YBDFGEAmleAY6zvYDcUYQDIV4Bj0qD+JhRhARZXgGPhLjI4FGEBpFdbAFthAJhgBIA2A2BAgRAVYQBiV2AAgP1bgQGQgIA1c///////////////////////////FpBgIAGQkpGQgDWQYCABkJKRkFBQUGEB8lZbAFthAMZgBIA2A2AggRAVYQCwV2AAgP1bgQGQgIA1kGAgAZCSkZBQUFBhBCNWWwBbYQEUYASANgNgQIEQFWEA3ldgAID9W4EBkICANXP//////////////////////////xaQYCABkJKRkIA1kGAgAZCSkZBQUFBhBG1WWwBbYQGiYASANgNggIEQFWEBLFdgAID9W4EBkICANXP//////////////////////////xaQYCABkJKRkIA1c///////////////////////////FpBgIAGQkpGQgDVz//////////////////////////8WkGAgAZCSkZCANZBgIAGQkpGQUFBQYQS4VlsAW2EB8GAEgDYDYECBEBVhAbpXYACA/VuBAZCAgDVz//////////////////////////8WkGAgAZCSkZCANZBgIAGQkpGQUFBQYQWnVlsAW4Fz//////////////////////////8WYQj8gpCBFQKQYEBRYABgQFGAgwOBhYiI8ZNQUFBQFYAVYQI4Vz1gAIA+PWAA/VtQgXP//////////////////////////xZhCPxgAoOBYQJdV/5bBJCBFQKQYEBRYABgQFGAgwOBhYiI8ZNQUFBQFYAVYQKJVz1gAIA+PWAA/VtQgXP//////////////////////////xZhCPxgBIOBYQKuV/5bBJCBFQKQYEBRYABgQFGAgwOBhYiI8ZNQUFBQFYAVYQLaVz1gAIA+PWAA/VtQgXP//////////////////////////xZhCPxgCIOBYQL/V/5bBJCBFQKQYEBRYABgQFGAgwOBhYiI8ZNQUFBQFYAVYQMrVz1gAIA+PWAA/VtQgXP//////////////////////////xZhCPxgEIOBYQNQV/5bBJCBFQKQYEBRYABgQFGAgwOBhYiI8ZNQUFBQFYAVYQN8Vz1gAIA+PWAA/VtQgXP//////////////////////////xZhCPxgIIOBYQOhV/5bBJCBFQKQYEBRYABgQFGAgwOBhYiI8ZNQUFBQFYAVYQPNVz1gAIA+PWAA/VtQgXP//////////////////////////xZhCPxgQIOBYQPyV/5bBJCBFQKQYEBRYABgQFGAgwOBhYiI8ZNQUFBQFYAVYQQeVz1gAIA+PWAA/VtQUFBWWzNz//////////////////////////8WYQj8gpCBFQKQYEBRYABgQFGAgwOBhYiI8ZNQUFBQFYAVYQRpVz1gAIA+PWAA/VtQUFZbgXP//////////////////////////xZhCPyCkIEVApBgQFFgAGBAUYCDA4GFiIjxk1BQUFAVgBVhBLNXPWAAgD49YAD9W1BQUFZbg3P//////////////////////////xZhCPyCkIEVApBgQFFgAGBAUYCDA4GFiIjxk1BQUFAVgBVhBP5XPWAAgD49YAD9W1CCc///////////////////////////FmEI/GACg4FhBSNX/lsEkIEVApBgQFFgAGBAUYCDA4GFiIjxk1BQUFAVgBVhBU9XPWAAgD49YAD9W1CBc///////////////////////////FmEI/GAEg4FhBXRX/lsEkIEVApBgQFFgAGBAUYCDA4GFiIjxk1BQUFAVgBVhBaBXPWAAgD49YAD9W1BQUFBQVluBc///////////////////////////FmEI/IJgAAOQgRUCkGBAUWAAYEBRgIMDgYWIiPGTUFBQUBWAFWEF8Fc9YACAPj1gAP1bUFBQVv6iZWJ6enIxWCDPeRu/R9y2E5N0QFKmIPdxtCnwcfAuNlfOPl+Aqz+XvWRzb2xjQwAFEQAyIoACAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACjAmgw6AxiCCUoWChQAAAAAAAAAAAAAAAAAAAAAAAAEgnIHCgMYggkQAVJHCgkKAhgDEJjiiRQKCgoCGGIQ4NyIxgMKCgoDGKAGEJq1iDcKCgoDGKEGEJq1iDcKCwoDGP8IEMvFpMgECgkKAxiCCRCgnAE="},{"b64Body":"ChAKCQiY/v6sBhDqERIDGP8IEgIYAxjgrLEDIgIIeDIgw4PCrsOCwrfDg8K5dEY4w4LCrkrDg8KLw4PCkMODwo46TwoDGIIJEKCNBiJE4S4yOAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAASAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAo=","b64Record":"CiUIISIDGIIJKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjCfL9NY+1JHXY8fHzXVm98Tt4SAn8UCYC3lG/mrSK6mElTVvpdXRlEWCPzE85Hwu50aDAjU/v6sBhD8w+ClAiIQCgkImP7+rAYQ6hESAxj/CCogw4PCrsOCwrfDg8K5dEY4w4LCrkrDg8KLw4PCkMODwo4wgNfaAjoIGgIweCiA8QRSFwoJCgIYYhCArrUFCgoKAxj/CBD/rbUF"},{"b64Body":"ChAKCQiZ/v6sBhDsERIDGP8IEgIYAxjgrLEDIgIIeDIgw4PCrsOCwrfDg8K5dEY4w4LCrkrDg8KLw4PCkMODwo46TwoDGIIJEKCNBiJE4S4yOAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAASAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=","b64Record":"CiUIFiIDGIIJKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjAKFTc5indBM14Ui71VXOgOyczhqWqtEejaWidna/f876JoeRwt8NmmGhxjCpuRNYIaCwjV/v6sBhDV2eYtIhAKCQiZ/v6sBhDsERIDGP8IKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjjCA19oCOowCCgMYggkigAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKIDxBFIXCgkKAhhiEICutQUKCgoDGP8IEP+ttQU="}]},"transferZeroHbars":{"placeholderNum":1155,"encodedItems":[{"b64Body":"Cg8KCQid/v6sBhCCEhICGAISAhgDGPuV9hQiAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjloxCiISIO5DHNHpXKDtJG4rYW54d8YNx3yeD/HAPAuKRLGExks/EIDIr6AlSgUIgM7aAw==","b64Record":"CiUIFhIDGIQJKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjCpXKnLst5XhhxHQV9PmO8rcE6OZnrN/Gz8/RMPv8KQZ2Pw+z9juxTMEN5N6FyFHOIaDAjZ/v6sBhDul5GrAyIPCgkInf7+rAYQghISAhgCKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjlIZCgoKAhgCEP+P38BKCgsKAxiECRCAkN/ASg=="},{"b64Body":"Cg8KCQie/v6sBhCEEhICGAISAhgDGPuV9hQiAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjlouCiISINEEQBzknqI0EpUniiCdNgmutNRwlhyAoALjKtUHtbp/EJBOSgUIgM7aAw==","b64Record":"CiUIFhIDGIUJKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjC57zTVla8GKsQP7Qh7cI7cMxTeVNCMuD6bnN4iSGV4snClIK1aRYMeAAxcdinjsBQaDAja/v6sBhCq/42zASIPCgkInv7+rAYQhBISAhgCKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjlIVCggKAhgCEJ+cAQoJCgMYhQkQoJwB"},{"b64Body":"Cg8KCQie/v6sBhCGEhICGAISAhgDGLXNziwiAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjooBjwESDAjazNmwBhDS/uqeAxptCiISIBvKRyjN7X4FPcc+RiLojXEbwHBWGjbLfFjXALVf0gQBCiM6IQOGSEr/hwDNpchUGuDE4n0QxZ+qGx1+FaVRQfpTECmeogoiEiADNjGqeryB1c3mcFiy+QlVAq8uTdR/mNddf2CrKl6VNyIMSGVsbG8gV29ybGQhKgAyAA==","b64Record":"CiUIFhoDGIYJKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjDKgVZzNWzWm8Ik83Pd5h8I87b/BitYKIcsL72xHdJ6GFSczYSyGcBOUqAxPHKlFUwaDAja/v6sBhCezte0AyIPCgkInv7+rAYQhhISAhgCKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjlIA"},{"b64Body":"Cg8KCQif/v6sBhCKEhICGAISAhgDGMSImDoiAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjpoBghkKAxiGCSL6GDYwODA2MDQwNTI2MTA2MmE4MDYxMDAxMzYwMDAzOTYwMDBmM2ZlNjA4MDYwNDA1MjYwMDQzNjEwNjEwMDRhNTc2MDAwMzU2MGUwMWM4MDYzMjZhYjk3ZWExNDYxMDA0YzU3ODA2MzhkZDk4MDQzMTQ2MTAwOWE1NzgwNjNhY2VmNjAzNzE0NjEwMGM4NTc4MDYzZDJhMGZlMjYxNDYxMDExNjU3ODA2M2UxMmUzMjM4MTQ2MTAxYTQ1NzViMDA1YjYxMDA5ODYwMDQ4MDM2MDM2MDQwODExMDE1NjEwMDYyNTc2MDAwODBmZDViODEwMTkwODA4MDM1NzNmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmMTY5MDYwMjAwMTkwOTI5MTkwODAzNTkwNjAyMDAxOTA5MjkxOTA1MDUwNTA2MTAxZjI1NjViMDA1YjYxMDBjNjYwMDQ4MDM2MDM2MDIwODExMDE1NjEwMGIwNTc2MDAwODBmZDViODEwMTkwODA4MDM1OTA2MDIwMDE5MDkyOTE5MDUwNTA1MDYxMDQyMzU2NWIwMDViNjEwMTE0NjAwNDgwMzYwMzYwNDA4MTEwMTU2MTAwZGU1NzYwMDA4MGZkNWI4MTAxOTA4MDgwMzU3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNjkwNjAyMDAxOTA5MjkxOTA4MDM1OTA2MDIwMDE5MDkyOTE5MDUwNTA1MDYxMDQ2ZDU2NWIwMDViNjEwMWEyNjAwNDgwMzYwMzYwODA4MTEwMTU2MTAxMmM1NzYwMDA4MGZkNWI4MTAxOTA4MDgwMzU3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNjkwNjAyMDAxOTA5MjkxOTA4MDM1NzNmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmMTY5MDYwMjAwMTkwOTI5MTkwODAzNTczZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjE2OTA2MDIwMDE5MDkyOTE5MDgwMzU5MDYwMjAwMTkwOTI5MTkwNTA1MDUwNjEwNGI4NTY1YjAwNWI2MTAxZjA2MDA0ODAzNjAzNjA0MDgxMTAxNTYxMDFiYTU3NjAwMDgwZmQ1YjgxMDE5MDgwODAzNTczZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjE2OTA2MDIwMDE5MDkyOTE5MDgwMzU5MDYwMjAwMTkwOTI5MTkwNTA1MDUwNjEwNWE3NTY1YjAwNWI4MTczZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjE2NjEwOGZjODI5MDgxMTUwMjkwNjA0MDUxNjAwMDYwNDA1MTgwODMwMzgxODU4ODg4ZjE5MzUwNTA1MDUwMTU4MDE1NjEwMjM4NTczZDYwMDA4MDNlM2Q2MDAwZmQ1YjUwODE3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNjYxMDhmYzYwMDI4MzgxNjEwMjVkNTdmZTViMDQ5MDgxMTUwMjkwNjA0MDUxNjAwMDYwNDA1MTgwODMwMzgxODU4ODg4ZjE5MzUwNTA1MDUwMTU4MDE1NjEwMjg5NTczZDYwMDA4MDNlM2Q2MDAwZmQ1YjUwODE3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNjYxMDhmYzYwMDQ4MzgxNjEwMmFlNTdmZTViMDQ5MDgxMTUwMjkwNjA0MDUxNjAwMDYwNDA1MTgwODMwMzgxODU4ODg4ZjE5MzUwNTA1MDUwMTU4MDE1NjEwMmRhNTczZDYwMDA4MDNlM2Q2MDAwZmQ1YjUwODE3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNjYxMDhmYzYwMDg4MzgxNjEwMmZmNTdmZTViMDQ5MDgxMTUwMjkwNjA0MDUxNjAwMDYwNDA1MTgwODMwMzgxODU4ODg4ZjE5MzUwNTA1MDUwMTU4MDE1NjEwMzJiNTczZDYwMDA4MDNlM2Q2MDAwZmQ1YjUwODE3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNjYxMDhmYzYwMTA4MzgxNjEwMzUwNTdmZTViMDQ5MDgxMTUwMjkwNjA0MDUxNjAwMDYwNDA1MTgwODMwMzgxODU4ODg4ZjE5MzUwNTA1MDUwMTU4MDE1NjEwMzdjNTczZDYwMDA4MDNlM2Q2MDAwZmQ1YjUwODE3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNjYxMDhmYzYwMjA4MzgxNjEwM2ExNTdmZTViMDQ5MDgxMTUwMjkwNjA0MDUxNjAwMDYwNDA1MTgwODMwMzgxODU4ODg4ZjE5MzUwNTA1MDUwMTU4MDE1NjEwM2NkNTczZDYwMDA4MDNlM2Q2MDAwZmQ1YjUwODE3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNjYxMDhmYzYwNDA4MzgxNjEwM2YyNTdmZTViMDQ5MDgxMTUwMjkwNjA0MDUxNjAwMDYwNDA1MTgwODMwMzgxODU4ODg4ZjE5MzUwNTA1MDUwMTU4MDE1NjEwNDFlNTczZDYwMDA4MDNlM2Q2MDAwZmQ1YjUwNTA1MDU2NWIzMzczZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjE2NjEwOGZjODI5MDgxMTUwMjkwNjA0MDUxNjAwMDYwNDA1MTgwODMwMzgxODU4ODg4ZjE5MzUwNTA1MDUwMTU4MDE1NjEwNDY5NTczZDYwMDA4MDNlM2Q2MDAwZmQ1YjUwNTA1NjViODE3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNjYxMDhmYzgyOTA4MTE1MDI5MDYwNDA1MTYwMDA2MDQwNTE4MDgzMDM4MTg1ODg4OGYxOTM1MDUwNTA1MDE1ODAxNTYxMDRiMzU3M2Q2MDAwODAzZTNkNjAwMGZkNWI1MDUwNTA1NjViODM3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNjYxMDhmYzgyOTA4MTE1MDI5MDYwNDA1MTYwMDA2MDQwNTE4MDgzMDM4MTg1ODg4OGYxOTM1MDUwNTA1MDE1ODAxNTYxMDRmZTU3M2Q2MDAwODAzZTNkNjAwMGZkNWI1MDgyNzNmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmMTY2MTA4ZmM2MDAyODM4MTYxMDUyMzU3ZmU1YjA0OTA4MTE1MDI5MDYwNDA1MTYwMDA2MDQwNTE4MDgzMDM4MTg1ODg4OGYxOTM1MDUwNTA1MDE1ODAxNTYxMDU0ZjU3M2Q2MDAwODAzZTNkNjAwMGZkNWI1MDgxNzNmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmMTY2MTA4ZmM2MDA0ODM4MTYxMDU3NDU3ZmU1YjA0OTA4MTE1MDI5MDYwNDA1MTYwMDA2MDQwNTE4MDgzMDM4MTg1ODg4OGYxOTM1MDUwNTA1MDE1ODAxNTYxMDVhMDU3M2Q2MDAwODAzZTNkNjAwMGZkNWI1MDUwNTA1MDUwNTY1YjgxNzNmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmMTY2MTA4ZmM4MjYwMDAwMzkwODExNTAyOTA2MDQwNTE2MDAwNjA0MDUxODA4MzAzODE4NTg4ODhmMTkzNTA1MDUwNTAxNTgwMTU2MTA1ZjA1NzNkNjAwMDgwM2UzZDYwMDBmZDViNTA1MDUwNTZmZWEyNjU2MjdhN2E3MjMxNTgyMGNmNzkxYmJmNDdkY2I2MTM5Mzc0NDA1MmE2MjBmNzcxYjQyOWYwNzFmMDJlMzY1N2NlM2U1ZjgwYWIzZjk3YmQ2NDczNmY2YzYzNDMwMDA1MTEwMDMy","b64Record":"CiAIFiocCgwIARAMGgYIgK6ZpA8SDAgBEA8aBgiArpmkDxIwEzAQ4Tpcs28xx8pqwEhkUzaOYa5iHBTRe6i5wBP6uo1Ss0rtbvggXRkxermZtPdrGgwI2/7+rAYQ47i3vAEiDwoJCJ/+/qwGEIoSEgIYAiogw4PCrsOCwrfDg8K5dEY4w4LCrkrDg8KLw4PCkMODwo5SAA=="},{"b64Body":"Cg8KCQif/v6sBhCMEhICGAISAhgDGJb7rp0CIgIIeDIgw4PCrsOCwrfDg8K5dEY4w4LCrkrDg8KLw4PCkMODwo5CSAoDGIYJGiISIGbrr9vg6WwVdIB1gMzJC/k9trLH+J5gks74e37Z4/ZOIJChDyiQTkIFCIDO2gNSAFoAagtjZWxsYXIgZG9vcg==","b64Record":"CiUIFiIDGIcJKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjCDAn052vK0JVK8pj0PRAecNjnptn1+N10/qj5rtvrQ4p3peq8yVEQJnqczVSuqBx4aDAjb/v6sBhDk3Im+AyIPCgkIn/7+rAYQjBISAhgCKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjjDA2eIGQt8OCgMYhwkSqgxggGBAUmAENhBhAEpXYAA1YOAcgGMmq5fqFGEATFeAY43ZgEMUYQCaV4BjrO9gNxRhAMhXgGPSoP4mFGEBFleAY+EuMjgUYQGkV1sAW2EAmGAEgDYDYECBEBVhAGJXYACA/VuBAZCAgDVz//////////////////////////8WkGAgAZCSkZCANZBgIAGQkpGQUFBQYQHyVlsAW2EAxmAEgDYDYCCBEBVhALBXYACA/VuBAZCAgDWQYCABkJKRkFBQUGEEI1ZbAFthARRgBIA2A2BAgRAVYQDeV2AAgP1bgQGQgIA1c///////////////////////////FpBgIAGQkpGQgDWQYCABkJKRkFBQUGEEbVZbAFthAaJgBIA2A2CAgRAVYQEsV2AAgP1bgQGQgIA1c///////////////////////////FpBgIAGQkpGQgDVz//////////////////////////8WkGAgAZCSkZCANXP//////////////////////////xaQYCABkJKRkIA1kGAgAZCSkZBQUFBhBLhWWwBbYQHwYASANgNgQIEQFWEBuldgAID9W4EBkICANXP//////////////////////////xaQYCABkJKRkIA1kGAgAZCSkZBQUFBhBadWWwBbgXP//////////////////////////xZhCPyCkIEVApBgQFFgAGBAUYCDA4GFiIjxk1BQUFAVgBVhAjhXPWAAgD49YAD9W1CBc///////////////////////////FmEI/GACg4FhAl1X/lsEkIEVApBgQFFgAGBAUYCDA4GFiIjxk1BQUFAVgBVhAolXPWAAgD49YAD9W1CBc///////////////////////////FmEI/GAEg4FhAq5X/lsEkIEVApBgQFFgAGBAUYCDA4GFiIjxk1BQUFAVgBVhAtpXPWAAgD49YAD9W1CBc///////////////////////////FmEI/GAIg4FhAv9X/lsEkIEVApBgQFFgAGBAUYCDA4GFiIjxk1BQUFAVgBVhAytXPWAAgD49YAD9W1CBc///////////////////////////FmEI/GAQg4FhA1BX/lsEkIEVApBgQFFgAGBAUYCDA4GFiIjxk1BQUFAVgBVhA3xXPWAAgD49YAD9W1CBc///////////////////////////FmEI/GAgg4FhA6FX/lsEkIEVApBgQFFgAGBAUYCDA4GFiIjxk1BQUFAVgBVhA81XPWAAgD49YAD9W1CBc///////////////////////////FmEI/GBAg4FhA/JX/lsEkIEVApBgQFFgAGBAUYCDA4GFiIjxk1BQUFAVgBVhBB5XPWAAgD49YAD9W1BQUFZbM3P//////////////////////////xZhCPyCkIEVApBgQFFgAGBAUYCDA4GFiIjxk1BQUFAVgBVhBGlXPWAAgD49YAD9W1BQVluBc///////////////////////////FmEI/IKQgRUCkGBAUWAAYEBRgIMDgYWIiPGTUFBQUBWAFWEEs1c9YACAPj1gAP1bUFBQVluDc///////////////////////////FmEI/IKQgRUCkGBAUWAAYEBRgIMDgYWIiPGTUFBQUBWAFWEE/lc9YACAPj1gAP1bUIJz//////////////////////////8WYQj8YAKDgWEFI1f+WwSQgRUCkGBAUWAAYEBRgIMDgYWIiPGTUFBQUBWAFWEFT1c9YACAPj1gAP1bUIFz//////////////////////////8WYQj8YASDgWEFdFf+WwSQgRUCkGBAUWAAYEBRgIMDgYWIiPGTUFBQUBWAFWEFoFc9YACAPj1gAP1bUFBQUFBWW4Fz//////////////////////////8WYQj8gmAAA5CBFQKQYEBRYABgQFGAgwOBhYiI8ZNQUFBQFYAVYQXwVz1gAIA+PWAA/VtQUFBW/qJlYnp6cjFYIM95G79H3LYTk3RAUqYg93G0KfBx8C42V84+X4CrP5e9ZHNvbGNDAAURADIigAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKMCaDDoDGIcJShYKFAAAAAAAAAAAAAAAAAAAAAAAAASHcgcKAxiHCRABUiEKCQoCGAIQn8/GDQoJCgIYYhCAs8UNCgkKAxiHCRCgnAE="},{"b64Body":"ChAKCQig/v6sBhCUEhIDGIQJEgIYAxjgrLEDIgIIeDIgw4PCrsOCwrfDg8K5dEY4w4LCrkrDg8KLw4PCkMODwo46TwoDGIcJEKCNBiJErO9gNwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAASFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=","b64Record":"CiUIFiIDGIcJKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjB/RW5TleuQW+qv9FSTbEx4Q14WV6ZPVb91RWcBT+2Yw6BQ7kZyP4OJvSQoDR7uBTIaDAjc/v6sBhDGz4PGASIQCgkIoP7+rAYQlBISAxiECSogw4PCrsOCwrfDg8K5dEY4w4LCrkrDg8KLw4PCkMODwo4wgNfaAjqMAgoDGIcJIoACAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACiA8QRSFwoJCgIYYhCArrUFCgoKAxiECRD/rbUF"}]},"sendHbarsToOuterContractFromDifferentAddresses":{"placeholderNum":1160,"encodedItems":[{"b64Body":"Cg8KCQil/v6sBhCqEhICGAISAhgDGPuV9hQiAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjloxCiISIOjs7ksiOB+IcupkSM/CLceN9kmF8YRaQQ0Djg49LO0mEIDIr6AlSgUIgM7aAw==","b64Record":"CiUIFhIDGIkJKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjBgtR4dcoFko+SVYW7jV6YCOr3M07/lHRXaYRmdG55PD+uh1EwDet0U3bvAm3ZgTCMaCwjh/v6sBhCB2txmIg8KCQil/v6sBhCqEhICGAIqIMODwq7DgsK3w4PCuXRGOMOCwq5Kw4PCi8ODwpDDg8KOUhkKCgoCGAIQ/4/fwEoKCwoDGIkJEICQ38BK"},{"b64Body":"Cg8KCQil/v6sBhCsEhICGAISAhgDGP7LzSwiAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjooBjwESDAjhzNmwBhDKqOjJAhptCiISIA5PNZ/1Z3t/Qgq64zEWX+nYBgGq/oLK8VAy+SWJXwBNCiM6IQOmVOUaSUJeIJiXppWDNpjf5cQEdm70lAz1ZUlMHvdZigoiEiCewnkk0jvk/kRWOvd+QW1yvbYgCM9lRzR/+bRF7+1hNCIMSGVsbG8gV29ybGQhKgAyAA==","b64Record":"CiUIFhoDGIoJKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjCT38LlShNkzhlurKfFwdGkgbfaLF+/XoqOjJdvl1en3I5Q5H5+uIemk3qTKngt6bgaDAjh/v6sBhCu67rLAiIPCgkIpf7+rAYQrBISAhgCKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjlIA"},{"b64Body":"Cg8KCQim/v6sBhCwEhICGAISAhgDGIudjj4iAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjpoBiCAKAxiKCSKAIDYwODA2MDQwNTI2MDQwNTE2MTBlNmIzODAzODA2MTBlNmI4MzM5ODE4MTAxNjA0MDUyNjA0MDgxMTAxNTYxMDAyNjU3NjAwMDgwZmQ1YjgxMDE5MDgwODA1MTkwNjAyMDAxOTA5MjkxOTA4MDUxOTA2MDIwMDE5MDkyOTE5MDUwNTA1MDgxNjAwMDgwNjEwMTAwMGE4MTU0ODE3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYwMjE5MTY5MDgzNzNmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmMTYwMjE3OTA1NTUwODA2MDAxNjAwMDYxMDEwMDBhODE1NDgxNzNmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmMDIxOTE2OTA4MzczZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjE2MDIxNzkwNTU1MDUwNTA2MTBkOTg4MDYxMDBkMzYwMDAzOTYwMDBmM2ZlNjA4MDYwNDA1MjYwMDQzNjEwNjEwMDNmNTc2MDAwMzU2MGUwMWM4MDYzNTU1NWQ3ZTQxNDYxMDA0MTU3ODA2MzVlY2YyOThhMTQ2MTAwY2Y1NzgwNjM5ZGM3ZGIxMDE0NjEwMGZkNTc4MDYzYTlhM2E2MzUxNDYxMDE0YjU3NWIwMDViNjEwMGNkNjAwNDgwMzYwMzYwODA4MTEwMTU2MTAwNTc1NzYwMDA4MGZkNWI4MTAxOTA4MDgwMzU3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNjkwNjAyMDAxOTA5MjkxOTA4MDM1NzNmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmMTY5MDYwMjAwMTkwOTI5MTkwODAzNTczZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjE2OTA2MDIwMDE5MDkyOTE5MDgwMzU5MDYwMjAwMTkwOTI5MTkwNTA1MDUwNjEwMTc5NTY1YjAwNWI2MTAwZmI2MDA0ODAzNjAzNjAyMDgxMTAxNTYxMDBlNTU3NjAwMDgwZmQ1YjgxMDE5MDgwODAzNTkwNjAyMDAxOTA5MjkxOTA1MDUwNTA2MTA3MTM1NjViMDA1YjYxMDE0OTYwMDQ4MDM2MDM2MDQwODExMDE1NjEwMTEzNTc2MDAwODBmZDViODEwMTkwODA4MDM1NzNmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmMTY5MDYwMjAwMTkwOTI5MTkwODAzNTkwNjAyMDAxOTA5MjkxOTA1MDUwNTA2MTA4MmY1NjViMDA1YjYxMDE3NzYwMDQ4MDM2MDM2MDIwODExMDE1NjEwMTYxNTc2MDAwODBmZDViODEwMTkwODA4MDM1OTA2MDIwMDE5MDkyOTE5MDUwNTA1MDYxMGEwZjU2NWIwMDViODM3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNjYxMDhmYzgyOTA4MTE1MDI5MDYwNDA1MTYwMDA2MDQwNTE4MDgzMDM4MTg1ODg4OGYxOTM1MDUwNTA1MDE1ODAxNTYxMDFiZjU3M2Q2MDAwODAzZTNkNjAwMGZkNWI1MDYwMDA4MDkwNTQ5MDYxMDEwMDBhOTAwNDczZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjE2NzNmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmMTY2M2FjZWY2MDM3ODU2MDAyODQ4MTYxMDIwODU3ZmU1YjA0NjA0MDUxODM2M2ZmZmZmZmZmMTY2MGUwMWI4MTUyNjAwNDAxODA4MzczZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjE2NzNmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmMTY4MTUyNjAyMDAxODI4MTUyNjAyMDAxOTI1MDUwNTA2MDAwNjA0MDUxODA4MzAzODE2MDAwODc4MDNiMTU4MDE1NjEwMjcyNTc2MDAwODBmZDViNTA1YWYxMTU4MDE1NjEwMjg2NTczZDYwMDA4MDNlM2Q2MDAwZmQ1YjUwNTA1MDUwNjAwMTYwMDA5MDU0OTA2MTAxMDAwYTkwMDQ3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNjczZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjE2NjNhY2VmNjAzNzg1NjAwMjg0ODE2MTAyZDM1N2ZlNWIwNDYwNDA1MTgzNjNmZmZmZmZmZjE2NjBlMDFiODE1MjYwMDQwMTgwODM3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNjczZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjE2ODE1MjYwMjAwMTgyODE1MjYwMjAwMTkyNTA1MDUwNjAwMDYwNDA1MTgwODMwMzgxNjAwMDg3ODAzYjE1ODAxNTYxMDMzZDU3NjAwMDgwZmQ1YjUwNWFmMTE1ODAxNTYxMDM1MTU3M2Q2MDAwODAzZTNkNjAwMGZkNWI1MDUwNTA1MDgyNzNmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmMTY2MTA4ZmM4MjkwODExNTAyOTA2MDQwNTE2MDAwNjA0MDUxODA4MzAzODE4NTg4ODhmMTkzNTA1MDUwNTAxNTgwMTU2MTAzOWI1NzNkNjAwMDgwM2UzZDYwMDBmZDViNTA2MDAwODA5MDU0OTA2MTAxMDAwYTkwMDQ3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNjczZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjE2NjNhY2VmNjAzNzg0NjAwMjg0ODE2MTAzZTQ1N2ZlNWIwNDYwNDA1MTgzNjNmZmZmZmZmZjE2NjBlMDFiODE1MjYwMDQwMTgwODM3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNjczZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjE2ODE1MjYwMjAwMTgyODE1MjYwMjAwMTkyNTA1MDUwNjAwMDYwNDA1MTgwODMwMzgxNjAwMDg3ODAzYjE1ODAxNTYxMDQ0ZTU3NjAwMDgwZmQ1YjUwNWFmMTE1ODAxNTYxMDQ2MjU3M2Q2MDAwODAzZTNkNjAwMGZkNWI1MDUwNTA1MDYwMDE2MDAwOTA1NDkwNjEwMTAwMGE5MDA0NzNmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmMTY3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNjYzYWNlZjYwMzc4NDYwMDI4NDgxNjEwNGFmNTdmZTViMDQ2MDQwNTE4MzYzZmZmZmZmZmYxNjYwZTAxYjgxNTI2MDA0MDE4MDgzNzNmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmMTY3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNjgxNTI2MDIwMDE4MjgxNTI2MDIwMDE5MjUwNTA1MDYwMDA2MDQwNTE4MDgzMDM4MTYwMDA4NzgwM2IxNTgwMTU2MTA1MTk1NzYwMDA4MGZkNWI1MDVhZjExNTgwMTU2MTA1MmQ1NzNkNjAwMDgwM2UzZDYwMDBmZDViNTA1MDUwNTA4MTczZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjE2NjEwOGZjODI5MDgxMTUwMjkwNjA0MDUxNjAwMDYwNDA1MTgwODMwMzgxODU4ODg4ZjE5MzUwNTA1MDUwMTU4MDE1NjEwNTc3NTczZDYwMDA4MDNlM2Q2MDAwZmQ1YjUwNjAwMDgwOTA1NDkwNjEwMTAwMGE5MDA0NzNmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmMTY3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNjYzYWNlZjYwMzc4MzYwMDI4NDgxNjEwNWMwNTdmZTViMDQ2MDQwNTE4MzYzZmZmZmZmZmYxNjYwZTAxYjgxNTI2MDA0MDE4MDgzNzNmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmMTY3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNjgxNTI2MDIwMDE4MjgxNTI2MDIwMDE5MjUwNTA1MDYwMDA2MDQwNTE4MDgzMDM4MTYwMDA4NzgwM2IxNTgwMTU2MTA2MmE1NzYwMDA4MGZkNWI1MDVhZjExNTgwMTU2MTA2M2U1NzNkNjAwMDgwM2UzZDYwMDBmZDViNTA1MDUwNTA2MDAxNjAwMDkwNTQ5MDYxMDEwMDBhOTAwNDczZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjE2NzNmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmMTY2M2FjZWY2MDM3ODM2MDAyODQ4MTYxMDY4YjU3ZmU1YjA0NjA0MDUxODM2M2ZmZmZmZmZmMTY2MGUwMWI4MTUyNjAwNDAxODA4MzczZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjE2NzNmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmMTY4MTUyNjAyMDAxODI4MTUyNjAyMDAxOTI1MDUwNTA2MDAwNjA0MDUxODA4MzAzODE2MDAwODc4MDNiMTU4MDE1NjEwNmY1NTc2MDAwODBmZDViNTA1YWYxMTU4MDE1NjEwNzA5NTczZDYwMDA4MDNlM2Q2MDAwZmQ1YjUwNTA1MDUwNTA1MDUwNTA1NjViNjAwMDgwOTA1NDkwNjEwMTAwMGE5MDA0NzNmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmY=","b64Record":"CiAIFiocCgwIARAMGgYIgK6ZpA8SDAgBEA8aBgiArpmkDxIwDEvRXmI0K+k60UE7fAhIrsjVkdDDeMJiC4WD+wrC2y0yYtm7xWgc7MheWMPDcQDpGgsI4v7+rAYQpfubcCIPCgkIpv7+rAYQsBISAhgCKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjlIA"},{"b64Body":"Cg8KCQim/v6sBhC2EhICGAISAhgDGPjsozsiAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjoIB3hkSAxiKCSLWGWZmZmZmZmZmZmZmZmZmZmYxNjczZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjE2NjM4ZGQ5ODA0MzgyNjA0MDUxODI2M2ZmZmZmZmZmMTY2MGUwMWI4MTUyNjAwNDAxODA4MjgxNTI2MDIwMDE5MTUwNTA2MDAwNjA0MDUxODA4MzAzODE2MDAwODc4MDNiMTU4MDE1NjEwNzg3NTc2MDAwODBmZDViNTA1YWYxMTU4MDE1NjEwNzliNTczZDYwMDA4MDNlM2Q2MDAwZmQ1YjUwNTA1MDUwNjAwMTYwMDA5MDU0OTA2MTAxMDAwYTkwMDQ3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNjczZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjE2NjM4ZGQ5ODA0MzgyNjA0MDUxODI2M2ZmZmZmZmZmMTY2MGUwMWI4MTUyNjAwNDAxODA4MjgxNTI2MDIwMDE5MTUwNTA2MDAwNjA0MDUxODA4MzAzODE2MDAwODc4MDNiMTU4MDE1NjEwODE0NTc2MDAwODBmZDViNTA1YWYxMTU4MDE1NjEwODI4NTczZDYwMDA4MDNlM2Q2MDAwZmQ1YjUwNTA1MDUwNTA1NjViODE3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNjYxMDhmYzgyOTA4MTE1MDI5MDYwNDA1MTYwMDA2MDQwNTE4MDgzMDM4MTg1ODg4OGYxOTM1MDUwNTA1MDE1ODAxNTYxMDg3NTU3M2Q2MDAwODAzZTNkNjAwMGZkNWI1MDYwMDA4MDkwNTQ5MDYxMDEwMDBhOTAwNDczZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjE2NzNmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmMTY2M2FjZWY2MDM3ODM2MDAyODQ4MTYxMDhiZTU3ZmU1YjA0NjA0MDUxODM2M2ZmZmZmZmZmMTY2MGUwMWI4MTUyNjAwNDAxODA4MzczZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjE2NzNmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmMTY4MTUyNjAyMDAxODI4MTUyNjAyMDAxOTI1MDUwNTA2MDAwNjA0MDUxODA4MzAzODE2MDAwODc4MDNiMTU4MDE1NjEwOTI4NTc2MDAwODBmZDViNTA1YWYxMTU4MDE1NjEwOTNjNTczZDYwMDA4MDNlM2Q2MDAwZmQ1YjUwNTA1MDUwNjAwMTYwMDA5MDU0OTA2MTAxMDAwYTkwMDQ3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNjczZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjE2NjNhY2VmNjAzNzgzNjAwMjg0ODE2MTA5ODk1N2ZlNWIwNDYwNDA1MTgzNjNmZmZmZmZmZjE2NjBlMDFiODE1MjYwMDQwMTgwODM3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNjczZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjE2ODE1MjYwMjAwMTgyODE1MjYwMjAwMTkyNTA1MDUwNjAwMDYwNDA1MTgwODMwMzgxNjAwMDg3ODAzYjE1ODAxNTYxMDlmMzU3NjAwMDgwZmQ1YjUwNWFmMTE1ODAxNTYxMGEwNzU3M2Q2MDAwODAzZTNkNjAwMGZkNWI1MDUwNTA1MDUwNTA1NjViNjAwMDYwNjA2MDAwODA5MDU0OTA2MTAxMDAwYTkwMDQ3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNjczZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjE2ODM2MDQwNTE2MDI0MDE4MDgyODE1MjYwMjAwMTkxNTA1MDYwNDA1MTYwMjA4MTgzMDMwMzgxNTI5MDYwNDA1MjdmOGRkOTgwNDMwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDdiZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxOTE2NjAyMDgyMDE4MDUxN2JmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjgzODE4MzE2MTc4MzUyNTA1MDUwNTA2MDQwNTE4MDgyODA1MTkwNjAyMDAxOTA4MDgzODM1YjYwMjA4MzEwNjEwYjBjNTc4MDUxODI1MjYwMjA4MjAxOTE1MDYwMjA4MTAxOTA1MDYwMjA4MzAzOTI1MDYxMGFlOTU2NWI2MDAxODM2MDIwMDM2MTAxMDAwYTAzODAxOTgyNTExNjgxODQ1MTE2ODA4MjE3ODU1MjUwNTA1MDUwNTA1MDkwNTAwMTkxNTA1MDYwMDA2MDQwNTE4MDgzMDM4MTg1NWFmNDkxNTA1MDNkODA2MDAwODExNDYxMGI2YzU3NjA0MDUxOTE1MDYwMWYxOTYwM2YzZDAxMTY4MjAxNjA0MDUyM2Q4MjUyM2Q2MDAwNjAyMDg0MDEzZTYxMGI3MTU2NWI2MDYwOTE1MDViNTA5MTUwOTE1MDYwMDA2MDYwNjAwMTYwMDA5MDU0OTA2MTAxMDAwYTkwMDQ3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNjczZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjE2ODU2MDQwNTE2MDI0MDE4MDgyODE1MjYwMjAwMTkxNTA1MDYwNDA1MTYwMjA4MTgzMDMwMzgxNTI5MDYwNDA1MjdmOGRkOTgwNDMwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDdiZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxOTE2NjAyMDgyMDE4MDUxN2JmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjgzODE4MzE2MTc4MzUyNTA1MDUwNTA2MDQwNTE4MDgyODA1MTkwNjAyMDAxOTA4MDgzODM1YjYwMjA4MzEwNjEwYzc0NTc4MDUxODI1MjYwMjA4MjAxOTE1MDYwMjA4MTAxOTA1MDYwMjA4MzAzOTI1MDYxMGM1MTU2NWI2MDAxODM2MDIwMDM2MTAxMDAwYTAzODAxOTgyNTExNjgxODQ1MTE2ODA4MjE3ODU1MjUwNTA1MDUwNTA1MDkwNTAwMTkxNTA1MDYwMDA2MDQwNTE4MDgzMDM4MTg1NWFmNDkxNTA1MDNkODA2MDAwODExNDYxMGNkNDU3NjA0MDUxOTE1MDYwMWYxOTYwM2YzZDAxMTY4MjAxNjA0MDUyM2Q4MjUyM2Q2MDAwNjAyMDg0MDEzZTYxMGNkOTU2NWI2MDYwOTE1MDViNTA5MTUwOTE1MDgzMTU4MDYxMGNlOTU3NTA4MTE1NWIxNTYxMGQ1YzU3NjA0MDUxN2YwOGMzNzlhMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwODE1MjYwMDQwMTgwODA2MDIwMDE4MjgxMDM4MjUyNjAxZTgxNTI2MDIwMDE4MDdmNDQ2NTZjNjU2NzYxNzQ2NTIwNzQ3MjYxNmU3MzY2NjU3MjIwNjM2MTZjNmMyMDY2NjE2OTZjNjU2NDIxMDAwMDgxNTI1MDYwMjAwMTkxNTA1MDYwNDA1MTgwOTEwMzkwZmQ1YjUwNTA1MDUwNTA1NmZlYTI2NTYyN2E3YTcyMzE1ODIwNjE5ZDU1NTI1Y2QxN2Y3MzliMGNiMjZkODZmN2QxOGYxNTVlYWM0NWY5NTM3MGQ5NjRkZWEzYzcwZGIxNGQ2ZTY0NzM2ZjZjNjM0MzAwMDUxMTAwMzI=","b64Record":"CiAIFiocCgwIARAMGgYIgK6ZpA8SDAgBEA8aBgiArpmkDxIw6CHL1g5Z4RsV2BgHrAFIFp+ewsXRa0VUSAGwQpos5fPgytw7EQxKQMQRWmb0ro7UGgwI4v7+rAYQo/G51QIiDwoJCKb+/qwGELYSEgIYAiogw4PCrsOCwrfDg8K5dEY4w4LCrkrDg8KLw4PCkMODwo5SAA=="},{"b64Body":"Cg8KCQin/v6sBhC4EhICGAISAhgDGP7LzSwiAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjooBjgESCwjjzNmwBhCw+ppnGm0KIhIgTfK+IPwEhNs7Ir/f6yFhtvfJ18xvHqV3qcVcnELv8CYKIzohAv9NeseDq7icLqHCh1gUGr/ccRMYNJzRXQZLmggdB8UrCiISIHa6UcXa+4Yv1NfYkfXqgWlKmVagHZo6SgWFLW2RE27CIgxIZWxsbyBXb3JsZCEqADIA","b64Record":"CiUIFhoDGIsJKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjCZdjjqJ9+uL9/M9uMskZpAMhQMCOau1RAICsrL6fdu8PwepbxJu3W+48l3PMNINXYaCwjj/v6sBhDwne15Ig8KCQin/v6sBhC4EhICGAIqIMODwq7DgsK3w4PCuXRGOMOCwq5Kw4PCi8ODwpDDg8KOUgA="},{"b64Body":"Cg8KCQin/v6sBhC8EhICGAISAhgDGM6Y0i8iAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjpoBkAYKAxiLCSKIBjYwODA2MDQwNTI2MTAxNzE4MDYxMDAxMzYwMDAzOTYwMDBmM2ZlNjA4MDYwNDA1MjYwMDQzNjEwNjEwMDI5NTc2MDAwMzU2MGUwMWM4MDYzOGRkOTgwNDMxNDYxMDAyYjU3ODA2M2FjZWY2MDM3MTQ2MTAwNTk1NzViMDA1YjYxMDA1NzYwMDQ4MDM2MDM2MDIwODExMDE1NjEwMDQxNTc2MDAwODBmZDViODEwMTkwODA4MDM1OTA2MDIwMDE5MDkyOTE5MDUwNTA1MDYxMDBhNzU2NWIwMDViNjEwMGE1NjAwNDgwMzYwMzYwNDA4MTEwMTU2MTAwNmY1NzYwMDA4MGZkNWI4MTAxOTA4MDgwMzU3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNjkwNjAyMDAxOTA5MjkxOTA4MDM1OTA2MDIwMDE5MDkyOTE5MDUwNTA1MDYxMDBmMTU2NWIwMDViMzM3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNjYxMDhmYzgyOTA4MTE1MDI5MDYwNDA1MTYwMDA2MDQwNTE4MDgzMDM4MTg1ODg4OGYxOTM1MDUwNTA1MDE1ODAxNTYxMDBlZDU3M2Q2MDAwODAzZTNkNjAwMGZkNWI1MDUwNTY1YjgxNzNmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmMTY2MTA4ZmM4MjkwODExNTAyOTA2MDQwNTE2MDAwNjA0MDUxODA4MzAzODE4NTg4ODhmMTkzNTA1MDUwNTAxNTgwMTU2MTAxMzc1NzNkNjAwMDgwM2UzZDYwMDBmZDViNTA1MDUwNTZmZWEyNjU2MjdhN2E3MjMxNTgyMDVlYjQwYmI2MjA4MzVmYjc5NjljZGQ3NWRlZDE4ODkyN2Q1YzkyN2Y5M2JhNjc1ZDY1YjljNTIzNDQ0MjQyODc2NDczNmY2YzYzNDMwMDA1MTEwMDMy","b64Record":"CiAIFiocCgwIARAMGgYIgK6ZpA8SDAgBEA8aBgiArpmkDxIw2J0T/sr0wnM1AvzktPGep53zh80IvV/CDYo3hZRCqOf//lIBbR5Jzf+0jRcpFULPGgwI4/7+rAYQk83k3gIiDwoJCKf+/qwGELwSEgIYAiogw4PCrsOCwrfDg8K5dEY4w4LCrkrDg8KLw4PCkMODwo5SAA=="},{"b64Body":"ChAKCQio/v6sBhC+EhIDGIkJEgIYAxiW+66dAiICCHgyIMODwq7DgsK3w4PCuXRGOMOCwq5Kw4PCi8ODwpDDg8KOQkgKAxiLCRoiEiAPPv+YffJNlbp/991SvlEcVgc9hxNiXoF8pelkVi/kcCCQoQ8okE5CBQiAztoDUgBaAGoLY2VsbGFyIGRvb3I=","b64Record":"CiUIFiIDGIwJKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjDFubMgN99EjQ5VK2nDe29yyI/IV7jHMvSCONnBBmSejSU7KOkGAl7t8d06MBwD8WIaDAjk/v6sBhDl/7iDASIQCgkIqP7+rAYQvhISAxiJCSogw4PCrsOCwrfDg8K5dEY4w4LCrkrDg8KLw4PCkMODwo4w1tSRpAJCpgUKAxiMCRLxAmCAYEBSYAQ2EGEAKVdgADVg4ByAY43ZgEMUYQArV4BjrO9gNxRhAFlXWwBbYQBXYASANgNgIIEQFWEAQVdgAID9W4EBkICANZBgIAGQkpGQUFBQYQCnVlsAW2EApWAEgDYDYECBEBVhAG9XYACA/VuBAZCAgDVz//////////////////////////8WkGAgAZCSkZCANZBgIAGQkpGQUFBQYQDxVlsAWzNz//////////////////////////8WYQj8gpCBFQKQYEBRYABgQFGAgwOBhYiI8ZNQUFBQFYAVYQDtVz1gAIA+PWAA/VtQUFZbgXP//////////////////////////xZhCPyCkIEVApBgQFFgAGBAUYCDA4GFiIjxk1BQUFAVgBVhATdXPWAAgD49YAD9W1BQUFb+omVienpyMVggXrQLtiCDX7eWnN113tGIkn1ckn+TumddZbnFI0RCQodkc29sY0MABREAMiKAAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAowJoMOgMYjAlKFgoUAAAAAAAAAAAAAAAAAAAAAAAABIxyBwoDGIwJEAFSRwoJCgIYAxCY4okUCgoKAhhiEODciMYDCgoKAxigBhCatYg3CgoKAxihBhCatYg3CgsKAxiJCRDLxaTIBAoJCgMYjAkQoJwB"},{"b64Body":"ChAKCQio/v6sBhDAEhIDGIkJEgIYAxiW+66dAiICCHgyIMODwq7DgsK3w4PCuXRGOMOCwq5Kw4PCi8ODwpDDg8KOQkgKAxiLCRoiEiCUAEV9FR1C8yRwlH+8DNiieX7wuvI/3NexK0WUO/DymSCQoQ8okE5CBQiAztoDUgBaAGoLY2VsbGFyIGRvb3I=","b64Record":"CiUIFiIDGI0JKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjBpKZCGXH0olCUzpVtAzIqO1pVZ8EfqsWFywnVSAUpkpOKcdvdowQniVx/WW5m434waDAjk/v6sBhCovLHoAiIQCgkIqP7+rAYQwBISAxiJCSogw4PCrsOCwrfDg8K5dEY4w4LCrkrDg8KLw4PCkMODwo4w1tSRpAJCpgUKAxiNCRLxAmCAYEBSYAQ2EGEAKVdgADVg4ByAY43ZgEMUYQArV4BjrO9gNxRhAFlXWwBbYQBXYASANgNgIIEQFWEAQVdgAID9W4EBkICANZBgIAGQkpGQUFBQYQCnVlsAW2EApWAEgDYDYECBEBVhAG9XYACA/VuBAZCAgDVz//////////////////////////8WkGAgAZCSkZCANZBgIAGQkpGQUFBQYQDxVlsAWzNz//////////////////////////8WYQj8gpCBFQKQYEBRYABgQFGAgwOBhYiI8ZNQUFBQFYAVYQDtVz1gAIA+PWAA/VtQUFZbgXP//////////////////////////xZhCPyCkIEVApBgQFFgAGBAUYCDA4GFiIjxk1BQUFAVgBVhATdXPWAAgD49YAD9W1BQUFb+omVienpyMVggXrQLtiCDX7eWnN113tGIkn1ckn+TumddZbnFI0RCQodkc29sY0MABREAMiKAAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAowJoMOgMYjQlKFgoUAAAAAAAAAAAAAAAAAAAAAAAABI1yBwoDGI0JEAFSRwoJCgIYAxCY4okUCgoKAhhiEODciMYDCgoKAxigBhCatYg3CgoKAxihBhCatYg3CgsKAxiJCRDLxaTIBAoJCgMYjQkQoJwB"},{"b64Body":"ChAKCQip/v6sBhDCEhIDGIkJEgIYAxjVgL+gAiICCHgyIMODwq7DgsK3w4PCuXRGOMOCwq5Kw4PCi8ODwpDDg8KOQooBCgMYigkaIhIgnGIqDbV8kzQkxYwbEbQpiv2VDQmkZ9sl56qY/mzPwukgkKEPKJBOQgUIgM7aA0pAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABIwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEjVIAWgBqC2NlbGxhciBkb29y","b64Record":"CiUIFiIDGI4JKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjD21oMrwfas615B9YLg/53aiNveygzus+vuBiMYKeXOSe+p4/3+WJE8n5Vgz3FbEIQaDAjl/v6sBhCqvN6MASIQCgkIqf7+rAYQwhISAxiJCSogw4PCrsOCwrfDg8K5dEY4w4LCrkrDg8KLw4PCkMODwo4wldqhpwJCzR0KAxiOCRKYG2CAYEBSYAQ2EGEAP1dgADVg4ByAY1VV1+QUYQBBV4BjXs8pihRhAM9XgGOdx9sQFGEA/VeAY6mjpjUUYQFLV1sAW2EAzWAEgDYDYICBEBVhAFdXYACA/VuBAZCAgDVz//////////////////////////8WkGAgAZCSkZCANXP//////////////////////////xaQYCABkJKRkIA1c///////////////////////////FpBgIAGQkpGQgDWQYCABkJKRkFBQUGEBeVZbAFthAPtgBIA2A2AggRAVYQDlV2AAgP1bgQGQgIA1kGAgAZCSkZBQUFBhBxNWWwBbYQFJYASANgNgQIEQFWEBE1dgAID9W4EBkICANXP//////////////////////////xaQYCABkJKRkIA1kGAgAZCSkZBQUFBhCC9WWwBbYQF3YASANgNgIIEQFWEBYVdgAID9W4EBkICANZBgIAGQkpGQUFBQYQoPVlsAW4Nz//////////////////////////8WYQj8gpCBFQKQYEBRYABgQFGAgwOBhYiI8ZNQUFBQFYAVYQG/Vz1gAIA+PWAA/VtQYACAkFSQYQEACpAEc///////////////////////////FnP//////////////////////////xZjrO9gN4VgAoSBYQIIV/5bBGBAUYNj/////xZg4BuBUmAEAYCDc///////////////////////////FnP//////////////////////////xaBUmAgAYKBUmAgAZJQUFBgAGBAUYCDA4FgAIeAOxWAFWECcldgAID9W1Ba8RWAFWEChlc9YACAPj1gAP1bUFBQUGABYACQVJBhAQAKkARz//////////////////////////8Wc///////////////////////////FmOs72A3hWAChIFhAtNX/lsEYEBRg2P/////FmDgG4FSYAQBgINz//////////////////////////8Wc///////////////////////////FoFSYCABgoFSYCABklBQUGAAYEBRgIMDgWAAh4A7FYAVYQM9V2AAgP1bUFrxFYAVYQNRVz1gAIA+PWAA/VtQUFBQgnP//////////////////////////xZhCPyCkIEVApBgQFFgAGBAUYCDA4GFiIjxk1BQUFAVgBVhA5tXPWAAgD49YAD9W1BgAICQVJBhAQAKkARz//////////////////////////8Wc///////////////////////////FmOs72A3hGAChIFhA+RX/lsEYEBRg2P/////FmDgG4FSYAQBgINz//////////////////////////8Wc///////////////////////////FoFSYCABgoFSYCABklBQUGAAYEBRgIMDgWAAh4A7FYAVYQROV2AAgP1bUFrxFYAVYQRiVz1gAIA+PWAA/VtQUFBQYAFgAJBUkGEBAAqQBHP//////////////////////////xZz//////////////////////////8WY6zvYDeEYAKEgWEEr1f+WwRgQFGDY/////8WYOAbgVJgBAGAg3P//////////////////////////xZz//////////////////////////8WgVJgIAGCgVJgIAGSUFBQYABgQFGAgwOBYACHgDsVgBVhBRlXYACA/VtQWvEVgBVhBS1XPWAAgD49YAD9W1BQUFCBc///////////////////////////FmEI/IKQgRUCkGBAUWAAYEBRgIMDgYWIiPGTUFBQUBWAFWEFd1c9YACAPj1gAP1bUGAAgJBUkGEBAAqQBHP//////////////////////////xZz//////////////////////////8WY6zvYDeDYAKEgWEFwFf+WwRgQFGDY/////8WYOAbgVJgBAGAg3P//////////////////////////xZz//////////////////////////8WgVJgIAGCgVJgIAGSUFBQYABgQFGAgwOBYACHgDsVgBVhBipXYACA/VtQWvEVgBVhBj5XPWAAgD49YAD9W1BQUFBgAWAAkFSQYQEACpAEc///////////////////////////FnP//////////////////////////xZjrO9gN4NgAoSBYQaLV/5bBGBAUYNj/////xZg4BuBUmAEAYCDc///////////////////////////FnP//////////////////////////xaBUmAgAYKBUmAgAZJQUFBgAGBAUYCDA4FgAIeAOxWAFWEG9VdgAID9W1Ba8RWAFWEHCVc9YACAPj1gAP1bUFBQUFBQUFBWW2AAgJBUkGEBAAqQBHP//////////////////////////xZz//////////////////////////8WY43ZgEOCYEBRgmP/////FmDgG4FSYAQBgIKBUmAgAZFQUGAAYEBRgIMDgWAAh4A7FYAVYQeHV2AAgP1bUFrxFYAVYQebVz1gAIA+PWAA/VtQUFBQYAFgAJBUkGEBAAqQBHP//////////////////////////xZz//////////////////////////8WY43ZgEOCYEBRgmP/////FmDgG4FSYAQBgIKBUmAgAZFQUGAAYEBRgIMDgWAAh4A7FYAVYQgUV2AAgP1bUFrxFYAVYQgoVz1gAIA+PWAA/VtQUFBQUFZbgXP//////////////////////////xZhCPyCkIEVApBgQFFgAGBAUYCDA4GFiIjxk1BQUFAVgBVhCHVXPWAAgD49YAD9W1BgAICQVJBhAQAKkARz//////////////////////////8Wc///////////////////////////FmOs72A3g2AChIFhCL5X/lsEYEBRg2P/////FmDgG4FSYAQBgINz//////////////////////////8Wc///////////////////////////FoFSYCABgoFSYCABklBQUGAAYEBRgIMDgWAAh4A7FYAVYQkoV2AAgP1bUFrxFYAVYQk8Vz1gAIA+PWAA/VtQUFBQYAFgAJBUkGEBAAqQBHP//////////////////////////xZz//////////////////////////8WY6zvYDeDYAKEgWEJiVf+WwRgQFGDY/////8WYOAbgVJgBAGAg3P//////////////////////////xZz//////////////////////////8WgVJgIAGCgVJgIAGSUFBQYABgQFGAgwOBYACHgDsVgBVhCfNXYACA/VtQWvEVgBVhCgdXPWAAgD49YAD9W1BQUFBQUFZbYABgYGAAgJBUkGEBAAqQBHP//////////////////////////xZz//////////////////////////8Wg2BAUWAkAYCCgVJgIAGRUFBgQFFgIIGDAwOBUpBgQFJ/jdmAQwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB7/////////////////////////////////////xkWYCCCAYBRe/////////////////////////////////////+DgYMWF4NSUFBQUGBAUYCCgFGQYCABkICDg1tgIIMQYQsMV4BRglJgIIIBkVBgIIEBkFBgIIMDklBhCulWW2ABg2AgA2EBAAoDgBmCURaBhFEWgIIXhVJQUFBQUFCQUAGRUFBgAGBAUYCDA4GFWvSRUFA9gGAAgRRhC2xXYEBRkVBgHxlgPz0BFoIBYEBSPYJSPWAAYCCEAT5hC3FWW2BgkVBbUJFQkVBgAGBgYAFgAJBUkGEBAAqQBHP//////////////////////////xZz//////////////////////////8WhWBAUWAkAYCCgVJgIAGRUFBgQFFgIIGDAwOBUpBgQFJ/jdmAQwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB7/////////////////////////////////////xkWYCCCAYBRe/////////////////////////////////////+DgYMWF4NSUFBQUGBAUYCCgFGQYCABkICDg1tgIIMQYQx0V4BRglJgIIIBkVBgIIEBkFBgIIMDklBhDFFWW2ABg2AgA2EBAAoDgBmCURaBhFEWgIIXhVJQUFBQUFCQUAGRUFBgAGBAUYCDA4GFWvSRUFA9gGAAgRRhDNRXYEBRkVBgHxlgPz0BFoIBYEBSPYJSPWAAYCCEAT5hDNlWW2BgkVBbUJFQkVCDFYBhDOlXUIEVWxVhDVxXYEBRfwjDeaAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgVJgBAGAgGAgAYKBA4JSYB6BUmAgAYB/RGVsZWdhdGUgdHJhbnNmZXIgY2FsbCBmYWlsZWQhAACBUlBgIAGRUFBgQFGAkQOQ/VtQUFBQUFb+omVienpyMVggYZ1VUlzRf3ObDLJthvfRjxVerEX5U3DZZN6jxw2xTW5kc29sY0MABREAMiKAAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAowJoMOgMYjglKFgoUAAAAAAAAAAAAAAAAAAAAAAAABI5yBwoDGI4JEAFSRwoJCgIYAxCu6rgUCgoKAhhiEJiS48oDCgoKAxigBhDy29M3CgoKAxihBhDy29M3CgsKAxiJCRDJ0MTOBAoJCgMYjgkQoJwB"},{"b64Body":"ChAKCQip/v6sBhDEEhIDGIkJEgIYAxjgrLEDIgIIeDIgw4PCrsOCwrfDg8K5dEY4w4LCrkrDg8KLw4PCkMODwo46LwoDGI4JEKCNBiIkXs8pigAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAy","b64Record":"CiUIFiIDGI4JKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjCdRV7F08O7nSMMB5YTtt18nQkCMBPv5w6g7Mzq7Z2/cfRgnpO0NH2ci259GhVbjccaDAjl/v6sBhDa9tfxAiIQCgkIqf7+rAYQxBISAxiJCSogw4PCrsOCwrfDg8K5dEY4w4LCrkrDg8KLw4PCkMODwo4wgNfaAjqMAgoDGI4JIoACAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACiA8QRSMwoJCgIYYhCArrUFCgoKAxiJCRD/rbUFCgcKAxiMCRBjCgcKAxiNCRBjCggKAxiOCRDIAQ=="}]},"sendHbarsToCallerFromDifferentAddresses":{"placeholderNum":1167,"encodedItems":[{"b64Body":"Cg8KCQiu/v6sBhDmEhICGAISAhgDGPuV9hQiAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjloxCiISIOaUQlz5P2r9MDy6ASivr3GxteaIrmdOjInFdM2LUxErEICU69wDSgUIgM7aAw==","b64Record":"CiUIFhIDGJAJKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjDhJyrTl12wmOu9GsmrqHJ57K6tl2IDq42EjUuyWn7t6catxaFOdb7gSxB+BodpV44aDAjq/v6sBhD7ruX1ASIPCgkIrv7+rAYQ5hISAhgCKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjlIZCgoKAhgCEP+n1rkHCgsKAxiQCRCAqNa5Bw=="},{"b64Body":"ChEKCQiv/v6sBhDoEhICGAIgAVpoCiM6IQMIls7RYpi0xCfekVbp7PiaTNimH6yAk5XF0m81utTrc0oFCIDO2gNqFGF1dG8tY3JlYXRlZCBhY2NvdW50kgEjOiEDCJbO0WKYtMQn3pFW6ez4mkzYph+sgJOVxdJvNbrU63M=","b64Record":"CgcIFhIDGJEJEjAv+2d3dmOFVr3RLAsOwjOTH9YgPZoIFy9n/xPjWdfKmI7hpTte+Hq/cWvf0BZrIEoaCwjr/v6sBhDWjtYVIhEKCQiv/v6sBhDoEhICGAIgASoUYXV0by1jcmVhdGVkIGFjY291bnRSAKoBFJJzP+0R+z6bJyL2nhnGo3nosPME"},{"b64Body":"Cg8KCQiv/v6sBhDoEhICGAISAhgDGKqQBSICCHgyIMODwq7DgsK3w4PCuXRGOMOCwq5Kw4PCi8ODwpDDg8KOcj0KOwoKCgIYAhD/j9/ASgotCiUiIzohAwiWztFimLTEJ96RVuns+JpM2KYfrICTlcXSbzW61OtzEICQ38BK","b64Record":"CiAIFiocCgwIARAMGgYIgK6ZpA8SDAgBEA8aBgiArpmkDxIwCRvv4gifpGIeu10x+1UNXke8wTLdQ7XmmbPQKsBnrGNWx+3lZ0g58fZZfFnCClggGgsI6/7+rAYQ147WFSIPCgkIr/7+rAYQ6BISAhgCKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjlIZCgoKAhgCEP+P38BKCgsKAxiRCRCAkN/ASg=="},{"b64Body":"Cg8KCQiv/v6sBhDqEhICGAISAhgDGLXNziwiAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjooBjwESDAjrzNmwBhCmuJjzARptCiISIE8xp/5QKyxp22simcTU95oxLQA/ZvSj5MR9MNEE2miMCiM6IQM19cAyJ+yWGR0ufcoLFjNLiuPD9XPwssg1yFbN4eDZfwoiEiAKHHeO67Qqy40bRGtAmcpBAB2MisscQTtY3NJukEOlAiIMSGVsbG8gV29ybGQhKgAyAA==","b64Record":"CiUIFhoDGJIJKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjCdxx7BObSRWvORgwmiUmVMnS620hQh7DxWx0/cAvDy987xNrwXUrIPhtEi4SbXGwYaDAjr/v6sBhDMsMn6ASIPCgkIr/7+rAYQ6hISAhgCKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjlIA"},{"b64Body":"Cg8KCQiw/v6sBhDuEhICGAISAhgDGNyejz4iAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjpoBiCAKAxiSCSKAIDYwODA2MDQwNTI2MDQwNTE2MTBlNmIzODAzODA2MTBlNmI4MzM5ODE4MTAxNjA0MDUyNjA0MDgxMTAxNTYxMDAyNjU3NjAwMDgwZmQ1YjgxMDE5MDgwODA1MTkwNjAyMDAxOTA5MjkxOTA4MDUxOTA2MDIwMDE5MDkyOTE5MDUwNTA1MDgxNjAwMDgwNjEwMTAwMGE4MTU0ODE3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYwMjE5MTY5MDgzNzNmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmMTYwMjE3OTA1NTUwODA2MDAxNjAwMDYxMDEwMDBhODE1NDgxNzNmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmMDIxOTE2OTA4MzczZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjE2MDIxNzkwNTU1MDUwNTA2MTBkOTg4MDYxMDBkMzYwMDAzOTYwMDBmM2ZlNjA4MDYwNDA1MjYwMDQzNjEwNjEwMDNmNTc2MDAwMzU2MGUwMWM4MDYzNTU1NWQ3ZTQxNDYxMDA0MTU3ODA2MzVlY2YyOThhMTQ2MTAwY2Y1NzgwNjM5ZGM3ZGIxMDE0NjEwMGZkNTc4MDYzYTlhM2E2MzUxNDYxMDE0YjU3NWIwMDViNjEwMGNkNjAwNDgwMzYwMzYwODA4MTEwMTU2MTAwNTc1NzYwMDA4MGZkNWI4MTAxOTA4MDgwMzU3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNjkwNjAyMDAxOTA5MjkxOTA4MDM1NzNmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmMTY5MDYwMjAwMTkwOTI5MTkwODAzNTczZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjE2OTA2MDIwMDE5MDkyOTE5MDgwMzU5MDYwMjAwMTkwOTI5MTkwNTA1MDUwNjEwMTc5NTY1YjAwNWI2MTAwZmI2MDA0ODAzNjAzNjAyMDgxMTAxNTYxMDBlNTU3NjAwMDgwZmQ1YjgxMDE5MDgwODAzNTkwNjAyMDAxOTA5MjkxOTA1MDUwNTA2MTA3MTM1NjViMDA1YjYxMDE0OTYwMDQ4MDM2MDM2MDQwODExMDE1NjEwMTEzNTc2MDAwODBmZDViODEwMTkwODA4MDM1NzNmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmMTY5MDYwMjAwMTkwOTI5MTkwODAzNTkwNjAyMDAxOTA5MjkxOTA1MDUwNTA2MTA4MmY1NjViMDA1YjYxMDE3NzYwMDQ4MDM2MDM2MDIwODExMDE1NjEwMTYxNTc2MDAwODBmZDViODEwMTkwODA4MDM1OTA2MDIwMDE5MDkyOTE5MDUwNTA1MDYxMGEwZjU2NWIwMDViODM3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNjYxMDhmYzgyOTA4MTE1MDI5MDYwNDA1MTYwMDA2MDQwNTE4MDgzMDM4MTg1ODg4OGYxOTM1MDUwNTA1MDE1ODAxNTYxMDFiZjU3M2Q2MDAwODAzZTNkNjAwMGZkNWI1MDYwMDA4MDkwNTQ5MDYxMDEwMDBhOTAwNDczZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjE2NzNmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmMTY2M2FjZWY2MDM3ODU2MDAyODQ4MTYxMDIwODU3ZmU1YjA0NjA0MDUxODM2M2ZmZmZmZmZmMTY2MGUwMWI4MTUyNjAwNDAxODA4MzczZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjE2NzNmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmMTY4MTUyNjAyMDAxODI4MTUyNjAyMDAxOTI1MDUwNTA2MDAwNjA0MDUxODA4MzAzODE2MDAwODc4MDNiMTU4MDE1NjEwMjcyNTc2MDAwODBmZDViNTA1YWYxMTU4MDE1NjEwMjg2NTczZDYwMDA4MDNlM2Q2MDAwZmQ1YjUwNTA1MDUwNjAwMTYwMDA5MDU0OTA2MTAxMDAwYTkwMDQ3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNjczZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjE2NjNhY2VmNjAzNzg1NjAwMjg0ODE2MTAyZDM1N2ZlNWIwNDYwNDA1MTgzNjNmZmZmZmZmZjE2NjBlMDFiODE1MjYwMDQwMTgwODM3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNjczZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjE2ODE1MjYwMjAwMTgyODE1MjYwMjAwMTkyNTA1MDUwNjAwMDYwNDA1MTgwODMwMzgxNjAwMDg3ODAzYjE1ODAxNTYxMDMzZDU3NjAwMDgwZmQ1YjUwNWFmMTE1ODAxNTYxMDM1MTU3M2Q2MDAwODAzZTNkNjAwMGZkNWI1MDUwNTA1MDgyNzNmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmMTY2MTA4ZmM4MjkwODExNTAyOTA2MDQwNTE2MDAwNjA0MDUxODA4MzAzODE4NTg4ODhmMTkzNTA1MDUwNTAxNTgwMTU2MTAzOWI1NzNkNjAwMDgwM2UzZDYwMDBmZDViNTA2MDAwODA5MDU0OTA2MTAxMDAwYTkwMDQ3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNjczZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjE2NjNhY2VmNjAzNzg0NjAwMjg0ODE2MTAzZTQ1N2ZlNWIwNDYwNDA1MTgzNjNmZmZmZmZmZjE2NjBlMDFiODE1MjYwMDQwMTgwODM3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNjczZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjE2ODE1MjYwMjAwMTgyODE1MjYwMjAwMTkyNTA1MDUwNjAwMDYwNDA1MTgwODMwMzgxNjAwMDg3ODAzYjE1ODAxNTYxMDQ0ZTU3NjAwMDgwZmQ1YjUwNWFmMTE1ODAxNTYxMDQ2MjU3M2Q2MDAwODAzZTNkNjAwMGZkNWI1MDUwNTA1MDYwMDE2MDAwOTA1NDkwNjEwMTAwMGE5MDA0NzNmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmMTY3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNjYzYWNlZjYwMzc4NDYwMDI4NDgxNjEwNGFmNTdmZTViMDQ2MDQwNTE4MzYzZmZmZmZmZmYxNjYwZTAxYjgxNTI2MDA0MDE4MDgzNzNmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmMTY3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNjgxNTI2MDIwMDE4MjgxNTI2MDIwMDE5MjUwNTA1MDYwMDA2MDQwNTE4MDgzMDM4MTYwMDA4NzgwM2IxNTgwMTU2MTA1MTk1NzYwMDA4MGZkNWI1MDVhZjExNTgwMTU2MTA1MmQ1NzNkNjAwMDgwM2UzZDYwMDBmZDViNTA1MDUwNTA4MTczZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjE2NjEwOGZjODI5MDgxMTUwMjkwNjA0MDUxNjAwMDYwNDA1MTgwODMwMzgxODU4ODg4ZjE5MzUwNTA1MDUwMTU4MDE1NjEwNTc3NTczZDYwMDA4MDNlM2Q2MDAwZmQ1YjUwNjAwMDgwOTA1NDkwNjEwMTAwMGE5MDA0NzNmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmMTY3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNjYzYWNlZjYwMzc4MzYwMDI4NDgxNjEwNWMwNTdmZTViMDQ2MDQwNTE4MzYzZmZmZmZmZmYxNjYwZTAxYjgxNTI2MDA0MDE4MDgzNzNmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmMTY3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNjgxNTI2MDIwMDE4MjgxNTI2MDIwMDE5MjUwNTA1MDYwMDA2MDQwNTE4MDgzMDM4MTYwMDA4NzgwM2IxNTgwMTU2MTA2MmE1NzYwMDA4MGZkNWI1MDVhZjExNTgwMTU2MTA2M2U1NzNkNjAwMDgwM2UzZDYwMDBmZDViNTA1MDUwNTA2MDAxNjAwMDkwNTQ5MDYxMDEwMDBhOTAwNDczZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjE2NzNmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmMTY2M2FjZWY2MDM3ODM2MDAyODQ4MTYxMDY4YjU3ZmU1YjA0NjA0MDUxODM2M2ZmZmZmZmZmMTY2MGUwMWI4MTUyNjAwNDAxODA4MzczZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjE2NzNmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmMTY4MTUyNjAyMDAxODI4MTUyNjAyMDAxOTI1MDUwNTA2MDAwNjA0MDUxODA4MzAzODE2MDAwODc4MDNiMTU4MDE1NjEwNmY1NTc2MDAwODBmZDViNTA1YWYxMTU4MDE1NjEwNzA5NTczZDYwMDA4MDNlM2Q2MDAwZmQ1YjUwNTA1MDUwNTA1MDUwNTA1NjViNjAwMDgwOTA1NDkwNjEwMTAwMGE5MDA0NzNmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmY=","b64Record":"CiAIFiocCgwIARAMGgYIgK6ZpA8SDAgBEA8aBgiArpmkDxIwkVrlvlu1X78v1PDQMKazxjLKh/S7juUHqEV0SrC2T55uc8D+9t9+wAktgxYT2asZGgsI7P7+rAYQ37jAHyIPCgkIsP7+rAYQ7hISAhgCKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjlIA"},{"b64Body":"Cg8KCQiw/v6sBhD0EhICGAISAhgDGKLvpDsiAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjoIB3hkSAxiSCSLWGWZmZmZmZmZmZmZmZmZmZmYxNjczZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjE2NjM4ZGQ5ODA0MzgyNjA0MDUxODI2M2ZmZmZmZmZmMTY2MGUwMWI4MTUyNjAwNDAxODA4MjgxNTI2MDIwMDE5MTUwNTA2MDAwNjA0MDUxODA4MzAzODE2MDAwODc4MDNiMTU4MDE1NjEwNzg3NTc2MDAwODBmZDViNTA1YWYxMTU4MDE1NjEwNzliNTczZDYwMDA4MDNlM2Q2MDAwZmQ1YjUwNTA1MDUwNjAwMTYwMDA5MDU0OTA2MTAxMDAwYTkwMDQ3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNjczZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjE2NjM4ZGQ5ODA0MzgyNjA0MDUxODI2M2ZmZmZmZmZmMTY2MGUwMWI4MTUyNjAwNDAxODA4MjgxNTI2MDIwMDE5MTUwNTA2MDAwNjA0MDUxODA4MzAzODE2MDAwODc4MDNiMTU4MDE1NjEwODE0NTc2MDAwODBmZDViNTA1YWYxMTU4MDE1NjEwODI4NTczZDYwMDA4MDNlM2Q2MDAwZmQ1YjUwNTA1MDUwNTA1NjViODE3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNjYxMDhmYzgyOTA4MTE1MDI5MDYwNDA1MTYwMDA2MDQwNTE4MDgzMDM4MTg1ODg4OGYxOTM1MDUwNTA1MDE1ODAxNTYxMDg3NTU3M2Q2MDAwODAzZTNkNjAwMGZkNWI1MDYwMDA4MDkwNTQ5MDYxMDEwMDBhOTAwNDczZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjE2NzNmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmMTY2M2FjZWY2MDM3ODM2MDAyODQ4MTYxMDhiZTU3ZmU1YjA0NjA0MDUxODM2M2ZmZmZmZmZmMTY2MGUwMWI4MTUyNjAwNDAxODA4MzczZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjE2NzNmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmMTY4MTUyNjAyMDAxODI4MTUyNjAyMDAxOTI1MDUwNTA2MDAwNjA0MDUxODA4MzAzODE2MDAwODc4MDNiMTU4MDE1NjEwOTI4NTc2MDAwODBmZDViNTA1YWYxMTU4MDE1NjEwOTNjNTczZDYwMDA4MDNlM2Q2MDAwZmQ1YjUwNTA1MDUwNjAwMTYwMDA5MDU0OTA2MTAxMDAwYTkwMDQ3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNjczZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjE2NjNhY2VmNjAzNzgzNjAwMjg0ODE2MTA5ODk1N2ZlNWIwNDYwNDA1MTgzNjNmZmZmZmZmZjE2NjBlMDFiODE1MjYwMDQwMTgwODM3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNjczZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjE2ODE1MjYwMjAwMTgyODE1MjYwMjAwMTkyNTA1MDUwNjAwMDYwNDA1MTgwODMwMzgxNjAwMDg3ODAzYjE1ODAxNTYxMDlmMzU3NjAwMDgwZmQ1YjUwNWFmMTE1ODAxNTYxMGEwNzU3M2Q2MDAwODAzZTNkNjAwMGZkNWI1MDUwNTA1MDUwNTA1NjViNjAwMDYwNjA2MDAwODA5MDU0OTA2MTAxMDAwYTkwMDQ3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNjczZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjE2ODM2MDQwNTE2MDI0MDE4MDgyODE1MjYwMjAwMTkxNTA1MDYwNDA1MTYwMjA4MTgzMDMwMzgxNTI5MDYwNDA1MjdmOGRkOTgwNDMwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDdiZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxOTE2NjAyMDgyMDE4MDUxN2JmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjgzODE4MzE2MTc4MzUyNTA1MDUwNTA2MDQwNTE4MDgyODA1MTkwNjAyMDAxOTA4MDgzODM1YjYwMjA4MzEwNjEwYjBjNTc4MDUxODI1MjYwMjA4MjAxOTE1MDYwMjA4MTAxOTA1MDYwMjA4MzAzOTI1MDYxMGFlOTU2NWI2MDAxODM2MDIwMDM2MTAxMDAwYTAzODAxOTgyNTExNjgxODQ1MTE2ODA4MjE3ODU1MjUwNTA1MDUwNTA1MDkwNTAwMTkxNTA1MDYwMDA2MDQwNTE4MDgzMDM4MTg1NWFmNDkxNTA1MDNkODA2MDAwODExNDYxMGI2YzU3NjA0MDUxOTE1MDYwMWYxOTYwM2YzZDAxMTY4MjAxNjA0MDUyM2Q4MjUyM2Q2MDAwNjAyMDg0MDEzZTYxMGI3MTU2NWI2MDYwOTE1MDViNTA5MTUwOTE1MDYwMDA2MDYwNjAwMTYwMDA5MDU0OTA2MTAxMDAwYTkwMDQ3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNjczZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjE2ODU2MDQwNTE2MDI0MDE4MDgyODE1MjYwMjAwMTkxNTA1MDYwNDA1MTYwMjA4MTgzMDMwMzgxNTI5MDYwNDA1MjdmOGRkOTgwNDMwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDdiZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxOTE2NjAyMDgyMDE4MDUxN2JmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjgzODE4MzE2MTc4MzUyNTA1MDUwNTA2MDQwNTE4MDgyODA1MTkwNjAyMDAxOTA4MDgzODM1YjYwMjA4MzEwNjEwYzc0NTc4MDUxODI1MjYwMjA4MjAxOTE1MDYwMjA4MTAxOTA1MDYwMjA4MzAzOTI1MDYxMGM1MTU2NWI2MDAxODM2MDIwMDM2MTAxMDAwYTAzODAxOTgyNTExNjgxODQ1MTE2ODA4MjE3ODU1MjUwNTA1MDUwNTA1MDkwNTAwMTkxNTA1MDYwMDA2MDQwNTE4MDgzMDM4MTg1NWFmNDkxNTA1MDNkODA2MDAwODExNDYxMGNkNDU3NjA0MDUxOTE1MDYwMWYxOTYwM2YzZDAxMTY4MjAxNjA0MDUyM2Q4MjUyM2Q2MDAwNjAyMDg0MDEzZTYxMGNkOTU2NWI2MDYwOTE1MDViNTA5MTUwOTE1MDgzMTU4MDYxMGNlOTU3NTA4MTE1NWIxNTYxMGQ1YzU3NjA0MDUxN2YwOGMzNzlhMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwODE1MjYwMDQwMTgwODA2MDIwMDE4MjgxMDM4MjUyNjAxZTgxNTI2MDIwMDE4MDdmNDQ2NTZjNjU2NzYxNzQ2NTIwNzQ3MjYxNmU3MzY2NjU3MjIwNjM2MTZjNmMyMDY2NjE2OTZjNjU2NDIxMDAwMDgxNTI1MDYwMjAwMTkxNTA1MDYwNDA1MTgwOTEwMzkwZmQ1YjUwNTA1MDUwNTA1NmZlYTI2NTYyN2E3YTcyMzE1ODIwNjE5ZDU1NTI1Y2QxN2Y3MzliMGNiMjZkODZmN2QxOGYxNTVlYWM0NWY5NTM3MGQ5NjRkZWEzYzcwZGIxNGQ2ZTY0NzM2ZjZjNjM0MzAwMDUxMTAwMzI=","b64Record":"CiAIFiocCgwIARAMGgYIgK6ZpA8SDAgBEA8aBgiArpmkDxIwbGUVWPygtuHPVvcxzaC8W263NgP8fXQRfr0pT2S2M9w56VhVuHhndb0nAD94acTQGgwI7P7+rAYQ9d+fhAIiDwoJCLD+/qwGEPQSEgIYAiogw4PCrsOCwrfDg8K5dEY4w4LCrkrDg8KLw4PCkMODwo5SAA=="},{"b64Body":"Cg8KCQix/v6sBhD2EhICGAISAhgDGP7LzSwiAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjooBjgESCwjtzNmwBhCjqO0NGm0KIhIgpJzBFgvOWu+NPIlUIth34HNvot7bKBUoAcVL8j//ouYKIzohA2baUZt4OMwmE3BvgeupxfQdNQgbC7c0VDzLLjc5OZ8FCiISINYIoeX/4sUPAsRr/zrF2+lkmoqvo9Jhs98x538YK9GkIgxIZWxsbyBXb3JsZCEqADIA","b64Record":"CiUIFhoDGJMJKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjAHbM+54oWIiA3mW2vddpTsnVNa6LtJqUX5KXKojO3tG3gqAuaTJ8RaIcjiaoPp+EAaCwjt/v6sBhDhw8koIg8KCQix/v6sBhD2EhICGAIqIMODwq7DgsK3w4PCuXRGOMOCwq5Kw4PCi8ODwpDDg8KOUgA="},{"b64Body":"Cg8KCQix/v6sBhD6EhICGAISAhgDGM6Y0i8iAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjpoBkAYKAxiTCSKIBjYwODA2MDQwNTI2MTAxNzE4MDYxMDAxMzYwMDAzOTYwMDBmM2ZlNjA4MDYwNDA1MjYwMDQzNjEwNjEwMDI5NTc2MDAwMzU2MGUwMWM4MDYzOGRkOTgwNDMxNDYxMDAyYjU3ODA2M2FjZWY2MDM3MTQ2MTAwNTk1NzViMDA1YjYxMDA1NzYwMDQ4MDM2MDM2MDIwODExMDE1NjEwMDQxNTc2MDAwODBmZDViODEwMTkwODA4MDM1OTA2MDIwMDE5MDkyOTE5MDUwNTA1MDYxMDBhNzU2NWIwMDViNjEwMGE1NjAwNDgwMzYwMzYwNDA4MTEwMTU2MTAwNmY1NzYwMDA4MGZkNWI4MTAxOTA4MDgwMzU3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNjkwNjAyMDAxOTA5MjkxOTA4MDM1OTA2MDIwMDE5MDkyOTE5MDUwNTA1MDYxMDBmMTU2NWIwMDViMzM3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNjYxMDhmYzgyOTA4MTE1MDI5MDYwNDA1MTYwMDA2MDQwNTE4MDgzMDM4MTg1ODg4OGYxOTM1MDUwNTA1MDE1ODAxNTYxMDBlZDU3M2Q2MDAwODAzZTNkNjAwMGZkNWI1MDUwNTY1YjgxNzNmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmMTY2MTA4ZmM4MjkwODExNTAyOTA2MDQwNTE2MDAwNjA0MDUxODA4MzAzODE4NTg4ODhmMTkzNTA1MDUwNTAxNTgwMTU2MTAxMzc1NzNkNjAwMDgwM2UzZDYwMDBmZDViNTA1MDUwNTZmZWEyNjU2MjdhN2E3MjMxNTgyMDVlYjQwYmI2MjA4MzVmYjc5NjljZGQ3NWRlZDE4ODkyN2Q1YzkyN2Y5M2JhNjc1ZDY1YjljNTIzNDQ0MjQyODc2NDczNmY2YzYzNDMwMDA1MTEwMDMy","b64Record":"CiAIFiocCgwIARAMGgYIgK6ZpA8SDAgBEA8aBgiArpmkDxIw9c2UimoI2rp8M855YPAMSQkjniDzqguvNa9AbrZBirW4RsOMoBlU/W72679eT7fnGgwI7f7+rAYQ5rbrjQIiDwoJCLH+/qwGEPoSEgIYAiogw4PCrsOCwrfDg8K5dEY4w4LCrkrDg8KLw4PCkMODwo5SAA=="},{"b64Body":"Cg8KCQiy/v6sBhD8EhICGAISAhgDGJb7rp0CIgIIeDIgw4PCrsOCwrfDg8K5dEY4w4LCrkrDg8KLw4PCkMODwo5CSAoDGJMJGiISIOgdq2oRDyz4MKmeeCIbRG3YAjfPnuY/fC9cEDO1sXdrIJChDyiQTkIFCIDO2gNSAFoAagtjZWxsYXIgZG9vcg==","b64Record":"CiUIFiIDGJQJKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjD+HY2xv8kL7z0HQ3mk/V1GyhNThaBJODFe2StNUXmbwZA47E/9ysvIhPjtfAuSUdwaCwju/v6sBhCj1PkVIg8KCQiy/v6sBhD8EhICGAIqIMODwq7DgsK3w4PCuXRGOMOCwq5Kw4PCi8ODwpDDg8KOMMDZ4gZCpgUKAxiUCRLxAmCAYEBSYAQ2EGEAKVdgADVg4ByAY43ZgEMUYQArV4BjrO9gNxRhAFlXWwBbYQBXYASANgNgIIEQFWEAQVdgAID9W4EBkICANZBgIAGQkpGQUFBQYQCnVlsAW2EApWAEgDYDYECBEBVhAG9XYACA/VuBAZCAgDVz//////////////////////////8WkGAgAZCSkZCANZBgIAGQkpGQUFBQYQDxVlsAWzNz//////////////////////////8WYQj8gpCBFQKQYEBRYABgQFGAgwOBhYiI8ZNQUFBQFYAVYQDtVz1gAIA+PWAA/VtQUFZbgXP//////////////////////////xZhCPyCkIEVApBgQFFgAGBAUYCDA4GFiIjxk1BQUFAVgBVhATdXPWAAgD49YAD9W1BQUFb+omVienpyMVggXrQLtiCDX7eWnN113tGIkn1ckn+TumddZbnFI0RCQodkc29sY0MABREAMiKAAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAowJoMOgMYlAlKFgoUAAAAAAAAAAAAAAAAAAAAAAAABJRyBwoDGJQJEAFSIQoJCgIYAhCfz8YNCgkKAhhiEICzxQ0KCQoDGJQJEKCcAQ=="},{"b64Body":"Cg8KCQiy/v6sBhD+EhICGAISAhgDGJb7rp0CIgIIeDIgw4PCrsOCwrfDg8K5dEY4w4LCrkrDg8KLw4PCkMODwo5CSAoDGJMJGiISIN9bgOf710npv/HIMnb4ykKuudCmIf4ZISiHi9wmr7deIJChDyiQTkIFCIDO2gNSAFoAagtjZWxsYXIgZG9vcg==","b64Record":"CiUIFiIDGJUJKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjD1vHaQgmYsqJ4i/Bg7ZLnJBO+SGXnT+54AoS6IHLU5zs9QyyWPZScTFzluy6pC1ZcaDAju/v6sBhD2wIWXAiIPCgkIsv7+rAYQ/hISAhgCKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjjDA2eIGQqYFCgMYlQkS8QJggGBAUmAENhBhAClXYAA1YOAcgGON2YBDFGEAK1eAY6zvYDcUYQBZV1sAW2EAV2AEgDYDYCCBEBVhAEFXYACA/VuBAZCAgDWQYCABkJKRkFBQUGEAp1ZbAFthAKVgBIA2A2BAgRAVYQBvV2AAgP1bgQGQgIA1c///////////////////////////FpBgIAGQkpGQgDWQYCABkJKRkFBQUGEA8VZbAFszc///////////////////////////FmEI/IKQgRUCkGBAUWAAYEBRgIMDgYWIiPGTUFBQUBWAFWEA7Vc9YACAPj1gAP1bUFBWW4Fz//////////////////////////8WYQj8gpCBFQKQYEBRYABgQFGAgwOBhYiI8ZNQUFBQFYAVYQE3Vz1gAIA+PWAA/VtQUFBW/qJlYnp6cjFYIF60C7Ygg1+3lpzddd7RiJJ9XJJ/k7pnXWW5xSNEQkKHZHNvbGNDAAURADIigAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKMCaDDoDGJUJShYKFAAAAAAAAAAAAAAAAAAAAAAAAASVcgcKAxiVCRABUiEKCQoCGAIQn8/GDQoJCgIYYhCAs8UNCgkKAxiVCRCgnAE="},{"b64Body":"Cg8KCQiz/v6sBhCAExICGAISAhgDGKqQBSICCHgyIMODwq7DgsK3w4PCuXRGOMOCwq5Kw4PCi8ODwpDDg8KOchkKFwoJCgIYAhD/2cQJCgoKAxiQCRCA2sQJ","b64Record":"CiAIFiocCgwIARAMGgYIgK6ZpA8SDAgBEA8aBgiArpmkDxIwJsRegmAOi4U6FQNtW1NaU//OkG7cHiR9xiy8jZt7CjHTN3sAvz21PfneBUnag007GgsI7/7+rAYQjJq/HyIPCgkIs/7+rAYQgBMSAhgCKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjlIXCgkKAhgCEP/ZxAkKCgoDGJAJEIDaxAk="},{"b64Body":"Cg8KCQiz/v6sBhCIExICGAISAhgDGNWAv6ACIgIIeDIgw4PCrsOCwrfDg8K5dEY4w4LCrkrDg8KLw4PCkMODwo5CigEKAxiSCRoiEiBGM1GHC5vkDX/m+zf4TdxIKS1nfZuWAp34kjlC5LScqiCQoQ8okE5CBQiAztoDSkAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAElAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAASVUgBaAGoLY2VsbGFyIGRvb3I=","b64Record":"CiUIFiIDGJYJKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjCDcQbsyL1RwRyMdiiov9ANY3vshegbuB270UwlGT+SyTMw8IJxqqBy6jse1Abt0HMaDAjv/v6sBhDimdOgAiIPCgkIs/7+rAYQiBMSAhgCKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjjDA2eIGQs0dCgMYlgkSmBtggGBAUmAENhBhAD9XYAA1YOAcgGNVVdfkFGEAQVeAY17PKYoUYQDPV4BjncfbEBRhAP1XgGOpo6Y1FGEBS1dbAFthAM1gBIA2A2CAgRAVYQBXV2AAgP1bgQGQgIA1c///////////////////////////FpBgIAGQkpGQgDVz//////////////////////////8WkGAgAZCSkZCANXP//////////////////////////xaQYCABkJKRkIA1kGAgAZCSkZBQUFBhAXlWWwBbYQD7YASANgNgIIEQFWEA5VdgAID9W4EBkICANZBgIAGQkpGQUFBQYQcTVlsAW2EBSWAEgDYDYECBEBVhARNXYACA/VuBAZCAgDVz//////////////////////////8WkGAgAZCSkZCANZBgIAGQkpGQUFBQYQgvVlsAW2EBd2AEgDYDYCCBEBVhAWFXYACA/VuBAZCAgDWQYCABkJKRkFBQUGEKD1ZbAFuDc///////////////////////////FmEI/IKQgRUCkGBAUWAAYEBRgIMDgYWIiPGTUFBQUBWAFWEBv1c9YACAPj1gAP1bUGAAgJBUkGEBAAqQBHP//////////////////////////xZz//////////////////////////8WY6zvYDeFYAKEgWECCFf+WwRgQFGDY/////8WYOAbgVJgBAGAg3P//////////////////////////xZz//////////////////////////8WgVJgIAGCgVJgIAGSUFBQYABgQFGAgwOBYACHgDsVgBVhAnJXYACA/VtQWvEVgBVhAoZXPWAAgD49YAD9W1BQUFBgAWAAkFSQYQEACpAEc///////////////////////////FnP//////////////////////////xZjrO9gN4VgAoSBYQLTV/5bBGBAUYNj/////xZg4BuBUmAEAYCDc///////////////////////////FnP//////////////////////////xaBUmAgAYKBUmAgAZJQUFBgAGBAUYCDA4FgAIeAOxWAFWEDPVdgAID9W1Ba8RWAFWEDUVc9YACAPj1gAP1bUFBQUIJz//////////////////////////8WYQj8gpCBFQKQYEBRYABgQFGAgwOBhYiI8ZNQUFBQFYAVYQObVz1gAIA+PWAA/VtQYACAkFSQYQEACpAEc///////////////////////////FnP//////////////////////////xZjrO9gN4RgAoSBYQPkV/5bBGBAUYNj/////xZg4BuBUmAEAYCDc///////////////////////////FnP//////////////////////////xaBUmAgAYKBUmAgAZJQUFBgAGBAUYCDA4FgAIeAOxWAFWEETldgAID9W1Ba8RWAFWEEYlc9YACAPj1gAP1bUFBQUGABYACQVJBhAQAKkARz//////////////////////////8Wc///////////////////////////FmOs72A3hGAChIFhBK9X/lsEYEBRg2P/////FmDgG4FSYAQBgINz//////////////////////////8Wc///////////////////////////FoFSYCABgoFSYCABklBQUGAAYEBRgIMDgWAAh4A7FYAVYQUZV2AAgP1bUFrxFYAVYQUtVz1gAIA+PWAA/VtQUFBQgXP//////////////////////////xZhCPyCkIEVApBgQFFgAGBAUYCDA4GFiIjxk1BQUFAVgBVhBXdXPWAAgD49YAD9W1BgAICQVJBhAQAKkARz//////////////////////////8Wc///////////////////////////FmOs72A3g2AChIFhBcBX/lsEYEBRg2P/////FmDgG4FSYAQBgINz//////////////////////////8Wc///////////////////////////FoFSYCABgoFSYCABklBQUGAAYEBRgIMDgWAAh4A7FYAVYQYqV2AAgP1bUFrxFYAVYQY+Vz1gAIA+PWAA/VtQUFBQYAFgAJBUkGEBAAqQBHP//////////////////////////xZz//////////////////////////8WY6zvYDeDYAKEgWEGi1f+WwRgQFGDY/////8WYOAbgVJgBAGAg3P//////////////////////////xZz//////////////////////////8WgVJgIAGCgVJgIAGSUFBQYABgQFGAgwOBYACHgDsVgBVhBvVXYACA/VtQWvEVgBVhBwlXPWAAgD49YAD9W1BQUFBQUFBQVltgAICQVJBhAQAKkARz//////////////////////////8Wc///////////////////////////FmON2YBDgmBAUYJj/////xZg4BuBUmAEAYCCgVJgIAGRUFBgAGBAUYCDA4FgAIeAOxWAFWEHh1dgAID9W1Ba8RWAFWEHm1c9YACAPj1gAP1bUFBQUGABYACQVJBhAQAKkARz//////////////////////////8Wc///////////////////////////FmON2YBDgmBAUYJj/////xZg4BuBUmAEAYCCgVJgIAGRUFBgAGBAUYCDA4FgAIeAOxWAFWEIFFdgAID9W1Ba8RWAFWEIKFc9YACAPj1gAP1bUFBQUFBWW4Fz//////////////////////////8WYQj8gpCBFQKQYEBRYABgQFGAgwOBhYiI8ZNQUFBQFYAVYQh1Vz1gAIA+PWAA/VtQYACAkFSQYQEACpAEc///////////////////////////FnP//////////////////////////xZjrO9gN4NgAoSBYQi+V/5bBGBAUYNj/////xZg4BuBUmAEAYCDc///////////////////////////FnP//////////////////////////xaBUmAgAYKBUmAgAZJQUFBgAGBAUYCDA4FgAIeAOxWAFWEJKFdgAID9W1Ba8RWAFWEJPFc9YACAPj1gAP1bUFBQUGABYACQVJBhAQAKkARz//////////////////////////8Wc///////////////////////////FmOs72A3g2AChIFhCYlX/lsEYEBRg2P/////FmDgG4FSYAQBgINz//////////////////////////8Wc///////////////////////////FoFSYCABgoFSYCABklBQUGAAYEBRgIMDgWAAh4A7FYAVYQnzV2AAgP1bUFrxFYAVYQoHVz1gAIA+PWAA/VtQUFBQUFBWW2AAYGBgAICQVJBhAQAKkARz//////////////////////////8Wc///////////////////////////FoNgQFFgJAGAgoFSYCABkVBQYEBRYCCBgwMDgVKQYEBSf43ZgEMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAe/////////////////////////////////////8ZFmAgggGAUXv/////////////////////////////////////g4GDFheDUlBQUFBgQFGAgoBRkGAgAZCAg4NbYCCDEGELDFeAUYJSYCCCAZFQYCCBAZBQYCCDA5JQYQrpVltgAYNgIANhAQAKA4AZglEWgYRRFoCCF4VSUFBQUFBQkFABkVBQYABgQFGAgwOBhVr0kVBQPYBgAIEUYQtsV2BAUZFQYB8ZYD89ARaCAWBAUj2CUj1gAGAghAE+YQtxVltgYJFQW1CRUJFQYABgYGABYACQVJBhAQAKkARz//////////////////////////8Wc///////////////////////////FoVgQFFgJAGAgoFSYCABkVBQYEBRYCCBgwMDgVKQYEBSf43ZgEMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAe/////////////////////////////////////8ZFmAgggGAUXv/////////////////////////////////////g4GDFheDUlBQUFBgQFGAgoBRkGAgAZCAg4NbYCCDEGEMdFeAUYJSYCCCAZFQYCCBAZBQYCCDA5JQYQxRVltgAYNgIANhAQAKA4AZglEWgYRRFoCCF4VSUFBQUFBQkFABkVBQYABgQFGAgwOBhVr0kVBQPYBgAIEUYQzUV2BAUZFQYB8ZYD89ARaCAWBAUj2CUj1gAGAghAE+YQzZVltgYJFQW1CRUJFQgxWAYQzpV1CBFVsVYQ1cV2BAUX8Iw3mgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIFSYAQBgIBgIAGCgQOCUmAegVJgIAGAf0RlbGVnYXRlIHRyYW5zZmVyIGNhbGwgZmFpbGVkIQAAgVJQYCABkVBQYEBRgJEDkP1bUFBQUFBW/qJlYnp6cjFYIGGdVVJc0X9zmwyybYb30Y8VXqxF+VNw2WTeo8cNsU1uZHNvbGNDAAURADIigAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKMCaDDoDGJYJShYKFAAAAAAAAAAAAAAAAAAAAAAAAASWcgcKAxiWCRABUiEKCQoCGAIQn8/GDQoJCgIYYhCAs8UNCgkKAxiWCRCgnAE="},{"b64Body":"ChAKCQi0/v6sBhCKExIDGJAJEgIYAxjgrLEDIgIIeDIgw4PCrsOCwrfDg8K5dEY4w4LCrkrDg8KLw4PCkMODwo46LwoDGJYJEKCNBiIkqaOmNQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABk","b64Record":"CiUIFiIDGJYJKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjDjVkGWQFih8G79tTmxrfdxMmS+4V3IaU6eTMZYeYptnh1xh2KbbfpvaF15y2BviRQaCwjw/v6sBhDllPsoIhAKCQi0/v6sBhCKExIDGJAJKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjjCA19oCOowCCgMYlgkigAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKIDxBFIhCgkKAhhiEICutQUKCgoDGJAJEO+qtQUKCAoDGJYJEI8D"}]},"whitelistingAliasedContract":{"placeholderNum":1181,"encodedItems":[{"b64Body":"Cg8KCQjC/v6sBhCeFBICGAISAhgDGL/0xiwiAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjooBgQESDAj+zNmwBhC+yu+KAxptCiISILfTmFbKPVx7nH1ht+PCZARbe57BBzln/Cl+NGxnz3JVCiM6IQNn4DEqFA7fN70+V5o1UmC2XpLkirrah3FSyKqKWA6VHAoiEiB9rD2YPfjQbi13T0Yx7Kdux6J1g7FuWLo6dvfTYJmtayoAMgA=","b64Record":"CiUIFhoDGJ4JKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjBNLqBGXVZcK0pkxyvP0vWEi3b1Eobq+tgw9hkVM4KzphGPmchuYLf9yPyfUHQ/1SsaDAj+/v6sBhDNmqKTAyIPCgkIwv7+rAYQnhQSAhgCKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjlIA"},{"b64Body":"Cg8KCQjD/v6sBhCiFBICGAISAhgDGNHBqDMiAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjpoB2gwKAxieCSLSDDYwODA2MDQwNTIzNDgwMTU2MTAwMTA1NzYwMDA4MGZkNWI1MDYxMDMwOTgwNjEwMDIwNjAwMDM5NjAwMGYzZmU2MDgwNjA0MDUyMzQ4MDE1NjEwMDEwNTc2MDAwODBmZDViNTA2MDA0MzYxMDYxMDA0YzU3NjAwMDM1NjBlMDFjODA2MzBhZDhmNGExMTQ2MTAwNTE1NzgwNjMzYWYzMmFiZjE0NjEwMDZmNTc4MDYzOWIxOTI1MWExNDYxMDA5ZjU3ODA2M2U0MzI1MmQ3MTQ2MTAwY2Y1NzViNjAwMDgwZmQ1YjYxMDA1OTYxMDBlYjU2NWI2MDQwNTE2MTAwNjY5MTkwNjEwMjVlNTY1YjYwNDA1MTgwOTEwMzkwZjM1YjYxMDA4OTYwMDQ4MDM2MDM4MTAxOTA2MTAwODQ5MTkwNjEwMjIyNTY1YjYxMDEzZTU2NWI2MDQwNTE2MTAwOTY5MTkwNjEwMjVlNTY1YjYwNDA1MTgwOTEwMzkwZjM1YjYxMDBiOTYwMDQ4MDM2MDM4MTAxOTA2MTAwYjQ5MTkwNjEwMjIyNTY1YjYxMDE5MzU2NWI2MDQwNTE2MTAwYzY5MTkwNjEwMjVlNTY1YjYwNDA1MTgwOTEwMzkwZjM1YjYxMDBlOTYwMDQ4MDM2MDM4MTAxOTA2MTAwZTQ5MTkwNjEwMjIyNTY1YjYxMDFiMzU2NWIwMDViNjAwMDgwNjAwMDMzNzNmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmMTY3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNjgxNTI2MDIwMDE5MDgxNTI2MDIwMDE2MDAwMjA2MDAwOTA1NDkwNjEwMTAwMGE5MDA0NjBmZjE2OTA1MDkwNTY1YjYwMDA4MDYwMDA4MzczZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjE2NzNmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmMTY4MTUyNjAyMDAxOTA4MTUyNjAyMDAxNjAwMDIwNjAwMDkwNTQ5MDYxMDEwMDBhOTAwNDYwZmYxNjkwNTA5MTkwNTA1NjViNjAwMDYwMjA1MjgwNjAwMDUyNjA0MDYwMDAyMDYwMDA5MTUwNTQ5MDYxMDEwMDBhOTAwNDYwZmYxNjgxNTY1YjYwMDE2MDAwODA4MzczZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjE2NzNmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmMTY4MTUyNjAyMDAxOTA4MTUyNjAyMDAxNjAwMDIwNjAwMDYxMDEwMDBhODE1NDgxNjBmZjAyMTkxNjkwODMxNTE1MDIxNzkwNTU1MDUwNTY1YjYwMDA4MTM1OTA1MDYxMDIxYzgxNjEwMmJjNTY1YjkyOTE1MDUwNTY1YjYwMDA2MDIwODI4NDAzMTIxNTYxMDIzODU3NjEwMjM3NjEwMmI3NTY1YjViNjAwMDYxMDI0Njg0ODI4NTAxNjEwMjBkNTY1YjkxNTA1MDkyOTE1MDUwNTY1YjYxMDI1ODgxNjEwMjhiNTY1YjgyNTI1MDUwNTY1YjYwMDA2MDIwODIwMTkwNTA2MTAyNzM2MDAwODMwMTg0NjEwMjRmNTY1YjkyOTE1MDUwNTY1YjYwMDA2MTAyODQ4MjYxMDI5NzU2NWI5MDUwOTE5MDUwNTY1YjYwMDA4MTE1MTU5MDUwOTE5MDUwNTY1YjYwMDA3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmY4MjE2OTA1MDkxOTA1MDU2NWI2MDAwODBmZDViNjEwMmM1ODE2MTAyNzk1NjViODExNDYxMDJkMDU3NjAwMDgwZmQ1YjUwNTZmZWEyNjQ2OTcwNjY3MzU4MjIxMjIwNmE1ZmJjMDAxMGQwY2JiMzQ1YjdkZGRkMzExYTI2M2ViYTUxZGM5ODNiNjQwMDJmNzYxNTM0ZWEyMjY0Nzc2MDY0NzM2ZjZjNjM0MzAwMDgwNzAwMzM=","b64Record":"CiAIFiocCgwIARAMGgYIgK6ZpA8SDAgBEA8aBgiArpmkDxIws1Tn88J4J8ui/il59S+qzRxaI7D9jmXDpJoKMUlzRUV0F51x3UDxD460Yx3ecAREGgwI//7+rAYQhKWLuAEiDwoJCMP+/qwGEKIUEgIYAiogw4PCrsOCwrfDg8K5dEY4w4LCrkrDg8KLw4PCkMODwo5SAA=="},{"b64Body":"Cg8KCQjD/v6sBhCkFBICGAISAhgDGL/0xiwiAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjooBgQESDAj/zNmwBhDJ/dWTAxptCiISIA3JsW7GuO7v54X7xEvJn/ms2adHgCPpbbo6ettlO7WsCiM6IQOsiBI9i2tU658BXA9OSArO+CojTDz8Hz1PqsiCMTbLqQoiEiDk3MrGN219Hxv1Ur206Js3aYIux3cYlf7kXMvtwNTJJCoAMgA=","b64Record":"CiUIFhoDGJ8JKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjBdsKWlbuQkIiVkvpVWjScCd+bvBAdHLp11SxxatptV5qMOvLuowYhhmxLHRQLAsVYaDAj//v6sBhC4u+KcAyIPCgkIw/7+rAYQpBQSAhgCKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjlIA"},{"b64Body":"Cg8KCQjE/v6sBhCoFBICGAISAhgDGKOkijoiAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjpoB6hgKAxifCSLiGDYwODA2MDQwNTIzNDgwMTU2MTAwMTA1NzYwMDA4MGZkNWI1MDYwMDA4MDYwNDA1MTgwNjAyMDAxNjEwMDI0OTA2MTAwYjc1NjViNjAyMDgyMDE4MTAzODI1MjYwMWYxOTYwMWY4MjAxMTY2MDQwNTI1MDkwNTA2MDAwNjA0MDUxNjAyMDAxNjEwMDRiOTA2MTAwZTc1NjViNjA0MDUxNjAyMDgxODMwMzAzODE1MjkwNjA0MDUyODA1MTkwNjAyMDAxMjA5MDUwODA4MjUxNjAyMDg0MDE2MDAwZjU5MjUwODI2MDAwODA2MTAxMDAwYTgxNTQ4MTczZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjAyMTkxNjkwODM3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNjAyMTc5MDU1NTA1MDUwNTA2MTAxMzA1NjViNjEwMjVlODA2MTAzZDM4MzM5MDE5MDU2NWI2MDAwNjEwMGQxNjAwNjgzNjEwMGZjNTY1YjkxNTA2MTAwZGM4MjYxMDEwNzU2NWI2MDA2ODIwMTkwNTA5MTkwNTA1NjViNjAwMDYxMDBmMjgyNjEwMGM0NTY1YjkxNTA4MTkwNTA5MTkwNTA1NjViNjAwMDgxOTA1MDkyOTE1MDUwNTY1YjdmNzA2NTcyNmQ2OTc0MDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDYwMDA4MjAxNTI1MDU2NWI2MTAyOTQ4MDYxMDEzZjYwMDAzOTYwMDBmM2ZlNjA4MDYwNDA1MjM0ODAxNTYxMDAxMDU3NjAwMDgwZmQ1YjUwNjAwNDM2MTA2MTAwMmI1NzYwMDAzNTYwZTAxYzgwNjMzYWYzMmFiZjE0NjEwMDMwNTc1YjYwMDA4MGZkNWI2MTAwNGE2MDA0ODAzNjAzODEwMTkwNjEwMDQ1OTE5MDYxMDEzZjU2NWI2MTAwNjA1NjViNjA0MDUxNjEwMDU3OTE5MDYxMDFkMjU2NWI2MDQwNTE4MDkxMDM5MGYzNWI2MDAwODA2MDAwOTA1NDkwNjEwMTAwMGE5MDA0NzNmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmMTY3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNjYzM2FmMzJhYmY4MzYwNDA1MTgyNjNmZmZmZmZmZjE2NjBlMDFiODE1MjYwMDQwMTYxMDBiYzkxOTA2MTAxYjc1NjViNjAyMDYwNDA1MTgwODMwMzgxNjAwMDg3ODAzYjE1ODAxNTYxMDBkNjU3NjAwMDgwZmQ1YjUwNWFmMTE1ODAxNTYxMDBlYTU3M2Q2MDAwODAzZTNkNjAwMGZkNWI1MDUwNTA1MDYwNDA1MTNkNjAxZjE5NjAxZjgyMDExNjgyMDE4MDYwNDA1MjUwODEwMTkwNjEwMTBlOTE5MDYxMDE2YzU2NWI5MDUwOTE5MDUwNTY1YjYwMDA4MTM1OTA1MDYxMDEyNDgxNjEwMjMwNTY1YjkyOTE1MDUwNTY1YjYwMDA4MTUxOTA1MDYxMDEzOTgxNjEwMjQ3NTY1YjkyOTE1MDUwNTY1YjYwMDA2MDIwODI4NDAzMTIxNTYxMDE1NTU3NjEwMTU0NjEwMjJiNTY1YjViNjAwMDYxMDE2Mzg0ODI4NTAxNjEwMTE1NTY1YjkxNTA1MDkyOTE1MDUwNTY1YjYwMDA2MDIwODI4NDAzMTIxNTYxMDE4MjU3NjEwMTgxNjEwMjJiNTY1YjViNjAwMDYxMDE5MDg0ODI4NTAxNjEwMTJhNTY1YjkxNTA1MDkyOTE1MDUwNTY1YjYxMDFhMjgxNjEwMWVkNTY1YjgyNTI1MDUwNTY1YjYxMDFiMTgxNjEwMWZmNTY1YjgyNTI1MDUwNTY1YjYwMDA2MDIwODIwMTkwNTA2MTAxY2M2MDAwODMwMTg0NjEwMTk5NTY1YjkyOTE1MDUwNTY1YjYwMDA2MDIwODIwMTkwNTA2MTAxZTc2MDAwODMwMTg0NjEwMWE4NTY1YjkyOTE1MDUwNTY1YjYwMDA2MTAxZjg4MjYxMDIwYjU2NWI5MDUwOTE5MDUwNTY1YjYwMDA4MTE1MTU5MDUwOTE5MDUwNTY1YjYwMDA3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmY4MjE2OTA1MDkxOTA1MDU2NWI2MDAwODBmZDViNjEwMjM5ODE2MTAxZWQ1NjViODExNDYxMDI0NDU3NjAwMDgwZmQ1YjUwNTY1YjYxMDI1MDgxNjEwMWZmNTY1YjgxMTQ2MTAyNWI1NzYwMDA4MGZkNWI1MDU2ZmVhMjY0Njk3MDY2NzM1ODIyMTIyMDFjNjJhZWJlM2M5ZDU5NTM1ZjYzMWI3MWQ5MjZlZGZhYzY3YzViYjcxN2Q4Mjc2Y2M3MjMzYTA0ODYwNzI2YzY2NDczNmY2YzYzNDMwMDA4MDcwMDMzNjA4MDYwNDA1MjM0ODAxNTYxMDAxMDU3NjAwMDgwZmQ1YjUwNjEwMjNlODA2MTAwMjA2MDAwMzk2MDAwZjNmZTYwODA2MDQwNTIzNDgwMTU2MTAwMTA1NzYwMDA4MGZkNWI1MDYwMDQzNjEwNjEwMDJiNTc2MDAwMzU2MGUwMWM4MDYzM2FmMzJhYmYxNDYxMDAzMDU3NWI2MDAwODBmZDViNjEwMDRhNjAwNDgwMzYwMzgxMDE5MDYxMDA0NTkxOTA2MTAxMTM1NjViNjEwMDYwNTY1YjYwNDA1MTYxMDA1NzkxOTA2MTAxN2M1NjViNjA0MDUxODA5MTAzOTBmMzViNjAwMDgxNzNmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmMTY2MzBhZDhmNGExNjA0MDUxODE2M2ZmZmZmZmZmMTY2MGUwMWI4MTUyNjAwNDAxNjAyMDYwNDA1MTgwODMwMzgxNjAwMDg3ODAzYjE1ODAxNTYxMDBhYTU3NjAwMDgwZmQ1YjUwNWFmMTE1ODAxNTYxMDBiZTU3M2Q2MDAwODAzZTNkNjAwMGZkNWI1MDUwNTA1MDYwNDA1MTNkNjAxZjE5NjAxZjgyMDExNjgyMDE4MDYwNDA1MjUwODEwMTkwNjEwMGUyOTE5MDYxMDE0MDU2NWI5MDUwOTE5MDUwNTY1YjYwMDA4MTM1OTA1MDYxMDBmODgxNjEwMWRhNTY1YjkyOTE1MDUwNTY1YjYwMDA4MTUxOTA1MDYxMDEwZDgxNjEwMWYxNTY1YjkyOTE1MDUwNTY1YjYwMDA2MDIwODI4NDAzMTIxNTYxMDEyOTU3NjEwMTI4NjEwMWQ1NTY1YjViNjAwMDYxMDEzNzg0ODI4NTAxNjEwMGU5NTY1YjkxNTA1MDkyOTE1MDUwNTY1YjYwMDA2MDIwODI4NDAzMTIxNTYxMDE1NjU3NjEwMTU1NjEwMWQ1NTY1YjViNjAwMDYxMDE2NDg0ODI4NTAxNjEwMGZlNTY1YjkxNTA1MDkyOTE1MDUwNTY1YjYxMDE3NjgxNjEwMWE5NTY1YjgyNTI1MDUwNTY1YjYwMDA2MDIwODIwMTkwNTA2MTAxOTE2MDAwODMwMTg0NjEwMTZkNTY1YjkyOTE1MDUwNTY1YjYwMDA2MTAxYTI4MjYxMDFiNTU2NWI5MDUwOTE5MDUwNTY1YjYwMDA4MTE1MTU5MDUwOTE5MDUwNTY1YjYwMDA3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmY4MjE2OTA1MDkxOTA1MDU2NWI2MDAwODBmZDViNjEwMWUzODE2MTAxOTc1NjViODExNDYxMDFlZTU3NjAwMDgwZmQ1YjUwNTY1YjYxMDFmYTgxNjEwMWE5NTY1YjgxMTQ2MTAyMDU1NzYwMDA4MGZkNWI1MDU2ZmVhMjY0Njk3MDY2NzM1ODIyMTIyMGU0NjQ1ODUzYTQyMDhlMTUzZDFiYjgxYWI1NmZiZDVjYzZjOTlmNWNjZjA4MWY5OWFjNGFmNzA4ZmNiNWE4ZTY2NDczNmY2YzYzNDMwMDA4MDcwMDMz","b64Record":"CiAIFiocCgwIARAMGgYIgK6ZpA8SDAgBEA8aBgiArpmkDxIwe8Td+HKEeTCTCk6WDD4BKEkfzpJsYJdXnjSMtAQOOexHPz8x0jQsGIifx4EDM7CDGgwIgP/+rAYQ3MzIwQEiDwoJCMT+/qwGEKgUEgIYAiogw4PCrsOCwrfDg8K5dEY4w4LCrkrDg8KLw4PCkMODwo5SAA=="},{"b64Body":"Cg8KCQjE/v6sBhCqFBICGAISAhgDGJb7rp0CIgIIeDIgw4PCrsOCwrfDg8K5dEY4w4LCrkrDg8KLw4PCkMODwo5CRQoDGJ4JGiISIJiFUToSzMphjJwYdtD+25B7yu5P9/tIO0MDjuWoG1ZtIICJekIFCIDO2gNSAFoAagtjZWxsYXIgZG9vcg==","b64Record":"CiUIFiIDGKAJKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjCSNgrRn+YgTbx2GohHLbSiIRablEvt2qAtECUbQTBLAH0oUDDnUO9YGa72Mf+DSKQaDAiA//6sBhDY7bSmAyIPCgkIxP7+rAYQqhQSAhgCKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjjCAzJU2Qr4ICgMYoAkSiQZggGBAUjSAFWEAEFdgAID9W1BgBDYQYQBMV2AANWDgHIBjCtj0oRRhAFFXgGM68yq/FGEAb1eAY5sZJRoUYQCfV4Bj5DJS1xRhAM9XW2AAgP1bYQBZYQDrVltgQFFhAGaRkGECXlZbYEBRgJEDkPNbYQCJYASANgOBAZBhAISRkGECIlZbYQE+VltgQFFhAJaRkGECXlZbYEBRgJEDkPNbYQC5YASANgOBAZBhALSRkGECIlZbYQGTVltgQFFhAMaRkGECXlZbYEBRgJEDkPNbYQDpYASANgOBAZBhAOSRkGECIlZbYQGzVlsAW2AAgGAAM3P//////////////////////////xZz//////////////////////////8WgVJgIAGQgVJgIAFgACBgAJBUkGEBAAqQBGD/FpBQkFZbYACAYACDc///////////////////////////FnP//////////////////////////xaBUmAgAZCBUmAgAWAAIGAAkFSQYQEACpAEYP8WkFCRkFBWW2AAYCBSgGAAUmBAYAAgYACRUFSQYQEACpAEYP8WgVZbYAFgAICDc///////////////////////////FnP//////////////////////////xaBUmAgAZCBUmAgAWAAIGAAYQEACoFUgWD/AhkWkIMVFQIXkFVQUFZbYACBNZBQYQIcgWECvFZbkpFQUFZbYABgIIKEAxIVYQI4V2ECN2ECt1ZbW2AAYQJGhIKFAWECDVZbkVBQkpFQUFZbYQJYgWECi1ZbglJQUFZbYABgIIIBkFBhAnNgAIMBhGECT1ZbkpFQUFZbYABhAoSCYQKXVluQUJGQUFZbYACBFRWQUJGQUFZbYABz//////////////////////////+CFpBQkZBQVltgAID9W2ECxYFhAnlWW4EUYQLQV2AAgP1bUFb+omRpcGZzWCISIGpfvAAQ0MuzRbfd3TEaJj66UdyYO2QAL3YVNOoiZHdgZHNvbGNDAAgHADMigAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKIDUYToDGKAJShYKFAAAAAAAAAAAAAAAAAAAAAAAAASgcgcKAxigCRABUhYKCQoCGAIQ/5erbAoJCgIYYhCAmKts"},{"b64Body":"Cg8KCQjF/v6sBhCsFBICGAISAhgDGJb7rp0CIgIIeDIgw4PCrsOCwrfDg8K5dEY4w4LCrkrDg8KLw4PCkMODwo5CRQoDGJ8JGiISIHWZQ8kz0OCsPweppYeVF5S7t5Q/Km0J5BHEnu6tHr+5IICJekIFCIDO2gNSAFoAagtjZWxsYXIgZG9vcg==","b64Record":"CiUIFiIDGKEJKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjCrYUVoxEIkWrsGb2RHTi4s6XnpJDirelR2w6/LX67qZiNGyaOOwXC+USxPIUslLisaDAiB//6sBhDqpq2uASIPCgkIxf7+rAYQrBQSAhgCKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjjCAzJU2QtcHCgMYoQkSlAVggGBAUjSAFWEAEFdgAID9W1BgBDYQYQArV2AANWDgHIBjOvMqvxRhADBXW2AAgP1bYQBKYASANgOBAZBhAEWRkGEBP1ZbYQBgVltgQFFhAFeRkGEB0lZbYEBRgJEDkPNbYACAYACQVJBhAQAKkARz//////////////////////////8Wc///////////////////////////FmM68yq/g2BAUYJj/////xZg4BuBUmAEAWEAvJGQYQG3VltgIGBAUYCDA4FgAIeAOxWAFWEA1ldgAID9W1Ba8RWAFWEA6lc9YACAPj1gAP1bUFBQUGBAUT1gHxlgH4IBFoIBgGBAUlCBAZBhAQ6RkGEBbFZbkFCRkFBWW2AAgTWQUGEBJIFhAjBWW5KRUFBWW2AAgVGQUGEBOYFhAkdWW5KRUFBWW2AAYCCChAMSFWEBVVdhAVRhAitWW1tgAGEBY4SChQFhARVWW5FQUJKRUFBWW2AAYCCChAMSFWEBgldhAYFhAitWW1tgAGEBkISChQFhASpWW5FQUJKRUFBWW2EBooFhAe1WW4JSUFBWW2EBsYFhAf9WW4JSUFBWW2AAYCCCAZBQYQHMYACDAYRhAZlWW5KRUFBWW2AAYCCCAZBQYQHnYACDAYRhAahWW5KRUFBWW2AAYQH4gmECC1ZbkFCRkFBWW2AAgRUVkFCRkFBWW2AAc///////////////////////////ghaQUJGQUFZbYACA/VthAjmBYQHtVluBFGECRFdgAID9W1BWW2ECUIFhAf9WW4EUYQJbV2AAgP1bUFb+omRpcGZzWCISIBxirr48nVlTX2Mbcdkm7frGfFu3F9gnbMcjOgSGBybGZHNvbGNDAAgHADMigAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKIDUYToDGKEJOgMYoglKFgoUAAAAAAAAAAAAAAAAAAAAAAAABKFyBwoDGKEJEAJyBwoDGKIJEAFSFgoJCgIYAhD/l6tsCgkKAhhiEICYq2w="},{"b64Body":"ChEKCQjF/v6sBhCsFBICGAIgAUI4GiISIHWZQ8kz0OCsPweppYeVF5S7t5Q/Km0J5BHEnu6tHr+5QgUIgM7aA2oLY2VsbGFyIGRvb3I=","b64Record":"CgcIFiIDGKIJEjBXJWNCGb3t22nLuuUZYUO9YB2yD9BWnVrvJgKLdzqbNObcSRo+V4PV7l3wq8qilZkaDAiB//6sBhDrpq2uASIRCgkIxf7+rAYQrBQSAhgCIAFCHQoDGKIJShYKFLLmJaHxTbIdUZogrV9kHfHOgEYTUgB6DAiB//6sBhDqpq2uAQ=="},{"b64Body":"Cg8KCQjF/v6sBhCyFBICGAISAhgDGOCssQMiAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjjovCgMYoAkQoI0GIiTkMlLXAAAAAAAAAAAAAAAAsuYlofFNsh1RmiCtX2Qd8c6ARhM=","b64Record":"CiUIFiIDGKAJKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjCuMXlJrH+hdQkAOGzaVmgA6SbfmsAo+FjKjzlBEes5q28yRtmPFnuzTkwVJXJm0fIaDAiB//6sBhCI4YCwAyIPCgkIxf7+rAYQshQSAhgCKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjjCA19oCOowCCgMYoAkigAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKIDxBFIWCgkKAhgCEP+ttQUKCQoCGGIQgK61BQ=="},{"b64Body":"Cg8KCQjG/v6sBhC0FBICGAISAhgDGOCssQMiAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjjovCgMYogkQoI0GIiQ68yq/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABKA=","b64Record":"CiUIFiIDGKIJKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjDLLmc/8HiNYf3aTX9wClteeLyryFJpBxp64NWy4jWPLNIbSedNN4h809fcttCegDgaDAiC//6sBhCjmPm3ASIPCgkIxv7+rAYQtBQSAhgCKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjjCA19oCOq4CCgMYogkSIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABIoACAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACiA8QRSFgoJCgIYAhD/rbUFCgkKAhhiEICutQU="},{"b64Body":"Cg8KCQjG/v6sBhC2FBICGAISAhgDGOCssQMiAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjjovCgMYoQkQoI0GIiQ68yq/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABKA=","b64Record":"CiUIFiIDGKEJKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjC2DDrWv3Yp3pH14j04OIT+P8PZs3DmfO1KqZyFlZdov/Ag6sSjUfRpdFj4RoQYPtMaDAiC//6sBhDwxMq5AyIPCgkIxv7+rAYQthQSAhgCKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjjCA19oCOq4CCgMYoQkSIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABIoACAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACiA8QRSFgoJCgIYAhD/rbUFCgkKAhhiEICutQU="}]},"cannotUseMirrorAddressOfAliasedContractInPrecompileMethod":{"placeholderNum":1187,"encodedItems":[{"b64Body":"Cg8KCQjL/v6sBhDOFBICGAISAhgDGPuV9hQiAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjloxCiISICFJ6tu6A5xUMrASFOLmw7D2KZZKttFJQKtQWynA+fR3EICU69wDSgUIgM7aAw==","b64Record":"CiUIFhIDGKQJKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjCWudZxm+wg6mu7YD6TM77Ipu/+Gmt8NGVCdbU7HAKtA8uhqa0nuNga2WbimWoPCFIaDAiH//6sBhCDrrm9AiIPCgkIy/7+rAYQzhQSAhgCKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjlIZCgoKAhgCEP+n1rkHCgsKAxikCRCAqNa5Bw=="},{"b64Body":"Cg8KCQjM/v6sBhDQFBICGAISAhgDGL/0xiwiAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjooBgAESCwiIzdmwBhCgwKhGGm0KIhIg3a1QQH+AFBlovb7BcMOLVTKpybHBnPXya7fjxv1QtfIKIzohA5e2zHdCm6biI6oKd2lgJJqN2Ag4hFEMK1gFtJKN8vtTCiISIIqbMteS53/qraUfPdKPl9hunaJkMjX7tedwFy3c8hG+KgAyAA==","b64Record":"CiUIFhoDGKUJKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjButk9v+HUk9FW6i+G1DhWk1mtayIe4gB+qQsGn5orA0zQGOwzFhAf6Aju5iC1y03gaCwiI//6sBhC/+I1iIg8KCQjM/v6sBhDQFBICGAIqIMODwq7DgsK3w4PCuXRGOMOCwq5Kw4PCi8ODwpDDg8KOUgA="},{"b64Body":"Cg8KCQjM/v6sBhDUFBICGAISAhgDGJPsjj4iAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjpoBiCAKAxilCSKAIDB4NjA4MDYwNDA1MjM0ODAxNTYxMDAxMDU3NjAwMDgwZmQ1YjUwNjAwMDgwNjA0MDUxODA2MDIwMDE2MTAwMjQ5MDYxMDBiNzU2NWI2MDIwODIwMTgxMDM4MjUyNjAxZjE5NjAxZjgyMDExNjYwNDA1MjUwOTA1MDYwMDA2MDQwNTE2MDIwMDE2MTAwNGI5MDYxMDExYjU2NWI2MDQwNTE2MDIwODE4MzAzMDM4MTUyOTA2MDQwNTI4MDUxOTA2MDIwMDEyMDkwNTA4MDgyNTE2MDIwODQwMTYwMDBmNTkyNTA4MjYwMDA4MDYxMDEwMDBhODE1NDgxNzNmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmMDIxOTE2OTA4MzczZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjE2MDIxNzkwNTU1MDUwNTA1MDYxMDEzMDU2NWI2MTA1MWQ4MDYxMDMyZDgzMzkwMTkwNTY1YjYwMDA4MTkwNTA5MjkxNTA1MDU2NWI3ZjcwNjU3MjZkNjk3NDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDA2MDAwODIwMTUyNTA1NjViNjAwMDYxMDEwNTYwMDY4MzYxMDBjNDU2NWI5MTUwNjEwMTEwODI2MTAwY2Y1NjViNjAwNjgyMDE5MDUwOTE5MDUwNTY1YjYwMDA2MTAxMjY4MjYxMDBmODU2NWI5MTUwODE5MDUwOTE5MDUwNTY1YjYxMDFlZTgwNjEwMTNmNjAwMDM5NjAwMGYzZmU2MDgwNjA0MDUyMzQ4MDE1NjEwMDEwNTc2MDAwODBmZDViNTA2MDA0MzYxMDYxMDAyYjU3NjAwMDM1NjBlMDFjODA2M2JmNDBjYmE5MTQ2MTAwMzA1NzViNjAwMDgwZmQ1YjYxMDA0YTYwMDQ4MDM2MDM4MTAxOTA2MTAwNDU5MTkwNjEwMTQwNTY1YjYxMDA0YzU2NWIwMDViNjAwMDgwNTQ5MDYxMDEwMDBhOTAwNDczZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjE2NzNmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmMTY2M2JmNDBjYmE5ODM4MzYwNDA1MTgzNjNmZmZmZmZmZjE2NjBlMDFiODE1MjYwMDQwMTYxMDBhNzkyOTE5MDYxMDE4ZjU2NWI2MDAwNjA0MDUxODA4MzAzODE2MDAwODc4MDNiMTU4MDE1NjEwMGMxNTc2MDAwODBmZDViNTA1YWYxMTU4MDE1NjEwMGQ1NTczZDYwMDA4MDNlM2Q2MDAwZmQ1YjUwNTA1MDUwNTA1MDU2NWI2MDAwODBmZDViNjAwMDczZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjgyMTY5MDUwOTE5MDUwNTY1YjYwMDA2MTAxMGQ4MjYxMDBlMjU2NWI5MDUwOTE5MDUwNTY1YjYxMDExZDgxNjEwMTAyNTY1YjgxMTQ2MTAxMjg1NzYwMDA4MGZkNWI1MDU2NWI2MDAwODEzNTkwNTA2MTAxM2E4MTYxMDExNDU2NWI5MjkxNTA1MDU2NWI2MDAwODA2MDQwODM4NTAzMTIxNTYxMDE1NzU3NjEwMTU2NjEwMGRkNTY1YjViNjAwMDYxMDE2NTg1ODI4NjAxNjEwMTJiNTY1YjkyNTA1MDYwMjA2MTAxNzY4NTgyODYwMTYxMDEyYjU2NWI5MTUwNTA5MjUwOTI5MDUwNTY1YjYxMDE4OTgxNjEwMTAyNTY1YjgyNTI1MDUwNTY1YjYwMDA2MDQwODIwMTkwNTA2MTAxYTQ2MDAwODMwMTg1NjEwMTgwNTY1YjYxMDFiMTYwMjA4MzAxODQ2MTAxODA1NjViOTM5MjUwNTA1MDU2ZmVhMjY0Njk3MDY2NzM1ODIyMTIyMGNhOGVmNjU1OTU3ZWNmM2Y2YjdmZjBiN2E1MWRhYzNhNzVjMzExYTQ5YzY2NDk0NjYxNGY5ODExNzEzZjkwMWM2NDczNmY2YzYzNDMwMDA4MGMwMDMzNjA4MDYwNDA1MjM0ODAxNTYxMDAxMDU3NjAwMDgwZmQ1YjUwNjEwNGZkODA2MTAwMjA2MDAwMzk2MDAwZjNmZTYwODA2MDQwNTIzNDgwMTU2MTAwMTA1NzYwMDA4MGZkNWI1MDYwMDQzNjEwNjEwMDM2NTc2MDAwMzU2MGUwMWM4MDYzM2FmMzJhYmYxNDYxMDAzYjU3ODA2M2JmNDBjYmE5MTQ2MTAwNmI1NzViNjAwMDgwZmQ1YjYxMDA1NTYwMDQ4MDM2MDM4MTAxOTA2MTAwNTA5MTkwNjEwMjljNTY1YjYxMDA4NzU2NWI2MDQwNTE2MTAwNjI5MTkwNjEwMmU0NTY1YjYwNDA1MTgwOTEwMzkwZjM1YjYxMDA4NTYwMDQ4MDM2MDM4MTAxOTA2MTAwODA5MTkwNjEwMmZmNTY1YjYxMDEwMTU2NWIwMDViNjAwMDgxNzNmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmMTY2MzYxMWI0MDk1NjA0MDUxODE2M2ZmZmZmZmZmMTY2MGUwMWI4MTUyNjAwNDAxNjAyMDYwNDA1MTgwODMwMzgxNjAwMDg3NWFmMTE1ODAxNTYxMDBkNjU3M2Q2MDAwODAzZTNkNjAwMGZkNWI1MDUwNTA1MDYwNDA1MTNkNjAxZjE5NjAxZjgyMDExNjgyMDE4MDYwNDA1MjUwODEwMTkwNjEwMGZhOTE5MDYxMDM2YjU2NWI5MDUwOTE5MDUwNTY1YjYwMDA2MTAxMGQ4MzgzNjEwMTIxNTY1YjkwNTA2MDE2ODExNDYxMDExYzU3NjAwMDgwZmQ1YjUwNTA1MDU2NWI2MDAwODA2MDAwNjEwMTY3NzNmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmMTY2MzQ5MTQ2YmRlNjBlMDFiODY4NjYwNDA1MTYwMjQwMTYxMDE1YTkyOTE5MDYxMDNhNzU2NWI2MDQwNTE2MDIwODE4MzAzMDM4MTUyOTA2MDQwNTI5MDdiZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxOTE2NjAyMDgyMDE4MDUxN2JmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjgzODE4MzE2MTc4MzUyNTA1MDUwNTA2MDQwNTE2MTAxYzQ5MTkwNjEwNDRhNTY1YjYwMDA2MDQwNTE4MDgzMDM4MTYwMDA4NjVhZjE5MTUwNTAzZDgwNjAwMDgxMTQ2MTAyMDE1NzYwNDA1MTkxNTA2MDFmMTk2MDNmM2QwMTE2ODIwMTYwNDA1MjNkODI1MjNkNjAwMDYwMjA4NDAxM2U2MTAyMDY1NjViNjA2MDkxNTA1YjUwOTE1MDkxNTA4MTYxMDIxNzU3NjAxNTYxMDIyYzU2NWI4MDgwNjAyMDAxOTA1MTgxMDE5MDYxMDIyYjkxOTA2MTA0OWE1NjViNWI2MDAzMGI5MjUwNTA1MDkyOTE1MDUwNTY1YjYwMDA4MGZkNWI2MDAwNzNmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmODIxNjkwNTA5MTkwNTA1NjViNjAwMDYxMDI2OTgyNjEwMjNlNTY1YjkwNTA5MTkwNTA1NjViNjEwMjc5ODE2MTAyNWU1NjViODExNDYxMDI4NDU3NjAwMDgwZmQ1YjUwNTY1YjYwMDA4MTM1OTA1MDYxMDI5NjgxNjEwMjcwNTY1YjkyOTE1MDUwNTY1YjYwMDA2MDIwODI4NDAzMTIxNTYxMDJiMjU3NjEwMmIxNjEwMjM5NTY1YjViNjAwMDYxMDJjMDg0ODI4NTAxNjEwMjg3NTY1YjkxNTA1MDkyOTE1MDUwNTY1YjYwMDA4MTE1MTU5MDUwOTE5MDUwNTY1YjYxMDJkZTgxNjEwMmM5NTY1YjgyNTI1MDUwNTY1YjYwMDA2MDIwODIwMTkwNTA2MTAyZjk2MDAwODMwMTg0NjEwMmQ1NTY1YjkyOTE1MDUwNTY1YjYwMDA4MDYwNDA4Mzg1MDMxMjE1NjEwMzE2NTc2MTAzMTU2MTAyMzk1NjViNWI2MDAwNjEwMzI0ODU4Mjg2MDE2MTAyODc1NjViOTI1MDUwNjAyMDYxMDMzNTg1ODI4NjAxNjEwMjg3NTY1YjkxNTA1MDkyNTA5MjkwNTA1NjViNjEwMzQ4ODE2MTAyYzk1NjViODExNDYxMDM1MzU3NjAwMDgwZmQ1YjUwNTY1YjYwMDA4MTUxOTA1MDYxMDM2NTgxNjEwMzNmNTY1YjkyOTE1MDUwNTY1YjYwMDA2MDIwODI4NDAzMTIxNTYxMDM4MTU3NjEwMzgwNjEwMjM5NTY1YjViNjAwMDYxMDM4Zjg0ODI4NTAxNjEwMzU2NTY1YjkxNTA1MDkyOTE1MDUwNTY1YjYxMDNhMTgxNjEwMjVlNTY1YjgyNTI1MDUwNTY1YjYwMDA2MDQwODIwMTkwNTA2MTAzYmM2MDAwODMwMTg1NjEwMzk4NTY1YjYxMDNjOTYwMjA4MzAxODQ2MTAzOTg1NjViOTM5MjUwNTA1MDU2NWI2MDAwODE1MTkwNTA5MTkwNTA1NjViNjAwMDgxOTA1MDkyOTE1MDUwNTY1YjYwMDA1YjgzODExMDE1NjEwNDA0NTc4MDgyMDE1MTgxODQwMTUyNjAyMDgxMDE5MDUwNjEwM2U5NTY1YjgzODExMTE1NjEwNDEzNTc2MDAwODQ4NDAxNTI1YjUwNTA1MDUwNTY1YjYwMDA2MTA0MjQ4MjYxMDNkMDU2NWI2MTA0MmU4MTg1NjEwM2RiNTY1YjkzNTA2MTA0M2U4MTg1NjAyMDg2MDE2MTAzZTY1NjViODA4NDAxOTE1MDUwOTI5MTUwNTA1NjViNjAwMDYxMDQ1NjgyODQ2MTA0MTk1NjViOTE1MDgxOTA1MDkyOTE1MDUwNTY1YjYwMDA4MTYwMDMwYjkwNTA5MTkwNTA1NjViNjEwNDc3ODE2MTA0NjE1NjViODExNDYxMDQ4MjU3NjAwMDgwZmQ1YjUwNTY1YjYwMDA4MTUxOTA1MDYxMDQ5NDgxNjEwNDZlNTY1YjkyOTE1MDUwNTY1YjYwMDA2MDIwODI4NDAzMTIxNTYxMDRiMDU3NjEwNGFmNjEwMjM5NTY1YjViNjA=","b64Record":"CiAIFiocCgwIARAMGgYIgK6ZpA8SDAgBEA8aBgiArpmkDxIwUe5A5qWNyRghONxuToP+RMDSXFzsfQEo17OLVTbD1fV1PYTEoa6EwR1cEwTnC7KbGgwIiP/+rAYQm4noxgIiDwoJCMz+/qwGENQUEgIYAiogw4PCrsOCwrfDg8K5dEY4w4LCrkrDg8KLw4PCkMODwo5SAA=="},{"b64Body":"Cg8KCQjN/v6sBhDaFBICGAISAhgDGJ2Lwy0iAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjoIBngESAxilCSKWATAwNjEwNGJlODQ4Mjg1MDE2MTA0ODU1NjViOTE1MDUwOTI5MTUwNTA1NmZlYTI2NDY5NzA2NjczNTgyMjEyMjA0OGVlZWQ2NmExMTJkMWZlYjI1NGFlMzFjOTI0ZWIxNDFkMDUxZjVjZTZmNjkxNGYzY2NlMWI0MjMzYWY5M2RhNjQ3MzZmNmM2MzQzMDAwODBjMDAzMw==","b64Record":"CiAIFiocCgwIARAMGgYIgK6ZpA8SDAgBEA8aBgiArpmkDxIw7+FkKF7AUq1nGjyuVX833dSkZ07L0jL3MXWdZ9Exbw97uqFtb5cKyyM39zS8N9g+GgsIif/+rAYQvt7+TiIPCgkIzf7+rAYQ2hQSAhgCKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjlIA"},{"b64Body":"Cg8KCQjN/v6sBhDcFBICGAISAhgDGJb7rp0CIgIIeDIgw4PCrsOCwrfDg8K5dEY4w4LCrkrDg8KLw4PCkMODwo5CRQoDGKUJGiISIGNm8FoCahFrv31m2H1jGd5/FdZ2If5RVmzOlno531KuIICJekIFCIDO2gNSAFoAagtjZWxsYXIgZG9vcg==","b64Record":"CiUIFiIDGKYJKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjCZ/T2DUamLFaQePL5BVjOPkXGk2eycS9W5dySJBRilZCRzGxwoXktj89Nl6m/jDVIaDAiJ//6sBhDR9NDQAiIPCgkIzf7+rAYQ3BQSAhgCKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjjCAzJU2QrEGCgMYpgkS7gNggGBAUjSAFWEAEFdgAID9W1BgBDYQYQArV2AANWDgHIBjv0DLqRRhADBXW2AAgP1bYQBKYASANgOBAZBhAEWRkGEBQFZbYQBMVlsAW2AAgFSQYQEACpAEc///////////////////////////FnP//////////////////////////xZjv0DLqYODYEBRg2P/////FmDgG4FSYAQBYQCnkpGQYQGPVltgAGBAUYCDA4FgAIeAOxWAFWEAwVdgAID9W1Ba8RWAFWEA1Vc9YACAPj1gAP1bUFBQUFBQVltgAID9W2AAc///////////////////////////ghaQUJGQUFZbYABhAQ2CYQDiVluQUJGQUFZbYQEdgWEBAlZbgRRhAShXYACA/VtQVltgAIE1kFBhATqBYQEUVluSkVBQVltgAIBgQIOFAxIVYQFXV2EBVmEA3VZbW2AAYQFlhYKGAWEBK1ZbklBQYCBhAXaFgoYBYQErVluRUFCSUJKQUFZbYQGJgWEBAlZbglJQUFZbYABgQIIBkFBhAaRgAIMBhWEBgFZbYQGxYCCDAYRhAYBWW5OSUFBQVv6iZGlwZnNYIhIgyo72VZV+zz9rf/C3pR2sOnXDEaScZklGYU+YEXE/kBxkc29sY0MACAwAMyKAAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAogNRhOgMYpgk6AxinCUoWChQAAAAAAAAAAAAAAAAAAAAAAAAEpnIHCgMYpgkQAnIHCgMYpwkQAVIWCgkKAhgCEP+Xq2wKCQoCGGIQgJirbA=="},{"b64Body":"ChEKCQjN/v6sBhDcFBICGAIgAUI4GiISIGNm8FoCahFrv31m2H1jGd5/FdZ2If5RVmzOlno531KuQgUIgM7aA2oLY2VsbGFyIGRvb3I=","b64Record":"CgcIFiIDGKcJEjBLP6BTqHTfU8skj4NlR485D7pFQhiV942mMDvtWJaQWCCawrZXBhGKWxA0APNkOgsaDAiJ//6sBhDS9NDQAiIRCgkIzf7+rAYQ3BQSAhgCIAFCHQoDGKcJShYKFF0vGaLGySh+cHJVCfJ3HRfJROShUgB6DAiJ//6sBhDR9NDQAg=="},{"b64Body":"Cg8KCQjO/v6sBhDiFBICGAISAhgDGKWr3ugCIgIIeDIgw4PCrsOCwrfDg8K5dEY4w4LCrkrDg8KLw4PCkMODwo7qASYKBlRva2VuQRIITkFRT0xaUUIgZCoDGKQJagsIis3ZsAYQnJOfVA==","b64Record":"CiUIFiocCgwIARAMGgYIgK6ZpA8SDAgBEA8aBgiArpmkD1IDGKgJEjAzcVXmC0V8lP3fErp6keTZtw2gvoeJgguW2SdYqGV+a0j00JWGddEr5pI7CaU1iuYaCwiK//6sBhCa9cBYIg8KCQjO/v6sBhDiFBICGAIqIMODwq7DgsK3w4PCuXRGOMOCwq5Kw4PCi8ODwpDDg8KOUgBaDwoDGKgJEggKAxikCRDIAXIKCgMYqAkSAxikCQ=="},{"b64Body":"Cg8KCQjO/v6sBhDkFBICGAISAhgDGID/2kMiAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjjpPCgMYpgkQgIl6IkS/QMupAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABKcAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEqA==","b64Record":"CiUIISIDGKYJKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjCu2UurkAele6bgkWfHLjs1GPDhQcO8IoS1NlLvA2wE4LtcjY5HFZcK7WK3MGH+s5caDAiK//6sBhDB+YbaAiIPCgkIzv7+rAYQ5BQSAhgCKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjjCAzJU2OggaAjB4KIDUYVIWCgkKAhgCEP+Xq2wKCQoCGGIQgJirbA=="},{"b64Body":"Cg8KCQjP/v6sBhDuFBICGAISAhgDGID/2kMiAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjjpPCgMYpgkQgIl6IkS/QMupAAAAAAAAAAAAAAAAXS8ZosbJKH5wclUJ8ncdF8lE5KEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEqA==","b64Record":"CiUIFiIDGKYJKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjD8a9T2pCCq9BZ/VIssJ8pVFRFdr2weF8qkDoUQhZflNZJwo6679OUJvIOcJqIo9fAaCwiL//6sBhCrrodiIg8KCQjP/v6sBhDuFBICGAIqIMODwq7DgsK3w4PCuXRGOMOCwq5Kw4PCi8ODwpDDg8KOMIDMlTY6jAIKAximCSKAAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAogNRhUhYKCQoCGAIQ/5erbAoJCgIYYhCAmKts"},{"b64Body":"ChEKCQjP/v6sBhDuFBICGAIgAcICCgoDGKcJEgMYqAk=","b64Record":"CgIIFhIwWhk/BYrEGG/5pzFLMKLxA2rgURvaM3LEzPn9irej0RdB1S1GE5KYWuuHXCV1DwWWGgsIi//+rAYQrK6HYiIRCgkIz/7+rAYQ7hQSAhgCIAE6egoDGOcCEiAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFiji/SpQs7J0YkRJFGveAAAAAAAAAAAAAAAAXS8ZosbJKH5wclUJ8ncdF8lE5KEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEqGoDGKcJUgB6CwiL//6sBhCrrodi"}]},"ExchangeRatePrecompileWorks":{"placeholderNum":1193,"encodedItems":[{"b64Body":"Cg8KCQjT/v6sBhD+FBICGAISAhgDGP7LzSwiAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjooBjwESDAiPzdmwBhC3nPvLAxptCiISIOmIYQt8xPixuoSWVBB1MpDgGC9EvP6iAeQ19xPxr9IKCiM6IQNAbmGnMrTkF/qQjIcm+U+R28KQTbtIJN+u5JlTyppG8goiEiDZogygdYelK1/ZlBgOMx2bb0aVGQay4flmKy1aPTw3FyIMSGVsbG8gV29ybGQhKgAyAA==","b64Record":"CiUIFhoDGKoJKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjBBaLhPtKAM2Dv4LkesNt/zwgyviA/qJb+0+HWLg6sbkNKtV95/Vh64jpeIc1M3T4waCwiQ//6sBhDS2OkCIg8KCQjT/v6sBhD+FBICGAIqIMODwq7DgsK3w4PCuXRGOMOCwq5Kw4PCi8ODwpDDg8KOUgA="},{"b64Body":"Cg8KCQjU/v6sBhCCFRICGAISAhgDGJ+grDoiAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjpoBqBkKAxiqCSKgGTYwODA2MDQwNTIzNDgwMTU2MTAwMTA1NzYwMDA4MGZkNWI1MDYwNDA1MTYxMDY1MDM4MDM4MDYxMDY1MDgzMzk4MTgxMDE2MDQwNTI4MTAxOTA2MTAwMzI5MTkwNjEwMDdhNTY1YjgwNjAwMDgxOTA1NTUwNTA2MTAwYTc1NjViNjAwMDgwZmQ1YjYwMDA4MTkwNTA5MTkwNTA1NjViNjEwMDU3ODE2MTAwNDQ1NjViODExNDYxMDA2MjU3NjAwMDgwZmQ1YjUwNTY1YjYwMDA4MTUxOTA1MDYxMDA3NDgxNjEwMDRlNTY1YjkyOTE1MDUwNTY1YjYwMDA2MDIwODI4NDAzMTIxNTYxMDA5MDU3NjEwMDhmNjEwMDNmNTY1YjViNjAwMDYxMDA5ZTg0ODI4NTAxNjEwMDY1NTY1YjkxNTA1MDkyOTE1MDUwNTY1YjYxMDU5YTgwNjEwMGI2NjAwMDM5NjAwMGYzZmU2MDgwNjA0MDUyNjAwNDM2MTA2MTAwMzQ1NzYwMDAzNTYwZTAxYzgwNjMwM2I2MzZlNjE0NjEwMDM5NTc4MDYzODlkZWExZDAxNDYxMDA0MzU3ODA2M2JmZjRmNTRmMTQ2MTAwNGQ1NzViNjAwMDgwZmQ1YjYxMDA0MTYxMDA2YjU2NWIwMDViNjEwMDRiNjEwMTU0NTY1YjAwNWI2MTAwNTU2MTAxOGE1NjViNjA0MDUxNjEwMDYyOTE5MDYxMDNkMTU2NWI2MDQwNTE4MDkxMDM5MGYzNWI2MDAwODA2MTAxNjg3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNjYzYmZmNGY1NGY2MGUwMWI2MDQwNTE2MDI0MDE2MDQwNTE2MDIwODE4MzAzMDM4MTUyOTA2MDQwNTI5MDdiZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxOTE2NjAyMDgyMDE4MDUxN2JmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjgzODE4MzE2MTc4MzUyNTA1MDUwNTA2MDQwNTE2MTAwZmY5MTkwNjEwNDY2NTY1YjYwMDA2MDQwNTE4MDgzMDM4MTYwMDA4NjVhZjE5MTUwNTAzZDgwNjAwMDgxMTQ2MTAxM2M1NzYwNDA1MTkxNTA2MDFmMTk2MDNmM2QwMTE2ODIwMTYwNDA1MjNkODI1MjNkNjAwMDYwMjA4NDAxM2U2MTAxNDE1NjViNjA2MDkxNTA1YjUwOTE1MDkxNTA4MTYxMDE1MDU3NjAwMDgwZmQ1YjUwNTA1NjViNjAwMDU0NjAwMDYzMDVmNWUxMDA4MjYxMDE2OTkxOTA2MTA0YWM1NjViOTA1MDYwMDA2MTAxNzY4MjYxMDE5YTU2NWI5MDUwODAzNDEwMTU2MTAxODU1NzYwMDA4MGZkNWI1MDUwNTA1NjViNjAwMDYxMDE5NTM0NjEwMmE5NTY1YjkwNTA5MDU2NWI2MDAwODA2MDAwNjEwMTY4NzNmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmMTY2MzJlM2NmZjZhNjBlMDFiODU2MDQwNTE2MDI0MDE2MTAxZDE5MTkwNjEwM2QxNTY1YjYwNDA1MTYwMjA4MTgzMDMwMzgxNTI5MDYwNDA1MjkwN2JmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjE5MTY2MDIwODIwMTgwNTE3YmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmODM4MTgzMTYxNzgzNTI1MDUwNTA1MDYwNDA1MTYxMDIzYjkxOTA2MTA0NjY1NjViNjAwMDYwNDA1MTgwODMwMzgxNjAwMDg2NWFmMTkxNTA1MDNkODA2MDAwODExNDYxMDI3ODU3NjA0MDUxOTE1MDYwMWYxOTYwM2YzZDAxMTY4MjAxNjA0MDUyM2Q4MjUyM2Q2MDAwNjAyMDg0MDEzZTYxMDI3ZDU2NWI2MDYwOTE1MDViNTA5MTUwOTE1MDgxNjEwMjhjNTc2MDAwODBmZDViODA4MDYwMjAwMTkwNTE4MTAxOTA2MTAyYTA5MTkwNjEwNTM3NTY1YjkyNTA1MDUwOTE5MDUwNTY1YjYwMDA4MDYwMDA2MTAxNjg3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNjYzNDNhODgyMjk2MGUwMWI4NTYwNDA1MTYwMjQwMTYxMDJlMDkxOTA2MTAzZDE1NjViNjA0MDUxNjAyMDgxODMwMzAzODE1MjkwNjA0MDUyOTA3YmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmMTkxNjYwMjA4MjAxODA1MTdiZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmY4MzgxODMxNjE3ODM1MjUwNTA1MDUwNjA0MDUxNjEwMzRhOTE5MDYxMDQ2NjU2NWI2MDAwNjA0MDUxODA4MzAzODE2MDAwODY1YWYxOTE1MDUwM2Q4MDYwMDA4MTE0NjEwMzg3NTc2MDQwNTE5MTUwNjAxZjE5NjAzZjNkMDExNjgyMDE2MDQwNTIzZDgyNTIzZDYwMDA2MDIwODQwMTNlNjEwMzhjNTY1YjYwNjA5MTUwNWI1MDkxNTA5MTUwODE2MTAzOWI1NzYwMDA4MGZkNWI4MDgwNjAyMDAxOTA1MTgxMDE5MDYxMDNhZjkxOTA2MTA1Mzc1NjViOTI1MDUwNTA5MTkwNTA1NjViNjAwMDgxOTA1MDkxOTA1MDU2NWI2MTAzY2I4MTYxMDNiODU2NWI4MjUyNTA1MDU2NWI2MDAwNjAyMDgyMDE5MDUwNjEwM2U2NjAwMDgzMDE4NDYxMDNjMjU2NWI5MjkxNTA1MDU2NWI2MDAwODE1MTkwNTA5MTkwNTA1NjViNjAwMDgxOTA1MDkyOTE1MDUwNTY1YjYwMDA1YjgzODExMDE1NjEwNDIwNTc4MDgyMDE1MTgxODQwMTUyNjAyMDgxMDE5MDUwNjEwNDA1NTY1YjgzODExMTE1NjEwNDJmNTc2MDAwODQ4NDAxNTI1YjUwNTA1MDUwNTY1YjYwMDA2MTA0NDA4MjYxMDNlYzU2NWI2MTA0NGE4MTg1NjEwM2Y3NTY1YjkzNTA2MTA0NWE4MTg1NjAyMDg2MDE2MTA0MDI1NjViODA4NDAxOTE1MDUwOTI5MTUwNTA1NjViNjAwMDYxMDQ3MjgyODQ2MTA0MzU1NjViOTE1MDgxOTA1MDkyOTE1MDUwNTY1YjdmNGU0ODdiNzEwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDYwMDA1MjYwMTE2MDA0NTI2MDI0NjAwMGZkNWI2MDAwNjEwNGI3ODI2MTAzYjg1NjViOTE1MDYxMDRjMjgzNjEwM2I4NTY1YjkyNTA4MTdmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjA0ODMxMTgyMTUxNTE2MTU2MTA0ZmI1NzYxMDRmYTYxMDQ3ZDU2NWI1YjgyODIwMjkwNTA5MjkxNTA1MDU2NWI2MDAwODBmZDViNjEwNTE0ODE2MTAzYjg1NjViODExNDYxMDUxZjU3NjAwMDgwZmQ1YjUwNTY1YjYwMDA4MTUxOTA1MDYxMDUzMTgxNjEwNTBiNTY1YjkyOTE1MDUwNTY1YjYwMDA2MDIwODI4NDAzMTIxNTYxMDU0ZDU3NjEwNTRjNjEwNTA2NTY1YjViNjAwMDYxMDU1Yjg0ODI4NTAxNjEwNTIyNTY1YjkxNTA1MDkyOTE1MDUwNTZmZWEyNjQ2OTcwNjY3MzU4MjIxMjIwOWRkMzZhZDI2ZDMwNWQ3YTYwMzU0YjZlODRmYTg0MjRmMzU1NWE0MWQ3NDEyMWNkYWRjNDdkZmMwNDBhMTFkNjY0NzM2ZjZjNjM0MzAwMDgwOTAwMzM=","b64Record":"CiAIFiocCgwIARAMGgYIgK6ZpA8SDAgBEA8aBgiArpmkDxIwhRmefZyNxM+ccrSnA100Hcmx69RGrGHTOKG2yl/RhaZbUlz/s4Pdmc02odZwUHATGgwIkP/+rAYQu+fQ5wEiDwoJCNT+/qwGEIIVEgIYAiogw4PCrsOCwrfDg8K5dEY4w4LCrkrDg8KLw4PCkMODwo5SAA=="},{"b64Body":"Cg8KCQjU/v6sBhCEFRICGAISAhgDGPb99p4CIgIIeDIgw4PCrsOCwrfDg8K5dEY4w4LCrkrDg8KLw4PCkMODwo5CZwoDGKoJGiISIJn1U5bYn0d2oPf/jqt2Za/P2guycdmdWooC9Oplg+H6IJChD0IFCIDO2gNKIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKaUgBaAGoLY2VsbGFyIGRvb3I=","b64Record":"CiUIFiIDGKsJKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjAY+vcOHBdGlD2G7v6hrkzXHC6rroh7G9VLVv2QPTY3fyJZcjnbfsrhFt0mTBIFqqcaCwiR//6sBhCHn6UMIg8KCQjU/v6sBhCEFRICGAIqIMODwq7DgsK3w4PCuXRGOMOCwq5Kw4PCi8ODwpDDg8KOMMDZ4gZCzw0KAxirCRKaC2CAYEBSYAQ2EGEANFdgADVg4ByAYwO2NuYUYQA5V4Bjid6h0BRhAENXgGO/9PVPFGEATVdbYACA/VthAEFhAGtWWwBbYQBLYQFUVlsAW2EAVWEBilZbYEBRYQBikZBhA9FWW2BAUYCRA5DzW2AAgGEBaHP//////////////////////////xZjv/T1T2DgG2BAUWAkAWBAUWAggYMDA4FSkGBAUpB7/////////////////////////////////////xkWYCCCAYBRe/////////////////////////////////////+DgYMWF4NSUFBQUGBAUWEA/5GQYQRmVltgAGBAUYCDA4FgAIZa8ZFQUD2AYACBFGEBPFdgQFGRUGAfGWA/PQEWggFgQFI9glI9YABgIIQBPmEBQVZbYGCRUFtQkVCRUIFhAVBXYACA/VtQUFZbYABUYABjBfXhAIJhAWmRkGEErFZbkFBgAGEBdoJhAZpWW5BQgDQQFWEBhVdgAID9W1BQUFZbYABhAZU0YQKpVluQUJBWW2AAgGAAYQFoc///////////////////////////FmMuPP9qYOAbhWBAUWAkAWEB0ZGQYQPRVltgQFFgIIGDAwOBUpBgQFKQe/////////////////////////////////////8ZFmAgggGAUXv/////////////////////////////////////g4GDFheDUlBQUFBgQFFhAjuRkGEEZlZbYABgQFGAgwOBYACGWvGRUFA9gGAAgRRhAnhXYEBRkVBgHxlgPz0BFoIBYEBSPYJSPWAAYCCEAT5hAn1WW2BgkVBbUJFQkVCBYQKMV2AAgP1bgIBgIAGQUYEBkGECoJGQYQU3VluSUFBQkZBQVltgAIBgAGEBaHP//////////////////////////xZjQ6iCKWDgG4VgQFFgJAFhAuCRkGED0VZbYEBRYCCBgwMDgVKQYEBSkHv/////////////////////////////////////GRZgIIIBgFF7/////////////////////////////////////4OBgxYXg1JQUFBQYEBRYQNKkZBhBGZWW2AAYEBRgIMDgWAAhlrxkVBQPYBgAIEUYQOHV2BAUZFQYB8ZYD89ARaCAWBAUj2CUj1gAGAghAE+YQOMVltgYJFQW1CRUJFQgWEDm1dgAID9W4CAYCABkFGBAZBhA6+RkGEFN1ZbklBQUJGQUFZbYACBkFCRkFBWW2EDy4FhA7hWW4JSUFBWW2AAYCCCAZBQYQPmYACDAYRhA8JWW5KRUFBWW2AAgVGQUJGQUFZbYACBkFCSkVBQVltgAFuDgRAVYQQgV4CCAVGBhAFSYCCBAZBQYQQFVluDgREVYQQvV2AAhIQBUltQUFBQVltgAGEEQIJhA+xWW2EESoGFYQP3VluTUGEEWoGFYCCGAWEEAlZbgIQBkVBQkpFQUFZbYABhBHKChGEENVZbkVCBkFCSkVBQVlt/Tkh7cQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABgAFJgEWAEUmAkYAD9W2AAYQS3gmEDuFZbkVBhBMKDYQO4VluSUIF///////////////////////////////////////////8EgxGCFRUWFWEE+1dhBPphBH1WW1uCggKQUJKRUFBWW2AAgP1bYQUUgWEDuFZbgRRhBR9XYACA/VtQVltgAIFRkFBhBTGBYQULVluSkVBQVltgAGAggoQDEhVhBU1XYQVMYQUGVltbYABhBVuEgoUBYQUiVluRUFCSkVBQVv6iZGlwZnNYIhIgndNq0m0wXXpgNUtuhPqEJPNVWkHXQSHNrcR9/AQKEdZkc29sY0MACAkAMyKAAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAowJoMOgMYqwlKFgoUAAAAAAAAAAAAAAAAAAAAAAAABKtyBwoDGKsJEAFSFgoJCgIYAhD/ssUNCgkKAhhiEICzxQ0="},{"b64Body":"Cg8KCQjV/v6sBhCGFRICGAISAhgDGOCssQMiAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjjoVCgMYqwkQoI0GGP+OudYUIgSJ3qHQ","b64Record":"CiUIISIDGKsJKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjAeaugvcnFruMtjZi92AhAP48IB0JftBVmuIQYYJ7Q0uIlI8ggWvc6PVRSYfIamcxUaDAiR//6sBhD77IjxASIPCgkI1f7+rAYQhhUSAhgCKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjjCA19oCOggaAjB4KIDxBFIWCgkKAhgCEP+ttQUKCQoCGGIQgK61BQ=="},{"b64Body":"Cg8KCQjV/v6sBhCIFRICGAISAhgDGOCssQMiAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjjoVCgMYqwkQoI0GGICPudYUIgSJ3qHQ","b64Record":"CiUIFiIDGKsJKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjB9sJhjN+HiTrbWS/SZzHQn4seVFXVqS1sg5McPAweLQOrY2QCJUEGkfzLxViq/s18aCwiS//6sBhDWjOsVIg8KCQjV/v6sBhCIFRICGAIqIMODwq7DgsK3w4PCuXRGOMOCwq5Kw4PCi8ODwpDDg8KOMIDX2gI6jAIKAxirCSKAAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAogPEEUiQKCgoCGAIQ/8unsikKCQoCGGIQgK61BQoLCgMYqwkQgJ7yrCk="},{"b64Body":"Cg8KCQjW/v6sBhCKFRICGAISAhgDGOCssQMiAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjjoVCgMYqwkQoI0GGICPudYUIgS/9PVP","b64Record":"CiUIFiIDGKsJKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjBvI/wBNSNN8SPSKUTo+bK50yjeIIq7i2LFORbMLDeWYiJhT6Ji4r35lBnWsQD7KR0aDAiS//6sBhDJtOP6ASIPCgkI1v7+rAYQihUSAhgCKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjjCA19oCOq4CCgMYqwkSIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA+Bq1oAIoACAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACiA8QRSJAoKCgIYAhD/y6eyKQoJCgIYYhCArrUFCgsKAxirCRCAnvKsKQ=="},{"b64Body":"Cg8KCQjW/v6sBhCQFRICGAISAhgDGOCssQMiAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjjoVCgMYqwkQoI0GGICPudYUIgQDtjbm","b64Record":"CiUIISIDGKsJKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjAkCdnnngoWjWfCY3OzCG8q8w8UuHyTHGZZnn6QKPo1D+Luf+uelhK5cYSkrs24mwEaCwiT//6sBhCoscMCIg8KCQjW/v6sBhCQFRICGAIqIMODwq7DgsK3w4PCuXRGOMOCwq5Kw4PCi8ODwpDDg8KOMLDIrAM6CBoCMHgo0IQGUhYKCQoCGAIQ35DZBgoJCgIYYhDgkNkG"}]},"NestedContractCannotOverSendValue":{"placeholderNum":1196,"encodedItems":[{"b64Body":"Cg8KCQjb/v6sBhCgFRICGAISAhgDGPuV9hQiAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjlozCiISIFibu5xHIQ2KCY/mZbOFrpMC5qJepUYWF4rFs84HS2IkEICA6YOx3hZKBQiAztoD","b64Record":"CiUIFhIDGK0JKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjCCAkgwtk5FuEKVTvKZjyD1JF2lUhayxxIOn9SlVbijd1Zq+J+wYxIhGGvRDtZviUsaDAiX//6sBhDNiPn/AiIPCgkI2/7+rAYQoBUSAhgCKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjlIdCgwKAhgCEP//0YfivC0KDQoDGK0JEICA0ofivC0="},{"b64Body":"Cg8KCQjc/v6sBhCiFRICGAISAhgDGPuV9hQiAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjlouCiISIFR12wErIPr/CScQqj/JWm8Tq8s6YgsJYKg4pgBnk0rvEJBOSgUIgM7aAw==","b64Record":"CiUIFhIDGK4JKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjCidvcPNvglkWQOE9mDM7nW/S/KWArbmfEnBEaFzW4Itqli2is6AVHlK4HK1dXhhboaDAiY//6sBhCcm62IASIPCgkI3P7+rAYQohUSAhgCKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjlIVCggKAhgCEJ+cAQoJCgMYrgkQoJwB"},{"b64Body":"Cg8KCQjc/v6sBhCkFRICGAISAhgDGP7LzSwiAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjooBjwESDAiYzdmwBhDhjL/3AhptCiISIFS84kMP9+2tHvO3K7sySNqPIK6O8y6oIh12Z2fVN6x9CiM6IQMVoQdaphpvtWoP3Icq4rUh+JjDcZ5yXjjkfeOrH18xhgoiEiAgBd1U+RNp4GZaCphn1wGoxW3jGprknZVHN+3Czs6T1SIMSGVsbG8gV29ybGQhKgAyAA==","b64Record":"CiUIFhoDGK8JKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjDGap0mG5P50VdpeYkdwtd8FB5qTtq8x4w2UpcwWhJ8EVuzEFm1BuxoA+UofKNyPg4aDAiY//6sBhD2jMeOAyIPCgkI3P7+rAYQpBUSAhgCKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjlIA"},{"b64Body":"Cg8KCQjd/v6sBhCoFRICGAISAhgDGIudjj4iAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjpoBiCAKAxivCSKAIDYwODA2MDQwNTI2MDQwNTE2MTBlNmIzODAzODA2MTBlNmI4MzM5ODE4MTAxNjA0MDUyNjA0MDgxMTAxNTYxMDAyNjU3NjAwMDgwZmQ1YjgxMDE5MDgwODA1MTkwNjAyMDAxOTA5MjkxOTA4MDUxOTA2MDIwMDE5MDkyOTE5MDUwNTA1MDgxNjAwMDgwNjEwMTAwMGE4MTU0ODE3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYwMjE5MTY5MDgzNzNmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmMTYwMjE3OTA1NTUwODA2MDAxNjAwMDYxMDEwMDBhODE1NDgxNzNmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmMDIxOTE2OTA4MzczZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjE2MDIxNzkwNTU1MDUwNTA2MTBkOTg4MDYxMDBkMzYwMDAzOTYwMDBmM2ZlNjA4MDYwNDA1MjYwMDQzNjEwNjEwMDNmNTc2MDAwMzU2MGUwMWM4MDYzNTU1NWQ3ZTQxNDYxMDA0MTU3ODA2MzVlY2YyOThhMTQ2MTAwY2Y1NzgwNjM5ZGM3ZGIxMDE0NjEwMGZkNTc4MDYzYTlhM2E2MzUxNDYxMDE0YjU3NWIwMDViNjEwMGNkNjAwNDgwMzYwMzYwODA4MTEwMTU2MTAwNTc1NzYwMDA4MGZkNWI4MTAxOTA4MDgwMzU3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNjkwNjAyMDAxOTA5MjkxOTA4MDM1NzNmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmMTY5MDYwMjAwMTkwOTI5MTkwODAzNTczZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjE2OTA2MDIwMDE5MDkyOTE5MDgwMzU5MDYwMjAwMTkwOTI5MTkwNTA1MDUwNjEwMTc5NTY1YjAwNWI2MTAwZmI2MDA0ODAzNjAzNjAyMDgxMTAxNTYxMDBlNTU3NjAwMDgwZmQ1YjgxMDE5MDgwODAzNTkwNjAyMDAxOTA5MjkxOTA1MDUwNTA2MTA3MTM1NjViMDA1YjYxMDE0OTYwMDQ4MDM2MDM2MDQwODExMDE1NjEwMTEzNTc2MDAwODBmZDViODEwMTkwODA4MDM1NzNmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmMTY5MDYwMjAwMTkwOTI5MTkwODAzNTkwNjAyMDAxOTA5MjkxOTA1MDUwNTA2MTA4MmY1NjViMDA1YjYxMDE3NzYwMDQ4MDM2MDM2MDIwODExMDE1NjEwMTYxNTc2MDAwODBmZDViODEwMTkwODA4MDM1OTA2MDIwMDE5MDkyOTE5MDUwNTA1MDYxMGEwZjU2NWIwMDViODM3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNjYxMDhmYzgyOTA4MTE1MDI5MDYwNDA1MTYwMDA2MDQwNTE4MDgzMDM4MTg1ODg4OGYxOTM1MDUwNTA1MDE1ODAxNTYxMDFiZjU3M2Q2MDAwODAzZTNkNjAwMGZkNWI1MDYwMDA4MDkwNTQ5MDYxMDEwMDBhOTAwNDczZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjE2NzNmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmMTY2M2FjZWY2MDM3ODU2MDAyODQ4MTYxMDIwODU3ZmU1YjA0NjA0MDUxODM2M2ZmZmZmZmZmMTY2MGUwMWI4MTUyNjAwNDAxODA4MzczZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjE2NzNmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmMTY4MTUyNjAyMDAxODI4MTUyNjAyMDAxOTI1MDUwNTA2MDAwNjA0MDUxODA4MzAzODE2MDAwODc4MDNiMTU4MDE1NjEwMjcyNTc2MDAwODBmZDViNTA1YWYxMTU4MDE1NjEwMjg2NTczZDYwMDA4MDNlM2Q2MDAwZmQ1YjUwNTA1MDUwNjAwMTYwMDA5MDU0OTA2MTAxMDAwYTkwMDQ3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNjczZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjE2NjNhY2VmNjAzNzg1NjAwMjg0ODE2MTAyZDM1N2ZlNWIwNDYwNDA1MTgzNjNmZmZmZmZmZjE2NjBlMDFiODE1MjYwMDQwMTgwODM3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNjczZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjE2ODE1MjYwMjAwMTgyODE1MjYwMjAwMTkyNTA1MDUwNjAwMDYwNDA1MTgwODMwMzgxNjAwMDg3ODAzYjE1ODAxNTYxMDMzZDU3NjAwMDgwZmQ1YjUwNWFmMTE1ODAxNTYxMDM1MTU3M2Q2MDAwODAzZTNkNjAwMGZkNWI1MDUwNTA1MDgyNzNmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmMTY2MTA4ZmM4MjkwODExNTAyOTA2MDQwNTE2MDAwNjA0MDUxODA4MzAzODE4NTg4ODhmMTkzNTA1MDUwNTAxNTgwMTU2MTAzOWI1NzNkNjAwMDgwM2UzZDYwMDBmZDViNTA2MDAwODA5MDU0OTA2MTAxMDAwYTkwMDQ3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNjczZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjE2NjNhY2VmNjAzNzg0NjAwMjg0ODE2MTAzZTQ1N2ZlNWIwNDYwNDA1MTgzNjNmZmZmZmZmZjE2NjBlMDFiODE1MjYwMDQwMTgwODM3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNjczZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjE2ODE1MjYwMjAwMTgyODE1MjYwMjAwMTkyNTA1MDUwNjAwMDYwNDA1MTgwODMwMzgxNjAwMDg3ODAzYjE1ODAxNTYxMDQ0ZTU3NjAwMDgwZmQ1YjUwNWFmMTE1ODAxNTYxMDQ2MjU3M2Q2MDAwODAzZTNkNjAwMGZkNWI1MDUwNTA1MDYwMDE2MDAwOTA1NDkwNjEwMTAwMGE5MDA0NzNmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmMTY3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNjYzYWNlZjYwMzc4NDYwMDI4NDgxNjEwNGFmNTdmZTViMDQ2MDQwNTE4MzYzZmZmZmZmZmYxNjYwZTAxYjgxNTI2MDA0MDE4MDgzNzNmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmMTY3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNjgxNTI2MDIwMDE4MjgxNTI2MDIwMDE5MjUwNTA1MDYwMDA2MDQwNTE4MDgzMDM4MTYwMDA4NzgwM2IxNTgwMTU2MTA1MTk1NzYwMDA4MGZkNWI1MDVhZjExNTgwMTU2MTA1MmQ1NzNkNjAwMDgwM2UzZDYwMDBmZDViNTA1MDUwNTA4MTczZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjE2NjEwOGZjODI5MDgxMTUwMjkwNjA0MDUxNjAwMDYwNDA1MTgwODMwMzgxODU4ODg4ZjE5MzUwNTA1MDUwMTU4MDE1NjEwNTc3NTczZDYwMDA4MDNlM2Q2MDAwZmQ1YjUwNjAwMDgwOTA1NDkwNjEwMTAwMGE5MDA0NzNmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmMTY3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNjYzYWNlZjYwMzc4MzYwMDI4NDgxNjEwNWMwNTdmZTViMDQ2MDQwNTE4MzYzZmZmZmZmZmYxNjYwZTAxYjgxNTI2MDA0MDE4MDgzNzNmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmMTY3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNjgxNTI2MDIwMDE4MjgxNTI2MDIwMDE5MjUwNTA1MDYwMDA2MDQwNTE4MDgzMDM4MTYwMDA4NzgwM2IxNTgwMTU2MTA2MmE1NzYwMDA4MGZkNWI1MDVhZjExNTgwMTU2MTA2M2U1NzNkNjAwMDgwM2UzZDYwMDBmZDViNTA1MDUwNTA2MDAxNjAwMDkwNTQ5MDYxMDEwMDBhOTAwNDczZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjE2NzNmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmMTY2M2FjZWY2MDM3ODM2MDAyODQ4MTYxMDY4YjU3ZmU1YjA0NjA0MDUxODM2M2ZmZmZmZmZmMTY2MGUwMWI4MTUyNjAwNDAxODA4MzczZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjE2NzNmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmMTY4MTUyNjAyMDAxODI4MTUyNjAyMDAxOTI1MDUwNTA2MDAwNjA0MDUxODA4MzAzODE2MDAwODc4MDNiMTU4MDE1NjEwNmY1NTc2MDAwODBmZDViNTA1YWYxMTU4MDE1NjEwNzA5NTczZDYwMDA4MDNlM2Q2MDAwZmQ1YjUwNTA1MDUwNTA1MDUwNTA1NjViNjAwMDgwOTA1NDkwNjEwMTAwMGE5MDA0NzNmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmY=","b64Record":"CiAIFiocCgwIARAMGgYIgK6ZpA8SDAgBEA8aBgiArpmkDxIwadyg7FLTAbedmZ4Y88TRzQelnjxcttCSpNdsbamx0Up+6FogiCVg5soonQL2o/lDGgwImf/+rAYQiNvElgEiDwoJCN3+/qwGEKgVEgIYAiogw4PCrsOCwrfDg8K5dEY4w4LCrkrDg8KLw4PCkMODwo5SAA=="},{"b64Body":"Cg8KCQjd/v6sBhCuFRICGAISAhgDGPjsozsiAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjoIB3hkSAxivCSLWGWZmZmZmZmZmZmZmZmZmZmYxNjczZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjE2NjM4ZGQ5ODA0MzgyNjA0MDUxODI2M2ZmZmZmZmZmMTY2MGUwMWI4MTUyNjAwNDAxODA4MjgxNTI2MDIwMDE5MTUwNTA2MDAwNjA0MDUxODA4MzAzODE2MDAwODc4MDNiMTU4MDE1NjEwNzg3NTc2MDAwODBmZDViNTA1YWYxMTU4MDE1NjEwNzliNTczZDYwMDA4MDNlM2Q2MDAwZmQ1YjUwNTA1MDUwNjAwMTYwMDA5MDU0OTA2MTAxMDAwYTkwMDQ3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNjczZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjE2NjM4ZGQ5ODA0MzgyNjA0MDUxODI2M2ZmZmZmZmZmMTY2MGUwMWI4MTUyNjAwNDAxODA4MjgxNTI2MDIwMDE5MTUwNTA2MDAwNjA0MDUxODA4MzAzODE2MDAwODc4MDNiMTU4MDE1NjEwODE0NTc2MDAwODBmZDViNTA1YWYxMTU4MDE1NjEwODI4NTczZDYwMDA4MDNlM2Q2MDAwZmQ1YjUwNTA1MDUwNTA1NjViODE3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNjYxMDhmYzgyOTA4MTE1MDI5MDYwNDA1MTYwMDA2MDQwNTE4MDgzMDM4MTg1ODg4OGYxOTM1MDUwNTA1MDE1ODAxNTYxMDg3NTU3M2Q2MDAwODAzZTNkNjAwMGZkNWI1MDYwMDA4MDkwNTQ5MDYxMDEwMDBhOTAwNDczZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjE2NzNmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmMTY2M2FjZWY2MDM3ODM2MDAyODQ4MTYxMDhiZTU3ZmU1YjA0NjA0MDUxODM2M2ZmZmZmZmZmMTY2MGUwMWI4MTUyNjAwNDAxODA4MzczZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjE2NzNmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmMTY4MTUyNjAyMDAxODI4MTUyNjAyMDAxOTI1MDUwNTA2MDAwNjA0MDUxODA4MzAzODE2MDAwODc4MDNiMTU4MDE1NjEwOTI4NTc2MDAwODBmZDViNTA1YWYxMTU4MDE1NjEwOTNjNTczZDYwMDA4MDNlM2Q2MDAwZmQ1YjUwNTA1MDUwNjAwMTYwMDA5MDU0OTA2MTAxMDAwYTkwMDQ3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNjczZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjE2NjNhY2VmNjAzNzgzNjAwMjg0ODE2MTA5ODk1N2ZlNWIwNDYwNDA1MTgzNjNmZmZmZmZmZjE2NjBlMDFiODE1MjYwMDQwMTgwODM3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNjczZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjE2ODE1MjYwMjAwMTgyODE1MjYwMjAwMTkyNTA1MDUwNjAwMDYwNDA1MTgwODMwMzgxNjAwMDg3ODAzYjE1ODAxNTYxMDlmMzU3NjAwMDgwZmQ1YjUwNWFmMTE1ODAxNTYxMGEwNzU3M2Q2MDAwODAzZTNkNjAwMGZkNWI1MDUwNTA1MDUwNTA1NjViNjAwMDYwNjA2MDAwODA5MDU0OTA2MTAxMDAwYTkwMDQ3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNjczZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjE2ODM2MDQwNTE2MDI0MDE4MDgyODE1MjYwMjAwMTkxNTA1MDYwNDA1MTYwMjA4MTgzMDMwMzgxNTI5MDYwNDA1MjdmOGRkOTgwNDMwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDdiZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxOTE2NjAyMDgyMDE4MDUxN2JmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjgzODE4MzE2MTc4MzUyNTA1MDUwNTA2MDQwNTE4MDgyODA1MTkwNjAyMDAxOTA4MDgzODM1YjYwMjA4MzEwNjEwYjBjNTc4MDUxODI1MjYwMjA4MjAxOTE1MDYwMjA4MTAxOTA1MDYwMjA4MzAzOTI1MDYxMGFlOTU2NWI2MDAxODM2MDIwMDM2MTAxMDAwYTAzODAxOTgyNTExNjgxODQ1MTE2ODA4MjE3ODU1MjUwNTA1MDUwNTA1MDkwNTAwMTkxNTA1MDYwMDA2MDQwNTE4MDgzMDM4MTg1NWFmNDkxNTA1MDNkODA2MDAwODExNDYxMGI2YzU3NjA0MDUxOTE1MDYwMWYxOTYwM2YzZDAxMTY4MjAxNjA0MDUyM2Q4MjUyM2Q2MDAwNjAyMDg0MDEzZTYxMGI3MTU2NWI2MDYwOTE1MDViNTA5MTUwOTE1MDYwMDA2MDYwNjAwMTYwMDA5MDU0OTA2MTAxMDAwYTkwMDQ3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNjczZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjE2ODU2MDQwNTE2MDI0MDE4MDgyODE1MjYwMjAwMTkxNTA1MDYwNDA1MTYwMjA4MTgzMDMwMzgxNTI5MDYwNDA1MjdmOGRkOTgwNDMwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDdiZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxOTE2NjAyMDgyMDE4MDUxN2JmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjgzODE4MzE2MTc4MzUyNTA1MDUwNTA2MDQwNTE4MDgyODA1MTkwNjAyMDAxOTA4MDgzODM1YjYwMjA4MzEwNjEwYzc0NTc4MDUxODI1MjYwMjA4MjAxOTE1MDYwMjA4MTAxOTA1MDYwMjA4MzAzOTI1MDYxMGM1MTU2NWI2MDAxODM2MDIwMDM2MTAxMDAwYTAzODAxOTgyNTExNjgxODQ1MTE2ODA4MjE3ODU1MjUwNTA1MDUwNTA1MDkwNTAwMTkxNTA1MDYwMDA2MDQwNTE4MDgzMDM4MTg1NWFmNDkxNTA1MDNkODA2MDAwODExNDYxMGNkNDU3NjA0MDUxOTE1MDYwMWYxOTYwM2YzZDAxMTY4MjAxNjA0MDUyM2Q4MjUyM2Q2MDAwNjAyMDg0MDEzZTYxMGNkOTU2NWI2MDYwOTE1MDViNTA5MTUwOTE1MDgzMTU4MDYxMGNlOTU3NTA4MTE1NWIxNTYxMGQ1YzU3NjA0MDUxN2YwOGMzNzlhMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwODE1MjYwMDQwMTgwODA2MDIwMDE4MjgxMDM4MjUyNjAxZTgxNTI2MDIwMDE4MDdmNDQ2NTZjNjU2NzYxNzQ2NTIwNzQ3MjYxNmU3MzY2NjU3MjIwNjM2MTZjNmMyMDY2NjE2OTZjNjU2NDIxMDAwMDgxNTI1MDYwMjAwMTkxNTA1MDYwNDA1MTgwOTEwMzkwZmQ1YjUwNTA1MDUwNTA1NmZlYTI2NTYyN2E3YTcyMzE1ODIwNjE5ZDU1NTI1Y2QxN2Y3MzliMGNiMjZkODZmN2QxOGYxNTVlYWM0NWY5NTM3MGQ5NjRkZWEzYzcwZGIxNGQ2ZTY0NzM2ZjZjNjM0MzAwMDUxMTAwMzI=","b64Record":"CiAIFiocCgwIARAMGgYIgK6ZpA8SDAgBEA8aBgiArpmkDxIwbSennKgpdl6uVnpqA3A35CjJFtDtpZKK/ZESSIgxCMar3PRFF4wEi0SQhfE+uUxdGgwImf/+rAYQhrn9lwMiDwoJCN3+/qwGEK4VEgIYAiogw4PCrsOCwrfDg8K5dEY4w4LCrkrDg8KLw4PCkMODwo5SAA=="},{"b64Body":"Cg8KCQje/v6sBhCwFRICGAISAhgDGP7LzSwiAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjooBjwESDAiazdmwBhCS8+ySARptCiISILZpiXrDM7gpade149CmjNt7TebQk3gTjOTDnuLIIt3ECiM6IQP48bvmGkXfFZ4+HU9uG4MhA9I8CWQ9fRyMsROjdttergoiEiCrzOGBwTDmS3ITola7gkoxPIjA/sB8ni3KQBsC3RN7DCIMSGVsbG8gV29ybGQhKgAyAA==","b64Record":"CiUIFhoDGLAJKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjAWwWga9xfb7Nc83EuihvUTsFcM3z69KszjA9bdiOw0dhvxiGWJ8NAOWcqETH6UQ7UaDAia//6sBhDk8P2fASIPCgkI3v7+rAYQsBUSAhgCKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjlIA"},{"b64Body":"Cg8KCQje/v6sBhC0FRICGAISAhgDGM6Y0i8iAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjpoBkAYKAxiwCSKIBjYwODA2MDQwNTI2MTAxNzE4MDYxMDAxMzYwMDAzOTYwMDBmM2ZlNjA4MDYwNDA1MjYwMDQzNjEwNjEwMDI5NTc2MDAwMzU2MGUwMWM4MDYzOGRkOTgwNDMxNDYxMDAyYjU3ODA2M2FjZWY2MDM3MTQ2MTAwNTk1NzViMDA1YjYxMDA1NzYwMDQ4MDM2MDM2MDIwODExMDE1NjEwMDQxNTc2MDAwODBmZDViODEwMTkwODA4MDM1OTA2MDIwMDE5MDkyOTE5MDUwNTA1MDYxMDBhNzU2NWIwMDViNjEwMGE1NjAwNDgwMzYwMzYwNDA4MTEwMTU2MTAwNmY1NzYwMDA4MGZkNWI4MTAxOTA4MDgwMzU3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNjkwNjAyMDAxOTA5MjkxOTA4MDM1OTA2MDIwMDE5MDkyOTE5MDUwNTA1MDYxMDBmMTU2NWIwMDViMzM3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNjYxMDhmYzgyOTA4MTE1MDI5MDYwNDA1MTYwMDA2MDQwNTE4MDgzMDM4MTg1ODg4OGYxOTM1MDUwNTA1MDE1ODAxNTYxMDBlZDU3M2Q2MDAwODAzZTNkNjAwMGZkNWI1MDUwNTY1YjgxNzNmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmMTY2MTA4ZmM4MjkwODExNTAyOTA2MDQwNTE2MDAwNjA0MDUxODA4MzAzODE4NTg4ODhmMTkzNTA1MDUwNTAxNTgwMTU2MTAxMzc1NzNkNjAwMDgwM2UzZDYwMDBmZDViNTA1MDUwNTZmZWEyNjU2MjdhN2E3MjMxNTgyMDVlYjQwYmI2MjA4MzVmYjc5NjljZGQ3NWRlZDE4ODkyN2Q1YzkyN2Y5M2JhNjc1ZDY1YjljNTIzNDQ0MjQyODc2NDczNmY2YzYzNDMwMDA1MTEwMDMy","b64Record":"CiAIFiocCgwIARAMGgYIgK6ZpA8SDAgBEA8aBgiArpmkDxIwUBDjJ4BUnBf8WLQS0GsGV+n47RXouOu9yG3CWJEYliJwgINN79DeyRcMLHFFfvs5GgwImv/+rAYQuPi2oQMiDwoJCN7+/qwGELQVEgIYAiogw4PCrsOCwrfDg8K5dEY4w4LCrkrDg8KLw4PCkMODwo5SAA=="},{"b64Body":"ChAKCQjf/v6sBhC2FRIDGK0JEgIYAxiW+66dAiICCHgyIMODwq7DgsK3w4PCuXRGOMOCwq5Kw4PCi8ODwpDDg8KOQkgKAxiwCRoiEiDRxfzVr6Sw1ZWIswyKZQrCVyZkn98w0ryxyK57QPk+aSCQoQ8okE5CBQiAztoDUgBaAGoLY2VsbGFyIGRvb3I=","b64Record":"CiUIFiIDGLEJKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjDLMXNMTZS1fa7un4G6Jmut/tTN3B734K15jM/GdNSDlP8p3/xmtnK2xWwx2xTMweAaDAib//6sBhCu4K+pASIQCgkI3/7+rAYQthUSAxitCSogw4PCrsOCwrfDg8K5dEY4w4LCrkrDg8KLw4PCkMODwo4w1tSRpAJCpgUKAxixCRLxAmCAYEBSYAQ2EGEAKVdgADVg4ByAY43ZgEMUYQArV4BjrO9gNxRhAFlXWwBbYQBXYASANgNgIIEQFWEAQVdgAID9W4EBkICANZBgIAGQkpGQUFBQYQCnVlsAW2EApWAEgDYDYECBEBVhAG9XYACA/VuBAZCAgDVz//////////////////////////8WkGAgAZCSkZCANZBgIAGQkpGQUFBQYQDxVlsAWzNz//////////////////////////8WYQj8gpCBFQKQYEBRYABgQFGAgwOBhYiI8ZNQUFBQFYAVYQDtVz1gAIA+PWAA/VtQUFZbgXP//////////////////////////xZhCPyCkIEVApBgQFFgAGBAUYCDA4GFiIjxk1BQUFAVgBVhATdXPWAAgD49YAD9W1BQUFb+omVienpyMVggXrQLtiCDX7eWnN113tGIkn1ckn+TumddZbnFI0RCQodkc29sY0MABREAMiKAAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAowJoMOgMYsQlKFgoUAAAAAAAAAAAAAAAAAAAAAAAABLFyBwoDGLEJEAFSRwoJCgIYAxCY4okUCgoKAhhiEODciMYDCgoKAxigBhCatYg3CgoKAxihBhCatYg3CgsKAxitCRDLxaTIBAoJCgMYsQkQoJwB"},{"b64Body":"ChAKCQjf/v6sBhC4FRIDGK0JEgIYAxiW+66dAiICCHgyIMODwq7DgsK3w4PCuXRGOMOCwq5Kw4PCi8ODwpDDg8KOQkgKAxiwCRoiEiAzb2meLC+bbH11ZPGbIL1eGo5nl05T3NqeU6LfytIzMyCQoQ8okE5CBQiAztoDUgBaAGoLY2VsbGFyIGRvb3I=","b64Record":"CiUIFiIDGLIJKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjBhWG5CJJX3+/0jC8JoznBvdQeN01zX/g2yptA2pZ+2BsHL7clBoTTcylrnS0iPjG4aDAib//6sBhCPrp6OAyIQCgkI3/7+rAYQuBUSAxitCSogw4PCrsOCwrfDg8K5dEY4w4LCrkrDg8KLw4PCkMODwo4w1tSRpAJCpgUKAxiyCRLxAmCAYEBSYAQ2EGEAKVdgADVg4ByAY43ZgEMUYQArV4BjrO9gNxRhAFlXWwBbYQBXYASANgNgIIEQFWEAQVdgAID9W4EBkICANZBgIAGQkpGQUFBQYQCnVlsAW2EApWAEgDYDYECBEBVhAG9XYACA/VuBAZCAgDVz//////////////////////////8WkGAgAZCSkZCANZBgIAGQkpGQUFBQYQDxVlsAWzNz//////////////////////////8WYQj8gpCBFQKQYEBRYABgQFGAgwOBhYiI8ZNQUFBQFYAVYQDtVz1gAIA+PWAA/VtQUFZbgXP//////////////////////////xZhCPyCkIEVApBgQFFgAGBAUYCDA4GFiIjxk1BQUFAVgBVhATdXPWAAgD49YAD9W1BQUFb+omVienpyMVggXrQLtiCDX7eWnN113tGIkn1ckn+TumddZbnFI0RCQodkc29sY0MABREAMiKAAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAowJoMOgMYsglKFgoUAAAAAAAAAAAAAAAAAAAAAAAABLJyBwoDGLIJEAFSRwoJCgIYAxCY4okUCgoKAhhiEODciMYDCgoKAxigBhCatYg3CgoKAxihBhCatYg3CgsKAxitCRDLxaTIBAoJCgMYsgkQoJwB"},{"b64Body":"ChAKCQjg/v6sBhDAFRIDGK0JEgIYAxjVgL+gAiICCHgyIMODwq7DgsK3w4PCuXRGOMOCwq5Kw4PCi8ODwpDDg8KOQooBCgMYrwkaIhIgkJ1Kbr3m+c0Ga4gmoZUkDHHiGlaoVQ16XAL4mEsXtSAgkKEPKJBOQgUIgM7aA0pAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABLEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEslIAWgBqC2NlbGxhciBkb29y","b64Record":"CiUIFiIDGLMJKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjDsIw8oKU04L0pst9HHvoBzjkTcPoF22NcW1lS/qY6oev+LBPInl2SJLtWW7WNdRq0aDAic//6sBhDO/pKzASIQCgkI4P7+rAYQwBUSAxitCSogw4PCrsOCwrfDg8K5dEY4w4LCrkrDg8KLw4PCkMODwo4wldqhpwJCzR0KAxizCRKYG2CAYEBSYAQ2EGEAP1dgADVg4ByAY1VV1+QUYQBBV4BjXs8pihRhAM9XgGOdx9sQFGEA/VeAY6mjpjUUYQFLV1sAW2EAzWAEgDYDYICBEBVhAFdXYACA/VuBAZCAgDVz//////////////////////////8WkGAgAZCSkZCANXP//////////////////////////xaQYCABkJKRkIA1c///////////////////////////FpBgIAGQkpGQgDWQYCABkJKRkFBQUGEBeVZbAFthAPtgBIA2A2AggRAVYQDlV2AAgP1bgQGQgIA1kGAgAZCSkZBQUFBhBxNWWwBbYQFJYASANgNgQIEQFWEBE1dgAID9W4EBkICANXP//////////////////////////xaQYCABkJKRkIA1kGAgAZCSkZBQUFBhCC9WWwBbYQF3YASANgNgIIEQFWEBYVdgAID9W4EBkICANZBgIAGQkpGQUFBQYQoPVlsAW4Nz//////////////////////////8WYQj8gpCBFQKQYEBRYABgQFGAgwOBhYiI8ZNQUFBQFYAVYQG/Vz1gAIA+PWAA/VtQYACAkFSQYQEACpAEc///////////////////////////FnP//////////////////////////xZjrO9gN4VgAoSBYQIIV/5bBGBAUYNj/////xZg4BuBUmAEAYCDc///////////////////////////FnP//////////////////////////xaBUmAgAYKBUmAgAZJQUFBgAGBAUYCDA4FgAIeAOxWAFWECcldgAID9W1Ba8RWAFWEChlc9YACAPj1gAP1bUFBQUGABYACQVJBhAQAKkARz//////////////////////////8Wc///////////////////////////FmOs72A3hWAChIFhAtNX/lsEYEBRg2P/////FmDgG4FSYAQBgINz//////////////////////////8Wc///////////////////////////FoFSYCABgoFSYCABklBQUGAAYEBRgIMDgWAAh4A7FYAVYQM9V2AAgP1bUFrxFYAVYQNRVz1gAIA+PWAA/VtQUFBQgnP//////////////////////////xZhCPyCkIEVApBgQFFgAGBAUYCDA4GFiIjxk1BQUFAVgBVhA5tXPWAAgD49YAD9W1BgAICQVJBhAQAKkARz//////////////////////////8Wc///////////////////////////FmOs72A3hGAChIFhA+RX/lsEYEBRg2P/////FmDgG4FSYAQBgINz//////////////////////////8Wc///////////////////////////FoFSYCABgoFSYCABklBQUGAAYEBRgIMDgWAAh4A7FYAVYQROV2AAgP1bUFrxFYAVYQRiVz1gAIA+PWAA/VtQUFBQYAFgAJBUkGEBAAqQBHP//////////////////////////xZz//////////////////////////8WY6zvYDeEYAKEgWEEr1f+WwRgQFGDY/////8WYOAbgVJgBAGAg3P//////////////////////////xZz//////////////////////////8WgVJgIAGCgVJgIAGSUFBQYABgQFGAgwOBYACHgDsVgBVhBRlXYACA/VtQWvEVgBVhBS1XPWAAgD49YAD9W1BQUFCBc///////////////////////////FmEI/IKQgRUCkGBAUWAAYEBRgIMDgYWIiPGTUFBQUBWAFWEFd1c9YACAPj1gAP1bUGAAgJBUkGEBAAqQBHP//////////////////////////xZz//////////////////////////8WY6zvYDeDYAKEgWEFwFf+WwRgQFGDY/////8WYOAbgVJgBAGAg3P//////////////////////////xZz//////////////////////////8WgVJgIAGCgVJgIAGSUFBQYABgQFGAgwOBYACHgDsVgBVhBipXYACA/VtQWvEVgBVhBj5XPWAAgD49YAD9W1BQUFBgAWAAkFSQYQEACpAEc///////////////////////////FnP//////////////////////////xZjrO9gN4NgAoSBYQaLV/5bBGBAUYNj/////xZg4BuBUmAEAYCDc///////////////////////////FnP//////////////////////////xaBUmAgAYKBUmAgAZJQUFBgAGBAUYCDA4FgAIeAOxWAFWEG9VdgAID9W1Ba8RWAFWEHCVc9YACAPj1gAP1bUFBQUFBQUFBWW2AAgJBUkGEBAAqQBHP//////////////////////////xZz//////////////////////////8WY43ZgEOCYEBRgmP/////FmDgG4FSYAQBgIKBUmAgAZFQUGAAYEBRgIMDgWAAh4A7FYAVYQeHV2AAgP1bUFrxFYAVYQebVz1gAIA+PWAA/VtQUFBQYAFgAJBUkGEBAAqQBHP//////////////////////////xZz//////////////////////////8WY43ZgEOCYEBRgmP/////FmDgG4FSYAQBgIKBUmAgAZFQUGAAYEBRgIMDgWAAh4A7FYAVYQgUV2AAgP1bUFrxFYAVYQgoVz1gAIA+PWAA/VtQUFBQUFZbgXP//////////////////////////xZhCPyCkIEVApBgQFFgAGBAUYCDA4GFiIjxk1BQUFAVgBVhCHVXPWAAgD49YAD9W1BgAICQVJBhAQAKkARz//////////////////////////8Wc///////////////////////////FmOs72A3g2AChIFhCL5X/lsEYEBRg2P/////FmDgG4FSYAQBgINz//////////////////////////8Wc///////////////////////////FoFSYCABgoFSYCABklBQUGAAYEBRgIMDgWAAh4A7FYAVYQkoV2AAgP1bUFrxFYAVYQk8Vz1gAIA+PWAA/VtQUFBQYAFgAJBUkGEBAAqQBHP//////////////////////////xZz//////////////////////////8WY6zvYDeDYAKEgWEJiVf+WwRgQFGDY/////8WYOAbgVJgBAGAg3P//////////////////////////xZz//////////////////////////8WgVJgIAGCgVJgIAGSUFBQYABgQFGAgwOBYACHgDsVgBVhCfNXYACA/VtQWvEVgBVhCgdXPWAAgD49YAD9W1BQUFBQUFZbYABgYGAAgJBUkGEBAAqQBHP//////////////////////////xZz//////////////////////////8Wg2BAUWAkAYCCgVJgIAGRUFBgQFFgIIGDAwOBUpBgQFJ/jdmAQwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB7/////////////////////////////////////xkWYCCCAYBRe/////////////////////////////////////+DgYMWF4NSUFBQUGBAUYCCgFGQYCABkICDg1tgIIMQYQsMV4BRglJgIIIBkVBgIIEBkFBgIIMDklBhCulWW2ABg2AgA2EBAAoDgBmCURaBhFEWgIIXhVJQUFBQUFCQUAGRUFBgAGBAUYCDA4GFWvSRUFA9gGAAgRRhC2xXYEBRkVBgHxlgPz0BFoIBYEBSPYJSPWAAYCCEAT5hC3FWW2BgkVBbUJFQkVBgAGBgYAFgAJBUkGEBAAqQBHP//////////////////////////xZz//////////////////////////8WhWBAUWAkAYCCgVJgIAGRUFBgQFFgIIGDAwOBUpBgQFJ/jdmAQwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB7/////////////////////////////////////xkWYCCCAYBRe/////////////////////////////////////+DgYMWF4NSUFBQUGBAUYCCgFGQYCABkICDg1tgIIMQYQx0V4BRglJgIIIBkVBgIIEBkFBgIIMDklBhDFFWW2ABg2AgA2EBAAoDgBmCURaBhFEWgIIXhVJQUFBQUFCQUAGRUFBgAGBAUYCDA4GFWvSRUFA9gGAAgRRhDNRXYEBRkVBgHxlgPz0BFoIBYEBSPYJSPWAAYCCEAT5hDNlWW2BgkVBbUJFQkVCDFYBhDOlXUIEVWxVhDVxXYEBRfwjDeaAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgVJgBAGAgGAgAYKBA4JSYB6BUmAgAYB/RGVsZWdhdGUgdHJhbnNmZXIgY2FsbCBmYWlsZWQhAACBUlBgIAGRUFBgQFGAkQOQ/VtQUFBQUFb+omVienpyMVggYZ1VUlzRf3ObDLJthvfRjxVerEX5U3DZZN6jxw2xTW5kc29sY0MABREAMiKAAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAowJoMOgMYswlKFgoUAAAAAAAAAAAAAAAAAAAAAAAABLNyBwoDGLMJEAFSRwoJCgIYAxCu6rgUCgoKAhhiEJiS48oDCgoKAxigBhDy29M3CgoKAxihBhDy29M3CgsKAxitCRDJ0MTOBAoJCgMYswkQoJwB"},{"b64Body":"ChAKCQjg/v6sBhDCFRIDGK0JEgIYAxjgrLEDIgIIeDIgw4PCrsOCwrfDg8K5dEY4w4LCrkrDg8KLw4PCkMODwo46TwoDGLMJEKCNBiJEncfbEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAASuAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAnEA=","b64Record":"CiUIISIDGLMJKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjAurlXjs8fyaDqySfHS80vls2QQzNzrDU24QZFvdAjuvDA99U0JQ0tvFRyI52SWTfEaDAic//6sBhC8wPaXAyIQCgkI4P7+rAYQwhUSAxitCSogw4PCrsOCwrfDg8K5dEY4w4LCrkrDg8KLw4PCkMODwo4wgNfaAjoIGgIweCiA8QRSFwoJCgIYYhCArrUFCgoKAxitCRD/rbUF"}]},"depositMoreThanBalanceFailsGracefully":{"placeholderNum":1204,"encodedItems":[{"b64Body":"Cg8KCQjl/v6sBhDeFRICGAISAhgDGLXNziwiAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjooBjwESDAihzdmwBhCahL+fAhptCiISIAp7XcMPqNqKtDyqaLArcwLtRL7st005cKs87Uh6aaMPCiM6IQNBRBmq9NwgWzrn0hxPzWMOeREkVwvcnwSqYVe88LwIfwoiEiA6InIRQcq8Grfl2ox7ENS+TPvmvgjoC5eDgVEtIxxllSIMSGVsbG8gV29ybGQhKgAyAA==","b64Record":"CiUIFhoDGLUJKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjB7QvCqkDjTR2EFUhANCRUVTJwjpgSn9+CqEY47oBBLiOoBeDBIkaVK3rEq4CE+Xg4aDAih//6sBhCEr9e4AiIPCgkI5f7+rAYQ3hUSAhgCKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjlIA"},{"b64Body":"Cg8KCQjm/v6sBhDiFRICGAISAhgDGMe6szEiAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjpoBogkKAxi1CSKaCTYwODA2MDQwNTI2MTAyM2E4MDYxMDAxMzYwMDAzOTYwMDBmM2ZlNjA4MDYwNDA1MjYwMDQzNjEwNjEwMDNmNTc2MDAwMzU2MGUwMWM4MDYzMTIwNjVmZTAxNDYxMDA4ZjU3ODA2MzNjY2ZkNjBiMTQ2MTAwYmE1NzgwNjM2ZjY0MjM0ZTE0NjEwMGQxNTc4MDYzYjZiNTVmMjUxNDYxMDEyYzU3NWIzMzczZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjE2N2ZmMWIwM2Y3MDhiOWMzOWY0NTNmZTNmMGNlZjg0MTY0YzdkNmY3ZGY4MzZkZjA3OTZlMWU5YzJiY2U2ZWUzOTdlMzQ2MDQwNTE4MDgyODE1MjYwMjAwMTkxNTA1MDYwNDA1MTgwOTEwMzkwYTIwMDViMzQ4MDE1NjEwMDliNTc2MDAwODBmZDViNTA2MTAwYTQ2MTAxNWE1NjViNjA0MDUxODA4MjgxNTI2MDIwMDE5MTUwNTA2MDQwNTE4MDkxMDM5MGYzNWIzNDgwMTU2MTAwYzY1NzYwMDA4MGZkNWI1MDYxMDBjZjYxMDE2MjU2NWIwMDViMzQ4MDE1NjEwMGRkNTc2MDAwODBmZDViNTA2MTAxMmE2MDA0ODAzNjAzNjA0MDgxMTAxNTYxMDBmNDU3NjAwMDgwZmQ1YjgxMDE5MDgwODAzNTczZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjE2OTA2MDIwMDE5MDkyOTE5MDgwMzU5MDYwMjAwMTkwOTI5MTkwNTA1MDUwNjEwMWFiNTY1YjAwNWI2MTAxNTg2MDA0ODAzNjAzNjAyMDgxMTAxNTYxMDE0MjU3NjAwMDgwZmQ1YjgxMDE5MDgwODAzNTkwNjAyMDAxOTA5MjkxOTA1MDUwNTA2MTAxZjY1NjViMDA1YjYwMDA0NzkwNTA5MDU2NWIzMzczZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjE2NjEwOGZjNDc5MDgxMTUwMjkwNjA0MDUxNjAwMDYwNDA1MTgwODMwMzgxODU4ODg4ZjE5MzUwNTA1MDUwMTU4MDE1NjEwMWE4NTczZDYwMDA4MDNlM2Q2MDAwZmQ1YjUwNTY1YjgxNzNmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmMTY2MTA4ZmM4MjkwODExNTAyOTA2MDQwNTE2MDAwNjA0MDUxODA4MzAzODE4NTg4ODhmMTkzNTA1MDUwNTAxNTgwMTU2MTAxZjE1NzNkNjAwMDgwM2UzZDYwMDBmZDViNTA1MDUwNTY1YjgwMzQxNDYxMDIwMjU3NjAwMDgwZmQ1YjUwNTZmZWEyNjU2MjdhN2E3MjMxNTgyMGY4Zjg0ZmMzMWE4NDUwNjRiNTc4MWU5MDgzMTZmM2M1OTExNTc5NjJkZWFiYjBmZDQyNGVkNTRmMjU2NDAwZjk2NDczNmY2YzYzNDMwMDA1MTEwMDMy","b64Record":"CiAIFiocCgwIARAMGgYIgK6ZpA8SDAgBEA8aBgiArpmkDxIwNkw5kJY8gK7Ezbl1t1pzOh/J2+VminJgkqZcelHHBs2FB+8hiKpwQGkfMY0yX102GgsIov/+rAYQp8TUQCIPCgkI5v7+rAYQ4hUSAhgCKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjlIA"},{"b64Body":"Cg8KCQjm/v6sBhDkFRICGAISAhgDGPuV9hQiAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjlowCiISIAfx73siuN6gkN5jzF7uOmGaqA8YD6HpA/HEMWmreGwGEP/B1y9KBQiAztoD","b64Record":"CiUIFhIDGLYJKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjC/4/ifUuKv4PxsiKqQ37IF9HIUMRsd/CGsbvBRZG93N/MbQU56gsBV08CwiGoX18gaDAii//6sBhDJ/pHCAiIPCgkI5v7+rAYQ5BUSAhgCKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjlIXCgkKAhgCEP2Dr18KCgoDGLYJEP6Dr18="},{"b64Body":"Cg8KCQjn/v6sBhDmFRICGAISAhgDGK/Dto0EIgIIeDIgw4PCrsOCwrfDg8K5dEY4w4LCrkrDg8KLw4PCkMODwo5ClgEKAxi1CRpzKnEIAhJtCiISIE2WPy/kQCtcfD4PxulwBxa0oM//jSGPkWiTqgB+v+7cCiM6IQMOioCzwFkAojYwh9Z+KJ/w3CBgiH+M58dHWAE/RVwS1goiEiAThz+pkt/aZpcYMSTawyiZM18hMSILUI3nukdQ1TDyISCQoQ9CBQiAztoDUgBaAGoLY2VsbGFyIGRvb3I=","b64Record":"CiUIFiIDGLcJKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjBg50CwzCQ7jlRoAtn+VJZkl+bIUVaxLaVlCSAubGzDkd+Mzmo5ZVb9wR87n2BhRawaCwij//6sBhD0wZ1KIg8KCQjn/v6sBhDmFRICGAIqIMODwq7DgsK3w4PCuXRGOMOCwq5Kw4PCi8ODwpDDg8KOMMDZ4gZC7wYKAxi3CRK6BGCAYEBSYAQ2EGEAP1dgADVg4ByAYxIGX+AUYQCPV4BjPM/WCxRhALpXgGNvZCNOFGEA0VeAY7a1XyUUYQEsV1szc///////////////////////////Fn/xsD9wi5w59FP+PwzvhBZMfW99+DbfB5bh6cK85u45fjRgQFGAgoFSYCABkVBQYEBRgJEDkKIAWzSAFWEAm1dgAID9W1BhAKRhAVpWW2BAUYCCgVJgIAGRUFBgQFGAkQOQ81s0gBVhAMZXYACA/VtQYQDPYQFiVlsAWzSAFWEA3VdgAID9W1BhASpgBIA2A2BAgRAVYQD0V2AAgP1bgQGQgIA1c///////////////////////////FpBgIAGQkpGQgDWQYCABkJKRkFBQUGEBq1ZbAFthAVhgBIA2A2AggRAVYQFCV2AAgP1bgQGQgIA1kGAgAZCSkZBQUFBhAfZWWwBbYABHkFCQVlszc///////////////////////////FmEI/EeQgRUCkGBAUWAAYEBRgIMDgYWIiPGTUFBQUBWAFWEBqFc9YACAPj1gAP1bUFZbgXP//////////////////////////xZhCPyCkIEVApBgQFFgAGBAUYCDA4GFiIjxk1BQUFAVgBVhAfFXPWAAgD49YAD9W1BQUFZbgDQUYQICV2AAgP1bUFb+omVienpyMVgg+PhPwxqEUGS1eB6QgxbzxZEVeWLeq7D9Qk7VTyVkAPlkc29sY0MABREAMiKAAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAowJoMOgMYtwlKFgoUAAAAAAAAAAAAAAAAAAAAAAAABLdyBwoDGLcJEAFSFgoJCgIYAhD/ssUNCgkKAhhiEICzxQ0="}]},"LowLevelEcrecCallBehavior":{"placeholderNum":1208,"encodedItems":[{"b64Body":"Cg8KCQjr/v6sBhD4FRICGAISAhgDGP7LzSwiAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjooBjwESDAinzdmwBhDOg9S1AxptCiISIA3x5I1hqhZqHXwK/HATF2OI6Nhg4JtsgQbhotqD7QR0CiM6IQOg93FxCTxBWSLLUUFvwgx5b9C+e6/9TiqJ7Ek4sVBoVQoiEiBmHgXQQTjvftSdB4G7MeexD8WL8ZXi03V1fkbAT6CNGyIMSGVsbG8gV29ybGQhKgAyAA==","b64Record":"CiUIFhoDGLkJKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjDY3M+syHFEjdfOJo03xodeeHad08k79+Ata/1jbOpWZzp9Vooh2JH1oblrzFa3M+caDAin//6sBhCHnb7HAyIPCgkI6/7+rAYQ+BUSAhgCKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjlIA"},{"b64Body":"Cg8KCQjs/v6sBhD8FRICGAISAhgDGIudjj4iAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjpoBiCAKAxi5CSKAIDYwODA2MDQwNTI2MDQwNTE2MTBiZjMzODAzODA2MTBiZjM4MzM5ODE4MTAxNjA0MDUyODEwMTkwNjEwMDI1OTE5MDYxMDEwYzU2NWI4MTYwMDA4MDYxMDEwMDBhODE1NDgxNzNmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmMDIxOTE2OTA4MzczZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjE2MDIxNzkwNTU1MDgwNjAwMTgxOTA1NTUwNTA1MDYxMDE0YzU2NWI2MDAwODBmZDViNjAwMDczZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjgyMTY5MDUwOTE5MDUwNTY1YjYwMDA2MTAwYTM4MjYxMDA3ODU2NWI5MDUwOTE5MDUwNTY1YjYxMDBiMzgxNjEwMDk4NTY1YjgxMTQ2MTAwYmU1NzYwMDA4MGZkNWI1MDU2NWI2MDAwODE1MTkwNTA2MTAwZDA4MTYxMDBhYTU2NWI5MjkxNTA1MDU2NWI2MDAwODE5MDUwOTE5MDUwNTY1YjYxMDBlOTgxNjEwMGQ2NTY1YjgxMTQ2MTAwZjQ1NzYwMDA4MGZkNWI1MDU2NWI2MDAwODE1MTkwNTA2MTAxMDY4MTYxMDBlMDU2NWI5MjkxNTA1MDU2NWI2MDAwODA2MDQwODM4NTAzMTIxNTYxMDEyMzU3NjEwMTIyNjEwMDczNTY1YjViNjAwMDYxMDEzMTg1ODI4NjAxNjEwMGMxNTY1YjkyNTA1MDYwMjA2MTAxNDI4NTgyODYwMTYxMDBmNzU2NWI5MTUwNTA5MjUwOTI5MDUwNTY1YjYxMGE5ODgwNjEwMTViNjAwMDM5NjAwMGYzZmU2MDgwNjA0MDUyNjAwNDM2MTA2MTAwOWM1NzYwMDAzNTYwZTAxYzgwNjM3ZDNkMWMyZjExNjEwMDY0NTc4MDYzN2QzZDFjMmYxNDYxMDEzNDU3ODA2MzhkYTVjYjViMTQ2MTAxNWQ1NzgwNjM5MzkyNTAxNTE0NjEwMTg4NTc4MDYzOWEyZDhkNWMxNDYxMDFjNTU3ODA2M2MyOTg1NTc4MTQ2MTAxZjU1NzgwNjNkNWJkNWU0OTE0NjEwMjIwNTc2MTAwOWM1NjViODA2MzA0MTg3OTUxMTQ2MTAwYTE1NzgwNjMxMjA2NWZlMDE0NjEwMGFiNTc4MDYzNWUwZmY5MDgxNDYxMDBkNjU3ODA2MzcwYTA4MjMxMTQ2MTAwZTA1NzgwNjM3MzdiYzNjOTE0NjEwMTFkNTc1YjYwMDA4MGZkNWI2MTAwYTk2MTAyNWQ1NjViMDA1YjM0ODAxNTYxMDBiNzU3NjAwMDgwZmQ1YjUwNjEwMGMwNjEwM2U0NTY1YjYwNDA1MTYxMDBjZDkxOTA2MTA3ODk1NjViNjA0MDUxODA5MTAzOTBmMzViNjEwMGRlNjEwM2VjNTY1YjAwNWIzNDgwMTU2MTAwZWM1NzYwMDA4MGZkNWI1MDYxMDEwNzYwMDQ4MDM2MDM4MTAxOTA2MTAxMDI5MTkwNjEwODA3NTY1YjYxMDU3NTU2NWI2MDQwNTE2MTAxMTQ5MTkwNjEwNzg5NTY1YjYwNDA1MTgwOTEwMzkwZjM1YjM0ODAxNTYxMDEyOTU3NjAwMDgwZmQ1YjUwNjEwMTMyNjEwNTk2NTY1YjAwNWIzNDgwMTU2MTAxNDA1NzYwMDA4MGZkNWI1MDYxMDE1YjYwMDQ4MDM2MDM4MTAxOTA2MTAxNTY5MTkwNjEwODcyNTY1YjYxMDVjZjU2NWIwMDViMzQ4MDE1NjEwMTY5NTc2MDAwODBmZDViNTA2MTAxNzI2MTA1ZTg1NjViNjA0MDUxNjEwMTdmOTE5MDYxMDhhZTU2NWI2MDQwNTE4MDkxMDM5MGYzNWIzNDgwMTU2MTAxOTQ1NzYwMDA4MGZkNWI1MDYxMDFhZjYwMDQ4MDM2MDM4MTAxOTA2MTAxYWE5MTkwNjEwODA3NTY1YjYxMDYwYzU2NWI2MDQwNTE2MTAxYmM5MTkwNjEwOGU0NTY1YjYwNDA1MTgwOTEwMzkwZjM1YjYxMDFkZjYwMDQ4MDM2MDM4MTAxOTA2MTAxZGE5MTkwNjEwODA3NTY1YjYxMDY3ZjU2NWI2MDQwNTE2MTAxZWM5MTkwNjEwOGU0NTY1YjYwNDA1MTgwOTEwMzkwZjM1YjM0ODAxNTYxMDIwMTU3NjAwMDgwZmQ1YjUwNjEwMjBhNjEwNmY1NTY1YjYwNDA1MTYxMDIxNzkxOTA2MTA3ODk1NjViNjA0MDUxODA5MTAzOTBmMzViMzQ4MDE1NjEwMjJjNTc2MDAwODBmZDViNTA2MTAyNDc2MDA0ODAzNjAzODEwMTkwNjEwMjQyOTE5MDYxMDgwNzU2NWI2MTA2ZmI1NjViNjA0MDUxNjEwMjU0OTE5MDYxMDhlNDU2NWI2MDQwNTE4MDkxMDM5MGYzNWI2MDAwN2Y2ODYxNzM2ODAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwOTA1MDYwMDA2MDAxOTA1MDYwMDA3ZjcyMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDA5MDUwNjAwMDdmNzMwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDkwNTA2MDAwNjAwMTczZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjE2ODU4NTg1ODU2MDQwNTE2MDI0MDE2MTAzMDM5NDkzOTI5MTkwNjEwOTM0NTY1YjYwNDA1MTYwMjA4MTgzMDMwMzgxNTI5MDYwNDA1MjdmOTZkMTA3ZjYwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDdiZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxOTE2NjAyMDgyMDE4MDUxN2JmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjgzODE4MzE2MTc4MzUyNTA1MDUwNTA2MDQwNTE2MTAzOGQ5MTkwNjEwOWVhNTY1YjYwMDA2MDQwNTE4MDgzMDM4MTYwMDA4NjVhZjE5MTUwNTAzZDgwNjAwMDgxMTQ2MTAzY2E1NzYwNDA1MTkxNTA2MDFmMTk2MDNmM2QwMTE2ODIwMTYwNDA1MjNkODI1MjNkNjAwMDYwMjA4NDAxM2U2MTAzY2Y1NjViNjA2MDkxNTA1YjUwNTA5MDUwODA2MTAzZGQ1NzYwMDA4MGZkNWI1MDUwNTA1MDUwNTY1YjYwMDA0NzkwNTA5MDU2NWI2MDAwN2Y2ODYxNzM2ODAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwOTA1MDYwMDA2MDAxOTA1MDYwMDA3ZjcyMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDA5MDUwNjAwMDdmNzMwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDkwNTA2MDAwNjAwMTczZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjE2NjAwMTg2ODY4Njg2NjA0MDUxNjAyNDAxNjEwNDk0OTQ5MzkyOTE5MDYxMDkzNDU2NWI2MDQwNTE2MDIwODE4MzAzMDM4MTUyOTA2MDQwNTI3Zjk2ZDEwN2Y2MDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDA3YmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmMTkxNjYwMjA4MjAxODA1MTdiZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmY4MzgxODMxNjE3ODM1MjUwNTA1MDUwNjA0MDUxNjEwNTFlOTE5MDYxMDllYTU2NWI2MDAwNjA0MDUxODA4MzAzODE4NTg3NWFmMTkyNTA1MDUwM2Q4MDYwMDA4MTE0NjEwNTViNTc2MDQwNTE5MTUwNjAxZjE5NjAzZjNkMDExNjgyMDE2MDQwNTIzZDgyNTIzZDYwMDA2MDIwODQwMTNlNjEwNTYwNTY1YjYwNjA5MTUwNWI1MDUwOTA1MDgwNjEwNTZlNTc2MDAwODBmZDViNTA1MDUwNTA1MDU2NWI2MDAwODE3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNjMxOTA1MDkxOTA1MDU2NWI2MDAwODA1NDkwNjEwMTAwMGE5MDA0NzNmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmMTY3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNmZmNWI4MDczZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjE2ZmY1YjYwMDA4MDU0OTA2MTAxMDAwYTkwMDQ3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNjgxNTY1YjYwMDA4MDgyNzNmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmMTY2MDQwNTE2MTA2MzI5MDYxMGE0ZDU2NWI2MDAwNjA0MDUxODA4MzAzODE4NTVhZjQ5MTUwNTAzZDgwNjAwMDgxMTQ2MTA2NmQ1NzYwNDA1MTkxNTA2MDFmMTk2MDNmM2QwMTE2ODIwMTYwNDA1MjNkODI1MjNkNjAwMDYwMjA4NDAxM2U2MTA2NzI1NjViNjA2MDkxNTA1YjUwNTA5MDUwODA5MTUwNTA5MTkwNTA1NjViNjAwMDgwODI3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNjM0NjA0MDUxNjEwNmE2OTA2MTBhNGQ=","b64Record":"CiAIFiocCgwIARAMGgYIgK6ZpA8SDAgBEA8aBgiArpmkDxIwuaAv0qR+u+bRENN9GxKsKbvQ0ZcoFP0uykGyMXOpLzDjD2UZSsM9V1Y3gJsdl23vGgwIqP/+rAYQwODUzwEiDwoJCOz+/qwGEPwVEgIYAiogw4PCrsOCwrfDg8K5dEY4w4LCrkrDg8KLw4PCkMODwo5SAA=="},{"b64Body":"Cg8KCQjs/v6sBhCCFhICGAISAhgDGPeT3jUiAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjoIB7g8SAxi5CSLmDzU2NWI2MDAwNjA0MDUxODA4MzAzODE4NTg3NWFmMTkyNTA1MDUwM2Q4MDYwMDA4MTE0NjEwNmUzNTc2MDQwNTE5MTUwNjAxZjE5NjAzZjNkMDExNjgyMDE2MDQwNTIzZDgyNTIzZDYwMDA2MDIwODQwMTNlNjEwNmU4NTY1YjYwNjA5MTUwNWI1MDUwOTA1MDgwOTE1MDUwOTE5MDUwNTY1YjYwMDE1NDgxNTY1YjYwMDA4MDgyNzNmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmMTY2MDQwNTE2MTA3MjE5MDYxMGE0ZDU2NWI2MDAwNjA0MDUxODA4MzAzODE2MDAwODY1YWYxOTE1MDUwM2Q4MDYwMDA4MTE0NjEwNzVlNTc2MDQwNTE5MTUwNjAxZjE5NjAzZjNkMDExNjgyMDE2MDQwNTIzZDgyNTIzZDYwMDA2MDIwODQwMTNlNjEwNzYzNTY1YjYwNjA5MTUwNWI1MDUwOTA1MDgwOTE1MDUwOTE5MDUwNTY1YjYwMDA4MTkwNTA5MTkwNTA1NjViNjEwNzgzODE2MTA3NzA1NjViODI1MjUwNTA1NjViNjAwMDYwMjA4MjAxOTA1MDYxMDc5ZTYwMDA4MzAxODQ2MTA3N2E1NjViOTI5MTUwNTA1NjViNjAwMDgwZmQ1YjYwMDA3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmY4MjE2OTA1MDkxOTA1MDU2NWI2MDAwNjEwN2Q0ODI2MTA3YTk1NjViOTA1MDkxOTA1MDU2NWI2MTA3ZTQ4MTYxMDdjOTU2NWI4MTE0NjEwN2VmNTc2MDAwODBmZDViNTA1NjViNjAwMDgxMzU5MDUwNjEwODAxODE2MTA3ZGI1NjViOTI5MTUwNTA1NjViNjAwMDYwMjA4Mjg0MDMxMjE1NjEwODFkNTc2MTA4MWM2MTA3YTQ1NjViNWI2MDAwNjEwODJiODQ4Mjg1MDE2MTA3ZjI1NjViOTE1MDUwOTI5MTUwNTA1NjViNjAwMDYxMDgzZjgyNjEwN2E5NTY1YjkwNTA5MTkwNTA1NjViNjEwODRmODE2MTA4MzQ1NjViODExNDYxMDg1YTU3NjAwMDgwZmQ1YjUwNTY1YjYwMDA4MTM1OTA1MDYxMDg2YzgxNjEwODQ2NTY1YjkyOTE1MDUwNTY1YjYwMDA2MDIwODI4NDAzMTIxNTYxMDg4ODU3NjEwODg3NjEwN2E0NTY1YjViNjAwMDYxMDg5Njg0ODI4NTAxNjEwODVkNTY1YjkxNTA1MDkyOTE1MDUwNTY1YjYxMDhhODgxNjEwN2M5NTY1YjgyNTI1MDUwNTY1YjYwMDA2MDIwODIwMTkwNTA2MTA4YzM2MDAwODMwMTg0NjEwODlmNTY1YjkyOTE1MDUwNTY1YjYwMDA4MTE1MTU5MDUwOTE5MDUwNTY1YjYxMDhkZTgxNjEwOGM5NTY1YjgyNTI1MDUwNTY1YjYwMDA2MDIwODIwMTkwNTA2MTA4Zjk2MDAwODMwMTg0NjEwOGQ1NTY1YjkyOTE1MDUwNTY1YjYwMDA4MTkwNTA5MTkwNTA1NjViNjEwOTEyODE2MTA4ZmY1NjViODI1MjUwNTA1NjViNjAwMDYwZmY4MjE2OTA1MDkxOTA1MDU2NWI2MTA5MmU4MTYxMDkxODU2NWI4MjUyNTA1MDU2NWI2MDAwNjA4MDgyMDE5MDUwNjEwOTQ5NjAwMDgzMDE4NzYxMDkwOTU2NWI2MTA5NTY2MDIwODMwMTg2NjEwOTI1NTY1YjYxMDk2MzYwNDA4MzAxODU2MTA5MDk1NjViNjEwOTcwNjA2MDgzMDE4NDYxMDkwOTU2NWI5NTk0NTA1MDUwNTA1MDU2NWI2MDAwODE1MTkwNTA5MTkwNTA1NjViNjAwMDgxOTA1MDkyOTE1MDUwNTY1YjYwMDA1YjgzODExMDE1NjEwOWFkNTc4MDgyMDE1MTgxODQwMTUyNjAyMDgxMDE5MDUwNjEwOTkyNTY1YjYwMDA4NDg0MDE1MjUwNTA1MDUwNTY1YjYwMDA2MTA5YzQ4MjYxMDk3OTU2NWI2MTA5Y2U4MTg1NjEwOTg0NTY1YjkzNTA2MTA5ZGU4MTg1NjAyMDg2MDE2MTA5OGY1NjViODA4NDAxOTE1MDUwOTI5MTUwNTA1NjViNjAwMDYxMDlmNjgyODQ2MTA5Yjk1NjViOTE1MDgxOTA1MDkyOTE1MDUwNTY1YjdmNjI2ZjZmMjg3NTY5NmU3NDMyMzUzNjI5MDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDYwMDA4MjAxNTI1MDU2NWI2MDAwNjEwYTM3NjAwYzgzNjEwOTg0NTY1YjkxNTA2MTBhNDI4MjYxMGEwMTU2NWI2MDBjODIwMTkwNTA5MTkwNTA1NjViNjAwMDYxMGE1ODgyNjEwYTJhNTY1YjkxNTA4MTkwNTA5MTkwNTA1NmZlYTI2NDY5NzA2NjczNTgyMjEyMjBlNWUwOWYyYzA3NGFmMDRlNWExNGJhMDkwMGUwZTg4MDE4NjgwZGQ3NDcwYmMyNjg4ODZlYWNiMDdmYzNlZDljNjQ3MzZmNmM2MzQzMDAwODEzMDAzMw==","b64Record":"CiAIFiocCgwIARAMGgYIgK6ZpA8SDAgBEA8aBgiArpmkDxIw1xUzZmpdID6m7uiw5+v6h10HfhtOrQZJru3Hwl259KxsuW00GjO28MP7Y4aDUzn8GgwIqP/+rAYQv7uV0QMiDwoJCOz+/qwGEIIWEgIYAiogw4PCrsOCwrfDg8K5dEY4w4LCrkrDg8KLw4PCkMODwo5SAA=="},{"b64Body":"Cg8KCQjt/v6sBhCEFhICGAISAhgDGNWAv6ACIgIIeDIgw4PCrsOCwrfDg8K5dEY4w4LCrkrDg8KLw4PCkMODwo5CjAEKAxi5CRoiEiAmRxO48gDohSidiGElGYWDtcjX7CcagU7Qi5Xe7LqZiiCQoQ8ogMLXL0IFCIDO2gNKQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFSAFoAagtjZWxsYXIgZG9vcg==","b64Record":"CiUIFiIDGLoJKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjBjrkie8Y2AuPuOvcvuKEitlx31UDlkNPJshyMwyCxk9Iapz7wIf/fNVpDTa2y5fOMaDAip//6sBhDH27vZASIPCgkI7f7+rAYQhBYSAhgCKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjjDA2eIGQs0XCgMYugkSmBVggGBAUmAENhBhAJxXYAA1YOAcgGN9PRwvEWEAZFeAY309HC8UYQE0V4BjjaXLWxRhAV1XgGOTklAVFGEBiFeAY5otjVwUYQHFV4BjwphVeBRhAfVXgGPVvV5JFGECIFdhAJxWW4BjBBh5URRhAKFXgGMSBl/gFGEAq1eAY14P+QgUYQDWV4BjcKCCMRRhAOBXgGNze8PJFGEBHVdbYACA/VthAKlhAl1WWwBbNIAVYQC3V2AAgP1bUGEAwGED5FZbYEBRYQDNkZBhB4lWW2BAUYCRA5DzW2EA3mED7FZbAFs0gBVhAOxXYACA/VtQYQEHYASANgOBAZBhAQKRkGEIB1ZbYQV1VltgQFFhARSRkGEHiVZbYEBRgJEDkPNbNIAVYQEpV2AAgP1bUGEBMmEFllZbAFs0gBVhAUBXYACA/VtQYQFbYASANgOBAZBhAVaRkGEIclZbYQXPVlsAWzSAFWEBaVdgAID9W1BhAXJhBehWW2BAUWEBf5GQYQiuVltgQFGAkQOQ81s0gBVhAZRXYACA/VtQYQGvYASANgOBAZBhAaqRkGEIB1ZbYQYMVltgQFFhAbyRkGEI5FZbYEBRgJEDkPNbYQHfYASANgOBAZBhAdqRkGEIB1ZbYQZ/VltgQFFhAeyRkGEI5FZbYEBRgJEDkPNbNIAVYQIBV2AAgP1bUGECCmEG9VZbYEBRYQIXkZBhB4lWW2BAUYCRA5DzWzSAFWECLFdgAID9W1BhAkdgBIA2A4EBkGECQpGQYQgHVlthBvtWW2BAUWECVJGQYQjkVltgQFGAkQOQ81tgAH9oYXNoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAJBQYABgAZBQYAB/cgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACQUGAAf3MAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAkFBgAGABc///////////////////////////FoWFhYVgQFFgJAFhAwOUk5KRkGEJNFZbYEBRYCCBgwMDgVKQYEBSf5bRB/YAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAe/////////////////////////////////////8ZFmAgggGAUXv/////////////////////////////////////g4GDFheDUlBQUFBgQFFhA42RkGEJ6lZbYABgQFGAgwOBYACGWvGRUFA9gGAAgRRhA8pXYEBRkVBgHxlgPz0BFoIBYEBSPYJSPWAAYCCEAT5hA89WW2BgkVBbUFCQUIBhA91XYACA/VtQUFBQUFZbYABHkFCQVltgAH9oYXNoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAJBQYABgAZBQYAB/cgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACQUGAAf3MAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAkFBgAGABc///////////////////////////FmABhoaGhmBAUWAkAWEElJSTkpGQYQk0VltgQFFgIIGDAwOBUpBgQFJ/ltEH9gAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB7/////////////////////////////////////xkWYCCCAYBRe/////////////////////////////////////+DgYMWF4NSUFBQUGBAUWEFHpGQYQnqVltgAGBAUYCDA4GFh1rxklBQUD2AYACBFGEFW1dgQFGRUGAfGWA/PQEWggFgQFI9glI9YABgIIQBPmEFYFZbYGCRUFtQUJBQgGEFbldgAID9W1BQUFBQVltgAIFz//////////////////////////8WMZBQkZBQVltgAIBUkGEBAAqQBHP//////////////////////////xZz//////////////////////////8W/1uAc///////////////////////////Fv9bYACAVJBhAQAKkARz//////////////////////////8WgVZbYACAgnP//////////////////////////xZgQFFhBjKQYQpNVltgAGBAUYCDA4GFWvSRUFA9gGAAgRRhBm1XYEBRkVBgHxlgPz0BFoIBYEBSPYJSPWAAYCCEAT5hBnJWW2BgkVBbUFCQUICRUFCRkFBWW2AAgIJz//////////////////////////8WNGBAUWEGppBhCk1WW2AAYEBRgIMDgYWHWvGSUFBQPYBgAIEUYQbjV2BAUZFQYB8ZYD89ARaCAWBAUj2CUj1gAGAghAE+YQboVltgYJFQW1BQkFCAkVBQkZBQVltgAVSBVltgAICCc///////////////////////////FmBAUWEHIZBhCk1WW2AAYEBRgIMDgWAAhlrxkVBQPYBgAIEUYQdeV2BAUZFQYB8ZYD89ARaCAWBAUj2CUj1gAGAghAE+YQdjVltgYJFQW1BQkFCAkVBQkZBQVltgAIGQUJGQUFZbYQeDgWEHcFZbglJQUFZbYABgIIIBkFBhB55gAIMBhGEHelZbkpFQUFZbYACA/VtgAHP//////////////////////////4IWkFCRkFBWW2AAYQfUgmEHqVZbkFCRkFBWW2EH5IFhB8lWW4EUYQfvV2AAgP1bUFZbYACBNZBQYQgBgWEH21ZbkpFQUFZbYABgIIKEAxIVYQgdV2EIHGEHpFZbW2AAYQgrhIKFAWEH8lZbkVBQkpFQUFZbYABhCD+CYQepVluQUJGQUFZbYQhPgWEINFZbgRRhCFpXYACA/VtQVltgAIE1kFBhCGyBYQhGVluSkVBQVltgAGAggoQDEhVhCIhXYQiHYQekVltbYABhCJaEgoUBYQhdVluRUFCSkVBQVlthCKiBYQfJVluCUlBQVltgAGAgggGQUGEIw2AAgwGEYQifVluSkVBQVltgAIEVFZBQkZBQVlthCN6BYQjJVluCUlBQVltgAGAgggGQUGEI+WAAgwGEYQjVVluSkVBQVltgAIGQUJGQUFZbYQkSgWEI/1ZbglJQUFZbYABg/4IWkFCRkFBWW2EJLoFhCRhWW4JSUFBWW2AAYICCAZBQYQlJYACDAYdhCQlWW2EJVmAggwGGYQklVlthCWNgQIMBhWEJCVZbYQlwYGCDAYRhCQlWW5WUUFBQUFBWW2AAgVGQUJGQUFZbYACBkFCSkVBQVltgAFuDgRAVYQmtV4CCAVGBhAFSYCCBAZBQYQmSVltgAISEAVJQUFBQVltgAGEJxIJhCXlWW2EJzoGFYQmEVluTUGEJ3oGFYCCGAWEJj1ZbgIQBkVBQkpFQUFZbYABhCfaChGEJuVZbkVCBkFCSkVBQVlt/Ym9vKHVpbnQyNTYpAAAAAAAAAAAAAAAAAAAAAAAAAABgAIIBUlBWW2AAYQo3YAyDYQmEVluRUGEKQoJhCgFWW2AMggGQUJGQUFZbYABhCliCYQoqVluRUIGQUJGQUFb+omRpcGZzWCISIOXgnywHSvBOWhS6CQDg6IAYaA3XRwvCaIhurLB/w+2cZHNvbGNDAAgTADMigAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKMCaDDoDGLoJShYKFAAAAAAAAAAAAAAAAAAAAAAAAAS6cgcKAxi6CRABUiIKCQoCGAIQ/7b0bAoJCgIYYhCAs8UNCgoKAxi6CRCAhK9f"},{"b64Body":"Cg8KCQjt/v6sBhCGFhICGAISAhgDGPuV9hQiAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjloxCiISIBv/Yyr64KJ2eGQfOYMEMATB5vHHlTzmHGrSdycR5YKVEICU69wDSgUIgM7aAw==","b64Record":"CiUIFhIDGLsJKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjAYoKEhg36J1kYq7OcKOvvVyjGmtsnDtPqR2aLB2PZgMro1vVfM1dlIIGMNx6at5ocaDAip//6sBhCihNTaAyIPCgkI7f7+rAYQhhYSAhgCKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjlIZCgoKAhgCEP+n1rkHCgsKAxi7CRCAqNa5Bw=="},{"b64Body":"Cg8KCQju/v6sBhCMFhICGAISAhgDGK32CiICCHgyIMODwq7DgsK3w4PCuXRGOMOCwq5Kw4PCi8ODwpDDg8KOeggSAhgBagIIAQ==","b64Record":"CiAIFiocCgwIARAMGgYIgK6ZpA8SDAgBEA8aBgiArpmkDxIwqbfLBLkeoKqiSln2tfrRlRLEmJAOMOtt8dZLgmnaDuraGdF+PXrEkZDOPTTLjYeJGgwIqv/+rAYQmdvN4gEiDwoJCO7+/qwGEIwWEgIYAiogw4PCrsOCwrfDg8K5dEY4w4LCrkrDg8KLw4PCkMODwo5SAA=="},{"b64Body":"ChAKCQju/v6sBhCOFhIDGLsJEgIYAxjgrLEDIgIIeDIgw4PCrsOCwrfDg8K5dEY4w4LCrkrDg8KLw4PCkMODwo46DwoDGLoJEKCNBiIEBBh5UQ==","b64Record":"CiUIFiIDGLoJKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjDqIUUMx2hR34L1asgwiJ4wQzV9vBN7RI78AKBtI7vdBFS3izGPHWUDAN74WoG0wj8aCwir//6sBhCj77QHIhAKCQju/v6sBhCOFhIDGLsJKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjjCA19oCOowCCgMYugkigAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKIDxBFIXCgkKAhhiEICutQUKCgoDGLsJEP+ttQU="},{"b64Body":"ChAKCQjv/v6sBhCQFhIDGLsJEgIYAxjgrLEDIgIIeDIgw4PCrsOCwrfDg8K5dEY4w4LCrkrDg8KLw4PCkMODwo46DwoDGLoJEKCNBiIEXg/5CA==","b64Record":"CiUIISIDGLoJKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjAfmJv7qLC98eEjOUR95FfcSYeanfn+qpB8K4Ftb0SsNT1TjUMoamjJtjTgB5PjZ6EaDAir//6sBhD/pJfxASIQCgkI7/7+rAYQkBYSAxi7CSogw4PCrsOCwrfDg8K5dEY4w4LCrkrDg8KLw4PCkMODwo4wv+CuAzoIGgIweCjJiAZSFwoJCgIYYhD+wN0GCgoKAxi7CRD9wN0G"},{"b64Body":"Cg8KCQjv/v6sBhCWFhICGAISAhgDGK32CiICCHgyIMODwq7DgsK3w4PCuXRGOMOCwq5Kw4PCi8ODwpDDg8KOegYSAhgBagA=","b64Record":"CiAIFiocCgwIARAMGgYIgK6ZpA8SDAgBEA8aBgiArpmkDxIwZrTPpuX5b+V25vvoPKHqsIi6Q8tCdHPr0OiateSL8zNuJCVkMsxSrcdqsIpZ/d21GgwIq//+rAYQ8p701QMiDwoJCO/+/qwGEJYWEgIYAiogw4PCrsOCwrfDg8K5dEY4w4LCrkrDg8KLw4PCkMODwo5SAA=="},{"b64Body":"ChAKCQjw/v6sBhCYFhIDGLsJEgIYAxjgrLEDIgIIeDIgw4PCrsOCwrfDg8K5dEY4w4LCrkrDg8KLw4PCkMODwo46DwoDGLoJEKCNBiIEBBh5UQ==","b64Record":"CiUIFiIDGLoJKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjB9+jX8/cHIB18Vi5WA2GeqaT8z4mohG6DZgStqa3U1c7FYpyCrRCrTM2AQFOGAzZQaDAis//6sBhCYwOn6ASIQCgkI8P7+rAYQmBYSAxi7CSogw4PCrsOCwrfDg8K5dEY4w4LCrkrDg8KLw4PCkMODwo4wgNfaAjqMAgoDGLoJIoACAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACiA8QRSFwoJCgIYYhCArrUFCgoKAxi7CRD/rbUF"},{"b64Body":"ChAKCQjw/v6sBhCaFhIDGLsJEgIYAxjgrLEDIgIIeDIgw4PCrsOCwrfDg8K5dEY4w4LCrkrDg8KLw4PCkMODwo46DwoDGLoJEKCNBiIEXg/5CA==","b64Record":"CiUIISIDGLoJKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjBiTbV+N/BaM8p5qQdSxbfk6vF/JX2C9ZS9dyD4SZsrQ4a04iYTzlEY7p+/iA7e+yYaCwit//6sBhCy/94CIhAKCQjw/v6sBhCaFhIDGLsJKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjjC/4K4DOggaAjB4KMmIBlIXCgkKAhhiEP7A3QYKCgoDGLsJEP3A3QY="}]},"callsToSystemEntityNumsAreTreatedAsPrecompileCalls":{"placeholderNum":1212,"encodedItems":[{"b64Body":"Cg8KCQj1/v6sBhCqFhICGAISAhgDGLXNziwiAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjooBjwESDAixzdmwBhDMmeLeAhptCiISIAM28Vj5H7U+O2iKUajc/wJQ3ZqR4/V+ltnYwahrJDmfCiM6IQNKYNP5wdnYjfOZGUnEcktMrFENoEdVXp5/F+R6iqC8TQoiEiA0svt3uDmgeLDac6cRgrMAFrz4M0gJGLmLHk5EWgzJnSIMSGVsbG8gV29ybGQhKgAyAA==","b64Record":"CiUIFhoDGL0JKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjDInPS/jlIqUMYI1U0RhWjqUkFELTB6/jvARgqQOIi5h2H1dDGIkb2Ik0qqxlHMeI4aDAix//6sBhCx7YroAiIPCgkI9f7+rAYQqhYSAhgCKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjlIA"},{"b64Body":"Cg8KCQj2/v6sBhCuFhICGAISAhgDGNyejz4iAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjpoBiCAKAxi9CSKAIDYwODA2MDQwNTI2MDQwNTE2MTBiZjMzODAzODA2MTBiZjM4MzM5ODE4MTAxNjA0MDUyODEwMTkwNjEwMDI1OTE5MDYxMDEwYzU2NWI4MTYwMDA4MDYxMDEwMDBhODE1NDgxNzNmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmMDIxOTE2OTA4MzczZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjE2MDIxNzkwNTU1MDgwNjAwMTgxOTA1NTUwNTA1MDYxMDE0YzU2NWI2MDAwODBmZDViNjAwMDczZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjgyMTY5MDUwOTE5MDUwNTY1YjYwMDA2MTAwYTM4MjYxMDA3ODU2NWI5MDUwOTE5MDUwNTY1YjYxMDBiMzgxNjEwMDk4NTY1YjgxMTQ2MTAwYmU1NzYwMDA4MGZkNWI1MDU2NWI2MDAwODE1MTkwNTA2MTAwZDA4MTYxMDBhYTU2NWI5MjkxNTA1MDU2NWI2MDAwODE5MDUwOTE5MDUwNTY1YjYxMDBlOTgxNjEwMGQ2NTY1YjgxMTQ2MTAwZjQ1NzYwMDA4MGZkNWI1MDU2NWI2MDAwODE1MTkwNTA2MTAxMDY4MTYxMDBlMDU2NWI5MjkxNTA1MDU2NWI2MDAwODA2MDQwODM4NTAzMTIxNTYxMDEyMzU3NjEwMTIyNjEwMDczNTY1YjViNjAwMDYxMDEzMTg1ODI4NjAxNjEwMGMxNTY1YjkyNTA1MDYwMjA2MTAxNDI4NTgyODYwMTYxMDBmNzU2NWI5MTUwNTA5MjUwOTI5MDUwNTY1YjYxMGE5ODgwNjEwMTViNjAwMDM5NjAwMGYzZmU2MDgwNjA0MDUyNjAwNDM2MTA2MTAwOWM1NzYwMDAzNTYwZTAxYzgwNjM3ZDNkMWMyZjExNjEwMDY0NTc4MDYzN2QzZDFjMmYxNDYxMDEzNDU3ODA2MzhkYTVjYjViMTQ2MTAxNWQ1NzgwNjM5MzkyNTAxNTE0NjEwMTg4NTc4MDYzOWEyZDhkNWMxNDYxMDFjNTU3ODA2M2MyOTg1NTc4MTQ2MTAxZjU1NzgwNjNkNWJkNWU0OTE0NjEwMjIwNTc2MTAwOWM1NjViODA2MzA0MTg3OTUxMTQ2MTAwYTE1NzgwNjMxMjA2NWZlMDE0NjEwMGFiNTc4MDYzNWUwZmY5MDgxNDYxMDBkNjU3ODA2MzcwYTA4MjMxMTQ2MTAwZTA1NzgwNjM3MzdiYzNjOTE0NjEwMTFkNTc1YjYwMDA4MGZkNWI2MTAwYTk2MTAyNWQ1NjViMDA1YjM0ODAxNTYxMDBiNzU3NjAwMDgwZmQ1YjUwNjEwMGMwNjEwM2U0NTY1YjYwNDA1MTYxMDBjZDkxOTA2MTA3ODk1NjViNjA0MDUxODA5MTAzOTBmMzViNjEwMGRlNjEwM2VjNTY1YjAwNWIzNDgwMTU2MTAwZWM1NzYwMDA4MGZkNWI1MDYxMDEwNzYwMDQ4MDM2MDM4MTAxOTA2MTAxMDI5MTkwNjEwODA3NTY1YjYxMDU3NTU2NWI2MDQwNTE2MTAxMTQ5MTkwNjEwNzg5NTY1YjYwNDA1MTgwOTEwMzkwZjM1YjM0ODAxNTYxMDEyOTU3NjAwMDgwZmQ1YjUwNjEwMTMyNjEwNTk2NTY1YjAwNWIzNDgwMTU2MTAxNDA1NzYwMDA4MGZkNWI1MDYxMDE1YjYwMDQ4MDM2MDM4MTAxOTA2MTAxNTY5MTkwNjEwODcyNTY1YjYxMDVjZjU2NWIwMDViMzQ4MDE1NjEwMTY5NTc2MDAwODBmZDViNTA2MTAxNzI2MTA1ZTg1NjViNjA0MDUxNjEwMTdmOTE5MDYxMDhhZTU2NWI2MDQwNTE4MDkxMDM5MGYzNWIzNDgwMTU2MTAxOTQ1NzYwMDA4MGZkNWI1MDYxMDFhZjYwMDQ4MDM2MDM4MTAxOTA2MTAxYWE5MTkwNjEwODA3NTY1YjYxMDYwYzU2NWI2MDQwNTE2MTAxYmM5MTkwNjEwOGU0NTY1YjYwNDA1MTgwOTEwMzkwZjM1YjYxMDFkZjYwMDQ4MDM2MDM4MTAxOTA2MTAxZGE5MTkwNjEwODA3NTY1YjYxMDY3ZjU2NWI2MDQwNTE2MTAxZWM5MTkwNjEwOGU0NTY1YjYwNDA1MTgwOTEwMzkwZjM1YjM0ODAxNTYxMDIwMTU3NjAwMDgwZmQ1YjUwNjEwMjBhNjEwNmY1NTY1YjYwNDA1MTYxMDIxNzkxOTA2MTA3ODk1NjViNjA0MDUxODA5MTAzOTBmMzViMzQ4MDE1NjEwMjJjNTc2MDAwODBmZDViNTA2MTAyNDc2MDA0ODAzNjAzODEwMTkwNjEwMjQyOTE5MDYxMDgwNzU2NWI2MTA2ZmI1NjViNjA0MDUxNjEwMjU0OTE5MDYxMDhlNDU2NWI2MDQwNTE4MDkxMDM5MGYzNWI2MDAwN2Y2ODYxNzM2ODAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwOTA1MDYwMDA2MDAxOTA1MDYwMDA3ZjcyMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDA5MDUwNjAwMDdmNzMwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDkwNTA2MDAwNjAwMTczZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjE2ODU4NTg1ODU2MDQwNTE2MDI0MDE2MTAzMDM5NDkzOTI5MTkwNjEwOTM0NTY1YjYwNDA1MTYwMjA4MTgzMDMwMzgxNTI5MDYwNDA1MjdmOTZkMTA3ZjYwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDdiZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxOTE2NjAyMDgyMDE4MDUxN2JmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjgzODE4MzE2MTc4MzUyNTA1MDUwNTA2MDQwNTE2MTAzOGQ5MTkwNjEwOWVhNTY1YjYwMDA2MDQwNTE4MDgzMDM4MTYwMDA4NjVhZjE5MTUwNTAzZDgwNjAwMDgxMTQ2MTAzY2E1NzYwNDA1MTkxNTA2MDFmMTk2MDNmM2QwMTE2ODIwMTYwNDA1MjNkODI1MjNkNjAwMDYwMjA4NDAxM2U2MTAzY2Y1NjViNjA2MDkxNTA1YjUwNTA5MDUwODA2MTAzZGQ1NzYwMDA4MGZkNWI1MDUwNTA1MDUwNTY1YjYwMDA0NzkwNTA5MDU2NWI2MDAwN2Y2ODYxNzM2ODAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwOTA1MDYwMDA2MDAxOTA1MDYwMDA3ZjcyMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDA5MDUwNjAwMDdmNzMwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDkwNTA2MDAwNjAwMTczZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjE2NjAwMTg2ODY4Njg2NjA0MDUxNjAyNDAxNjEwNDk0OTQ5MzkyOTE5MDYxMDkzNDU2NWI2MDQwNTE2MDIwODE4MzAzMDM4MTUyOTA2MDQwNTI3Zjk2ZDEwN2Y2MDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDA3YmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmMTkxNjYwMjA4MjAxODA1MTdiZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmY4MzgxODMxNjE3ODM1MjUwNTA1MDUwNjA0MDUxNjEwNTFlOTE5MDYxMDllYTU2NWI2MDAwNjA0MDUxODA4MzAzODE4NTg3NWFmMTkyNTA1MDUwM2Q4MDYwMDA4MTE0NjEwNTViNTc2MDQwNTE5MTUwNjAxZjE5NjAzZjNkMDExNjgyMDE2MDQwNTIzZDgyNTIzZDYwMDA2MDIwODQwMTNlNjEwNTYwNTY1YjYwNjA5MTUwNWI1MDUwOTA1MDgwNjEwNTZlNTc2MDAwODBmZDViNTA1MDUwNTA1MDU2NWI2MDAwODE3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNjMxOTA1MDkxOTA1MDU2NWI2MDAwODA1NDkwNjEwMTAwMGE5MDA0NzNmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmMTY3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNmZmNWI4MDczZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjE2ZmY1YjYwMDA4MDU0OTA2MTAxMDAwYTkwMDQ3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNjgxNTY1YjYwMDA4MDgyNzNmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmMTY2MDQwNTE2MTA2MzI5MDYxMGE0ZDU2NWI2MDAwNjA0MDUxODA4MzAzODE4NTVhZjQ5MTUwNTAzZDgwNjAwMDgxMTQ2MTA2NmQ1NzYwNDA1MTkxNTA2MDFmMTk2MDNmM2QwMTE2ODIwMTYwNDA1MjNkODI1MjNkNjAwMDYwMjA4NDAxM2U2MTA2NzI1NjViNjA2MDkxNTA1YjUwNTA5MDUwODA5MTUwNTA5MTkwNTA1NjViNjAwMDgwODI3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNjM0NjA0MDUxNjEwNmE2OTA2MTBhNGQ=","b64Record":"CiAIFiocCgwIARAMGgYIgK6ZpA8SDAgBEA8aBgiArpmkDxIwitqFop+OwWIMssa8oVrCvbyAYVutu254D7fAz52igbhYujWMg3C0NzaGvnNCuviNGgwIsv/+rAYQwN3yjAEiDwoJCPb+/qwGEK4WEgIYAiogw4PCrsOCwrfDg8K5dEY4w4LCrkrDg8KLw4PCkMODwo5SAA=="},{"b64Body":"Cg8KCQj2/v6sBhC0FhICGAISAhgDGKGW3zUiAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjoIB7g8SAxi9CSLmDzU2NWI2MDAwNjA0MDUxODA4MzAzODE4NTg3NWFmMTkyNTA1MDUwM2Q4MDYwMDA4MTE0NjEwNmUzNTc2MDQwNTE5MTUwNjAxZjE5NjAzZjNkMDExNjgyMDE2MDQwNTIzZDgyNTIzZDYwMDA2MDIwODQwMTNlNjEwNmU4NTY1YjYwNjA5MTUwNWI1MDUwOTA1MDgwOTE1MDUwOTE5MDUwNTY1YjYwMDE1NDgxNTY1YjYwMDA4MDgyNzNmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmMTY2MDQwNTE2MTA3MjE5MDYxMGE0ZDU2NWI2MDAwNjA0MDUxODA4MzAzODE2MDAwODY1YWYxOTE1MDUwM2Q4MDYwMDA4MTE0NjEwNzVlNTc2MDQwNTE5MTUwNjAxZjE5NjAzZjNkMDExNjgyMDE2MDQwNTIzZDgyNTIzZDYwMDA2MDIwODQwMTNlNjEwNzYzNTY1YjYwNjA5MTUwNWI1MDUwOTA1MDgwOTE1MDUwOTE5MDUwNTY1YjYwMDA4MTkwNTA5MTkwNTA1NjViNjEwNzgzODE2MTA3NzA1NjViODI1MjUwNTA1NjViNjAwMDYwMjA4MjAxOTA1MDYxMDc5ZTYwMDA4MzAxODQ2MTA3N2E1NjViOTI5MTUwNTA1NjViNjAwMDgwZmQ1YjYwMDA3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmY4MjE2OTA1MDkxOTA1MDU2NWI2MDAwNjEwN2Q0ODI2MTA3YTk1NjViOTA1MDkxOTA1MDU2NWI2MTA3ZTQ4MTYxMDdjOTU2NWI4MTE0NjEwN2VmNTc2MDAwODBmZDViNTA1NjViNjAwMDgxMzU5MDUwNjEwODAxODE2MTA3ZGI1NjViOTI5MTUwNTA1NjViNjAwMDYwMjA4Mjg0MDMxMjE1NjEwODFkNTc2MTA4MWM2MTA3YTQ1NjViNWI2MDAwNjEwODJiODQ4Mjg1MDE2MTA3ZjI1NjViOTE1MDUwOTI5MTUwNTA1NjViNjAwMDYxMDgzZjgyNjEwN2E5NTY1YjkwNTA5MTkwNTA1NjViNjEwODRmODE2MTA4MzQ1NjViODExNDYxMDg1YTU3NjAwMDgwZmQ1YjUwNTY1YjYwMDA4MTM1OTA1MDYxMDg2YzgxNjEwODQ2NTY1YjkyOTE1MDUwNTY1YjYwMDA2MDIwODI4NDAzMTIxNTYxMDg4ODU3NjEwODg3NjEwN2E0NTY1YjViNjAwMDYxMDg5Njg0ODI4NTAxNjEwODVkNTY1YjkxNTA1MDkyOTE1MDUwNTY1YjYxMDhhODgxNjEwN2M5NTY1YjgyNTI1MDUwNTY1YjYwMDA2MDIwODIwMTkwNTA2MTA4YzM2MDAwODMwMTg0NjEwODlmNTY1YjkyOTE1MDUwNTY1YjYwMDA4MTE1MTU5MDUwOTE5MDUwNTY1YjYxMDhkZTgxNjEwOGM5NTY1YjgyNTI1MDUwNTY1YjYwMDA2MDIwODIwMTkwNTA2MTA4Zjk2MDAwODMwMTg0NjEwOGQ1NTY1YjkyOTE1MDUwNTY1YjYwMDA4MTkwNTA5MTkwNTA1NjViNjEwOTEyODE2MTA4ZmY1NjViODI1MjUwNTA1NjViNjAwMDYwZmY4MjE2OTA1MDkxOTA1MDU2NWI2MTA5MmU4MTYxMDkxODU2NWI4MjUyNTA1MDU2NWI2MDAwNjA4MDgyMDE5MDUwNjEwOTQ5NjAwMDgzMDE4NzYxMDkwOTU2NWI2MTA5NTY2MDIwODMwMTg2NjEwOTI1NTY1YjYxMDk2MzYwNDA4MzAxODU2MTA5MDk1NjViNjEwOTcwNjA2MDgzMDE4NDYxMDkwOTU2NWI5NTk0NTA1MDUwNTA1MDU2NWI2MDAwODE1MTkwNTA5MTkwNTA1NjViNjAwMDgxOTA1MDkyOTE1MDUwNTY1YjYwMDA1YjgzODExMDE1NjEwOWFkNTc4MDgyMDE1MTgxODQwMTUyNjAyMDgxMDE5MDUwNjEwOTkyNTY1YjYwMDA4NDg0MDE1MjUwNTA1MDUwNTY1YjYwMDA2MTA5YzQ4MjYxMDk3OTU2NWI2MTA5Y2U4MTg1NjEwOTg0NTY1YjkzNTA2MTA5ZGU4MTg1NjAyMDg2MDE2MTA5OGY1NjViODA4NDAxOTE1MDUwOTI5MTUwNTA1NjViNjAwMDYxMDlmNjgyODQ2MTA5Yjk1NjViOTE1MDgxOTA1MDkyOTE1MDUwNTY1YjdmNjI2ZjZmMjg3NTY5NmU3NDMyMzUzNjI5MDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDYwMDA4MjAxNTI1MDU2NWI2MDAwNjEwYTM3NjAwYzgzNjEwOTg0NTY1YjkxNTA2MTBhNDI4MjYxMGEwMTU2NWI2MDBjODIwMTkwNTA5MTkwNTA1NjViNjAwMDYxMGE1ODgyNjEwYTJhNTY1YjkxNTA4MTkwNTA5MTkwNTA1NmZlYTI2NDY5NzA2NjczNTgyMjEyMjBlNWUwOWYyYzA3NGFmMDRlNWExNGJhMDkwMGUwZTg4MDE4NjgwZGQ3NDcwYmMyNjg4ODZlYWNiMDdmYzNlZDljNjQ3MzZmNmM2MzQzMDAwODEzMDAzMw==","b64Record":"CiAIFiocCgwIARAMGgYIgK6ZpA8SDAgBEA8aBgiArpmkDxIwpElTaJ9qyox/zJVrKZdf2IEmNu5XZZbQW5ohiuGeS1WkKA7Ul9vJefl5V+sNuSspGgwIsv/+rAYQ1PnP8QIiDwoJCPb+/qwGELQWEgIYAiogw4PCrsOCwrfDg8K5dEY4w4LCrkrDg8KLw4PCkMODwo5SAA=="},{"b64Body":"Cg8KCQj3/v6sBhC2FhICGAISAhgDGNWAv6ACIgIIeDIgw4PCrsOCwrfDg8K5dEY4w4LCrkrDg8KLw4PCkMODwo5CjAEKAxi9CRoiEiB6/N8Yr0AF4DVo3CB2mx9XEpxx42HiJ0ALVCRdY6AaNSCQoQ8ogMLXL0IFCIDO2gNKQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFSAFoAagtjZWxsYXIgZG9vcg==","b64Record":"CiUIFiIDGL4JKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjApwlyLSzbMB60mMCZEHKegH6EVB486NLgBI881grcF2HtOrLC5vx4ZFWMSAM1hEEMaDAiz//6sBhCsqdmWASIPCgkI9/7+rAYQthYSAhgCKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjjDA2eIGQs0XCgMYvgkSmBVggGBAUmAENhBhAJxXYAA1YOAcgGN9PRwvEWEAZFeAY309HC8UYQE0V4BjjaXLWxRhAV1XgGOTklAVFGEBiFeAY5otjVwUYQHFV4BjwphVeBRhAfVXgGPVvV5JFGECIFdhAJxWW4BjBBh5URRhAKFXgGMSBl/gFGEAq1eAY14P+QgUYQDWV4BjcKCCMRRhAOBXgGNze8PJFGEBHVdbYACA/VthAKlhAl1WWwBbNIAVYQC3V2AAgP1bUGEAwGED5FZbYEBRYQDNkZBhB4lWW2BAUYCRA5DzW2EA3mED7FZbAFs0gBVhAOxXYACA/VtQYQEHYASANgOBAZBhAQKRkGEIB1ZbYQV1VltgQFFhARSRkGEHiVZbYEBRgJEDkPNbNIAVYQEpV2AAgP1bUGEBMmEFllZbAFs0gBVhAUBXYACA/VtQYQFbYASANgOBAZBhAVaRkGEIclZbYQXPVlsAWzSAFWEBaVdgAID9W1BhAXJhBehWW2BAUWEBf5GQYQiuVltgQFGAkQOQ81s0gBVhAZRXYACA/VtQYQGvYASANgOBAZBhAaqRkGEIB1ZbYQYMVltgQFFhAbyRkGEI5FZbYEBRgJEDkPNbYQHfYASANgOBAZBhAdqRkGEIB1ZbYQZ/VltgQFFhAeyRkGEI5FZbYEBRgJEDkPNbNIAVYQIBV2AAgP1bUGECCmEG9VZbYEBRYQIXkZBhB4lWW2BAUYCRA5DzWzSAFWECLFdgAID9W1BhAkdgBIA2A4EBkGECQpGQYQgHVlthBvtWW2BAUWECVJGQYQjkVltgQFGAkQOQ81tgAH9oYXNoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAJBQYABgAZBQYAB/cgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACQUGAAf3MAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAkFBgAGABc///////////////////////////FoWFhYVgQFFgJAFhAwOUk5KRkGEJNFZbYEBRYCCBgwMDgVKQYEBSf5bRB/YAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAe/////////////////////////////////////8ZFmAgggGAUXv/////////////////////////////////////g4GDFheDUlBQUFBgQFFhA42RkGEJ6lZbYABgQFGAgwOBYACGWvGRUFA9gGAAgRRhA8pXYEBRkVBgHxlgPz0BFoIBYEBSPYJSPWAAYCCEAT5hA89WW2BgkVBbUFCQUIBhA91XYACA/VtQUFBQUFZbYABHkFCQVltgAH9oYXNoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAJBQYABgAZBQYAB/cgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACQUGAAf3MAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAkFBgAGABc///////////////////////////FmABhoaGhmBAUWAkAWEElJSTkpGQYQk0VltgQFFgIIGDAwOBUpBgQFJ/ltEH9gAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB7/////////////////////////////////////xkWYCCCAYBRe/////////////////////////////////////+DgYMWF4NSUFBQUGBAUWEFHpGQYQnqVltgAGBAUYCDA4GFh1rxklBQUD2AYACBFGEFW1dgQFGRUGAfGWA/PQEWggFgQFI9glI9YABgIIQBPmEFYFZbYGCRUFtQUJBQgGEFbldgAID9W1BQUFBQVltgAIFz//////////////////////////8WMZBQkZBQVltgAIBUkGEBAAqQBHP//////////////////////////xZz//////////////////////////8W/1uAc///////////////////////////Fv9bYACAVJBhAQAKkARz//////////////////////////8WgVZbYACAgnP//////////////////////////xZgQFFhBjKQYQpNVltgAGBAUYCDA4GFWvSRUFA9gGAAgRRhBm1XYEBRkVBgHxlgPz0BFoIBYEBSPYJSPWAAYCCEAT5hBnJWW2BgkVBbUFCQUICRUFCRkFBWW2AAgIJz//////////////////////////8WNGBAUWEGppBhCk1WW2AAYEBRgIMDgYWHWvGSUFBQPYBgAIEUYQbjV2BAUZFQYB8ZYD89ARaCAWBAUj2CUj1gAGAghAE+YQboVltgYJFQW1BQkFCAkVBQkZBQVltgAVSBVltgAICCc///////////////////////////FmBAUWEHIZBhCk1WW2AAYEBRgIMDgWAAhlrxkVBQPYBgAIEUYQdeV2BAUZFQYB8ZYD89ARaCAWBAUj2CUj1gAGAghAE+YQdjVltgYJFQW1BQkFCAkVBQkZBQVltgAIGQUJGQUFZbYQeDgWEHcFZbglJQUFZbYABgIIIBkFBhB55gAIMBhGEHelZbkpFQUFZbYACA/VtgAHP//////////////////////////4IWkFCRkFBWW2AAYQfUgmEHqVZbkFCRkFBWW2EH5IFhB8lWW4EUYQfvV2AAgP1bUFZbYACBNZBQYQgBgWEH21ZbkpFQUFZbYABgIIKEAxIVYQgdV2EIHGEHpFZbW2AAYQgrhIKFAWEH8lZbkVBQkpFQUFZbYABhCD+CYQepVluQUJGQUFZbYQhPgWEINFZbgRRhCFpXYACA/VtQVltgAIE1kFBhCGyBYQhGVluSkVBQVltgAGAggoQDEhVhCIhXYQiHYQekVltbYABhCJaEgoUBYQhdVluRUFCSkVBQVlthCKiBYQfJVluCUlBQVltgAGAgggGQUGEIw2AAgwGEYQifVluSkVBQVltgAIEVFZBQkZBQVlthCN6BYQjJVluCUlBQVltgAGAgggGQUGEI+WAAgwGEYQjVVluSkVBQVltgAIGQUJGQUFZbYQkSgWEI/1ZbglJQUFZbYABg/4IWkFCRkFBWW2EJLoFhCRhWW4JSUFBWW2AAYICCAZBQYQlJYACDAYdhCQlWW2EJVmAggwGGYQklVlthCWNgQIMBhWEJCVZbYQlwYGCDAYRhCQlWW5WUUFBQUFBWW2AAgVGQUJGQUFZbYACBkFCSkVBQVltgAFuDgRAVYQmtV4CCAVGBhAFSYCCBAZBQYQmSVltgAISEAVJQUFBQVltgAGEJxIJhCXlWW2EJzoGFYQmEVluTUGEJ3oGFYCCGAWEJj1ZbgIQBkVBQkpFQUFZbYABhCfaChGEJuVZbkVCBkFCSkVBQVlt/Ym9vKHVpbnQyNTYpAAAAAAAAAAAAAAAAAAAAAAAAAABgAIIBUlBWW2AAYQo3YAyDYQmEVluRUGEKQoJhCgFWW2AMggGQUJGQUFZbYABhCliCYQoqVluRUIGQUJGQUFb+omRpcGZzWCISIOXgnywHSvBOWhS6CQDg6IAYaA3XRwvCaIhurLB/w+2cZHNvbGNDAAgTADMigAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKMCaDDoDGL4JShYKFAAAAAAAAAAAAAAAAAAAAAAAAAS+cgcKAxi+CRABUiIKCQoCGAIQ/7b0bAoJCgIYYhCAs8UNCgoKAxi+CRCAhK9f"},{"b64Body":"Cg8KCQj3/v6sBhC4FhICGAISAhgDGOCssQMiAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjjovCgMYvgkQoI0GIiTVvV5JAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=","b64Record":"CiUIFiIDGL4JKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjASCrRTkCYBQ37Lc6CxrI5CtSe4wb2JYP8JV2anKUHLa+06PqinrhaI70X3MOl59P0aDAiz//6sBhDyiZj7AiIPCgkI9/7+rAYQuBYSAhgCKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjjD/rq0DOq4CCgMYvgkSIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIoACAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACiJhgZSFgoJCgIYAhD93doGCgkKAhhiEP7d2gY="},{"b64Body":"Cg8KCQj4/v6sBhC6FhICGAISAhgDGOCssQMiAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjjoyCgMYvgkQoI0GGPQDIiSaLY1cAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=","b64Record":"CiUIFiIDGL4JKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjDlAOl7Pz5j4DChgk7EyoM+dEiLgl7mzOO2d3ZlqPvKysrOFCJB0eWvg9aQTClEm7QaDAi0//6sBhDEmdCDASIPCgkI+P7+rAYQuhYSAhgCKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjjDS1q8DOq4CCgMYvgkSIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIoACAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACieigZSIAoJCgIYAhCLtd8GCgkKAhhiEKSt3wYKCAoDGL4JEOgH"},{"b64Body":"Cg8KCQj4/v6sBhC8FhICGAISAhgDGOCssQMiAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjjovCgMYvgkQoI0GIiTVvV5JAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADI=","b64Record":"CiUIFiIDGL4JKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjANEYNIYlm8wWNR/xBNjuHHCyAkHyVYIK2QjvxpZsU5yjGQu9VpGiOX4SfODcG9fN0aDAi0//6sBhCppoyFAyIPCgkI+P7+rAYQvBYSAhgCKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjjDGr60DOq4CCgMYvgkSIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIoACAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACiKhgZSFgoJCgIYAhCL39oGCgkKAhhiEIzf2gY="},{"b64Body":"Cg8KCQj5/v6sBhC+FhICGAISAhgDGOCssQMiAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjjoyCgMYvgkQoI0GGPQDIiSaLY1cAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADI=","b64Record":"CiUIFiIDGL4JKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjCLqkmUmJks07F5jdd6Y4MdJZFAYYRjRblWloPLOYdtmZfimMEsz8peuOSQcMo1EN8aDAi1//6sBhDMot2MASIPCgkI+f7+rAYQvhYSAhgCKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjjDS1q8DOq4CCgMYvgkSIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIoACAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACieigZSIAoJCgIYAhCLtd8GCgkKAhhiEKSt3wYKCAoDGL4JEOgH"},{"b64Body":"Cg8KCQj5/v6sBhDAFhICGAISAhgDGOCssQMiAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjjovCgMYvgkQoI0GIiTVvV5JAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAE=","b64Record":"CiUIFiIDGL4JKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjB7Qb8QavKRgQH/ZTjjc7Af35ZZ9BOD6E9jSnPo6Vb+BVH31oGnlnl2SjMIryPHdwgaDAi1//6sBhDQhqGOAyIPCgkI+f7+rAYQwBYSAhgCKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjjCA19oCOq4CCgMYvgkSIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABIoACAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACiA8QRSFgoJCgIYAhD/rbUFCgkKAhhiEICutQU="},{"b64Body":"Cg8KCQj6/v6sBhDCFhICGAISAhgDGOCssQMiAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjjoyCgMYvgkQoI0GGPQDIiSaLY1cAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAE=","b64Record":"CiUIFiIDGL4JKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjDu0vpBHXxt0mNtUM4/jBymHTnm9qN2GWuBgX8FA1ZqIvJK/Hy6MRJEr5RarOzCDFgaDAi2//6sBhDHvdKWASIPCgkI+v7+rAYQwhYSAhgCKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjjCBwa8DOq4CCgMYvgkSIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIoACAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACj3iQZSIAoJCgIYAhDpid8GCgkKAhhiEIKC3wYKCAoDGL4JEOgH"},{"b64Body":"Cg8KCQj6/v6sBhDEFhICGAISAhgDGOCssQMiAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjjovCgMYvgkQoI0GIiTVvV5JAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAVk=","b64Record":"CiUIFiIDGL4JKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjD6/ikBM0OdzVKV/C0JkXXoQ/ZIQyZj/fDcB+qqnXv4ptxez7jrbtcSphD3GitIV64aDAi2//6sBhC24qf7AiIPCgkI+v7+rAYQxBYSAhgCKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjjDGr60DOq4CCgMYvgkSIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIoACAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACiKhgZSFgoJCgIYAhCL39oGCgkKAhhiEIzf2gY="},{"b64Body":"Cg8KCQj7/v6sBhDGFhICGAISAhgDGOCssQMiAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjjoyCgMYvgkQoI0GGPQDIiSaLY1cAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAVk=","b64Record":"CiUIFiIDGL4JKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjAa0YxVcidYL9u0YaKxvmMr6hfYJ5Hqw2wfCxF1MZX34/hWnJDfuhLOz55m245BcxsaDAi3//6sBhCNmICgASIPCgkI+/7+rAYQxhYSAhgCKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjjDS1q8DOq4CCgMYvgkSIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIoACAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACieigZSIAoJCgIYAhCLtd8GCgkKAhhiEKSt3wYKCAoDGL4JEOgH"},{"b64Body":"Cg8KCQj7/v6sBhDIFhICGAISAhgDGOCssQMiAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjjovCgMYvgkQoI0GIiSTklAVAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAE=","b64Record":"CiUIFiIDGL4JKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjC4tgsPRJpokKLYZ0lloMZH4eKD7v1g7/ZnSw2GPLkvP3VoRgUwOrIHWK8N772JjpMaDAi3//6sBhCUh4qFAyIPCgkI+/7+rAYQyBYSAhgCKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjjCA19oCOq4CCgMYvgkSIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABIoACAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACiA8QRSFgoJCgIYAhD/rbUFCgkKAhhiEICutQU="},{"b64Body":"Cg8KCQj8/v6sBhDKFhICGAISAhgDGOCssQMiAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjjovCgMYvgkQoI0GIiSTklAVAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGI=","b64Record":"CiUIFiIDGL4JKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjDVJFhOUXcvi7Mxxy4qa0+zoaQ4MrCxxoFrmk+wfbJ8ge0EPMzwGBZPAKZu6zmbuJ8aDAi4//6sBhD5tqmpASIPCgkI/P7+rAYQyhYSAhgCKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjjD/rq0DOq4CCgMYvgkSIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIoACAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACiJhgZSFgoJCgIYAhD93doGCgkKAhhiEP7d2gY="},{"b64Body":"Cg8KCQj8/v6sBhDMFhICGAISAhgDGOCssQMiAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjjovCgMYvgkQoI0GIiSTklAVAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAH0=","b64Record":"CiUIFiIDGL4JKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjAnRF/vas6DIfPYuDNaSC9mnj4pnzh/2aYU6xMWjoh3rdMDr4FFnvev99rmGBjgY/waDAi4//6sBhCdpsOOAyIPCgkI/P7+rAYQzBYSAhgCKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjjD/rq0DOq4CCgMYvgkSIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIoACAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACiJhgZSFgoJCgIYAhD93doGCgkKAhhiEP7d2gY="},{"b64Body":"Cg8KCQj9/v6sBhDOFhICGAISAhgDGOCssQMiAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjjovCgMYvgkQoI0GIiR9PRwvAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAI=","b64Record":"CiUIHSIDGL4JKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjDp/JEE2oES2yitbssZ33Cm8biWrcYoOXp/1AAPgLliQXrLJP7Kx2URLvgaxyqLHaAaDAi5//6sBhCLtsWWASIPCgkI/f7+rAYQzhYSAhgCKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjjDgrLEDOh4aGElOVkFMSURfU09MSURJVFlfQUREUkVTUyigjQZSFgoJCgIYAhC/2eIGCgkKAhhiEMDZ4gY="},{"b64Body":"Cg8KCQj9/v6sBhDQFhICGAISAhgDGOCssQMiAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjjovCgMYvgkQoI0GIiRwoIIxAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAI=","b64Record":"CiUIFiIDGL4JKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjD5sipreqy5vmQ+7tl8WO0lCF+TLrWeZXGY/71XNaDRwknuz+PDTl2coWKmF7/vQh4aDAi5//6sBhDonY2YAyIPCgkI/f7+rAYQ0BYSAhgCKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjjCA19oCOq4CCgMYvgkSIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIoACAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACiA8QRSFgoJCgIYAhD/rbUFCgkKAhhiEICutQU="}]},"repeatedCreate2FailsWithInterpretableActionSidecars":{"placeholderNum":1218,"encodedItems":[{"b64Body":"Cg8KCQiK//6sBhCsFxICGAISAhgDGPuV9hQiAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjlozCiISIGqN5rX71qJwq2zF75HlO4zdMzSChZF7yuD7jwZy7eoLEICA6YOx3hZKBQiAztoD","b64Record":"CiUIFhIDGMMJKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjDOMvn+Z6tTjCs1eqaB1F7wfRqhcgdKMiov/WuyCmIrUHiN8XCn8vMLp/nIHLTYylQaCwjG//6sBhDUpOAtIg8KCQiK//6sBhCsFxICGAIqIMODwq7DgsK3w4PCuXRGOMOCwq5Kw4PCi8ODwpDDg8KOUh0KDAoCGAIQ///Rh+K8LQoNCgMYwwkQgIDSh+K8LQ=="},{"b64Body":"Cg8KCQiK//6sBhCuFxICGAISAhgDGP7LzSwiAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjooBjwESDAjGzdmwBhCtqqaVAhptCiISIB/DJKmmtu456r9MjimzPyPJJ9u8mqeA930l3EEern7KCiM6IQOi86ODQpgrCPlopcF1/5NcEhIgeTx9FYmUxBObMV6CmwoiEiBc5ukOxHYr0JmJHvFvors3kA0Qctn0yNJKz9OKonR8zSIMSGVsbG8gV29ybGQhKgAyAA==","b64Record":"CiUIFhoDGMQJKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjDmzgJfn+ynrxwW56PuShHZWoVCf8GCfkrKZ7u+SFjmn4Sgk10mkFpbzibqpUradmEaDAjG//6sBhCS+POuAiIPCgkIiv/+rAYQrhcSAhgCKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjlIA"},{"b64Body":"Cg8KCQiL//6sBhCyFxICGAISAhgDGIudjj4iAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjpoBiCAKAxjECSKAIDYwODA2MDQwNTIzNDgwMTU2MTAwMTA1NzYwMDA4MGZkNWI1MDYxMjgwMjgwNjEwMDIwNjAwMDM5NjAwMGYzZmU2MDgwNjA0MDUyMzQ4MDE1NjEwMDEwNTc2MDAwODBmZDViNTA2MDA0MzYxMDYxMDA1NzU3NjAwMDM1NjBlMDFjODA2MzQyNWRiZjk2MTQ2MTAwNWM1NzgwNjM2N2Q3MzFkMzE0NjEwMDhjNTc4MDYzNzA3YzUwMTkxNDYxMDBiYzU3ODA2M2FiZjdiZmQ4MTQ2MTAwZDg1NzgwNjNjYTk3MjE2MTE0NjEwMGY0NTc1YjYwMDA4MGZkNWI2MTAwNzY2MDA0ODAzNjAzODEwMTkwNjEwMDcxOTE5MDYxMDlhMTU2NWI2MTAxMTA1NjViNjA0MDUxNjEwMDgzOTE5MDYxMDlmYTU2NWI2MDQwNTE4MDkxMDM5MGYzNWI2MTAwYTY2MDA0ODAzNjAzODEwMTkwNjEwMGExOTE5MDYxMDlhMTU2NWI2MTAxNDY1NjViNjA0MDUxNjEwMGIzOTE5MDYxMDlmYTU2NWI2MDQwNTE4MDkxMDM5MGYzNWI2MTAwZDY2MDA0ODAzNjAzODEwMTkwNjEwMGQxOTE5MDYxMGExNTU2NWI2MTAxN2M1NjViMDA1YjYxMDBmMjYwMDQ4MDM2MDM4MTAxOTA2MTAwZWQ5MTkwNjEwYTc4NTY1YjYxMDI1ZjU2NWIwMDViNjEwMTBlNjAwNDgwMzYwMzgxMDE5MDYxMDEwOTkxOTA2MTBhMTU1NjViNjEwM2E5NTY1YjAwNWI2MDAwNjEwMTNlODMzMDYwMDA4MDU0OTA2MTAxMDAwYTkwMDQ3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNjg1NjEwNDhjNTY1YjkwNTA5MjkxNTA1MDU2NWI2MDAwNjEwMTc0ODMzMDYwMDA4MDU0OTA2MTAxMDAwYTkwMDQ3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNjg1NjEwNWFhNTY1YjkwNTA5MjkxNTA1MDU2NWI2MDAwNjEwMTg4MzA4MzYxMDZjODU2NWI5MDUwNjAxNjYwMDMwYjgxMTQ2MTAxZDA1NzYwNDA1MTdmMDhjMzc5YTAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDgxNTI2MDA0MDE2MTAxYzc5MDYxMGIwMjU2NWI2MDQwNTE4MDkxMDM5MGZkNWI2MDAwODA1NDkwNjEwMTAwMGE5MDA0NzNmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmMTY3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNjYzZDk0MjU0MDM4MzYwNDA1MTgyNjNmZmZmZmZmZjE2NjBlMDFiODE1MjYwMDQwMTYxMDIyOTkxOTA2MTBiMzE1NjViNjAwMDYwNDA1MTgwODMwMzgxNjAwMDg3ODAzYjE1ODAxNTYxMDI0MzU3NjAwMDgwZmQ1YjUwNWFmMTE1ODAxNTYxMDI1NzU3M2Q2MDAwODAzZTNkNjAwMGZkNWI1MDUwNTA1MDUwNTA1NjViNjAwMDYwZmY2MGY4MWIzMDgzNjA0MDUxODA2MDIwMDE2MTAyNzg5MDYxMDhmODU2NWI2MDIwODIwMTgxMDM4MjUyNjAxZjE5NjAxZjgyMDExNjYwNDA1MjUwNjA0MDUxNjAyMDAxNjEwMjljOTE5MDYxMGJjNjU2NWI2MDQwNTE2MDIwODE4MzAzMDM4MTUyOTA2MDQwNTI4MDUxOTA2MDIwMDEyMDYwNDA1MTYwMjAwMTYxMDJjNTk0OTM5MjkxOTA2MTBjOTM1NjViNjA0MDUxNjAyMDgxODMwMzAzODE1MjkwNjA0MDUyODA1MTkwNjAyMDAxMjA2MDAwMWM5MDUwODE2MDQwNTE2MTAyZWQ5MDYxMDhmODU2NWI4MTkwNjA0MDUxODA5MTAzOTA2MDAwZjU5MDUwODAxNTgwMTU2MTAzMGQ1NzNkNjAwMDgwM2UzZDYwMDBmZDViNTA2MDAwODA2MTAxMDAwYTgxNTQ4MTczZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjAyMTkxNjkwODM3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNjAyMTc5MDU1NTA4MDczZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjE2NjAwMDgwNTQ5MDYxMDEwMDBhOTAwNDczZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjE2NzNmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmMTYxNDYxMDNhNTU3NjAwMDgwZmQ1YjUwNTA1NjViNjAwMDYxMDNiNTMwODM2MTA3ZTA1NjViOTA1MDYwMTY2MDAzMGI4MTE0NjEwM2ZkNTc2MDQwNTE3ZjA4YzM3OWEwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDA4MTUyNjAwNDAxNjEwM2Y0OTA2MTBkMmQ1NjViNjA0MDUxODA5MTAzOTBmZDViNjAwMDgwNTQ5MDYxMDEwMDBhOTAwNDczZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjE2NzNmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmMTY2M2VhNThjZTIxODM2MDQwNTE4MjYzZmZmZmZmZmYxNjYwZTAxYjgxNTI2MDA0MDE2MTA0NTY5MTkwNjEwYjMxNTY1YjYwMDA2MDQwNTE4MDgzMDM4MTYwMDA4NzgwM2IxNTgwMTU2MTA0NzA1NzYwMDA4MGZkNWI1MDVhZjExNTgwMTU2MTA0ODQ1NzNkNjAwMDgwM2UzZDYwMDBmZDViNTA1MDUwNTA1MDUwNTY1YjYwMDA4MDYwMDA2MTAxNjc3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNjYzZWNhMzY5MTc2MGUwMWI4ODg4ODg4ODYwNDA1MTYwMjQwMTYxMDRjOTk0OTM5MjkxOTA2MTBkNWM1NjViNjA0MDUxNjAyMDgxODMwMzAzODE1MjkwNjA0MDUyOTA3YmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmMTkxNjYwMjA4MjAxODA1MTdiZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmY4MzgxODMxNjE3ODM1MjUwNTA1MDUwNjA0MDUxNjEwNTMzOTE5MDYxMGJjNjU2NWI2MDAwNjA0MDUxODA4MzAzODE2MDAwODY1YWYxOTE1MDUwM2Q4MDYwMDA4MTE0NjEwNTcwNTc2MDQwNTE5MTUwNjAxZjE5NjAzZjNkMDExNjgyMDE2MDQwNTIzZDgyNTIzZDYwMDA2MDIwODQwMTNlNjEwNTc1NTY1YjYwNjA5MTUwNWI1MDkxNTA5MTUwODE2MTA1ODY1NzYwMTU2MTA1OWI1NjViODA4MDYwMjAwMTkwNTE4MTAxOTA2MTA1OWE5MTkwNjEwZGRhNTY1YjViNjAwMzBiOTI1MDUwNTA5NDkzNTA1MDUwNTA1NjViNjAwMDgwNjAwMDYxMDE2NzczZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjE2NjM1Y2ZjOTAxMTYwZTAxYjg4ODg4ODg4NjA0MDUxNjAyNDAxNjEwNWU3OTQ5MzkyOTE5MDYxMGQ1YzU2NWI2MDQwNTE2MDIwODE4MzAzMDM4MTUyOTA2MDQwNTI5MDdiZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxOTE2NjAyMDgyMDE4MDUxN2JmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjgzODE4MzE2MTc4MzUyNTA1MDUwNTA2MDQwNTE2MTA2NTE5MTkwNjEwYmM2NTY1YjYwMDA2MDQwNTE4MDgzMDM4MTYwMDA4NjVhZjE5MTUwNTAzZDgwNjAwMDgxMTQ2MTA2OGU1NzYwNDA1MTkxNTA2MDFmMTk2MDNmM2QwMTE2ODIwMTYwNDA1MjNkODI1MjNkNjAwMDYwMjA4NDAxM2U2MTA2OTM1NjViNjA2MDkxNTA1YjUwOTE1MDkxNTA4MTYxMDZhNDU3NjAxNTYxMDZiOTU2NWI4MDgwNjAyMDAxOTA1MTgxMDE5MDYxMDZiODkxOTA2MTBkZGE1NjViNWI2MDAzMGI5MjUwNTA1MDk0OTM1MDUwNTA1MDU2NWI2MDAwODA2MDAwNjEwMTY3NzNmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmMTY2MzA5OTc5NGU4NjBlMDFiODY4NjYwNDA1MTYwMjQwMTYxMDcwMTkyOTE5MDYxMGUwNzU2NWI2MDQwNTE2MDIwODE4MzAzMDM4MTUyOTA2MDQwNTI5MDdiZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxOTE2NjAyMDgyMDE4MDUxN2JmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjgzODE4MzE2MTc4MzUyNTA1MDUwNTA2MDQwNTE2MTA3NmI5MTkwNjEwYmM2NTY1YjYwMDA2MDQwNTE4MDgzMDM4MTYwMDA4NjVhZjE5MTUwNTAzZDgwNjAwMDgxMTQ2MTA3YTg1NzYwNDA1MTkxNTA2MDFmMTk2MDNmM2QwMTE2ODIwMTYwNDA1MjNkODI1MjNkNjAwMDYwMjA4NDAxM2U2MTA3YWQ1NjViNjA2MDkxNTA1YjUwOTE1MDkxNTA4MTYxMDdiZTU3NjAxNTYxMDdkMzU2NWI4MDgwNjAyMDAxOTA1MTgxMDE5MDYxMDdkMjkxOTA2MTBkZGE1NjViNWI2MDAzMGI5MjUwNTA1MDkyOTE1MDUwNTY=","b64Record":"CiAIFiocCgwIARAMGgYIgK6ZpA8SDAgBEA8aBgiArpmkDxIwJwtNuGKjlV7R7tEPrz42Xije/vSXpjWeVCZui46h9e5Ps210VvR6m+r00zRJrb6lGgsIx//+rAYQkJmANyIPCgkIi//+rAYQshcSAhgCKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjlIA"},{"b64Body":"Cg8KCQiL//6sBhC4FxICGAISAhgDGPjR6j4iAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjoIBiCASAxjECSKAIDViNjAwMDgwNjAwMDYxMDE2NzczZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjE2NjM0OTE0NmJkZTYwZTAxYjg2ODY2MDQwNTE2MDI0MDE2MTA4MTk5MjkxOTA2MTBlMDc1NjViNjA0MDUxNjAyMDgxODMwMzAzODE1MjkwNjA0MDUyOTA3YmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmMTkxNjYwMjA4MjAxODA1MTdiZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmY4MzgxODMxNjE3ODM1MjUwNTA1MDUwNjA0MDUxNjEwODgzOTE5MDYxMGJjNjU2NWI2MDAwNjA0MDUxODA4MzAzODE2MDAwODY1YWYxOTE1MDUwM2Q4MDYwMDA4MTE0NjEwOGMwNTc2MDQwNTE5MTUwNjAxZjE5NjAzZjNkMDExNjgyMDE2MDQwNTIzZDgyNTIzZDYwMDA2MDIwODQwMTNlNjEwOGM1NTY1YjYwNjA5MTUwNWI1MDkxNTA5MTUwODE2MTA4ZDY1NzYwMTU2MTA4ZWI1NjViODA4MDYwMjAwMTkwNTE4MTAxOTA2MTA4ZWE5MTkwNjEwZGRhNTY1YjViNjAwMzBiOTI1MDUwNTA5MjkxNTA1MDU2NWI2MTE5OWM4MDYxMGUzMTgzMzkwMTkwNTY1YjYwMDA4MGZkNWI2MDAwNzNmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmODIxNjkwNTA5MTkwNTA1NjViNjAwMDYxMDkzNTgyNjEwOTBhNTY1YjkwNTA5MTkwNTA1NjViNjEwOTQ1ODE2MTA5MmE1NjViODExNDYxMDk1MDU3NjAwMDgwZmQ1YjUwNTY1YjYwMDA4MTM1OTA1MDYxMDk2MjgxNjEwOTNjNTY1YjkyOTE1MDUwNTY1YjYwMDA4MTYwMDcwYjkwNTA5MTkwNTA1NjViNjEwOTdlODE2MTA5Njg1NjViODExNDYxMDk4OTU3NjAwMDgwZmQ1YjUwNTY1YjYwMDA4MTM1OTA1MDYxMDk5YjgxNjEwOTc1NTY1YjkyOTE1MDUwNTY1YjYwMDA4MDYwNDA4Mzg1MDMxMjE1NjEwOWI4NTc2MTA5Yjc2MTA5MDU1NjViNWI2MDAwNjEwOWM2ODU4Mjg2MDE2MTA5NTM1NjViOTI1MDUwNjAyMDYxMDlkNzg1ODI4NjAxNjEwOThjNTY1YjkxNTA1MDkyNTA5MjkwNTA1NjViNjAwMDgxOTA1MDkxOTA1MDU2NWI2MTA5ZjQ4MTYxMDllMTU2NWI4MjUyNTA1MDU2NWI2MDAwNjAyMDgyMDE5MDUwNjEwYTBmNjAwMDgzMDE4NDYxMDllYjU2NWI5MjkxNTA1MDU2NWI2MDAwNjAyMDgyODQwMzEyMTU2MTBhMmI1NzYxMGEyYTYxMDkwNTU2NWI1YjYwMDA2MTBhMzk4NDgyODUwMTYxMDk1MzU2NWI5MTUwNTA5MjkxNTA1MDU2NWI2MDAwODE5MDUwOTE5MDUwNTY1YjYxMGE1NTgxNjEwYTQyNTY1YjgxMTQ2MTBhNjA1NzYwMDA4MGZkNWI1MDU2NWI2MDAwODEzNTkwNTA2MTBhNzI4MTYxMGE0YzU2NWI5MjkxNTA1MDU2NWI2MDAwNjAyMDgyODQwMzEyMTU2MTBhOGU1NzYxMGE4ZDYxMDkwNTU2NWI1YjYwMDA2MTBhOWM4NDgyODUwMTYxMGE2MzU2NWI5MTUwNTA5MjkxNTA1MDU2NWI2MDAwODI4MjUyNjAyMDgyMDE5MDUwOTI5MTUwNTA1NjViN2Y0ZTY1NzY2NTcyMjA2NTZlNjQ3MzIwNzc2NTZjNmMyZTAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwNjAwMDgyMDE1MjUwNTY1YjYwMDA2MTBhZWM2MDEwODM2MTBhYTU1NjViOTE1MDYxMGFmNzgyNjEwYWI2NTY1YjYwMjA4MjAxOTA1MDkxOTA1MDU2NWI2MDAwNjAyMDgyMDE5MDUwODE4MTAzNjAwMDgzMDE1MjYxMGIxYjgxNjEwYWRmNTY1YjkwNTA5MTkwNTA1NjViNjEwYjJiODE2MTA5MmE1NjViODI1MjUwNTA1NjViNjAwMDYwMjA4MjAxOTA1MDYxMGI0NjYwMDA4MzAxODQ2MTBiMjI1NjViOTI5MTUwNTA1NjViNjAwMDgxNTE5MDUwOTE5MDUwNTY1YjYwMDA4MTkwNTA5MjkxNTA1MDU2NWI2MDAwNWI4MzgxMTAxNTYxMGI4MDU3ODA4MjAxNTE4MTg0MDE1MjYwMjA4MTAxOTA1MDYxMGI2NTU2NWI4MzgxMTExNTYxMGI4ZjU3NjAwMDg0ODQwMTUyNWI1MDUwNTA1MDU2NWI2MDAwNjEwYmEwODI2MTBiNGM1NjViNjEwYmFhODE4NTYxMGI1NzU2NWI5MzUwNjEwYmJhODE4NTYwMjA4NjAxNjEwYjYyNTY1YjgwODQwMTkxNTA1MDkyOTE1MDUwNTY1YjYwMDA2MTBiZDI4Mjg0NjEwYjk1NTY1YjkxNTA4MTkwNTA5MjkxNTA1MDU2NWI2MDAwN2ZmZjAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwODIxNjkwNTA5MTkwNTA1NjViNjAwMDgxOTA1MDkxOTA1MDU2NWI2MTBjMjQ2MTBjMWY4MjYxMGJkZDU2NWI2MTBjMDk1NjViODI1MjUwNTA1NjViNjAwMDgxNjA2MDFiOTA1MDkxOTA1MDU2NWI2MDAwNjEwYzQyODI2MTBjMmE1NjViOTA1MDkxOTA1MDU2NWI2MDAwNjEwYzU0ODI2MTBjMzc1NjViOTA1MDkxOTA1MDU2NWI2MTBjNmM2MTBjNjc4MjYxMDkyYTU2NWI2MTBjNDk1NjViODI1MjUwNTA1NjViNjAwMDgxOTA1MDkxOTA1MDU2NWI2MTBjOGQ2MTBjODg4MjYxMGE0MjU2NWI2MTBjNzI1NjViODI1MjUwNTA1NjViNjAwMDYxMGM5ZjgyODc2MTBjMTM1NjViNjAwMTgyMDE5MTUwNjEwY2FmODI4NjYxMGM1YjU2NWI2MDE0ODIwMTkxNTA2MTBjYmY4Mjg1NjEwYzdjNTY1YjYwMjA4MjAxOTE1MDYxMGNjZjgyODQ2MTBjN2M1NjViNjAyMDgyMDE5MTUwODE5MDUwOTU5NDUwNTA1MDUwNTA1NjViN2Y1NzY1NmM2YzJjMjA0OTIwNmU2NTc2NjU3MjIxMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwNjAwMDgyMDE1MjUwNTY1YjYwMDA2MTBkMTc2MDBlODM2MTBhYTU1NjViOTE1MDYxMGQyMjgyNjEwY2UxNTY1YjYwMjA4MjAxOTA1MDkxOTA1MDU2NWI2MDAwNjAyMDgyMDE5MDUwODE4MTAzNjAwMDgzMDE1MjYxMGQ0NjgxNjEwZDBhNTY1YjkwNTA5MTkwNTA1NjViNjEwZDU2ODE2MTA5Njg1NjViODI1MjUwNTA1NjViNjAwMDYwODA4MjAxOTA1MDYxMGQ3MTYwMDA4MzAxODc2MTBiMjI1NjViNjEwZDdlNjAyMDgzMDE4NjYxMGIyMjU2NWI2MTBkOGI2MDQwODMwMTg1NjEwYjIyNTY1YjYxMGQ5ODYwNjA4MzAxODQ2MTBkNGQ1NjViOTU5NDUwNTA1MDUwNTA1NjViNjAwMDgxNjAwMzBiOTA1MDkxOTA1MDU2NWI2MTBkYjc4MTYxMGRhMTU2NWI4MTE0NjEwZGMyNTc2MDAwODBmZDViNTA1NjViNjAwMDgxNTE5MDUwNjEwZGQ0ODE2MTBkYWU1NjViOTI5MTUwNTA1NjViNjAwMDYwMjA4Mjg0MDMxMjE1NjEwZGYwNTc2MTBkZWY2MTA5MDU1NjViNWI2MDAwNjEwZGZlODQ4Mjg1MDE2MTBkYzU1NjViOTE1MDUwOTI5MTUwNTA1NjViNjAwMDYwNDA4MjAxOTA1MDYxMGUxYzYwMDA4MzAxODU2MTBiMjI1NjViNjEwZTI5NjAyMDgzMDE4NDYxMGIyMjU2NWI5MzkyNTA1MDUwNTZmZTYwODA2MDQwNTIzNDgwMTU2MTAwMTA1NzYwMDA4MGZkNWI1MDYxMTk3YzgwNjEwMDIwNjAwMDM5NjAwMGYzZmU2MDgwNjA0MDUyMzQ4MDE1NjEwMDEwNTc2MDAwODBmZDViNTA2MDA0MzYxMDYxMDA0YzU3NjAwMDM1NjBlMDFjODA2MzBhMjg0Y2I2MTQ2MTAwNTE1NzgwNjM0ODRhOTVhOTE0NjEwMDZkNTc4MDYzZDk0MjU0MDMxNDYxMDA4OTU3ODA2M2VhNThjZTIxMTQ2MTAwYTU1NzViNjAwMDgwZmQ1YjYxMDA2YjYwMDQ4MDM2MDM4MTAxOTA2MTAwNjY5MTkwNjEwOTM5NTY1YjYxMDBjMTU2NWIwMDViNjEwMDg3NjAwNDgwMzYwMzgxMDE5MDYxMDA4MjkxOTA2MTA5Mzk1NjViNjEwMTI1NTY1YjAwNWI2MTAwYTM2MDA0ODAzNjAzODEwMTkwNjEwMDllOTE5MDYxMDk5NTU2NWI2MTAyMzY1NjViMDA1YjYxMDBiZjYwMDQ4MDM2MDM4MTAxOTA2MTAwYmE5MTkwNjEwOTk1NTY1YjYxMDI4ZTU2NWIwMDViNjAwMDgwNjAwMDYxMDBkMjg1NjAwMDg2NjEwMmU2NTY1YjkyNTA5MjUwOTI1MDYwMTY2MDAzMGI4MzE0NjEwMTFlNTc2MDQwNTE3ZjA4YzM3OWEwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDA4MTUyNjAwNDAxNjEwMTE1OTA2MTBhMWY1NjViNjA0MDUxODA5MTAzOTBmZDViNTA1MDUwNTA1MDU2NWI2MDAwNjA0MDUxNjEwMTMzOTA2MTA2OGU1NjViNjA0MDUxODA5MTAzOTA2MDAwZjA4MDE1ODAxNTYxMDE0ZjU3M2Q2MDAwODAzZTNkNjAwMGZkNWI1MDkwNTA4MDczZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjE2NjMwYTI4NGNiNjYwZTAxYjg0ODQ2MDQwNTE2MDI0MDE2MTAxODQ5MjkxOTA2MTBiOTg1NjViNjA0MDUxNjAyMDgxODMwMzAzODE=","b64Record":"CiAIFiocCgwIARAMGgYIgK6ZpA8SDAgBEA8aBgiArpmkDxIwuEt2Tzdg2Lhrp1MeUoSv9D7WbE1rnMNDFs9aGSADk+D9YCf43JZa29RU146ZqXdYGgwIx//+rAYQr+TKuAIiDwoJCIv//qwGELgXEgIYAiogw4PCrsOCwrfDg8K5dEY4w4LCrkrDg8KLw4PCkMODwo5SAA=="},{"b64Body":"Cg8KCQiM//6sBhC+FxICGAISAhgDGPfR6j4iAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjoIBiCASAxjECSKAIDUyOTA2MDQwNTI5MDdiZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxOTE2NjAyMDgyMDE4MDUxN2JmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjgzODE4MzE2MTc4MzUyNTA1MDUwNTA2MDQwNTE2MTAxZWU5MTkwNjEwYzA0NTY1YjYwMDA2MDQwNTE4MDgzMDM4MTg1NWFmNDkxNTA1MDNkODA2MDAwODExNDYxMDIyOTU3NjA0MDUxOTE1MDYwMWYxOTYwM2YzZDAxMTY4MjAxNjA0MDUyM2Q4MjUyM2Q2MDAwNjAyMDg0MDEzZTYxMDIyZTU2NWI2MDYwOTE1MDViNTA1MDUwNTA1MDUwNTY1YjYwMDA2MTAyNDIzMDgzNjEwNDVlNTY1YjkwNTA2MDE2NjAwMzBiODExNDYxMDI4YTU3NjA0MDUxN2YwOGMzNzlhMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwODE1MjYwMDQwMTYxMDI4MTkwNjEwYzY3NTY1YjYwNDA1MTgwOTEwMzkwZmQ1YjUwNTA1NjViNjAwMDYxMDI5YTMwODM2MTA1NzY1NjViOTA1MDYwMTY2MDAzMGI4MTE0NjEwMmUyNTc2MDQwNTE3ZjA4YzM3OWEwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDA4MTUyNjAwNDAxNjEwMmQ5OTA2MTBjZDM1NjViNjA0MDUxODA5MTAzOTBmZDViNTA1MDU2NWI2MDAwODA2MDYwNjAwMDgwNjEwMTY3NzNmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmMTY2MzI3OGUwYjg4NjBlMDFiODk4OTg5NjA0MDUxNjAyNDAxNjEwMzI0OTM5MjkxOTA2MTBkMTY1NjViNjA0MDUxNjAyMDgxODMwMzAzODE1MjkwNjA0MDUyOTA3YmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmMTkxNjYwMjA4MjAxODA1MTdiZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmY4MzgxODMxNjE3ODM1MjUwNTA1MDUwNjA0MDUxNjEwMzhlOTE5MDYxMGMwNDU2NWI2MDAwNjA0MDUxODA4MzAzODE2MDAwODY1YWYxOTE1MDUwM2Q4MDYwMDA4MTE0NjEwM2NiNTc2MDQwNTE5MTUwNjAxZjE5NjAzZjNkMDExNjgyMDE2MDQwNTIzZDgyNTIzZDYwMDA2MDIwODQwMTNlNjEwM2QwNTY1YjYwNjA5MTUwNWI1MDkxNTA5MTUwODE2MTA0MmM1NzYwMTU2MDAwODA2N2ZmZmZmZmZmZmZmZmZmZmY4MTExMTU2MTAzZjg1NzYxMDNmNzYxMDcyMzU2NWI1YjYwNDA1MTkwODA4MjUyODA2MDIwMDI2MDIwMDE4MjAxNjA0MDUyODAxNTYxMDQyNjU3ODE2MDIwMDE2MDIwODIwMjgwMzY4MzM3ODA4MjAxOTE1MDUwOTA1MDViNTA2MTA0NDE1NjViODA4MDYwMjAwMTkwNTE4MTAxOTA2MTA0NDA5MTkwNjEwZWIyNTY1YjViODI2MDAzMGI5MjUwODA5NTUwODE5NjUwODI5NzUwNTA1MDUwNTA1MDkzNTA5MzUwOTM5MDUwNTY1YjYwMDA4MDYwMDA2MTAxNjc3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNjYzMDk5Nzk0ZTg2MGUwMWI4Njg2NjA0MDUxNjAyNDAxNjEwNDk3OTI5MTkwNjEwZjIxNTY1YjYwNDA1MTYwMjA4MTgzMDMwMzgxNTI5MDYwNDA1MjkwN2JmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjE5MTY2MDIwODIwMTgwNTE3YmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmODM4MTgzMTYxNzgzNTI1MDUwNTA1MDYwNDA1MTYxMDUwMTkxOTA2MTBjMDQ1NjViNjAwMDYwNDA1MTgwODMwMzgxNjAwMDg2NWFmMTkxNTA1MDNkODA2MDAwODExNDYxMDUzZTU3NjA0MDUxOTE1MDYwMWYxOTYwM2YzZDAxMTY4MjAxNjA0MDUyM2Q4MjUyM2Q2MDAwNjAyMDg0MDEzZTYxMDU0MzU2NWI2MDYwOTE1MDViNTA5MTUwOTE1MDgxNjEwNTU0NTc2MDE1NjEwNTY5NTY1YjgwODA2MDIwMDE5MDUxODEwMTkwNjEwNTY4OTE5MDYxMGY0YTU2NWI1YjYwMDMwYjkyNTA1MDUwOTI5MTUwNTA1NjViNjAwMDgwNjAwMDYxMDE2NzczZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjE2NjM0OTE0NmJkZTYwZTAxYjg2ODY2MDQwNTE2MDI0MDE2MTA1YWY5MjkxOTA2MTBmMjE1NjViNjA0MDUxNjAyMDgxODMwMzAzODE1MjkwNjA0MDUyOTA3YmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmMTkxNjYwMjA4MjAxODA1MTdiZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmY4MzgxODMxNjE3ODM1MjUwNTA1MDUwNjA0MDUxNjEwNjE5OTE5MDYxMGMwNDU2NWI2MDAwNjA0MDUxODA4MzAzODE2MDAwODY1YWYxOTE1MDUwM2Q4MDYwMDA4MTE0NjEwNjU2NTc2MDQwNTE5MTUwNjAxZjE5NjAzZjNkMDExNjgyMDE2MDQwNTIzZDgyNTIzZDYwMDA2MDIwODQwMTNlNjEwNjViNTY1YjYwNjA5MTUwNWI1MDkxNTA5MTUwODE2MTA2NmM1NzYwMTU2MTA2ODE1NjViODA4MDYwMjAwMTkwNTE4MTAxOTA2MTA2ODA5MTkwNjEwZjRhNTY1YjViNjAwMzBiOTI1MDUwNTA5MjkxNTA1MDU2NWI2MTA5Y2Y4MDYxMGY3ODgzMzkwMTkwNTY1YjYwMDA2MDQwNTE5MDUwOTA1NjViNjAwMDgwZmQ1YjYwMDA4MGZkNWI2MDAwNzNmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmODIxNjkwNTA5MTkwNTA1NjViNjAwMDYxMDZkYTgyNjEwNmFmNTY1YjkwNTA5MTkwNTA1NjViNjEwNmVhODE2MTA2Y2Y1NjViODExNDYxMDZmNTU3NjAwMDgwZmQ1YjUwNTY1YjYwMDA4MTM1OTA1MDYxMDcwNzgxNjEwNmUxNTY1YjkyOTE1MDUwNTY1YjYwMDA4MGZkNWI2MDAwNjAxZjE5NjAxZjgzMDExNjkwNTA5MTkwNTA1NjViN2Y0ZTQ4N2I3MTAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwNjAwMDUyNjA0MTYwMDQ1MjYwMjQ2MDAwZmQ1YjYxMDc1YjgyNjEwNzEyNTY1YjgxMDE4MTgxMTA2N2ZmZmZmZmZmZmZmZmZmZmY4MjExMTcxNTYxMDc3YTU3NjEwNzc5NjEwNzIzNTY1YjViODA2MDQwNTI1MDUwNTA1NjViNjAwMDYxMDc4ZDYxMDY5YjU2NWI5MDUwNjEwNzk5ODI4MjYxMDc1MjU2NWI5MTkwNTA1NjViNjAwMDY3ZmZmZmZmZmZmZmZmZmZmZjgyMTExNTYxMDdiOTU3NjEwN2I4NjEwNzIzNTY1YjViNjAyMDgyMDI5MDUwNjAyMDgxMDE5MDUwOTE5MDUwNTY1YjYwMDA4MGZkNWI2MDAwODBmZDViNjAwMDY3ZmZmZmZmZmZmZmZmZmZmZjgyMTExNTYxMDdlZjU3NjEwN2VlNjEwNzIzNTY1YjViNjEwN2Y4ODI2MTA3MTI1NjViOTA1MDYwMjA4MTAxOTA1MDkxOTA1MDU2NWI4MjgxODMzNzYwMDA4MzgzMDE1MjUwNTA1MDU2NWI2MDAwNjEwODI3NjEwODIyODQ2MTA3ZDQ1NjViNjEwNzgzNTY1YjkwNTA4MjgxNTI2MDIwODEwMTg0ODQ4NDAxMTExNTYxMDg0MzU3NjEwODQyNjEwN2NmNTY1YjViNjEwODRlODQ4Mjg1NjEwODA1NTY1YjUwOTM5MjUwNTA1MDU2NWI2MDAwODI2MDFmODMwMTEyNjEwODZiNTc2MTA4NmE2MTA3MGQ1NjViNWI4MTM1NjEwODdiODQ4MjYwMjA4NjAxNjEwODE0NTY1YjkxNTA1MDkyOTE1MDUwNTY1YjYwMDA2MTA4OTc2MTA4OTI4NDYxMDc5ZTU2NWI2MTA3ODM1NjViOTA1MDgwODM4MjUyNjAyMDgyMDE5MDUwNjAyMDg0MDI4MzAxODU4MTExMTU2MTA4YmE1NzYxMDhiOTYxMDdjYTU2NWI1YjgzNWI4MTgxMTAxNTYxMDkwMTU3ODAzNTY3ZmZmZmZmZmZmZmZmZmZmZjgxMTExNTYxMDhkZjU3NjEwOGRlNjEwNzBkNTY1YjViODA4NjAxNjEwOGVjODk4MjYxMDg1NjU2NWI4NTUyNjAyMDg1MDE5NDUwNTA1MDYwMjA4MTAxOTA1MDYxMDhiYzU2NWI1MDUwNTA5MzkyNTA1MDUwNTY1YjYwMDA4MjYwMWY4MzAxMTI2MTA5MjA1NzYxMDkxZjYxMDcwZDU2NWI1YjgxMzU2MTA5MzA4NDgyNjAyMDg2MDE2MTA4ODQ1NjViOTE1MDUwOTI5MTUwNTA1NjViNjAwMDgwNjA0MDgzODUwMzEyMTU2MTA5NTA1NzYxMDk0ZjYxMDZhNTU2NWI1YjYwMDA2MTA5NWU4NTgyODYwMTYxMDZmODU2NWI5MjUwNTA2MDIwODMwMTM1NjdmZmZmZmZmZmZmZmZmZmZmODExMTE1NjEwOTdmNTc2MTA5N2U2MTA2YWE1NjViNWI2MTA5OGI4NTgyODYwMTYxMDkwYjU2NWI5MTUwNTA=","b64Record":"CiAIFiocCgwIARAMGgYIgK6ZpA8SDAgBEA8aBgiArpmkDxIw2UiE7E9+rGE6mcm/f6RVB7rcRR/4vaU7oguh+CqRbuCngDq4y8rc6f1t2IwS76zZGgsIyP/+rAYQuLXPQCIPCgkIjP/+rAYQvhcSAhgCKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjlIA"},{"b64Body":"Cg8KCQiM//6sBhDEFxICGAISAhgDGPfR6j4iAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjoIBiCASAxjECSKAIDkyNTA5MjkwNTA1NjViNjAwMDYwMjA4Mjg0MDMxMjE1NjEwOWFiNTc2MTA5YWE2MTA2YTU1NjViNWI2MDAwNjEwOWI5ODQ4Mjg1MDE2MTA2Zjg1NjViOTE1MDUwOTI5MTUwNTA1NjViNjAwMDgyODI1MjYwMjA4MjAxOTA1MDkyOTE1MDUwNTY1YjdmNDM2MTZlMjc3NDIxMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDYwMDA4MjAxNTI1MDU2NWI2MDAwNjEwYTA5NjAwNjgzNjEwOWMyNTY1YjkxNTA2MTBhMTQ4MjYxMDlkMzU2NWI2MDIwODIwMTkwNTA5MTkwNTA1NjViNjAwMDYwMjA4MjAxOTA1MDgxODEwMzYwMDA4MzAxNTI2MTBhMzg4MTYxMDlmYzU2NWI5MDUwOTE5MDUwNTY1YjYxMGE0ODgxNjEwNmNmNTY1YjgyNTI1MDUwNTY1YjYwMDA4MTUxOTA1MDkxOTA1MDU2NWI2MDAwODI4MjUyNjAyMDgyMDE5MDUwOTI5MTUwNTA1NjViNjAwMDgxOTA1MDYwMjA4MjAxOTA1MDkxOTA1MDU2NWI2MDAwODE1MTkwNTA5MTkwNTA1NjViNjAwMDgyODI1MjYwMjA4MjAxOTA1MDkyOTE1MDUwNTY1YjYwMDA1YjgzODExMDE1NjEwYWI0NTc4MDgyMDE1MTgxODQwMTUyNjAyMDgxMDE5MDUwNjEwYTk5NTY1YjgzODExMTE1NjEwYWMzNTc2MDAwODQ4NDAxNTI1YjUwNTA1MDUwNTY1YjYwMDA2MTBhZDQ4MjYxMGE3YTU2NWI2MTBhZGU4MTg1NjEwYTg1NTY1YjkzNTA2MTBhZWU4MTg1NjAyMDg2MDE2MTBhOTY1NjViNjEwYWY3ODE2MTA3MTI1NjViODQwMTkxNTA1MDkyOTE1MDUwNTY1YjYwMDA2MTBiMGU4MzgzNjEwYWM5NTY1YjkwNTA5MjkxNTA1MDU2NWI2MDAwNjAyMDgyMDE5MDUwOTE5MDUwNTY1YjYwMDA2MTBiMmU4MjYxMGE0ZTU2NWI2MTBiMzg4MTg1NjEwYTU5NTY1YjkzNTA4MzYwMjA4MjAyODUwMTYxMGI0YTg1NjEwYTZhNTY1YjgwNjAwMDViODU4MTEwMTU2MTBiODY1Nzg0ODQwMzg5NTI4MTUxNjEwYjY3ODU4MjYxMGIwMjU2NWI5NDUwNjEwYjcyODM2MTBiMTY1NjViOTI1MDYwMjA4YTAxOTk1MDUwNjAwMTgxMDE5MDUwNjEwYjRlNTY1YjUwODI5NzUwODc5NTUwNTA1MDUwNTA1MDkyOTE1MDUwNTY1YjYwMDA2MDQwODIwMTkwNTA2MTBiYWQ2MDAwODMwMTg1NjEwYTNmNTY1YjgxODEwMzYwMjA4MzAxNTI2MTBiYmY4MTg0NjEwYjIzNTY1YjkwNTA5MzkyNTA1MDUwNTY1YjYwMDA4MTkwNTA5MjkxNTA1MDU2NWI2MDAwNjEwYmRlODI2MTBhN2E1NjViNjEwYmU4ODE4NTYxMGJjODU2NWI5MzUwNjEwYmY4ODE4NTYwMjA4NjAxNjEwYTk2NTY1YjgwODQwMTkxNTA1MDkyOTE1MDUwNTY1YjYwMDA2MTBjMTA4Mjg0NjEwYmQzNTY1YjkxNTA4MTkwNTA5MjkxNTA1MDU2NWI3ZjUzNmYyMDc1NmU2NjYxNjk3MjAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDA2MDAwODIwMTUyNTA1NjViNjAwMDYxMGM1MTYwMDk4MzYxMDljMjU2NWI5MTUwNjEwYzVjODI2MTBjMWI1NjViNjAyMDgyMDE5MDUwOTE5MDUwNTY1YjYwMDA2MDIwODIwMTkwNTA4MTgxMDM2MDAwODMwMTUyNjEwYzgwODE2MTBjNDQ1NjViOTA1MDkxOTA1MDU2NWI3ZjQ5NzQyNzczMjA3NTZlNjg2NTYxNzI2NDIwNmY2NjJlMmUyZTAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDA2MDAwODIwMTUyNTA1NjViNjAwMDYxMGNiZDYwMTI4MzYxMDljMjU2NWI5MTUwNjEwY2M4ODI2MTBjODc1NjViNjAyMDgyMDE5MDUwOTE5MDUwNTY1YjYwMDA2MDIwODIwMTkwNTA4MTgxMDM2MDAwODMwMTUyNjEwY2VjODE2MTBjYjA1NjViOTA1MDkxOTA1MDU2NWI2MDAwNjdmZmZmZmZmZmZmZmZmZmZmODIxNjkwNTA5MTkwNTA1NjViNjEwZDEwODE2MTBjZjM1NjViODI1MjUwNTA1NjViNjAwMDYwNjA4MjAxOTA1MDYxMGQyYjYwMDA4MzAxODY2MTBhM2Y1NjViNjEwZDM4NjAyMDgzMDE4NTYxMGQwNzU2NWI4MTgxMDM2MDQwODMwMTUyNjEwZDRhODE4NDYxMGIyMzU2NWI5MDUwOTQ5MzUwNTA1MDUwNTY1YjYwMDA4MTYwMDMwYjkwNTA5MTkwNTA1NjViNjEwZDZhODE2MTBkNTQ1NjViODExNDYxMGQ3NTU3NjAwMDgwZmQ1YjUwNTY1YjYwMDA4MTUxOTA1MDYxMGQ4NzgxNjEwZDYxNTY1YjkyOTE1MDUwNTY1YjYxMGQ5NjgxNjEwY2YzNTY1YjgxMTQ2MTBkYTE1NzYwMDA4MGZkNWI1MDU2NWI2MDAwODE1MTkwNTA2MTBkYjM4MTYxMGQ4ZDU2NWI5MjkxNTA1MDU2NWI2MDAwNjdmZmZmZmZmZmZmZmZmZmZmODIxMTE1NjEwZGQ0NTc2MTBkZDM2MTA3MjM1NjViNWI2MDIwODIwMjkwNTA2MDIwODEwMTkwNTA5MTkwNTA1NjViNjAwMDgxOTA1MDkxOTA1MDU2NWI2MTBkZjg4MTYxMGRlNTU2NWI4MTE0NjEwZTAzNTc2MDAwODBmZDViNTA1NjViNjAwMDgxNTE5MDUwNjEwZTE1ODE2MTBkZWY1NjViOTI5MTUwNTA1NjViNjAwMDYxMGUyZTYxMGUyOTg0NjEwZGI5NTY1YjYxMDc4MzU2NWI5MDUwODA4MzgyNTI2MDIwODIwMTkwNTA2MDIwODQwMjgzMDE4NTgxMTExNTYxMGU1MTU3NjEwZTUwNjEwN2NhNTY1YjViODM1YjgxODExMDE1NjEwZTdhNTc4MDYxMGU2Njg4ODI2MTBlMDY1NjViODQ1MjYwMjA4NDAxOTM1MDUwNjAyMDgxMDE5MDUwNjEwZTUzNTY1YjUwNTA1MDkzOTI1MDUwNTA1NjViNjAwMDgyNjAxZjgzMDExMjYxMGU5OTU3NjEwZTk4NjEwNzBkNTY1YjViODE1MTYxMGVhOTg0ODI2MDIwODYwMTYxMGUxYjU2NWI5MTUwNTA5MjkxNTA1MDU2NWI2MDAwODA2MDAwNjA2MDg0ODYwMzEyMTU2MTBlY2I1NzYxMGVjYTYxMDZhNTU2NWI1YjYwMDA2MTBlZDk4NjgyODcwMTYxMGQ3ODU2NWI5MzUwNTA2MDIwNjEwZWVhODY4Mjg3MDE2MTBkYTQ1NjViOTI1MDUwNjA0MDg0MDE1MTY3ZmZmZmZmZmZmZmZmZmZmZjgxMTExNTYxMGYwYjU3NjEwZjBhNjEwNmFhNTY1YjViNjEwZjE3ODY4Mjg3MDE2MTBlODQ1NjViOTE1MDUwOTI1MDkyNTA5MjU2NWI2MDAwNjA0MDgyMDE5MDUwNjEwZjM2NjAwMDgzMDE4NTYxMGEzZjU2NWI2MTBmNDM2MDIwODMwMTg0NjEwYTNmNTY1YjkzOTI1MDUwNTA1NjViNjAwMDYwMjA4Mjg0MDMxMjE1NjEwZjYwNTc2MTBmNWY2MTA2YTU1NjViNWI2MDAwNjEwZjZlODQ4Mjg1MDE2MTBkNzg1NjViOTE1MDUwOTI5MTUwNTA1NmZlNjA4MDYwNDA1MjM0ODAxNTYxMDAxMDU3NjAwMDgwZmQ1YjUwNjEwOWFmODA2MTAwMjA2MDAwMzk2MDAwZjNmZTYwODA2MDQwNTIzNDgwMTU2MTAwMTA1NzYwMDA4MGZkNWI1MDYwMDQzNjEwNjEwMDJiNTc2MDAwMzU2MGUwMWM4MDYzMGEyODRjYjYxNDYxMDAzMDU3NWI2MDAwODBmZDViNjEwMDRhNjAwNDgwMzYwMzgxMDE5MDYxMDA0NTkxOTA2MTA0YzY1NjViNjEwMDRjNTY1YjAwNWI2MDAwODA2MDAwNjEwMDVkODU2MDAwODY2MTAwYjA1NjViOTI1MDkyNTA5MjUwNjAxNjYwMDMwYjgzMTQ2MTAwYTk1NzYwNDA1MTdmMDhjMzc5YTAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDgxNTI2MDA0MDE2MTAwYTA5MDYxMDU3ZjU2NWI2MDQwNTE4MDkxMDM5MGZkNWI1MDUwNTA1MDUwNTY1YjYwMDA4MDYwNjA2MDAwODA2MTAxNjc3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNjYzMjc4ZTBiODg2MGUwMWI4OTg5ODk2MDQwNTE2MDI0MDE2MTAwZWU5MzkyOTE5MDYxMDcxYjU2NWI2MDQwNTE2MDIwODE4MzAzMDM4MTUyOTA2MDQwNTI5MDdiZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxOTE2NjAyMDgyMDE4MDUxN2JmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjgzODE4MzE2MTc4MzUyNTA1MDUwNTA2MDQwNTE2MTAxNTg5MTkwNjEwNzk1NTY1YjYwMDA2MDQwNTE4MDgzMDM4MTYwMDA4NjVhZjE5MTUwNTAzZDgwNjAwMDgxMTQ2MTAxOTU1NzYwNDA1MTkxNTA2MDFmMTk2MDNmM2QwMTE2ODIwMTYwNDA1MjNkODI1MjNkNjAwMDYwMjA4NDAxM2U2MTAxOWE1NjViNjA2MDkxNTA1YjUwOTE1MDkxNTA4MTYxMDFmNjU3NjAxNTYwMDA4MDY3ZmZmZmZmZmZmZmZmZmZmZjgxMTExNTYxMDFjMjU3NjEwMWMxNjEwMmIwNTY1YjViNjA0MDUxOTA4MDgyNTI4MDYwMjAwMjYwMjAwMTgyMDE2MDQwNTI4MDE1NjEwMWYwNTc4MTYwMjAwMTYwMjA4MjAyODAzNjgzMzc4MDgyMDE5MTUwNTA5MDUwNWI1MDYxMDIwYjU2NWI=","b64Record":"CiAIFiocCgwIARAMGgYIgK6ZpA8SDAgBEA8aBgiArpmkDxIw1Sys7Z45SLCs19NrLoR1IyGsqz26d+dDs8BK2IgpjTUv9NQINhgArC/AUhNfB429GgwIyP/+rAYQt4bIpQIiDwoJCIz//qwGEMQXEgIYAiogw4PCrsOCwrfDg8K5dEY4w4LCrkrDg8KLw4PCkMODwo5SAA=="},{"b64Body":"Cg8KCQiN//6sBhDKFxICGAISAhgDGPbR6j4iAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjoIBiCASAxjECSKAIDgwODA2MDIwMDE5MDUxODEwMTkwNjEwMjBhOTE5MDYxMDkwYTU2NWI1YjgyNjAwMzBiOTI1MDgwOTU1MDgxOTY1MDgyOTc1MDUwNTA1MDUwNTA5MzUwOTM1MDkzOTA1MDU2NWI2MDAwNjA0MDUxOTA1MDkwNTY1YjYwMDA4MGZkNWI2MDAwODBmZDViNjAwMDczZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjgyMTY5MDUwOTE5MDUwNTY1YjYwMDA2MTAyNjc4MjYxMDIzYzU2NWI5MDUwOTE5MDUwNTY1YjYxMDI3NzgxNjEwMjVjNTY1YjgxMTQ2MTAyODI1NzYwMDA4MGZkNWI1MDU2NWI2MDAwODEzNTkwNTA2MTAyOTQ4MTYxMDI2ZTU2NWI5MjkxNTA1MDU2NWI2MDAwODBmZDViNjAwMDYwMWYxOTYwMWY4MzAxMTY5MDUwOTE5MDUwNTY1YjdmNGU0ODdiNzEwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDYwMDA1MjYwNDE2MDA0NTI2MDI0NjAwMGZkNWI2MTAyZTg4MjYxMDI5ZjU2NWI4MTAxODE4MTEwNjdmZmZmZmZmZmZmZmZmZmZmODIxMTE3MTU2MTAzMDc1NzYxMDMwNjYxMDJiMDU2NWI1YjgwNjA0MDUyNTA1MDUwNTY1YjYwMDA2MTAzMWE2MTAyMjg1NjViOTA1MDYxMDMyNjgyODI2MTAyZGY1NjViOTE5MDUwNTY1YjYwMDA2N2ZmZmZmZmZmZmZmZmZmZmY4MjExMTU2MTAzNDY1NzYxMDM0NTYxMDJiMDU2NWI1YjYwMjA4MjAyOTA1MDYwMjA4MTAxOTA1MDkxOTA1MDU2NWI2MDAwODBmZDViNjAwMDgwZmQ1YjYwMDA2N2ZmZmZmZmZmZmZmZmZmZmY4MjExMTU2MTAzN2M1NzYxMDM3YjYxMDJiMDU2NWI1YjYxMDM4NTgyNjEwMjlmNTY1YjkwNTA2MDIwODEwMTkwNTA5MTkwNTA1NjViODI4MTgzMzc2MDAwODM4MzAxNTI1MDUwNTA1NjViNjAwMDYxMDNiNDYxMDNhZjg0NjEwMzYxNTY1YjYxMDMxMDU2NWI5MDUwODI4MTUyNjAyMDgxMDE4NDg0ODQwMTExMTU2MTAzZDA1NzYxMDNjZjYxMDM1YzU2NWI1YjYxMDNkYjg0ODI4NTYxMDM5MjU2NWI1MDkzOTI1MDUwNTA1NjViNjAwMDgyNjAxZjgzMDExMjYxMDNmODU3NjEwM2Y3NjEwMjlhNTY1YjViODEzNTYxMDQwODg0ODI2MDIwODYwMTYxMDNhMTU2NWI5MTUwNTA5MjkxNTA1MDU2NWI2MDAwNjEwNDI0NjEwNDFmODQ2MTAzMmI1NjViNjEwMzEwNTY1YjkwNTA4MDgzODI1MjYwMjA4MjAxOTA1MDYwMjA4NDAyODMwMTg1ODExMTE1NjEwNDQ3NTc2MTA0NDY2MTAzNTc1NjViNWI4MzViODE4MTEwMTU2MTA0OGU1NzgwMzU2N2ZmZmZmZmZmZmZmZmZmZmY4MTExMTU2MTA0NmM1NzYxMDQ2YjYxMDI5YTU2NWI1YjgwODYwMTYxMDQ3OTg5ODI2MTAzZTM1NjViODU1MjYwMjA4NTAxOTQ1MDUwNTA2MDIwODEwMTkwNTA2MTA0NDk1NjViNTA1MDUwOTM5MjUwNTA1MDU2NWI2MDAwODI2MDFmODMwMTEyNjEwNGFkNTc2MTA0YWM2MTAyOWE1NjViNWI4MTM1NjEwNGJkODQ4MjYwMjA4NjAxNjEwNDExNTY1YjkxNTA1MDkyOTE1MDUwNTY1YjYwMDA4MDYwNDA4Mzg1MDMxMjE1NjEwNGRkNTc2MTA0ZGM2MTAyMzI1NjViNWI2MDAwNjEwNGViODU4Mjg2MDE2MTAyODU1NjViOTI1MDUwNjAyMDgzMDEzNTY3ZmZmZmZmZmZmZmZmZmZmZjgxMTExNTYxMDUwYzU3NjEwNTBiNjEwMjM3NTY1YjViNjEwNTE4ODU4Mjg2MDE2MTA0OTg1NjViOTE1MDUwOTI1MDkyOTA1MDU2NWI2MDAwODI4MjUyNjAyMDgyMDE5MDUwOTI5MTUwNTA1NjViN2Y0MzYxNmUyNzc0MjA2NTc2NjU2ZTIxMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwNjAwMDgyMDE1MjUwNTY1YjYwMDA2MTA1Njk2MDBiODM2MTA1MjI1NjViOTE1MDYxMDU3NDgyNjEwNTMzNTY1YjYwMjA4MjAxOTA1MDkxOTA1MDU2NWI2MDAwNjAyMDgyMDE5MDUwODE4MTAzNjAwMDgzMDE1MjYxMDU5ODgxNjEwNTVjNTY1YjkwNTA5MTkwNTA1NjViNjEwNWE4ODE2MTAyNWM1NjViODI1MjUwNTA1NjViNjAwMDY3ZmZmZmZmZmZmZmZmZmZmZjgyMTY5MDUwOTE5MDUwNTY1YjYxMDVjYjgxNjEwNWFlNTY1YjgyNTI1MDUwNTY1YjYwMDA4MTUxOTA1MDkxOTA1MDU2NWI2MDAwODI4MjUyNjAyMDgyMDE5MDUwOTI5MTUwNTA1NjViNjAwMDgxOTA1MDYwMjA4MjAxOTA1MDkxOTA1MDU2NWI2MDAwODE1MTkwNTA5MTkwNTA1NjViNjAwMDgyODI1MjYwMjA4MjAxOTA1MDkyOTE1MDUwNTY1YjYwMDA1YjgzODExMDE1NjEwNjM3NTc4MDgyMDE1MTgxODQwMTUyNjAyMDgxMDE5MDUwNjEwNjFjNTY1YjgzODExMTE1NjEwNjQ2NTc2MDAwODQ4NDAxNTI1YjUwNTA1MDUwNTY1YjYwMDA2MTA2NTc4MjYxMDVmZDU2NWI2MTA2NjE4MTg1NjEwNjA4NTY1YjkzNTA2MTA2NzE4MTg1NjAyMDg2MDE2MTA2MTk1NjViNjEwNjdhODE2MTAyOWY1NjViODQwMTkxNTA1MDkyOTE1MDUwNTY1YjYwMDA2MTA2OTE4MzgzNjEwNjRjNTY1YjkwNTA5MjkxNTA1MDU2NWI2MDAwNjAyMDgyMDE5MDUwOTE5MDUwNTY1YjYwMDA2MTA2YjE4MjYxMDVkMTU2NWI2MTA2YmI4MTg1NjEwNWRjNTY1YjkzNTA4MzYwMjA4MjAyODUwMTYxMDZjZDg1NjEwNWVkNTY1YjgwNjAwMDViODU4MTEwMTU2MTA3MDk1Nzg0ODQwMzg5NTI4MTUxNjEwNmVhODU4MjYxMDY4NTU2NWI5NDUwNjEwNmY1ODM2MTA2OTk1NjViOTI1MDYwMjA4YTAxOTk1MDUwNjAwMTgxMDE5MDUwNjEwNmQxNTY1YjUwODI5NzUwODc5NTUwNTA1MDUwNTA1MDkyOTE1MDUwNTY1YjYwMDA2MDYwODIwMTkwNTA2MTA3MzA2MDAwODMwMTg2NjEwNTlmNTY1YjYxMDczZDYwMjA4MzAxODU2MTA1YzI1NjViODE4MTAzNjA0MDgzMDE1MjYxMDc0ZjgxODQ2MTA2YTY1NjViOTA1MDk0OTM1MDUwNTA1MDU2NWI2MDAwODE5MDUwOTI5MTUwNTA1NjViNjAwMDYxMDc2ZjgyNjEwNWZkNTY1YjYxMDc3OTgxODU2MTA3NTk1NjViOTM1MDYxMDc4OTgxODU2MDIwODYwMTYxMDYxOTU2NWI4MDg0MDE5MTUwNTA5MjkxNTA1MDU2NWI2MDAwNjEwN2ExODI4NDYxMDc2NDU2NWI5MTUwODE5MDUwOTI5MTUwNTA1NjViNjAwMDgxNjAwMzBiOTA1MDkxOTA1MDU2NWI2MTA3YzI4MTYxMDdhYzU2NWI4MTE0NjEwN2NkNTc2MDAwODBmZDViNTA1NjViNjAwMDgxNTE5MDUwNjEwN2RmODE2MTA3Yjk1NjViOTI5MTUwNTA1NjViNjEwN2VlODE2MTA1YWU1NjViODExNDYxMDdmOTU3NjAwMDgwZmQ1YjUwNTY1YjYwMDA4MTUxOTA1MDYxMDgwYjgxNjEwN2U1NTY1YjkyOTE1MDUwNTY1YjYwMDA2N2ZmZmZmZmZmZmZmZmZmZmY4MjExMTU2MTA4MmM1NzYxMDgyYjYxMDJiMDU2NWI1YjYwMjA4MjAyOTA1MDYwMjA4MTAxOTA1MDkxOTA1MDU2NWI2MDAwODE5MDUwOTE5MDUwNTY1YjYxMDg1MDgxNjEwODNkNTY1YjgxMTQ2MTA4NWI1NzYwMDA4MGZkNWI1MDU2NWI2MDAwODE1MTkwNTA2MTA4NmQ4MTYxMDg0NzU2NWI5MjkxNTA1MDU2NWI2MDAwNjEwODg2NjEwODgxODQ2MTA4MTE1NjViNjEwMzEwNTY1YjkwNTA4MDgzODI1MjYwMjA4MjAxOTA1MDYwMjA4NDAyODMwMTg1ODExMTE1NjEwOGE5NTc2MTA4YTg2MTAzNTc1NjViNWI4MzViODE4MTEwMTU2MTA4ZDI1NzgwNjEwOGJlODg4MjYxMDg1ZTU2NWI4NDUyNjAyMDg0MDE5MzUwNTA2MDIwODEwMTkwNTA2MTA4YWI1NjViNTA1MDUwOTM5MjUwNTA1MDU2NWI2MDAwODI2MDFmODMwMTEyNjEwOGYxNTc2MTA4ZjA2MTAyOWE1NjViNWI4MTUxNjEwOTAxODQ4MjYwMjA4NjAxNjEwODczNTY1YjkxNTA1MDkyOTE1MDUwNTY1YjYwMDA4MDYwMDA2MDYwODQ4NjAzMTIxNTYxMDkyMzU3NjEwOTIyNjEwMjMyNTY1YjViNjAwMDYxMDkzMTg2ODI4NzAxNjEwN2QwNTY1YjkzNTA1MDYwMjA2MTA5NDI4NjgyODcwMTYxMDdmYzU2NWI5MjUwNTA2MDQwODQwMTUxNjdmZmZmZmZmZmZmZmZmZmZmODExMTE1NjEwOTYzNTc2MTA5NjI2MTAyMzc1NjViNWI2MTA5NmY4NjgyODcwMTYxMDhkYzU2NWI5MTUwNTA5MjUwOTI1MDkyNTZmZWEyNjQ2OTcwNjY3MzU4MjIxMjIwYTNkZmY5MDJkODg2Y2ZmNDUzZTY2MWQyODAzODIwYWUyNmFiNGY0NWE0OGZhMzE5NTBiZDAzMWYwOGQzZWJhYzY0NzM2ZjZjNjM0MzAwMDgwYzAwMzNhMjY0Njk3MDY2NzM1ODIyMTIyMDU1MDQ1OTE4MjMwMjQ4NTQ0MjA2NThkNDk0NThiNGFlZDg0ZWYyYWZhZTI4OTg5ZTAzMTU2ODdmNTk5MTRiM2Y2NDczNmY2YzYzNDMwMDA4MGMwMDMzYTI2NDY5NzA2NjczNTgyMjEyMjA5OTA0NDBlYTM0MmY5YzgzMTE=","b64Record":"CiAIFiocCgwIARAMGgYIgK6ZpA8SDAgBEA8aBgiArpmkDxIwXviK7M0q7fDVSGAEGayIO4bwgRDMmjq4tAcHaGd9j0BO7LQQnHE4HHYEmq7DKV51GgsIyf/+rAYQh/aOSiIPCgkIjf/+rAYQyhcSAhgCKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjlIA"},{"b64Body":"Cg8KCQiN//6sBhDQFxICGAISAhgDGImGlS0iAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjoIBSxIDGMQJIkRhYTkzOTZkOTY0YmMyM2RjYjU2MjY2MjVmNTBjMGYyZDZmM2U3ODA4NDMzY2U2NjQ3MzZmNmM2MzQzMDAwODBjMDAzMw==","b64Record":"CiAIFiocCgwIARAMGgYIgK6ZpA8SDAgBEA8aBgiArpmkDxIwd1ab/do5aiDFZpuFXm1PVCfBjYYtgb6/STwNzGu7QdmQycSITXNFR6BVuWXjfh0FGgwIyf/+rAYQjruLrwIiDwoJCI3//qwGENAXEgIYAiogw4PCrsOCwrfDg8K5dEY4w4LCrkrDg8KLw4PCkMODwo5SAA=="},{"b64Body":"Cg8KCQiO//6sBhDSFxICGAISAhgDGJb7rp0CIgIIeDIgw4PCrsOCwrfDg8K5dEY4w4LCrkrDg8KLw4PCkMODwo5CRQoDGMQJGiISIAFIuFAN3KwzuAdFGJDChqGklU3FMswGG3POn//A7TVbIJChD0IFCIDO2gNSAFoAagtjZWxsYXIgZG9vcg==","b64Record":"CiUIFiIDGMUJKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjBsMqucglIgtpJgClsQwelG5Liaw45diMTCoND2bZzm6mza50Go93rw/mdJhDT1rY8aCwjK//6sBhC+uedTIg8KCQiO//6sBhDSFxICGAIqIMODwq7DgsK3w4PCuXRGOMOCwq5Kw4PCi8ODwpDDg8KOMIL4kAdCt1IKAxjFCRKCUGCAYEBSNIAVYQAQV2AAgP1bUGAENhBhAFdXYAA1YOAcgGNCXb+WFGEAXFeAY2fXMdMUYQCMV4BjcHxQGRRhALxXgGOr97/YFGEA2FeAY8qXIWEUYQD0V1tgAID9W2EAdmAEgDYDgQGQYQBxkZBhCaFWW2EBEFZbYEBRYQCDkZBhCfpWW2BAUYCRA5DzW2EApmAEgDYDgQGQYQChkZBhCaFWW2EBRlZbYEBRYQCzkZBhCfpWW2BAUYCRA5DzW2EA1mAEgDYDgQGQYQDRkZBhChVWW2EBfFZbAFthAPJgBIA2A4EBkGEA7ZGQYQp4VlthAl9WWwBbYQEOYASANgOBAZBhAQmRkGEKFVZbYQOpVlsAW2AAYQE+gzBgAIBUkGEBAAqQBHP//////////////////////////xaFYQSMVluQUJKRUFBWW2AAYQF0gzBgAIBUkGEBAAqQBHP//////////////////////////xaFYQWqVluQUJKRUFBWW2AAYQGIMINhBshWW5BQYBZgAwuBFGEB0FdgQFF/CMN5oAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACBUmAEAWEBx5BhCwJWW2BAUYCRA5D9W2AAgFSQYQEACpAEc///////////////////////////FnP//////////////////////////xZj2UJUA4NgQFGCY/////8WYOAbgVJgBAFhAimRkGELMVZbYABgQFGAgwOBYACHgDsVgBVhAkNXYACA/VtQWvEVgBVhAldXPWAAgD49YAD9W1BQUFBQUFZbYABg/2D4GzCDYEBRgGAgAWECeJBhCPhWW2AgggGBA4JSYB8ZYB+CARZgQFJQYEBRYCABYQKckZBhC8ZWW2BAUWAggYMDA4FSkGBAUoBRkGAgASBgQFFgIAFhAsWUk5KRkGEMk1ZbYEBRYCCBgwMDgVKQYEBSgFGQYCABIGAAHJBQgWBAUWEC7ZBhCPhWW4GQYEBRgJEDkGAA9ZBQgBWAFWEDDVc9YACAPj1gAP1bUGAAgGEBAAqBVIFz//////////////////////////8CGRaQg3P//////////////////////////xYCF5BVUIBz//////////////////////////8WYACAVJBhAQAKkARz//////////////////////////8Wc///////////////////////////FhRhA6VXYACA/VtQUFZbYABhA7Uwg2EH4FZbkFBgFmADC4EUYQP9V2BAUX8Iw3mgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIFSYAQBYQP0kGENLVZbYEBRgJEDkP1bYACAVJBhAQAKkARz//////////////////////////8Wc///////////////////////////FmPqWM4hg2BAUYJj/////xZg4BuBUmAEAWEEVpGQYQsxVltgAGBAUYCDA4FgAIeAOxWAFWEEcFdgAID9W1Ba8RWAFWEEhFc9YACAPj1gAP1bUFBQUFBQVltgAIBgAGEBZ3P//////////////////////////xZj7KNpF2DgG4iIiIhgQFFgJAFhBMmUk5KRkGENXFZbYEBRYCCBgwMDgVKQYEBSkHv/////////////////////////////////////GRZgIIIBgFF7/////////////////////////////////////4OBgxYXg1JQUFBQYEBRYQUzkZBhC8ZWW2AAYEBRgIMDgWAAhlrxkVBQPYBgAIEUYQVwV2BAUZFQYB8ZYD89ARaCAWBAUj2CUj1gAGAghAE+YQV1VltgYJFQW1CRUJFQgWEFhldgFWEFm1ZbgIBgIAGQUYEBkGEFmpGQYQ3aVltbYAMLklBQUJSTUFBQUFZbYACAYABhAWdz//////////////////////////8WY1z8kBFg4BuIiIiIYEBRYCQBYQXnlJOSkZBhDVxWW2BAUWAggYMDA4FSkGBAUpB7/////////////////////////////////////xkWYCCCAYBRe/////////////////////////////////////+DgYMWF4NSUFBQUGBAUWEGUZGQYQvGVltgAGBAUYCDA4FgAIZa8ZFQUD2AYACBFGEGjldgQFGRUGAfGWA/PQEWggFgQFI9glI9YABgIIQBPmEGk1ZbYGCRUFtQkVCRUIFhBqRXYBVhBrlWW4CAYCABkFGBAZBhBriRkGEN2lZbW2ADC5JQUFCUk1BQUFBWW2AAgGAAYQFnc///////////////////////////FmMJl5ToYOAbhoZgQFFgJAFhBwGSkZBhDgdWW2BAUWAggYMDA4FSkGBAUpB7/////////////////////////////////////xkWYCCCAYBRe/////////////////////////////////////+DgYMWF4NSUFBQUGBAUWEHa5GQYQvGVltgAGBAUYCDA4FgAIZa8ZFQUD2AYACBFGEHqFdgQFGRUGAfGWA/PQEWggFgQFI9glI9YABgIIQBPmEHrVZbYGCRUFtQkVCRUIFhB75XYBVhB9NWW4CAYCABkFGBAZBhB9KRkGEN2lZbW2ADC5JQUFCSkVBQVltgAIBgAGEBZ3P//////////////////////////xZjSRRr3mDgG4aGYEBRYCQBYQgZkpGQYQ4HVltgQFFgIIGDAwOBUpBgQFKQe/////////////////////////////////////8ZFmAgggGAUXv/////////////////////////////////////g4GDFheDUlBQUFBgQFFhCIORkGELxlZbYABgQFGAgwOBYACGWvGRUFA9gGAAgRRhCMBXYEBRkVBgHxlgPz0BFoIBYEBSPYJSPWAAYCCEAT5hCMVWW2BgkVBbUJFQkVCBYQjWV2AVYQjrVluAgGAgAZBRgQGQYQjqkZBhDdpWW1tgAwuSUFBQkpFQUFZbYRmcgGEOMYM5AZBWW2AAgP1bYABz//////////////////////////+CFpBQkZBQVltgAGEJNYJhCQpWW5BQkZBQVlthCUWBYQkqVluBFGEJUFdgAID9W1BWW2AAgTWQUGEJYoFhCTxWW5KRUFBWW2AAgWAHC5BQkZBQVlthCX6BYQloVluBFGEJiVdgAID9W1BWW2AAgTWQUGEJm4FhCXVWW5KRUFBWW2AAgGBAg4UDEhVhCbhXYQm3YQkFVltbYABhCcaFgoYBYQlTVluSUFBgIGEJ14WChgFhCYxWW5FQUJJQkpBQVltgAIGQUJGQUFZbYQn0gWEJ4VZbglJQUFZbYABgIIIBkFBhCg9gAIMBhGEJ61ZbkpFQUFZbYABgIIKEAxIVYQorV2EKKmEJBVZbW2AAYQo5hIKFAWEJU1ZbkVBQkpFQUFZbYACBkFCRkFBWW2EKVYFhCkJWW4EUYQpgV2AAgP1bUFZbYACBNZBQYQpygWEKTFZbkpFQUFZbYABgIIKEAxIVYQqOV2EKjWEJBVZbW2AAYQqchIKFAWEKY1ZbkVBQkpFQUFZbYACCglJgIIIBkFCSkVBQVlt/TmV2ZXIgZW5kcyB3ZWxsLgAAAAAAAAAAAAAAAAAAAABgAIIBUlBWW2AAYQrsYBCDYQqlVluRUGEK94JhCrZWW2AgggGQUJGQUFZbYABgIIIBkFCBgQNgAIMBUmELG4FhCt9WW5BQkZBQVlthCyuBYQkqVluCUlBQVltgAGAgggGQUGELRmAAgwGEYQsiVluSkVBQVltgAIFRkFCRkFBWW2AAgZBQkpFQUFZbYABbg4EQFWELgFeAggFRgYQBUmAggQGQUGELZVZbg4ERFWELj1dgAISEAVJbUFBQUFZbYABhC6CCYQtMVlthC6qBhWELV1Zbk1BhC7qBhWAghgFhC2JWW4CEAZFQUJKRUFBWW2AAYQvSgoRhC5VWW5FQgZBQkpFQUFZbYAB//wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACCFpBQkZBQVltgAIGQUJGQUFZbYQwkYQwfgmEL3VZbYQwJVluCUlBQVltgAIFgYBuQUJGQUFZbYABhDEKCYQwqVluQUJGQUFZbYABhDFSCYQw3VluQUJGQUFZbYQxsYQxngmEJKlZbYQxJVluCUlBQVltgAIGQUJGQUFZbYQyNYQyIgmEKQlZbYQxyVluCUlBQVltgAGEMn4KHYQwTVltgAYIBkVBhDK+ChmEMW1ZbYBSCAZFQYQy/goVhDHxWW2AgggGRUGEMz4KEYQx8VltgIIIBkVCBkFCVlFBQUFBQVlt/V2VsbCwgSSBuZXZlciEAAAAAAAAAAAAAAAAAAAAAAABgAIIBUlBWW2AAYQ0XYA6DYQqlVluRUGENIoJhDOFWW2AgggGQUJGQUFZbYABgIIIBkFCBgQNgAIMBUmENRoFhDQpWW5BQkZBQVlthDVaBYQloVluCUlBQVltgAGCAggGQUGENcWAAgwGHYQsiVlthDX5gIIMBhmELIlZbYQ2LYECDAYVhCyJWW2ENmGBggwGEYQ1NVluVlFBQUFBQVltgAIFgAwuQUJGQUFZbYQ23gWENoVZbgRRhDcJXYACA/VtQVltgAIFRkFBhDdSBYQ2uVluSkVBQVltgAGAggoQDEhVhDfBXYQ3vYQkFVltbYABhDf6EgoUBYQ3FVluRUFCSkVBQVltgAGBAggGQUGEOHGAAgwGFYQsiVlthDilgIIMBhGELIlZbk5JQUFBW/mCAYEBSNIAVYQAQV2AAgP1bUGEZfIBhACBgADlgAPP+YIBgQFI0gBVhABBXYACA/VtQYAQ2EGEATFdgADVg4ByAYwooTLYUYQBRV4BjSEqVqRRhAG1XgGPZQlQDFGEAiVeAY+pYziEUYQClV1tgAID9W2EAa2AEgDYDgQGQYQBmkZBhCTlWW2EAwVZbAFthAIdgBIA2A4EBkGEAgpGQYQk5VlthASVWWwBbYQCjYASANgOBAZBhAJ6RkGEJlVZbYQI2VlsAW2EAv2AEgDYDgQGQYQC6kZBhCZVWW2ECjlZbAFtgAIBgAGEA0oVgAIZhAuZWW5JQklCSUGAWYAMLgxRhAR5XYEBRfwjDeaAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgVJgBAFhARWQYQofVltgQFGAkQOQ/VtQUFBQUFZbYABgQFFhATOQYQaOVltgQFGAkQOQYADwgBWAFWEBT1c9YACAPj1gAP1bUJBQgHP//////////////////////////xZjCihMtmDgG4SEYEBRYCQBYQGEkpGQYQuYVltgQFFgIIGDAwOBUpBgQFKQe/////////////////////////////////////8ZFmAgggGAUXv/////////////////////////////////////g4GDFheDUlBQUFBgQFFhAe6RkGEMBFZbYABgQFGAgwOBhVr0kVBQPYBgAIEUYQIpV2BAUZFQYB8ZYD89ARaCAWBAUj2CUj1gAGAghAE+YQIuVltgYJFQW1BQUFBQUFZbYABhAkIwg2EEXlZbkFBgFmADC4EUYQKKV2BAUX8Iw3mgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIFSYAQBYQKBkGEMZ1ZbYEBRgJEDkP1bUFBWW2AAYQKaMINhBXZWW5BQYBZgAwuBFGEC4ldgQFF/CMN5oAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACBUmAEAWEC2ZBhDNNWW2BAUYCRA5D9W1BQVltgAIBgYGAAgGEBZ3P//////////////////////////xZjJ44LiGDgG4mJiWBAUWAkAWEDJJOSkZBhDRZWW2BAUWAggYMDA4FSkGBAUpB7/////////////////////////////////////xkWYCCCAYBRe/////////////////////////////////////+DgYMWF4NSUFBQUGBAUWEDjpGQYQwEVltgAGBAUYCDA4FgAIZa8ZFQUD2AYACBFGEDy1dgQFGRUGAfGWA/PQEWggFgQFI9glI9YABgIIQBPmED0FZbYGCRUFtQkVCRUIFhBCxXYBVgAIBn//////////+BERVhA/hXYQP3YQcjVltbYEBRkICCUoBgIAJgIAGCAWBAUoAVYQQmV4FgIAFgIIICgDaDN4CCAZFQUJBQW1BhBEFWW4CAYCABkFGBAZBhBECRkGEOslZbW4JgAwuSUICVUIGWUIKXUFBQUFBQk1CTUJOQUFZbYACAYABhAWdz//////////////////////////8WYwmXlOhg4BuGhmBAUWAkAWEEl5KRkGEPIVZbYEBRYCCBgwMDgVKQYEBSkHv/////////////////////////////////////GRZgIIIBgFF7/////////////////////////////////////4OBgxYXg1JQUFBQYEBRYQUBkZBhDARWW2AAYEBRgIMDgWAAhlrxkVBQPYBgAIEUYQU+V2BAUZFQYB8ZYD89ARaCAWBAUj2CUj1gAGAghAE+YQVDVltgYJFQW1CRUJFQgWEFVFdgFWEFaVZbgIBgIAGQUYEBkGEFaJGQYQ9KVltbYAMLklBQUJKRUFBWW2AAgGAAYQFnc///////////////////////////FmNJFGveYOAbhoZgQFFgJAFhBa+SkZBhDyFWW2BAUWAggYMDA4FSkGBAUpB7/////////////////////////////////////xkWYCCCAYBRe/////////////////////////////////////+DgYMWF4NSUFBQUGBAUWEGGZGQYQwEVltgAGBAUYCDA4FgAIZa8ZFQUD2AYACBFGEGVldgQFGRUGAfGWA/PQEWggFgQFI9glI9YABgIIQBPmEGW1ZbYGCRUFtQkVCRUIFhBmxXYBVhBoFWW4CAYCABkFGBAZBhBoCRkGEPSlZbW2ADC5JQUFCSkVBQVlthCc+AYQ94gzkBkFZbYABgQFGQUJBWW2AAgP1bYACA/VtgAHP//////////////////////////4IWkFCRkFBWW2AAYQbagmEGr1ZbkFCRkFBWW2EG6oFhBs9WW4EUYQb1V2AAgP1bUFZbYACBNZBQYQcHgWEG4VZbkpFQUFZbYACA/VtgAGAfGWAfgwEWkFCRkFBWW39OSHtxAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGAAUmBBYARSYCRgAP1bYQdbgmEHElZbgQGBgRBn//////////+CERcVYQd6V2EHeWEHI1ZbW4BgQFJQUFBWW2AAYQeNYQabVluQUGEHmYKCYQdSVluRkFBWW2AAZ///////////ghEVYQe5V2EHuGEHI1ZbW2AgggKQUGAggQGQUJGQUFZbYACA/VtgAID9W2AAZ///////////ghEVYQfvV2EH7mEHI1ZbW2EH+IJhBxJWW5BQYCCBAZBQkZBQVluCgYM3YACDgwFSUFBQVltgAGEIJ2EIIoRhB9RWW2EHg1ZbkFCCgVJgIIEBhISEAREVYQhDV2EIQmEHz1ZbW2EIToSChWEIBVZbUJOSUFBQVltgAIJgH4MBEmEIa1dhCGphBw1WW1uBNWEIe4SCYCCGAWEIFFZbkVBQkpFQUFZbYABhCJdhCJKEYQeeVlthB4NWW5BQgIOCUmAgggGQUGAghAKDAYWBERVhCLpXYQi5YQfKVltbg1uBgRAVYQkBV4A1Z///////////gREVYQjfV2EI3mEHDVZbW4CGAWEI7ImCYQhWVluFUmAghQGUUFBQYCCBAZBQYQi8VltQUFCTklBQUFZbYACCYB+DARJhCSBXYQkfYQcNVltbgTVhCTCEgmAghgFhCIRWW5FQUJKRUFBWW2AAgGBAg4UDEhVhCVBXYQlPYQalVltbYABhCV6FgoYBYQb4VluSUFBgIIMBNWf//////////4ERFWEJf1dhCX5hBqpWW1thCYuFgoYBYQkLVluRUFCSUJKQUFZbYABgIIKEAxIVYQmrV2EJqmEGpVZbW2AAYQm5hIKFAWEG+FZbkVBQkpFQUFZbYACCglJgIIIBkFCSkVBQVlt/Q2FuJ3QhAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABgAIIBUlBWW2AAYQoJYAaDYQnCVluRUGEKFIJhCdNWW2AgggGQUJGQUFZbYABgIIIBkFCBgQNgAIMBUmEKOIFhCfxWW5BQkZBQVlthCkiBYQbPVluCUlBQVltgAIFRkFCRkFBWW2AAgoJSYCCCAZBQkpFQUFZbYACBkFBgIIIBkFCRkFBWW2AAgVGQUJGQUFZbYACCglJgIIIBkFCSkVBQVltgAFuDgRAVYQq0V4CCAVGBhAFSYCCBAZBQYQqZVluDgREVYQrDV2AAhIQBUltQUFBQVltgAGEK1IJhCnpWW2EK3oGFYQqFVluTUGEK7oGFYCCGAWEKllZbYQr3gWEHElZbhAGRUFCSkVBQVltgAGELDoODYQrJVluQUJKRUFBWW2AAYCCCAZBQkZBQVltgAGELLoJhCk5WW2ELOIGFYQpZVluTUINgIIIChQFhC0qFYQpqVluAYABbhYEQFWELhleEhAOJUoFRYQtnhYJhCwJWW5RQYQtyg2ELFlZbklBgIIoBmVBQYAGBAZBQYQtOVltQgpdQh5VQUFBQUFCSkVBQVltgAGBAggGQUGELrWAAgwGFYQo/VluBgQNgIIMBUmELv4GEYQsjVluQUJOSUFBQVltgAIGQUJKRUFBWW2AAYQvegmEKelZbYQvogYVhC8hWW5NQYQv4gYVgIIYBYQqWVluAhAGRUFCSkVBQVltgAGEMEIKEYQvTVluRUIGQUJKRUFBWW39TbyB1bmZhaXIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGAAggFSUFZbYABhDFFgCYNhCcJWW5FQYQxcgmEMG1ZbYCCCAZBQkZBQVltgAGAgggGQUIGBA2AAgwFSYQyAgWEMRFZbkFCRkFBWW39JdCdzIHVuaGVhcmQgb2YuLi4AAAAAAAAAAAAAAAAAAGAAggFSUFZbYABhDL1gEoNhCcJWW5FQYQzIgmEMh1ZbYCCCAZBQkZBQVltgAGAgggGQUIGBA2AAgwFSYQzsgWEMsFZbkFCRkFBWW2AAZ///////////ghaQUJGQUFZbYQ0QgWEM81ZbglJQUFZbYABgYIIBkFBhDStgAIMBhmEKP1ZbYQ04YCCDAYVhDQdWW4GBA2BAgwFSYQ1KgYRhCyNWW5BQlJNQUFBQVltgAIFgAwuQUJGQUFZbYQ1qgWENVFZbgRRhDXVXYACA/VtQVltgAIFRkFBhDYeBYQ1hVluSkVBQVlthDZaBYQzzVluBFGENoVdgAID9W1BWW2AAgVGQUGENs4FhDY1WW5KRUFBWW2AAZ///////////ghEVYQ3UV2EN02EHI1ZbW2AgggKQUGAggQGQUJGQUFZbYACBkFCRkFBWW2EN+IFhDeVWW4EUYQ4DV2AAgP1bUFZbYACBUZBQYQ4VgWEN71ZbkpFQUFZbYABhDi5hDimEYQ25VlthB4NWW5BQgIOCUmAgggGQUGAghAKDAYWBERVhDlFXYQ5QYQfKVltbg1uBgRAVYQ56V4BhDmaIgmEOBlZbhFJgIIQBk1BQYCCBAZBQYQ5TVltQUFCTklBQUFZbYACCYB+DARJhDplXYQ6YYQcNVltbgVFhDqmEgmAghgFhDhtWW5FQUJKRUFBWW2AAgGAAYGCEhgMSFWEOy1dhDsphBqVWW1tgAGEO2YaChwFhDXhWW5NQUGAgYQ7qhoKHAWENpFZbklBQYECEAVFn//////////+BERVhDwtXYQ8KYQaqVltbYQ8XhoKHAWEOhFZbkVBQklCSUJJWW2AAYECCAZBQYQ82YACDAYVhCj9WW2EPQ2AggwGEYQo/VluTklBQUFZbYABgIIKEAxIVYQ9gV2EPX2EGpVZbW2AAYQ9uhIKFAWENeFZbkVBQkpFQUFb+YIBgQFI0gBVhABBXYACA/VtQYQmvgGEAIGAAOWAA8/5ggGBAUjSAFWEAEFdgAID9W1BgBDYQYQArV2AANWDgHIBjCihMthRhADBXW2AAgP1bYQBKYASANgOBAZBhAEWRkGEExlZbYQBMVlsAW2AAgGAAYQBdhWAAhmEAsFZbklCSUJJQYBZgAwuDFGEAqVdgQFF/CMN5oAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACBUmAEAWEAoJBhBX9WW2BAUYCRA5D9W1BQUFBQVltgAIBgYGAAgGEBZ3P//////////////////////////xZjJ44LiGDgG4mJiWBAUWAkAWEA7pOSkZBhBxtWW2BAUWAggYMDA4FSkGBAUpB7/////////////////////////////////////xkWYCCCAYBRe/////////////////////////////////////+DgYMWF4NSUFBQUGBAUWEBWJGQYQeVVltgAGBAUYCDA4FgAIZa8ZFQUD2AYACBFGEBlVdgQFGRUGAfGWA/PQEWggFgQFI9glI9YABgIIQBPmEBmlZbYGCRUFtQkVCRUIFhAfZXYBVgAIBn//////////+BERVhAcJXYQHBYQKwVltbYEBRkICCUoBgIAJgIAGCAWBAUoAVYQHwV4FgIAFgIIICgDaDN4CCAZFQUJBQW1BhAgtWW4CAYCABkFGBAZBhAgqRkGEJClZbW4JgAwuSUICVUIGWUIKXUFBQUFBQk1CTUJOQUFZbYABgQFGQUJBWW2AAgP1bYACA/VtgAHP//////////////////////////4IWkFCRkFBWW2AAYQJngmECPFZbkFCRkFBWW2ECd4FhAlxWW4EUYQKCV2AAgP1bUFZbYACBNZBQYQKUgWECblZbkpFQUFZbYACA/VtgAGAfGWAfgwEWkFCRkFBWW39OSHtxAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGAAUmBBYARSYCRgAP1bYQLogmECn1ZbgQGBgRBn//////////+CERcVYQMHV2EDBmECsFZbW4BgQFJQUFBWW2AAYQMaYQIoVluQUGEDJoKCYQLfVluRkFBWW2AAZ///////////ghEVYQNGV2EDRWECsFZbW2AgggKQUGAggQGQUJGQUFZbYACA/VtgAID9W2AAZ///////////ghEVYQN8V2EDe2ECsFZbW2EDhYJhAp9WW5BQYCCBAZBQkZBQVluCgYM3YACDgwFSUFBQVltgAGEDtGEDr4RhA2FWW2EDEFZbkFCCgVJgIIEBhISEAREVYQPQV2EDz2EDXFZbW2ED24SChWEDklZbUJOSUFBQVltgAIJgH4MBEmED+FdhA/dhAppWW1uBNWEECISCYCCGAWEDoVZbkVBQkpFQUFZbYABhBCRhBB+EYQMrVlthAxBWW5BQgIOCUmAgggGQUGAghAKDAYWBERVhBEdXYQRGYQNXVltbg1uBgRAVYQSOV4A1Z///////////gREVYQRsV2EEa2ECmlZbW4CGAWEEeYmCYQPjVluFUmAghQGUUFBQYCCBAZBQYQRJVltQUFCTklBQUFZbYACCYB+DARJhBK1XYQSsYQKaVltbgTVhBL2EgmAghgFhBBFWW5FQUJKRUFBWW2AAgGBAg4UDEhVhBN1XYQTcYQIyVltbYABhBOuFgoYBYQKFVluSUFBgIIMBNWf//////////4ERFWEFDFdhBQthAjdWW1thBRiFgoYBYQSYVluRUFCSUJKQUFZbYACCglJgIIIBkFCSkVBQVlt/Q2FuJ3QgZXZlbiEAAAAAAAAAAAAAAAAAAAAAAAAAAABgAIIBUlBWW2AAYQVpYAuDYQUiVluRUGEFdIJhBTNWW2AgggGQUJGQUFZbYABgIIIBkFCBgQNgAIMBUmEFmIFhBVxWW5BQkZBQVlthBaiBYQJcVluCUlBQVltgAGf//////////4IWkFCRkFBWW2EFy4FhBa5WW4JSUFBWW2AAgVGQUJGQUFZbYACCglJgIIIBkFCSkVBQVltgAIGQUGAgggGQUJGQUFZbYACBUZBQkZBQVltgAIKCUmAgggGQUJKRUFBWW2AAW4OBEBVhBjdXgIIBUYGEAVJgIIEBkFBhBhxWW4OBERVhBkZXYACEhAFSW1BQUFBWW2AAYQZXgmEF/VZbYQZhgYVhBghWW5NQYQZxgYVgIIYBYQYZVlthBnqBYQKfVluEAZFQUJKRUFBWW2AAYQaRg4NhBkxWW5BQkpFQUFZbYABgIIIBkFCRkFBWW2AAYQaxgmEF0VZbYQa7gYVhBdxWW5NQg2AgggKFAWEGzYVhBe1WW4BgAFuFgRAVYQcJV4SEA4lSgVFhBuqFgmEGhVZblFBhBvWDYQaZVluSUGAgigGZUFBgAYEBkFBhBtFWW1CCl1CHlVBQUFBQUJKRUFBWW2AAYGCCAZBQYQcwYACDAYZhBZ9WW2EHPWAggwGFYQXCVluBgQNgQIMBUmEHT4GEYQamVluQUJSTUFBQUFZbYACBkFCSkVBQVltgAGEHb4JhBf1WW2EHeYGFYQdZVluTUGEHiYGFYCCGAWEGGVZbgIQBkVBQkpFQUFZbYABhB6GChGEHZFZbkVCBkFCSkVBQVltgAIFgAwuQUJGQUFZbYQfCgWEHrFZbgRRhB81XYACA/VtQVltgAIFRkFBhB9+BYQe5VluSkVBQVlthB+6BYQWuVluBFGEH+VdgAID9W1BWW2AAgVGQUGEIC4FhB+VWW5KRUFBWW2AAZ///////////ghEVYQgsV2EIK2ECsFZbW2AgggKQUGAggQGQUJGQUFZbYACBkFCRkFBWW2EIUIFhCD1WW4EUYQhbV2AAgP1bUFZbYACBUZBQYQhtgWEIR1ZbkpFQUFZbYABhCIZhCIGEYQgRVlthAxBWW5BQgIOCUmAgggGQUGAghAKDAYWBERVhCKlXYQioYQNXVltbg1uBgRAVYQjSV4BhCL6IgmEIXlZbhFJgIIQBk1BQYCCBAZBQYQirVltQUFCTklBQUFZbYACCYB+DARJhCPFXYQjwYQKaVltbgVFhCQGEgmAghgFhCHNWW5FQUJKRUFBWW2AAgGAAYGCEhgMSFWEJI1dhCSJhAjJWW1tgAGEJMYaChwFhB9BWW5NQUGAgYQlChoKHAWEH/FZbklBQYECEAVFn//////////+BERVhCWNXYQliYQI3VltbYQlvhoKHAWEI3FZbkVBQklCSUJJW/qJkaXBmc1giEiCj3/kC2IbP9FPmYdKAOCCuJqtPRaSPoxlQvQMfCNPrrGRzb2xjQwAIDAAzomRpcGZzWCISIFUEWRgjAkhUQgZY1JRYtK7YTvKvriiYngMVaH9ZkUs/ZHNvbGNDAAgMADOiZGlwZnNYIhIgmQRA6jQvnIMRqpOW2WS8I9y1YmYl9QwPLW8+eAhDPOZkc29sY0MACAwAMyKAAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAo7u0MOgMYxQlKFgoUAAAAAAAAAAAAAAAAAAAAAAAABMVyBwoDGMUJEAFSFgoJCgIYAhCD8KEOCgkKAhhiEITwoQ4="},{"b64Body":"ChAKCQiO//6sBhDUFxIDGMMJEgIYAxiA/rWHASICCHgyIMODwq7DgsK3w4PCuXRGOMOCwq5Kw4PCi8ODwpDDg8KOOjAKAxjFCRCAkvQBIiSr97/YqrvM3e7/ABGqu8zd7v8AEaq7zN3u/wARqrvM3e7/ABE=","b64Record":"CiUIFiIDGMUJKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjAmy27S2YyE45V6BDIPmepxl/4IiA3gaibHMnpzqeNFrR3Mdt/RkLeUhR2k3MfDQ6QaDAjK//6sBhDG8cy4AiIQCgkIjv/+rAYQ1BcSAxjDCSogw4PCrsOCwrfDg8K5dEY4w4LCrkrDg8KLw4PCkMODwo4wgJirbDqkAgoDGMUJIoACAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACiAqMMBOgMYxglyBwoDGMUJEAJyBwoDGMYJEAFSGQoKCgIYYhCAsNbYAQoLCgMYwwkQ/6/W2AE="},{"b64Body":"ChIKCQiO//6sBhDUFxIDGMMJIAFCOBoiEiABSLhQDdysM7gHRRiQwoahpJVNxTLMBhtzzp//wO01W0IFCIDO2gNqC2NlbGxhciBkb29y","b64Record":"CgcIFiIDGMYJEjAHCYEpIfWDER6dLecNNS5UbdKmOXmOBCFJzI8lmFw53a4p3kJoX68QsKQ2bEU+rP0aDAjK//6sBhDH8cy4AiISCgkIjv/+rAYQ1BcSAxjDCSABQh0KAxjGCUoWChSbalfbIH86LRNoPcd+Kxk8fVyKR1IAegwIyv/+rAYQxvHMuAI="},{"b64Body":"ChAKCQiP//6sBhDWFxIDGMMJEgIYAxiA/rWHASICCHgyIMODwq7DgsK3w4PCuXRGOMOCwq5Kw4PCi8ODwpDDg8KOOjAKAxjFCRCAkvQBIiSr97/YqrvM3e7/ABGqu8zd7v8AEaq7zN3u/wARqrvM3e7/ABE=","b64Record":"CiUIISIDGMUJKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjDRoYovNRSToKO4f1hJ2+9n3VD7Pyprr6L+I7R5be5lO+aLEf8/YdeOJg0nNl/XI3YaCwjL//6sBhDXl7NdIhAKCQiP//6sBhDWFxIDGMMJKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjjCHyayFAToJGgIweCjBs/ABUhkKCgoCGGIQjpLZigIKCwoDGMMJEI2S2YoC"}]}}} \ No newline at end of file +{"specSnapshots":{"ConsTimeManagementWorksWithRevertedInternalCreations":{"placeholderNum":1016,"encodedItems":[{"b64Body":"Cg8KCQjE/P6sBhC/BxICGAISAhgDGP7LzSwiAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjooBjwESDAiAy9mwBhDUiuiEAhptCiISICOWph9j8lOo5q/tQLgLolDeKnYE3XmD2Oj4NftacZ1YCiM6IQOXBtQRyaNn/w5IYJgVJ5qTiRcRlsD7IS6ypZEpSPx2igoiEiA0beZ/xd1vyAOwZdnvE/TXWk/1h+aP2AkWvkX1y6IQHCIMSGVsbG8gV29ybGQhKgAyAA==","b64Record":"CiUIFhoDGPkHKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjAgXDbciouS1iV8HgoEUB7CLn0BS4Og+Cv9V4+NwYWez51tn8ApiK0XnZlGyjn6I3QaDAiA/f6sBhD+wumgAiIPCgkIxPz+rAYQvwcSAhgCKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjlIA"},{"b64Body":"Cg8KCQjF/P6sBhDDBxICGAISAhgDGI322zkiAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjpoBmBgKAxj5ByKQGDYwODA2MDQwNTIzNDgwMTU2MTAwMTA1NzYwMDA4MGZkNWI1MDYxMDVlODgwNjEwMDIwNjAwMDM5NjAwMGYzZmU2MDgwNjA0MDUyMzQ4MDE1NjEwMDEwNTc2MDAwODBmZDViNTA2MDA0MzYxMDYxMDAzNjU3NjAwMDM1NjBlMDFjODA2MzYyNTM4YTE2MTQ2MTAwM2I1NzgwNjM3YzQxYWQyYzE0NjEwMDU3NTc1YjYwMDA4MGZkNWI2MTAwNTU2MDA0ODAzNjAzODEwMTkwNjEwMDUwOTE5MDYxMDM3NDU2NWI2MTAwODc1NjViMDA1YjYxMDA3MTYwMDQ4MDM2MDM4MTAxOTA2MTAwNmM5MTkwNjEwM2I0NTY1YjYxMDBkODU2NWI2MDQwNTE2MTAwN2U5MTkwNjEwM2ZhNTY1YjYwNDA1MTgwOTEwMzkwZjM1YjYwMDA2MDQwNTE2MTAwOTU5MDYxMDMwNTU2NWI2MDQwNTE4MDkxMDM5MDYwMDBmMDgwMTU4MDE1NjEwMGIxNTczZDYwMDA4MDNlM2Q2MDAwZmQ1YjUwOTA1MDYwMDA2MTAwYzA4NDg0NjEwMWVkNTY1YjkwNTA2MDE2NjAwMzBiODExNDYxMDBkMjU3NjAwMDgwZmQ1YjUwNTA1MDUwNTY1YjYwMDA4MDYwMDA2MTAxNjc3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNjYzN2M0MWFkMmM2MGUwMWI4NTYwNDA1MTYwMjQwMTYxMDEwZjkxOTA2MTA0MjQ1NjViNjA0MDUxNjAyMDgxODMwMzAzODE1MjkwNjA0MDUyOTA3YmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmMTkxNjYwMjA4MjAxODA1MTdiZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmY4MzgxODMxNjE3ODM1MjUwNTA1MDUwNjA0MDUxNjEwMTc5OTE5MDYxMDRiMDU2NWI2MDAwNjA0MDUxODA4MzAzODE2MDAwODY1YWYxOTE1MDUwM2Q4MDYwMDA4MTE0NjEwMWI2NTc2MDQwNTE5MTUwNjAxZjE5NjAzZjNkMDExNjgyMDE2MDQwNTIzZDgyNTIzZDYwMDA2MDIwODQwMTNlNjEwMWJiNTY1YjYwNjA5MTUwNWI1MDkxNTA5MTUwODE2MTAxY2M1NzYwMTU2MTAxZTE1NjViODA4MDYwMjAwMTkwNTE4MTAxOTA2MTAxZTA5MTkwNjEwNTAwNTY1YjViNjAwMzBiOTI1MDUwNTA5MTkwNTA1NjViNjAwMDgwNjAwMDYxMDE2NzczZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjE2NjM0OTE0NmJkZTYwZTAxYjg2ODY2MDQwNTE2MDI0MDE2MTAyMjY5MjkxOTA2MTA1MmQ1NjViNjA0MDUxNjAyMDgxODMwMzAzODE1MjkwNjA0MDUyOTA3YmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmMTkxNjYwMjA4MjAxODA1MTdiZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmY4MzgxODMxNjE3ODM1MjUwNTA1MDUwNjA0MDUxNjEwMjkwOTE5MDYxMDRiMDU2NWI2MDAwNjA0MDUxODA4MzAzODE2MDAwODY1YWYxOTE1MDUwM2Q4MDYwMDA4MTE0NjEwMmNkNTc2MDQwNTE5MTUwNjAxZjE5NjAzZjNkMDExNjgyMDE2MDQwNTIzZDgyNTIzZDYwMDA2MDIwODQwMTNlNjEwMmQyNTY1YjYwNjA5MTUwNWI1MDkxNTA5MTUwODE2MTAyZTM1NzYwMTU2MTAyZjg1NjViODA4MDYwMjAwMTkwNTE4MTAxOTA2MTAyZjc5MTkwNjEwNTAwNTY1YjViNjAwMzBiOTI1MDUwNTA5MjkxNTA1MDU2NWI2MDVjODA2MTA1NTc4MzM5MDE5MDU2NWI2MDAwODBmZDViNjAwMDczZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjgyMTY5MDUwOTE5MDUwNTY1YjYwMDA2MTAzNDE4MjYxMDMxNjU2NWI5MDUwOTE5MDUwNTY1YjYxMDM1MTgxNjEwMzM2NTY1YjgxMTQ2MTAzNWM1NzYwMDA4MGZkNWI1MDU2NWI2MDAwODEzNTkwNTA2MTAzNmU4MTYxMDM0ODU2NWI5MjkxNTA1MDU2NWI2MDAwODA2MDQwODM4NTAzMTIxNTYxMDM4YjU3NjEwMzhhNjEwMzExNTY1YjViNjAwMDYxMDM5OTg1ODI4NjAxNjEwMzVmNTY1YjkyNTA1MDYwMjA2MTAzYWE4NTgyODYwMTYxMDM1ZjU2NWI5MTUwNTA5MjUwOTI5MDUwNTY1YjYwMDA2MDIwODI4NDAzMTIxNTYxMDNjYTU3NjEwM2M5NjEwMzExNTY1YjViNjAwMDYxMDNkODg0ODI4NTAxNjEwMzVmNTY1YjkxNTA1MDkyOTE1MDUwNTY1YjYwMDA4MTkwNTA5MTkwNTA1NjViNjEwM2Y0ODE2MTAzZTE1NjViODI1MjUwNTA1NjViNjAwMDYwMjA4MjAxOTA1MDYxMDQwZjYwMDA4MzAxODQ2MTAzZWI1NjViOTI5MTUwNTA1NjViNjEwNDFlODE2MTAzMzY1NjViODI1MjUwNTA1NjViNjAwMDYwMjA4MjAxOTA1MDYxMDQzOTYwMDA4MzAxODQ2MTA0MTU1NjViOTI5MTUwNTA1NjViNjAwMDgxNTE5MDUwOTE5MDUwNTY1YjYwMDA4MTkwNTA5MjkxNTA1MDU2NWI2MDAwNWI4MzgxMTAxNTYxMDQ3MzU3ODA4MjAxNTE4MTg0MDE1MjYwMjA4MTAxOTA1MDYxMDQ1ODU2NWI2MDAwODQ4NDAxNTI1MDUwNTA1MDU2NWI2MDAwNjEwNDhhODI2MTA0M2Y1NjViNjEwNDk0ODE4NTYxMDQ0YTU2NWI5MzUwNjEwNGE0ODE4NTYwMjA4NjAxNjEwNDU1NTY1YjgwODQwMTkxNTA1MDkyOTE1MDUwNTY1YjYwMDA2MTA0YmM4Mjg0NjEwNDdmNTY1YjkxNTA4MTkwNTA5MjkxNTA1MDU2NWI2MDAwODE2MDAzMGI5MDUwOTE5MDUwNTY1YjYxMDRkZDgxNjEwNGM3NTY1YjgxMTQ2MTA0ZTg1NzYwMDA4MGZkNWI1MDU2NWI2MDAwODE1MTkwNTA2MTA0ZmE4MTYxMDRkNDU2NWI5MjkxNTA1MDU2NWI2MDAwNjAyMDgyODQwMzEyMTU2MTA1MTY1NzYxMDUxNTYxMDMxMTU2NWI1YjYwMDA2MTA1MjQ4NDgyODUwMTYxMDRlYjU2NWI5MTUwNTA5MjkxNTA1MDU2NWI2MDAwNjA0MDgyMDE5MDUwNjEwNTQyNjAwMDgzMDE4NTYxMDQxNTU2NWI2MTA1NGY2MDIwODMwMTg0NjEwNDE1NTY1YjkzOTI1MDUwNTA1NmZlNjA4MDYwNDA1MjM0ODAxNTYwMGY1NzYwMDA4MGZkNWI1MDYwM2Y4MDYwMWQ2MDAwMzk2MDAwZjNmZTYwODA2MDQwNTI2MDAwODBmZGZlYTI2NDY5NzA2NjczNTgyMjEyMjAzZWM4ZGUwZGJhZjQ0ZDJkYmYyNGZkM2E1YTI1OGRlM2NkNmJmMzJiMjE2OGU2ODhhNDRlYmE3ZTc5YWI4YzU5NjQ3MzZmNmM2MzQzMDAwODEwMDAzM2EyNjQ2OTcwNjY3MzU4MjIxMjIwZTMyY2EyNzUxM2FhODgwY2IwMWZmODc0MjEwNGZlMTZlZTY4ZDYyNjE1MDYzNjE4YmI3NGE0MWRmN2FlMTE1NjY0NzM2ZjZjNjM0MzAwMDgxMDAwMzM=","b64Record":"CiAIFiocCgwIARAMGgYIgK6ZpA8SDAgBEA8aBgiArpmkDxIwRSwOJFwZ5Y/TCdYXuaAG7bn8fCMPpfFegVklS2DxKVKAw+YyXLhsp566iJH/lZkUGgsIgf3+rAYQorP6YSIPCgkIxfz+rAYQwwcSAhgCKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjlIA"},{"b64Body":"Cg8KCQjF/P6sBhDFBxICGAISAhgDGJb7rp0CIgIIeDIgw4PCrsOCwrfDg8K5dEY4w4LCrkrDg8KLw4PCkMODwo5CRQoDGPkHGiISIDMDdFNZ0zLVX0qp427Aw84Au2oh8Sj8ueF2H600vszmIJChD0IFCIDO2gNSAFoAagtjZWxsYXIgZG9vcg==","b64Record":"CiUIFiIDGPoHKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjDuU4WpTCQrK8BHFAWBboJxMnRx9+w857QdeO0mEXCp0lvsk3GhdgqCt3vh4QuvekoaDAiB/f6sBhCq8u7GAiIPCgkIxfz+rAYQxQcSAhgCKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjjDA2eIGQp0OCgMY+gcS6AtggGBAUjSAFWEAEFdgAID9W1BgBDYQYQA2V2AANWDgHIBjYlOKFhRhADtXgGN8Qa0sFGEAV1dbYACA/VthAFVgBIA2A4EBkGEAUJGQYQN0VlthAIdWWwBbYQBxYASANgOBAZBhAGyRkGEDtFZbYQDYVltgQFFhAH6RkGED+lZbYEBRgJEDkPNbYABgQFFhAJWQYQMFVltgQFGAkQOQYADwgBWAFWEAsVc9YACAPj1gAP1bUJBQYABhAMCEhGEB7VZbkFBgFmADC4EUYQDSV2AAgP1bUFBQUFZbYACAYABhAWdz//////////////////////////8WY3xBrSxg4BuFYEBRYCQBYQEPkZBhBCRWW2BAUWAggYMDA4FSkGBAUpB7/////////////////////////////////////xkWYCCCAYBRe/////////////////////////////////////+DgYMWF4NSUFBQUGBAUWEBeZGQYQSwVltgAGBAUYCDA4FgAIZa8ZFQUD2AYACBFGEBtldgQFGRUGAfGWA/PQEWggFgQFI9glI9YABgIIQBPmEBu1ZbYGCRUFtQkVCRUIFhAcxXYBVhAeFWW4CAYCABkFGBAZBhAeCRkGEFAFZbW2ADC5JQUFCRkFBWW2AAgGAAYQFnc///////////////////////////FmNJFGveYOAbhoZgQFFgJAFhAiaSkZBhBS1WW2BAUWAggYMDA4FSkGBAUpB7/////////////////////////////////////xkWYCCCAYBRe/////////////////////////////////////+DgYMWF4NSUFBQUGBAUWECkJGQYQSwVltgAGBAUYCDA4FgAIZa8ZFQUD2AYACBFGECzVdgQFGRUGAfGWA/PQEWggFgQFI9glI9YABgIIQBPmEC0lZbYGCRUFtQkVCRUIFhAuNXYBVhAvhWW4CAYCABkFGBAZBhAveRkGEFAFZbW2ADC5JQUFCSkVBQVltgXIBhBVeDOQGQVltgAID9W2AAc///////////////////////////ghaQUJGQUFZbYABhA0GCYQMWVluQUJGQUFZbYQNRgWEDNlZbgRRhA1xXYACA/VtQVltgAIE1kFBhA26BYQNIVluSkVBQVltgAIBgQIOFAxIVYQOLV2EDimEDEVZbW2AAYQOZhYKGAWEDX1ZbklBQYCBhA6qFgoYBYQNfVluRUFCSUJKQUFZbYABgIIKEAxIVYQPKV2EDyWEDEVZbW2AAYQPYhIKFAWEDX1ZbkVBQkpFQUFZbYACBkFCRkFBWW2ED9IFhA+FWW4JSUFBWW2AAYCCCAZBQYQQPYACDAYRhA+tWW5KRUFBWW2EEHoFhAzZWW4JSUFBWW2AAYCCCAZBQYQQ5YACDAYRhBBVWW5KRUFBWW2AAgVGQUJGQUFZbYACBkFCSkVBQVltgAFuDgRAVYQRzV4CCAVGBhAFSYCCBAZBQYQRYVltgAISEAVJQUFBQVltgAGEEioJhBD9WW2EElIGFYQRKVluTUGEEpIGFYCCGAWEEVVZbgIQBkVBQkpFQUFZbYABhBLyChGEEf1ZbkVCBkFCSkVBQVltgAIFgAwuQUJGQUFZbYQTdgWEEx1ZbgRRhBOhXYACA/VtQVltgAIFRkFBhBPqBYQTUVluSkVBQVltgAGAggoQDEhVhBRZXYQUVYQMRVltbYABhBSSEgoUBYQTrVluRUFCSkVBQVltgAGBAggGQUGEFQmAAgwGFYQQVVlthBU9gIIMBhGEEFVZbk5JQUFBW/mCAYEBSNIAVYA9XYACA/VtQYD+AYB1gADlgAPP+YIBgQFJgAID9/qJkaXBmc1giEiA+yN4NuvRNLb8k/TpaJY3jzWvzKyFo5oikTrp+eauMWWRzb2xjQwAIEAAzomRpcGZzWCISIOMsonUTqogMsB/4dCEE/hbuaNYmFQY2GLt0pB33rhFWZHNvbGNDAAgQADMigAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKMCaDDoDGPoHShYKFAAAAAAAAAAAAAAAAAAAAAAAAAP6cgcKAxj6BxABUhYKCQoCGAIQ/7LFDQoJCgIYYhCAs8UN"},{"b64Body":"Cg8KCQjG/P6sBhDHBxICGAISAhgDGOCssQMiAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjjpPCgMY+gcQoI0GIkRiU4oWAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==","b64Record":"CiUIISIDGPoHKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjAF0zxP2TtaojXAfZ9trOFg7xse34nBIcFUObWjPllVxDeMeKZjhX962no/HLVWg9UaCwiC/f6sBhCd1s5rIg8KCQjG/P6sBhDHBxICGAIqIMODwq7DgsK3w4PCuXRGOMOCwq5Kw4PCi8ODwpDDg8KOMN6arwM6CBoCMHgosokGUhYKCQoCGAIQu7XeBgoJCgIYYhC8td4G"},{"b64Body":"ChEKCQjG/P6sBhDHBxICGAIgAcICBgoCGAASAA==","b64Record":"CgIIHhIwJf/H4tAZMVk5hEmN8lkhigg/HZpYNSj/k1Cg2H2cOQFbhSAF1WRTb20MjHrpgXMlGgsIgv3+rAYQntbOayIRCgkIxvz+rAYQxwcSAhgCIAE6jAEKAxjnAhIgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB4aEElOU1VGRklDSUVOVF9HQVMo4v0qUJLEAmJESRRr3gAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABqAxj6B1IAegsIgv3+rAYQndbOaw=="}]},"PayableSuccess":{"placeholderNum":1019,"encodedItems":[{"b64Body":"Cg8KCQjL/P6sBhDjBxICGAISAhgDGP7LzSwiAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjooBjgESCwiHy9mwBhCyj4Q2Gm0KIhIgEDp5opPQRd+kyPoPKVSzEx7N0wlAFc9yvwRKgaWELVEKIzohAgsrh0IQ9+xF71qP+H3dlmvcGZnufsNojcnr70DQsO3dCiISIK2cErWvxy1g0s0aT0virBpJploduoKlpH2uUfhkeiC9IgxIZWxsbyBXb3JsZCEqADIA","b64Record":"CiUIFhoDGPwHKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjASEJig/eiAFIBHx/FGdpzaMOS6XBske+Y4BiZ74nx0PawuixTC/LfK2AHYdwChAL4aCwiH/f6sBhCl3qNFIg8KCQjL/P6sBhDjBxICGAIqIMODwq7DgsK3w4PCuXRGOMOCwq5Kw4PCi8ODwpDDg8KOUgA="},{"b64Body":"Cg8KCQjL/P6sBhDnBxICGAISAhgDGPa4sjEiAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjpoBogkKAxj8ByKaCTYwODA2MDQwNTI2MTAyM2E4MDYxMDAxMzYwMDAzOTYwMDBmM2ZlNjA4MDYwNDA1MjYwMDQzNjEwNjEwMDNmNTc2MDAwMzU2MGUwMWM4MDYzMTIwNjVmZTAxNDYxMDA4ZjU3ODA2MzNjY2ZkNjBiMTQ2MTAwYmE1NzgwNjM2ZjY0MjM0ZTE0NjEwMGQxNTc4MDYzYjZiNTVmMjUxNDYxMDEyYzU3NWIzMzczZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjE2N2ZmMWIwM2Y3MDhiOWMzOWY0NTNmZTNmMGNlZjg0MTY0YzdkNmY3ZGY4MzZkZjA3OTZlMWU5YzJiY2U2ZWUzOTdlMzQ2MDQwNTE4MDgyODE1MjYwMjAwMTkxNTA1MDYwNDA1MTgwOTEwMzkwYTIwMDViMzQ4MDE1NjEwMDliNTc2MDAwODBmZDViNTA2MTAwYTQ2MTAxNWE1NjViNjA0MDUxODA4MjgxNTI2MDIwMDE5MTUwNTA2MDQwNTE4MDkxMDM5MGYzNWIzNDgwMTU2MTAwYzY1NzYwMDA4MGZkNWI1MDYxMDBjZjYxMDE2MjU2NWIwMDViMzQ4MDE1NjEwMGRkNTc2MDAwODBmZDViNTA2MTAxMmE2MDA0ODAzNjAzNjA0MDgxMTAxNTYxMDBmNDU3NjAwMDgwZmQ1YjgxMDE5MDgwODAzNTczZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjE2OTA2MDIwMDE5MDkyOTE5MDgwMzU5MDYwMjAwMTkwOTI5MTkwNTA1MDUwNjEwMWFiNTY1YjAwNWI2MTAxNTg2MDA0ODAzNjAzNjAyMDgxMTAxNTYxMDE0MjU3NjAwMDgwZmQ1YjgxMDE5MDgwODAzNTkwNjAyMDAxOTA5MjkxOTA1MDUwNTA2MTAxZjY1NjViMDA1YjYwMDA0NzkwNTA5MDU2NWIzMzczZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjE2NjEwOGZjNDc5MDgxMTUwMjkwNjA0MDUxNjAwMDYwNDA1MTgwODMwMzgxODU4ODg4ZjE5MzUwNTA1MDUwMTU4MDE1NjEwMWE4NTczZDYwMDA4MDNlM2Q2MDAwZmQ1YjUwNTY1YjgxNzNmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmMTY2MTA4ZmM4MjkwODExNTAyOTA2MDQwNTE2MDAwNjA0MDUxODA4MzAzODE4NTg4ODhmMTkzNTA1MDUwNTAxNTgwMTU2MTAxZjE1NzNkNjAwMDgwM2UzZDYwMDBmZDViNTA1MDUwNTY1YjgwMzQxNDYxMDIwMjU3NjAwMDgwZmQ1YjUwNTZmZWEyNjU2MjdhN2E3MjMxNTgyMGY4Zjg0ZmMzMWE4NDUwNjRiNTc4MWU5MDgzMTZmM2M1OTExNTc5NjJkZWFiYjBmZDQyNGVkNTRmMjU2NDAwZjk2NDczNmY2YzYzNDMwMDA1MTEwMDMy","b64Record":"CiAIFiocCgwIARAMGgYIgK6ZpA8SDAgBEA8aBgiArpmkDxIw1LrpsBPieMQHpMH0VgiDABJXI/5yLA2KdrW+yHbtJt8e8F7us0HhafBE/0sIxW+BGgwIh/3+rAYQ8pz4xgIiDwoJCMv8/qwGEOcHEgIYAiogw4PCrsOCwrfDg8K5dEY4w4LCrkrDg8KLw4PCkMODwo5SAA=="},{"b64Body":"Cg8KCQjM/P6sBhDpBxICGAISAhgDGK/Dto0EIgIIeDIgw4PCrsOCwrfDg8K5dEY4w4LCrkrDg8KLw4PCkMODwo5ClgEKAxj8BxpzKnEIAhJtCiISIIUDdW/SuLoui8DAGLBRL6AMOZWtMbTdL93RWCqddbbcCiM6IQNq1ziP0CNzoxTWvpYSSoYH7reJn6WYK2y7zrkVnPd+zwoiEiCod48C6osZIM/h9xo+gmd9lWxnEprn8pD8NMBbgIBKEiDAhD1CBQiAztoDUgBaAGoLY2VsbGFyIGRvb3I=","b64Record":"CiUIFiIDGP0HKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjADYn2oi9H62zoxaUNaaZlec8axN1r9vbg2oevzle9tw0iQ5dHZ50P61HGVkKuGhbUaCwiI/f6sBhDSn+1OIg8KCQjM/P6sBhDpBxICGAIqIMODwq7DgsK3w4PCuXRGOMOCwq5Kw4PCi8ODwpDDg8KOMIDmihtC7wYKAxj9BxK6BGCAYEBSYAQ2EGEAP1dgADVg4ByAYxIGX+AUYQCPV4BjPM/WCxRhALpXgGNvZCNOFGEA0VeAY7a1XyUUYQEsV1szc///////////////////////////Fn/xsD9wi5w59FP+PwzvhBZMfW99+DbfB5bh6cK85u45fjRgQFGAgoFSYCABkVBQYEBRgJEDkKIAWzSAFWEAm1dgAID9W1BhAKRhAVpWW2BAUYCCgVJgIAGRUFBgQFGAkQOQ81s0gBVhAMZXYACA/VtQYQDPYQFiVlsAWzSAFWEA3VdgAID9W1BhASpgBIA2A2BAgRAVYQD0V2AAgP1bgQGQgIA1c///////////////////////////FpBgIAGQkpGQgDWQYCABkJKRkFBQUGEBq1ZbAFthAVhgBIA2A2AggRAVYQFCV2AAgP1bgQGQgIA1kGAgAZCSkZBQUFBhAfZWWwBbYABHkFCQVlszc///////////////////////////FmEI/EeQgRUCkGBAUWAAYEBRgIMDgYWIiPGTUFBQUBWAFWEBqFc9YACAPj1gAP1bUFZbgXP//////////////////////////xZhCPyCkIEVApBgQFFgAGBAUYCDA4GFiIjxk1BQUFAVgBVhAfFXPWAAgD49YAD9W1BQUFZbgDQUYQICV2AAgP1bUFb+omVienpyMVgg+PhPwxqEUGS1eB6QgxbzxZEVeWLeq7D9Qk7VTyVkAPlkc29sY0MABREAMiKAAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAogOowOgMY/QdKFgoUAAAAAAAAAAAAAAAAAAAAAAAAA/1yBwoDGP0HEAFSFgoJCgIYAhD/y5U2CgkKAhhiEIDMlTY="},{"b64Body":"Cg8KCQjM/P6sBhDrBxICGAISAhgDGOCssQMiAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjjoMCgMY/QcQoI0GGOgH","b64Record":"CiUIFiIDGP0HKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjCBj+DMrsPPyVn+6ZpI769E2YQ1pXHifIv/lgsp9sR5Ml+TUzTsS7SmpwsQlro3HPsaDAiI/f6sBhDNjczQAiIPCgkIzPz+rAYQ6wcSAhgCKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjjCA19oCOv0ECgMY/QcigAIEAAAAAAAAAACAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEAAAQAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIAAAAAAAAAAAAAAKIDxBDLuAgoDGP0HEoACBAAAAAAAAAAAgAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAAEAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAAAAAAAAAAAAAABog8bA/cIucOfRT/j8M74QWTH1vffg23weW4enCvObuOX4aIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACIiAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD6FIgCgkKAhgCEM+9tQUKCQoCGGIQgK61BQoICgMY/QcQ0A8="}]},"DepositSuccess":{"placeholderNum":1022,"encodedItems":[{"b64Body":"Cg8KCQjR/P6sBhD/BxICGAISAhgDGP7LzSwiAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjooBjwESDAiNy9mwBhDI4vjEARptCiISIAYAP6ns5XNJSv1ivZiXOcacuL0lq7DNzNZMim4KXLwRCiM6IQL+NW0cqcsHsxxYEjh7UNnrwX+OMv0K5jrtHmIG1X59UwoiEiC+ogkpFCk7SEgmyzlBxUyo9d4dE8myhaWpCPUzxBAG4iIMSGVsbG8gV29ybGQhKgAyAA==","b64Record":"CiUIFhoDGP8HKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjCoqhAc4Rsv1GNlHs66SsBn72SANG9KbyvcUTRMG0TWeO/7k1bEIsF+zDYu3BqxPZgaDAiN/f6sBhCiwLzUASIPCgkI0fz+rAYQ/wcSAhgCKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjlIA"},{"b64Body":"Cg8KCQjR/P6sBhCDCBICGAISAhgDGPa4sjEiAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjpoBogkKAxj/ByKaCTYwODA2MDQwNTI2MTAyM2E4MDYxMDAxMzYwMDAzOTYwMDBmM2ZlNjA4MDYwNDA1MjYwMDQzNjEwNjEwMDNmNTc2MDAwMzU2MGUwMWM4MDYzMTIwNjVmZTAxNDYxMDA4ZjU3ODA2MzNjY2ZkNjBiMTQ2MTAwYmE1NzgwNjM2ZjY0MjM0ZTE0NjEwMGQxNTc4MDYzYjZiNTVmMjUxNDYxMDEyYzU3NWIzMzczZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjE2N2ZmMWIwM2Y3MDhiOWMzOWY0NTNmZTNmMGNlZjg0MTY0YzdkNmY3ZGY4MzZkZjA3OTZlMWU5YzJiY2U2ZWUzOTdlMzQ2MDQwNTE4MDgyODE1MjYwMjAwMTkxNTA1MDYwNDA1MTgwOTEwMzkwYTIwMDViMzQ4MDE1NjEwMDliNTc2MDAwODBmZDViNTA2MTAwYTQ2MTAxNWE1NjViNjA0MDUxODA4MjgxNTI2MDIwMDE5MTUwNTA2MDQwNTE4MDkxMDM5MGYzNWIzNDgwMTU2MTAwYzY1NzYwMDA4MGZkNWI1MDYxMDBjZjYxMDE2MjU2NWIwMDViMzQ4MDE1NjEwMGRkNTc2MDAwODBmZDViNTA2MTAxMmE2MDA0ODAzNjAzNjA0MDgxMTAxNTYxMDBmNDU3NjAwMDgwZmQ1YjgxMDE5MDgwODAzNTczZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjE2OTA2MDIwMDE5MDkyOTE5MDgwMzU5MDYwMjAwMTkwOTI5MTkwNTA1MDUwNjEwMWFiNTY1YjAwNWI2MTAxNTg2MDA0ODAzNjAzNjAyMDgxMTAxNTYxMDE0MjU3NjAwMDgwZmQ1YjgxMDE5MDgwODAzNTkwNjAyMDAxOTA5MjkxOTA1MDUwNTA2MTAxZjY1NjViMDA1YjYwMDA0NzkwNTA5MDU2NWIzMzczZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjE2NjEwOGZjNDc5MDgxMTUwMjkwNjA0MDUxNjAwMDYwNDA1MTgwODMwMzgxODU4ODg4ZjE5MzUwNTA1MDUwMTU4MDE1NjEwMWE4NTczZDYwMDA4MDNlM2Q2MDAwZmQ1YjUwNTY1YjgxNzNmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmMTY2MTA4ZmM4MjkwODExNTAyOTA2MDQwNTE2MDAwNjA0MDUxODA4MzAzODE4NTg4ODhmMTkzNTA1MDUwNTAxNTgwMTU2MTAxZjE1NzNkNjAwMDgwM2UzZDYwMDBmZDViNTA1MDUwNTY1YjgwMzQxNDYxMDIwMjU3NjAwMDgwZmQ1YjUwNTZmZWEyNjU2MjdhN2E3MjMxNTgyMGY4Zjg0ZmMzMWE4NDUwNjRiNTc4MWU5MDgzMTZmM2M1OTExNTc5NjJkZWFiYjBmZDQyNGVkNTRmMjU2NDAwZjk2NDczNmY2YzYzNDMwMDA1MTEwMDMy","b64Record":"CiAIFiocCgwIARAMGgYIgK6ZpA8SDAgBEA8aBgiArpmkDxIw6OBKpIvlSofeU/I7SYO4aJUmqiEqAKaYVlG+14KEFbCBUf6AQCtXQvTTrvwg018/GgwIjf3+rAYQkZuE1gMiDwoJCNH8/qwGEIMIEgIYAiogw4PCrsOCwrfDg8K5dEY4w4LCrkrDg8KLw4PCkMODwo5SAA=="},{"b64Body":"Cg8KCQjS/P6sBhCFCBICGAISAhgDGK/Dto0EIgIIeDIgw4PCrsOCwrfDg8K5dEY4w4LCrkrDg8KLw4PCkMODwo5ClgEKAxj/BxpzKnEIAhJtCiISIBPzEbpn5/+irTzkz8wlR5ZAYS29KczyadpTFLTFc1CRCiM6IQPyAvlbUz5tpjDo/Th9cRPur9Kqcqy152zwZ+czoAP+SwoiEiBmz+k1btmzjL8Uon+Jn4+IUv5VisWN5WR8epeJQmGKliCQoQ9CBQiAztoDUgBaAGoLY2VsbGFyIGRvb3I=","b64Record":"CiUIFiIDGIAIKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjCdCNkMeHS54zftenI1NUBjz9hgfXCgBEf0u2ifdqeSY8H1mkHin2CSuQ7CJn43NiAaDAiO/f6sBhDK/4LeASIPCgkI0vz+rAYQhQgSAhgCKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjjDA2eIGQu8GCgMYgAgSugRggGBAUmAENhBhAD9XYAA1YOAcgGMSBl/gFGEAj1eAYzzP1gsUYQC6V4Bjb2QjThRhANFXgGO2tV8lFGEBLFdbM3P//////////////////////////xZ/8bA/cIucOfRT/j8M74QWTH1vffg23weW4enCvObuOX40YEBRgIKBUmAgAZFQUGBAUYCRA5CiAFs0gBVhAJtXYACA/VtQYQCkYQFaVltgQFGAgoFSYCABkVBQYEBRgJEDkPNbNIAVYQDGV2AAgP1bUGEAz2EBYlZbAFs0gBVhAN1XYACA/VtQYQEqYASANgNgQIEQFWEA9FdgAID9W4EBkICANXP//////////////////////////xaQYCABkJKRkIA1kGAgAZCSkZBQUFBhAatWWwBbYQFYYASANgNgIIEQFWEBQldgAID9W4EBkICANZBgIAGQkpGQUFBQYQH2VlsAW2AAR5BQkFZbM3P//////////////////////////xZhCPxHkIEVApBgQFFgAGBAUYCDA4GFiIjxk1BQUFAVgBVhAahXPWAAgD49YAD9W1BWW4Fz//////////////////////////8WYQj8gpCBFQKQYEBRYABgQFGAgwOBhYiI8ZNQUFBQFYAVYQHxVz1gAIA+PWAA/VtQUFBWW4A0FGECAldgAID9W1BW/qJlYnp6cjFYIPj4T8MahFBktXgekIMW88WRFXli3quw/UJO1U8lZAD5ZHNvbGNDAAURADIigAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKMCaDDoDGIAIShYKFAAAAAAAAAAAAAAAAAAAAAAAAAQAcgcKAxiACBABUhYKCQoCGAIQ/7LFDQoJCgIYYhCAs8UN"},{"b64Body":"Cg8KCQjS/P6sBhCHCBICGAISAhgDGOCssQMiAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjjoyCgMYgAgQoI0GGOgHIiS2tV8lAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA+g=","b64Record":"CiUIFiIDGIAIKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjDrn8Cj3xs/FgX1gexpw8wEO68SF6yManCvidYya74vIfKqxcLndZNdEvuE05q0LPYaCwiP/f6sBhCe3cACIg8KCQjS/P6sBhCHCBICGAIqIMODwq7DgsK3w4PCuXRGOMOCwq5Kw4PCi8ODwpDDg8KOMIDX2gI6jAIKAxiACCKAAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAogPEEUiAKCQoCGAIQz721BQoJCgIYYhCArrUFCggKAxiACBDQDw=="}]},"DepositDeleteSuccess":{"placeholderNum":1025,"encodedItems":[{"b64Body":"Cg8KCQjX/P6sBhCbCBICGAISAhgDGPuV9hQiAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjlouCiISILqlEt7vpKVphgUUqG34jiLSpodCkI7bZ6LnneC7b1TLENI9SgUIgM7aAw==","b64Record":"CiUIFhIDGIIIKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjCSU82s6//kLvbf74s4Gc9W4oREC4IZpnJiXnKGyGES4qrBTdJkizW3SFy1g9qDvSIaDAiT/f6sBhDn4qKAAyIPCgkI1/z+rAYQmwgSAhgCKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjlITCgcKAhgCEKN7CggKAxiCCBCkew=="},{"b64Body":"Cg8KCQjY/P6sBhCdCBICGAISAhgDGP7LzSwiAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjooBjgESCwiUy9mwBhD12et4Gm0KIhIgTIzpl8Zr4eMakwHQdPNq6fPUCZ3CU92/Y5wMKufjFesKIzohAh8ahCjBzJIkHvWGLA4/ANsKq5YoWyg6/qWMEvSwh66hCiISIFO1+cV/EsNltNqkklmpGP+JW+IzPvphQIvY4119ni8nIgxIZWxsbyBXb3JsZCEqADIA","b64Record":"CiUIFhoDGIMIKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjCyT80bpJ+UotLlaJ45ncyimIH22DLkRzSIUTAXCdpq4dGxEWSoMe9AWkQDH8ektFwaDAiU/f6sBhDw+6mIASIPCgkI2Pz+rAYQnQgSAhgCKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjlIA"},{"b64Body":"Cg8KCQjY/P6sBhChCBICGAISAhgDGPa4sjEiAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjpoBogkKAxiDCCKaCTYwODA2MDQwNTI2MTAyM2E4MDYxMDAxMzYwMDAzOTYwMDBmM2ZlNjA4MDYwNDA1MjYwMDQzNjEwNjEwMDNmNTc2MDAwMzU2MGUwMWM4MDYzMTIwNjVmZTAxNDYxMDA4ZjU3ODA2MzNjY2ZkNjBiMTQ2MTAwYmE1NzgwNjM2ZjY0MjM0ZTE0NjEwMGQxNTc4MDYzYjZiNTVmMjUxNDYxMDEyYzU3NWIzMzczZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjE2N2ZmMWIwM2Y3MDhiOWMzOWY0NTNmZTNmMGNlZjg0MTY0YzdkNmY3ZGY4MzZkZjA3OTZlMWU5YzJiY2U2ZWUzOTdlMzQ2MDQwNTE4MDgyODE1MjYwMjAwMTkxNTA1MDYwNDA1MTgwOTEwMzkwYTIwMDViMzQ4MDE1NjEwMDliNTc2MDAwODBmZDViNTA2MTAwYTQ2MTAxNWE1NjViNjA0MDUxODA4MjgxNTI2MDIwMDE5MTUwNTA2MDQwNTE4MDkxMDM5MGYzNWIzNDgwMTU2MTAwYzY1NzYwMDA4MGZkNWI1MDYxMDBjZjYxMDE2MjU2NWIwMDViMzQ4MDE1NjEwMGRkNTc2MDAwODBmZDViNTA2MTAxMmE2MDA0ODAzNjAzNjA0MDgxMTAxNTYxMDBmNDU3NjAwMDgwZmQ1YjgxMDE5MDgwODAzNTczZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjE2OTA2MDIwMDE5MDkyOTE5MDgwMzU5MDYwMjAwMTkwOTI5MTkwNTA1MDUwNjEwMWFiNTY1YjAwNWI2MTAxNTg2MDA0ODAzNjAzNjAyMDgxMTAxNTYxMDE0MjU3NjAwMDgwZmQ1YjgxMDE5MDgwODAzNTkwNjAyMDAxOTA5MjkxOTA1MDUwNTA2MTAxZjY1NjViMDA1YjYwMDA0NzkwNTA5MDU2NWIzMzczZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjE2NjEwOGZjNDc5MDgxMTUwMjkwNjA0MDUxNjAwMDYwNDA1MTgwODMwMzgxODU4ODg4ZjE5MzUwNTA1MDUwMTU4MDE1NjEwMWE4NTczZDYwMDA4MDNlM2Q2MDAwZmQ1YjUwNTY1YjgxNzNmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmMTY2MTA4ZmM4MjkwODExNTAyOTA2MDQwNTE2MDAwNjA0MDUxODA4MzAzODE4NTg4ODhmMTkzNTA1MDUwNTAxNTgwMTU2MTAxZjE1NzNkNjAwMDgwM2UzZDYwMDBmZDViNTA1MDUwNTY1YjgwMzQxNDYxMDIwMjU3NjAwMDgwZmQ1YjUwNTZmZWEyNjU2MjdhN2E3MjMxNTgyMGY4Zjg0ZmMzMWE4NDUwNjRiNTc4MWU5MDgzMTZmM2M1OTExNTc5NjJkZWFiYjBmZDQyNGVkNTRmMjU2NDAwZjk2NDczNmY2YzYzNDMwMDA1MTEwMDMy","b64Record":"CiAIFiocCgwIARAMGgYIgK6ZpA8SDAgBEA8aBgiArpmkDxIwzaeyEK3ytZ97BOcbyBjGSXCxPbxSDfKSMyafGDGoUijcqWaKoSQrI83kieq7pTssGgwIlP3+rAYQ0InbiQMiDwoJCNj8/qwGEKEIEgIYAiogw4PCrsOCwrfDg8K5dEY4w4LCrkrDg8KLw4PCkMODwo5SAA=="},{"b64Body":"Cg8KCQjZ/P6sBhCjCBICGAISAhgDGK/Dto0EIgIIeDIgw4PCrsOCwrfDg8K5dEY4w4LCrkrDg8KLw4PCkMODwo5ClgEKAxiDCBpzKnEIAhJtCiISILbiz9Ic0fNIHfxp7H+Hjb16A2WSeOoaMDsIAU1KaYtECiM6IQIWjrf1QOFQKrN9GGfcnBOqOkwvzHzTMUijNxsYt2b9/QoiEiBqfKDtumOh8ZWcCnF5wC+vyLZXlWnO/nllBP0/x8Pf1SCQoQ9CBQiAztoDUgBaAGoLY2VsbGFyIGRvb3I=","b64Record":"CiUIFiIDGIQIKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjBk1iNTqLpOpVSK9/C9PENMK4V9BLcoEnMIuHiFEwOctFV6rMIjQbrTwEtMiNxxwlgaDAiV/f6sBhCVqeGRASIPCgkI2fz+rAYQowgSAhgCKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjjDA2eIGQu8GCgMYhAgSugRggGBAUmAENhBhAD9XYAA1YOAcgGMSBl/gFGEAj1eAYzzP1gsUYQC6V4Bjb2QjThRhANFXgGO2tV8lFGEBLFdbM3P//////////////////////////xZ/8bA/cIucOfRT/j8M74QWTH1vffg23weW4enCvObuOX40YEBRgIKBUmAgAZFQUGBAUYCRA5CiAFs0gBVhAJtXYACA/VtQYQCkYQFaVltgQFGAgoFSYCABkVBQYEBRgJEDkPNbNIAVYQDGV2AAgP1bUGEAz2EBYlZbAFs0gBVhAN1XYACA/VtQYQEqYASANgNgQIEQFWEA9FdgAID9W4EBkICANXP//////////////////////////xaQYCABkJKRkIA1kGAgAZCSkZBQUFBhAatWWwBbYQFYYASANgNgIIEQFWEBQldgAID9W4EBkICANZBgIAGQkpGQUFBQYQH2VlsAW2AAR5BQkFZbM3P//////////////////////////xZhCPxHkIEVApBgQFFgAGBAUYCDA4GFiIjxk1BQUFAVgBVhAahXPWAAgD49YAD9W1BWW4Fz//////////////////////////8WYQj8gpCBFQKQYEBRYABgQFGAgwOBhYiI8ZNQUFBQFYAVYQHxVz1gAIA+PWAA/VtQUFBWW4A0FGECAldgAID9W1BW/qJlYnp6cjFYIPj4T8MahFBktXgekIMW88WRFXli3quw/UJO1U8lZAD5ZHNvbGNDAAURADIigAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKMCaDDoDGIQIShYKFAAAAAAAAAAAAAAAAAAAAAAAAAQEcgcKAxiECBABUhYKCQoCGAIQ/7LFDQoJCgIYYhCAs8UN"},{"b64Body":"Cg8KCQjZ/P6sBhClCBICGAISAhgDGOCssQMiAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjjoyCgMYhAgQoI0GGOgHIiS2tV8lAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA+g=","b64Record":"CiUIFiIDGIQIKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjC7AdX8mydRLUyvND7uG6UENqUK4YSl8a3pJvLgWxv0xX/CfwwARGCmRGMrWAlATU0aDAiV/f6sBhCihbGTAyIPCgkI2fz+rAYQpQgSAhgCKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjjCA19oCOowCCgMYhAgigAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKIDxBFIgCgkKAhgCEM+9tQUKCQoCGGIQgK61BQoICgMYhAgQ0A8="},{"b64Body":"Cg8KCQja/P6sBhCnCBICGAISAhgDGPyuhQgiAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjrIBCgoDGIQIEgMYggg=","b64Record":"CiUIFiIDGIQIKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjDeCcVtFR1MBDSQUtTNnkJsYGq/Y6ZQHRyC6d+zE7ll2G1QiHcT3yDCUhFwsQmoDPYaDAiW/f6sBhCxnoSbASIPCgkI2vz+rAYQpwgSAhgCKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjlIUCggKAxiCCBDQDwoICgMYhAgQzw8="}]},"MultipleDepositSuccess":{"placeholderNum":1033,"encodedItems":[{"b64Body":"Cg8KCQji/P6sBhDPCBICGAISAhgDGP7LzSwiAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjooBjwESDAiey9mwBhCH6uqZAhptCiISIGASthiNpw1mrEAN2R55r+0b4bOCb3R/U3bAdMUMw7sqCiM6IQM0P+kzRvbkvNWKPLgFIb6h0JOb4t2svXtRsUGSVXMFjgoiEiCFcMrehbbxfdt6Nw+P+0hiGFZsWYGH8OyGqD4UhEYPpiIMSGVsbG8gV29ybGQhKgAyAA==","b64Record":"CiUIFhoDGIoIKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjAzy0YCHK4Jg85rdZDrrCSOfWfHwYJIQYNMpMynQvMjNF2X5ZViVa4qoLFPyH0L/HkaDAie/f6sBhCfmcSgAiIPCgkI4vz+rAYQzwgSAhgCKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjlIA"},{"b64Body":"Cg8KCQjj/P6sBhDTCBICGAISAhgDGPa4sjEiAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjpoBogkKAxiKCCKaCTYwODA2MDQwNTI2MTAyM2E4MDYxMDAxMzYwMDAzOTYwMDBmM2ZlNjA4MDYwNDA1MjYwMDQzNjEwNjEwMDNmNTc2MDAwMzU2MGUwMWM4MDYzMTIwNjVmZTAxNDYxMDA4ZjU3ODA2MzNjY2ZkNjBiMTQ2MTAwYmE1NzgwNjM2ZjY0MjM0ZTE0NjEwMGQxNTc4MDYzYjZiNTVmMjUxNDYxMDEyYzU3NWIzMzczZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjE2N2ZmMWIwM2Y3MDhiOWMzOWY0NTNmZTNmMGNlZjg0MTY0YzdkNmY3ZGY4MzZkZjA3OTZlMWU5YzJiY2U2ZWUzOTdlMzQ2MDQwNTE4MDgyODE1MjYwMjAwMTkxNTA1MDYwNDA1MTgwOTEwMzkwYTIwMDViMzQ4MDE1NjEwMDliNTc2MDAwODBmZDViNTA2MTAwYTQ2MTAxNWE1NjViNjA0MDUxODA4MjgxNTI2MDIwMDE5MTUwNTA2MDQwNTE4MDkxMDM5MGYzNWIzNDgwMTU2MTAwYzY1NzYwMDA4MGZkNWI1MDYxMDBjZjYxMDE2MjU2NWIwMDViMzQ4MDE1NjEwMGRkNTc2MDAwODBmZDViNTA2MTAxMmE2MDA0ODAzNjAzNjA0MDgxMTAxNTYxMDBmNDU3NjAwMDgwZmQ1YjgxMDE5MDgwODAzNTczZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjE2OTA2MDIwMDE5MDkyOTE5MDgwMzU5MDYwMjAwMTkwOTI5MTkwNTA1MDUwNjEwMWFiNTY1YjAwNWI2MTAxNTg2MDA0ODAzNjAzNjAyMDgxMTAxNTYxMDE0MjU3NjAwMDgwZmQ1YjgxMDE5MDgwODAzNTkwNjAyMDAxOTA5MjkxOTA1MDUwNTA2MTAxZjY1NjViMDA1YjYwMDA0NzkwNTA5MDU2NWIzMzczZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjE2NjEwOGZjNDc5MDgxMTUwMjkwNjA0MDUxNjAwMDYwNDA1MTgwODMwMzgxODU4ODg4ZjE5MzUwNTA1MDUwMTU4MDE1NjEwMWE4NTczZDYwMDA4MDNlM2Q2MDAwZmQ1YjUwNTY1YjgxNzNmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmMTY2MTA4ZmM4MjkwODExNTAyOTA2MDQwNTE2MDAwNjA0MDUxODA4MzAzODE4NTg4ODhmMTkzNTA1MDUwNTAxNTgwMTU2MTAxZjE1NzNkNjAwMDgwM2UzZDYwMDBmZDViNTA1MDUwNTY1YjgwMzQxNDYxMDIwMjU3NjAwMDgwZmQ1YjUwNTZmZWEyNjU2MjdhN2E3MjMxNTgyMGY4Zjg0ZmMzMWE4NDUwNjRiNTc4MWU5MDgzMTZmM2M1OTExNTc5NjJkZWFiYjBmZDQyNGVkNTRmMjU2NDAwZjk2NDczNmY2YzYzNDMwMDA1MTEwMDMy","b64Record":"CiAIFiocCgwIARAMGgYIgK6ZpA8SDAgBEA8aBgiArpmkDxIwn6aHgLtjzyI/AB4dcz51O5bSCU/j5A6Ub85UfOmc0xoIzptniaU/87Sp+CFb9cgMGgsIn/3+rAYQ7JK3RSIPCgkI4/z+rAYQ0wgSAhgCKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjlIA"},{"b64Body":"Cg8KCQjj/P6sBhDVCBICGAISAhgDGK/Dto0EIgIIeDIgw4PCrsOCwrfDg8K5dEY4w4LCrkrDg8KLw4PCkMODwo5ClgEKAxiKCBpzKnEIAhJtCiISIHJVm5Qs/bTKJUpOj7t0KkXO7T9vxSbPQFeCrLj1alATCiM6IQNA90mY1LCdl86IJepYxvy+wjf3OVBJYA/pnKlz6PwyewoiEiD7YKJ3sjy8Q5X+nUfJQR8Tu4wdXK1V1U/1viU11MXQdyCQoQ9CBQiAztoDUgBaAGoLY2VsbGFyIGRvb3I=","b64Record":"CiUIFiIDGIsIKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjASRDIfLq1Ax7uy3U5xcIJhcZRC7DhjxCT3hok14su/3BG1C0PjASPQPsHlzO1/n/MaDAif/f6sBhCl8ouqAiIPCgkI4/z+rAYQ1QgSAhgCKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjjDA2eIGQu8GCgMYiwgSugRggGBAUmAENhBhAD9XYAA1YOAcgGMSBl/gFGEAj1eAYzzP1gsUYQC6V4Bjb2QjThRhANFXgGO2tV8lFGEBLFdbM3P//////////////////////////xZ/8bA/cIucOfRT/j8M74QWTH1vffg23weW4enCvObuOX40YEBRgIKBUmAgAZFQUGBAUYCRA5CiAFs0gBVhAJtXYACA/VtQYQCkYQFaVltgQFGAgoFSYCABkVBQYEBRgJEDkPNbNIAVYQDGV2AAgP1bUGEAz2EBYlZbAFs0gBVhAN1XYACA/VtQYQEqYASANgNgQIEQFWEA9FdgAID9W4EBkICANXP//////////////////////////xaQYCABkJKRkIA1kGAgAZCSkZBQUFBhAatWWwBbYQFYYASANgNgIIEQFWEBQldgAID9W4EBkICANZBgIAGQkpGQUFBQYQH2VlsAW2AAR5BQkFZbM3P//////////////////////////xZhCPxHkIEVApBgQFFgAGBAUYCDA4GFiIjxk1BQUFAVgBVhAahXPWAAgD49YAD9W1BWW4Fz//////////////////////////8WYQj8gpCBFQKQYEBRYABgQFGAgwOBhYiI8ZNQUFBQFYAVYQHxVz1gAIA+PWAA/VtQUFBWW4A0FGECAldgAID9W1BW/qJlYnp6cjFYIPj4T8MahFBktXgekIMW88WRFXli3quw/UJO1U8lZAD5ZHNvbGNDAAURADIigAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKMCaDDoDGIsIShYKFAAAAAAAAAAAAAAAAAAAAAAAAAQLcgcKAxiLCBABUhYKCQoCGAIQ/7LFDQoJCgIYYhCAs8UN"},{"b64Body":"Cg8KCQjk/P6sBhDXCBICGAISAhgDGOCssQMiAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjjoyCgMYiwgQoI0GGOgHIiS2tV8lAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA+g=","b64Record":"CiUIFiIDGIsIKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjCZSE74DbfZ7bgJ3Kr+AMOz0ynQqrzWO4jsgl7p6gU1MVFcqikv1Pj5fg9TKEsR+jkaCwig/f6sBhCPm4xPIg8KCQjk/P6sBhDXCBICGAIqIMODwq7DgsK3w4PCuXRGOMOCwq5Kw4PCi8ODwpDDg8KOMIDX2gI6jAIKAxiLCCKAAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAogPEEUiAKCQoCGAIQz721BQoJCgIYYhCArrUFCggKAxiLCBDQDw=="},{"b64Body":"Cg8KCQjk/P6sBhDZCBICGAISAhgDGOCssQMiAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjjoyCgMYiwgQoI0GGOgHIiS2tV8lAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA+g=","b64Record":"CiUIFiIDGIsIKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjDlFuB3/d39SOEzuTD8AXKlBTn+c4eFo5RRTyhWww0cGxr7AL07iEB12vhkFuzNw0gaDAig/f6sBhDQqsTQAiIPCgkI5Pz+rAYQ2QgSAhgCKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjjCA19oCOowCCgMYiwgigAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKIDxBFIgCgkKAhgCEM+9tQUKCQoCGGIQgK61BQoICgMYiwgQ0A8="},{"b64Body":"Cg8KCQjl/P6sBhDbCBICGAISAhgDGOCssQMiAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjjoyCgMYiwgQoI0GGOgHIiS2tV8lAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA+g=","b64Record":"CiUIFiIDGIsIKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjDsy3HZA5KV5QM8hx4LDL2GXxH/MN2idp9FJSvuI8YfFuZOugl3dbtj8K640BZjnVkaCwih/f6sBhCfudJYIg8KCQjl/P6sBhDbCBICGAIqIMODwq7DgsK3w4PCuXRGOMOCwq5Kw4PCi8ODwpDDg8KOMIDX2gI6jAIKAxiLCCKAAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAogPEEUiAKCQoCGAIQz721BQoJCgIYYhCArrUFCggKAxiLCBDQDw=="},{"b64Body":"Cg8KCQjl/P6sBhDdCBICGAISAhgDGOCssQMiAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjjoyCgMYiwgQoI0GGOgHIiS2tV8lAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA+g=","b64Record":"CiUIFiIDGIsIKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjCt7MbgVWDmOY3cTUmN9c6e3QtWNJsv9WX3MMAkTzirr94Vj0O5QcETka6z1X3OFEIaDAih/f6sBhCNpYXaAiIPCgkI5fz+rAYQ3QgSAhgCKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjjCA19oCOowCCgMYiwgigAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKIDxBFIgCgkKAhgCEM+9tQUKCQoCGGIQgK61BQoICgMYiwgQ0A8="},{"b64Body":"Cg8KCQjm/P6sBhDfCBICGAISAhgDGOCssQMiAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjjoyCgMYiwgQoI0GGOgHIiS2tV8lAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA+g=","b64Record":"CiUIFiIDGIsIKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjA1E5WAaj7pGzciqjkHfQ85ryNwbCt15AzzRIECGgHt3x8x+CyQ5KMr5Ea/3ORrQaAaCwii/f6sBhDc/P1hIg8KCQjm/P6sBhDfCBICGAIqIMODwq7DgsK3w4PCuXRGOMOCwq5Kw4PCi8ODwpDDg8KOMIDX2gI6jAIKAxiLCCKAAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAogPEEUiAKCQoCGAIQz721BQoJCgIYYhCArrUFCggKAxiLCBDQDw=="},{"b64Body":"Cg8KCQjm/P6sBhDhCBICGAISAhgDGOCssQMiAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjjoyCgMYiwgQoI0GGOgHIiS2tV8lAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA+g=","b64Record":"CiUIFiIDGIsIKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjBY19mdGtF3OV92JYNA7rXFvvvCSny5dpUd0gMjyM62CKkoLoRKR6DncdjVZxHyx3MaDAii/f6sBhCBqbDjAiIPCgkI5vz+rAYQ4QgSAhgCKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjjCA19oCOowCCgMYiwgigAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKIDxBFIgCgkKAhgCEM+9tQUKCQoCGGIQgK61BQoICgMYiwgQ0A8="},{"b64Body":"Cg8KCQjn/P6sBhDjCBICGAISAhgDGOCssQMiAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjjoyCgMYiwgQoI0GGOgHIiS2tV8lAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA+g=","b64Record":"CiUIFiIDGIsIKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjA2qCq8qoiTu/EO301cCDXowS/7BkYi1GUBISoR8BKSsTNk2Q1gvLF+cnPm2CYYokUaCwij/f6sBhCs6rZrIg8KCQjn/P6sBhDjCBICGAIqIMODwq7DgsK3w4PCuXRGOMOCwq5Kw4PCi8ODwpDDg8KOMIDX2gI6jAIKAxiLCCKAAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAogPEEUiAKCQoCGAIQz721BQoJCgIYYhCArrUFCggKAxiLCBDQDw=="},{"b64Body":"Cg8KCQjn/P6sBhDlCBICGAISAhgDGOCssQMiAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjjoyCgMYiwgQoI0GGOgHIiS2tV8lAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA+g=","b64Record":"CiUIFiIDGIsIKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjBIs6eBduRFO5XDV/Sz9vFOQE/tEuE4gnx+x6FF235xSbjW7YvX7GHKV2hRQzt0/9waDAij/f6sBhCig4LtAiIPCgkI5/z+rAYQ5QgSAhgCKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjjCA19oCOowCCgMYiwgigAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKIDxBFIgCgkKAhgCEM+9tQUKCQoCGGIQgK61BQoICgMYiwgQ0A8="},{"b64Body":"Cg8KCQjo/P6sBhDnCBICGAISAhgDGOCssQMiAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjjoyCgMYiwgQoI0GGOgHIiS2tV8lAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA+g=","b64Record":"CiUIFiIDGIsIKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjCa3+yYg/jMfW+5nVu2Rtgz6aL/NOOal2Ochx2xh8s6apygHudU2pheDo9TdYT4TSIaCwik/f6sBhDo7oJ1Ig8KCQjo/P6sBhDnCBICGAIqIMODwq7DgsK3w4PCuXRGOMOCwq5Kw4PCi8ODwpDDg8KOMIDX2gI6jAIKAxiLCCKAAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAogPEEUiAKCQoCGAIQz721BQoJCgIYYhCArrUFCggKAxiLCBDQDw=="},{"b64Body":"Cg8KCQjo/P6sBhDpCBICGAISAhgDGOCssQMiAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjjoyCgMYiwgQoI0GGOgHIiS2tV8lAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA+g=","b64Record":"CiUIFiIDGIsIKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjCKMTQgaoOA9THza6z+fgm5dXPsZ0PqN9U9BMP1MZOcP/GU5JtjCYqut14tF12KYKkaDAik/f6sBhCGltv2AiIPCgkI6Pz+rAYQ6QgSAhgCKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjjCA19oCOowCCgMYiwgigAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKIDxBFIgCgkKAhgCEM+9tQUKCQoCGGIQgK61BQoICgMYiwgQ0A8="}]},"payTestSelfDestructCall":{"placeholderNum":1036,"encodedItems":[{"b64Body":"Cg8KCQjt/P6sBhD5CBICGAISAhgDGPuV9hQiAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjloyCiISINruqmOQrxyDRCIoHMX+TQ5/64bru7824N+tdpnIzobJEICglKWNHUoFCIDO2gM=","b64Record":"CiUIFhIDGI0IKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjDS/K4bwlZp49OukfIu2HYAetdPHLUiBr3ZRl60G/RhIWgvpO1gipTlu2dTHZnLLjEaDAip/f6sBhDarpreASIPCgkI7fz+rAYQ+QgSAhgCKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjlIbCgsKAhgCEP+/qMqaOgoMCgMYjQgQgMCoypo6"},{"b64Body":"Cg8KCQjt/P6sBhD7CBICGAISAhgDGPuV9hQiAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjlouCiISIIPFcWOlu7AXHFQtdgBR4WGiHr7D/dfpuCK86o/29aQSEOgHSgUIgM7aAw==","b64Record":"CiUIFhIDGI4IKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjD0Sr4MeKBrcMgG/gAKicjZV8PCJUsmuXYub+AiI1UuxLqYRfNjvfZcJvb75OUsqtkaDAip/f6sBhDPtfDCAyIPCgkI7fz+rAYQ+wgSAhgCKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjlITCgcKAhgCEM8PCggKAxiOCBDQDw=="},{"b64Body":"Cg8KCQju/P6sBhD9CBICGAISAhgDGP7LzSwiAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjooBjwESDAiqy9mwBhCl26fQARptCiISIIrsAUu9T0Aj2OCrera3Gb9pduNj+rubrLlDiM0XA3eACiM6IQM0zmnaQaWUMK7lZ44nwQAe28nYwT9c94CLl25+yjcBDAoiEiB+7Z48w9AuxdoT2XowVZNzlJ2lPlKcKSALl2qi3uzLzyIMSGVsbG8gV29ybGQhKgAyAA==","b64Record":"CiUIFhoDGI8IKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjBhO5z4VpHeEw8mIcuT36o7OtHtbGx211hC+92gmr6AueoXKCrh89Ksb2//TNjK+7IaDAiq/f6sBhCmvdbnASIPCgkI7vz+rAYQ/QgSAhgCKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjlIA"},{"b64Body":"Cg8KCQju/P6sBhCBCRICGAISAhgDGKu53TMiAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjpoBug0KAxiPCCKyDTYwODA2MDQwNTIzNDgwMTU2MTAwMTA1NzYwMDA4MGZkNWI1MDYxMDMzOTgwNjEwMDIwNjAwMDM5NjAwMGYzZmU2MDgwNjA0MDUyNjAwNDM2MTA2MTAwNzI1NzYwMDAzNTdjMDEwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDkwMDQ4MDYzMTIwNjVmZTAxNDYxMDA3NzU3ODA2MzNjY2ZkNjBiMTQ2MTAwYTI1NzgwNjM2ZjY0MjM0ZTE0NjEwMGI5NTc4MDYzODE0MmU4ZjYxNDYxMDExNDU3ODA2MzliOTZlZWNlMTQ2MTAxNjU1NzgwNjNiNmI1NWYyNTE0NjEwMWNhNTc1YjYwMDA4MGZkNWIzNDgwMTU2MTAwODM1NzYwMDA4MGZkNWI1MDYxMDA4YzYxMDFmODU2NWI2MDQwNTE4MDgyODE1MjYwMjAwMTkxNTA1MDYwNDA1MTgwOTEwMzkwZjM1YjM0ODAxNTYxMDBhZTU3NjAwMDgwZmQ1YjUwNjEwMGI3NjEwMjE3NTY1YjAwNWIzNDgwMTU2MTAwYzU1NzYwMDA4MGZkNWI1MDYxMDExMjYwMDQ4MDM2MDM2MDQwODExMDE1NjEwMGRjNTc2MDAwODBmZDViODEwMTkwODA4MDM1NzNmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmMTY5MDYwMjAwMTkwOTI5MTkwODAzNTkwNjAyMDAxOTA5MjkxOTA1MDUwNTA2MTAyNzc1NjViMDA1YjM0ODAxNTYxMDEyMDU3NjAwMDgwZmQ1YjUwNjEwMTYzNjAwNDgwMzYwMzYwMjA4MTEwMTU2MTAxMzc1NzYwMDA4MGZkNWI4MTAxOTA4MDgwMzU3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNjkwNjAyMDAxOTA5MjkxOTA1MDUwNTA2MTAyYzI1NjViMDA1YjM0ODAxNTYxMDE3MTU3NjAwMDgwZmQ1YjUwNjEwMWI0NjAwNDgwMzYwMzYwMjA4MTEwMTU2MTAxODg1NzYwMDA4MGZkNWI4MTAxOTA4MDgwMzU3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNjkwNjAyMDAxOTA5MjkxOTA1MDUwNTA2MTAyZGI1NjViNjA0MDUxODA4MjgxNTI2MDIwMDE5MTUwNTA2MDQwNTE4MDkxMDM5MGYzNWI2MTAxZjY2MDA0ODAzNjAzNjAyMDgxMTAxNTYxMDFlMDU3NjAwMDgwZmQ1YjgxMDE5MDgwODAzNTkwNjAyMDAxOTA5MjkxOTA1MDUwNTA2MTAyZmM1NjViMDA1YjYwMDAzMDczZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjE2MzE5MDUwOTA1NjViMzM3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNjYxMDhmYzMwNzNmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmMTYzMTkwODExNTAyOTA2MDQwNTE2MDAwNjA0MDUxODA4MzAzODE4NTg4ODhmMTkzNTA1MDUwNTAxNTgwMTU2MTAyNzQ1NzNkNjAwMDgwM2UzZDYwMDBmZDViNTA1NjViODE3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNjYxMDhmYzgyOTA4MTE1MDI5MDYwNDA1MTYwMDA2MDQwNTE4MDgzMDM4MTg1ODg4OGYxOTM1MDUwNTA1MDE1ODAxNTYxMDJiZDU3M2Q2MDAwODAzZTNkNjAwMGZkNWI1MDUwNTA1NjViODA3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNmZmNWI2MDAwODE3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNjMxOTA1MDkxOTA1MDU2NWI4MDM0MTQxNTE1NjEwMzBhNTc2MDAwODBmZDViNTA1NmZlYTE2NTYyN2E3YTcyMzA1ODIwMjFjNGI1NzE5NTI2OGZmMGNlMWU5MGIwOGM3ZTkxYmU0MTg0MjA5OTMzZmY0ZTcwYjYyZTY5ZDM4OTU2OGQ5YTAwMjk=","b64Record":"CiAIFiocCgwIARAMGgYIgK6ZpA8SDAgBEA8aBgiArpmkDxIwnWa4ObzH/SPcUxDuOI7HT9QLiRdtgD4iOFnhrOVXx/a5oQtMySpua7QS93NzunbMGgwIqv3+rAYQy/iYzAMiDwoJCO78/qwGEIEJEgIYAiogw4PCrsOCwrfDg8K5dEY4w4LCrkrDg8KLw4PCkMODwo5SAA=="},{"b64Body":"Cg8KCQjv/P6sBhCDCRICGAISAhgDGJb7rp0CIgIIeDIgw4PCrsOCwrfDg8K5dEY4w4LCrkrDg8KLw4PCkMODwo5CRQoDGI8IGiISIODAfZfeLSoW/zIAu41WJfDHVSMgDWtTeiERc3az2CS1IJChD0IFCIDO2gNSAFoAagtjZWxsYXIgZG9vcg==","b64Record":"CiUIFiIDGJAIKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjBqg7t79KBuT5/VAy/JFJ23sXgVuVjAfT5GXNUuQX2ek7PTm6c5Mbocbjviv4UzU94aDAir/f6sBhDyqPTwASIPCgkI7/z+rAYQgwkSAhgCKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjjDA2eIGQu4ICgMYkAgSuQZggGBAUmAENhBhAHJXYAA1fAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAkASAYxIGX+AUYQB3V4BjPM/WCxRhAKJXgGNvZCNOFGEAuVeAY4FC6PYUYQEUV4Bjm5buzhRhAWVXgGO2tV8lFGEByldbYACA/Vs0gBVhAINXYACA/VtQYQCMYQH4VltgQFGAgoFSYCABkVBQYEBRgJEDkPNbNIAVYQCuV2AAgP1bUGEAt2ECF1ZbAFs0gBVhAMVXYACA/VtQYQESYASANgNgQIEQFWEA3FdgAID9W4EBkICANXP//////////////////////////xaQYCABkJKRkIA1kGAgAZCSkZBQUFBhAndWWwBbNIAVYQEgV2AAgP1bUGEBY2AEgDYDYCCBEBVhATdXYACA/VuBAZCAgDVz//////////////////////////8WkGAgAZCSkZBQUFBhAsJWWwBbNIAVYQFxV2AAgP1bUGEBtGAEgDYDYCCBEBVhAYhXYACA/VuBAZCAgDVz//////////////////////////8WkGAgAZCSkZBQUFBhAttWW2BAUYCCgVJgIAGRUFBgQFGAkQOQ81thAfZgBIA2A2AggRAVYQHgV2AAgP1bgQGQgIA1kGAgAZCSkZBQUFBhAvxWWwBbYAAwc///////////////////////////FjGQUJBWWzNz//////////////////////////8WYQj8MHP//////////////////////////xYxkIEVApBgQFFgAGBAUYCDA4GFiIjxk1BQUFAVgBVhAnRXPWAAgD49YAD9W1BWW4Fz//////////////////////////8WYQj8gpCBFQKQYEBRYABgQFGAgwOBhYiI8ZNQUFBQFYAVYQK9Vz1gAIA+PWAA/VtQUFBWW4Bz//////////////////////////8W/1tgAIFz//////////////////////////8WMZBQkZBQVluANBQVFWEDCldgAID9W1BW/qFlYnp6cjBYICHEtXGVJo/wzh6QsIx+kb5BhCCZM/9OcLYuadOJVo2aACkigAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKMCaDDoDGJAIShYKFAAAAAAAAAAAAAAAAAAAAAAAAAQQcgcKAxiQCBABUhYKCQoCGAIQ/7LFDQoJCgIYYhCAs8UN"},{"b64Body":"ChAKCQjv/P6sBhCFCRIDGI0IEgIYAxighpQKIgIIeDIgw4PCrsOCwrfDg8K5dEY4w4LCrkrDg8KLw4PCkMODwo46MgoDGJAIEOCnEhjoByIktrVfJQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPo","b64Record":"CiUIFiIDGJAIKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjCfQ7aFYNqGlX4739iiCihog6AEoMfP/16aU7QbqM5s8PcqZ/bR3qQyrDZO6zQht98aDAir/f6sBhCPsefVAyIQCgkI7/z+rAYQhQkSAxiNCCogw4PCrsOCwrfDg8K5dEY4w4LCrkrDg8KLw4PCkMODwo4wgIWQCDqMAgoDGJAIIoACAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACiA0w5SIQoJCgIYYhCAiqAQCgoKAxiNCBDPmaAQCggKAxiQCBDQDw=="},{"b64Body":"ChAKCQjw/P6sBhCHCRIDGI0IEgIYAxighpQKIgIIeDIgw4PCrsOCwrfDg8K5dEY4w4LCrkrDg8KLw4PCkMODwo46DwoDGJAIEOCnEiIEEgZf4A==","b64Record":"CiUIFiIDGJAIKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjDGnZx19e5dpyalQwovTShnWATyyBWu05pgH0UgyBDN0pShf7Etl1Q0OxtlgYra+tgaDAis/f6sBhDjjtP6ASIQCgkI8Pz+rAYQhwkSAxiNCCogw4PCrsOCwrfDg8K5dEY4w4LCrkrDg8KLw4PCkMODwo4wgIWQCDquAgoDGJAIEiAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD6CKAAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAogNMOUhcKCQoCGGIQgIqgEAoKCgMYjQgQ/4mgEA=="},{"b64Body":"ChAKCQjw/P6sBhCJCRIDGI0IEgIYAxighpQKIgIIeDIgw4PCrsOCwrfDg8K5dEY4w4LCrkrDg8KLw4PCkMODwo46LwoDGJAIEOCnEiIkgULo9gAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQQ","b64Record":"CiUITSIDGJAIKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjBLVph7uxqvM8RPcGqDTpCZ+jT+4jXIsGwB8Q/4bSNPQ9NyxbD8jLa7ZRJKxWREgrYaCwit/f6sBhCb/NQCIhAKCQjw/P6sBhCJCRIDGI0IKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjjCghpQKOhsaFVNFTEZfREVTVFJVQ1RfVE9fU0VMRijgpxJSFwoJCgIYYhDAjKgUCgoKAxiNCBC/jKgU"},{"b64Body":"ChAKCQjx/P6sBhCLCRIDGI0IEgIYAxighpQKIgIIeDIgw4PCrsOCwrfDg8K5dEY4w4LCrkrDg8KLw4PCkMODwo46LwoDGJAIEOCnEiIkgULo9gAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA","b64Record":"CiUIHSIDGJAIKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjBDQKH/G/9HkbjQo68W+GJ+7BqesEVkfNwRaLkxncdWhA9vXgZGts5c3ZnBgYQSX5UaDAit/f6sBhCBxZ+EAiIQCgkI8fz+rAYQiwkSAxiNCCogw4PCrsOCwrfDg8K5dEY4w4LCrkrDg8KLw4PCkMODwo4woIaUCjoeGhhJTlZBTElEX1NPTElESVRZX0FERFJFU1Mo4KcSUhcKCQoCGGIQwIyoFAoKCgMYjQgQv4yoFA=="},{"b64Body":"ChAKCQjy/P6sBhCNCRIDGI0IEgIYAxighpQKIgIIeDIgw4PCrsOCwrfDg8K5dEY4w4LCrkrDg8KLw4PCkMODwo46LwoDGJAIEOCnEiIkgULo9gAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQO","b64Record":"CiUIFiIDGJAIKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjA6x9RFsbqvHPezyYKc8wZD6WLWQuYzgGYQ7vDe6aeXHmfYtKz/udqBjm3pOFTAwM4aCwiu/f6sBhDgzpYMIhAKCQjy/P6sBhCNCRIDGI0IKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjjCAhZAIOowCCgMYkAgigAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKIDTDlIrCgkKAhhiEICKoBAKCgoDGI0IEP+JoBAKCAoDGI4IENAPCggKAxiQCBDPDw=="}]},"MultipleSelfDestructsAreSafe":{"placeholderNum":1041,"encodedItems":[{"b64Body":"Cg8KCQj2/P6sBhClCRICGAISAhgDGP7LzSwiAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjooBjwESDAiyy9mwBhCukNvkAhptCiISICF7W4ycBpdQCr8sSUe3cQAlx3DOoVTUBEO7rrgD2ZBcCiM6IQMwiDCkUcg5HS5E2oi0SIFAXjHa4cqk5+h5fDJI6C1QzgoiEiBfTS3TX+YO5t60JoWG8aHnuAovkHtoieoEsRlbEB5qMCIMSGVsbG8gV29ybGQhKgAyAA==","b64Record":"CiUIFhoDGJIIKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjAUul5dQ+Pe3XdIePKetojPXpf6DO2Z8PWPla7lbgjxz/je/ZrY/jZEHLj2jBJ37VwaDAiy/f6sBhCa7JLtAiIPCgkI9vz+rAYQpQkSAhgCKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjlIA"},{"b64Body":"Cg8KCQj3/P6sBhCpCRICGAISAhgDGILlpzoiAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjpoBoBkKAxiSCCKYGTYwODA2MDQwNTIzNDgwMTU2MTAwMTA1NzYwMDA4MGZkNWI1MDMwNjAwMDYwNDA1MTYxMDAyMDkwNjEwMjViNTY1YjgwODM3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNjczZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjE2ODE1MjYwMjAwMTgyNjNmZmZmZmZmZjE2NjNmZmZmZmZmZjE2ODE1MjYwMjAwMTkyNTA1MDUwNjA0MDUxODA5MTAzOTA2MDAwZjA4MDE1ODAxNTYxMDA4NTU3M2Q2MDAwODAzZTNkNjAwMGZkNWI1MDYwMDA4MDYwMDM4MTEwNjEwMDkzNTdmZTViMDE2MDAwNjEwMTAwMGE4MTU0ODE3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYwMjE5MTY5MDgzNzNmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmMTYwMjE3OTA1NTUwMzA2MDAxNjA0MDUxNjEwMGUxOTA2MTAyNWI1NjViODA4MzczZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjE2NzNmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmMTY4MTUyNjAyMDAxODI2M2ZmZmZmZmZmMTY2M2ZmZmZmZmZmMTY4MTUyNjAyMDAxOTI1MDUwNTA2MDQwNTE4MDkxMDM5MDYwMDBmMDgwMTU4MDE1NjEwMTQ2NTczZDYwMDA4MDNlM2Q2MDAwZmQ1YjUwNjAwMDYwMDE2MDAzODExMDYxMDE1NTU3ZmU1YjAxNjAwMDYxMDEwMDBhODE1NDgxNzNmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmMDIxOTE2OTA4MzczZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjE2MDIxNzkwNTU1MDMwNjAwMjYwNDA1MTYxMDFhMzkwNjEwMjViNTY1YjgwODM3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNjczZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjE2ODE1MjYwMjAwMTgyNjNmZmZmZmZmZjE2NjNmZmZmZmZmZjE2ODE1MjYwMjAwMTkyNTA1MDUwNjA0MDUxODA5MTAzOTA2MDAwZjA4MDE1ODAxNTYxMDIwODU3M2Q2MDAwODAzZTNkNjAwMGZkNWI1MDYwMDA2MDAyNjAwMzgxMTA2MTAyMTc1N2ZlNWIwMTYwMDA2MTAxMDAwYTgxNTQ4MTczZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjAyMTkxNjkwODM3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNjAyMTc5MDU1NTA2MTAyNjg1NjViNjEwMWI4ODA2MTA0OTQ4MzM5MDE5MDU2NWI2MTAyMWQ4MDYxMDI3NzYwMDAzOTYwMDBmM2ZlNjA4MDYwNDA1MjM0ODAxNTYxMDAxMDU3NjAwMDgwZmQ1YjUwNjAwNDM2MTA2MTAwMmI1NzYwMDAzNTYwZTAxYzgwNjM2NGRiZTc4YzE0NjEwMDMwNTc1YjYwMDA4MGZkNWI2MTAwMzg2MTAwM2E1NjViMDA1YjYwMDA4MDYwMDM4MTEwNjEwMDQ3NTdmZTViMDE2MDAwOTA1NDkwNjEwMTAwMGE5MDA0NzNmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmMTY3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNjYzYjhiM2RiYzY2MDQwNTE4MTYzZmZmZmZmZmYxNjYwZTAxYjgxNTI2MDA0MDE2MDAwNjA0MDUxODA4MzAzODE2MDAwODc4MDNiMTU4MDE1NjEwMGIwNTc2MDAwODBmZDViNTA1YWYxMTU4MDE1NjEwMGM0NTczZDYwMDA4MDNlM2Q2MDAwZmQ1YjUwNTA1MDUwNjAwMDYwMDE2MDAzODExMDYxMDBkNjU3ZmU1YjAxNjAwMDkwNTQ5MDYxMDEwMDBhOTAwNDczZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjE2NzNmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmMTY2M2I4YjNkYmM2NjA0MDUxODE2M2ZmZmZmZmZmMTY2MGUwMWI4MTUyNjAwNDAxNjAwMDYwNDA1MTgwODMwMzgxNjAwMDg3ODAzYjE1ODAxNTYxMDEzZjU3NjAwMDgwZmQ1YjUwNWFmMTE1ODAxNTYxMDE1MzU3M2Q2MDAwODAzZTNkNjAwMGZkNWI1MDUwNTA1MDYwMDA2MDAyNjAwMzgxMTA2MTAxNjU1N2ZlNWIwMTYwMDA5MDU0OTA2MTAxMDAwYTkwMDQ3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNjczZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjE2NjNiOGIzZGJjNjYwNDA1MTgxNjNmZmZmZmZmZjE2NjBlMDFiODE1MjYwMDQwMTYwMDA2MDQwNTE4MDgzMDM4MTYwMDA4NzgwM2IxNTgwMTU2MTAxY2U1NzYwMDA4MGZkNWI1MDVhZjExNTgwMTU2MTAxZTI1NzNkNjAwMDgwM2UzZDYwMDBmZDViNTA1MDUwNTA1NmZlYTI2NTYyN2E3YTcyMzE1ODIwNmI1ODg3NjljODIzYmQ0MjMyMzRjYTY3MjI2MTdjOTE1NTkzNzc4NzczMjg5ZTVlYTY5NTdmNmIzMDgzYTcyNjY0NzM2ZjZjNjM0MzAwMDUxMTAwMzI2MDgwNjA0MDUyMzQ4MDE1NjEwMDEwNTc2MDAwODBmZDViNTA2MDQwNTE2MTAxYjgzODAzODA2MTAxYjg4MzM5ODE4MTAxNjA0MDUyNjA0MDgxMTAxNTYxMDAzMzU3NjAwMDgwZmQ1YjgxMDE5MDgwODA1MTkwNjAyMDAxOTA5MjkxOTA4MDUxOTA2MDIwMDE5MDkyOTE5MDUwNTA1MDgxNjAwMDgwNjEwMTAwMGE4MTU0ODE3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYwMjE5MTY5MDgzNzNmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmMTYwMjE3OTA1NTUwODA2MDAwNjAxNDYxMDEwMDBhODE1NDgxNjNmZmZmZmZmZjAyMTkxNjkwODM2M2ZmZmZmZmZmMTYwMjE3OTA1NTUwNTA1MDYwZjk4MDYxMDBiZjYwMDAzOTYwMDBmM2ZlNjA4MDYwNDA1MjM0ODAxNTYwMGY1NzYwMDA4MGZkNWI1MDYwMDQzNjEwNjAyODU3NjAwMDM1NjBlMDFjODA2M2I4YjNkYmM2MTQ2MDJkNTc1YjYwMDA4MGZkNWI2MDMzNjAzNTU2NWIwMDViN2ZlZmVkZTI2ODc2NDE2MzM5YjBhNzg5ZGEzNTE3Njk0N2JkZDMwMmI4ZWE5ZDQ5NWRjZDZjMDg0ZWMzZDFjYzllNjAwMDYwMTQ5MDU0OTA2MTAxMDAwYTkwMDQ2M2ZmZmZmZmZmMTY2MDQwNTE4MDgyNjNmZmZmZmZmZjE2NjNmZmZmZmZmZjE2ODE1MjYwMjAwMTkxNTA1MDYwNDA1MTgwOTEwMzkwYTE2MDAwODA5MDU0OTA2MTAxMDAwYTkwMDQ3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNjczZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjE2ZmZmZWEyNjU2MjdhN2E3MjMxNTgyMGJiNGExZTEzMzZjMzRkNDc2NTQ3MWU5ODkzNDgxMDM3MTVhYzI0Nzk5NmU5YWM2ODYwZjRlYTQ1OWE5YjJiMDg2NDczNmY2YzYzNDMwMDA1MTEwMDMy","b64Record":"CiAIFiocCgwIARAMGgYIgK6ZpA8SDAgBEA8aBgiArpmkDxIwEgNEDG1K9OQIg/377F1PYHeFW+fSjcBYPut1LAK1ZpBytf05ju1Gx4r9o9DaKcVnGgwIs/3+rAYQkKjvkQEiDwoJCPf8/qwGEKkJEgIYAiogw4PCrsOCwrfDg8K5dEY4w4LCrkrDg8KLw4PCkMODwo5SAA=="},{"b64Body":"Cg8KCQj3/P6sBhCrCRICGAISAhgDGJb7rp0CIgIIeDIgw4PCrsOCwrfDg8K5dEY4w4LCrkrDg8KLw4PCkMODwo5CRQoDGJIIGiISIJFCFo5PTUCeYZhbbmhwviuwySk/4DdpHTn88GJQrpYXIMDPJEIFCIDO2gNSAFoAagtjZWxsYXIgZG9vcg==","b64Record":"CiUIFiIDGJMIKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjDILGgKiU+jXyl1a9ILUbgMY6PxT5KS2P6pXbsSbvtcW3fkllitCgT+xWZpWniVvUkaDAiz/f6sBhDM1tP2AiIPCgkI9/z+rAYQqwkSAhgCKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjjCAiqAQQvwGCgMYkwgSnQRggGBAUjSAFWEAEFdgAID9W1BgBDYQYQArV2AANWDgHIBjZNvnjBRhADBXW2AAgP1bYQA4YQA6VlsAW2AAgGADgRBhAEdX/lsBYACQVJBhAQAKkARz//////////////////////////8Wc///////////////////////////FmO4s9vGYEBRgWP/////FmDgG4FSYAQBYABgQFGAgwOBYACHgDsVgBVhALBXYACA/VtQWvEVgBVhAMRXPWAAgD49YAD9W1BQUFBgAGABYAOBEGEA1lf+WwFgAJBUkGEBAAqQBHP//////////////////////////xZz//////////////////////////8WY7iz28ZgQFGBY/////8WYOAbgVJgBAFgAGBAUYCDA4FgAIeAOxWAFWEBP1dgAID9W1Ba8RWAFWEBU1c9YACAPj1gAP1bUFBQUGAAYAJgA4EQYQFlV/5bAWAAkFSQYQEACpAEc///////////////////////////FnP//////////////////////////xZjuLPbxmBAUYFj/////xZg4BuBUmAEAWAAYEBRgIMDgWAAh4A7FYAVYQHOV2AAgP1bUFrxFYAVYQHiVz1gAIA+PWAA/VtQUFBQVv6iZWJ6enIxWCBrWIdpyCO9QjI0ymciYXyRVZN3h3Monl6mlX9rMIOnJmRzb2xjQwAFEQAyIoACAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACiAph06AxiTCDoDGJQIOgMYlQg6AxiWCEoWChQAAAAAAAAAAAAAAAAAAAAAAAAEE3IHCgMYkwgQBHIHCgMYlAgQAXIHCgMYlQgQAXIHCgMYlggQAVIWCgkKAhgCEP+TwCAKCQoCGGIQgJTAIA=="},{"b64Body":"ChEKCQj3/P6sBhCrCRICGAIgAUI4GiISIJFCFo5PTUCeYZhbbmhwviuwySk/4DdpHTn88GJQrpYXQgUIgM7aA2oLY2VsbGFyIGRvb3I=","b64Record":"CgcIFiIDGJQIEjCwU4yvcctZBZYl0IyWFF+pExYJzlMbE8X5izaUqnzhp0w5QXYcRQEhJfIbIiBZO80aDAiz/f6sBhDN1tP2AiIRCgkI9/z+rAYQqwkSAhgCIAFCHQoDGJQIShYKFK1F9n0CA4WiHZ2y2TOU8ZsOPJyxUgB6DAiz/f6sBhDM1tP2Ag=="},{"b64Body":"ChEKCQj3/P6sBhCrCRICGAIgAkI4GiISIJFCFo5PTUCeYZhbbmhwviuwySk/4DdpHTn88GJQrpYXQgUIgM7aA2oLY2VsbGFyIGRvb3I=","b64Record":"CgcIFiIDGJUIEjB7vlOYBYuT8wQZclPI20YDS8pwZLBocEqfd5ob3GWtZfGiq0nnTOYwvMZKL9KUWi0aDAiz/f6sBhDO1tP2AiIRCgkI9/z+rAYQqwkSAhgCIAJCHQoDGJUIShYKFFAclXKVzmaHxj05dnr8vcvYeP5HUgB6DAiz/f6sBhDM1tP2Ag=="},{"b64Body":"ChEKCQj3/P6sBhCrCRICGAIgA0I4GiISIJFCFo5PTUCeYZhbbmhwviuwySk/4DdpHTn88GJQrpYXQgUIgM7aA2oLY2VsbGFyIGRvb3I=","b64Record":"CgcIFiIDGJYIEjC9wOMGgKhWwifGUej7RjZksAXRpCirYPnlKCAJNqpx1Ni2HpijfFdoCH+m8Z21RPkaDAiz/f6sBhDP1tP2AiIRCgkI9/z+rAYQqwkSAhgCIANCHQoDGJYIShYKFK3lJKuCKlZ6yirQUpZ5uLY8anyyUgB6DAiz/f6sBhDM1tP2Ag=="},{"b64Body":"Cg8KCQj4/P6sBhCtCRICGAISAhgDGOCssQMiAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjjoPCgMYkwgQoI0GIgRk2+eM","b64Record":"CiUIFiIDGJMIKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjCu11NtpgJO8WSB0BRDI3cOXAG+s110Fg4Jxm4yov1NOvAjc3/fk4LT3YrSNYqWB6saDAi0/f6sBhCt17abASIPCgkI+Pz+rAYQrQkSAhgCKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjjCA19oCOvkJCgMYkwgigAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAAAAAAAAAEAAAAIAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAABAAAAAAAAAgAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAgAAAAAAAAAAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKIDxBDLMAgoDGJQIEoACAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgAAAAAAAABAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABog7+3iaHZBYzmwp4naNRdpR73TArjqnUldzWwITsPRzJ4iIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMswCCgMYlQgSgAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAAAAAAAAAAAAAAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgAAAAAAAAAAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGiDv7eJodkFjObCnido1F2lHvdMCuOqdSV3NbAhOw9HMniIgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEyzAIKAxiWCBKAAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEAAAAAAAACAAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAACAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAaIO/t4mh2QWM5sKeJ2jUXaUe90wK46p1JXc1sCE7D0cyeIiAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAlIWCgkKAhgCEP+ttQUKCQoCGGIQgK61BQ=="}]},"smartContractInlineAssemblyCheck":{"placeholderNum":1047,"encodedItems":[{"b64Body":"Cg8KCQj9/P6sBhDBCRICGAISAhgDGPuV9hQiAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjlozCiISIIy6Le62LSErrYq5D38B7w1KuxywAgoC2s9Q+r7dHAo4EIDAyvOEowJKBQiAztoD","b64Record":"CiUIFhIDGJgIKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjCAoYg4g6UN/r30Vwl2z4U0wJw2qprpVB/+Gll8xlDGjBLzYV78v5HjGmx6WgRscoEaCwi5/f6sBhCv+/ojIg8KCQj9/P6sBhDBCRICGAIqIMODwq7DgsK3w4PCuXRGOMOCwq5Kw4PCi8ODwpDDg8KOUh0KDAoCGAIQ//+U54nGBAoNCgMYmAgQgICV54nGBA=="},{"b64Body":"Cg8KCQj9/P6sBhDDCRICGAISAhgDGP7LzSwiAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjooBjwESDAi5y9mwBhCGy52TAhptCiISIEELz0z8/OOcTK9BAOBdFzfvQSDsxfwkArn4GkXFttq5CiM6IQLf+FCRjCg9hgtundTYdvvBCUk541DbXci6IY6SjSNtqAoiEiAxZ90mpIv7d9Fu3LT7OG3TYK3l06oDuphk40FCPIL1qyIMSGVsbG8gV29ybGQhKgAyAA==","b64Record":"CiUIFhoDGJkIKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjAfhLEG1LMqc46MVDxLsT+wwO35uBa0XcVLudlo5In9b+eiShKqWbrsz8bHCtuKbrwaDAi5/f6sBhDI49mlAiIPCgkI/fz+rAYQwwkSAhgCKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjlIA"},{"b64Body":"Cg8KCQj+/P6sBhDHCRICGAISAhgDGKGwpi4iAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjpoB3AMKAxiZCCLUAzYwODA2MDQwNTI2MDBmNjAwMDU1MzQ4MDE1NjEwMDE1NTc2MDAwODBmZDViNTA2MGM2ODA2MTAwMjQ2MDAwMzk2MDAwZjNmZTYwODA2MDQwNTIzNDgwMTU2MDBmNTc2MDAwODBmZDViNTA2MDA0MzYxMDYwMzI1NzYwMDAzNTYwZTAxYzgwNjM2MGZlNDdiMTE0NjAzNzU3ODA2MzZkNGNlNjNjMTQ2MDYyNTc1YjYwMDA4MGZkNWI2MDYwNjAwNDgwMzYwMzYwMjA4MTEwMTU2MDRiNTc2MDAwODBmZDViODEwMTkwODA4MDM1OTA2MDIwMDE5MDkyOTE5MDUwNTA1MDYwN2U1NjViMDA1YjYwNjg2MDg4NTY1YjYwNDA1MTgwODI4MTUyNjAyMDAxOTE1MDUwNjA0MDUxODA5MTAzOTBmMzViODA2MDAwODE5MDU1NTA1MDU2NWI2MDAwODA1NDkwNTA5MDU2ZmVhMjY1NjI3YTdhNzIzMTU4MjAyODIzMzhiZmVmZDNmOTk5ZGYxYzA3MjBjODViN2M3OTMzNTFlNTk3NWJiNDk1OTkyZjEyMTY5MjZlZWE4OTE1NjQ3MzZmNmM2MzQzMDAwNTBiMDAzMg==","b64Record":"CiAIFiocCgwIARAMGgYIgK6ZpA8SDAgBEA8aBgiArpmkDxIw5oCt1Frfplmlx52hguv4OC4+6K6upkAc5g4JMWmx0Dxle0U+1808GooMhFG67xvNGgsIuv3+rAYQ/8TcLSIPCgkI/vz+rAYQxwkSAhgCKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjlIA"},{"b64Body":"Cg8KCQj+/P6sBhDJCRICGAISAhgDGP7LzSwiAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjooBjwESDAi6y9mwBhCr++OcAhptCiISIKNN+UTxdf2itrU+/GX4ervMcSBKJD4ebs2ZxmQB3CzvCiM6IQLsnlGVnw54sSAyl2tJ3qx7oG0T7mFNBbXkFH/Bq1p5CQoiEiAW8yuNyiAKs9tKeGUEbcQ/dURSLmj7f7Hyr3FGr4NvoiIMSGVsbG8gV29ybGQhKgAyAA==","b64Record":"CiUIFhoDGJoIKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjB7TSgvKXF6aXZDTUjUXBPHCYIzOPHerjmkAnDkTMmwkYInp3pmfMasXycpUqXwJ5kaDAi6/f6sBhCbz/2uAiIPCgkI/vz+rAYQyQkSAhgCKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjlIA"},{"b64Body":"Cg8KCQj//P6sBhDNCRICGAISAhgDGM+WmC8iAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjpoBqAUKAxiaCCKgBTYwODA2MDQwNTIzNDgwMTU2MTAwMTA1NzYwMDA4MGZkNWI1MDYxMDEzMDgwNjEwMDIwNjAwMDM5NjAwMGYzZmU2MDgwNjA0MDUyMzQ4MDE1NjAwZjU3NjAwMDgwZmQ1YjUwNjAwNDM2MTA2MDNjNTc2MDAwMzU2MGUwMWM4MDYzYjUxYzRmOTYxNDYwNDE1NzgwNjNjMjcyMmVjYzE0NjA5NjU3ODA2M2U3MTQ4Y2MzMTQ2MGIyNTc1YjYwMDA4MGZkNWI2MDgwNjAwNDgwMzYwMzYwMjA4MTEwMTU2MDU1NTc2MDAwODBmZDViODEwMTkwODA4MDM1NzNmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmMTY5MDYwMjAwMTkwOTI5MTkwNTA1MDUwNjBkZDU2NWI2MDQwNTE4MDgyODE1MjYwMjAwMTkxNTA1MDYwNDA1MTgwOTEwMzkwZjM1YjYwOWM2MGU4NTY1YjYwNDA1MTgwODI4MTUyNjAyMDAxOTE1MDUwNjA0MDUxODA5MTAzOTBmMzViNjBkYjYwMDQ4MDM2MDM2MDIwODExMDE1NjBjNjU3NjAwMDgwZmQ1YjgxMDE5MDgwODAzNTkwNjAyMDAxOTA5MjkxOTA1MDUwNTA2MGYxNTY1YjAwNWI2MDAwODEzYjkwNTA5MTkwNTA1NjViNjAwMDgwNTQ5MDUwOTA1NjViODA2MDAwODE5MDU1NTA1MDU2ZmVhMjY1NjI3YTdhNzIzMTU4MjA0ZWFkOGM3NzBiYjRiYmY0MDBhM2I0NmEyMTYyMWU3ZWU1MmJmNThlYjkxMDJkM2E0NmY2NjEyZTRlMTA3Y2I4NjQ3MzZmNmM2MzQzMDAwNTBiMDAzMg==","b64Record":"CiAIFiocCgwIARAMGgYIgK6ZpA8SDAgBEA8aBgiArpmkDxIwcGeiiXPT8PBIXSVjaXofYgp3TaQOxbU7+zJ1iE/Ycj60mwRgj1RZOynUXViqXvOnGgsIu/3+rAYQnqiINyIPCgkI//z+rAYQzQkSAhgCKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjlIA"},{"b64Body":"Cg8KCQj//P6sBhDPCRICGAISAhgDGJb7rp0CIgIIeDIgw4PCrsOCwrfDg8K5dEY4w4LCrkrDg8KLw4PCkMODwo5CRQoDGJkIGiISICHTT7QdjDsrgrlo3b+AceH71lhTp5l3UJ8gCQAGM/fwIJChD0IFCIDO2gNSAFoAagtjZWxsYXIgZG9vcg==","b64Record":"CiUIFiIDGJsIKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjAFG1kCI+Udm/QyndzD5l9untMCz4AvLtUWL7QTIvNxIqcwuO4/vwtsc5YKOzjIH7AaDAi7/f6sBhDEueC4AiIPCgkI//z+rAYQzwkSAhgCKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjjDA2eIGQvsDCgMYmwgSxgFggGBAUjSAFWAPV2AAgP1bUGAENhBgMldgADVg4ByAY2D+R7EUYDdXgGNtTOY8FGBiV1tgAID9W2BgYASANgNgIIEQFWBLV2AAgP1bgQGQgIA1kGAgAZCSkZBQUFBgflZbAFtgaGCIVltgQFGAgoFSYCABkVBQYEBRgJEDkPNbgGAAgZBVUFBWW2AAgFSQUJBW/qJlYnp6cjFYICgjOL/v0/mZ3xwHIMhbfHkzUeWXW7SVmS8SFpJu6okVZHNvbGNDAAULADIigAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKMCaDDoDGJsIShYKFAAAAAAAAAAAAAAAAAAAAAAAAAQbcgcKAxibCBABUhYKCQoCGAIQ/7LFDQoJCgIYYhCAs8UN"},{"b64Body":"Cg8KCQiA/f6sBhDRCRICGAISAhgDGJb7rp0CIgIIeDIgw4PCrsOCwrfDg8K5dEY4w4LCrkrDg8KLw4PCkMODwo5CRQoDGJoIGiISIIo5cgMvigWsSKB9oMxETrHQNvx6RFnBtii8kgfZRUrLIJChD0IFCIDO2gNSAFoAagtjZWxsYXIgZG9vcg==","b64Record":"CiUIFiIDGJwIKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjARWz4uDXQYcl+06PMxH5gzgIzU9wfmtcA/+GvSajwVyPhtK/crbjp17+2W58afZFEaCwi8/f6sBhDV/99AIg8KCQiA/f6sBhDRCRICGAIqIMODwq7DgsK3w4PCuXRGOMOCwq5Kw4PCi8ODwpDDg8KOMMDZ4gZC5QQKAxicCBKwAmCAYEBSNIAVYA9XYACA/VtQYAQ2EGA8V2AANWDgHIBjtRxPlhRgQVeAY8JyLswUYJZXgGPnFIzDFGCyV1tgAID9W2CAYASANgNgIIEQFWBVV2AAgP1bgQGQgIA1c///////////////////////////FpBgIAGQkpGQUFBQYN1WW2BAUYCCgVJgIAGRUFBgQFGAkQOQ81tgnGDoVltgQFGAgoFSYCABkVBQYEBRgJEDkPNbYNtgBIA2A2AggRAVYMZXYACA/VuBAZCAgDWQYCABkJKRkFBQUGDxVlsAW2AAgTuQUJGQUFZbYACAVJBQkFZbgGAAgZBVUFBW/qJlYnp6cjFYIE6tjHcLtLv0AKO0aiFiHn7lK/WOuRAtOkb2YS5OEHy4ZHNvbGNDAAULADIigAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKMCaDDoDGJwIShYKFAAAAAAAAAAAAAAAAAAAAAAAAAQccgcKAxicCBABUhYKCQoCGAIQ/7LFDQoJCgIYYhCAs8UN"}]},"ocToken":{"placeholderNum":1053,"encodedItems":[{"b64Body":"Cg8KCQiE/f6sBhDzCRICGAISAhgDGPuV9hQiAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjloyCiISIC7Q45jtfsCLflF9PfqgqX3S4uPsJlV+I+A1iyXz83QAEICglKWNHUoFCIDO2gM=","b64Record":"CiUIFhIDGJ4IKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjBP/G9+O9dHbAa6AOSKW5ha20Wf8yod8u5zaTjTsGW+znTE+QBYCQ6uCA7bU9/XIaAaDAjA/f6sBhCxgZu+AyIPCgkIhP3+rAYQ8wkSAhgCKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjlIbCgsKAhgCEP+/qMqaOgoMCgMYnggQgMCoypo6"},{"b64Body":"ChAKCQiF/f6sBhD1CRIDGJ4IEgIYAxj7lfYUIgIIeDIgw4PCrsOCwrfDg8K5dEY4w4LCrkrDg8KLw4PCkMODwo5aMQoiEiDaIH5TTu8vj7wtYDJ0HsGLuGfRTYVg6qDgnDH16HekpRCAyK+gJUoFCIDO2gM=","b64Record":"CiUIFhIDGJ8IKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjDzFmRhLzxesVvN6Whdc0UIJj66+gk3o39lLmGazyRkuVnvZElpqvtKUqZzHW5kljIaDAjB/f6sBhDTtpjGASIQCgkIhf3+rAYQ9QkSAxieCCogw4PCrsOCwrfDg8K5dEY4w4LCrkrDg8KLw4PCkMODwo4w+5X2FFJICgkKAhgDEKyPgwEKCQoCGGIQ8pbUIAoKCgMYoAYQ7MKKBAoKCgMYoQYQ7MKKBAoLCgMYnggQ9bvL6koKCwoDGJ8IEICQ38BK"},{"b64Body":"ChAKCQiF/f6sBhD3CRIDGJ4IEgIYAxj7lfYUIgIIeDIgw4PCrsOCwrfDg8K5dEY4w4LCrkrDg8KLw4PCkMODwo5aMQoiEiAxYE976UQSdQkh5KPhjOw1O/rl8V7pmFzP3BBlVydBqhCAyK+gJUoFCIDO2gM=","b64Record":"CiUIFhIDGKAIKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjCJeIbl5lCu/t8ggxur378BG3zKTl+7JVeTlz4Esen0fOakLEC7VMsCmuoMNLbI7swaDAjB/f6sBhCcrMfHAyIQCgkIhf3+rAYQ9wkSAxieCCogw4PCrsOCwrfDg8K5dEY4w4LCrkrDg8KLw4PCkMODwo4w+5X2FFJICgkKAhgDEKyPgwEKCQoCGGIQ8pbUIAoKCgMYoAYQ7MKKBAoKCgMYoQYQ7MKKBAoLCgMYnggQ9bvL6koKCwoDGKAIEICQ38BK"},{"b64Body":"ChAKCQiG/f6sBhD5CRIDGJ4IEgIYAxj7lfYUIgIIeDIgw4PCrsOCwrfDg8K5dEY4w4LCrkrDg8KLw4PCkMODwo5aMQoiEiACuM/hocQNG7MvCB0Ml49iJEHSqdnH0gNslFjfWsTvGRCAyK+gJUoFCIDO2gM=","b64Record":"CiUIFhIDGKEIKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjBvFHCxxtBFR8wQrhPn9c5LRXt/3Ck0dvZl6N+P2n6xzjDWceTITr6RRZsJS6wh4A4aDAjC/f6sBhCQ9MXPASIQCgkIhv3+rAYQ+QkSAxieCCogw4PCrsOCwrfDg8K5dEY4w4LCrkrDg8KLw4PCkMODwo4w+5X2FFJICgkKAhgDEKyPgwEKCQoCGGIQ8pbUIAoKCgMYoAYQ7MKKBAoKCgMYoQYQ7MKKBAoLCgMYnggQ9bvL6koKCwoDGKEIEICQ38BK"},{"b64Body":"ChAKCQiG/f6sBhD7CRIDGJ4IEgIYAxj7lfYUIgIIeDIgw4PCrsOCwrfDg8K5dEY4w4LCrkrDg8KLw4PCkMODwo5aMQoiEiCKM73ZIL/7ehBaSjE1pyUXgEmnglOX5Whl0bl4lUkZ8xCAyK+gJUoFCIDO2gM=","b64Record":"CiUIFhIDGKIIKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjC4S8O+X2cvVCrv4INVH1Kui5iLWiTKztrxJ2XAmFiwK0ebPoLnVm7wvdITY5Q4COQaDAjC/f6sBhDvmanRAyIQCgkIhv3+rAYQ+wkSAxieCCogw4PCrsOCwrfDg8K5dEY4w4LCrkrDg8KLw4PCkMODwo4w+5X2FFJICgkKAhgDEKyPgwEKCQoCGGIQ8pbUIAoKCgMYoAYQ7MKKBAoKCgMYoQYQ7MKKBAoLCgMYnggQ9bvL6koKCwoDGKIIEICQ38BK"},{"b64Body":"Cg8KCQiH/f6sBhCbChICGAISAhgDGP7LzSwiAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjooBjwESDAjDy9mwBhD96d3lARptCiISIKJwBl6PPBfoTg2D7qy2cr998impdJ7mlKi9YHSqsCa5CiM6IQIo8ITM3fhHSsW/zZqgNExaMrR7LdrYlfhRGs/oymnpqwoiEiChG2v7ZFbnoQjpw4aGSiImKeCFOpfjN/fYzEyj6PvSeCIMSGVsbG8gV29ybGQhKgAyAA==","b64Record":"CiUIFhoDGKMIKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjDfbB83XM+ulI755rp7eNsiiDUb+YZp7cWfYoA9rKYL97wyBuAgThtRAQTaisFp9e0aDAjD/f6sBhCx+eD1ASIPCgkIh/3+rAYQmwoSAhgCKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjlIA"},{"b64Body":"Cg8KCQiH/f6sBhCfChICGAISAhgDGIydjj4iAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjpoBiCAKAxijCCKAIDYwNjA2MDQwNTI2MDAyODA1NDYwZmYxOTE2NjAwMzE3OTA1NTM0MTU2MTAwMWM1NzYwMDA4MGZkNWI2MDQwNTE2MTBhMWYzODAzODA2MTBhMWY4MzM5ODEwMTYwNDA1MjgwODA1MTkxOTA2MDIwMDE4MDUxODIwMTkxOTA2MDIwMDE4MDUxNjAwMjU0NjBmZjE2NjAwYTBhODUwMjYwMDM4MTkwNTU2MDAxNjBhMDYwMDIwYTAzMzMxNjYwMDA5MDgxNTI2MDA0NjAyMDUyNjA0MDgxMjA5MTkwOTE1NTkyMDE5MTkwNTA4MjgwNTE2MTAwODQ5MjkxNjAyMDAxOTA2MTAwYTE1NjViNTA2MDAxODE4MDUxNjEwMDk4OTI5MTYwMjAwMTkwNjEwMGExNTY1YjUwNTA1MDUwNjEwMTNjNTY1YjgyODA1NDYwMDE4MTYwMDExNjE1NjEwMTAwMDIwMzE2NjAwMjkwMDQ5MDYwMDA1MjYwMjA2MDAwMjA5MDYwMWYwMTYwMjA5MDA0ODEwMTkyODI2MDFmMTA2MTAwZTI1NzgwNTE2MGZmMTkxNjgzODAwMTE3ODU1NTYxMDEwZjU2NWI4MjgwMDE2MDAxMDE4NTU1ODIxNTYxMDEwZjU3OTE4MjAxNWI4MjgxMTExNTYxMDEwZjU3ODI1MTgyNTU5MTYwMjAwMTkxOTA2MDAxMDE5MDYxMDBmNDU2NWI1MDYxMDExYjkyOTE1MDYxMDExZjU2NWI1MDkwNTY1YjYxMDEzOTkxOTA1YjgwODIxMTE1NjEwMTFiNTc2MDAwODE1NTYwMDEwMTYxMDEyNTU2NWI5MDU2NWI2MTA4ZDQ4MDYxMDE0YjYwMDAzOTYwMDBmMzAwNjA2MDYwNDA1MjYwMDQzNjEwNjEwMGI5NTc2M2ZmZmZmZmZmN2MwMTAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwNjAwMDM1MDQxNjYzMDZmZGRlMDM4MTE0NjEwMGJlNTc4MDYzMDk1ZWE3YjMxNDYxMDE0ODU3ODA2MzE4MTYwZGRkMTQ2MTAxN2U1NzgwNjMyM2I4NzJkZDE0NjEwMWEzNTc4MDYzMzEzY2U1NjcxNDYxMDFjYjU3ODA2MzQyOTY2YzY4MTQ2MTAxZjQ1NzgwNjM3MGEwODIzMTE0NjEwMjBhNTc4MDYzNzljYzY3OTAxNDYxMDIyOTU3ODA2Mzk1ZDg5YjQxMTQ2MTAyNGI1NzgwNjNhOTA1OWNiYjE0NjEwMjVlNTc4MDYzY2FlOWNhNTExNDYxMDI4MjU3ODA2M2RkNjJlZDNlMTQ2MTAyZTc1NzViNjAwMDgwZmQ1YjM0MTU2MTAwYzk1NzYwMDA4MGZkNWI2MTAwZDE2MTAzMGM1NjViNjA0MDUxNjAyMDgwODI1MjgxOTA4MTAxODM4MTgxNTE4MTUyNjAyMDAxOTE1MDgwNTE5MDYwMjAwMTkwODA4MzgzNjAwMDViODM4MTEwMTU2MTAxMGQ1NzgwODIwMTUxODM4MjAxNTI2MDIwMDE2MTAwZjU1NjViNTA1MDUwNTA5MDUwOTA4MTAxOTA2MDFmMTY4MDE1NjEwMTNhNTc4MDgyMDM4MDUxNjAwMTgzNjAyMDAzNjEwMTAwMGEwMzE5MTY4MTUyNjAyMDAxOTE1MDViNTA5MjUwNTA1MDYwNDA1MTgwOTEwMzkwZjM1YjM0MTU2MTAxNTM1NzYwMDA4MGZkNWI2MTAxNmE2MDAxNjBhMDYwMDIwYTAzNjAwNDM1MTY2MDI0MzU2MTAzYWE1NjViNjA0MDUxOTAxNTE1ODE1MjYwMjAwMTYwNDA1MTgwOTEwMzkwZjM1YjM0MTU2MTAxODk1NzYwMDA4MGZkNWI2MTAxOTE2MTAzZGE1NjViNjA0MDUxOTA4MTUyNjAyMDAxNjA0MDUxODA5MTAzOTBmMzViMzQxNTYxMDFhZTU3NjAwMDgwZmQ1YjYxMDE2YTYwMDE2MGEwNjAwMjBhMDM2MDA0MzU4MTE2OTA2MDI0MzUxNjYwNDQzNTYxMDNlMDU2NWIzNDE1NjEwMWQ2NTc2MDAwODBmZDViNjEwMWRlNjEwNDU3NTY1YjYwNDA1MTYwZmY5MDkxMTY4MTUyNjAyMDAxNjA0MDUxODA5MTAzOTBmMzViMzQxNTYxMDFmZjU3NjAwMDgwZmQ1YjYxMDE2YTYwMDQzNTYxMDQ2MDU2NWIzNDE1NjEwMjE1NTc2MDAwODBmZDViNjEwMTkxNjAwMTYwYTA2MDAyMGEwMzYwMDQzNTE2NjEwNGViNTY1YjM0MTU2MTAyMzQ1NzYwMDA4MGZkNWI2MTAxNmE2MDAxNjBhMDYwMDIwYTAzNjAwNDM1MTY2MDI0MzU2MTA0ZmQ1NjViMzQxNTYxMDI1NjU3NjAwMDgwZmQ1YjYxMDBkMTYxMDVkOTU2NWIzNDE1NjEwMjY5NTc2MDAwODBmZDViNjEwMjgwNjAwMTYwYTA2MDAyMGEwMzYwMDQzNTE2NjAyNDM1NjEwNjQ0NTY1YjAwNWIzNDE1NjEwMjhkNTc2MDAwODBmZDViNjEwMTZhNjAwNDgwMzU2MDAxNjBhMDYwMDIwYTAzMTY5MDYwMjQ4MDM1OTE5MDYwNjQ5MDYwNDQzNTkwODEwMTkwODMwMTM1ODA2MDIwNjAxZjgyMDE4MTkwMDQ4MTAyMDE2MDQwNTE5MDgxMDE2MDQwNTI4MTgxNTI5MjkxOTA2MDIwODQwMTgzODM4MDgyODQzNzUwOTQ5NjUwNjEwNjUzOTU1MDUwNTA1MDUwNTA1NjViMzQxNTYxMDJmMjU3NjAwMDgwZmQ1YjYxMDE5MTYwMDE2MGEwNjAwMjBhMDM2MDA0MzU4MTE2OTA2MDI0MzUxNjYxMDc4NTU2NWI2MDAwODA1NDYwMDE4MTYwMDExNjE1NjEwMTAwMDIwMzE2NjAwMjkwMDQ4MDYwMWYwMTYwMjA4MDkxMDQwMjYwMjAwMTYwNDA1MTkwODEwMTYwNDA1MjgwOTI5MTkwODE4MTUyNjAyMDAxODI4MDU0NjAwMTgxNjAwMTE2MTU2MTAxMDAwMjAzMTY2MDAyOTAwNDgwMTU2MTAzYTI1NzgwNjAxZjEwNjEwMzc3NTc2MTAxMDA4MDgzNTQwNDAyODM1MjkxNjAyMDAxOTE2MTAzYTI1NjViODIwMTkxOTA2MDAwNTI2MDIwNjAwMDIwOTA1YjgxNTQ4MTUyOTA2MDAxMDE5MDYwMjAwMTgwODMxMTYxMDM4NTU3ODI5MDAzNjAxZjE2ODIwMTkxNWI1MDUwNTA1MDUwODE1NjViNjAwMTYwYTA2MDAyMGEwMzMzODExNjYwMDA5MDgxNTI2MDA1NjAyMDkwODE1MjYwNDA4MDgzMjA5Mzg2MTY4MzUyOTI5MDUyMjA4MTkwNTU2MDAxOTI5MTUwNTA1NjViNjAwMzU0ODE1NjViNjAwMTYwYTA2MDAyMGEwMzgwODQxNjYwMDA5MDgxNTI2MDA1NjAyMDkwODE1MjYwNDA4MDgzMjAzMzkwOTQxNjgzNTI5MjkwNTI5MDgxMjA1NDgyMTExNTYxMDQxNTU3NjAwMDgwZmQ1YjYwMDE2MGEwNjAwMjBhMDM4MDg1MTY2MDAwOTA4MTUyNjAwNTYwMjA5MDgxNTI2MDQwODA4MzIwMzM5MDk0MTY4MzUyOTI5MDUyMjA4MDU0ODM5MDAzOTA1NTYxMDQ0ZDg0ODQ4NDYxMDdhMjU2NWI1MDYwMDE5MzkyNTA1MDUwNTY1YjYwMDI1NDYwZmYxNjgxNTY1YjYwMDE2MGEwNjAwMjBhMDMzMzE2NjAwMDkwODE1MjYwMDQ2MDIwNTI2MDQwODEyMDU0ODI5MDEwMTU2MTA0ODY1NzYwMDA4MGZkNWI2MDAxNjBhMDYwMDIwYTAzMzMxNjYwMDA4MTgxNTI2MDA0NjAyMDUyNjA0MDkwODE5MDIwODA1NDg1OTAwMzkwNTU2MDAzODA1NDg1OTAwMzkwNTU3ZmNjMTZmNWRiYjQ4NzMyODA4MTVjMWVlMDlkYmQwNjczNmNmZmNjMTg0NDEyY2Y3YTcxYTBmZGI3NWQzOTdjYTU5MDg0OTA1MTkwODE1MjYwMjAwMTYwNDA1MTgwOTEwMzkwYTI1MDYwMDE5MTkwNTA1NjViNjAwNDYwMjA1MjYwMDA5MDgxNTI2MDQwOTAyMDU0ODE1NjViNjAwMTYwYTA2MDAyMGEwMzgyMTY2MDAwOTA4MTUyNjAwNDYwMjA1MjYwNDA4MTIwNTQ4MjkwMTAxNTYxMDUyMzU3NjAwMDgwZmQ1YjYwMDE2MGEwNjAwMjBhMDM4MDg0MTY2MDAwOTA4MTUyNjAwNTYwMjA5MDgxNTI2MDQwODA4MzIwMzM5MDk0MTY4MzUyOTI5MDUyMjA1NDgyMTExNTYxMDU1NjU3NjAwMDgwZmQ1YjYwMDE2MGEwNjAwMjBhMDM4MDg0MTY2MDAwODE4MTUyNjAwNDYwMjA5MDgxNTI2MDQwODA4MzIwODA1NDg4OTAwMzkwNTU2MDA1ODI1MjgwODMyMDMzOTA5NTE2ODM1MjkzOTA1MjgyOTAyMDgwNTQ4NTkwMDM5MDU1NjAwMzgwNTQ4NTkwMDM5MDU1OTA3ZmNjMTZmNWRiYjQ4NzMyODA4MTVjMWVlMDlkYmQwNjczNmNmZmNjMTg0NDEyY2Y3YTcxYTBmZGI3NWQzOTdjYTU5MDg0OTA1MTkwODE1MjYwMjAwMTYwNDA1MTgwOTEwMzkwYTI1MDYwMDE5MjkxNTA1MDU2NWI2MDAxODA1NDYwMDE4MTYwMDExNjE1NjEwMTAwMDIwMzE2NjAwMjkwMDQ4MDYwMWYwMTYwMjA4MDkxMDQwMjYwMjAwMTYwNDA1MTkwODEwMTYwNDA1MjgwOTI5MTkwODE4MTUyNjAyMDAxODI4MDU0NjAwMTgxNjAwMTE2MTU2MTAxMDAwMjAzMTY2MDAyOTAwNDgwMTU2MTAzYTI1NzgwNjAxZjEwNjEwMzc3NTc2MTAxMDA4MDgzNTQwNDAyODM1MjkxNjAyMDAxOTE2MTAzYTI1NjViNjEwNjRmMzM4MzgzNjEwN2EyNTY1YjUwNTA1NjViNjAwMDgzNjEwNjYwODE4NTYxMDNhYTU2NWIxNTYxMDc3ZDU3ODA2MDAxNjBhMDYwMDIwYTAzMTY2MzhmNGZmY2IxMzM4NjMwODc2MDQwNTE4NTYzZmZmZmZmZmYxNjdjMDEwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAyODE1MjYwMDQwMTgwODU2MDAxNjBhMDYwMDIwYTAzMTY2MDAxNjA=","b64Record":"CiAIFiocCgwIARAMGgYIgK6ZpA8SDAgBEA8aBgiArpmkDxIwwbZhCULESuzHPGvcMdnmzAVGWHhAI2FUdrAPMA3D6UsDG+X8qdFh1htOBjOXku5KGgsIxP3+rAYQpf3XGiIPCgkIh/3+rAYQnwoSAhgCKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjlIA"},{"b64Body":"Cg8KCQiI/f6sBhClChICGAISAhgDGMnP0DEiAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjoIBxggSAxijCCK+CGEwNjAwMjBhMDMxNjgxNTI2MDIwMDE4NDgxNTI2MDIwMDE4MzYwMDE2MGEwNjAwMjBhMDMxNjYwMDE2MGEwNjAwMjBhMDMxNjgxNTI2MDIwMDE4MDYwMjAwMTgyODEwMzgyNTI4MzgxODE1MTgxNTI2MDIwMDE5MTUwODA1MTkwNjAyMDAxOTA4MDgzODM2MDAwNWI4MzgxMTAxNTYxMDcxNjU3ODA4MjAxNTE4MzgyMDE1MjYwMjAwMTYxMDZmZTU2NWI1MDUwNTA1MDkwNTA5MDgxMDE5MDYwMWYxNjgwMTU2MTA3NDM1NzgwODIwMzgwNTE2MDAxODM2MDIwMDM2MTAxMDAwYTAzMTkxNjgxNTI2MDIwMDE5MTUwNWI1MDk1NTA1MDUwNTA1MDUwNjAwMDYwNDA1MTgwODMwMzgxNjAwMDg3ODAzYjE1MTU2MTA3NjQ1NzYwMDA4MGZkNWI2MTAyYzY1YTAzZjExNTE1NjEwNzc1NTc2MDAwODBmZDViNTA1MDUwNjAwMTkxNTA1YjUwOTM5MjUwNTA1MDU2NWI2MDA1NjAyMDkwODE1MjYwMDA5MjgzNTI2MDQwODA4NDIwOTA5MTUyOTA4MjUyOTAyMDU0ODE1NjViNjAwMDYwMDE2MGEwNjAwMjBhMDM4MzE2MTUxNTYxMDdiOTU3NjAwMDgwZmQ1YjYwMDE2MGEwNjAwMjBhMDM4NDE2NjAwMDkwODE1MjYwMDQ2MDIwNTI2MDQwOTAyMDU0ODI5MDEwMTU2MTA3ZGY1NzYwMDA4MGZkNWI2MDAxNjBhMDYwMDIwYTAzODMxNjYwMDA5MDgxNTI2MDA0NjAyMDUyNjA0MDkwMjA1NDgyODEwMTExNjEwODA1NTc2MDAwODBmZDViNTA2MDAxNjBhMDYwMDIwYTAzODA4MzE2NjAwMDgxODE1MjYwMDQ2MDIwNTI2MDQwODA4MjIwODA1NDk0ODgxNjgwODQ1MjgyODQyMDgwNTQ4ODgxMDM5MDkxNTU5Mzg1OTA1MjgxNTQ4NzAxOTA5MTU1OTE5MDkzMDE5MjdmZGRmMjUyYWQxYmUyYzg5YjY5YzJiMDY4ZmMzNzhkYWE5NTJiYTdmMTYzYzRhMTE2MjhmNTVhNGRmNTIzYjNlZjkwODU5MDUxOTA4MTUyNjAyMDAxNjA0MDUxODA5MTAzOTBhMzYwMDE2MGEwNjAwMjBhMDM4MDg0MTY2MDAwOTA4MTUyNjAwNDYwMjA1MjYwNDA4MDgyMjA1NDkyODcxNjgyNTI5MDIwNTQwMTgxMTQ2MTA4YTI1N2ZlNWI1MDUwNTA1MDU2MDBhMTY1NjI3YTdhNzIzMDU4MjAxNTE4MzhmNTZlNTZiNTBmNjNlNTQyODU2MmQ2MmI1M2Q1N2U0Njk3ZGQwZTFmZjdkNTNiNGViMWQ4ZDEwOGZmMDAyOQ==","b64Record":"CiAIFiocCgwIARAMGgYIgK6ZpA8SDAgBEA8aBgiArpmkDxIwxuW1xcuqRYkJGZsyo1ElAfCPTDs92PVFZNYp2Qpgdv581AG3fUtm9SyjD0xjBGptGgwIxP3+rAYQn+PH/wEiDwoJCIj9/qwGEKUKEgIYAiogw4PCrsOCwrfDg8K5dEY4w4LCrkrDg8KLw4PCkMODwo5SAA=="},{"b64Body":"ChAKCQiJ/f6sBhCnChIDGJ4IEgIYAxi1jqeoAiICCHgyIMODwq7DgsK3w4PCuXRGOMOCwq5Kw4PCi8ODwpDDg8KOQqgCCgMYowgaIhIg+GVqo3Q0vV3wWSqqrfo1KAahaWd5SL68u6eWneqRrjggkKEPQgUIgM7aA0rgAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD0JAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPT3BlbkNyb3dkIFRva2VuAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA09DVAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAUgBaAGoLY2VsbGFyIGRvb3I=","b64Record":"CiUIFiIDGKQIKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjDF342mpHdiyUuGRVaMEqMCzN4hjmcUU+4ue4OH62Wb4PwSvBKVl16GN581vdaQuVYaCwjF/f6sBhDXi5EkIhAKCQiJ/f6sBhCnChIDGJ4IKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjjC30aSvAkKJFAoDGKQIEtQRYGBgQFJgBDYQYQC5V2P/////fAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAYAA1BBZjBv3eA4EUYQC+V4BjCV6nsxRhAUhXgGMYFg3dFGEBfleAYyO4ct0UYQGjV4BjMTzlZxRhActXgGNClmxoFGEB9FeAY3CggjEUYQIKV4BjecxnkBRhAilXgGOV2JtBFGECS1eAY6kFnLsUYQJeV4BjyunKURRhAoJXgGPdYu0+FGEC51dbYACA/Vs0FWEAyVdgAID9W2EA0WEDDFZbYEBRYCCAglKBkIEBg4GBUYFSYCABkVCAUZBgIAGQgIODYABbg4EQFWEBDVeAggFRg4IBUmAgAWEA9VZbUFBQUJBQkIEBkGAfFoAVYQE6V4CCA4BRYAGDYCADYQEACgMZFoFSYCABkVBbUJJQUFBgQFGAkQOQ81s0FWEBU1dgAID9W2EBamABYKBgAgoDYAQ1FmAkNWEDqlZbYEBRkBUVgVJgIAFgQFGAkQOQ81s0FWEBiVdgAID9W2EBkWED2lZbYEBRkIFSYCABYEBRgJEDkPNbNBVhAa5XYACA/VthAWpgAWCgYAIKA2AENYEWkGAkNRZgRDVhA+BWWzQVYQHWV2AAgP1bYQHeYQRXVltgQFFg/5CRFoFSYCABYEBRgJEDkPNbNBVhAf9XYACA/VthAWpgBDVhBGBWWzQVYQIVV2AAgP1bYQGRYAFgoGACCgNgBDUWYQTrVls0FWECNFdgAID9W2EBamABYKBgAgoDYAQ1FmAkNWEE/VZbNBVhAlZXYACA/VthANFhBdlWWzQVYQJpV2AAgP1bYQKAYAFgoGACCgNgBDUWYCQ1YQZEVlsAWzQVYQKNV2AAgP1bYQFqYASANWABYKBgAgoDFpBgJIA1kZBgZJBgRDWQgQGQgwE1gGAgYB+CAYGQBIECAWBAUZCBAWBAUoGBUpKRkGAghAGDg4CChDdQlJZQYQZTlVBQUFBQUFZbNBVhAvJXYACA/VthAZFgAWCgYAIKA2AENYEWkGAkNRZhB4VWW2AAgFRgAYFgARYVYQEAAgMWYAKQBIBgHwFgIICRBAJgIAFgQFGQgQFgQFKAkpGQgYFSYCABgoBUYAGBYAEWFWEBAAIDFmACkASAFWEDoleAYB8QYQN3V2EBAICDVAQCg1KRYCABkWEDolZbggGRkGAAUmAgYAAgkFuBVIFSkGABAZBgIAGAgxFhA4VXgpADYB8WggGRW1BQUFBQgVZbYAFgoGACCgMzgRZgAJCBUmAFYCCQgVJgQICDIJOGFoNSkpBSIIGQVWABkpFQUFZbYANUgVZbYAFgoGACCgOAhBZgAJCBUmAFYCCQgVJgQICDIDOQlBaDUpKQUpCBIFSCERVhBBVXYACA/VtgAWCgYAIKA4CFFmAAkIFSYAVgIJCBUmBAgIMgM5CUFoNSkpBSIIBUg5ADkFVhBE2EhIRhB6JWW1BgAZOSUFBQVltgAlRg/xaBVltgAWCgYAIKAzMWYACQgVJgBGAgUmBAgSBUgpAQFWEEhldgAID9W2ABYKBgAgoDMxZgAIGBUmAEYCBSYECQgZAggFSFkAOQVWADgFSFkAOQVX/MFvXbtIcygIFcHuCdvQZzbP/MGEQSz3pxoP23XTl8pZCEkFGQgVJgIAFgQFGAkQOQolBgAZGQUFZbYARgIFJgAJCBUmBAkCBUgVZbYAFgoGACCgOCFmAAkIFSYARgIFJgQIEgVIKQEBVhBSNXYACA/VtgAWCgYAIKA4CEFmAAkIFSYAVgIJCBUmBAgIMgM5CUFoNSkpBSIFSCERVhBVZXYACA/VtgAWCgYAIKA4CEFmAAgYFSYARgIJCBUmBAgIMggFSIkAOQVWAFglKAgyAzkJUWg1KTkFKCkCCAVIWQA5BVYAOAVIWQA5BVkH/MFvXbtIcygIFcHuCdvQZzbP/MGEQSz3pxoP23XTl8pZCEkFGQgVJgIAFgQFGAkQOQolBgAZKRUFBWW2ABgFRgAYFgARYVYQEAAgMWYAKQBIBgHwFgIICRBAJgIAFgQFGQgQFgQFKAkpGQgYFSYCABgoBUYAGBYAEWFWEBAAIDFmACkASAFWEDoleAYB8QYQN3V2EBAICDVAQCg1KRYCABkWEDolZbYQZPM4ODYQeiVltQUFZbYACDYQZggYVhA6pWWxVhB31XgGABYKBgAgoDFmOPT/yxM4Ywh2BAUYVj/////xZ8AQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACgVJgBAGAhWABYKBgAgoDFmABYKBgAgoDFoFSYCABhIFSYCABg2ABYKBgAgoDFmABYKBgAgoDFoFSYCABgGAgAYKBA4JSg4GBUYFSYCABkVCAUZBgIAGQgIODYABbg4EQFWEHFleAggFRg4IBUmAgAWEG/lZbUFBQUJBQkIEBkGAfFoAVYQdDV4CCA4BRYAGDYCADYQEACgMZFoFSYCABkVBbUJVQUFBQUFBgAGBAUYCDA4FgAIeAOxUVYQdkV2AAgP1bYQLGWgPxFRVhB3VXYACA/VtQUFBgAZFQW1CTklBQUFZbYAVgIJCBUmAAkoNSYECAhCCQkVKQglKQIFSBVltgAGABYKBgAgoDgxYVFWEHuVdgAID9W2ABYKBgAgoDhBZgAJCBUmAEYCBSYECQIFSCkBAVYQffV2AAgP1bYAFgoGACCgODFmAAkIFSYARgIFJgQJAgVIKBARFhCAVXYACA/VtQYAFgoGACCgOAgxZgAIGBUmAEYCBSYECAgiCAVJSIFoCEUoKEIIBUiIEDkJFVk4WQUoFUhwGQkVWRkJMBkn/d8lKtG+LIm2nCsGj8N42qlSun8WPEoRYo9VpN9SOz75CFkFGQgVJgIAFgQFGAkQOQo2ABYKBgAgoDgIQWYACQgVJgBGAgUmBAgIIgVJKHFoJSkCBUAYEUYQiiV/5bUFBQUFYAoWVienpyMFggFRg49W5WtQ9j5UKFYtYrU9V+RpfdDh/31TtOsdjRCP8AKSKAAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAo7soMOgMYpAhKFgoUAAAAAAAAAAAAAAAAAAAAAAAABCRyBwoDGKQIEAFSPAoJCgIYAxDovq4VCgoKAhhiEO7q+tYDCgoKAxigBhDM/I85CgoKAxihBhDM/I85CgsKAxieCBDtosneBA=="},{"b64Body":"ChAKCQiJ/f6sBhCvChIDGJ4IEgIYAxiqkAUiAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjnIZChcKCgoDGJ4IEM+WjQcKCQoCGAMQ0JaNBw==","b64Record":"CiAIFiocCgwIARAMGgYIgK6ZpA8SDAgBEA8aBgiArpmkDxIws+CeUDnCUPOLJ0huzvdyJchWfxhZiRgNHtJh/IeqJH7Rv8607V3l8bYCc2iDntrvGgwIxf3+rAYQu4ThiAIiEAoJCIn9/qwGEK8KEgMYnggqIMODwq7DgsK3w4PCuXRGOMOCwq5Kw4PCi8ODwpDDg8KOMKqQBVI1CgkKAhgDEObLjQcKCAoCGGIQzu8HCggKAxigBhD4fQoICgMYoQYQ+H0KCgoDGJ4IEKO3lwc="},{"b64Body":"ChAKCQiJ/f6sBhCzChIDGJ4IEgIYAxiqkAUiAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjnIZChcKCgoDGJ4IEM+WjQcKCQoCGAMQ0JaNBw==","b64Record":"CiAIFiocCgwIARAMGgYIgK6ZpA8SDAgBEA8aBgiArpmkDxIwLthDAoKnPNdculosZ8pGZIft13kak6mhbtNZrZ3uPqQvf5fFBMnRWi8F22Tlw+NYGgwIxf3+rAYQo4zhiAIiEAoJCIn9/qwGELMKEgMYnggqIMODwq7DgsK3w4PCuXRGOMOCwq5Kw4PCi8ODwpDDg8KOMKqQBVI1CgkKAhgDEObLjQcKCAoCGGIQzu8HCggKAxigBhD4fQoICgMYoQYQ+H0KCgoDGJ4IEKO3lwc="},{"b64Body":"ChAKCQiJ/f6sBhC5ChIDGJ4IEgIYAxjwr7sIIgIIeDIgw4PCrsOCwrfDg8K5dEY4w4LCrkrDg8KLw4PCkMODwo46TwoDGKQIEJChDyJEqQWcuwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQfAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPQkA=","b64Record":"CiUIFiIDGKQIKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjA/qnQBXQPTs8Umv3XL78BQYJPgUpdhDHfGbK+RnC0GeFMmnWFxpraq7sLpg1mYac4aDAjF/f6sBhDkj76lAiIQCgkIif3+rAYQuQoSAxieCCogw4PCrsOCwrfDg8K5dEY4w4LCrkrDg8KLw4PCkMODwo4wwNniBjqfBQoDGKQIIoACAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAACAAAAAAAAAAgAAAAAAAAABAAAAAAASAAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEAAAACAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACjAmgwykAMKAxikCBKAAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAgAAAAAAAAAIAAAAAAAAAAQAAAAAAEgAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAACAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAAAAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAaIN3yUq0b4sibacKwaPw3jaqVK6fxY8ShFij1Wk31I7PvGiAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEHhogAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABB8iIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD0JAUhcKCQoCGGIQgLPFDQoKCgMYnggQ/7LFDQ=="},{"b64Body":"ChAKCQiK/f6sBhC7ChIDGJ4IEgIYAxjwr7sIIgIIeDIgw4PCrsOCwrfDg8K5dEY4w4LCrkrDg8KLw4PCkMODwo46TwoDGKQIEJChDyJEqQWcuwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAehIA=","b64Record":"CiUIFiIDGKQIKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjBQotcz36eIoIa7Iox+C0oZ2Fs2QEZ9fFS155GqGL1bzyQtqufL4WCq5hs73GXFI2AaCwjG/f6sBhDri9MtIhAKCQiK/f6sBhC7ChIDGJ4IKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjjDA2eIGOp8FCgMYpAgigAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAAAAAEAAAAAAAAAAABIAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAQAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAAACAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKMCaDDKQAwoDGKQIEoACAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgAAAABAAAAAAAAAAAASAAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEAAEAAAAAAAAAAAAAAAAAAAAAAAAAAEAAAAAAAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABog3fJSrRviyJtpwrBo/DeNqpUrp/FjxKEWKPVaTfUjs+8aIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQeGiAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEICIgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAehIBSFwoJCgIYYhCAs8UNCgoKAxieCBD/ssUN"},{"b64Body":"ChAKCQiK/f6sBhC9ChIDGKAIEgIYAxjwr7sIIgIIeDIgw4PCrsOCwrfDg8K5dEY4w4LCrkrDg8KLw4PCkMODwo46TwoDGKQIEJChDyJEqQWcuwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQhAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHoSA=","b64Record":"CiUIFiIDGKQIKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjAECVw/Y8fyIsuFxw6/P/CITRzB+QT3yDC88PhDjxi5yhHpGqt6yLmhPdr7wJTgOI0aDAjG/f6sBhCKzsSSAiIQCgkIiv3+rAYQvQoSAxigCCogw4PCrsOCwrfDg8K5dEY4w4LCrkrDg8KLw4PCkMODwo4wwNniBjqfBQoDGKQIIoACAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAAASAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEAAEAAAAAAAAAAAAAAAAIAAAAAAAAAEAAAAAAAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAACjAmgwykAMKAxikCBKAAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAEgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAABAAAAAAAAAAAAAAAACAAAAAAAAABAAAAAAAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAaIN3yUq0b4sibacKwaPw3jaqVK6fxY8ShFij1Wk31I7PvGiAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEIBogAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABCEiIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB6EgUhcKCQoCGGIQgLPFDQoKCgMYoAgQ/7LFDQ=="},{"b64Body":"ChAKCQiL/f6sBhDLChIDGJ8IEgIYAxjwr7sIIgIIeDIgw4PCrsOCwrfDg8K5dEY4w4LCrkrDg8KLw4PCkMODwo46TwoDGKQIEJChDyJECV6nswAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQiAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADDUA=","b64Record":"CiUIFiIDGKQIKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjD+q4+l7linqfQE1SQOVDbH4UApOYzWxzdJp/ScF7bUkzfcCWaqCBmALRm2wCTaI8EaCwjH/f6sBhDZw/g2IhAKCQiL/f6sBhDLChIDGJ8IKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjjDA2eIGOq4CCgMYpAgSIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABIoACAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACjAmgxSFwoJCgIYYhCAs8UNCgoKAxifCBD/ssUN"},{"b64Body":"ChAKCQiL/f6sBhDNChIDGKIIEgIYAxjwr7sIIgIIeDIgw4PCrsOCwrfDg8K5dEY4w4LCrkrDg8KLw4PCkMODwo46bwoDGKQIEJChDyJkI7hy3QAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQfAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABCAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGGoA==","b64Record":"CiUIFiIDGKQIKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjCeNdVySEJTf7hB2bAzWhZQniatFSRhgYnMW4s/++o0yQldY7+ahp9RzuLEh0wlCf8aDAjH/f6sBhCxwOe4AiIQCgkIi/3+rAYQzQoSAxiiCCogw4PCrsOCwrfDg8K5dEY4w4LCrkrDg8KLw4PCkMODwo4wwNniBjrBBQoDGKQIEiAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAASKAAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAgAAAAAAAAAAAAAAAQAAAAQAAAAAAEgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAABAAAAAgAAAAAAAAAAAAAAAAAAAABAAAAAAAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAowJoMMpADCgMYpAgSgAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAIAAAAAAAAAAAAAAAEAAAAEAAAAAABIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAQAAAAIAAAAAAAAAAAAAAAAAAAAAQAAAAAACAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGiDd8lKtG+LIm2nCsGj8N42qlSun8WPEoRYo9VpN9SOz7xogAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABB8aIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQgIiAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGGoFIXCgkKAhhiEICzxQ0KCgoDGKIIEP+yxQ0="}]},"erc721TokenUriAndHtsNftInfoTreatNonUtf8BytesDifferently":{"placeholderNum":1061,"encodedItems":[{"b64Body":"Cg8KCQiQ/f6sBhD1ChICGAISAhgDGP7LzSwiAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjooBjgESCwjMy9mwBhCEt4ppGm0KIhIgly7NuGwER017X6MxqiUDRQNDXSq9atLlYCPOnfcuOS4KIzohA1k45xDmSPddQzc+RODI4OdNkpzM60m6lPzy0vtbTyucCiISIKmVMaFp8Xjyc6gmFZISJSKMHP9VhqGQrNAYPdLrxjDgIgxIZWxsbyBXb3JsZCEqADIA","b64Record":"CiUIFhoDGKYIKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjBczXJqVX+x10LJMChWJKaHiqDbRTlI7qgg2EDt+29LD5zhGZfgrgtR7QGe0fT/wwcaDAjM/f6sBhDS1qqDASIPCgkIkP3+rAYQ9QoSAhgCKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjlIA"},{"b64Body":"Cg8KCQiQ/f6sBhD5ChICGAISAhgDGIydjj4iAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjpoBiCAKAximCCKAIDYwODA2MDQwNTIzNDgwMTU2MTAwMTA1NzYwMDA4MGZkNWI1MDYxMTI5MTgwNjEwMDIwNjAwMDM5NjAwMGYzZmU2MDgwNjA0MDUyMzQ4MDE1NjEwMDEwNTc2MDAwODBmZDViNTA2MDA0MzYxMDYxMDAzNjU3NjAwMDM1NjBlMDFjODA2M2JhNDkxNjU1MTQ2MTAwM2I1NzgwNjNlNTMxMGI5MTE0NjEwMDZiNTc1YjYwMDA4MGZkNWI2MTAwNTU2MDA0ODAzNjAzODEwMTkwNjEwMDUwOTE5MDYxMDJmYzU2NWI2MTAwOWI1NjViNjA0MDUxNjEwMDYyOTE5MDYxMDNjYzU2NWI2MDQwNTE4MDkxMDM5MGYzNWI2MTAwODU2MDA0ODAzNjAzODEwMTkwNjEwMDgwOTE5MDYxMDJmYzU2NWI2MTAxMjQ1NjViNjA0MDUxNjEwMDkyOTE5MDYxMDNjYzU2NWI2MDQwNTE4MDkxMDM5MGYzNWI2MDYwODI3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNjYzYzg3YjU2ZGQ4MzYwNDA1MTgyNjNmZmZmZmZmZjE2NjBlMDFiODE1MjYwMDQwMTYxMDBkNjkxOTA2MTAzZmQ1NjViNjAwMDYwNDA1MTgwODMwMzgxODY1YWZhMTU4MDE1NjEwMGYzNTczZDYwMDA4MDNlM2Q2MDAwZmQ1YjUwNTA1MDUwNjA0MDUxM2Q2MDAwODIzZTNkNjAxZjE5NjAxZjgyMDExNjgyMDE4MDYwNDA1MjUwODEwMTkwNjEwMTFjOTE5MDYxMDUzZTU2NWI5MDUwOTI5MTUwNTA1NjViNjA2MDYwMDA4MDYxMDE2NzczZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjE2NjMyODdlMWRhODYwZTAxYjg2ODY2MDQwNTE2MDI0MDE2MTAxNWQ5MjkxOTA2MTA1YjI1NjViNjA0MDUxNjAyMDgxODMwMzAzODE1MjkwNjA0MDUyOTA3YmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmMTkxNjYwMjA4MjAxODA1MTdiZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmY4MzgxODMxNjE3ODM1MjUwNTA1MDUwNjA0MDUxNjEwMWM3OTE5MDYxMDYxNzU2NWI2MDAwNjA0MDUxODA4MzAzODE2MDAwODY1YWYxOTE1MDUwM2Q4MDYwMDA4MTE0NjEwMjA0NTc2MDQwNTE5MTUwNjAxZjE5NjAzZjNkMDExNjgyMDE2MDQwNTIzZDgyNTIzZDYwMDA2MDIwODQwMTNlNjEwMjA5NTY1YjYwNjA5MTUwNWI1MDkxNTA5MTUwODE2MTAyMTg1NzYwMDA4MGZkNWI2MDAwODA4MjgwNjAyMDAxOTA1MTgxMDE5MDYxMDIyZjkxOTA2MTExZmY1NjViOTE1MDkxNTA2MDE2ODI2MDAzMGIxNDYxMDI0MzU3NjAwMDgwZmQ1YjgwNjA4MDAxNTE5NDUwNTA1MDUwNTA5MjkxNTA1MDU2NWI2MDAwNjA0MDUxOTA1MDkwNTY1YjYwMDA4MGZkNWI2MDAwODBmZDViNjAwMDczZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjgyMTY5MDUwOTE5MDUwNTY1YjYwMDA2MTAyOTM4MjYxMDI2ODU2NWI5MDUwOTE5MDUwNTY1YjYxMDJhMzgxNjEwMjg4NTY1YjgxMTQ2MTAyYWU1NzYwMDA4MGZkNWI1MDU2NWI2MDAwODEzNTkwNTA2MTAyYzA4MTYxMDI5YTU2NWI5MjkxNTA1MDU2NWI2MDAwODE5MDUwOTE5MDUwNTY1YjYxMDJkOTgxNjEwMmM2NTY1YjgxMTQ2MTAyZTQ1NzYwMDA4MGZkNWI1MDU2NWI2MDAwODEzNTkwNTA2MTAyZjY4MTYxMDJkMDU2NWI5MjkxNTA1MDU2NWI2MDAwODA2MDQwODM4NTAzMTIxNTYxMDMxMzU3NjEwMzEyNjEwMjVlNTY1YjViNjAwMDYxMDMyMTg1ODI4NjAxNjEwMmIxNTY1YjkyNTA1MDYwMjA2MTAzMzI4NTgyODYwMTYxMDJlNzU2NWI5MTUwNTA5MjUwOTI5MDUwNTY1YjYwMDA4MTUxOTA1MDkxOTA1MDU2NWI2MDAwODI4MjUyNjAyMDgyMDE5MDUwOTI5MTUwNTA1NjViNjAwMDViODM4MTEwMTU2MTAzNzY1NzgwODIwMTUxODE4NDAxNTI2MDIwODEwMTkwNTA2MTAzNWI1NjViNjAwMDg0ODQwMTUyNTA1MDUwNTA1NjViNjAwMDYwMWYxOTYwMWY4MzAxMTY5MDUwOTE5MDUwNTY1YjYwMDA2MTAzOWU4MjYxMDMzYzU2NWI2MTAzYTg4MTg1NjEwMzQ3NTY1YjkzNTA2MTAzYjg4MTg1NjAyMDg2MDE2MTAzNTg1NjViNjEwM2MxODE2MTAzODI1NjViODQwMTkxNTA1MDkyOTE1MDUwNTY1YjYwMDA2MDIwODIwMTkwNTA4MTgxMDM2MDAwODMwMTUyNjEwM2U2ODE4NDYxMDM5MzU2NWI5MDUwOTI5MTUwNTA1NjViNjEwM2Y3ODE2MTAyYzY1NjViODI1MjUwNTA1NjViNjAwMDYwMjA4MjAxOTA1MDYxMDQxMjYwMDA4MzAxODQ2MTAzZWU1NjViOTI5MTUwNTA1NjViNjAwMDgwZmQ1YjYwMDA4MGZkNWI3ZjRlNDg3YjcxMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDA2MDAwNTI2MDQxNjAwNDUyNjAyNDYwMDBmZDViNjEwNDVhODI2MTAzODI1NjViODEwMTgxODExMDY3ZmZmZmZmZmZmZmZmZmZmZjgyMTExNzE1NjEwNDc5NTc2MTA0Nzg2MTA0MjI1NjViNWI4MDYwNDA1MjUwNTA1MDU2NWI2MDAwNjEwNDhjNjEwMjU0NTY1YjkwNTA2MTA0OTg4MjgyNjEwNDUxNTY1YjkxOTA1MDU2NWI2MDAwNjdmZmZmZmZmZmZmZmZmZmZmODIxMTE1NjEwNGI4NTc2MTA0Yjc2MTA0MjI1NjViNWI2MTA0YzE4MjYxMDM4MjU2NWI5MDUwNjAyMDgxMDE5MDUwOTE5MDUwNTY1YjYwMDA2MTA0ZTE2MTA0ZGM4NDYxMDQ5ZDU2NWI2MTA0ODI1NjViOTA1MDgyODE1MjYwMjA4MTAxODQ4NDg0MDExMTE1NjEwNGZkNTc2MTA0ZmM2MTA0MWQ1NjViNWI2MTA1MDg4NDgyODU2MTAzNTg1NjViNTA5MzkyNTA1MDUwNTY1YjYwMDA4MjYwMWY4MzAxMTI2MTA1MjU1NzYxMDUyNDYxMDQxODU2NWI1YjgxNTE2MTA1MzU4NDgyNjAyMDg2MDE2MTA0Y2U1NjViOTE1MDUwOTI5MTUwNTA1NjViNjAwMDYwMjA4Mjg0MDMxMjE1NjEwNTU0NTc2MTA1NTM2MTAyNWU1NjViNWI2MDAwODIwMTUxNjdmZmZmZmZmZmZmZmZmZmZmODExMTE1NjEwNTcyNTc2MTA1NzE2MTAyNjM1NjViNWI2MTA1N2U4NDgyODUwMTYxMDUxMDU2NWI5MTUwNTA5MjkxNTA1MDU2NWI2MTA1OTA4MTYxMDI4ODU2NWI4MjUyNTA1MDU2NWI2MDAwODE2MDA3MGI5MDUwOTE5MDUwNTY1YjYxMDVhYzgxNjEwNTk2NTY1YjgyNTI1MDUwNTY1YjYwMDA2MDQwODIwMTkwNTA2MTA1Yzc2MDAwODMwMTg1NjEwNTg3NTY1YjYxMDVkNDYwMjA4MzAxODQ2MTA1YTM1NjViOTM5MjUwNTA1MDU2NWI2MDAwODE5MDUwOTI5MTUwNTA1NjViNjAwMDYxMDVmMTgyNjEwMzNjNTY1YjYxMDVmYjgxODU2MTA1ZGI1NjViOTM1MDYxMDYwYjgxODU2MDIwODYwMTYxMDM1ODU2NWI4MDg0MDE5MTUwNTA5MjkxNTA1MDU2NWI2MDAwNjEwNjIzODI4NDYxMDVlNjU2NWI5MTUwODE5MDUwOTI5MTUwNTA1NjViNjAwMDgxNjAwMzBiOTA1MDkxOTA1MDU2NWI2MTA2NDQ4MTYxMDYyZTU2NWI4MTE0NjEwNjRmNTc2MDAwODBmZDViNTA1NjViNjAwMDgxNTE5MDUwNjEwNjYxODE2MTA2M2I1NjViOTI5MTUwNTA1NjViNjAwMDgwZmQ1YjYwMDA4MGZkNWI2MDAwODE1MTkwNTA2MTA2ODA4MTYxMDI5YTU2NWI5MjkxNTA1MDU2NWI2MDAwODExNTE1OTA1MDkxOTA1MDU2NWI2MTA2OWI4MTYxMDY4NjU2NWI4MTE0NjEwNmE2NTc2MDAwODBmZDViNTA1NjViNjAwMDgxNTE5MDUwNjEwNmI4ODE2MTA2OTI1NjViOTI5MTUwNTA1NjViNjAwMDYzZmZmZmZmZmY4MjE2OTA1MDkxOTA1MDU2NWI2MTA2ZDc4MTYxMDZiZTU2NWI4MTE0NjEwNmUyNTc2MDAwODBmZDViNTA1NjViNjAwMDgxNTE5MDUwNjEwNmY0ODE2MTA2Y2U1NjViOTI5MTUwNTA1NjViNjAwMDY3ZmZmZmZmZmZmZmZmZmZmZjgyMTExNTYxMDcxNTU3NjEwNzE0NjEwNDIyNTY1YjViNjAyMDgyMDI5MDUwNjAyMDgxMDE5MDUwOTE5MDUwNTY1YjYwMDA4MGZkNWI2MDAwODE1MTkwNTA2MTA3M2E4MTYxMDJkMDU2NWI5MjkxNTA1MDU2NWI2MDAwNjdmZmZmZmZmZmZmZmZmZmZmODIxMTE1NjEwNzViNTc2MTA3NWE2MTA0MjI1NjViNWI2MTA3NjQ4MjYxMDM4MjU2NWI5MDUwNjAyMDgxMDE5MDUwOTE5MDUwNTY1YjYwMDA2MTA3ODQ2MTA3N2Y4NDYxMDc0MDU2NWI2MTA0ODI1NjViOTA1MDgyODE1MjYwMjA4MTAxODQ4NDg0MDExMTE1NjEwN2EwNTc2MTA3OWY2MTA0MWQ1NjViNWI2MTA3YWI4NDgyODU2MTAzNTg1NjViNTA5MzkyNTA1MDUwNTY1YjYwMDA4MjYwMWY4MzAxMTI2MTA3Yzg1NzYxMDdjNzYxMDQxODU2NWI1YjgxNTE2MTA3ZDg4NDgyNjAyMDg2MDE2MTA3NzE1NjViOTE1MDUwOTI5MTUwNTA=","b64Record":"CiAIFiocCgwIARAMGgYIgK6ZpA8SDAgBEA8aBgiArpmkDxIwlSoiTd7tSz/lhaWGSb1qKRwyKmpkFxDe78bLepGZumXgqpr/SZKVmxdyIFoyXYBGGgwIzP3+rAYQxeXC6AIiDwoJCJD9/qwGEPkKEgIYAiogw4PCrsOCwrfDg8K5dEY4w4LCrkrDg8KLw4PCkMODwo5SAA=="},{"b64Body":"Cg8KCQiR/f6sBhD/ChICGAISAhgDGPjR6j4iAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjoIBiCASAximCCKAIDU2NWI2MDAwNjBhMDgyODQwMzEyMTU2MTA3Zjc1NzYxMDdmNjYxMDY2NzU2NWI1YjYxMDgwMTYwYTA2MTA0ODI1NjViOTA1MDYwMDA2MTA4MTE4NDgyODUwMTYxMDZhOTU2NWI2MDAwODMwMTUyNTA2MDIwNjEwODI1ODQ4Mjg1MDE2MTA2NzE1NjViNjAyMDgzMDE1MjUwNjA0MDgyMDE1MTY3ZmZmZmZmZmZmZmZmZmZmZjgxMTExNTYxMDg0OTU3NjEwODQ4NjEwNjZjNTY1YjViNjEwODU1ODQ4Mjg1MDE2MTA3YjM1NjViNjA0MDgzMDE1MjUwNjA2MDgyMDE1MTY3ZmZmZmZmZmZmZmZmZmZmZjgxMTExNTYxMDg3OTU3NjEwODc4NjEwNjZjNTY1YjViNjEwODg1ODQ4Mjg1MDE2MTA3YjM1NjViNjA2MDgzMDE1MjUwNjA4MDYxMDg5OTg0ODI4NTAxNjEwNjcxNTY1YjYwODA4MzAxNTI1MDkyOTE1MDUwNTY1YjYwMDA2MDQwODI4NDAzMTIxNTYxMDhiYjU3NjEwOGJhNjEwNjY3NTY1YjViNjEwOGM1NjA0MDYxMDQ4MjU2NWI5MDUwNjAwMDYxMDhkNTg0ODI4NTAxNjEwNzJiNTY1YjYwMDA4MzAxNTI1MDYwMjA4MjAxNTE2N2ZmZmZmZmZmZmZmZmZmZmY4MTExMTU2MTA4Zjk1NzYxMDhmODYxMDY2YzU2NWI1YjYxMDkwNTg0ODI4NTAxNjEwN2UxNTY1YjYwMjA4MzAxNTI1MDkyOTE1MDUwNTY1YjYwMDA2MTA5MjQ2MTA5MWY4NDYxMDZmYTU2NWI2MTA0ODI1NjViOTA1MDgwODM4MjUyNjAyMDgyMDE5MDUwNjAyMDg0MDI4MzAxODU4MTExMTU2MTA5NDc1NzYxMDk0NjYxMDcyNjU2NWI1YjgzNWI4MTgxMTAxNTYxMDk4ZTU3ODA1MTY3ZmZmZmZmZmZmZmZmZmZmZjgxMTExNTYxMDk2YzU3NjEwOTZiNjEwNDE4NTY1YjViODA4NjAxNjEwOTc5ODk4MjYxMDhhNTU2NWI4NTUyNjAyMDg1MDE5NDUwNTA1MDYwMjA4MTAxOTA1MDYxMDk0OTU2NWI1MDUwNTA5MzkyNTA1MDUwNTY1YjYwMDA4MjYwMWY4MzAxMTI2MTA5YWQ1NzYxMDlhYzYxMDQxODU2NWI1YjgxNTE2MTA5YmQ4NDgyNjAyMDg2MDE2MTA5MTE1NjViOTE1MDUwOTI5MTUwNTA1NjViNjAwMDYwNjA4Mjg0MDMxMjE1NjEwOWRjNTc2MTA5ZGI2MTA2Njc1NjViNWI2MTA5ZTY2MDYwNjEwNDgyNTY1YjkwNTA2MDAwNjEwOWY2ODQ4Mjg1MDE2MTA2ZTU1NjViNjAwMDgzMDE1MjUwNjAyMDYxMGEwYTg0ODI4NTAxNjEwNjcxNTY1YjYwMjA4MzAxNTI1MDYwNDA2MTBhMWU4NDgyODUwMTYxMDZlNTU2NWI2MDQwODMwMTUyNTA5MjkxNTA1MDU2NWI2MDAwNjEwMTYwODI4NDAzMTIxNTYxMGE0MTU3NjEwYTQwNjEwNjY3NTY1YjViNjEwYTRjNjEwMTIwNjEwNDgyNTY1YjkwNTA2MDAwODIwMTUxNjdmZmZmZmZmZmZmZmZmZmZmODExMTE1NjEwYTZjNTc2MTBhNmI2MTA2NmM1NjViNWI2MTBhNzg4NDgyODUwMTYxMDUxMDU2NWI2MDAwODMwMTUyNTA2MDIwODIwMTUxNjdmZmZmZmZmZmZmZmZmZmZmODExMTE1NjEwYTljNTc2MTBhOWI2MTA2NmM1NjViNWI2MTBhYTg4NDgyODUwMTYxMDUxMDU2NWI2MDIwODMwMTUyNTA2MDQwNjEwYWJjODQ4Mjg1MDE2MTA2NzE1NjViNjA0MDgzMDE1MjUwNjA2MDgyMDE1MTY3ZmZmZmZmZmZmZmZmZmZmZjgxMTExNTYxMGFlMDU3NjEwYWRmNjEwNjZjNTY1YjViNjEwYWVjODQ4Mjg1MDE2MTA1MTA1NjViNjA2MDgzMDE1MjUwNjA4MDYxMGIwMDg0ODI4NTAxNjEwNmE5NTY1YjYwODA4MzAxNTI1MDYwYTA2MTBiMTQ4NDgyODUwMTYxMDZlNTU2NWI2MGEwODMwMTUyNTA2MGMwNjEwYjI4ODQ4Mjg1MDE2MTA2YTk1NjViNjBjMDgzMDE1MjUwNjBlMDgyMDE1MTY3ZmZmZmZmZmZmZmZmZmZmZjgxMTExNTYxMGI0YzU3NjEwYjRiNjEwNjZjNTY1YjViNjEwYjU4ODQ4Mjg1MDE2MTA5OTg1NjViNjBlMDgzMDE1MjUwNjEwMTAwNjEwYjZkODQ4Mjg1MDE2MTA5YzY1NjViNjEwMTAwODMwMTUyNTA5MjkxNTA1MDU2NWI2MTBiODM4MTYxMDU5NjU2NWI4MTE0NjEwYjhlNTc2MDAwODBmZDViNTA1NjViNjAwMDgxNTE5MDUwNjEwYmEwODE2MTBiN2E1NjViOTI5MTUwNTA1NjViNjAwMDY3ZmZmZmZmZmZmZmZmZmZmZjgyMTExNTYxMGJjMTU3NjEwYmMwNjEwNDIyNTY1YjViNjAyMDgyMDI5MDUwNjAyMDgxMDE5MDUwOTE5MDUwNTY1YjYwMDA2MGEwODI4NDAzMTIxNTYxMGJlODU3NjEwYmU3NjEwNjY3NTY1YjViNjEwYmYyNjBhMDYxMDQ4MjU2NWI5MDUwNjAwMDYxMGMwMjg0ODI4NTAxNjEwNmU1NTY1YjYwMDA4MzAxNTI1MDYwMjA2MTBjMTY4NDgyODUwMTYxMDY3MTU2NWI2MDIwODMwMTUyNTA2MDQwNjEwYzJhODQ4Mjg1MDE2MTA2YTk1NjViNjA0MDgzMDE1MjUwNjA2MDYxMGMzZTg0ODI4NTAxNjEwNmE5NTY1YjYwNjA4MzAxNTI1MDYwODA2MTBjNTI4NDgyODUwMTYxMDY3MTU2NWI2MDgwODMwMTUyNTA5MjkxNTA1MDU2NWI2MDAwNjEwYzcxNjEwYzZjODQ2MTBiYTY1NjViNjEwNDgyNTY1YjkwNTA4MDgzODI1MjYwMjA4MjAxOTA1MDYwYTA4NDAyODMwMTg1ODExMTE1NjEwYzk0NTc2MTBjOTM2MTA3MjY1NjViNWI4MzViODE4MTEwMTU2MTBjYmQ1NzgwNjEwY2E5ODg4MjYxMGJkMjU2NWI4NDUyNjAyMDg0MDE5MzUwNTA2MGEwODEwMTkwNTA2MTBjOTY1NjViNTA1MDUwOTM5MjUwNTA1MDU2NWI2MDAwODI2MDFmODMwMTEyNjEwY2RjNTc2MTBjZGI2MTA0MTg1NjViNWI4MTUxNjEwY2VjODQ4MjYwMjA4NjAxNjEwYzVlNTY1YjkxNTA1MDkyOTE1MDUwNTY1YjYwMDA2N2ZmZmZmZmZmZmZmZmZmZmY4MjExMTU2MTBkMTA1NzYxMGQwZjYxMDQyMjU2NWI1YjYwMjA4MjAyOTA1MDYwMjA4MTAxOTA1MDkxOTA1MDU2NWI2MDAwNjBjMDgyODQwMzEyMTU2MTBkMzc1NzYxMGQzNjYxMDY2NzU2NWI1YjYxMGQ0MTYwYzA2MTA0ODI1NjViOTA1MDYwMDA2MTBkNTE4NDgyODUwMTYxMDZlNTU2NWI2MDAwODMwMTUyNTA2MDIwNjEwZDY1ODQ4Mjg1MDE2MTA2ZTU1NjViNjAyMDgzMDE1MjUwNjA0MDYxMGQ3OTg0ODI4NTAxNjEwNmU1NTY1YjYwNDA4MzAxNTI1MDYwNjA2MTBkOGQ4NDgyODUwMTYxMDZlNTU2NWI2MDYwODMwMTUyNTA2MDgwNjEwZGExODQ4Mjg1MDE2MTA2YTk1NjViNjA4MDgzMDE1MjUwNjBhMDYxMGRiNTg0ODI4NTAxNjEwNjcxNTY1YjYwYTA4MzAxNTI1MDkyOTE1MDUwNTY1YjYwMDA2MTBkZDQ2MTBkY2Y4NDYxMGNmNTU2NWI2MTA0ODI1NjViOTA1MDgwODM4MjUyNjAyMDgyMDE5MDUwNjBjMDg0MDI4MzAxODU4MTExMTU2MTBkZjc1NzYxMGRmNjYxMDcyNjU2NWI1YjgzNWI4MTgxMTAxNTYxMGUyMDU3ODA2MTBlMGM4ODgyNjEwZDIxNTY1Yjg0NTI2MDIwODQwMTkzNTA1MDYwYzA4MTAxOTA1MDYxMGRmOTU2NWI1MDUwNTA5MzkyNTA1MDUwNTY1YjYwMDA4MjYwMWY4MzAxMTI2MTBlM2Y1NzYxMGUzZTYxMDQxODU2NWI1YjgxNTE2MTBlNGY4NDgyNjAyMDg2MDE2MTBkYzE1NjViOTE1MDUwOTI5MTUwNTA1NjViNjAwMDY3ZmZmZmZmZmZmZmZmZmZmZjgyMTExNTYxMGU3MzU3NjEwZTcyNjEwNDIyNTY1YjViNjAyMDgyMDI5MDUwNjAyMDgxMDE5MDUwOTE5MDUwNTY1YjYwMDA2MGMwODI4NDAzMTIxNTYxMGU5YTU3NjEwZTk5NjEwNjY3NTY1YjViNjEwZWE0NjBjMDYxMDQ4MjU2NWI5MDUwNjAwMDYxMGViNDg0ODI4NTAxNjEwNmU1NTY1YjYwMDA4MzAxNTI1MDYwMjA2MTBlYzg4NDgyODUwMTYxMDZlNTU2NWI2MDIwODMwMTUyNTA2MDQwNjEwZWRjODQ4Mjg1MDE2MTA2ZTU1NjViNjA0MDgzMDE1MjUwNjA2MDYxMGVmMDg0ODI4NTAxNjEwNjcxNTY1YjYwNjA4MzAxNTI1MDYwODA2MTBmMDQ4NDgyODUwMTYxMDZhOTU2NWI2MDgwODMwMTUyNTA2MGEwNjEwZjE4ODQ4Mjg1MDE2MTA2NzE1NjViNjBhMDgzMDE1MjUwOTI5MTUwNTA1NjViNjAwMDYxMGYzNzYxMGYzMjg0NjEwZTU4NTY1YjYxMDQ4MjU2NWI5MDUwODA4MzgyNTI2MDIwODIwMTkwNTA2MGMwODQwMjgzMDE4NTgxMTExNTYxMGY1YTU3NjEwZjU5NjEwNzI2NTY1YjViODM1YjgxODExMDE1NjEwZjgzNTc4MDYxMGY2Zjg4ODI2MTBlODQ1NjViODQ1MjYwMjA4NDAxOTM1MDUwNjBjMDgxMDE5MDUwNjEwZjVjNTY1YjUwNTA1MDkzOTI1MDUwNTA1NjViNjAwMDgyNjAxZjgzMDExMjYxMGZhMjU3NjEwZmExNjEwNDE4NTY1YjViODE1MTYxMGZiMjg0ODI2MDIwODYwMTYxMGYyNDU2NWI5MTUwNTA5MjkxNTA1MDU2NWI2MDAwNjEwMTIwODI4NDAzMTIxNTYxMGZkMjU3NjEwZmQxNjEwNjY3NTY1YjViNjEwZmRkNjEwMTIwNjEwNDgyNTY1YjkwNTA=","b64Record":"CiAIFiocCgwIARAMGgYIgK6ZpA8SDAgBEA8aBgiArpmkDxIwOvp+gjpl7Cw8vRSswQ4pTSzkpjlj/lEQ21TBud9vdxS2M/Tx2BILAd/heDXbJsKIGgwIzf3+rAYQl5eJjQEiDwoJCJH9/qwGEP8KEgIYAiogw4PCrsOCwrfDg8K5dEY4w4LCrkrDg8KLw4PCkMODwo5SAA=="},{"b64Body":"Cg8KCQiR/f6sBhCFCxICGAISAhgDGIDJ9DIiAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjoIB6goSAximCCLiCjYwMDA4MjAxNTE2N2ZmZmZmZmZmZmZmZmZmZmY4MTExMTU2MTBmZmQ1NzYxMGZmYzYxMDY2YzU2NWI1YjYxMTAwOTg0ODI4NTAxNjEwYTJhNTY1YjYwMDA4MzAxNTI1MDYwMjA2MTEwMWQ4NDgyODUwMTYxMGI5MTU2NWI2MDIwODMwMTUyNTA2MDQwNjExMDMxODQ4Mjg1MDE2MTA2YTk1NjViNjA0MDgzMDE1MjUwNjA2MDYxMTA0NTg0ODI4NTAxNjEwNmE5NTY1YjYwNjA4MzAxNTI1MDYwODA2MTEwNTk4NDgyODUwMTYxMDZhOTU2NWI2MDgwODMwMTUyNTA2MGEwODIwMTUxNjdmZmZmZmZmZmZmZmZmZmZmODExMTE1NjExMDdkNTc2MTEwN2M2MTA2NmM1NjViNWI2MTEwODk4NDgyODUwMTYxMGNjNzU2NWI2MGEwODMwMTUyNTA2MGMwODIwMTUxNjdmZmZmZmZmZmZmZmZmZmZmODExMTE1NjExMGFkNTc2MTEwYWM2MTA2NmM1NjViNWI2MTEwYjk4NDgyODUwMTYxMGUyYTU2NWI2MGMwODMwMTUyNTA2MGUwODIwMTUxNjdmZmZmZmZmZmZmZmZmZmZmODExMTE1NjExMGRkNTc2MTEwZGM2MTA2NmM1NjViNWI2MTEwZTk4NDgyODUwMTYxMGY4ZDU2NWI2MGUwODMwMTUyNTA2MTAxMDA4MjAxNTE2N2ZmZmZmZmZmZmZmZmZmZmY4MTExMTU2MTExMGU1NzYxMTEwZDYxMDY2YzU2NWI1YjYxMTExYTg0ODI4NTAxNjEwNTEwNTY1YjYxMDEwMDgzMDE1MjUwOTI5MTUwNTA1NjViNjAwMDYwYzA4Mjg0MDMxMjE1NjExMTNkNTc2MTExM2M2MTA2Njc1NjViNWI2MTExNDc2MGMwNjEwNDgyNTY1YjkwNTA2MDAwODIwMTUxNjdmZmZmZmZmZmZmZmZmZmZmODExMTE1NjExMTY3NTc2MTExNjY2MTA2NmM1NjViNWI2MTExNzM4NDgyODUwMTYxMGZiYjU2NWI2MDAwODMwMTUyNTA2MDIwNjExMTg3ODQ4Mjg1MDE2MTBiOTE1NjViNjAyMDgzMDE1MjUwNjA0MDYxMTE5Yjg0ODI4NTAxNjEwNjcxNTY1YjYwNDA4MzAxNTI1MDYwNjA2MTExYWY4NDgyODUwMTYxMGI5MTU2NWI2MDYwODMwMTUyNTA2MDgwODIwMTUxNjdmZmZmZmZmZmZmZmZmZmZmODExMTE1NjExMWQzNTc2MTExZDI2MTA2NmM1NjViNWI2MTExZGY4NDgyODUwMTYxMDdiMzU2NWI2MDgwODMwMTUyNTA2MGEwNjExMWYzODQ4Mjg1MDE2MTA2NzE1NjViNjBhMDgzMDE1MjUwOTI5MTUwNTA1NjViNjAwMDgwNjA0MDgzODUwMzEyMTU2MTEyMTY1NzYxMTIxNTYxMDI1ZTU2NWI1YjYwMDA2MTEyMjQ4NTgyODYwMTYxMDY1MjU2NWI5MjUwNTA2MDIwODMwMTUxNjdmZmZmZmZmZmZmZmZmZmZmODExMTE1NjExMjQ1NTc2MTEyNDQ2MTAyNjM1NjViNWI2MTEyNTE4NTgyODYwMTYxMTEyNzU2NWI5MTUwNTA5MjUwOTI5MDUwNTZmZWEyNjQ2OTcwNjY3MzU4MjIxMjIwMGJiNWI4OWQyNGFlNjIyMzdiYTJiNTEyZjc0YTg5NTdkY2JlNDcwOTVhMDZiNTJhMTA4YjhhNWJhMmQ5Nzk3YjY0NzM2ZjZjNjM0MzAwMDgxMDAwMzM=","b64Record":"CiAIFiocCgwIARAMGgYIgK6ZpA8SDAgBEA8aBgiArpmkDxIwMZ0a2VrDnCuoMkSVFwMJP7KqPCNeSQX5IxQ1+uUv3V+9iXdvcF3K3UdFIVgmRSAGGgwIzf3+rAYQqd/i8QIiDwoJCJH9/qwGEIULEgIYAiogw4PCrsOCwrfDg8K5dEY4w4LCrkrDg8KLw4PCkMODwo5SAA=="},{"b64Body":"Cg8KCQiS/f6sBhCHCxICGAISAhgDGJb7rp0CIgIIeDIgw4PCrsOCwrfDg8K5dEY4w4LCrkrDg8KLw4PCkMODwo5CRQoDGKYIGiISIPcgremqoxsIoP1ObBHii/srG6zNmF35TAhoDa6MSU0cIJChD0IFCIDO2gNSAFoAagtjZWxsYXIgZG9vcg==","b64Record":"CiUIFiIDGKcIKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjB3Xtjt85DOKqaeOlD4Ro8e8rq5cJnUgyFCNpB8QN/1J160hz4PWnUkrxyj2eLSx+gaDAjO/f6sBhCkpbmWASIPCgkIkv3+rAYQhwsSAhgCKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjjDA2eIGQsYnCgMYpwgSkSVggGBAUjSAFWEAEFdgAID9W1BgBDYQYQA2V2AANWDgHIBjukkWVRRhADtXgGPlMQuRFGEAa1dbYACA/VthAFVgBIA2A4EBkGEAUJGQYQL8VlthAJtWW2BAUWEAYpGQYQPMVltgQFGAkQOQ81thAIVgBIA2A4EBkGEAgJGQYQL8VlthASRWW2BAUWEAkpGQYQPMVltgQFGAkQOQ81tgYIJz//////////////////////////8WY8h7Vt2DYEBRgmP/////FmDgG4FSYAQBYQDWkZBhA/1WW2AAYEBRgIMDgYZa+hWAFWEA81c9YACAPj1gAP1bUFBQUGBAUT1gAII+PWAfGWAfggEWggGAYEBSUIEBkGEBHJGQYQU+VluQUJKRUFBWW2BgYACAYQFnc///////////////////////////FmMofh2oYOAbhoZgQFFgJAFhAV2SkZBhBbJWW2BAUWAggYMDA4FSkGBAUpB7/////////////////////////////////////xkWYCCCAYBRe/////////////////////////////////////+DgYMWF4NSUFBQUGBAUWEBx5GQYQYXVltgAGBAUYCDA4FgAIZa8ZFQUD2AYACBFGECBFdgQFGRUGAfGWA/PQEWggFgQFI9glI9YABgIIQBPmECCVZbYGCRUFtQkVCRUIFhAhhXYACA/VtgAICCgGAgAZBRgQGQYQIvkZBhEf9WW5FQkVBgFoJgAwsUYQJDV2AAgP1bgGCAAVGUUFBQUFCSkVBQVltgAGBAUZBQkFZbYACA/VtgAID9W2AAc///////////////////////////ghaQUJGQUFZbYABhApOCYQJoVluQUJGQUFZbYQKjgWECiFZbgRRhAq5XYACA/VtQVltgAIE1kFBhAsCBYQKaVluSkVBQVltgAIGQUJGQUFZbYQLZgWECxlZbgRRhAuRXYACA/VtQVltgAIE1kFBhAvaBYQLQVluSkVBQVltgAIBgQIOFAxIVYQMTV2EDEmECXlZbW2AAYQMhhYKGAWECsVZbklBQYCBhAzKFgoYBYQLnVluRUFCSUJKQUFZbYACBUZBQkZBQVltgAIKCUmAgggGQUJKRUFBWW2AAW4OBEBVhA3ZXgIIBUYGEAVJgIIEBkFBhA1tWW2AAhIQBUlBQUFBWW2AAYB8ZYB+DARaQUJGQUFZbYABhA56CYQM8VlthA6iBhWEDR1Zbk1BhA7iBhWAghgFhA1hWW2EDwYFhA4JWW4QBkVBQkpFQUFZbYABgIIIBkFCBgQNgAIMBUmED5oGEYQOTVluQUJKRUFBWW2ED94FhAsZWW4JSUFBWW2AAYCCCAZBQYQQSYACDAYRhA+5WW5KRUFBWW2AAgP1bYACA/Vt/Tkh7cQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABgAFJgQWAEUmAkYAD9W2EEWoJhA4JWW4EBgYEQZ///////////ghEXFWEEeVdhBHhhBCJWW1uAYEBSUFBQVltgAGEEjGECVFZbkFBhBJiCgmEEUVZbkZBQVltgAGf//////////4IRFWEEuFdhBLdhBCJWW1thBMGCYQOCVluQUGAggQGQUJGQUFZbYABhBOFhBNyEYQSdVlthBIJWW5BQgoFSYCCBAYSEhAERFWEE/VdhBPxhBB1WW1thBQiEgoVhA1hWW1CTklBQUFZbYACCYB+DARJhBSVXYQUkYQQYVltbgVFhBTWEgmAghgFhBM5WW5FQUJKRUFBWW2AAYCCChAMSFWEFVFdhBVNhAl5WW1tgAIIBUWf//////////4ERFWEFcldhBXFhAmNWW1thBX6EgoUBYQUQVluRUFCSkVBQVlthBZCBYQKIVluCUlBQVltgAIFgBwuQUJGQUFZbYQWsgWEFllZbglJQUFZbYABgQIIBkFBhBcdgAIMBhWEFh1ZbYQXUYCCDAYRhBaNWW5OSUFBQVltgAIGQUJKRUFBWW2AAYQXxgmEDPFZbYQX7gYVhBdtWW5NQYQYLgYVgIIYBYQNYVluAhAGRUFCSkVBQVltgAGEGI4KEYQXmVluRUIGQUJKRUFBWW2AAgWADC5BQkZBQVlthBkSBYQYuVluBFGEGT1dgAID9W1BWW2AAgVGQUGEGYYFhBjtWW5KRUFBWW2AAgP1bYACA/VtgAIFRkFBhBoCBYQKaVluSkVBQVltgAIEVFZBQkZBQVlthBpuBYQaGVluBFGEGpldgAID9W1BWW2AAgVGQUGEGuIFhBpJWW5KRUFBWW2AAY/////+CFpBQkZBQVlthBteBYQa+VluBFGEG4ldgAID9W1BWW2AAgVGQUGEG9IFhBs5WW5KRUFBWW2AAZ///////////ghEVYQcVV2EHFGEEIlZbW2AgggKQUGAggQGQUJGQUFZbYACA/VtgAIFRkFBhBzqBYQLQVluSkVBQVltgAGf//////////4IRFWEHW1dhB1phBCJWW1thB2SCYQOCVluQUGAggQGQUJGQUFZbYABhB4RhB3+EYQdAVlthBIJWW5BQgoFSYCCBAYSEhAERFWEHoFdhB59hBB1WW1thB6uEgoVhA1hWW1CTklBQUFZbYACCYB+DARJhB8hXYQfHYQQYVltbgVFhB9iEgmAghgFhB3FWW5FQUJKRUFBWW2AAYKCChAMSFWEH91dhB/ZhBmdWW1thCAFgoGEEglZbkFBgAGEIEYSChQFhBqlWW2AAgwFSUGAgYQglhIKFAWEGcVZbYCCDAVJQYECCAVFn//////////+BERVhCElXYQhIYQZsVltbYQhVhIKFAWEHs1ZbYECDAVJQYGCCAVFn//////////+BERVhCHlXYQh4YQZsVltbYQiFhIKFAWEHs1ZbYGCDAVJQYIBhCJmEgoUBYQZxVltggIMBUlCSkVBQVltgAGBAgoQDEhVhCLtXYQi6YQZnVltbYQjFYEBhBIJWW5BQYABhCNWEgoUBYQcrVltgAIMBUlBgIIIBUWf//////////4ERFWEI+VdhCPhhBmxWW1thCQWEgoUBYQfhVltgIIMBUlCSkVBQVltgAGEJJGEJH4RhBvpWW2EEglZbkFCAg4JSYCCCAZBQYCCEAoMBhYERFWEJR1dhCUZhByZWW1uDW4GBEBVhCY5XgFFn//////////+BERVhCWxXYQlrYQQYVltbgIYBYQl5iYJhCKVWW4VSYCCFAZRQUFBgIIEBkFBhCUlWW1BQUJOSUFBQVltgAIJgH4MBEmEJrVdhCaxhBBhWW1uBUWEJvYSCYCCGAWEJEVZbkVBQkpFQUFZbYABgYIKEAxIVYQncV2EJ22EGZ1ZbW2EJ5mBgYQSCVluQUGAAYQn2hIKFAWEG5VZbYACDAVJQYCBhCgqEgoUBYQZxVltgIIMBUlBgQGEKHoSChQFhBuVWW2BAgwFSUJKRUFBWW2AAYQFggoQDEhVhCkFXYQpAYQZnVltbYQpMYQEgYQSCVluQUGAAggFRZ///////////gREVYQpsV2EKa2EGbFZbW2EKeISChQFhBRBWW2AAgwFSUGAgggFRZ///////////gREVYQqcV2EKm2EGbFZbW2EKqISChQFhBRBWW2AggwFSUGBAYQq8hIKFAWEGcVZbYECDAVJQYGCCAVFn//////////+BERVhCuBXYQrfYQZsVltbYQrshIKFAWEFEFZbYGCDAVJQYIBhCwCEgoUBYQapVltggIMBUlBgoGELFISChQFhBuVWW2CggwFSUGDAYQsohIKFAWEGqVZbYMCDAVJQYOCCAVFn//////////+BERVhC0xXYQtLYQZsVltbYQtYhIKFAWEJmFZbYOCDAVJQYQEAYQtthIKFAWEJxlZbYQEAgwFSUJKRUFBWW2ELg4FhBZZWW4EUYQuOV2AAgP1bUFZbYACBUZBQYQuggWELelZbkpFQUFZbYABn//////////+CERVhC8FXYQvAYQQiVltbYCCCApBQYCCBAZBQkZBQVltgAGCggoQDEhVhC+hXYQvnYQZnVltbYQvyYKBhBIJWW5BQYABhDAKEgoUBYQblVltgAIMBUlBgIGEMFoSChQFhBnFWW2AggwFSUGBAYQwqhIKFAWEGqVZbYECDAVJQYGBhDD6EgoUBYQapVltgYIMBUlBggGEMUoSChQFhBnFWW2CAgwFSUJKRUFBWW2AAYQxxYQxshGELplZbYQSCVluQUICDglJgIIIBkFBgoIQCgwGFgREVYQyUV2EMk2EHJlZbW4NbgYEQFWEMvVeAYQypiIJhC9JWW4RSYCCEAZNQUGCggQGQUGEMllZbUFBQk5JQUFBWW2AAgmAfgwESYQzcV2EM22EEGFZbW4FRYQzshIJgIIYBYQxeVluRUFCSkVBQVltgAGf//////////4IRFWENEFdhDQ9hBCJWW1tgIIICkFBgIIEBkFCRkFBWW2AAYMCChAMSFWENN1dhDTZhBmdWW1thDUFgwGEEglZbkFBgAGENUYSChQFhBuVWW2AAgwFSUGAgYQ1lhIKFAWEG5VZbYCCDAVJQYEBhDXmEgoUBYQblVltgQIMBUlBgYGENjYSChQFhBuVWW2BggwFSUGCAYQ2hhIKFAWEGqVZbYICDAVJQYKBhDbWEgoUBYQZxVltgoIMBUlCSkVBQVltgAGEN1GENz4RhDPVWW2EEglZbkFCAg4JSYCCCAZBQYMCEAoMBhYERFWEN91dhDfZhByZWW1uDW4GBEBVhDiBXgGEODIiCYQ0hVluEUmAghAGTUFBgwIEBkFBhDflWW1BQUJOSUFBQVltgAIJgH4MBEmEOP1dhDj5hBBhWW1uBUWEOT4SCYCCGAWENwVZbkVBQkpFQUFZbYABn//////////+CERVhDnNXYQ5yYQQiVltbYCCCApBQYCCBAZBQkZBQVltgAGDAgoQDEhVhDppXYQ6ZYQZnVltbYQ6kYMBhBIJWW5BQYABhDrSEgoUBYQblVltgAIMBUlBgIGEOyISChQFhBuVWW2AggwFSUGBAYQ7chIKFAWEG5VZbYECDAVJQYGBhDvCEgoUBYQZxVltgYIMBUlBggGEPBISChQFhBqlWW2CAgwFSUGCgYQ8YhIKFAWEGcVZbYKCDAVJQkpFQUFZbYABhDzdhDzKEYQ5YVlthBIJWW5BQgIOCUmAgggGQUGDAhAKDAYWBERVhD1pXYQ9ZYQcmVltbg1uBgRAVYQ+DV4BhD2+IgmEOhFZbhFJgIIQBk1BQYMCBAZBQYQ9cVltQUFCTklBQUFZbYACCYB+DARJhD6JXYQ+hYQQYVltbgVFhD7KEgmAghgFhDyRWW5FQUJKRUFBWW2AAYQEggoQDEhVhD9JXYQ/RYQZnVltbYQ/dYQEgYQSCVluQUGAAggFRZ///////////gREVYQ/9V2EP/GEGbFZbW2EQCYSChQFhCipWW2AAgwFSUGAgYRAdhIKFAWELkVZbYCCDAVJQYEBhEDGEgoUBYQapVltgQIMBUlBgYGEQRYSChQFhBqlWW2BggwFSUGCAYRBZhIKFAWEGqVZbYICDAVJQYKCCAVFn//////////+BERVhEH1XYRB8YQZsVltbYRCJhIKFAWEMx1ZbYKCDAVJQYMCCAVFn//////////+BERVhEK1XYRCsYQZsVltbYRC5hIKFAWEOKlZbYMCDAVJQYOCCAVFn//////////+BERVhEN1XYRDcYQZsVltbYRDphIKFAWEPjVZbYOCDAVJQYQEAggFRZ///////////gREVYREOV2ERDWEGbFZbW2ERGoSChQFhBRBWW2EBAIMBUlCSkVBQVltgAGDAgoQDEhVhET1XYRE8YQZnVltbYRFHYMBhBIJWW5BQYACCAVFn//////////+BERVhEWdXYRFmYQZsVltbYRFzhIKFAWEPu1ZbYACDAVJQYCBhEYeEgoUBYQuRVltgIIMBUlBgQGERm4SChQFhBnFWW2BAgwFSUGBgYRGvhIKFAWELkVZbYGCDAVJQYICCAVFn//////////+BERVhEdNXYRHSYQZsVltbYRHfhIKFAWEHs1ZbYICDAVJQYKBhEfOEgoUBYQZxVltgoIMBUlCSkVBQVltgAIBgQIOFAxIVYRIWV2ESFWECXlZbW2AAYRIkhYKGAWEGUlZbklBQYCCDAVFn//////////+BERVhEkVXYRJEYQJjVltbYRJRhYKGAWERJ1ZbkVBQklCSkFBW/qJkaXBmc1giEiALtbidJK5iI3uitRL3SolX3L5HCVoGtSoQi4pbotl5e2Rzb2xjQwAIEAAzIoACAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACjAmgw6AxinCEoWChQAAAAAAAAAAAAAAAAAAAAAAAAEJ3IHCgMYpwgQAVIWCgkKAhgCEP+yxQ0KCQoCGGIQgLPFDQ=="},{"b64Body":"Cg8KCQiS/f6sBhCJCxICGAISAhgDGKbV6tYCIgIIeDIgw4PCrsOCwrfDg8K5dEY4w4LCrkrDg8KLw4PCkMODwo7qAUwKA25mdBIIRU5GWllGUEcqAhgCUiYyJAoiEiAKqOIQZMYeq4biqcFkVltOeppBRhBuCmzQOow5WhEOkmoMCM7L2bAGEOq87+0CiAEB","b64Record":"CiUIFiocCgwIARAMGgYIgK6ZpA8SDAgBEA8aBgiArpmkD1IDGKgIEjC462OFVVSwzeCD9Aaj0p72cCYj3tx1GH4lN6IXFC60BOdZDJTZ/tVVnYbN7xfg1PAaDAjO/f6sBhDF5pP7AiIPCgkIkv3+rAYQiQsSAhgCKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjlIAcgkKAxioCBICGAI="},{"b64Body":"Cg8KCQiT/f6sBhCPCxICGAISAhgDGKqg+QciAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjqoCCAoDGKgIGgH/","b64Record":"CiUIFiocCgwIARAMGgYIgK6ZpA8SDAgBEA8aBgiArpmkD1gBcgEBEjAmqLs+fI22u2mmbvyMAeOiHURZHzfVuHY9WjItccClyCvJ5B69kAM5TG4ODRnELKUaDAjP/f6sBhDk1fyfASIPCgkIk/3+rAYQjwsSAhgCKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjlIAWhEKAxioCBoKCgIYABICGAIYAQ=="},{"b64Body":"Cg8KCQiT/f6sBhCTCxICGAISAhgDGOCssQMiAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjjpPCgMYpwgQoI0GIkS6SRZVAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABCgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQ==","b64Record":"CiUIFiIDGKcIKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjB4PyeAXlaDCBWYmBzDCEhjP+x8HE7/KJwpsmGZ2o6FBzen9TP8LETPioqtPYj+E3EaDAjP/f6sBhDmo+CEAyIPCgkIk/3+rAYQkwsSAhgCKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjjCA19oCOu4CCgMYpwgSYAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPvv70AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACKAAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAogPEEUhYKCQoCGAIQ/621BQoJCgIYYhCArrUF"},{"b64Body":"ChEKCQiT/f6sBhCTCxICGAIgATpFCgMY5wIQASI8YY3GXgAAAAAAAAAAAAAAAAAAAAAAAAQoyHtW3QAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB","b64Record":"CgIIFhIwWHOeg5xVuqyqM1OIdMhcnuRGfylgfB75RS0Ul8f1n0Xw8LQKQWTcZEq4I1/hGg1fGgwIz/3+rAYQ56PghAMiEQoJCJP9/qwGEJMLEgIYAiABOm4KAxjnAhJgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA++/vQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKGRqAxinCFIAegwIz/3+rAYQ5qPghAM="},{"b64Body":"Cg8KCQiU/f6sBhCVCxICGAISAhgDGMC/7SEiAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjjpPCgMYpwgQwIQ9IkTlMQuRAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABCgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQ==","b64Record":"CiUIFiIDGKcIKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjD5sm3Y2JI2NU/EI2oTJLh0Cqj0AyyiI23cQe8I0pHU7lk1H1ywXwEj7RsiAWp3hncaDAjQ/f6sBhDvk66pASIPCgkIlP3+rAYQlQsSAhgCKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjjCA5oobOu4CCgMYpwgSYAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAH/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACKAAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAogOowUhYKCQoCGAIQ/8uVNgoJCgIYYhCAzJU2"},{"b64Body":"ChEKCQiU/f6sBhCVCxICGAIgATpNCgMY5wIQASJEKH4dqAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAE=","b64Record":"CgIIFhIwyQtQF0i+EB9nmXON6AYX6fsh15THNamuWilgWcozi8Ii9F3CPKLtm33oiABSAG8zGgwI0P3+rAYQ8JOuqQEiEQoJCJT9/qwGEJULEgIYAiABOu8bCgMY5wIS4BsAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGWfvs8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAANYAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAASAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADCAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAxgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAWAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAeAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABmFmXOAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADbmZ0AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACEVORlpZRlBHAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAARAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABWAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAegAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQweDAwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB/wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAoZGoDGKcIUgB6DAjQ/f6sBhDvk66pAQ=="}]},"MinChargeIsTXGasUsedByContractCall":{"placeholderNum":1065,"encodedItems":[{"b64Body":"Cg8KCQiZ/f6sBhCtCxICGAISAhgDGP7LzSwiAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjooBjgESCwjVy9mwBhCv/JYUGm0KIhIgnvXuho7J8/d4AcWfDjKOyGFkvcmLMY28/ivUfYk4wzMKIzohA9ywQxssH+8Wwo9MEJRhun4yVYkaM2dmxTV8SkXgi45oCiISIPyyV/EB0oT0ibVDw3rXTyG7yj4WxEr8vkIEn/P/b5qjIgxIZWxsbyBXb3JsZCEqADIA","b64Record":"CiUIFhoDGKoIKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjAoT2VeSfSnR1CoBNao7q+eHoFk17of5XYDV3X+GQZc7t1rKSKTUh//thoIDuUVr4EaCwjV/f6sBhCJ+9UVIg8KCQiZ/f6sBhCtCxICGAIqIMODwq7DgsK3w4PCuXRGOMOCwq5Kw4PCi8ODwpDDg8KOUgA="},{"b64Body":"Cg8KCQiZ/f6sBhCxCxICGAISAhgDGNzJ7TEiAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjpoBjAoKAxiqCCKECjYwODA2MDQwNTIzNDgwMTU2MTAwMTA1NzYwMDA4MGZkNWI1MDYxMDI2MjgwNjEwMDIwNjAwMDM5NjAwMGYzZmU2MDgwNjA0MDUyMzQ4MDE1NjEwMDEwNTc2MDAwODBmZDViNTA2MDA0MzYxMDYxMDA0YzU3NjAwMDM1NjBlMDFjODA2MzFhYjA2ZWU1MTQ2MTAwNTE1NzgwNjNiOWNhZWJmNDE0NjEwMDZkNTc4MDYzYzMwMzY1ZDIxNDYxMDA4OTU3ODA2M2VkZWQ5N2ZhMTQ2MTAwYTc1NzViNjAwMDgwZmQ1YjYxMDA2YjYwMDQ4MDM2MDM4MTAxOTA2MTAwNjY5MTkwNjEwMTM3NTY1YjYxMDBjNTU2NWIwMDViNjEwMDg3NjAwNDgwMzYwMzgxMDE5MDYxMDA4MjkxOTA2MTAxZDU1NjViNjEwMGQ3NTY1YjAwNWI2MTAwOTE2MTAwZjA1NjViNjA0MDUxNjEwMDllOTE5MDYxMDIxMTU2NWI2MDQwNTE4MDkxMDM5MGYzNWI2MTAwYWY2MTAwZjY1NjViNjA0MDUxNjEwMGJjOTE5MDYxMDIxMTU2NWI2MDQwNTE4MDkxMDM5MGYzNWI4MTYwMDA4MTkwNTU1MDgwNjAwMTgxOTA1NTUwNTA1MDU2NWI4MDczZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjE2ZmY1YjYwMDE1NDgxNTY1YjYwMDA1NDgxNTY1YjYwMDA4MGZkNWI2MDAwODE5MDUwOTE5MDUwNTY1YjYxMDExNDgxNjEwMTAxNTY1YjgxMTQ2MTAxMWY1NzYwMDA4MGZkNWI1MDU2NWI2MDAwODEzNTkwNTA2MTAxMzE4MTYxMDEwYjU2NWI5MjkxNTA1MDU2NWI2MDAwODA2MDQwODM4NTAzMTIxNTYxMDE0ZTU3NjEwMTRkNjEwMGZjNTY1YjViNjAwMDYxMDE1Yzg1ODI4NjAxNjEwMTIyNTY1YjkyNTA1MDYwMjA2MTAxNmQ4NTgyODYwMTYxMDEyMjU2NWI5MTUwNTA5MjUwOTI5MDUwNTY1YjYwMDA3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmY4MjE2OTA1MDkxOTA1MDU2NWI2MDAwNjEwMWEyODI2MTAxNzc1NjViOTA1MDkxOTA1MDU2NWI2MTAxYjI4MTYxMDE5NzU2NWI4MTE0NjEwMWJkNTc2MDAwODBmZDViNTA1NjViNjAwMDgxMzU5MDUwNjEwMWNmODE2MTAxYTk1NjViOTI5MTUwNTA1NjViNjAwMDYwMjA4Mjg0MDMxMjE1NjEwMWViNTc2MTAxZWE2MTAwZmM1NjViNWI2MDAwNjEwMWY5ODQ4Mjg1MDE2MTAxYzA1NjViOTE1MDUwOTI5MTUwNTA1NjViNjEwMjBiODE2MTAxMDE1NjViODI1MjUwNTA1NjViNjAwMDYwMjA4MjAxOTA1MDYxMDIyNjYwMDA4MzAxODQ2MTAyMDI1NjViOTI5MTUwNTA1NmZlYTI2NDY5NzA2NjczNTgyMjEyMjA0NjJmNjM0OGJlN2RjNWQ0Y2IyZmRkMjE0MGFmOTQ3NzYzM2Y0MjJlMDg1ZmJmMWU1M2QxODlkMGUzYWQ1ODE5NjQ3MzZmNmM2MzQzMDAwODBjMDAzMw==","b64Record":"CiAIFiocCgwIARAMGgYIgK6ZpA8SDAgBEA8aBgiArpmkDxIw7Q/K6IY/u0j/YvNt1/RCpy1SodF4rVCXaWlnpSJa3EmpyZydTNrZGKlIn8qIZiw1GgwI1f3+rAYQhem1lwIiDwoJCJn9/qwGELELEgIYAiogw4PCrsOCwrfDg8K5dEY4w4LCrkrDg8KLw4PCkMODwo5SAA=="},{"b64Body":"Cg8KCQia/f6sBhCzCxICGAISAhgDGJb7rp0CIgIIeDIgw4PCrsOCwrfDg8K5dEY4w4LCrkrDg8KLw4PCkMODwo5CRQoDGKoIGiISINDQrTlsltb7ADTK6Mf0Kk00hbip90OATVTLdZrQHSwjIOCnEkIFCIDO2gNSAFoAagtjZWxsYXIgZG9vcg==","b64Record":"CiUIFiIDGKsIKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjBwG1dki3gH8BVmpWgIztoLklrRldbAffx7i1E3xvaC3K39592NNki9CEvqYjv6uiMaCwjW/f6sBhDD0IY8Ig8KCQia/f6sBhCzCxICGAIqIMODwq7DgsK3w4PCuXRGOMOCwq5Kw4PCi8ODwpDDg8KOMICFkAhClwcKAxirCBLiBGCAYEBSNIAVYQAQV2AAgP1bUGAENhBhAExXYAA1YOAcgGMasG7lFGEAUVeAY7nK6/QUYQBtV4BjwwNl0hRhAIlXgGPt7Zf6FGEAp1dbYACA/VthAGtgBIA2A4EBkGEAZpGQYQE3VlthAMVWWwBbYQCHYASANgOBAZBhAIKRkGEB1VZbYQDXVlsAW2EAkWEA8FZbYEBRYQCekZBhAhFWW2BAUYCRA5DzW2EAr2EA9lZbYEBRYQC8kZBhAhFWW2BAUYCRA5DzW4FgAIGQVVCAYAGBkFVQUFBWW4Bz//////////////////////////8W/1tgAVSBVltgAFSBVltgAID9W2AAgZBQkZBQVlthARSBYQEBVluBFGEBH1dgAID9W1BWW2AAgTWQUGEBMYFhAQtWW5KRUFBWW2AAgGBAg4UDEhVhAU5XYQFNYQD8VltbYABhAVyFgoYBYQEiVluSUFBgIGEBbYWChgFhASJWW5FQUJJQkpBQVltgAHP//////////////////////////4IWkFCRkFBWW2AAYQGigmEBd1ZbkFCRkFBWW2EBsoFhAZdWW4EUYQG9V2AAgP1bUFZbYACBNZBQYQHPgWEBqVZbkpFQUFZbYABgIIKEAxIVYQHrV2EB6mEA/FZbW2AAYQH5hIKFAWEBwFZbkVBQkpFQUFZbYQILgWEBAVZbglJQUFZbYABgIIIBkFBhAiZgAIMBhGECAlZbkpFQUFb+omRpcGZzWCISIEYvY0i+fcXUyy/dIUCvlHdjP0IuCF+/HlPRidDjrVgZZHNvbGNDAAgMADMigAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKIDTDjoDGKsIShYKFAAAAAAAAAAAAAAAAAAAAAAAAAQrcgcKAxirCBABUhYKCQoCGAIQ/4mgEAoJCgIYYhCAiqAQ"},{"b64Body":"Cg8KCQia/f6sBhC1CxICGAISAhgDGKCGlAoiAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjjpPCgMYqwgQ4KcSIkQasG7lAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKg==","b64Record":"CiUIFiIDGKsIKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjDOZ74j17DjGA2dE8kV9R/BXaEWg4zbiN5dgPwuDUNnKzDheZvEGQR5OjwikzRusNoaDAjW/f6sBhC2mcOgAiIPCgkImv3+rAYQtQsSAhgCKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjjCAhZAIOowCCgMYqwgigAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKIDTDlIWCgkKAhgCEP+JoBAKCQoCGGIQgIqgEA=="}]},"hscsEvm005TransferOfHBarsWorksBetweenContracts":{"placeholderNum":1068,"encodedItems":[{"b64Body":"Cg8KCQif/f6sBhDJCxICGAISAhgDGPuV9hQiAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjloxCiISIK+88/negFJShddfUeiBZ/wyDfMERB1GfcEgTrtgwqj0EIDIr6AlSgUIgM7aAw==","b64Record":"CiUIFhIDGK0IKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjDVsSXB8zWShheLCp69onjY9v5I8sJUPVaiPBoiwxZ9DJ1+4fq2RPLFAD1l49VdT6oaDAjb/f6sBhDx6L3BASIPCgkIn/3+rAYQyQsSAhgCKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjlIZCgoKAhgCEP+P38BKCgsKAxitCBCAkN/ASg=="},{"b64Body":"Cg8KCQif/f6sBhDLCxICGAISAhgDGP7LzSwiAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjooBjwESDAjby9mwBhCrq+KfAxptCiISIO98HF01ywERzNwNPtVjprPbbczHwQYhBIWmw6DbyhogCiM6IQJvuJsATTS9qz6BNYlTHQzrMVfu+gg9yAHCcSivQ45J1woiEiBP7DysLw7qU7t+D2BDuj5dGVYWTurj8uHddCRRcdRnnCIMSGVsbG8gV29ybGQhKgAyAA==","b64Record":"CiUIFhoDGK4IKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjC/lxZuGLJCBuFqQNClQxZRn5QKSpp79vFiSdM9svvurQhVaKjolhWUg3tttuumBNYaDAjb/f6sBhDS5L6mAyIPCgkIn/3+rAYQywsSAhgCKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjlIA"},{"b64Body":"Cg8KCQig/f6sBhDPCxICGAISAhgDGPOGlzoiAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjpoBghkKAxiuCCL6GDYwODA2MDQwNTI2MTA2MmE4MDYxMDAxMzYwMDAzOTYwMDBmM2ZlNjA4MDYwNDA1MjYwMDQzNjEwNjEwMDRhNTc2MDAwMzU2MGUwMWM4MDYzMjZhYjk3ZWExNDYxMDA0YzU3ODA2MzhkZDk4MDQzMTQ2MTAwOWE1NzgwNjNhY2VmNjAzNzE0NjEwMGM4NTc4MDYzZDJhMGZlMjYxNDYxMDExNjU3ODA2M2UxMmUzMjM4MTQ2MTAxYTQ1NzViMDA1YjYxMDA5ODYwMDQ4MDM2MDM2MDQwODExMDE1NjEwMDYyNTc2MDAwODBmZDViODEwMTkwODA4MDM1NzNmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmMTY5MDYwMjAwMTkwOTI5MTkwODAzNTkwNjAyMDAxOTA5MjkxOTA1MDUwNTA2MTAxZjI1NjViMDA1YjYxMDBjNjYwMDQ4MDM2MDM2MDIwODExMDE1NjEwMGIwNTc2MDAwODBmZDViODEwMTkwODA4MDM1OTA2MDIwMDE5MDkyOTE5MDUwNTA1MDYxMDQyMzU2NWIwMDViNjEwMTE0NjAwNDgwMzYwMzYwNDA4MTEwMTU2MTAwZGU1NzYwMDA4MGZkNWI4MTAxOTA4MDgwMzU3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNjkwNjAyMDAxOTA5MjkxOTA4MDM1OTA2MDIwMDE5MDkyOTE5MDUwNTA1MDYxMDQ2ZDU2NWIwMDViNjEwMWEyNjAwNDgwMzYwMzYwODA4MTEwMTU2MTAxMmM1NzYwMDA4MGZkNWI4MTAxOTA4MDgwMzU3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNjkwNjAyMDAxOTA5MjkxOTA4MDM1NzNmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmMTY5MDYwMjAwMTkwOTI5MTkwODAzNTczZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjE2OTA2MDIwMDE5MDkyOTE5MDgwMzU5MDYwMjAwMTkwOTI5MTkwNTA1MDUwNjEwNGI4NTY1YjAwNWI2MTAxZjA2MDA0ODAzNjAzNjA0MDgxMTAxNTYxMDFiYTU3NjAwMDgwZmQ1YjgxMDE5MDgwODAzNTczZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjE2OTA2MDIwMDE5MDkyOTE5MDgwMzU5MDYwMjAwMTkwOTI5MTkwNTA1MDUwNjEwNWE3NTY1YjAwNWI4MTczZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjE2NjEwOGZjODI5MDgxMTUwMjkwNjA0MDUxNjAwMDYwNDA1MTgwODMwMzgxODU4ODg4ZjE5MzUwNTA1MDUwMTU4MDE1NjEwMjM4NTczZDYwMDA4MDNlM2Q2MDAwZmQ1YjUwODE3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNjYxMDhmYzYwMDI4MzgxNjEwMjVkNTdmZTViMDQ5MDgxMTUwMjkwNjA0MDUxNjAwMDYwNDA1MTgwODMwMzgxODU4ODg4ZjE5MzUwNTA1MDUwMTU4MDE1NjEwMjg5NTczZDYwMDA4MDNlM2Q2MDAwZmQ1YjUwODE3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNjYxMDhmYzYwMDQ4MzgxNjEwMmFlNTdmZTViMDQ5MDgxMTUwMjkwNjA0MDUxNjAwMDYwNDA1MTgwODMwMzgxODU4ODg4ZjE5MzUwNTA1MDUwMTU4MDE1NjEwMmRhNTczZDYwMDA4MDNlM2Q2MDAwZmQ1YjUwODE3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNjYxMDhmYzYwMDg4MzgxNjEwMmZmNTdmZTViMDQ5MDgxMTUwMjkwNjA0MDUxNjAwMDYwNDA1MTgwODMwMzgxODU4ODg4ZjE5MzUwNTA1MDUwMTU4MDE1NjEwMzJiNTczZDYwMDA4MDNlM2Q2MDAwZmQ1YjUwODE3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNjYxMDhmYzYwMTA4MzgxNjEwMzUwNTdmZTViMDQ5MDgxMTUwMjkwNjA0MDUxNjAwMDYwNDA1MTgwODMwMzgxODU4ODg4ZjE5MzUwNTA1MDUwMTU4MDE1NjEwMzdjNTczZDYwMDA4MDNlM2Q2MDAwZmQ1YjUwODE3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNjYxMDhmYzYwMjA4MzgxNjEwM2ExNTdmZTViMDQ5MDgxMTUwMjkwNjA0MDUxNjAwMDYwNDA1MTgwODMwMzgxODU4ODg4ZjE5MzUwNTA1MDUwMTU4MDE1NjEwM2NkNTczZDYwMDA4MDNlM2Q2MDAwZmQ1YjUwODE3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNjYxMDhmYzYwNDA4MzgxNjEwM2YyNTdmZTViMDQ5MDgxMTUwMjkwNjA0MDUxNjAwMDYwNDA1MTgwODMwMzgxODU4ODg4ZjE5MzUwNTA1MDUwMTU4MDE1NjEwNDFlNTczZDYwMDA4MDNlM2Q2MDAwZmQ1YjUwNTA1MDU2NWIzMzczZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjE2NjEwOGZjODI5MDgxMTUwMjkwNjA0MDUxNjAwMDYwNDA1MTgwODMwMzgxODU4ODg4ZjE5MzUwNTA1MDUwMTU4MDE1NjEwNDY5NTczZDYwMDA4MDNlM2Q2MDAwZmQ1YjUwNTA1NjViODE3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNjYxMDhmYzgyOTA4MTE1MDI5MDYwNDA1MTYwMDA2MDQwNTE4MDgzMDM4MTg1ODg4OGYxOTM1MDUwNTA1MDE1ODAxNTYxMDRiMzU3M2Q2MDAwODAzZTNkNjAwMGZkNWI1MDUwNTA1NjViODM3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNjYxMDhmYzgyOTA4MTE1MDI5MDYwNDA1MTYwMDA2MDQwNTE4MDgzMDM4MTg1ODg4OGYxOTM1MDUwNTA1MDE1ODAxNTYxMDRmZTU3M2Q2MDAwODAzZTNkNjAwMGZkNWI1MDgyNzNmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmMTY2MTA4ZmM2MDAyODM4MTYxMDUyMzU3ZmU1YjA0OTA4MTE1MDI5MDYwNDA1MTYwMDA2MDQwNTE4MDgzMDM4MTg1ODg4OGYxOTM1MDUwNTA1MDE1ODAxNTYxMDU0ZjU3M2Q2MDAwODAzZTNkNjAwMGZkNWI1MDgxNzNmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmMTY2MTA4ZmM2MDA0ODM4MTYxMDU3NDU3ZmU1YjA0OTA4MTE1MDI5MDYwNDA1MTYwMDA2MDQwNTE4MDgzMDM4MTg1ODg4OGYxOTM1MDUwNTA1MDE1ODAxNTYxMDVhMDU3M2Q2MDAwODAzZTNkNjAwMGZkNWI1MDUwNTA1MDUwNTY1YjgxNzNmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmMTY2MTA4ZmM4MjYwMDAwMzkwODExNTAyOTA2MDQwNTE2MDAwNjA0MDUxODA4MzAzODE4NTg4ODhmMTkzNTA1MDUwNTAxNTgwMTU2MTA1ZjA1NzNkNjAwMDgwM2UzZDYwMDBmZDViNTA1MDUwNTZmZWEyNjU2MjdhN2E3MjMxNTgyMGNmNzkxYmJmNDdkY2I2MTM5Mzc0NDA1MmE2MjBmNzcxYjQyOWYwNzFmMDJlMzY1N2NlM2U1ZjgwYWIzZjk3YmQ2NDczNmY2YzYzNDMwMDA1MTEwMDMy","b64Record":"CiAIFiocCgwIARAMGgYIgK6ZpA8SDAgBEA8aBgiArpmkDxIwKTMzoXeRO2pg0o4ZJaUtOngezMn+5ugFC5z2ByEdrjFxwMfpeCPC7ruqE+A72NA4GgwI3P3+rAYQvvraygEiDwoJCKD9/qwGEM8LEgIYAiogw4PCrsOCwrfDg8K5dEY4w4LCrkrDg8KLw4PCkMODwo5SAA=="},{"b64Body":"ChAKCQig/f6sBhDRCxIDGK0IEgIYAxiW+66dAiICCHgyIMODwq7DgsK3w4PCuXRGOMOCwq5Kw4PCi8ODwpDDg8KOQkgKAxiuCBoiEiBzULg6Bd2we7aeP+iN3IMJz5FEn2R298slxe+5y3ms+yCQoQ8okE5CBQiAztoDUgBaAGoLY2VsbGFyIGRvb3I=","b64Record":"CiUIFiIDGK8IKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjBcXks92mXQY8Kt47+3jvnVUcIEjbelLeNEdvTMf/QwaWvVrbFq34D4vF9WayTRqoUaDAjc/f6sBhCLp4WwAyIQCgkIoP3+rAYQ0QsSAxitCCogw4PCrsOCwrfDg8K5dEY4w4LCrkrDg8KLw4PCkMODwo4w1tSRpAJC3w4KAxivCBKqDGCAYEBSYAQ2EGEASldgADVg4ByAYyarl+oUYQBMV4BjjdmAQxRhAJpXgGOs72A3FGEAyFeAY9Kg/iYUYQEWV4Bj4S4yOBRhAaRXWwBbYQCYYASANgNgQIEQFWEAYldgAID9W4EBkICANXP//////////////////////////xaQYCABkJKRkIA1kGAgAZCSkZBQUFBhAfJWWwBbYQDGYASANgNgIIEQFWEAsFdgAID9W4EBkICANZBgIAGQkpGQUFBQYQQjVlsAW2EBFGAEgDYDYECBEBVhAN5XYACA/VuBAZCAgDVz//////////////////////////8WkGAgAZCSkZCANZBgIAGQkpGQUFBQYQRtVlsAW2EBomAEgDYDYICBEBVhASxXYACA/VuBAZCAgDVz//////////////////////////8WkGAgAZCSkZCANXP//////////////////////////xaQYCABkJKRkIA1c///////////////////////////FpBgIAGQkpGQgDWQYCABkJKRkFBQUGEEuFZbAFthAfBgBIA2A2BAgRAVYQG6V2AAgP1bgQGQgIA1c///////////////////////////FpBgIAGQkpGQgDWQYCABkJKRkFBQUGEFp1ZbAFuBc///////////////////////////FmEI/IKQgRUCkGBAUWAAYEBRgIMDgYWIiPGTUFBQUBWAFWECOFc9YACAPj1gAP1bUIFz//////////////////////////8WYQj8YAKDgWECXVf+WwSQgRUCkGBAUWAAYEBRgIMDgYWIiPGTUFBQUBWAFWECiVc9YACAPj1gAP1bUIFz//////////////////////////8WYQj8YASDgWECrlf+WwSQgRUCkGBAUWAAYEBRgIMDgYWIiPGTUFBQUBWAFWEC2lc9YACAPj1gAP1bUIFz//////////////////////////8WYQj8YAiDgWEC/1f+WwSQgRUCkGBAUWAAYEBRgIMDgYWIiPGTUFBQUBWAFWEDK1c9YACAPj1gAP1bUIFz//////////////////////////8WYQj8YBCDgWEDUFf+WwSQgRUCkGBAUWAAYEBRgIMDgYWIiPGTUFBQUBWAFWEDfFc9YACAPj1gAP1bUIFz//////////////////////////8WYQj8YCCDgWEDoVf+WwSQgRUCkGBAUWAAYEBRgIMDgYWIiPGTUFBQUBWAFWEDzVc9YACAPj1gAP1bUIFz//////////////////////////8WYQj8YECDgWED8lf+WwSQgRUCkGBAUWAAYEBRgIMDgYWIiPGTUFBQUBWAFWEEHlc9YACAPj1gAP1bUFBQVlszc///////////////////////////FmEI/IKQgRUCkGBAUWAAYEBRgIMDgYWIiPGTUFBQUBWAFWEEaVc9YACAPj1gAP1bUFBWW4Fz//////////////////////////8WYQj8gpCBFQKQYEBRYABgQFGAgwOBhYiI8ZNQUFBQFYAVYQSzVz1gAIA+PWAA/VtQUFBWW4Nz//////////////////////////8WYQj8gpCBFQKQYEBRYABgQFGAgwOBhYiI8ZNQUFBQFYAVYQT+Vz1gAIA+PWAA/VtQgnP//////////////////////////xZhCPxgAoOBYQUjV/5bBJCBFQKQYEBRYABgQFGAgwOBhYiI8ZNQUFBQFYAVYQVPVz1gAIA+PWAA/VtQgXP//////////////////////////xZhCPxgBIOBYQV0V/5bBJCBFQKQYEBRYABgQFGAgwOBhYiI8ZNQUFBQFYAVYQWgVz1gAIA+PWAA/VtQUFBQUFZbgXP//////////////////////////xZhCPyCYAADkIEVApBgQFFgAGBAUYCDA4GFiIjxk1BQUFAVgBVhBfBXPWAAgD49YAD9W1BQUFb+omVienpyMVggz3kbv0fcthOTdEBSpiD3cbQp8HHwLjZXzj5fgKs/l71kc29sY0MABREAMiKAAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAowJoMOgMYrwhKFgoUAAAAAAAAAAAAAAAAAAAAAAAABC9yBwoDGK8IEAFSRwoJCgIYAxCY4okUCgoKAhhiEODciMYDCgoKAxigBhCatYg3CgoKAxihBhCatYg3CgsKAxitCBDLxaTIBAoJCgMYrwgQoJwB"},{"b64Body":"ChAKCQih/f6sBhDTCxIDGK0IEgIYAxiW+66dAiICCHgyIMODwq7DgsK3w4PCuXRGOMOCwq5Kw4PCi8ODwpDDg8KOQkgKAxiuCBoiEiAfpTxN7LmBWDOBBZV/fC/Y4sY/m40cvx/wUPQGBDc4iSCQoQ8okE5CBQiAztoDUgBaAGoLY2VsbGFyIGRvb3I=","b64Record":"CiUIFiIDGLAIKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjBuaiyZb8u9xo/RnHpF9zvmG6UzmWEysF9qpMhPPK7IMt0PKVTXzdI5Yi+4Eu7hj6saDAjd/f6sBhDdjavUASIQCgkIof3+rAYQ0wsSAxitCCogw4PCrsOCwrfDg8K5dEY4w4LCrkrDg8KLw4PCkMODwo4w1tSRpAJC3w4KAxiwCBKqDGCAYEBSYAQ2EGEASldgADVg4ByAYyarl+oUYQBMV4BjjdmAQxRhAJpXgGOs72A3FGEAyFeAY9Kg/iYUYQEWV4Bj4S4yOBRhAaRXWwBbYQCYYASANgNgQIEQFWEAYldgAID9W4EBkICANXP//////////////////////////xaQYCABkJKRkIA1kGAgAZCSkZBQUFBhAfJWWwBbYQDGYASANgNgIIEQFWEAsFdgAID9W4EBkICANZBgIAGQkpGQUFBQYQQjVlsAW2EBFGAEgDYDYECBEBVhAN5XYACA/VuBAZCAgDVz//////////////////////////8WkGAgAZCSkZCANZBgIAGQkpGQUFBQYQRtVlsAW2EBomAEgDYDYICBEBVhASxXYACA/VuBAZCAgDVz//////////////////////////8WkGAgAZCSkZCANXP//////////////////////////xaQYCABkJKRkIA1c///////////////////////////FpBgIAGQkpGQgDWQYCABkJKRkFBQUGEEuFZbAFthAfBgBIA2A2BAgRAVYQG6V2AAgP1bgQGQgIA1c///////////////////////////FpBgIAGQkpGQgDWQYCABkJKRkFBQUGEFp1ZbAFuBc///////////////////////////FmEI/IKQgRUCkGBAUWAAYEBRgIMDgYWIiPGTUFBQUBWAFWECOFc9YACAPj1gAP1bUIFz//////////////////////////8WYQj8YAKDgWECXVf+WwSQgRUCkGBAUWAAYEBRgIMDgYWIiPGTUFBQUBWAFWECiVc9YACAPj1gAP1bUIFz//////////////////////////8WYQj8YASDgWECrlf+WwSQgRUCkGBAUWAAYEBRgIMDgYWIiPGTUFBQUBWAFWEC2lc9YACAPj1gAP1bUIFz//////////////////////////8WYQj8YAiDgWEC/1f+WwSQgRUCkGBAUWAAYEBRgIMDgYWIiPGTUFBQUBWAFWEDK1c9YACAPj1gAP1bUIFz//////////////////////////8WYQj8YBCDgWEDUFf+WwSQgRUCkGBAUWAAYEBRgIMDgYWIiPGTUFBQUBWAFWEDfFc9YACAPj1gAP1bUIFz//////////////////////////8WYQj8YCCDgWEDoVf+WwSQgRUCkGBAUWAAYEBRgIMDgYWIiPGTUFBQUBWAFWEDzVc9YACAPj1gAP1bUIFz//////////////////////////8WYQj8YECDgWED8lf+WwSQgRUCkGBAUWAAYEBRgIMDgYWIiPGTUFBQUBWAFWEEHlc9YACAPj1gAP1bUFBQVlszc///////////////////////////FmEI/IKQgRUCkGBAUWAAYEBRgIMDgYWIiPGTUFBQUBWAFWEEaVc9YACAPj1gAP1bUFBWW4Fz//////////////////////////8WYQj8gpCBFQKQYEBRYABgQFGAgwOBhYiI8ZNQUFBQFYAVYQSzVz1gAIA+PWAA/VtQUFBWW4Nz//////////////////////////8WYQj8gpCBFQKQYEBRYABgQFGAgwOBhYiI8ZNQUFBQFYAVYQT+Vz1gAIA+PWAA/VtQgnP//////////////////////////xZhCPxgAoOBYQUjV/5bBJCBFQKQYEBRYABgQFGAgwOBhYiI8ZNQUFBQFYAVYQVPVz1gAIA+PWAA/VtQgXP//////////////////////////xZhCPxgBIOBYQV0V/5bBJCBFQKQYEBRYABgQFGAgwOBhYiI8ZNQUFBQFYAVYQWgVz1gAIA+PWAA/VtQUFBQUFZbgXP//////////////////////////xZhCPyCYAADkIEVApBgQFFgAGBAUYCDA4GFiIjxk1BQUFAVgBVhBfBXPWAAgD49YAD9W1BQUFb+omVienpyMVggz3kbv0fcthOTdEBSpiD3cbQp8HHwLjZXzj5fgKs/l71kc29sY0MABREAMiKAAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAowJoMOgMYsAhKFgoUAAAAAAAAAAAAAAAAAAAAAAAABDByBwoDGLAIEAFSRwoJCgIYAxCY4okUCgoKAhhiEODciMYDCgoKAxigBhCatYg3CgoKAxihBhCatYg3CgsKAxitCBDLxaTIBAoJCgMYsAgQoJwB"},{"b64Body":"ChAKCQih/f6sBhDnCxIDGK0IEgIYAxjgrLEDIgIIeDIgw4PCrsOCwrfDg8K5dEY4w4LCrkrDg8KLw4PCkMODwo46TwoDGK8IEKCNBiJErO9gNwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAo=","b64Record":"CiUIFiIDGK8IKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjDT2jUHpezsKb2DzareDLQpmy1f2q5gij0KUn+GCUbfUdzh+qanWJt3QA8Xa7iVSl0aDAjd/f6sBhCTs/bVAyIQCgkIof3+rAYQ5wsSAxitCCogw4PCrsOCwrfDg8K5dEY4w4LCrkrDg8KLw4PCkMODwo4wgNfaAjqMAgoDGK8IIoACAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACiA8QRSKQoJCgIYYhCArrUFCgoKAxitCBD/rbUFCgcKAxivCBATCgcKAxiwCBAU"}]},"hscsEvm006ContractHBarTransferToAccount":{"placeholderNum":1073,"encodedItems":[{"b64Body":"Cg8KCQim/f6sBhD3CxICGAISAhgDGPuV9hQiAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjloxCiISIE3xbitgOUtvr9NUI8zUDe3AtPXIshNgRLYBsb0Wqf6BEIDIr6AlSgUIgM7aAw==","b64Record":"CiUIFhIDGLIIKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjBqB+WBCzuXg81kofGESjBOW8VCZnDDg1om3iZr9go03a20sxTulTyOV+urldS76AAaDAji/f6sBhCgnePZAiIPCgkIpv3+rAYQ9wsSAhgCKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjlIZCgoKAhgCEP+P38BKCgsKAxiyCBCAkN/ASg=="},{"b64Body":"Cg8KCQin/f6sBhD5CxICGAISAhgDGPuV9hQiAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjlouCiISIFq8rGhwAwy08pRyFNRKPw8I+xrMSYuv23YuSvRVVhx2EJBOSgUIgM7aAw==","b64Record":"CiUIFhIDGLMIKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjBlyDOgi5N1KqBF7uG76uKrYGYADMvinI3ExcX6RDGPWFvCBm7W1Q4VrubAJmiaGOIaCwjj/f6sBhD3gaJiIg8KCQin/f6sBhD5CxICGAIqIMODwq7DgsK3w4PCuXRGOMOCwq5Kw4PCi8ODwpDDg8KOUhUKCAoCGAIQn5wBCgkKAxizCBCgnAE="},{"b64Body":"Cg8KCQin/f6sBhD7CxICGAISAhgDGP7LzSwiAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjooBjwESDAjjy9mwBhDCqvLNAhptCiISINCJLtZz0nkVQA55TpP2K/dh3kzq+LKTYKJiu+i+QZVUCiM6IQNvZ0nWg32yuy2q5x4sSDSqWYj/dOoJOkN+A0bU8W/l/woiEiBaXBeAYVd7Ce/UbZ8la4bpjO9AL3WUDjz3s4srqyzWmCIMSGVsbG8gV29ybGQhKgAyAA==","b64Record":"CiUIFhoDGLQIKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjAPKzpFea5ARn3nvyobeavgonLVxWdGipchJioQuuOk4Mfi7jkt0/7Zll4wzt214REaDAjj/f6sBhC1ubPjAiIPCgkIp/3+rAYQ+wsSAhgCKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjlIA"},{"b64Body":"Cg8KCQio/f6sBhD/CxICGAISAhgDGPOGlzoiAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjpoBghkKAxi0CCL6GDYwODA2MDQwNTI2MTA2MmE4MDYxMDAxMzYwMDAzOTYwMDBmM2ZlNjA4MDYwNDA1MjYwMDQzNjEwNjEwMDRhNTc2MDAwMzU2MGUwMWM4MDYzMjZhYjk3ZWExNDYxMDA0YzU3ODA2MzhkZDk4MDQzMTQ2MTAwOWE1NzgwNjNhY2VmNjAzNzE0NjEwMGM4NTc4MDYzZDJhMGZlMjYxNDYxMDExNjU3ODA2M2UxMmUzMjM4MTQ2MTAxYTQ1NzViMDA1YjYxMDA5ODYwMDQ4MDM2MDM2MDQwODExMDE1NjEwMDYyNTc2MDAwODBmZDViODEwMTkwODA4MDM1NzNmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmMTY5MDYwMjAwMTkwOTI5MTkwODAzNTkwNjAyMDAxOTA5MjkxOTA1MDUwNTA2MTAxZjI1NjViMDA1YjYxMDBjNjYwMDQ4MDM2MDM2MDIwODExMDE1NjEwMGIwNTc2MDAwODBmZDViODEwMTkwODA4MDM1OTA2MDIwMDE5MDkyOTE5MDUwNTA1MDYxMDQyMzU2NWIwMDViNjEwMTE0NjAwNDgwMzYwMzYwNDA4MTEwMTU2MTAwZGU1NzYwMDA4MGZkNWI4MTAxOTA4MDgwMzU3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNjkwNjAyMDAxOTA5MjkxOTA4MDM1OTA2MDIwMDE5MDkyOTE5MDUwNTA1MDYxMDQ2ZDU2NWIwMDViNjEwMWEyNjAwNDgwMzYwMzYwODA4MTEwMTU2MTAxMmM1NzYwMDA4MGZkNWI4MTAxOTA4MDgwMzU3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNjkwNjAyMDAxOTA5MjkxOTA4MDM1NzNmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmMTY5MDYwMjAwMTkwOTI5MTkwODAzNTczZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjE2OTA2MDIwMDE5MDkyOTE5MDgwMzU5MDYwMjAwMTkwOTI5MTkwNTA1MDUwNjEwNGI4NTY1YjAwNWI2MTAxZjA2MDA0ODAzNjAzNjA0MDgxMTAxNTYxMDFiYTU3NjAwMDgwZmQ1YjgxMDE5MDgwODAzNTczZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjE2OTA2MDIwMDE5MDkyOTE5MDgwMzU5MDYwMjAwMTkwOTI5MTkwNTA1MDUwNjEwNWE3NTY1YjAwNWI4MTczZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjE2NjEwOGZjODI5MDgxMTUwMjkwNjA0MDUxNjAwMDYwNDA1MTgwODMwMzgxODU4ODg4ZjE5MzUwNTA1MDUwMTU4MDE1NjEwMjM4NTczZDYwMDA4MDNlM2Q2MDAwZmQ1YjUwODE3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNjYxMDhmYzYwMDI4MzgxNjEwMjVkNTdmZTViMDQ5MDgxMTUwMjkwNjA0MDUxNjAwMDYwNDA1MTgwODMwMzgxODU4ODg4ZjE5MzUwNTA1MDUwMTU4MDE1NjEwMjg5NTczZDYwMDA4MDNlM2Q2MDAwZmQ1YjUwODE3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNjYxMDhmYzYwMDQ4MzgxNjEwMmFlNTdmZTViMDQ5MDgxMTUwMjkwNjA0MDUxNjAwMDYwNDA1MTgwODMwMzgxODU4ODg4ZjE5MzUwNTA1MDUwMTU4MDE1NjEwMmRhNTczZDYwMDA4MDNlM2Q2MDAwZmQ1YjUwODE3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNjYxMDhmYzYwMDg4MzgxNjEwMmZmNTdmZTViMDQ5MDgxMTUwMjkwNjA0MDUxNjAwMDYwNDA1MTgwODMwMzgxODU4ODg4ZjE5MzUwNTA1MDUwMTU4MDE1NjEwMzJiNTczZDYwMDA4MDNlM2Q2MDAwZmQ1YjUwODE3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNjYxMDhmYzYwMTA4MzgxNjEwMzUwNTdmZTViMDQ5MDgxMTUwMjkwNjA0MDUxNjAwMDYwNDA1MTgwODMwMzgxODU4ODg4ZjE5MzUwNTA1MDUwMTU4MDE1NjEwMzdjNTczZDYwMDA4MDNlM2Q2MDAwZmQ1YjUwODE3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNjYxMDhmYzYwMjA4MzgxNjEwM2ExNTdmZTViMDQ5MDgxMTUwMjkwNjA0MDUxNjAwMDYwNDA1MTgwODMwMzgxODU4ODg4ZjE5MzUwNTA1MDUwMTU4MDE1NjEwM2NkNTczZDYwMDA4MDNlM2Q2MDAwZmQ1YjUwODE3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNjYxMDhmYzYwNDA4MzgxNjEwM2YyNTdmZTViMDQ5MDgxMTUwMjkwNjA0MDUxNjAwMDYwNDA1MTgwODMwMzgxODU4ODg4ZjE5MzUwNTA1MDUwMTU4MDE1NjEwNDFlNTczZDYwMDA4MDNlM2Q2MDAwZmQ1YjUwNTA1MDU2NWIzMzczZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjE2NjEwOGZjODI5MDgxMTUwMjkwNjA0MDUxNjAwMDYwNDA1MTgwODMwMzgxODU4ODg4ZjE5MzUwNTA1MDUwMTU4MDE1NjEwNDY5NTczZDYwMDA4MDNlM2Q2MDAwZmQ1YjUwNTA1NjViODE3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNjYxMDhmYzgyOTA4MTE1MDI5MDYwNDA1MTYwMDA2MDQwNTE4MDgzMDM4MTg1ODg4OGYxOTM1MDUwNTA1MDE1ODAxNTYxMDRiMzU3M2Q2MDAwODAzZTNkNjAwMGZkNWI1MDUwNTA1NjViODM3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNjYxMDhmYzgyOTA4MTE1MDI5MDYwNDA1MTYwMDA2MDQwNTE4MDgzMDM4MTg1ODg4OGYxOTM1MDUwNTA1MDE1ODAxNTYxMDRmZTU3M2Q2MDAwODAzZTNkNjAwMGZkNWI1MDgyNzNmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmMTY2MTA4ZmM2MDAyODM4MTYxMDUyMzU3ZmU1YjA0OTA4MTE1MDI5MDYwNDA1MTYwMDA2MDQwNTE4MDgzMDM4MTg1ODg4OGYxOTM1MDUwNTA1MDE1ODAxNTYxMDU0ZjU3M2Q2MDAwODAzZTNkNjAwMGZkNWI1MDgxNzNmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmMTY2MTA4ZmM2MDA0ODM4MTYxMDU3NDU3ZmU1YjA0OTA4MTE1MDI5MDYwNDA1MTYwMDA2MDQwNTE4MDgzMDM4MTg1ODg4OGYxOTM1MDUwNTA1MDE1ODAxNTYxMDVhMDU3M2Q2MDAwODAzZTNkNjAwMGZkNWI1MDUwNTA1MDUwNTY1YjgxNzNmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmMTY2MTA4ZmM4MjYwMDAwMzkwODExNTAyOTA2MDQwNTE2MDAwNjA0MDUxODA4MzAzODE4NTg4ODhmMTkzNTA1MDUwNTAxNTgwMTU2MTA1ZjA1NzNkNjAwMDgwM2UzZDYwMDBmZDViNTA1MDUwNTZmZWEyNjU2MjdhN2E3MjMxNTgyMGNmNzkxYmJmNDdkY2I2MTM5Mzc0NDA1MmE2MjBmNzcxYjQyOWYwNzFmMDJlMzY1N2NlM2U1ZjgwYWIzZjk3YmQ2NDczNmY2YzYzNDMwMDA1MTEwMDMy","b64Record":"CiAIFiocCgwIARAMGgYIgK6ZpA8SDAgBEA8aBgiArpmkDxIwyzQpPYlZorVeqm8eYiqRxaEdfj4XZuUfmNga9Wr1mbJy121/mj5Pv+NcR3OuZCt+GgsI5P3+rAYQ4s+zayIPCgkIqP3+rAYQ/wsSAhgCKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjlIA"},{"b64Body":"ChAKCQio/f6sBhCBDBIDGLIIEgIYAxiW+66dAiICCHgyIMODwq7DgsK3w4PCuXRGOMOCwq5Kw4PCi8ODwpDDg8KOQkgKAxi0CBoiEiDftM443T5Bd+xzLaiHMF5kN2h4FqoyVTD4z9RkKQ1YdCCQoQ8okE5CBQiAztoDUgBaAGoLY2VsbGFyIGRvb3I=","b64Record":"CiUIFiIDGLUIKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjBQfP9rji0oG1h2pjZa8qnra9rh/juxN+UAjx7E36/CZRuV76VTjyncqqexDoIe+30aDAjk/f6sBhDOooftAiIQCgkIqP3+rAYQgQwSAxiyCCogw4PCrsOCwrfDg8K5dEY4w4LCrkrDg8KLw4PCkMODwo4w1tSRpAJC3w4KAxi1CBKqDGCAYEBSYAQ2EGEASldgADVg4ByAYyarl+oUYQBMV4BjjdmAQxRhAJpXgGOs72A3FGEAyFeAY9Kg/iYUYQEWV4Bj4S4yOBRhAaRXWwBbYQCYYASANgNgQIEQFWEAYldgAID9W4EBkICANXP//////////////////////////xaQYCABkJKRkIA1kGAgAZCSkZBQUFBhAfJWWwBbYQDGYASANgNgIIEQFWEAsFdgAID9W4EBkICANZBgIAGQkpGQUFBQYQQjVlsAW2EBFGAEgDYDYECBEBVhAN5XYACA/VuBAZCAgDVz//////////////////////////8WkGAgAZCSkZCANZBgIAGQkpGQUFBQYQRtVlsAW2EBomAEgDYDYICBEBVhASxXYACA/VuBAZCAgDVz//////////////////////////8WkGAgAZCSkZCANXP//////////////////////////xaQYCABkJKRkIA1c///////////////////////////FpBgIAGQkpGQgDWQYCABkJKRkFBQUGEEuFZbAFthAfBgBIA2A2BAgRAVYQG6V2AAgP1bgQGQgIA1c///////////////////////////FpBgIAGQkpGQgDWQYCABkJKRkFBQUGEFp1ZbAFuBc///////////////////////////FmEI/IKQgRUCkGBAUWAAYEBRgIMDgYWIiPGTUFBQUBWAFWECOFc9YACAPj1gAP1bUIFz//////////////////////////8WYQj8YAKDgWECXVf+WwSQgRUCkGBAUWAAYEBRgIMDgYWIiPGTUFBQUBWAFWECiVc9YACAPj1gAP1bUIFz//////////////////////////8WYQj8YASDgWECrlf+WwSQgRUCkGBAUWAAYEBRgIMDgYWIiPGTUFBQUBWAFWEC2lc9YACAPj1gAP1bUIFz//////////////////////////8WYQj8YAiDgWEC/1f+WwSQgRUCkGBAUWAAYEBRgIMDgYWIiPGTUFBQUBWAFWEDK1c9YACAPj1gAP1bUIFz//////////////////////////8WYQj8YBCDgWEDUFf+WwSQgRUCkGBAUWAAYEBRgIMDgYWIiPGTUFBQUBWAFWEDfFc9YACAPj1gAP1bUIFz//////////////////////////8WYQj8YCCDgWEDoVf+WwSQgRUCkGBAUWAAYEBRgIMDgYWIiPGTUFBQUBWAFWEDzVc9YACAPj1gAP1bUIFz//////////////////////////8WYQj8YECDgWED8lf+WwSQgRUCkGBAUWAAYEBRgIMDgYWIiPGTUFBQUBWAFWEEHlc9YACAPj1gAP1bUFBQVlszc///////////////////////////FmEI/IKQgRUCkGBAUWAAYEBRgIMDgYWIiPGTUFBQUBWAFWEEaVc9YACAPj1gAP1bUFBWW4Fz//////////////////////////8WYQj8gpCBFQKQYEBRYABgQFGAgwOBhYiI8ZNQUFBQFYAVYQSzVz1gAIA+PWAA/VtQUFBWW4Nz//////////////////////////8WYQj8gpCBFQKQYEBRYABgQFGAgwOBhYiI8ZNQUFBQFYAVYQT+Vz1gAIA+PWAA/VtQgnP//////////////////////////xZhCPxgAoOBYQUjV/5bBJCBFQKQYEBRYABgQFGAgwOBhYiI8ZNQUFBQFYAVYQVPVz1gAIA+PWAA/VtQgXP//////////////////////////xZhCPxgBIOBYQV0V/5bBJCBFQKQYEBRYABgQFGAgwOBhYiI8ZNQUFBQFYAVYQWgVz1gAIA+PWAA/VtQUFBQUFZbgXP//////////////////////////xZhCPyCYAADkIEVApBgQFFgAGBAUYCDA4GFiIjxk1BQUFAVgBVhBfBXPWAAgD49YAD9W1BQUFb+omVienpyMVggz3kbv0fcthOTdEBSpiD3cbQp8HHwLjZXzj5fgKs/l71kc29sY0MABREAMiKAAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAowJoMOgMYtQhKFgoUAAAAAAAAAAAAAAAAAAAAAAAABDVyBwoDGLUIEAFSRwoJCgIYAxCY4okUCgoKAhhiEODciMYDCgoKAxigBhCatYg3CgoKAxihBhCatYg3CgsKAxiyCBDLxaTIBAoJCgMYtQgQoJwB"},{"b64Body":"ChAKCQip/f6sBhCVDBIDGLIIEgIYAxjgrLEDIgIIeDIgw4PCrsOCwrfDg8K5dEY4w4LCrkrDg8KLw4PCkMODwo46TwoDGLUIEKCNBiJErO9gNwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQzAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAo=","b64Record":"CiUIFiIDGLUIKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjBN2OEPrgrTG21G3o+11B41J3tpQ4aicZ53B4csrVE2BKtL7wDeq+gdtXCAwyv1oOwaDAjl/f6sBhDZ2suRASIQCgkIqf3+rAYQlQwSAxiyCCogw4PCrsOCwrfDg8K5dEY4w4LCrkrDg8KLw4PCkMODwo4wgNfaAjqMAgoDGLUIIoACAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACiA8QRSKQoJCgIYYhCArrUFCgoKAxiyCBD/rbUFCgcKAxizCBAUCgcKAxi1CBAT"}]},"hscsEvm005TransfersWithSubLevelCallsBetweenContracts":{"placeholderNum":1078,"encodedItems":[{"b64Body":"Cg8KCQiu/f6sBhClDBICGAISAhgDGPuV9hQiAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjloxCiISINNsINDLW9vDRCwDOtC1z1tVezUUBBtvFENsgRHEkv89EIDIr6AlSgUIgM7aAw==","b64Record":"CiUIFhIDGLcIKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjCraGU/sScgaPUR5yqnwcF8Nf99ZD8BHnPt1Yx34ZxP8xXiw4JkSuIatPOR0/LS4CEaCwjq/f6sBhC9m88aIg8KCQiu/f6sBhClDBICGAIqIMODwq7DgsK3w4PCuXRGOMOCwq5Kw4PCi8ODwpDDg8KOUhkKCgoCGAIQ/4/fwEoKCwoDGLcIEICQ38BK"},{"b64Body":"Cg8KCQiu/f6sBhCnDBICGAISAhgDGP7LzSwiAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjooBjwESDAjqy9mwBhCo05L0ARptCiISIPQRfnBOcYQ0BgotiIlo5gKx8X6NegYSJjqxL568E734CiM6IQLgwM0ySpR5yBV6NWU5Iw424dpa/86t6ybv5EtzPYxLWAoiEiCxll93a4PpzkM+4wiS6r6hJUPMlbnnV9LdvgeYssFhtSIMSGVsbG8gV29ybGQhKgAyAA==","b64Record":"CiUIFhoDGLgIKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjDObX4FzILY8SKSTYoy7pd6jnK8hBr2CjF9gPxUkgDuVfiEauaNQ4tszeYWgMbKrfQaDAjq/f6sBhC/0sr/ASIPCgkIrv3+rAYQpwwSAhgCKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjlIA"},{"b64Body":"Cg8KCQiv/f6sBhCrDBICGAISAhgDGMGu0DQiAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjpoBiA8KAxi4CCKADzYwODA2MDQwNTI2MTAzYWQ4MDYxMDAxMzYwMDAzOTYwMDBmM2ZlNjA4MDYwNDA1MjYwMDQzNjEwNjEwMDQzNTc2MDAwMzU2MGUwMWM4MDYzMTBjN2NmOWExNDYxMDA0ZjU3ODA2M2JmNTM1MTI1MTQ2MTAwNzg1NzgwNjNjMzA2ZjliMTE0NjEwMDk2NTc4MDYzZGI4YTViZjYxNDYxMDBiZjU3NjEwMDRhNTY1YjM2NjEwMDRhNTcwMDViNjAwMDgwZmQ1YjM0ODAxNTYxMDA1YjU3NjAwMDgwZmQ1YjUwNjEwMDc2NjAwNDgwMzYwMzgxMDE5MDYxMDA3MTkxOTA2MTAyN2U1NjViNjEwMGVhNTY1YjAwNWI2MTAwODA2MTAxNjE1NjViNjA0MDUxNjEwMDhkOTE5MDYxMDJjZDU2NWI2MDQwNTE4MDkxMDM5MGYzNWIzNDgwMTU2MTAwYTI1NzYwMDA4MGZkNWI1MDYxMDBiZDYwMDQ4MDM2MDM4MTAxOTA2MTAwYjg5MTkwNjEwMjdlNTY1YjYxMDE2OTU2NWIwMDViMzQ4MDE1NjEwMGNiNTc2MDAwODBmZDViNTA2MTAwZDQ2MTAxZTA1NjViNjA0MDUxNjEwMGUxOTE5MDYxMDMwMzU2NWI2MDQwNTE4MDkxMDM5MGYzNWI4MTczZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjE2NjM1OThmZGViODgyNjA0MDUxODI2M2ZmZmZmZmZmMTY2MGUwMWI4MTUyNjAwNDAxNjAyMDYwNDA1MTgwODMwMzgxODU4ODVhZjExNTgwMTU2MTAxMzc1NzNkNjAwMDgwM2UzZDYwMDBmZDViNTA1MDUwNTA1MDYwNDA1MTNkNjAxZjE5NjAxZjgyMDExNjgyMDE4MDYwNDA1MjUwODEwMTkwNjEwMTVjOTE5MDYxMDM0YTU2NWI1MDUwNTA1NjViNjAwMDM0OTA1MDkwNTY1YjgxNzNmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmMTY2MzU5OGZkZWI4ODI2MDQwNTE4MjYzZmZmZmZmZmYxNjYwZTAxYjgxNTI2MDA0MDE2MDIwNjA0MDUxODA4MzAzODE4NTg4NWFmMTE1ODAxNTYxMDFiNjU3M2Q2MDAwODAzZTNkNjAwMGZkNWI1MDUwNTA1MDUwNjA0MDUxM2Q2MDFmMTk2MDFmODIwMTE2ODIwMTgwNjA0MDUyNTA4MTAxOTA2MTAxZGI5MTkwNjEwMzRhNTY1YjUwNTA1MDU2NWI2MDAwOTA1NjViNjAwMDgwZmQ1YjYwMDA3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmY4MjE2OTA1MDkxOTA1MDU2NWI2MDAwNjEwMjE1ODI2MTAxZWE1NjViOTA1MDkxOTA1MDU2NWI2MTAyMjU4MTYxMDIwYTU2NWI4MTE0NjEwMjMwNTc2MDAwODBmZDViNTA1NjViNjAwMDgxMzU5MDUwNjEwMjQyODE2MTAyMWM1NjViOTI5MTUwNTA1NjViNjAwMDgxOTA1MDkxOTA1MDU2NWI2MTAyNWI4MTYxMDI0ODU2NWI4MTE0NjEwMjY2NTc2MDAwODBmZDViNTA1NjViNjAwMDgxMzU5MDUwNjEwMjc4ODE2MTAyNTI1NjViOTI5MTUwNTA1NjViNjAwMDgwNjA0MDgzODUwMzEyMTU2MTAyOTU1NzYxMDI5NDYxMDFlNTU2NWI1YjYwMDA2MTAyYTM4NTgyODYwMTYxMDIzMzU2NWI5MjUwNTA2MDIwNjEwMmI0ODU4Mjg2MDE2MTAyNjk1NjViOTE1MDUwOTI1MDkyOTA1MDU2NWI2MTAyYzc4MTYxMDI0ODU2NWI4MjUyNTA1MDU2NWI2MDAwNjAyMDgyMDE5MDUwNjEwMmUyNjAwMDgzMDE4NDYxMDJiZTU2NWI5MjkxNTA1MDU2NWI2MDAwODExNTE1OTA1MDkxOTA1MDU2NWI2MTAyZmQ4MTYxMDJlODU2NWI4MjUyNTA1MDU2NWI2MDAwNjAyMDgyMDE5MDUwNjEwMzE4NjAwMDgzMDE4NDYxMDJmNDU2NWI5MjkxNTA1MDU2NWI2MTAzMjc4MTYxMDJlODU2NWI4MTE0NjEwMzMyNTc2MDAwODBmZDViNTA1NjViNjAwMDgxNTE5MDUwNjEwMzQ0ODE2MTAzMWU1NjViOTI5MTUwNTA1NjViNjAwMDYwMjA4Mjg0MDMxMjE1NjEwMzYwNTc2MTAzNWY2MTAxZTU1NjViNWI2MDAwNjEwMzZlODQ4Mjg1MDE2MTAzMzU1NjViOTE1MDUwOTI5MTUwNTA1NmZlYTI2NDY5NzA2NjczNTgyMjEyMjA1ZTRiYTVlNjlmODM0Y2Y5Yzc0ZDZhMjU4YTA2NGRlNWU1OGJmNTNhZDE5MGRkMWQ4YTFiMGUzYjUzMDE1MWIyNjQ3MzZmNmM2MzQzMDAwODBjMDAzMw==","b64Record":"CiAIFiocCgwIARAMGgYIgK6ZpA8SDAgBEA8aBgiArpmkDxIwIzlSLV+L2CdqzXYt7fSr0pY+6tTMmvb/gu41tTJRPEh2BwvZXjrYlPzKLnk6D9QHGgsI6/3+rAYQ0O2CJCIPCgkIr/3+rAYQqwwSAhgCKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjlIA"},{"b64Body":"Cg8KCQiv/f6sBhCtDBICGAISAhgDGP7LzSwiAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjooBjwESDAjry9mwBhDT5J7+ARptCiISIAWgQD7xmTzjAOQiOEAEU/6cgqR2O8S90Ns+rcsUzR+gCiM6IQPYkmI087f7NBKvZLD7Iyrqr35oWCa49QFtj93a4R6ZEAoiEiAhewwKmPtbSWKDq9MVMgxzX0SFeMyNu60c7R6EBzP+0iIMSGVsbG8gV29ybGQhKgAyAA==","b64Record":"CiUIFhoDGLkIKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjAXhm6yLo/N2ZAG0lym+v7n9n/yCzZmh3fx5nZJUC7J7HI9Toz7qHokXY2tA6LOUGgaDAjr/f6sBhCwm/WIAiIPCgkIr/3+rAYQrQwSAhgCKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjlIA"},{"b64Body":"Cg8KCQiw/f6sBhCxDBICGAISAhgDGNKPzS0iAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjpoBvAIKAxi5CCK0AjYwODA2MDQwNTI2MDg5ODA2MDExNjAwMDM5NjAwMGYzZmU2MDgwNjA0MDUyNjAwNDM2MTA2MDI1NTc2MDAwMzU2MGUwMWM4MDYyMzAxNDZlMTQ2MDJhNTc4MDYzNTk4ZmRlYjgxNDYwNGQ1NzViNjAwMDgwZmQ1YjM0ODAxNTYwMzU1NzYwMDA4MGZkNWI1MDYwMDE1YjYwNDA1MTkwMTUxNTgxNTI2MDIwMDE2MDQwNTE4MDkxMDM5MGYzNWI2MDAxNjAzOTU2ZmVhMjY0Njk3MDY2NzM1ODIyMTIyMGM0YjY3OTY0MmYzYjFmN2UxNGJlNzY2ZmVhNTlhNWQ4ZmY3MWM1YWEyZjg2MjMzYWRhY2ZmNWM1YmFjNDI4ZDg2NDczNmY2YzYzNDMwMDA4MDcwMDMz","b64Record":"CiAIFiocCgwIARAMGgYIgK6ZpA8SDAgBEA8aBgiArpmkDxIwAttWkIax7qesxq9MDKjosX3tkZCPFrTMO4Sp/zhhIxq/jhWqA5eskMWe790zWWOZGgsI7P3+rAYQssSsLSIPCgkIsP3+rAYQsQwSAhgCKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjlIA"},{"b64Body":"ChAKCQiw/f6sBhCzDBIDGLcIEgIYAxiW+66dAiICCHgyIMODwq7DgsK3w4PCuXRGOMOCwq5Kw4PCi8ODwpDDg8KOQkcKAxi4CBoiEiBywi9BwYlRGUHWMVaJl2f6tDSZVs0H9ZW9gN5qaKEnBiCQoQ8oZEIFCIDO2gNSAFoAagtjZWxsYXIgZG9vcg==","b64Record":"CiUIFiIDGLoIKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjDsufndYtyEtKEXaoBOzV6Y5mpxPlL8e3IlnGpefud/VTLgKzCSdO/L2+5z816CkcUaDAjs/f6sBhDavqeSAiIQCgkIsP3+rAYQswwSAxi3CCogw4PCrsOCwrfDg8K5dEY4w4LCrkrDg8KLw4PCkMODwo4w1tSRpAJC4gkKAxi6CBKtB2CAYEBSYAQ2EGEAQ1dgADVg4ByAYxDHz5oUYQBPV4Bjv1NRJRRhAHhXgGPDBvmxFGEAlleAY9uKW/YUYQC/V2EASlZbNmEASlcAW2AAgP1bNIAVYQBbV2AAgP1bUGEAdmAEgDYDgQGQYQBxkZBhAn5WW2EA6lZbAFthAIBhAWFWW2BAUWEAjZGQYQLNVltgQFGAkQOQ81s0gBVhAKJXYACA/VtQYQC9YASANgOBAZBhALiRkGECflZbYQFpVlsAWzSAFWEAy1dgAID9W1BhANRhAeBWW2BAUWEA4ZGQYQMDVltgQFGAkQOQ81uBc///////////////////////////FmNZj964gmBAUYJj/////xZg4BuBUmAEAWAgYEBRgIMDgYWIWvEVgBVhATdXPWAAgD49YAD9W1BQUFBQYEBRPWAfGWAfggEWggGAYEBSUIEBkGEBXJGQYQNKVltQUFBWW2AANJBQkFZbgXP//////////////////////////xZjWY/euIJgQFGCY/////8WYOAbgVJgBAFgIGBAUYCDA4GFiFrxFYAVYQG2Vz1gAIA+PWAA/VtQUFBQUGBAUT1gHxlgH4IBFoIBgGBAUlCBAZBhAduRkGEDSlZbUFBQVltgAJBWW2AAgP1bYABz//////////////////////////+CFpBQkZBQVltgAGECFYJhAepWW5BQkZBQVlthAiWBYQIKVluBFGECMFdgAID9W1BWW2AAgTWQUGECQoFhAhxWW5KRUFBWW2AAgZBQkZBQVlthAluBYQJIVluBFGECZldgAID9W1BWW2AAgTWQUGECeIFhAlJWW5KRUFBWW2AAgGBAg4UDEhVhApVXYQKUYQHlVltbYABhAqOFgoYBYQIzVluSUFBgIGECtIWChgFhAmlWW5FQUJJQkpBQVlthAseBYQJIVluCUlBQVltgAGAgggGQUGEC4mAAgwGEYQK+VluSkVBQVltgAIEVFZBQkZBQVlthAv2BYQLoVluCUlBQVltgAGAgggGQUGEDGGAAgwGEYQL0VluSkVBQVlthAyeBYQLoVluBFGEDMldgAID9W1BWW2AAgVGQUGEDRIFhAx5WW5KRUFBWW2AAYCCChAMSFWEDYFdhA19hAeVWW1tgAGEDboSChQFhAzVWW5FQUJKRUFBW/qJkaXBmc1giEiBeS6Xmn4NM+cdNaiWKBk3l5Yv1OtGQ3R2KGw47UwFRsmRzb2xjQwAIDAAzIoACAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACjAmgw6Axi6CEoWChQAAAAAAAAAAAAAAAAAAAAAAAAEOnIHCgMYuggQAVJGCgkKAhgDEJjiiRQKCgoCGGIQ4NyIxgMKCgoDGKAGEJq1iDcKCgoDGKEGEJq1iDcKCwoDGLcIEPOqo8gECggKAxi6CBDIAQ=="},{"b64Body":"ChAKCQix/f6sBhC1DBIDGLcIEgIYAxiW+66dAiICCHgyIMODwq7DgsK3w4PCuXRGOMOCwq5Kw4PCi8ODwpDDg8KOQkcKAxi5CBoiEiDrkEs3q57WiG7QePD1aURaQSABIgGb8pYMPL6SWcD2wSCQoQ8oZEIFCIDO2gNSAFoAagtjZWxsYXIgZG9vcg==","b64Record":"CiUIFiIDGLsIKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjAI+44oZjKHAA95Fdlf2BxVjPTapNEohXUlhEMPY0Bk8t7HKswOk0CxhPxUupRW8C4aCwjt/f6sBhCA+4o3IhAKCQix/f6sBhC1DBIDGLcIKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjjDW1JGkAkK+AwoDGLsIEokBYIBgQFJgBDYQYCVXYAA1YOAcgGIwFG4UYCpXgGNZj964FGBNV1tgAID9WzSAFWA1V2AAgP1bUGABW2BAUZAVFYFSYCABYEBRgJEDkPNbYAFgOVb+omRpcGZzWCISIMS2eWQvOx9+FL52b+pZpdj/ccWqL4YjOtrP9cW6xCjYZHNvbGNDAAgHADMigAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKMCaDDoDGLsIShYKFAAAAAAAAAAAAAAAAAAAAAAAAAQ7cgcKAxi7CBABUkYKCQoCGAMQmOKJFAoKCgIYYhDg3IjGAwoKCgMYoAYQmrWINwoKCgMYoQYQmrWINwoLCgMYtwgQ86qjyAQKCAoDGLsIEMgB"},{"b64Body":"ChAKCQix/f6sBhC3DBIDGLcIEgIYAxjgrLEDIgIIeDIgw4PCrsOCwrfDg8K5dEY4w4LCrkrDg8KLw4PCkMODwo46CwoDGLoIEKCNBhgK","b64Record":"CiUIFiIDGLoIKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjDMVkqHY7TOZXt18dwdrl9299MXDiFt76buAq6345km5BdJcUQ+TtpCNw3Rm2AzXhAaDAjt/f6sBhDP/IucAiIQCgkIsf3+rAYQtwwSAxi3CCogw4PCrsOCwrfDg8K5dEY4w4LCrkrDg8KLw4PCkMODwo4wgNfaAjqMAgoDGLoIIoACAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACiA8QRSIAoJCgIYYhCArrUFCgoKAxi3CBCTrrUFCgcKAxi6CBAU"},{"b64Body":"ChAKCQiy/f6sBhC5DBIDGLcIEgIYAxjgrLEDIgIIeDIgw4PCrsOCwrfDg8K5dEY4w4LCrkrDg8KLw4PCkMODwo46EQoDGLoIEKCNBhgKIgS/U1El","b64Record":"CiUIFiIDGLoIKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjDbBkAYyAZ2xpoCkrJ4XJ5nhtvnDk0p0TvRINBYyqiUY4t5cUGW3cZ4zIrRU4fAia0aCwju/f6sBhCdoNhAIhAKCQiy/f6sBhC5DBIDGLcIKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjjCA19oCOq4CCgMYuggSIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKIoACAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACiA8QRSIAoJCgIYYhCArrUFCgoKAxi3CBCTrrUFCgcKAxi6CBAU"},{"b64Body":"ChAKCQiy/f6sBhC7DBIDGLcIEgIYAxjgrLEDIgIIeDIgw4PCrsOCwrfDg8K5dEY4w4LCrkrDg8KLw4PCkMODwo46EQoDGLoIEKCNBhgKIgTbilv2","b64Record":"CiUIISIDGLoIKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjBv870O7T0QUVWzhfyFwznq0iEyo25G5DKiA7JbZicEQR4oKwjaQrpK3QwtOyvPPIwaDAju/f6sBhDEt9ClAiIQCgkIsv3+rAYQuwwSAxi3CCogw4PCrsOCwrfDg8K5dEY4w4LCrkrDg8KLw4PCkMODwo4wgNfaAjoIGgIweCiA8QRSFwoJCgIYYhCArrUFCgoKAxi3CBD/rbUF"},{"b64Body":"Cg8KCQiz/f6sBhDJDBICGAISAhgDGOCssQMiAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjjpPCgMYuwgQoI0GIkQQx8+aAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABDsAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFA==","b64Record":"CiUIISIDGLsIKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjCMPyhWethMtrZ1zR6DNihd/refxVB79rP1QVV8b3T/5DkNuAZ/PXiYLW3nLEPwChEaCwjv/f6sBhCW8ZZKIg8KCQiz/f6sBhDJDBICGAIqIMODwq7DgsK3w4PCuXRGOMOCwq5Kw4PCi8ODwpDDg8KOMIDX2gI6CBoCMHgogPEEUhYKCQoCGAIQ/621BQoJCgIYYhCArrUF"},{"b64Body":"Cg8KCQiz/f6sBhDLDBICGAISAhgDGOCssQMiAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjjpPCgMYuggQoI0GIkTDBvmxAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABDsAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFA==","b64Record":"CiUIFiIDGLoIKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjBY6sXtae14Q289KpP5s/xiqNKG1coIKahXg7hXwREFhSOADeBrfuri2tCNFVIG3oIaDAjv/f6sBhCsnYevAiIPCgkIs/3+rAYQywwSAhgCKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjjCA19oCOowCCgMYuggigAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKIDxBFIoCgkKAhgCEP+ttQUKCQoCGGIQgK61BQoHCgMYuggQJwoHCgMYuwgQKA=="}]},"hscsEvm010MultiSignatureAccounts":{"placeholderNum":1084,"encodedItems":[{"b64Body":"Cg8KCQi4/f6sBhDbDBICGAISAhgDGMPM/xUiAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjlpZCkoySAoiEiA8qVYjJEmAGwZhuYzdMhaC7r0qPULCtjw0K+12beYKwQoiEiDVAyv+4aglsIW8UC5/xMklzo6kNW2GsRaZsJP2kDVLfxCAyK+gJUoFCIDO2gM=","b64Record":"CiUIFhIDGL0IKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjAZs4LKyjoLsEvyx7SIBolCVI2iNZHmQ6vz/z0GWAbD1DafmVHFFQr00ctec9372CMaDAj0/f6sBhDQvZizASIPCgkIuP3+rAYQ2wwSAhgCKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjlIZCgoKAhgCEP+P38BKCgsKAxi9CBCAkN/ASg=="},{"b64Body":"Cg8KCQi4/f6sBhDdDBICGAISAhgDGP7LzSwiAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjooBjwESDAj0y9mwBhCp8s6MAxptCiISILYLVTc3RFAg72XgtZtjBQ8fKrhbMphHpC5q2bXtIJXoCiM6IQPTzkemanZNEQqtHVC09XB7DpXOVF7a7bACrVMU+ZbLvgoiEiBdQ317UmangY0vFnMl6jpJP+Roifoxpwest7ZKTjxRgyIMSGVsbG8gV29ybGQhKgAyAA==","b64Record":"CiUIFhoDGL4IKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjAUhLSSP7c/7k/aZBqUJsu1UR6anZ70gZyrejrARno4LRHQ5qZDkCgExpzWderZPL4aDAj0/f6sBhDqnYGYAyIPCgkIuP3+rAYQ3QwSAhgCKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjlIA"},{"b64Body":"Cg8KCQi5/f6sBhDhDBICGAISAhgDGPOGlzoiAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjpoBghkKAxi+CCL6GDYwODA2MDQwNTI2MTA2MmE4MDYxMDAxMzYwMDAzOTYwMDBmM2ZlNjA4MDYwNDA1MjYwMDQzNjEwNjEwMDRhNTc2MDAwMzU2MGUwMWM4MDYzMjZhYjk3ZWExNDYxMDA0YzU3ODA2MzhkZDk4MDQzMTQ2MTAwOWE1NzgwNjNhY2VmNjAzNzE0NjEwMGM4NTc4MDYzZDJhMGZlMjYxNDYxMDExNjU3ODA2M2UxMmUzMjM4MTQ2MTAxYTQ1NzViMDA1YjYxMDA5ODYwMDQ4MDM2MDM2MDQwODExMDE1NjEwMDYyNTc2MDAwODBmZDViODEwMTkwODA4MDM1NzNmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmMTY5MDYwMjAwMTkwOTI5MTkwODAzNTkwNjAyMDAxOTA5MjkxOTA1MDUwNTA2MTAxZjI1NjViMDA1YjYxMDBjNjYwMDQ4MDM2MDM2MDIwODExMDE1NjEwMGIwNTc2MDAwODBmZDViODEwMTkwODA4MDM1OTA2MDIwMDE5MDkyOTE5MDUwNTA1MDYxMDQyMzU2NWIwMDViNjEwMTE0NjAwNDgwMzYwMzYwNDA4MTEwMTU2MTAwZGU1NzYwMDA4MGZkNWI4MTAxOTA4MDgwMzU3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNjkwNjAyMDAxOTA5MjkxOTA4MDM1OTA2MDIwMDE5MDkyOTE5MDUwNTA1MDYxMDQ2ZDU2NWIwMDViNjEwMWEyNjAwNDgwMzYwMzYwODA4MTEwMTU2MTAxMmM1NzYwMDA4MGZkNWI4MTAxOTA4MDgwMzU3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNjkwNjAyMDAxOTA5MjkxOTA4MDM1NzNmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmMTY5MDYwMjAwMTkwOTI5MTkwODAzNTczZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjE2OTA2MDIwMDE5MDkyOTE5MDgwMzU5MDYwMjAwMTkwOTI5MTkwNTA1MDUwNjEwNGI4NTY1YjAwNWI2MTAxZjA2MDA0ODAzNjAzNjA0MDgxMTAxNTYxMDFiYTU3NjAwMDgwZmQ1YjgxMDE5MDgwODAzNTczZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjE2OTA2MDIwMDE5MDkyOTE5MDgwMzU5MDYwMjAwMTkwOTI5MTkwNTA1MDUwNjEwNWE3NTY1YjAwNWI4MTczZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjE2NjEwOGZjODI5MDgxMTUwMjkwNjA0MDUxNjAwMDYwNDA1MTgwODMwMzgxODU4ODg4ZjE5MzUwNTA1MDUwMTU4MDE1NjEwMjM4NTczZDYwMDA4MDNlM2Q2MDAwZmQ1YjUwODE3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNjYxMDhmYzYwMDI4MzgxNjEwMjVkNTdmZTViMDQ5MDgxMTUwMjkwNjA0MDUxNjAwMDYwNDA1MTgwODMwMzgxODU4ODg4ZjE5MzUwNTA1MDUwMTU4MDE1NjEwMjg5NTczZDYwMDA4MDNlM2Q2MDAwZmQ1YjUwODE3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNjYxMDhmYzYwMDQ4MzgxNjEwMmFlNTdmZTViMDQ5MDgxMTUwMjkwNjA0MDUxNjAwMDYwNDA1MTgwODMwMzgxODU4ODg4ZjE5MzUwNTA1MDUwMTU4MDE1NjEwMmRhNTczZDYwMDA4MDNlM2Q2MDAwZmQ1YjUwODE3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNjYxMDhmYzYwMDg4MzgxNjEwMmZmNTdmZTViMDQ5MDgxMTUwMjkwNjA0MDUxNjAwMDYwNDA1MTgwODMwMzgxODU4ODg4ZjE5MzUwNTA1MDUwMTU4MDE1NjEwMzJiNTczZDYwMDA4MDNlM2Q2MDAwZmQ1YjUwODE3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNjYxMDhmYzYwMTA4MzgxNjEwMzUwNTdmZTViMDQ5MDgxMTUwMjkwNjA0MDUxNjAwMDYwNDA1MTgwODMwMzgxODU4ODg4ZjE5MzUwNTA1MDUwMTU4MDE1NjEwMzdjNTczZDYwMDA4MDNlM2Q2MDAwZmQ1YjUwODE3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNjYxMDhmYzYwMjA4MzgxNjEwM2ExNTdmZTViMDQ5MDgxMTUwMjkwNjA0MDUxNjAwMDYwNDA1MTgwODMwMzgxODU4ODg4ZjE5MzUwNTA1MDUwMTU4MDE1NjEwM2NkNTczZDYwMDA4MDNlM2Q2MDAwZmQ1YjUwODE3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNjYxMDhmYzYwNDA4MzgxNjEwM2YyNTdmZTViMDQ5MDgxMTUwMjkwNjA0MDUxNjAwMDYwNDA1MTgwODMwMzgxODU4ODg4ZjE5MzUwNTA1MDUwMTU4MDE1NjEwNDFlNTczZDYwMDA4MDNlM2Q2MDAwZmQ1YjUwNTA1MDU2NWIzMzczZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjE2NjEwOGZjODI5MDgxMTUwMjkwNjA0MDUxNjAwMDYwNDA1MTgwODMwMzgxODU4ODg4ZjE5MzUwNTA1MDUwMTU4MDE1NjEwNDY5NTczZDYwMDA4MDNlM2Q2MDAwZmQ1YjUwNTA1NjViODE3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNjYxMDhmYzgyOTA4MTE1MDI5MDYwNDA1MTYwMDA2MDQwNTE4MDgzMDM4MTg1ODg4OGYxOTM1MDUwNTA1MDE1ODAxNTYxMDRiMzU3M2Q2MDAwODAzZTNkNjAwMGZkNWI1MDUwNTA1NjViODM3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNjYxMDhmYzgyOTA4MTE1MDI5MDYwNDA1MTYwMDA2MDQwNTE4MDgzMDM4MTg1ODg4OGYxOTM1MDUwNTA1MDE1ODAxNTYxMDRmZTU3M2Q2MDAwODAzZTNkNjAwMGZkNWI1MDgyNzNmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmMTY2MTA4ZmM2MDAyODM4MTYxMDUyMzU3ZmU1YjA0OTA4MTE1MDI5MDYwNDA1MTYwMDA2MDQwNTE4MDgzMDM4MTg1ODg4OGYxOTM1MDUwNTA1MDE1ODAxNTYxMDU0ZjU3M2Q2MDAwODAzZTNkNjAwMGZkNWI1MDgxNzNmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmMTY2MTA4ZmM2MDA0ODM4MTYxMDU3NDU3ZmU1YjA0OTA4MTE1MDI5MDYwNDA1MTYwMDA2MDQwNTE4MDgzMDM4MTg1ODg4OGYxOTM1MDUwNTA1MDE1ODAxNTYxMDVhMDU3M2Q2MDAwODAzZTNkNjAwMGZkNWI1MDUwNTA1MDUwNTY1YjgxNzNmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmMTY2MTA4ZmM4MjYwMDAwMzkwODExNTAyOTA2MDQwNTE2MDAwNjA0MDUxODA4MzAzODE4NTg4ODhmMTkzNTA1MDUwNTAxNTgwMTU2MTA1ZjA1NzNkNjAwMDgwM2UzZDYwMDBmZDViNTA1MDUwNTZmZWEyNjU2MjdhN2E3MjMxNTgyMGNmNzkxYmJmNDdkY2I2MTM5Mzc0NDA1MmE2MjBmNzcxYjQyOWYwNzFmMDJlMzY1N2NlM2U1ZjgwYWIzZjk3YmQ2NDczNmY2YzYzNDMwMDA1MTEwMDMy","b64Record":"CiAIFiocCgwIARAMGgYIgK6ZpA8SDAgBEA8aBgiArpmkDxIwZp+2wlbPRUWY2KaA2tF82axifhGka2SSy3Jm1klsuZfUZCXw84hkeiFLCKGrKYSGGgwI9f3+rAYQg7fhvAEiDwoJCLn9/qwGEOEMEgIYAiogw4PCrsOCwrfDg8K5dEY4w4LCrkrDg8KLw4PCkMODwo5SAA=="},{"b64Body":"ChAKCQi5/f6sBhDrDBIDGL0IEgIYAxjOmY6mAiICCHgyIMODwq7DgsK3w4PCuXRGOMOCwq5Kw4PCi8ODwpDDg8KOQm8KAxi+CBpKMkgKIhIgPKlWIyRJgBsGYbmM3TIWgu69Kj1CwrY8NCvtdm3mCsEKIhIg1QMr/uGoJbCFvFAuf8TJJc6OpDVthrEWmbCT9pA1S38gkKEPKApCBQiAztoDUgBaAGoLY2VsbGFyIGRvb3I=","b64Record":"CiUIFiIDGL8IKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjDoLrIjzf53dKWfRP6BDvl5kppy2w+GyKFPx7fK8ZMfTC2haD0mYbKlFmvqI4c3qrcaDAj1/f6sBhDR1b6hAyIQCgkIuf3+rAYQ6wwSAxi9CCogw4PCrsOCwrfDg8K5dEY4w4LCrkrDg8KLw4PCkMODwo4wjvPwrAJC3w4KAxi/CBKqDGCAYEBSYAQ2EGEASldgADVg4ByAYyarl+oUYQBMV4BjjdmAQxRhAJpXgGOs72A3FGEAyFeAY9Kg/iYUYQEWV4Bj4S4yOBRhAaRXWwBbYQCYYASANgNgQIEQFWEAYldgAID9W4EBkICANXP//////////////////////////xaQYCABkJKRkIA1kGAgAZCSkZBQUFBhAfJWWwBbYQDGYASANgNgIIEQFWEAsFdgAID9W4EBkICANZBgIAGQkpGQUFBQYQQjVlsAW2EBFGAEgDYDYECBEBVhAN5XYACA/VuBAZCAgDVz//////////////////////////8WkGAgAZCSkZCANZBgIAGQkpGQUFBQYQRtVlsAW2EBomAEgDYDYICBEBVhASxXYACA/VuBAZCAgDVz//////////////////////////8WkGAgAZCSkZCANXP//////////////////////////xaQYCABkJKRkIA1c///////////////////////////FpBgIAGQkpGQgDWQYCABkJKRkFBQUGEEuFZbAFthAfBgBIA2A2BAgRAVYQG6V2AAgP1bgQGQgIA1c///////////////////////////FpBgIAGQkpGQgDWQYCABkJKRkFBQUGEFp1ZbAFuBc///////////////////////////FmEI/IKQgRUCkGBAUWAAYEBRgIMDgYWIiPGTUFBQUBWAFWECOFc9YACAPj1gAP1bUIFz//////////////////////////8WYQj8YAKDgWECXVf+WwSQgRUCkGBAUWAAYEBRgIMDgYWIiPGTUFBQUBWAFWECiVc9YACAPj1gAP1bUIFz//////////////////////////8WYQj8YASDgWECrlf+WwSQgRUCkGBAUWAAYEBRgIMDgYWIiPGTUFBQUBWAFWEC2lc9YACAPj1gAP1bUIFz//////////////////////////8WYQj8YAiDgWEC/1f+WwSQgRUCkGBAUWAAYEBRgIMDgYWIiPGTUFBQUBWAFWEDK1c9YACAPj1gAP1bUIFz//////////////////////////8WYQj8YBCDgWEDUFf+WwSQgRUCkGBAUWAAYEBRgIMDgYWIiPGTUFBQUBWAFWEDfFc9YACAPj1gAP1bUIFz//////////////////////////8WYQj8YCCDgWEDoVf+WwSQgRUCkGBAUWAAYEBRgIMDgYWIiPGTUFBQUBWAFWEDzVc9YACAPj1gAP1bUIFz//////////////////////////8WYQj8YECDgWED8lf+WwSQgRUCkGBAUWAAYEBRgIMDgYWIiPGTUFBQUBWAFWEEHlc9YACAPj1gAP1bUFBQVlszc///////////////////////////FmEI/IKQgRUCkGBAUWAAYEBRgIMDgYWIiPGTUFBQUBWAFWEEaVc9YACAPj1gAP1bUFBWW4Fz//////////////////////////8WYQj8gpCBFQKQYEBRYABgQFGAgwOBhYiI8ZNQUFBQFYAVYQSzVz1gAIA+PWAA/VtQUFBWW4Nz//////////////////////////8WYQj8gpCBFQKQYEBRYABgQFGAgwOBhYiI8ZNQUFBQFYAVYQT+Vz1gAIA+PWAA/VtQgnP//////////////////////////xZhCPxgAoOBYQUjV/5bBJCBFQKQYEBRYABgQFGAgwOBhYiI8ZNQUFBQFYAVYQVPVz1gAIA+PWAA/VtQgXP//////////////////////////xZhCPxgBIOBYQV0V/5bBJCBFQKQYEBRYABgQFGAgwOBhYiI8ZNQUFBQFYAVYQWgVz1gAIA+PWAA/VtQUFBQUFZbgXP//////////////////////////xZhCPyCYAADkIEVApBgQFFgAGBAUYCDA4GFiIjxk1BQUFAVgBVhBfBXPWAAgD49YAD9W1BQUFb+omVienpyMVggz3kbv0fcthOTdEBSpiD3cbQp8HHwLjZXzj5fgKs/l71kc29sY0MABREAMiKAAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAowJoMOgMYvwhKFgoUAAAAAAAAAAAAAAAAAAAAAAAABD9yBwoDGL8IEAFSRQoJCgIYAxDU3c8iCgoKAhhiELz3tcgDCgoKAxigBhDGiK43CgoKAxihBhDGiK43CgsKAxi9CBCv5uHZBAoHCgMYvwgQFA=="},{"b64Body":"ChAKCQi6/f6sBhDvDBIDGL0IEgIYAxjgrLEDIgIIeDIgw4PCrsOCwrfDg8K5dEY4w4LCrkrDg8KLw4PCkMODwo46TwoDGL8IEKCNBiJErO9gNwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQ9AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAo=","b64Record":"CiUIFiIDGL8IKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjDiQHw4vrlf0FlijvBRl0Wcpa7jBVNJPRV+YK2aLLnU0NdaTb8iZ1LIQe1Aug04j5QaDAj2/f6sBhDSkafGASIQCgkIuv3+rAYQ7wwSAxi9CCogw4PCrsOCwrfDg8K5dEY4w4LCrkrDg8KLw4PCkMODwo4wgNfaAjqMAgoDGL8IIoACAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACiA8QRSIAoJCgIYYhCArrUFCgoKAxi9CBDrrbUFCgcKAxi/CBAT"}]},"hscsEvm010ReceiverMustSignContractTx":{"placeholderNum":1088,"encodedItems":[{"b64Body":"Cg8KCQi//f6sBhD/DBICGAISAhgDGPuV9hQiAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjlo0CiISII7/xuc9tQnklLimHYQad+7h1v+n9fAXx6VN6BP3sPDKEIDo7aG6AUABSgUIgM7aAw==","b64Record":"CiUIFhIDGMEIKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjDbGyAw6dPETfBf1I9uoLlcG2UA4Vx9mhg+qnNKFCXoCiFIh8VZ75ESFb7Jdk5JeRAaCwj7/f6sBhDCuoRKIg8KCQi//f6sBhD/DBICGAIqIMODwq7DgsK3w4PCuXRGOMOCwq5Kw4PCi8ODwpDDg8KOUhsKCwoCGAIQ/8/bw/QCCgwKAxjBCBCA0NvD9AI="},{"b64Body":"Cg8KCQi//f6sBhCHDRICGAISAhgDGP7LzSwiAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjooBjwESDAj7y9mwBhDh4Oy6AhptCiISICpZm41UjvGR20o+HUS25RaMrHsZSpv8SeHK8ApLin/dCiM6IQJGlX5JcBc7+EKXx//R2sMWRmUhVmDKX0qGCU7D566vRgoiEiCSLhot2AesT4eURO/u58plLPt1pvs7oFO2lmvdaUuSLyIMSGVsbG8gV29ybGQhKgAyAA==","b64Record":"CiUIFhoDGMIIKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjCiV1f7kDJ+C/smWp8sHHJ22SANQ7czAe0GXmFYQt82bQlWzira7v7VTRSLxGNUvfcaDAj7/f6sBhC+9NjLAiIPCgkIv/3+rAYQhw0SAhgCKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjlIA"},{"b64Body":"Cg8KCQjA/f6sBhCLDRICGAISAhgDGPOGlzoiAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjpoBghkKAxjCCCL6GDYwODA2MDQwNTI2MTA2MmE4MDYxMDAxMzYwMDAzOTYwMDBmM2ZlNjA4MDYwNDA1MjYwMDQzNjEwNjEwMDRhNTc2MDAwMzU2MGUwMWM4MDYzMjZhYjk3ZWExNDYxMDA0YzU3ODA2MzhkZDk4MDQzMTQ2MTAwOWE1NzgwNjNhY2VmNjAzNzE0NjEwMGM4NTc4MDYzZDJhMGZlMjYxNDYxMDExNjU3ODA2M2UxMmUzMjM4MTQ2MTAxYTQ1NzViMDA1YjYxMDA5ODYwMDQ4MDM2MDM2MDQwODExMDE1NjEwMDYyNTc2MDAwODBmZDViODEwMTkwODA4MDM1NzNmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmMTY5MDYwMjAwMTkwOTI5MTkwODAzNTkwNjAyMDAxOTA5MjkxOTA1MDUwNTA2MTAxZjI1NjViMDA1YjYxMDBjNjYwMDQ4MDM2MDM2MDIwODExMDE1NjEwMGIwNTc2MDAwODBmZDViODEwMTkwODA4MDM1OTA2MDIwMDE5MDkyOTE5MDUwNTA1MDYxMDQyMzU2NWIwMDViNjEwMTE0NjAwNDgwMzYwMzYwNDA4MTEwMTU2MTAwZGU1NzYwMDA4MGZkNWI4MTAxOTA4MDgwMzU3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNjkwNjAyMDAxOTA5MjkxOTA4MDM1OTA2MDIwMDE5MDkyOTE5MDUwNTA1MDYxMDQ2ZDU2NWIwMDViNjEwMWEyNjAwNDgwMzYwMzYwODA4MTEwMTU2MTAxMmM1NzYwMDA4MGZkNWI4MTAxOTA4MDgwMzU3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNjkwNjAyMDAxOTA5MjkxOTA4MDM1NzNmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmMTY5MDYwMjAwMTkwOTI5MTkwODAzNTczZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjE2OTA2MDIwMDE5MDkyOTE5MDgwMzU5MDYwMjAwMTkwOTI5MTkwNTA1MDUwNjEwNGI4NTY1YjAwNWI2MTAxZjA2MDA0ODAzNjAzNjA0MDgxMTAxNTYxMDFiYTU3NjAwMDgwZmQ1YjgxMDE5MDgwODAzNTczZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjE2OTA2MDIwMDE5MDkyOTE5MDgwMzU5MDYwMjAwMTkwOTI5MTkwNTA1MDUwNjEwNWE3NTY1YjAwNWI4MTczZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjE2NjEwOGZjODI5MDgxMTUwMjkwNjA0MDUxNjAwMDYwNDA1MTgwODMwMzgxODU4ODg4ZjE5MzUwNTA1MDUwMTU4MDE1NjEwMjM4NTczZDYwMDA4MDNlM2Q2MDAwZmQ1YjUwODE3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNjYxMDhmYzYwMDI4MzgxNjEwMjVkNTdmZTViMDQ5MDgxMTUwMjkwNjA0MDUxNjAwMDYwNDA1MTgwODMwMzgxODU4ODg4ZjE5MzUwNTA1MDUwMTU4MDE1NjEwMjg5NTczZDYwMDA4MDNlM2Q2MDAwZmQ1YjUwODE3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNjYxMDhmYzYwMDQ4MzgxNjEwMmFlNTdmZTViMDQ5MDgxMTUwMjkwNjA0MDUxNjAwMDYwNDA1MTgwODMwMzgxODU4ODg4ZjE5MzUwNTA1MDUwMTU4MDE1NjEwMmRhNTczZDYwMDA4MDNlM2Q2MDAwZmQ1YjUwODE3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNjYxMDhmYzYwMDg4MzgxNjEwMmZmNTdmZTViMDQ5MDgxMTUwMjkwNjA0MDUxNjAwMDYwNDA1MTgwODMwMzgxODU4ODg4ZjE5MzUwNTA1MDUwMTU4MDE1NjEwMzJiNTczZDYwMDA4MDNlM2Q2MDAwZmQ1YjUwODE3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNjYxMDhmYzYwMTA4MzgxNjEwMzUwNTdmZTViMDQ5MDgxMTUwMjkwNjA0MDUxNjAwMDYwNDA1MTgwODMwMzgxODU4ODg4ZjE5MzUwNTA1MDUwMTU4MDE1NjEwMzdjNTczZDYwMDA4MDNlM2Q2MDAwZmQ1YjUwODE3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNjYxMDhmYzYwMjA4MzgxNjEwM2ExNTdmZTViMDQ5MDgxMTUwMjkwNjA0MDUxNjAwMDYwNDA1MTgwODMwMzgxODU4ODg4ZjE5MzUwNTA1MDUwMTU4MDE1NjEwM2NkNTczZDYwMDA4MDNlM2Q2MDAwZmQ1YjUwODE3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNjYxMDhmYzYwNDA4MzgxNjEwM2YyNTdmZTViMDQ5MDgxMTUwMjkwNjA0MDUxNjAwMDYwNDA1MTgwODMwMzgxODU4ODg4ZjE5MzUwNTA1MDUwMTU4MDE1NjEwNDFlNTczZDYwMDA4MDNlM2Q2MDAwZmQ1YjUwNTA1MDU2NWIzMzczZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjE2NjEwOGZjODI5MDgxMTUwMjkwNjA0MDUxNjAwMDYwNDA1MTgwODMwMzgxODU4ODg4ZjE5MzUwNTA1MDUwMTU4MDE1NjEwNDY5NTczZDYwMDA4MDNlM2Q2MDAwZmQ1YjUwNTA1NjViODE3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNjYxMDhmYzgyOTA4MTE1MDI5MDYwNDA1MTYwMDA2MDQwNTE4MDgzMDM4MTg1ODg4OGYxOTM1MDUwNTA1MDE1ODAxNTYxMDRiMzU3M2Q2MDAwODAzZTNkNjAwMGZkNWI1MDUwNTA1NjViODM3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNjYxMDhmYzgyOTA4MTE1MDI5MDYwNDA1MTYwMDA2MDQwNTE4MDgzMDM4MTg1ODg4OGYxOTM1MDUwNTA1MDE1ODAxNTYxMDRmZTU3M2Q2MDAwODAzZTNkNjAwMGZkNWI1MDgyNzNmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmMTY2MTA4ZmM2MDAyODM4MTYxMDUyMzU3ZmU1YjA0OTA4MTE1MDI5MDYwNDA1MTYwMDA2MDQwNTE4MDgzMDM4MTg1ODg4OGYxOTM1MDUwNTA1MDE1ODAxNTYxMDU0ZjU3M2Q2MDAwODAzZTNkNjAwMGZkNWI1MDgxNzNmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmMTY2MTA4ZmM2MDA0ODM4MTYxMDU3NDU3ZmU1YjA0OTA4MTE1MDI5MDYwNDA1MTYwMDA2MDQwNTE4MDgzMDM4MTg1ODg4OGYxOTM1MDUwNTA1MDE1ODAxNTYxMDVhMDU3M2Q2MDAwODAzZTNkNjAwMGZkNWI1MDUwNTA1MDUwNTY1YjgxNzNmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmMTY2MTA4ZmM4MjYwMDAwMzkwODExNTAyOTA2MDQwNTE2MDAwNjA0MDUxODA4MzAzODE4NTg4ODhmMTkzNTA1MDUwNTAxNTgwMTU2MTA1ZjA1NzNkNjAwMDgwM2UzZDYwMDBmZDViNTA1MDUwNTZmZWEyNjU2MjdhN2E3MjMxNTgyMGNmNzkxYmJmNDdkY2I2MTM5Mzc0NDA1MmE2MjBmNzcxYjQyOWYwNzFmMDJlMzY1N2NlM2U1ZjgwYWIzZjk3YmQ2NDczNmY2YzYzNDMwMDA1MTEwMDMy","b64Record":"CiAIFiocCgwIARAMGgYIgK6ZpA8SDAgBEA8aBgiArpmkDxIwaYui3Wklr5CtizSoGGSAJ6+T2Wn/cwJDe/AyrLBI6SJBShyyLOIP2nSgp35o2IhEGgsI/P3+rAYQy4nEUyIPCgkIwP3+rAYQiw0SAhgCKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjlIA"},{"b64Body":"ChAKCQjA/f6sBhCNDRIDGMEIEgIYAxiW+66dAiICCHgyIMODwq7DgsK3w4PCuXRGOMOCwq5Kw4PCi8ODwpDDg8KOQksKAxjCCBoiEiD6NUgVkIZhyPXKDGw51h/0QK2K/vzPwkCxj2iggOS7/yCQoQ8ogMivoCVCBQiAztoDUgBaAGoLY2VsbGFyIGRvb3I=","b64Record":"CiUIFiIDGMMIKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjB06HOdUspOerZH4W3Ewn3oVv4O47oytX691C4CQlqH9FrnS9Z0iZXObN24roWIJzUaDAj8/f6sBhC/wIHVAiIQCgkIwP3+rAYQjQ0SAxjBCCogw4PCrsOCwrfDg8K5dEY4w4LCrkrDg8KLw4PCkMODwo4w1tSRpAJC3w4KAxjDCBKqDGCAYEBSYAQ2EGEASldgADVg4ByAYyarl+oUYQBMV4BjjdmAQxRhAJpXgGOs72A3FGEAyFeAY9Kg/iYUYQEWV4Bj4S4yOBRhAaRXWwBbYQCYYASANgNgQIEQFWEAYldgAID9W4EBkICANXP//////////////////////////xaQYCABkJKRkIA1kGAgAZCSkZBQUFBhAfJWWwBbYQDGYASANgNgIIEQFWEAsFdgAID9W4EBkICANZBgIAGQkpGQUFBQYQQjVlsAW2EBFGAEgDYDYECBEBVhAN5XYACA/VuBAZCAgDVz//////////////////////////8WkGAgAZCSkZCANZBgIAGQkpGQUFBQYQRtVlsAW2EBomAEgDYDYICBEBVhASxXYACA/VuBAZCAgDVz//////////////////////////8WkGAgAZCSkZCANXP//////////////////////////xaQYCABkJKRkIA1c///////////////////////////FpBgIAGQkpGQgDWQYCABkJKRkFBQUGEEuFZbAFthAfBgBIA2A2BAgRAVYQG6V2AAgP1bgQGQgIA1c///////////////////////////FpBgIAGQkpGQgDWQYCABkJKRkFBQUGEFp1ZbAFuBc///////////////////////////FmEI/IKQgRUCkGBAUWAAYEBRgIMDgYWIiPGTUFBQUBWAFWECOFc9YACAPj1gAP1bUIFz//////////////////////////8WYQj8YAKDgWECXVf+WwSQgRUCkGBAUWAAYEBRgIMDgYWIiPGTUFBQUBWAFWECiVc9YACAPj1gAP1bUIFz//////////////////////////8WYQj8YASDgWECrlf+WwSQgRUCkGBAUWAAYEBRgIMDgYWIiPGTUFBQUBWAFWEC2lc9YACAPj1gAP1bUIFz//////////////////////////8WYQj8YAiDgWEC/1f+WwSQgRUCkGBAUWAAYEBRgIMDgYWIiPGTUFBQUBWAFWEDK1c9YACAPj1gAP1bUIFz//////////////////////////8WYQj8YBCDgWEDUFf+WwSQgRUCkGBAUWAAYEBRgIMDgYWIiPGTUFBQUBWAFWEDfFc9YACAPj1gAP1bUIFz//////////////////////////8WYQj8YCCDgWEDoVf+WwSQgRUCkGBAUWAAYEBRgIMDgYWIiPGTUFBQUBWAFWEDzVc9YACAPj1gAP1bUIFz//////////////////////////8WYQj8YECDgWED8lf+WwSQgRUCkGBAUWAAYEBRgIMDgYWIiPGTUFBQUBWAFWEEHlc9YACAPj1gAP1bUFBQVlszc///////////////////////////FmEI/IKQgRUCkGBAUWAAYEBRgIMDgYWIiPGTUFBQUBWAFWEEaVc9YACAPj1gAP1bUFBWW4Fz//////////////////////////8WYQj8gpCBFQKQYEBRYABgQFGAgwOBhYiI8ZNQUFBQFYAVYQSzVz1gAIA+PWAA/VtQUFBWW4Nz//////////////////////////8WYQj8gpCBFQKQYEBRYABgQFGAgwOBhYiI8ZNQUFBQFYAVYQT+Vz1gAIA+PWAA/VtQgnP//////////////////////////xZhCPxgAoOBYQUjV/5bBJCBFQKQYEBRYABgQFGAgwOBhYiI8ZNQUFBQFYAVYQVPVz1gAIA+PWAA/VtQgXP//////////////////////////xZhCPxgBIOBYQV0V/5bBJCBFQKQYEBRYABgQFGAgwOBhYiI8ZNQUFBQFYAVYQWgVz1gAIA+PWAA/VtQUFBQUFZbgXP//////////////////////////xZhCPyCYAADkIEVApBgQFFgAGBAUYCDA4GFiIjxk1BQUFAVgBVhBfBXPWAAgD49YAD9W1BQUFb+omVienpyMVggz3kbv0fcthOTdEBSpiD3cbQp8HHwLjZXzj5fgKs/l71kc29sY0MABREAMiKAAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAowJoMOgMYwwhKFgoUAAAAAAAAAAAAAAAAAAAAAAAABENyBwoDGMMIEAFSSQoJCgIYAxCY4okUCgoKAhhiEODciMYDCgoKAxigBhCatYg3CgoKAxihBhCatYg3CgsKAxjBCBCruYKJTwoLCgMYwwgQgJDfwEo="},{"b64Body":"ChAKCQjB/f6sBhCRDRIDGMEIEgIYAxjgrLEDIgIIeDIgw4PCrsOCwrfDg8K5dEY4w4LCrkrDg8KLw4PCkMODwo46TwoDGMMIEKCNBiJErO9gNwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAARBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAASoF8gA=","b64Record":"CiUIFiIDGMMIKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjDgt32WmGVPX8bBGTnSuIpZ2idhHksAmLyBUMetuib7ieO+IUIR1WrLbbQycZ/cPP8aDAj9/f6sBhDlqrzjAiIQCgkIwf3+rAYQkQ0SAxjBCCogw4PCrsOCwrfDg8K5dEY4w4LCrkrDg8KLw4PCkMODwo4wgNfaAjqMAgoDGMMIIoACAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACiA8QRSJQoJCgIYYhCArrUFCgsKAxjBCBCAmvqaJQoLCgMYwwgQ/8evoCU="}]},"InsufficientGas":{"placeholderNum":1092,"encodedItems":[{"b64Body":"Cg8KCQjG/f6sBhChDRICGAISAhgDGP7LzSwiAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjooBjwESDAiCzNmwBhDXsZLdARptCiISIK7wwRuE+ORvx+H2N6/W9yvXyqkGCdsJ00e5OSv/TTajCiM6IQKovpJa+tFXkSNx06GUhL/3Z3gEUKd3TsvSbu/NN2cWnwoiEiCeQiXDbf6PKMxYDyKr+uF5n7BZZawTu7x54tbiOZPUtyIMSGVsbG8gV29ybGQhKgAyAA==","b64Record":"CiUIFhoDGMUIKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjC6aymcSuO/sAut10sWVzVbZ2pmDj/9IoNSGiYb8jlJh+3FucoRP1RLbdTI4Y/6vLYaDAiC/v6sBhD219LnASIPCgkIxv3+rAYQoQ0SAhgCKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjlIA"},{"b64Body":"Cg8KCQjG/f6sBhClDRICGAISAhgDGKGwpi4iAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjpoB3AMKAxjFCCLUAzYwODA2MDQwNTI2MDBmNjAwMDU1MzQ4MDE1NjEwMDE1NTc2MDAwODBmZDViNTA2MGM2ODA2MTAwMjQ2MDAwMzk2MDAwZjNmZTYwODA2MDQwNTIzNDgwMTU2MDBmNTc2MDAwODBmZDViNTA2MDA0MzYxMDYwMzI1NzYwMDAzNTYwZTAxYzgwNjM2MGZlNDdiMTE0NjAzNzU3ODA2MzZkNGNlNjNjMTQ2MDYyNTc1YjYwMDA4MGZkNWI2MDYwNjAwNDgwMzYwMzYwMjA4MTEwMTU2MDRiNTc2MDAwODBmZDViODEwMTkwODA4MDM1OTA2MDIwMDE5MDkyOTE5MDUwNTA1MDYwN2U1NjViMDA1YjYwNjg2MDg4NTY1YjYwNDA1MTgwODI4MTUyNjAyMDAxOTE1MDUwNjA0MDUxODA5MTAzOTBmMzViODA2MDAwODE5MDU1NTA1MDU2NWI2MDAwODA1NDkwNTA5MDU2ZmVhMjY1NjI3YTdhNzIzMTU4MjAyODIzMzhiZmVmZDNmOTk5ZGYxYzA3MjBjODViN2M3OTMzNTFlNTk3NWJiNDk1OTkyZjEyMTY5MjZlZWE4OTE1NjQ3MzZmNmM2MzQzMDAwNTBiMDAzMg==","b64Record":"CiAIFiocCgwIARAMGgYIgK6ZpA8SDAgBEA8aBgiArpmkDxIw4Rixn9lqiti50iPYUtq3dLg4qE52rEUPMLT2FmiOw0p3i+D33fLCBKevgnUyhOD5GgsIg/7+rAYQqoHiECIPCgkIxv3+rAYQpQ0SAhgCKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjlIA"},{"b64Body":"Cg8KCQjH/f6sBhCnDRICGAISAhgDGK/Dto0EIgIIeDIgw4PCrsOCwrfDg8K5dEY4w4LCrkrDg8KLw4PCkMODwo5ClgEKAxjFCBpzKnEIAhJtCiISIFrbkDBHLyqXMnRoROIj8G3s3ga1xudjcvs0B1wZPEMeCiM6IQL6Y57ESGXJ1quTcdDnELOYMpTI9snY9JqIVzM2JsmbeQoiEiAdz5VnNR7kqxrIOZVQOzq+CeD2YhA3jgFh27PJUPVQriCQoQ9CBQiAztoDUgBaAGoLY2VsbGFyIGRvb3I=","b64Record":"CiUIFiIDGMYIKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjDCaPQeERiIViopIYt2fUiL+c8SsiYQ9CGlIeSi1d20BXERNB+YnzX5GrCf0Q0nY8saDAiD/v6sBhCIutL1ASIPCgkIx/3+rAYQpw0SAhgCKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjjDA2eIGQvsDCgMYxggSxgFggGBAUjSAFWAPV2AAgP1bUGAENhBgMldgADVg4ByAY2D+R7EUYDdXgGNtTOY8FGBiV1tgAID9W2BgYASANgNgIIEQFWBLV2AAgP1bgQGQgIA1kGAgAZCSkZBQUFBgflZbAFtgaGCIVltgQFGAgoFSYCABkVBQYEBRgJEDkPNbgGAAgZBVUFBWW2AAgFSQUJBW/qJlYnp6cjFYICgjOL/v0/mZ3xwHIMhbfHkzUeWXW7SVmS8SFpJu6okVZHNvbGNDAAULADIigAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKMCaDDoDGMYIShYKFAAAAAAAAAAAAAAAAAAAAAAAAARGcgcKAxjGCBABUhYKCQoCGAIQ/7LFDQoJCgIYYhCAs8UN"}]},"NonPayable":{"placeholderNum":1100,"encodedItems":[{"b64Body":"Cg8KCQjP/f6sBhDWDRICGAISAhgDGP7LzSwiAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjooBjwESDAiLzNmwBhCZma74AhptCiISIOJL3nYCDc8RmZyXn9U8AUfgZ14G1BLmue1aH80XKD2WCiM6IQIZ2p1smmrbX5LqAPyTfXeyWcq3ar06uN08qMm4PycwogoiEiB6/HDMRnkcVwS+E1CA0Cs2DK5Fu4DVG33HbLf9oAcgNCIMSGVsbG8gV29ybGQhKgAyAA==","b64Record":"CiUIFhoDGM0IKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjB4vS1H+pDpjSgcAyXM24+DfJ/TiyHb3GGe3Ta1SCiVPVU+ipxYVf8S1WmClw2B0r8aDAiL/v6sBhD13av7AiIPCgkIz/3+rAYQ1g0SAhgCKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjlIA"},{"b64Body":"Cg8KCQjQ/f6sBhDaDRICGAISAhgDGIi18DMiAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjpoB3A0KAxjNCCLUDTYwODA2MDQwNTIzNDgwMTU2MTAwMTA1NzYwMDA4MGZkNWI1MDYxMDM0YTgwNjEwMDIwNjAwMDM5NjAwMGYzMDA2MDgwNjA0MDUyNjAwNDM2MTA2MTAwNTc1NzYwMDAzNTdjMDEwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDkwMDQ2M2ZmZmZmZmZmMTY4MDYzMmYxOWMwNGExNDYxMDA1YzU3ODA2MzM4Y2M0ODMxMTQ2MTAwODc1NzgwNjNlZmM4MWE4YzE0NjEwMGRlNTc1YjYwMDA4MGZkNWIzNDgwMTU2MTAwNjg1NzYwMDA4MGZkNWI1MDYxMDA3MTYxMDBmNTU2NWI2MDQwNTE4MDgyODE1MjYwMjAwMTkxNTA1MDYwNDA1MTgwOTEwMzkwZjM1YjM0ODAxNTYxMDA5MzU3NjAwMDgwZmQ1YjUwNjEwMDljNjEwMWJjNTY1YjYwNDA1MTgwODI3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNjczZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjE2ODE1MjYwMjAwMTkxNTA1MDYwNDA1MTgwOTEwMzkwZjM1YjM0ODAxNTYxMDBlYTU3NjAwMDgwZmQ1YjUwNjEwMGYzNjEwMWU1NTY1YjAwNWI2MDAwODA2MDAwOTA1NDkwNjEwMTAwMGE5MDA0NzNmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmMTY3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNjYzMDg2OTQ5Yjc2MDQwNTE4MTYzZmZmZmZmZmYxNjdjMDEwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAyODE1MjYwMDQwMTYwMjA2MDQwNTE4MDgzMDM4MTYwMDA4NzgwM2IxNTgwMTU2MTAxN2M1NzYwMDA4MGZkNWI1MDVhZjExNTgwMTU2MTAxOTA1NzNkNjAwMDgwM2UzZDYwMDBmZDViNTA1MDUwNTA2MDQwNTEzZDYwMjA4MTEwMTU2MTAxYTY1NzYwMDA4MGZkNWI4MTAxOTA4MDgwNTE5MDYwMjAwMTkwOTI5MTkwNTA1MDUwOTA1MDkwNTY1YjYwMDA4MDYwMDA5MDU0OTA2MTAxMDAwYTkwMDQ3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNjkwNTA5MDU2NWI2MTAxZWQ2MTAyNGI1NjViNjA0MDUxODA5MTAzOTA2MDAwZjA4MDE1ODAxNTYxMDIwOTU3M2Q2MDAwODAzZTNkNjAwMGZkNWI1MDYwMDA4MDYxMDEwMDBhODE1NDgxNzNmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmMDIxOTE2OTA4MzczZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjE2MDIxNzkwNTU1MDU2NWI2MDQwNTE2MGM0ODA2MTAyNWI4MzM5MDE5MDU2MDA2MDgwNjA0MDUyNjAwODYwMDA1NTM0ODAxNTYwMTQ1NzYwMDA4MGZkNWI1MDYwYTE4MDYxMDAyMzYwMDAzOTYwMDBmMzAwNjA4MDYwNDA1MjYwMDQzNjEwNjAzZjU3NjAwMDM1N2MwMTAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwOTAwNDYzZmZmZmZmZmYxNjgwNjMwODY5NDliNzE0NjA0NDU3NWI2MDAwODBmZDViMzQ4MDE1NjA0ZjU3NjAwMDgwZmQ1YjUwNjA1NjYwNmM1NjViNjA0MDUxODA4MjgxNTI2MDIwMDE5MTUwNTA2MDQwNTE4MDkxMDM5MGYzNWI2MDAwNjAwNzkwNTA5MDU2MDBhMTY1NjI3YTdhNzIzMDU4MjAyZTA5N2JiZTEyMmFkNWQ4NmU4NDBiZTYwYWFiNDFkMTYwYWQ1Yjg2NzQ1YWE3YWEwMDk5YTZiYmZjMjY1MjE4MDAyOWExNjU2MjdhN2E3MjMwNTgyMDZjZjdlYTlkNGU1MDY4ODZiNjAyZmY3YTYyODQwMTYxMTQzN2NiZmQwZGZjYmQ1YmVlYzM3NzU3MDcwZGE1YjMwMDI5","b64Record":"CiAIFiocCgwIARAMGgYIgK6ZpA8SDAgBEA8aBgiArpmkDxIw+1fplWFGEXL8ychS1YHC+2UajlsdNh8bYUJUi53lnMh9inwmc/ZJIy8VjprayZahGgwIjP7+rAYQ5KLmnwEiDwoJCND9/qwGENoNEgIYAiogw4PCrsOCwrfDg8K5dEY4w4LCrkrDg8KLw4PCkMODwo5SAA=="},{"b64Body":"Cg8KCQjQ/f6sBhDcDRICGAISAhgDGJb7rp0CIgIIeDIgw4PCrsOCwrfDg8K5dEY4w4LCrkrDg8KLw4PCkMODwo5CRQoDGM0IGiISIKBoHYp4ez7/Yk4PEdv60StLAbm0H16b0/70vN0EIPgVIJChD0IFCIDO2gNSAFoAagtjZWxsYXIgZG9vcg==","b64Record":"CiUIFiIDGM4IKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjBo167il5HVlOlAJKyzUXcF0T+UDII4lQaZpxuvx0rTb6Hp00pgi48vxLuDm6xMgGIaDAiM/v6sBhCrsOKEAyIPCgkI0P3+rAYQ3A0SAhgCKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjjDA2eIGQv8ICgMYzggSygZggGBAUmAENhBhAFdXYAA1fAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAkARj/////xaAYy8ZwEoUYQBcV4BjOMxIMRRhAIdXgGPvyBqMFGEA3ldbYACA/Vs0gBVhAGhXYACA/VtQYQBxYQD1VltgQFGAgoFSYCABkVBQYEBRgJEDkPNbNIAVYQCTV2AAgP1bUGEAnGEBvFZbYEBRgIJz//////////////////////////8Wc///////////////////////////FoFSYCABkVBQYEBRgJEDkPNbNIAVYQDqV2AAgP1bUGEA82EB5VZbAFtgAIBgAJBUkGEBAAqQBHP//////////////////////////xZz//////////////////////////8WYwhpSbdgQFGBY/////8WfAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAoFSYAQBYCBgQFGAgwOBYACHgDsVgBVhAXxXYACA/VtQWvEVgBVhAZBXPWAAgD49YAD9W1BQUFBgQFE9YCCBEBVhAaZXYACA/VuBAZCAgFGQYCABkJKRkFBQUJBQkFZbYACAYACQVJBhAQAKkARz//////////////////////////8WkFCQVlthAe1hAktWW2BAUYCRA5BgAPCAFYAVYQIJVz1gAIA+PWAA/VtQYACAYQEACoFUgXP//////////////////////////wIZFpCDc///////////////////////////FgIXkFVQVltgQFFgxIBhAluDOQGQVgBggGBAUmAIYABVNIAVYBRXYACA/VtQYKGAYQAjYAA5YADzAGCAYEBSYAQ2EGA/V2AANXwBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAJAEY/////8WgGMIaUm3FGBEV1tgAID9WzSAFWBPV2AAgP1bUGBWYGxWW2BAUYCCgVJgIAGRUFBgQFGAkQOQ81tgAGAHkFCQVgChZWJ6enIwWCAuCXu+EirV2G6EC+YKq0HRYK1bhnRap6oAmaa7/CZSGAApoWVienpyMFggbPfqnU5QaIa2Av96YoQBYRQ3y/0N/L1b7sN3VwcNpbMAKSKAAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAowJoMOgMYzghKFgoUAAAAAAAAAAAAAAAAAAAAAAAABE5yBwoDGM4IEAFSFgoJCgIYAhD/ssUNCgkKAhhiEICzxQ0="},{"b64Body":"Cg8KCQjR/f6sBhDeDRICGAISAhgDGOCssQMiAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjjoSCgMYzggQoI0GGOgHIgTvyBqM","b64Record":"CiUIISIDGM4IKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjCngeGkbM/4GFL1/SqHxJMhPWOVtq/d+x5E25UhxlgGrxrTku3jYMh2AqDKaaviTg4aDAiN/v6sBhCMx8+pASIPCgkI0f3+rAYQ3g0SAhgCKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjjCA19oCOggaAjB4KIDxBFIWCgkKAhgCEP+ttQUKCQoCGGIQgK61BQ=="}]},"ContractTransferToSigReqAccountWithoutKeyFails":{"placeholderNum":1106,"encodedItems":[{"b64Body":"Cg8KCQja/f6sBhCsDhICGAISAhgDGPuV9hQiAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjlo0CiISIH5bK40qxu6mIJ3UhIrQOieYI6gmGlodtjhMyDX9s18mEICglKWNHUABSgUIgM7aAw==","b64Record":"CiUIFhIDGNMIKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjDHM2sTlBog/eHhJvy7f58JOyaG4cg7XETF6Dblp1Ya2HVFZx0FoHLhPyKyZsn36WIaDAiW/v6sBhDh98y4AiIPCgkI2v3+rAYQrA4SAhgCKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjlIbCgsKAhgCEP+/qMqaOgoMCgMY0wgQgMCoypo6"},{"b64Body":"Cg8KCQjb/f6sBhC0DhICGAISAhgDGP7LzSwiAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjooBjgESCwiXzNmwBhDB6sY+Gm0KIhIg5DI+Jph9c844gQJJfl7Umfyn6JpkcjLFS2EHgIbL0Q0KIzohA6NmgICEl4eHNpsqd73cJ2awsk16Ra5AKaUAyKiOMw+kCiISIC95gWDEV31N2PVkFYYPm0mW8r80+u92xjlVKbPVYtsfIgxIZWxsbyBXb3JsZCEqADIA","b64Record":"CiUIFhoDGNQIKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjA0EU3jrYdg/SbQKHAX7FM52fWOQVOEFTpTL8HxfVwJotgcNRW51yJ9ObZ8//pCAM4aCwiX/v6sBhC/vdlAIg8KCQjb/f6sBhC0DhICGAIqIMODwq7DgsK3w4PCuXRGOMOCwq5Kw4PCi8ODwpDDg8KOUgA="},{"b64Body":"Cg8KCQjb/f6sBhC4DhICGAISAhgDGPOGlzoiAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjpoBghkKAxjUCCL6GDYwODA2MDQwNTI2MTA2MmE4MDYxMDAxMzYwMDAzOTYwMDBmM2ZlNjA4MDYwNDA1MjYwMDQzNjEwNjEwMDRhNTc2MDAwMzU2MGUwMWM4MDYzMjZhYjk3ZWExNDYxMDA0YzU3ODA2MzhkZDk4MDQzMTQ2MTAwOWE1NzgwNjNhY2VmNjAzNzE0NjEwMGM4NTc4MDYzZDJhMGZlMjYxNDYxMDExNjU3ODA2M2UxMmUzMjM4MTQ2MTAxYTQ1NzViMDA1YjYxMDA5ODYwMDQ4MDM2MDM2MDQwODExMDE1NjEwMDYyNTc2MDAwODBmZDViODEwMTkwODA4MDM1NzNmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmMTY5MDYwMjAwMTkwOTI5MTkwODAzNTkwNjAyMDAxOTA5MjkxOTA1MDUwNTA2MTAxZjI1NjViMDA1YjYxMDBjNjYwMDQ4MDM2MDM2MDIwODExMDE1NjEwMGIwNTc2MDAwODBmZDViODEwMTkwODA4MDM1OTA2MDIwMDE5MDkyOTE5MDUwNTA1MDYxMDQyMzU2NWIwMDViNjEwMTE0NjAwNDgwMzYwMzYwNDA4MTEwMTU2MTAwZGU1NzYwMDA4MGZkNWI4MTAxOTA4MDgwMzU3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNjkwNjAyMDAxOTA5MjkxOTA4MDM1OTA2MDIwMDE5MDkyOTE5MDUwNTA1MDYxMDQ2ZDU2NWIwMDViNjEwMWEyNjAwNDgwMzYwMzYwODA4MTEwMTU2MTAxMmM1NzYwMDA4MGZkNWI4MTAxOTA4MDgwMzU3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNjkwNjAyMDAxOTA5MjkxOTA4MDM1NzNmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmMTY5MDYwMjAwMTkwOTI5MTkwODAzNTczZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjE2OTA2MDIwMDE5MDkyOTE5MDgwMzU5MDYwMjAwMTkwOTI5MTkwNTA1MDUwNjEwNGI4NTY1YjAwNWI2MTAxZjA2MDA0ODAzNjAzNjA0MDgxMTAxNTYxMDFiYTU3NjAwMDgwZmQ1YjgxMDE5MDgwODAzNTczZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjE2OTA2MDIwMDE5MDkyOTE5MDgwMzU5MDYwMjAwMTkwOTI5MTkwNTA1MDUwNjEwNWE3NTY1YjAwNWI4MTczZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjE2NjEwOGZjODI5MDgxMTUwMjkwNjA0MDUxNjAwMDYwNDA1MTgwODMwMzgxODU4ODg4ZjE5MzUwNTA1MDUwMTU4MDE1NjEwMjM4NTczZDYwMDA4MDNlM2Q2MDAwZmQ1YjUwODE3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNjYxMDhmYzYwMDI4MzgxNjEwMjVkNTdmZTViMDQ5MDgxMTUwMjkwNjA0MDUxNjAwMDYwNDA1MTgwODMwMzgxODU4ODg4ZjE5MzUwNTA1MDUwMTU4MDE1NjEwMjg5NTczZDYwMDA4MDNlM2Q2MDAwZmQ1YjUwODE3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNjYxMDhmYzYwMDQ4MzgxNjEwMmFlNTdmZTViMDQ5MDgxMTUwMjkwNjA0MDUxNjAwMDYwNDA1MTgwODMwMzgxODU4ODg4ZjE5MzUwNTA1MDUwMTU4MDE1NjEwMmRhNTczZDYwMDA4MDNlM2Q2MDAwZmQ1YjUwODE3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNjYxMDhmYzYwMDg4MzgxNjEwMmZmNTdmZTViMDQ5MDgxMTUwMjkwNjA0MDUxNjAwMDYwNDA1MTgwODMwMzgxODU4ODg4ZjE5MzUwNTA1MDUwMTU4MDE1NjEwMzJiNTczZDYwMDA4MDNlM2Q2MDAwZmQ1YjUwODE3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNjYxMDhmYzYwMTA4MzgxNjEwMzUwNTdmZTViMDQ5MDgxMTUwMjkwNjA0MDUxNjAwMDYwNDA1MTgwODMwMzgxODU4ODg4ZjE5MzUwNTA1MDUwMTU4MDE1NjEwMzdjNTczZDYwMDA4MDNlM2Q2MDAwZmQ1YjUwODE3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNjYxMDhmYzYwMjA4MzgxNjEwM2ExNTdmZTViMDQ5MDgxMTUwMjkwNjA0MDUxNjAwMDYwNDA1MTgwODMwMzgxODU4ODg4ZjE5MzUwNTA1MDUwMTU4MDE1NjEwM2NkNTczZDYwMDA4MDNlM2Q2MDAwZmQ1YjUwODE3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNjYxMDhmYzYwNDA4MzgxNjEwM2YyNTdmZTViMDQ5MDgxMTUwMjkwNjA0MDUxNjAwMDYwNDA1MTgwODMwMzgxODU4ODg4ZjE5MzUwNTA1MDUwMTU4MDE1NjEwNDFlNTczZDYwMDA4MDNlM2Q2MDAwZmQ1YjUwNTA1MDU2NWIzMzczZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjE2NjEwOGZjODI5MDgxMTUwMjkwNjA0MDUxNjAwMDYwNDA1MTgwODMwMzgxODU4ODg4ZjE5MzUwNTA1MDUwMTU4MDE1NjEwNDY5NTczZDYwMDA4MDNlM2Q2MDAwZmQ1YjUwNTA1NjViODE3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNjYxMDhmYzgyOTA4MTE1MDI5MDYwNDA1MTYwMDA2MDQwNTE4MDgzMDM4MTg1ODg4OGYxOTM1MDUwNTA1MDE1ODAxNTYxMDRiMzU3M2Q2MDAwODAzZTNkNjAwMGZkNWI1MDUwNTA1NjViODM3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNjYxMDhmYzgyOTA4MTE1MDI5MDYwNDA1MTYwMDA2MDQwNTE4MDgzMDM4MTg1ODg4OGYxOTM1MDUwNTA1MDE1ODAxNTYxMDRmZTU3M2Q2MDAwODAzZTNkNjAwMGZkNWI1MDgyNzNmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmMTY2MTA4ZmM2MDAyODM4MTYxMDUyMzU3ZmU1YjA0OTA4MTE1MDI5MDYwNDA1MTYwMDA2MDQwNTE4MDgzMDM4MTg1ODg4OGYxOTM1MDUwNTA1MDE1ODAxNTYxMDU0ZjU3M2Q2MDAwODAzZTNkNjAwMGZkNWI1MDgxNzNmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmMTY2MTA4ZmM2MDA0ODM4MTYxMDU3NDU3ZmU1YjA0OTA4MTE1MDI5MDYwNDA1MTYwMDA2MDQwNTE4MDgzMDM4MTg1ODg4OGYxOTM1MDUwNTA1MDE1ODAxNTYxMDVhMDU3M2Q2MDAwODAzZTNkNjAwMGZkNWI1MDUwNTA1MDUwNTY1YjgxNzNmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmMTY2MTA4ZmM4MjYwMDAwMzkwODExNTAyOTA2MDQwNTE2MDAwNjA0MDUxODA4MzAzODE4NTg4ODhmMTkzNTA1MDUwNTAxNTgwMTU2MTA1ZjA1NzNkNjAwMDgwM2UzZDYwMDBmZDViNTA1MDUwNTZmZWEyNjU2MjdhN2E3MjMxNTgyMGNmNzkxYmJmNDdkY2I2MTM5Mzc0NDA1MmE2MjBmNzcxYjQyOWYwNzFmMDJlMzY1N2NlM2U1ZjgwYWIzZjk3YmQ2NDczNmY2YzYzNDMwMDA1MTEwMDMy","b64Record":"CiAIFiocCgwIARAMGgYIgK6ZpA8SDAgBEA8aBgiArpmkDxIwYJXgfYJpnpn9dGJjU+Tzg1R/aYDF03SmbqaVGyWWd1oYAhEvq2Ue06EO8OcqKdGtGgwIl/7+rAYQ0If/wQIiDwoJCNv9/qwGELgOEgIYAiogw4PCrsOCwrfDg8K5dEY4w4LCrkrDg8KLw4PCkMODwo5SAA=="},{"b64Body":"Cg8KCQjc/f6sBhC6DhICGAISAhgDGJb7rp0CIgIIeDIgw4PCrsOCwrfDg8K5dEY4w4LCrkrDg8KLw4PCkMODwo5CSAoDGNQIGiISIIZOnPN1ul/dZ8sX+ufK8uqtIZ4aiTRbxR4tzB+JIGuPIOCnEiiIJ0IFCIDO2gNSAFoAagtjZWxsYXIgZG9vcg==","b64Record":"CiUIFiIDGNUIKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjCEQ2zg5KO0cFXRFvYBi7928TeC6Z1P+9fQE3hglAtjMME1OJPIqlk0qyAHXA5hKB8aCwiY/v6sBhDk4odKIg8KCQjc/f6sBhC6DhICGAIqIMODwq7DgsK3w4PCuXRGOMOCwq5Kw4PCi8ODwpDDg8KOMICFkAhC3w4KAxjVCBKqDGCAYEBSYAQ2EGEASldgADVg4ByAYyarl+oUYQBMV4BjjdmAQxRhAJpXgGOs72A3FGEAyFeAY9Kg/iYUYQEWV4Bj4S4yOBRhAaRXWwBbYQCYYASANgNgQIEQFWEAYldgAID9W4EBkICANXP//////////////////////////xaQYCABkJKRkIA1kGAgAZCSkZBQUFBhAfJWWwBbYQDGYASANgNgIIEQFWEAsFdgAID9W4EBkICANZBgIAGQkpGQUFBQYQQjVlsAW2EBFGAEgDYDYECBEBVhAN5XYACA/VuBAZCAgDVz//////////////////////////8WkGAgAZCSkZCANZBgIAGQkpGQUFBQYQRtVlsAW2EBomAEgDYDYICBEBVhASxXYACA/VuBAZCAgDVz//////////////////////////8WkGAgAZCSkZCANXP//////////////////////////xaQYCABkJKRkIA1c///////////////////////////FpBgIAGQkpGQgDWQYCABkJKRkFBQUGEEuFZbAFthAfBgBIA2A2BAgRAVYQG6V2AAgP1bgQGQgIA1c///////////////////////////FpBgIAGQkpGQgDWQYCABkJKRkFBQUGEFp1ZbAFuBc///////////////////////////FmEI/IKQgRUCkGBAUWAAYEBRgIMDgYWIiPGTUFBQUBWAFWECOFc9YACAPj1gAP1bUIFz//////////////////////////8WYQj8YAKDgWECXVf+WwSQgRUCkGBAUWAAYEBRgIMDgYWIiPGTUFBQUBWAFWECiVc9YACAPj1gAP1bUIFz//////////////////////////8WYQj8YASDgWECrlf+WwSQgRUCkGBAUWAAYEBRgIMDgYWIiPGTUFBQUBWAFWEC2lc9YACAPj1gAP1bUIFz//////////////////////////8WYQj8YAiDgWEC/1f+WwSQgRUCkGBAUWAAYEBRgIMDgYWIiPGTUFBQUBWAFWEDK1c9YACAPj1gAP1bUIFz//////////////////////////8WYQj8YBCDgWEDUFf+WwSQgRUCkGBAUWAAYEBRgIMDgYWIiPGTUFBQUBWAFWEDfFc9YACAPj1gAP1bUIFz//////////////////////////8WYQj8YCCDgWEDoVf+WwSQgRUCkGBAUWAAYEBRgIMDgYWIiPGTUFBQUBWAFWEDzVc9YACAPj1gAP1bUIFz//////////////////////////8WYQj8YECDgWED8lf+WwSQgRUCkGBAUWAAYEBRgIMDgYWIiPGTUFBQUBWAFWEEHlc9YACAPj1gAP1bUFBQVlszc///////////////////////////FmEI/IKQgRUCkGBAUWAAYEBRgIMDgYWIiPGTUFBQUBWAFWEEaVc9YACAPj1gAP1bUFBWW4Fz//////////////////////////8WYQj8gpCBFQKQYEBRYABgQFGAgwOBhYiI8ZNQUFBQFYAVYQSzVz1gAIA+PWAA/VtQUFBWW4Nz//////////////////////////8WYQj8gpCBFQKQYEBRYABgQFGAgwOBhYiI8ZNQUFBQFYAVYQT+Vz1gAIA+PWAA/VtQgnP//////////////////////////xZhCPxgAoOBYQUjV/5bBJCBFQKQYEBRYABgQFGAgwOBhYiI8ZNQUFBQFYAVYQVPVz1gAIA+PWAA/VtQgXP//////////////////////////xZhCPxgBIOBYQV0V/5bBJCBFQKQYEBRYABgQFGAgwOBhYiI8ZNQUFBQFYAVYQWgVz1gAIA+PWAA/VtQUFBQUFZbgXP//////////////////////////xZhCPyCYAADkIEVApBgQFFgAGBAUYCDA4GFiIjxk1BQUFAVgBVhBfBXPWAAgD49YAD9W1BQUFb+omVienpyMVggz3kbv0fcthOTdEBSpiD3cbQp8HHwLjZXzj5fgKs/l71kc29sY0MABREAMiKAAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAogNMOOgMY1QhKFgoUAAAAAAAAAAAAAAAAAAAAAAAABFVyBwoDGNUIEAFSIAoJCgIYAhCP2KAQCgkKAhhiEICKoBAKCAoDGNUIEJBO"}]},"SpecialQueriesXTest":{"placeholderNum":1009,"encodedItems":[]},"sendHbarsToAddressesMultipleTimes":{"placeholderNum":1120,"encodedItems":[{"b64Body":"Cg8KCQjx/f6sBhDoDxICGAISAhgDGPuV9hQiAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjloxCiISIGbxRA94chGPNplK+KghOF9OQpc8M9I2dAzQR72UC3C1EIDIr6AlSgUIgM7aAw==","b64Record":"CiUIFhIDGOEIKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjDwuJjEAal2CvV9j32waCQEr0GHsaMQ9MILdV+0GnZ5OOcEX2JsDmtawZHkvuzf34saDAit/v6sBhCs5eaEAyIPCgkI8f3+rAYQ6A8SAhgCKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjlIZCgoKAhgCEP+P38BKCgsKAxjhCBCAkN/ASg=="},{"b64Body":"Cg8KCQjy/f6sBhDqDxICGAISAhgDGPuV9hQiAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjlouCiISIIUzeX1EaPeUCRwUZaJJKDFSC1NngBZ+g8cbGcXVdi9REJBOSgUIgM7aAw==","b64Record":"CiUIFhIDGOIIKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjDA2WJHyTVfB6oyF3UjBaVAoO9Puu4tSiP3UF4ZHqwkMS6Kq1TkcpyNRSTX/ZGYS9kaDAiu/v6sBhDj9+yMASIPCgkI8v3+rAYQ6g8SAhgCKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjlIVCggKAhgCEJ+cAQoJCgMY4ggQoJwB"},{"b64Body":"Cg8KCQjy/f6sBhDsDxICGAISAhgDGP7LzSwiAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjooBjwESDAiuzNmwBhCFuu3wAhptCiISIPrUO02Sr+xYGBzQOyV23uPS/WpIpgALD/Fbencg+amUCiM6IQN/Q+EDPJ2hRrPXW9RrDvLhsd8n0RrBV70P1gfCNNjFaAoiEiBBlvRmxFuZbxKQhOQXat967MYtbMcRZ2A3cKTxx9bP8yIMSGVsbG8gV29ybGQhKgAyAA==","b64Record":"CiUIFhoDGOMIKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjATypjj3Z9g8hlTYmtUjVlCAdrqXwafNwANX+IiP/7GrM7PpvXLo5SNzGNzaWZiAkcaDAiu/v6sBhDxiLKOAyIPCgkI8v3+rAYQ7A8SAhgCKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjlIA"},{"b64Body":"Cg8KCQjz/f6sBhDwDxICGAISAhgDGPOGlzoiAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjpoBghkKAxjjCCL6GDYwODA2MDQwNTI2MTA2MmE4MDYxMDAxMzYwMDAzOTYwMDBmM2ZlNjA4MDYwNDA1MjYwMDQzNjEwNjEwMDRhNTc2MDAwMzU2MGUwMWM4MDYzMjZhYjk3ZWExNDYxMDA0YzU3ODA2MzhkZDk4MDQzMTQ2MTAwOWE1NzgwNjNhY2VmNjAzNzE0NjEwMGM4NTc4MDYzZDJhMGZlMjYxNDYxMDExNjU3ODA2M2UxMmUzMjM4MTQ2MTAxYTQ1NzViMDA1YjYxMDA5ODYwMDQ4MDM2MDM2MDQwODExMDE1NjEwMDYyNTc2MDAwODBmZDViODEwMTkwODA4MDM1NzNmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmMTY5MDYwMjAwMTkwOTI5MTkwODAzNTkwNjAyMDAxOTA5MjkxOTA1MDUwNTA2MTAxZjI1NjViMDA1YjYxMDBjNjYwMDQ4MDM2MDM2MDIwODExMDE1NjEwMGIwNTc2MDAwODBmZDViODEwMTkwODA4MDM1OTA2MDIwMDE5MDkyOTE5MDUwNTA1MDYxMDQyMzU2NWIwMDViNjEwMTE0NjAwNDgwMzYwMzYwNDA4MTEwMTU2MTAwZGU1NzYwMDA4MGZkNWI4MTAxOTA4MDgwMzU3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNjkwNjAyMDAxOTA5MjkxOTA4MDM1OTA2MDIwMDE5MDkyOTE5MDUwNTA1MDYxMDQ2ZDU2NWIwMDViNjEwMWEyNjAwNDgwMzYwMzYwODA4MTEwMTU2MTAxMmM1NzYwMDA4MGZkNWI4MTAxOTA4MDgwMzU3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNjkwNjAyMDAxOTA5MjkxOTA4MDM1NzNmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmMTY5MDYwMjAwMTkwOTI5MTkwODAzNTczZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjE2OTA2MDIwMDE5MDkyOTE5MDgwMzU5MDYwMjAwMTkwOTI5MTkwNTA1MDUwNjEwNGI4NTY1YjAwNWI2MTAxZjA2MDA0ODAzNjAzNjA0MDgxMTAxNTYxMDFiYTU3NjAwMDgwZmQ1YjgxMDE5MDgwODAzNTczZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjE2OTA2MDIwMDE5MDkyOTE5MDgwMzU5MDYwMjAwMTkwOTI5MTkwNTA1MDUwNjEwNWE3NTY1YjAwNWI4MTczZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjE2NjEwOGZjODI5MDgxMTUwMjkwNjA0MDUxNjAwMDYwNDA1MTgwODMwMzgxODU4ODg4ZjE5MzUwNTA1MDUwMTU4MDE1NjEwMjM4NTczZDYwMDA4MDNlM2Q2MDAwZmQ1YjUwODE3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNjYxMDhmYzYwMDI4MzgxNjEwMjVkNTdmZTViMDQ5MDgxMTUwMjkwNjA0MDUxNjAwMDYwNDA1MTgwODMwMzgxODU4ODg4ZjE5MzUwNTA1MDUwMTU4MDE1NjEwMjg5NTczZDYwMDA4MDNlM2Q2MDAwZmQ1YjUwODE3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNjYxMDhmYzYwMDQ4MzgxNjEwMmFlNTdmZTViMDQ5MDgxMTUwMjkwNjA0MDUxNjAwMDYwNDA1MTgwODMwMzgxODU4ODg4ZjE5MzUwNTA1MDUwMTU4MDE1NjEwMmRhNTczZDYwMDA4MDNlM2Q2MDAwZmQ1YjUwODE3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNjYxMDhmYzYwMDg4MzgxNjEwMmZmNTdmZTViMDQ5MDgxMTUwMjkwNjA0MDUxNjAwMDYwNDA1MTgwODMwMzgxODU4ODg4ZjE5MzUwNTA1MDUwMTU4MDE1NjEwMzJiNTczZDYwMDA4MDNlM2Q2MDAwZmQ1YjUwODE3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNjYxMDhmYzYwMTA4MzgxNjEwMzUwNTdmZTViMDQ5MDgxMTUwMjkwNjA0MDUxNjAwMDYwNDA1MTgwODMwMzgxODU4ODg4ZjE5MzUwNTA1MDUwMTU4MDE1NjEwMzdjNTczZDYwMDA4MDNlM2Q2MDAwZmQ1YjUwODE3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNjYxMDhmYzYwMjA4MzgxNjEwM2ExNTdmZTViMDQ5MDgxMTUwMjkwNjA0MDUxNjAwMDYwNDA1MTgwODMwMzgxODU4ODg4ZjE5MzUwNTA1MDUwMTU4MDE1NjEwM2NkNTczZDYwMDA4MDNlM2Q2MDAwZmQ1YjUwODE3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNjYxMDhmYzYwNDA4MzgxNjEwM2YyNTdmZTViMDQ5MDgxMTUwMjkwNjA0MDUxNjAwMDYwNDA1MTgwODMwMzgxODU4ODg4ZjE5MzUwNTA1MDUwMTU4MDE1NjEwNDFlNTczZDYwMDA4MDNlM2Q2MDAwZmQ1YjUwNTA1MDU2NWIzMzczZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjE2NjEwOGZjODI5MDgxMTUwMjkwNjA0MDUxNjAwMDYwNDA1MTgwODMwMzgxODU4ODg4ZjE5MzUwNTA1MDUwMTU4MDE1NjEwNDY5NTczZDYwMDA4MDNlM2Q2MDAwZmQ1YjUwNTA1NjViODE3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNjYxMDhmYzgyOTA4MTE1MDI5MDYwNDA1MTYwMDA2MDQwNTE4MDgzMDM4MTg1ODg4OGYxOTM1MDUwNTA1MDE1ODAxNTYxMDRiMzU3M2Q2MDAwODAzZTNkNjAwMGZkNWI1MDUwNTA1NjViODM3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNjYxMDhmYzgyOTA4MTE1MDI5MDYwNDA1MTYwMDA2MDQwNTE4MDgzMDM4MTg1ODg4OGYxOTM1MDUwNTA1MDE1ODAxNTYxMDRmZTU3M2Q2MDAwODAzZTNkNjAwMGZkNWI1MDgyNzNmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmMTY2MTA4ZmM2MDAyODM4MTYxMDUyMzU3ZmU1YjA0OTA4MTE1MDI5MDYwNDA1MTYwMDA2MDQwNTE4MDgzMDM4MTg1ODg4OGYxOTM1MDUwNTA1MDE1ODAxNTYxMDU0ZjU3M2Q2MDAwODAzZTNkNjAwMGZkNWI1MDgxNzNmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmMTY2MTA4ZmM2MDA0ODM4MTYxMDU3NDU3ZmU1YjA0OTA4MTE1MDI5MDYwNDA1MTYwMDA2MDQwNTE4MDgzMDM4MTg1ODg4OGYxOTM1MDUwNTA1MDE1ODAxNTYxMDVhMDU3M2Q2MDAwODAzZTNkNjAwMGZkNWI1MDUwNTA1MDUwNTY1YjgxNzNmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmMTY2MTA4ZmM4MjYwMDAwMzkwODExNTAyOTA2MDQwNTE2MDAwNjA0MDUxODA4MzAzODE4NTg4ODhmMTkzNTA1MDUwNTAxNTgwMTU2MTA1ZjA1NzNkNjAwMDgwM2UzZDYwMDBmZDViNTA1MDUwNTZmZWEyNjU2MjdhN2E3MjMxNTgyMGNmNzkxYmJmNDdkY2I2MTM5Mzc0NDA1MmE2MjBmNzcxYjQyOWYwNzFmMDJlMzY1N2NlM2U1ZjgwYWIzZjk3YmQ2NDczNmY2YzYzNDMwMDA1MTEwMDMy","b64Record":"CiAIFiocCgwIARAMGgYIgK6ZpA8SDAgBEA8aBgiArpmkDxIwKpl+ck8gGxN1UyOt74WY7wpTFwz7EsOvPC8RL7Y77xh3Kh7462DJaBxUOWJVr27KGgwIr/7+rAYQt/jDlgEiDwoJCPP9/qwGEPAPEgIYAiogw4PCrsOCwrfDg8K5dEY4w4LCrkrDg8KLw4PCkMODwo5SAA=="},{"b64Body":"ChAKCQjz/f6sBhDyDxIDGOEIEgIYAxiW+66dAiICCHgyIMODwq7DgsK3w4PCuXRGOMOCwq5Kw4PCi8ODwpDDg8KOQkgKAxjjCBoiEiBoGTU6IEzkcSx7AatFqn5rl1Xst+IWsOkmNaJUhniM7yCQoQ8okE5CBQiAztoDUgBaAGoLY2VsbGFyIGRvb3I=","b64Record":"CiUIFiIDGOQIKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjAWYBYk457AyB8gOjSA2bIrVMehJ0Wu+Kg/hkDS60XICgH7iNalgLrCH5NE7LCHgVwaDAiv/v6sBhCchqj7AiIQCgkI8/3+rAYQ8g8SAxjhCCogw4PCrsOCwrfDg8K5dEY4w4LCrkrDg8KLw4PCkMODwo4w1tSRpAJC3w4KAxjkCBKqDGCAYEBSYAQ2EGEASldgADVg4ByAYyarl+oUYQBMV4BjjdmAQxRhAJpXgGOs72A3FGEAyFeAY9Kg/iYUYQEWV4Bj4S4yOBRhAaRXWwBbYQCYYASANgNgQIEQFWEAYldgAID9W4EBkICANXP//////////////////////////xaQYCABkJKRkIA1kGAgAZCSkZBQUFBhAfJWWwBbYQDGYASANgNgIIEQFWEAsFdgAID9W4EBkICANZBgIAGQkpGQUFBQYQQjVlsAW2EBFGAEgDYDYECBEBVhAN5XYACA/VuBAZCAgDVz//////////////////////////8WkGAgAZCSkZCANZBgIAGQkpGQUFBQYQRtVlsAW2EBomAEgDYDYICBEBVhASxXYACA/VuBAZCAgDVz//////////////////////////8WkGAgAZCSkZCANXP//////////////////////////xaQYCABkJKRkIA1c///////////////////////////FpBgIAGQkpGQgDWQYCABkJKRkFBQUGEEuFZbAFthAfBgBIA2A2BAgRAVYQG6V2AAgP1bgQGQgIA1c///////////////////////////FpBgIAGQkpGQgDWQYCABkJKRkFBQUGEFp1ZbAFuBc///////////////////////////FmEI/IKQgRUCkGBAUWAAYEBRgIMDgYWIiPGTUFBQUBWAFWECOFc9YACAPj1gAP1bUIFz//////////////////////////8WYQj8YAKDgWECXVf+WwSQgRUCkGBAUWAAYEBRgIMDgYWIiPGTUFBQUBWAFWECiVc9YACAPj1gAP1bUIFz//////////////////////////8WYQj8YASDgWECrlf+WwSQgRUCkGBAUWAAYEBRgIMDgYWIiPGTUFBQUBWAFWEC2lc9YACAPj1gAP1bUIFz//////////////////////////8WYQj8YAiDgWEC/1f+WwSQgRUCkGBAUWAAYEBRgIMDgYWIiPGTUFBQUBWAFWEDK1c9YACAPj1gAP1bUIFz//////////////////////////8WYQj8YBCDgWEDUFf+WwSQgRUCkGBAUWAAYEBRgIMDgYWIiPGTUFBQUBWAFWEDfFc9YACAPj1gAP1bUIFz//////////////////////////8WYQj8YCCDgWEDoVf+WwSQgRUCkGBAUWAAYEBRgIMDgYWIiPGTUFBQUBWAFWEDzVc9YACAPj1gAP1bUIFz//////////////////////////8WYQj8YECDgWED8lf+WwSQgRUCkGBAUWAAYEBRgIMDgYWIiPGTUFBQUBWAFWEEHlc9YACAPj1gAP1bUFBQVlszc///////////////////////////FmEI/IKQgRUCkGBAUWAAYEBRgIMDgYWIiPGTUFBQUBWAFWEEaVc9YACAPj1gAP1bUFBWW4Fz//////////////////////////8WYQj8gpCBFQKQYEBRYABgQFGAgwOBhYiI8ZNQUFBQFYAVYQSzVz1gAIA+PWAA/VtQUFBWW4Nz//////////////////////////8WYQj8gpCBFQKQYEBRYABgQFGAgwOBhYiI8ZNQUFBQFYAVYQT+Vz1gAIA+PWAA/VtQgnP//////////////////////////xZhCPxgAoOBYQUjV/5bBJCBFQKQYEBRYABgQFGAgwOBhYiI8ZNQUFBQFYAVYQVPVz1gAIA+PWAA/VtQgXP//////////////////////////xZhCPxgBIOBYQV0V/5bBJCBFQKQYEBRYABgQFGAgwOBhYiI8ZNQUFBQFYAVYQWgVz1gAIA+PWAA/VtQUFBQUFZbgXP//////////////////////////xZhCPyCYAADkIEVApBgQFFgAGBAUYCDA4GFiIjxk1BQUFAVgBVhBfBXPWAAgD49YAD9W1BQUFb+omVienpyMVggz3kbv0fcthOTdEBSpiD3cbQp8HHwLjZXzj5fgKs/l71kc29sY0MABREAMiKAAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAowJoMOgMY5AhKFgoUAAAAAAAAAAAAAAAAAAAAAAAABGRyBwoDGOQIEAFSRwoJCgIYAxCY4okUCgoKAhhiEODciMYDCgoKAxigBhCatYg3CgoKAxihBhCatYg3CgsKAxjhCBDLxaTIBAoJCgMY5AgQoJwB"},{"b64Body":"ChAKCQj0/f6sBhD6DxIDGOEIEgIYAxjgrLEDIgIIeDIgw4PCrsOCwrfDg8K5dEY4w4LCrkrDg8KLw4PCkMODwo46TwoDGOQIEKCNBiJEJquX6gAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAARiAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEA=","b64Record":"CiUIFiIDGOQIKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjAZGLyiQqVoOtMaYY73vrlgFtEUrPI2dKKGrUh6vMH22BPTwnH7rA9fFLKo9yO3JQ8aDAiw/v6sBhCMzoegASIQCgkI9P3+rAYQ+g8SAxjhCCogw4PCrsOCwrfDg8K5dEY4w4LCrkrDg8KLw4PCkMODwo4wgNfaAjqMAgoDGOQIIoACAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACiA8QRSKwoJCgIYYhCArrUFCgoKAxjhCBD/rbUFCggKAxjiCBD+AQoICgMY5AgQ/QE="}]},"sendHbarsToDifferentAddresses":{"placeholderNum":1125,"encodedItems":[{"b64Body":"Cg8KCQj5/f6sBhCUEBICGAISAhgDGPuV9hQiAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjloxCiISICTf+CaEoDHPHUFVesWtpg0sk7I5zVsOt67vkHkpjc6BEIDIr6AlSgUIgM7aAw==","b64Record":"CiUIFhIDGOYIKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjCJjrQ9zw0Xc8NoN4JWKuvGhoZpxAchumIgC2Lx5hafGrJVcjVdTk3aqugxSaEdUpoaCwi1/v6sBhC0hO9AIg8KCQj5/f6sBhCUEBICGAIqIMODwq7DgsK3w4PCuXRGOMOCwq5Kw4PCi8ODwpDDg8KOUhkKCgoCGAIQ/4/fwEoKCwoDGOYIEICQ38BK"},{"b64Body":"Cg8KCQj5/f6sBhCWEBICGAISAhgDGPuV9hQiAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjlouCiISIA4d2WHDNsm3OqCWFr0cNzZZnxOrMXOQljpXpg6ooeZbEJBOSgUIgM7aAw==","b64Record":"CiUIFhIDGOcIKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjCiRqIedEFYtTU5CnVSbGEeZRp7y5fuWFg7HWhZe7nR8tdc6iUwgZ/yWTRv368zCMsaDAi1/v6sBhDZttulAiIPCgkI+f3+rAYQlhASAhgCKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjlIVCggKAhgCEJ+cAQoJCgMY5wgQoJwB"},{"b64Body":"Cg8KCQj6/f6sBhCYEBICGAISAhgDGPuV9hQiAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjlouCiISIN/l62J7Hs+P52g8dKF10lK5K9LvKl8rorJDzwikV0K1EJBOSgUIgM7aAw==","b64Record":"CiUIFhIDGOgIKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjBt1hkXbYy74yhCxV1+dktPKT4qknJru8CAqwKDg5ojR9GfBxCEoRdaGtd73NOmowkaCwi2/v6sBhDx4tItIg8KCQj6/f6sBhCYEBICGAIqIMODwq7DgsK3w4PCuXRGOMOCwq5Kw4PCi8ODwpDDg8KOUhUKCAoCGAIQn5wBCgkKAxjoCBCgnAE="},{"b64Body":"Cg8KCQj6/f6sBhCaEBICGAISAhgDGPuV9hQiAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjlouCiISIJlINE6qkubBourz4u0PkqYxwtop5kNjSMy3nCTWxzMqEJBOSgUIgM7aAw==","b64Record":"CiUIFhIDGOkIKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjBgLTDCtj+B5rLLlWFVeCq6lyzrsHi52KdIFHT1M0o0x+tQ62clI8v26qHRlcrDHGYaDAi2/v6sBhDejpGvAiIPCgkI+v3+rAYQmhASAhgCKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjlIVCggKAhgCEJ+cAQoJCgMY6QgQoJwB"},{"b64Body":"Cg8KCQj7/f6sBhCcEBICGAISAhgDGP7LzSwiAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjooBjgESCwi3zNmwBhCWp9kwGm0KIhIgzKse5mR6kOH7KDn+8MP9JCe3iHNANdCWIeXtl7rKCSsKIzohA3ZhsXhoeUaVIt+QLev9OUbZyDlvTkMQEPx7zy/839jFCiISIIU1dbcyXoEvnTFtAqBhoiQoKlUXtm8/t+ta3Fxbc62YIgxIZWxsbyBXb3JsZCEqADIA","b64Record":"CiUIFhoDGOoIKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjBnS3+FnNbns701mQxXyyKHX1it/gbsE6NAyltwlq3uCyXc8uwMrgOGI9WA9m++Ch0aCwi3/v6sBhCwsJs3Ig8KCQj7/f6sBhCcEBICGAIqIMODwq7DgsK3w4PCuXRGOMOCwq5Kw4PCi8ODwpDDg8KOUgA="},{"b64Body":"Cg8KCQj7/f6sBhCgEBICGAISAhgDGPOGlzoiAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjpoBghkKAxjqCCL6GDYwODA2MDQwNTI2MTA2MmE4MDYxMDAxMzYwMDAzOTYwMDBmM2ZlNjA4MDYwNDA1MjYwMDQzNjEwNjEwMDRhNTc2MDAwMzU2MGUwMWM4MDYzMjZhYjk3ZWExNDYxMDA0YzU3ODA2MzhkZDk4MDQzMTQ2MTAwOWE1NzgwNjNhY2VmNjAzNzE0NjEwMGM4NTc4MDYzZDJhMGZlMjYxNDYxMDExNjU3ODA2M2UxMmUzMjM4MTQ2MTAxYTQ1NzViMDA1YjYxMDA5ODYwMDQ4MDM2MDM2MDQwODExMDE1NjEwMDYyNTc2MDAwODBmZDViODEwMTkwODA4MDM1NzNmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmMTY5MDYwMjAwMTkwOTI5MTkwODAzNTkwNjAyMDAxOTA5MjkxOTA1MDUwNTA2MTAxZjI1NjViMDA1YjYxMDBjNjYwMDQ4MDM2MDM2MDIwODExMDE1NjEwMGIwNTc2MDAwODBmZDViODEwMTkwODA4MDM1OTA2MDIwMDE5MDkyOTE5MDUwNTA1MDYxMDQyMzU2NWIwMDViNjEwMTE0NjAwNDgwMzYwMzYwNDA4MTEwMTU2MTAwZGU1NzYwMDA4MGZkNWI4MTAxOTA4MDgwMzU3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNjkwNjAyMDAxOTA5MjkxOTA4MDM1OTA2MDIwMDE5MDkyOTE5MDUwNTA1MDYxMDQ2ZDU2NWIwMDViNjEwMWEyNjAwNDgwMzYwMzYwODA4MTEwMTU2MTAxMmM1NzYwMDA4MGZkNWI4MTAxOTA4MDgwMzU3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNjkwNjAyMDAxOTA5MjkxOTA4MDM1NzNmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmMTY5MDYwMjAwMTkwOTI5MTkwODAzNTczZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjE2OTA2MDIwMDE5MDkyOTE5MDgwMzU5MDYwMjAwMTkwOTI5MTkwNTA1MDUwNjEwNGI4NTY1YjAwNWI2MTAxZjA2MDA0ODAzNjAzNjA0MDgxMTAxNTYxMDFiYTU3NjAwMDgwZmQ1YjgxMDE5MDgwODAzNTczZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjE2OTA2MDIwMDE5MDkyOTE5MDgwMzU5MDYwMjAwMTkwOTI5MTkwNTA1MDUwNjEwNWE3NTY1YjAwNWI4MTczZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjE2NjEwOGZjODI5MDgxMTUwMjkwNjA0MDUxNjAwMDYwNDA1MTgwODMwMzgxODU4ODg4ZjE5MzUwNTA1MDUwMTU4MDE1NjEwMjM4NTczZDYwMDA4MDNlM2Q2MDAwZmQ1YjUwODE3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNjYxMDhmYzYwMDI4MzgxNjEwMjVkNTdmZTViMDQ5MDgxMTUwMjkwNjA0MDUxNjAwMDYwNDA1MTgwODMwMzgxODU4ODg4ZjE5MzUwNTA1MDUwMTU4MDE1NjEwMjg5NTczZDYwMDA4MDNlM2Q2MDAwZmQ1YjUwODE3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNjYxMDhmYzYwMDQ4MzgxNjEwMmFlNTdmZTViMDQ5MDgxMTUwMjkwNjA0MDUxNjAwMDYwNDA1MTgwODMwMzgxODU4ODg4ZjE5MzUwNTA1MDUwMTU4MDE1NjEwMmRhNTczZDYwMDA4MDNlM2Q2MDAwZmQ1YjUwODE3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNjYxMDhmYzYwMDg4MzgxNjEwMmZmNTdmZTViMDQ5MDgxMTUwMjkwNjA0MDUxNjAwMDYwNDA1MTgwODMwMzgxODU4ODg4ZjE5MzUwNTA1MDUwMTU4MDE1NjEwMzJiNTczZDYwMDA4MDNlM2Q2MDAwZmQ1YjUwODE3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNjYxMDhmYzYwMTA4MzgxNjEwMzUwNTdmZTViMDQ5MDgxMTUwMjkwNjA0MDUxNjAwMDYwNDA1MTgwODMwMzgxODU4ODg4ZjE5MzUwNTA1MDUwMTU4MDE1NjEwMzdjNTczZDYwMDA4MDNlM2Q2MDAwZmQ1YjUwODE3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNjYxMDhmYzYwMjA4MzgxNjEwM2ExNTdmZTViMDQ5MDgxMTUwMjkwNjA0MDUxNjAwMDYwNDA1MTgwODMwMzgxODU4ODg4ZjE5MzUwNTA1MDUwMTU4MDE1NjEwM2NkNTczZDYwMDA4MDNlM2Q2MDAwZmQ1YjUwODE3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNjYxMDhmYzYwNDA4MzgxNjEwM2YyNTdmZTViMDQ5MDgxMTUwMjkwNjA0MDUxNjAwMDYwNDA1MTgwODMwMzgxODU4ODg4ZjE5MzUwNTA1MDUwMTU4MDE1NjEwNDFlNTczZDYwMDA4MDNlM2Q2MDAwZmQ1YjUwNTA1MDU2NWIzMzczZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjE2NjEwOGZjODI5MDgxMTUwMjkwNjA0MDUxNjAwMDYwNDA1MTgwODMwMzgxODU4ODg4ZjE5MzUwNTA1MDUwMTU4MDE1NjEwNDY5NTczZDYwMDA4MDNlM2Q2MDAwZmQ1YjUwNTA1NjViODE3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNjYxMDhmYzgyOTA4MTE1MDI5MDYwNDA1MTYwMDA2MDQwNTE4MDgzMDM4MTg1ODg4OGYxOTM1MDUwNTA1MDE1ODAxNTYxMDRiMzU3M2Q2MDAwODAzZTNkNjAwMGZkNWI1MDUwNTA1NjViODM3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNjYxMDhmYzgyOTA4MTE1MDI5MDYwNDA1MTYwMDA2MDQwNTE4MDgzMDM4MTg1ODg4OGYxOTM1MDUwNTA1MDE1ODAxNTYxMDRmZTU3M2Q2MDAwODAzZTNkNjAwMGZkNWI1MDgyNzNmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmMTY2MTA4ZmM2MDAyODM4MTYxMDUyMzU3ZmU1YjA0OTA4MTE1MDI5MDYwNDA1MTYwMDA2MDQwNTE4MDgzMDM4MTg1ODg4OGYxOTM1MDUwNTA1MDE1ODAxNTYxMDU0ZjU3M2Q2MDAwODAzZTNkNjAwMGZkNWI1MDgxNzNmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmMTY2MTA4ZmM2MDA0ODM4MTYxMDU3NDU3ZmU1YjA0OTA4MTE1MDI5MDYwNDA1MTYwMDA2MDQwNTE4MDgzMDM4MTg1ODg4OGYxOTM1MDUwNTA1MDE1ODAxNTYxMDVhMDU3M2Q2MDAwODAzZTNkNjAwMGZkNWI1MDUwNTA1MDUwNTY1YjgxNzNmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmMTY2MTA4ZmM4MjYwMDAwMzkwODExNTAyOTA2MDQwNTE2MDAwNjA0MDUxODA4MzAzODE4NTg4ODhmMTkzNTA1MDUwNTAxNTgwMTU2MTA1ZjA1NzNkNjAwMDgwM2UzZDYwMDBmZDViNTA1MDUwNTZmZWEyNjU2MjdhN2E3MjMxNTgyMGNmNzkxYmJmNDdkY2I2MTM5Mzc0NDA1MmE2MjBmNzcxYjQyOWYwNzFmMDJlMzY1N2NlM2U1ZjgwYWIzZjk3YmQ2NDczNmY2YzYzNDMwMDA1MTEwMDMy","b64Record":"CiAIFiocCgwIARAMGgYIgK6ZpA8SDAgBEA8aBgiArpmkDxIwSEmVq/r6l/AgopnMMyre0s5N7+OQR8PJ6o6lh2eG7F3BmSkyQ6gM49cuavUMNcsqGgwIt/7+rAYQxO3CuAIiDwoJCPv9/qwGEKAQEgIYAiogw4PCrsOCwrfDg8K5dEY4w4LCrkrDg8KLw4PCkMODwo5SAA=="},{"b64Body":"ChAKCQj8/f6sBhCiEBIDGOYIEgIYAxiW+66dAiICCHgyIMODwq7DgsK3w4PCuXRGOMOCwq5Kw4PCi8ODwpDDg8KOQkgKAxjqCBoiEiB6+qNyaJgg3X4ft9qPP4iD4srJwCiKYkWJqsxop8B1fiCQoQ8okE5CBQiAztoDUgBaAGoLY2VsbGFyIGRvb3I=","b64Record":"CiUIFiIDGOsIKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjC+SwYI6ubO18uLgbeLcNBd5d3vaKKuBJwARo066RD+hM4iDmH/XVlr9jSvC+ByjKMaCwi4/v6sBhCP58tAIhAKCQj8/f6sBhCiEBIDGOYIKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjjDW1JGkAkLfDgoDGOsIEqoMYIBgQFJgBDYQYQBKV2AANWDgHIBjJquX6hRhAExXgGON2YBDFGEAmleAY6zvYDcUYQDIV4Bj0qD+JhRhARZXgGPhLjI4FGEBpFdbAFthAJhgBIA2A2BAgRAVYQBiV2AAgP1bgQGQgIA1c///////////////////////////FpBgIAGQkpGQgDWQYCABkJKRkFBQUGEB8lZbAFthAMZgBIA2A2AggRAVYQCwV2AAgP1bgQGQgIA1kGAgAZCSkZBQUFBhBCNWWwBbYQEUYASANgNgQIEQFWEA3ldgAID9W4EBkICANXP//////////////////////////xaQYCABkJKRkIA1kGAgAZCSkZBQUFBhBG1WWwBbYQGiYASANgNggIEQFWEBLFdgAID9W4EBkICANXP//////////////////////////xaQYCABkJKRkIA1c///////////////////////////FpBgIAGQkpGQgDVz//////////////////////////8WkGAgAZCSkZCANZBgIAGQkpGQUFBQYQS4VlsAW2EB8GAEgDYDYECBEBVhAbpXYACA/VuBAZCAgDVz//////////////////////////8WkGAgAZCSkZCANZBgIAGQkpGQUFBQYQWnVlsAW4Fz//////////////////////////8WYQj8gpCBFQKQYEBRYABgQFGAgwOBhYiI8ZNQUFBQFYAVYQI4Vz1gAIA+PWAA/VtQgXP//////////////////////////xZhCPxgAoOBYQJdV/5bBJCBFQKQYEBRYABgQFGAgwOBhYiI8ZNQUFBQFYAVYQKJVz1gAIA+PWAA/VtQgXP//////////////////////////xZhCPxgBIOBYQKuV/5bBJCBFQKQYEBRYABgQFGAgwOBhYiI8ZNQUFBQFYAVYQLaVz1gAIA+PWAA/VtQgXP//////////////////////////xZhCPxgCIOBYQL/V/5bBJCBFQKQYEBRYABgQFGAgwOBhYiI8ZNQUFBQFYAVYQMrVz1gAIA+PWAA/VtQgXP//////////////////////////xZhCPxgEIOBYQNQV/5bBJCBFQKQYEBRYABgQFGAgwOBhYiI8ZNQUFBQFYAVYQN8Vz1gAIA+PWAA/VtQgXP//////////////////////////xZhCPxgIIOBYQOhV/5bBJCBFQKQYEBRYABgQFGAgwOBhYiI8ZNQUFBQFYAVYQPNVz1gAIA+PWAA/VtQgXP//////////////////////////xZhCPxgQIOBYQPyV/5bBJCBFQKQYEBRYABgQFGAgwOBhYiI8ZNQUFBQFYAVYQQeVz1gAIA+PWAA/VtQUFBWWzNz//////////////////////////8WYQj8gpCBFQKQYEBRYABgQFGAgwOBhYiI8ZNQUFBQFYAVYQRpVz1gAIA+PWAA/VtQUFZbgXP//////////////////////////xZhCPyCkIEVApBgQFFgAGBAUYCDA4GFiIjxk1BQUFAVgBVhBLNXPWAAgD49YAD9W1BQUFZbg3P//////////////////////////xZhCPyCkIEVApBgQFFgAGBAUYCDA4GFiIjxk1BQUFAVgBVhBP5XPWAAgD49YAD9W1CCc///////////////////////////FmEI/GACg4FhBSNX/lsEkIEVApBgQFFgAGBAUYCDA4GFiIjxk1BQUFAVgBVhBU9XPWAAgD49YAD9W1CBc///////////////////////////FmEI/GAEg4FhBXRX/lsEkIEVApBgQFFgAGBAUYCDA4GFiIjxk1BQUFAVgBVhBaBXPWAAgD49YAD9W1BQUFBQVluBc///////////////////////////FmEI/IJgAAOQgRUCkGBAUWAAYEBRgIMDgYWIiPGTUFBQUBWAFWEF8Fc9YACAPj1gAP1bUFBQVv6iZWJ6enIxWCDPeRu/R9y2E5N0QFKmIPdxtCnwcfAuNlfOPl+Aqz+XvWRzb2xjQwAFEQAyIoACAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACjAmgw6AxjrCEoWChQAAAAAAAAAAAAAAAAAAAAAAAAEa3IHCgMY6wgQAVJHCgkKAhgDEJjiiRQKCgoCGGIQ4NyIxgMKCgoDGKAGEJq1iDcKCgoDGKEGEJq1iDcKCwoDGOYIEMvFpMgECgkKAxjrCBCgnAE="},{"b64Body":"ChAKCQj8/f6sBhC2EBIDGOYIEgIYAxjgrLEDIgIIeDIgw4PCrsOCwrfDg8K5dEY4w4LCrkrDg8KLw4PCkMODwo46kAEKAxjrCBCgjQYihAHSoP4mAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABGcAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEaAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAARpAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABQ=","b64Record":"CiUIFiIDGOsIKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjD7yBwbdeYC8xSGXqeCdXcwA8fywZ0bIqUzDcauoklDPzAVxosDmHw9XvDKnjg1YNcaDAi4/v6sBhDhraLCAiIQCgkI/P3+rAYQthASAxjmCCogw4PCrsOCwrfDg8K5dEY4w4LCrkrDg8KLw4PCkMODwo4wgNfaAjqMAgoDGOsIIoACAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACiA8QRSOwoJCgIYYhCArrUFCgoKAxjmCBD/rbUFCgcKAxjnCBAoCgcKAxjoCBAUCgcKAxjpCBAKCgcKAxjrCBBF"}]},"sendHbarsFromDifferentAddressessToAddress":{"placeholderNum":1132,"encodedItems":[{"b64Body":"Cg8KCQiB/v6sBhDMEBICGAISAhgDGPuV9hQiAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjloxCiISIA132D7y/dPDfSRNhWraeDTwhH5lEQkHlhOXXXwc/4UPEIDIr6AlSgUIgM7aAw==","b64Record":"CiUIFhIDGO0IKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjBXiIVhvh6wbrn9mpwWLbzczRYoGzb+ACtlIwnlbVU09xpK+BBsZXxbiwNBuV3Gbu4aDAi9/v6sBhCi4P7FASIPCgkIgf7+rAYQzBASAhgCKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjlIZCgoKAhgCEP+P38BKCgsKAxjtCBCAkN/ASg=="},{"b64Body":"Cg8KCQiB/v6sBhDOEBICGAISAhgDGPuV9hQiAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjlouCiISIB7PpCmGHp5C8eRRbKsxlBcG4J9uhuJsn7qTyikMYMExEJBOSgUIgM7aAw==","b64Record":"CiUIFhIDGO4IKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjCZ718/VC42wEQhvEzp1iXLv3eFb/mF+SjW6+hjDSjlD0PVBmXpKtLohe3CJuEmZLkaDAi9/v6sBhD588jHAyIPCgkIgf7+rAYQzhASAhgCKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjlIVCggKAhgCEJ+cAQoJCgMY7ggQoJwB"},{"b64Body":"Cg8KCQiC/v6sBhDQEBICGAISAhgDGP7LzSwiAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjooBjwESDAi+zNmwBhCGx83DARptCiISIP6EXjYdkk3KMa/oSVbJIdLHJh5UitI/P7EC0lHJAl0aCiM6IQPPfykw+C+aVN5JJgQWfkyhdjOmeo2ujDunIXUjKhPmqQoiEiB5oAPxVxhA/nFM0/JUogtdkdDGkrRbhgXc8J6seVJ3fiIMSGVsbG8gV29ybGQhKgAyAA==","b64Record":"CiUIFhoDGO8IKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjAh6n1zgFv/XTCghEE7xrSk3oRtOjqr85A87axbALpR2aiy/ho+JijeZBsdINCj4oUaDAi+/v6sBhD8/MPPASIPCgkIgv7+rAYQ0BASAhgCKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjlIA"},{"b64Body":"Cg8KCQiC/v6sBhDUEBICGAISAhgDGIydjj4iAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjpoBiCAKAxjvCCKAIDYwODA2MDQwNTI2MDQwNTE2MTBlNmIzODAzODA2MTBlNmI4MzM5ODE4MTAxNjA0MDUyNjA0MDgxMTAxNTYxMDAyNjU3NjAwMDgwZmQ1YjgxMDE5MDgwODA1MTkwNjAyMDAxOTA5MjkxOTA4MDUxOTA2MDIwMDE5MDkyOTE5MDUwNTA1MDgxNjAwMDgwNjEwMTAwMGE4MTU0ODE3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYwMjE5MTY5MDgzNzNmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmMTYwMjE3OTA1NTUwODA2MDAxNjAwMDYxMDEwMDBhODE1NDgxNzNmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmMDIxOTE2OTA4MzczZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjE2MDIxNzkwNTU1MDUwNTA2MTBkOTg4MDYxMDBkMzYwMDAzOTYwMDBmM2ZlNjA4MDYwNDA1MjYwMDQzNjEwNjEwMDNmNTc2MDAwMzU2MGUwMWM4MDYzNTU1NWQ3ZTQxNDYxMDA0MTU3ODA2MzVlY2YyOThhMTQ2MTAwY2Y1NzgwNjM5ZGM3ZGIxMDE0NjEwMGZkNTc4MDYzYTlhM2E2MzUxNDYxMDE0YjU3NWIwMDViNjEwMGNkNjAwNDgwMzYwMzYwODA4MTEwMTU2MTAwNTc1NzYwMDA4MGZkNWI4MTAxOTA4MDgwMzU3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNjkwNjAyMDAxOTA5MjkxOTA4MDM1NzNmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmMTY5MDYwMjAwMTkwOTI5MTkwODAzNTczZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjE2OTA2MDIwMDE5MDkyOTE5MDgwMzU5MDYwMjAwMTkwOTI5MTkwNTA1MDUwNjEwMTc5NTY1YjAwNWI2MTAwZmI2MDA0ODAzNjAzNjAyMDgxMTAxNTYxMDBlNTU3NjAwMDgwZmQ1YjgxMDE5MDgwODAzNTkwNjAyMDAxOTA5MjkxOTA1MDUwNTA2MTA3MTM1NjViMDA1YjYxMDE0OTYwMDQ4MDM2MDM2MDQwODExMDE1NjEwMTEzNTc2MDAwODBmZDViODEwMTkwODA4MDM1NzNmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmMTY5MDYwMjAwMTkwOTI5MTkwODAzNTkwNjAyMDAxOTA5MjkxOTA1MDUwNTA2MTA4MmY1NjViMDA1YjYxMDE3NzYwMDQ4MDM2MDM2MDIwODExMDE1NjEwMTYxNTc2MDAwODBmZDViODEwMTkwODA4MDM1OTA2MDIwMDE5MDkyOTE5MDUwNTA1MDYxMGEwZjU2NWIwMDViODM3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNjYxMDhmYzgyOTA4MTE1MDI5MDYwNDA1MTYwMDA2MDQwNTE4MDgzMDM4MTg1ODg4OGYxOTM1MDUwNTA1MDE1ODAxNTYxMDFiZjU3M2Q2MDAwODAzZTNkNjAwMGZkNWI1MDYwMDA4MDkwNTQ5MDYxMDEwMDBhOTAwNDczZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjE2NzNmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmMTY2M2FjZWY2MDM3ODU2MDAyODQ4MTYxMDIwODU3ZmU1YjA0NjA0MDUxODM2M2ZmZmZmZmZmMTY2MGUwMWI4MTUyNjAwNDAxODA4MzczZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjE2NzNmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmMTY4MTUyNjAyMDAxODI4MTUyNjAyMDAxOTI1MDUwNTA2MDAwNjA0MDUxODA4MzAzODE2MDAwODc4MDNiMTU4MDE1NjEwMjcyNTc2MDAwODBmZDViNTA1YWYxMTU4MDE1NjEwMjg2NTczZDYwMDA4MDNlM2Q2MDAwZmQ1YjUwNTA1MDUwNjAwMTYwMDA5MDU0OTA2MTAxMDAwYTkwMDQ3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNjczZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjE2NjNhY2VmNjAzNzg1NjAwMjg0ODE2MTAyZDM1N2ZlNWIwNDYwNDA1MTgzNjNmZmZmZmZmZjE2NjBlMDFiODE1MjYwMDQwMTgwODM3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNjczZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjE2ODE1MjYwMjAwMTgyODE1MjYwMjAwMTkyNTA1MDUwNjAwMDYwNDA1MTgwODMwMzgxNjAwMDg3ODAzYjE1ODAxNTYxMDMzZDU3NjAwMDgwZmQ1YjUwNWFmMTE1ODAxNTYxMDM1MTU3M2Q2MDAwODAzZTNkNjAwMGZkNWI1MDUwNTA1MDgyNzNmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmMTY2MTA4ZmM4MjkwODExNTAyOTA2MDQwNTE2MDAwNjA0MDUxODA4MzAzODE4NTg4ODhmMTkzNTA1MDUwNTAxNTgwMTU2MTAzOWI1NzNkNjAwMDgwM2UzZDYwMDBmZDViNTA2MDAwODA5MDU0OTA2MTAxMDAwYTkwMDQ3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNjczZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjE2NjNhY2VmNjAzNzg0NjAwMjg0ODE2MTAzZTQ1N2ZlNWIwNDYwNDA1MTgzNjNmZmZmZmZmZjE2NjBlMDFiODE1MjYwMDQwMTgwODM3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNjczZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjE2ODE1MjYwMjAwMTgyODE1MjYwMjAwMTkyNTA1MDUwNjAwMDYwNDA1MTgwODMwMzgxNjAwMDg3ODAzYjE1ODAxNTYxMDQ0ZTU3NjAwMDgwZmQ1YjUwNWFmMTE1ODAxNTYxMDQ2MjU3M2Q2MDAwODAzZTNkNjAwMGZkNWI1MDUwNTA1MDYwMDE2MDAwOTA1NDkwNjEwMTAwMGE5MDA0NzNmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmMTY3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNjYzYWNlZjYwMzc4NDYwMDI4NDgxNjEwNGFmNTdmZTViMDQ2MDQwNTE4MzYzZmZmZmZmZmYxNjYwZTAxYjgxNTI2MDA0MDE4MDgzNzNmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmMTY3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNjgxNTI2MDIwMDE4MjgxNTI2MDIwMDE5MjUwNTA1MDYwMDA2MDQwNTE4MDgzMDM4MTYwMDA4NzgwM2IxNTgwMTU2MTA1MTk1NzYwMDA4MGZkNWI1MDVhZjExNTgwMTU2MTA1MmQ1NzNkNjAwMDgwM2UzZDYwMDBmZDViNTA1MDUwNTA4MTczZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjE2NjEwOGZjODI5MDgxMTUwMjkwNjA0MDUxNjAwMDYwNDA1MTgwODMwMzgxODU4ODg4ZjE5MzUwNTA1MDUwMTU4MDE1NjEwNTc3NTczZDYwMDA4MDNlM2Q2MDAwZmQ1YjUwNjAwMDgwOTA1NDkwNjEwMTAwMGE5MDA0NzNmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmMTY3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNjYzYWNlZjYwMzc4MzYwMDI4NDgxNjEwNWMwNTdmZTViMDQ2MDQwNTE4MzYzZmZmZmZmZmYxNjYwZTAxYjgxNTI2MDA0MDE4MDgzNzNmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmMTY3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNjgxNTI2MDIwMDE4MjgxNTI2MDIwMDE5MjUwNTA1MDYwMDA2MDQwNTE4MDgzMDM4MTYwMDA4NzgwM2IxNTgwMTU2MTA2MmE1NzYwMDA4MGZkNWI1MDVhZjExNTgwMTU2MTA2M2U1NzNkNjAwMDgwM2UzZDYwMDBmZDViNTA1MDUwNTA2MDAxNjAwMDkwNTQ5MDYxMDEwMDBhOTAwNDczZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjE2NzNmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmMTY2M2FjZWY2MDM3ODM2MDAyODQ4MTYxMDY4YjU3ZmU1YjA0NjA0MDUxODM2M2ZmZmZmZmZmMTY2MGUwMWI4MTUyNjAwNDAxODA4MzczZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjE2NzNmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmMTY4MTUyNjAyMDAxODI4MTUyNjAyMDAxOTI1MDUwNTA2MDAwNjA0MDUxODA4MzAzODE2MDAwODc4MDNiMTU4MDE1NjEwNmY1NTc2MDAwODBmZDViNTA1YWYxMTU4MDE1NjEwNzA5NTczZDYwMDA4MDNlM2Q2MDAwZmQ1YjUwNTA1MDUwNTA1MDUwNTA1NjViNjAwMDgwOTA1NDkwNjEwMTAwMGE5MDA0NzNmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmY=","b64Record":"CiAIFiocCgwIARAMGgYIgK6ZpA8SDAgBEA8aBgiArpmkDxIw2CetI8FIdrR6PniaRCzVo7jzX9JFRm5oraYabZRjkiU85Mwx5IVvoMkABlgl91UYGgwIvv7+rAYQtfuX0QMiDwoJCIL+/qwGENQQEgIYAiogw4PCrsOCwrfDg8K5dEY4w4LCrkrDg8KLw4PCkMODwo5SAA=="},{"b64Body":"Cg8KCQiD/v6sBhDaEBICGAISAhgDGPjsozsiAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjoIB3hkSAxjvCCLWGWZmZmZmZmZmZmZmZmZmZmYxNjczZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjE2NjM4ZGQ5ODA0MzgyNjA0MDUxODI2M2ZmZmZmZmZmMTY2MGUwMWI4MTUyNjAwNDAxODA4MjgxNTI2MDIwMDE5MTUwNTA2MDAwNjA0MDUxODA4MzAzODE2MDAwODc4MDNiMTU4MDE1NjEwNzg3NTc2MDAwODBmZDViNTA1YWYxMTU4MDE1NjEwNzliNTczZDYwMDA4MDNlM2Q2MDAwZmQ1YjUwNTA1MDUwNjAwMTYwMDA5MDU0OTA2MTAxMDAwYTkwMDQ3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNjczZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjE2NjM4ZGQ5ODA0MzgyNjA0MDUxODI2M2ZmZmZmZmZmMTY2MGUwMWI4MTUyNjAwNDAxODA4MjgxNTI2MDIwMDE5MTUwNTA2MDAwNjA0MDUxODA4MzAzODE2MDAwODc4MDNiMTU4MDE1NjEwODE0NTc2MDAwODBmZDViNTA1YWYxMTU4MDE1NjEwODI4NTczZDYwMDA4MDNlM2Q2MDAwZmQ1YjUwNTA1MDUwNTA1NjViODE3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNjYxMDhmYzgyOTA4MTE1MDI5MDYwNDA1MTYwMDA2MDQwNTE4MDgzMDM4MTg1ODg4OGYxOTM1MDUwNTA1MDE1ODAxNTYxMDg3NTU3M2Q2MDAwODAzZTNkNjAwMGZkNWI1MDYwMDA4MDkwNTQ5MDYxMDEwMDBhOTAwNDczZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjE2NzNmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmMTY2M2FjZWY2MDM3ODM2MDAyODQ4MTYxMDhiZTU3ZmU1YjA0NjA0MDUxODM2M2ZmZmZmZmZmMTY2MGUwMWI4MTUyNjAwNDAxODA4MzczZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjE2NzNmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmMTY4MTUyNjAyMDAxODI4MTUyNjAyMDAxOTI1MDUwNTA2MDAwNjA0MDUxODA4MzAzODE2MDAwODc4MDNiMTU4MDE1NjEwOTI4NTc2MDAwODBmZDViNTA1YWYxMTU4MDE1NjEwOTNjNTczZDYwMDA4MDNlM2Q2MDAwZmQ1YjUwNTA1MDUwNjAwMTYwMDA5MDU0OTA2MTAxMDAwYTkwMDQ3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNjczZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjE2NjNhY2VmNjAzNzgzNjAwMjg0ODE2MTA5ODk1N2ZlNWIwNDYwNDA1MTgzNjNmZmZmZmZmZjE2NjBlMDFiODE1MjYwMDQwMTgwODM3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNjczZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjE2ODE1MjYwMjAwMTgyODE1MjYwMjAwMTkyNTA1MDUwNjAwMDYwNDA1MTgwODMwMzgxNjAwMDg3ODAzYjE1ODAxNTYxMDlmMzU3NjAwMDgwZmQ1YjUwNWFmMTE1ODAxNTYxMGEwNzU3M2Q2MDAwODAzZTNkNjAwMGZkNWI1MDUwNTA1MDUwNTA1NjViNjAwMDYwNjA2MDAwODA5MDU0OTA2MTAxMDAwYTkwMDQ3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNjczZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjE2ODM2MDQwNTE2MDI0MDE4MDgyODE1MjYwMjAwMTkxNTA1MDYwNDA1MTYwMjA4MTgzMDMwMzgxNTI5MDYwNDA1MjdmOGRkOTgwNDMwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDdiZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxOTE2NjAyMDgyMDE4MDUxN2JmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjgzODE4MzE2MTc4MzUyNTA1MDUwNTA2MDQwNTE4MDgyODA1MTkwNjAyMDAxOTA4MDgzODM1YjYwMjA4MzEwNjEwYjBjNTc4MDUxODI1MjYwMjA4MjAxOTE1MDYwMjA4MTAxOTA1MDYwMjA4MzAzOTI1MDYxMGFlOTU2NWI2MDAxODM2MDIwMDM2MTAxMDAwYTAzODAxOTgyNTExNjgxODQ1MTE2ODA4MjE3ODU1MjUwNTA1MDUwNTA1MDkwNTAwMTkxNTA1MDYwMDA2MDQwNTE4MDgzMDM4MTg1NWFmNDkxNTA1MDNkODA2MDAwODExNDYxMGI2YzU3NjA0MDUxOTE1MDYwMWYxOTYwM2YzZDAxMTY4MjAxNjA0MDUyM2Q4MjUyM2Q2MDAwNjAyMDg0MDEzZTYxMGI3MTU2NWI2MDYwOTE1MDViNTA5MTUwOTE1MDYwMDA2MDYwNjAwMTYwMDA5MDU0OTA2MTAxMDAwYTkwMDQ3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNjczZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjE2ODU2MDQwNTE2MDI0MDE4MDgyODE1MjYwMjAwMTkxNTA1MDYwNDA1MTYwMjA4MTgzMDMwMzgxNTI5MDYwNDA1MjdmOGRkOTgwNDMwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDdiZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxOTE2NjAyMDgyMDE4MDUxN2JmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjgzODE4MzE2MTc4MzUyNTA1MDUwNTA2MDQwNTE4MDgyODA1MTkwNjAyMDAxOTA4MDgzODM1YjYwMjA4MzEwNjEwYzc0NTc4MDUxODI1MjYwMjA4MjAxOTE1MDYwMjA4MTAxOTA1MDYwMjA4MzAzOTI1MDYxMGM1MTU2NWI2MDAxODM2MDIwMDM2MTAxMDAwYTAzODAxOTgyNTExNjgxODQ1MTE2ODA4MjE3ODU1MjUwNTA1MDUwNTA1MDkwNTAwMTkxNTA1MDYwMDA2MDQwNTE4MDgzMDM4MTg1NWFmNDkxNTA1MDNkODA2MDAwODExNDYxMGNkNDU3NjA0MDUxOTE1MDYwMWYxOTYwM2YzZDAxMTY4MjAxNjA0MDUyM2Q4MjUyM2Q2MDAwNjAyMDg0MDEzZTYxMGNkOTU2NWI2MDYwOTE1MDViNTA5MTUwOTE1MDgzMTU4MDYxMGNlOTU3NTA4MTE1NWIxNTYxMGQ1YzU3NjA0MDUxN2YwOGMzNzlhMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwODE1MjYwMDQwMTgwODA2MDIwMDE4MjgxMDM4MjUyNjAxZTgxNTI2MDIwMDE4MDdmNDQ2NTZjNjU2NzYxNzQ2NTIwNzQ3MjYxNmU3MzY2NjU3MjIwNjM2MTZjNmMyMDY2NjE2OTZjNjU2NDIxMDAwMDgxNTI1MDYwMjAwMTkxNTA1MDYwNDA1MTgwOTEwMzkwZmQ1YjUwNTA1MDUwNTA1NmZlYTI2NTYyN2E3YTcyMzE1ODIwNjE5ZDU1NTI1Y2QxN2Y3MzliMGNiMjZkODZmN2QxOGYxNTVlYWM0NWY5NTM3MGQ5NjRkZWEzYzcwZGIxNGQ2ZTY0NzM2ZjZjNjM0MzAwMDUxMTAwMzI=","b64Record":"CiAIFiocCgwIARAMGgYIgK6ZpA8SDAgBEA8aBgiArpmkDxIwcJGwH87+lRtrto8un6SzTvFGa0uukhKYv4hNJJG208MH/HUh48oKLwy/eMHCfnW2GgwIv/7+rAYQ3t6O2QEiDwoJCIP+/qwGENoQEgIYAiogw4PCrsOCwrfDg8K5dEY4w4LCrkrDg8KLw4PCkMODwo5SAA=="},{"b64Body":"Cg8KCQiD/v6sBhDcEBICGAISAhgDGP7LzSwiAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjooBjwESDAi/zNmwBhDL5Ka+AxptCiISIKAiRxoqow/HvaIBfa/UL4p4fIkxOTEzFhDvneYqT2JfCiM6IQK/k0xjAwwaKRw3O38pzHgKlC+KUq01FDubNWsfobsRtQoiEiDJ4uyEZCzgHd8rY1aVce4Y5Z+svWRGhD+nKl1Ghq+QcCIMSGVsbG8gV29ybGQhKgAyAA==","b64Record":"CiUIFhoDGPAIKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjDgWUjjFMmnDriwWOr9P7iRU5j/CvkwjmjYTfwB3sL+r1q1TFh/M1L/vBkxQEylbHYaDAi//v6sBhCls8PaAyIPCgkIg/7+rAYQ3BASAhgCKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjlIA"},{"b64Body":"Cg8KCQiE/v6sBhDgEBICGAISAhgDGM6Y0i8iAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjpoBkAYKAxjwCCKIBjYwODA2MDQwNTI2MTAxNzE4MDYxMDAxMzYwMDAzOTYwMDBmM2ZlNjA4MDYwNDA1MjYwMDQzNjEwNjEwMDI5NTc2MDAwMzU2MGUwMWM4MDYzOGRkOTgwNDMxNDYxMDAyYjU3ODA2M2FjZWY2MDM3MTQ2MTAwNTk1NzViMDA1YjYxMDA1NzYwMDQ4MDM2MDM2MDIwODExMDE1NjEwMDQxNTc2MDAwODBmZDViODEwMTkwODA4MDM1OTA2MDIwMDE5MDkyOTE5MDUwNTA1MDYxMDBhNzU2NWIwMDViNjEwMGE1NjAwNDgwMzYwMzYwNDA4MTEwMTU2MTAwNmY1NzYwMDA4MGZkNWI4MTAxOTA4MDgwMzU3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNjkwNjAyMDAxOTA5MjkxOTA4MDM1OTA2MDIwMDE5MDkyOTE5MDUwNTA1MDYxMDBmMTU2NWIwMDViMzM3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNjYxMDhmYzgyOTA4MTE1MDI5MDYwNDA1MTYwMDA2MDQwNTE4MDgzMDM4MTg1ODg4OGYxOTM1MDUwNTA1MDE1ODAxNTYxMDBlZDU3M2Q2MDAwODAzZTNkNjAwMGZkNWI1MDUwNTY1YjgxNzNmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmMTY2MTA4ZmM4MjkwODExNTAyOTA2MDQwNTE2MDAwNjA0MDUxODA4MzAzODE4NTg4ODhmMTkzNTA1MDUwNTAxNTgwMTU2MTAxMzc1NzNkNjAwMDgwM2UzZDYwMDBmZDViNTA1MDUwNTZmZWEyNjU2MjdhN2E3MjMxNTgyMDVlYjQwYmI2MjA4MzVmYjc5NjljZGQ3NWRlZDE4ODkyN2Q1YzkyN2Y5M2JhNjc1ZDY1YjljNTIzNDQ0MjQyODc2NDczNmY2YzYzNDMwMDA1MTEwMDMy","b64Record":"CiAIFiocCgwIARAMGgYIgK6ZpA8SDAgBEA8aBgiArpmkDxIwas5TIiYGCHockrYPLb1h/boxBOot9LKhdJ+Q279TBnqzCOkFdH3CEQQyUZwTYoYvGgwIwP7+rAYQ4vLK4gEiDwoJCIT+/qwGEOAQEgIYAiogw4PCrsOCwrfDg8K5dEY4w4LCrkrDg8KLw4PCkMODwo5SAA=="},{"b64Body":"ChAKCQiE/v6sBhDiEBIDGO0IEgIYAxiW+66dAiICCHgyIMODwq7DgsK3w4PCuXRGOMOCwq5Kw4PCi8ODwpDDg8KOQkgKAxjwCBoiEiBOFADUbvDGHhlNMnfEXmMFqg1X2D++USQaqT3sawzqsSCQoQ8okE5CBQiAztoDUgBaAGoLY2VsbGFyIGRvb3I=","b64Record":"CiUIFiIDGPEIKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjBXf1hn0oY04asB6zTPLWqToBCJlO+6g6WeTUmIKj0+zSuWICOEk3eF5KRhrrCGXmEaCwjB/v6sBhCGraUHIhAKCQiE/v6sBhDiEBIDGO0IKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjjDW1JGkAkKmBQoDGPEIEvECYIBgQFJgBDYQYQApV2AANWDgHIBjjdmAQxRhACtXgGOs72A3FGEAWVdbAFthAFdgBIA2A2AggRAVYQBBV2AAgP1bgQGQgIA1kGAgAZCSkZBQUFBhAKdWWwBbYQClYASANgNgQIEQFWEAb1dgAID9W4EBkICANXP//////////////////////////xaQYCABkJKRkIA1kGAgAZCSkZBQUFBhAPFWWwBbM3P//////////////////////////xZhCPyCkIEVApBgQFFgAGBAUYCDA4GFiIjxk1BQUFAVgBVhAO1XPWAAgD49YAD9W1BQVluBc///////////////////////////FmEI/IKQgRUCkGBAUWAAYEBRgIMDgYWIiPGTUFBQUBWAFWEBN1c9YACAPj1gAP1bUFBQVv6iZWJ6enIxWCBetAu2IINft5ac3XXe0YiSfVySf5O6Z11lucUjREJCh2Rzb2xjQwAFEQAyIoACAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACjAmgw6AxjxCEoWChQAAAAAAAAAAAAAAAAAAAAAAAAEcXIHCgMY8QgQAVJHCgkKAhgDEJjiiRQKCgoCGGIQ4NyIxgMKCgoDGKAGEJq1iDcKCgoDGKEGEJq1iDcKCwoDGO0IEMvFpMgECgkKAxjxCBCgnAE="},{"b64Body":"ChAKCQiF/v6sBhDkEBIDGO0IEgIYAxiW+66dAiICCHgyIMODwq7DgsK3w4PCuXRGOMOCwq5Kw4PCi8ODwpDDg8KOQkgKAxjwCBoiEiCkv5DWz4L2s9mNokDo7Wk42ylnW71ntG8ieQILoRHMsyCQoQ8okE5CBQiAztoDUgBaAGoLY2VsbGFyIGRvb3I=","b64Record":"CiUIFiIDGPIIKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjCJ0YcQcpP+PtcnHvsQL8tPPYRYA+a2OavJ03UZ97sM+92M0tmUWE+owZAIf1Ct2NQaDAjB/v6sBhC6hLPsASIQCgkIhf7+rAYQ5BASAxjtCCogw4PCrsOCwrfDg8K5dEY4w4LCrkrDg8KLw4PCkMODwo4w1tSRpAJCpgUKAxjyCBLxAmCAYEBSYAQ2EGEAKVdgADVg4ByAY43ZgEMUYQArV4BjrO9gNxRhAFlXWwBbYQBXYASANgNgIIEQFWEAQVdgAID9W4EBkICANZBgIAGQkpGQUFBQYQCnVlsAW2EApWAEgDYDYECBEBVhAG9XYACA/VuBAZCAgDVz//////////////////////////8WkGAgAZCSkZCANZBgIAGQkpGQUFBQYQDxVlsAWzNz//////////////////////////8WYQj8gpCBFQKQYEBRYABgQFGAgwOBhYiI8ZNQUFBQFYAVYQDtVz1gAIA+PWAA/VtQUFZbgXP//////////////////////////xZhCPyCkIEVApBgQFFgAGBAUYCDA4GFiIjxk1BQUFAVgBVhATdXPWAAgD49YAD9W1BQUFb+omVienpyMVggXrQLtiCDX7eWnN113tGIkn1ckn+TumddZbnFI0RCQodkc29sY0MABREAMiKAAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAowJoMOgMY8ghKFgoUAAAAAAAAAAAAAAAAAAAAAAAABHJyBwoDGPIIEAFSRwoJCgIYAxCY4okUCgoKAhhiEODciMYDCgoKAxigBhCatYg3CgoKAxihBhCatYg3CgsKAxjtCBDLxaTIBAoJCgMY8ggQoJwB"},{"b64Body":"ChAKCQiF/v6sBhDsEBIDGO0IEgIYAxjVgL+gAiICCHgyIMODwq7DgsK3w4PCuXRGOMOCwq5Kw4PCi8ODwpDDg8KOQooBCgMY7wgaIhIg/yp3zexbwSRnbNBj1mOSCBCR1sC/LQhbDuqFrWWzr1wgkKEPKJBOQgUIgM7aA0pAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABHEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEclIAWgBqC2NlbGxhciBkb29y","b64Record":"CiUIFiIDGPMIKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjB8FH04ILNNbGgPP3uCRjUtHwq6eotU6K3csD1ECUQWnkdeXINmBjmeGXuBWX0R6fUaCwjC/v6sBhDD2N4QIhAKCQiF/v6sBhDsEBIDGO0IKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjjCV2qGnAkLNHQoDGPMIEpgbYIBgQFJgBDYQYQA/V2AANWDgHIBjVVXX5BRhAEFXgGNezymKFGEAz1eAY53H2xAUYQD9V4BjqaOmNRRhAUtXWwBbYQDNYASANgNggIEQFWEAV1dgAID9W4EBkICANXP//////////////////////////xaQYCABkJKRkIA1c///////////////////////////FpBgIAGQkpGQgDVz//////////////////////////8WkGAgAZCSkZCANZBgIAGQkpGQUFBQYQF5VlsAW2EA+2AEgDYDYCCBEBVhAOVXYACA/VuBAZCAgDWQYCABkJKRkFBQUGEHE1ZbAFthAUlgBIA2A2BAgRAVYQETV2AAgP1bgQGQgIA1c///////////////////////////FpBgIAGQkpGQgDWQYCABkJKRkFBQUGEIL1ZbAFthAXdgBIA2A2AggRAVYQFhV2AAgP1bgQGQgIA1kGAgAZCSkZBQUFBhCg9WWwBbg3P//////////////////////////xZhCPyCkIEVApBgQFFgAGBAUYCDA4GFiIjxk1BQUFAVgBVhAb9XPWAAgD49YAD9W1BgAICQVJBhAQAKkARz//////////////////////////8Wc///////////////////////////FmOs72A3hWAChIFhAghX/lsEYEBRg2P/////FmDgG4FSYAQBgINz//////////////////////////8Wc///////////////////////////FoFSYCABgoFSYCABklBQUGAAYEBRgIMDgWAAh4A7FYAVYQJyV2AAgP1bUFrxFYAVYQKGVz1gAIA+PWAA/VtQUFBQYAFgAJBUkGEBAAqQBHP//////////////////////////xZz//////////////////////////8WY6zvYDeFYAKEgWEC01f+WwRgQFGDY/////8WYOAbgVJgBAGAg3P//////////////////////////xZz//////////////////////////8WgVJgIAGCgVJgIAGSUFBQYABgQFGAgwOBYACHgDsVgBVhAz1XYACA/VtQWvEVgBVhA1FXPWAAgD49YAD9W1BQUFCCc///////////////////////////FmEI/IKQgRUCkGBAUWAAYEBRgIMDgYWIiPGTUFBQUBWAFWEDm1c9YACAPj1gAP1bUGAAgJBUkGEBAAqQBHP//////////////////////////xZz//////////////////////////8WY6zvYDeEYAKEgWED5Ff+WwRgQFGDY/////8WYOAbgVJgBAGAg3P//////////////////////////xZz//////////////////////////8WgVJgIAGCgVJgIAGSUFBQYABgQFGAgwOBYACHgDsVgBVhBE5XYACA/VtQWvEVgBVhBGJXPWAAgD49YAD9W1BQUFBgAWAAkFSQYQEACpAEc///////////////////////////FnP//////////////////////////xZjrO9gN4RgAoSBYQSvV/5bBGBAUYNj/////xZg4BuBUmAEAYCDc///////////////////////////FnP//////////////////////////xaBUmAgAYKBUmAgAZJQUFBgAGBAUYCDA4FgAIeAOxWAFWEFGVdgAID9W1Ba8RWAFWEFLVc9YACAPj1gAP1bUFBQUIFz//////////////////////////8WYQj8gpCBFQKQYEBRYABgQFGAgwOBhYiI8ZNQUFBQFYAVYQV3Vz1gAIA+PWAA/VtQYACAkFSQYQEACpAEc///////////////////////////FnP//////////////////////////xZjrO9gN4NgAoSBYQXAV/5bBGBAUYNj/////xZg4BuBUmAEAYCDc///////////////////////////FnP//////////////////////////xaBUmAgAYKBUmAgAZJQUFBgAGBAUYCDA4FgAIeAOxWAFWEGKldgAID9W1Ba8RWAFWEGPlc9YACAPj1gAP1bUFBQUGABYACQVJBhAQAKkARz//////////////////////////8Wc///////////////////////////FmOs72A3g2AChIFhBotX/lsEYEBRg2P/////FmDgG4FSYAQBgINz//////////////////////////8Wc///////////////////////////FoFSYCABgoFSYCABklBQUGAAYEBRgIMDgWAAh4A7FYAVYQb1V2AAgP1bUFrxFYAVYQcJVz1gAIA+PWAA/VtQUFBQUFBQUFZbYACAkFSQYQEACpAEc///////////////////////////FnP//////////////////////////xZjjdmAQ4JgQFGCY/////8WYOAbgVJgBAGAgoFSYCABkVBQYABgQFGAgwOBYACHgDsVgBVhB4dXYACA/VtQWvEVgBVhB5tXPWAAgD49YAD9W1BQUFBgAWAAkFSQYQEACpAEc///////////////////////////FnP//////////////////////////xZjjdmAQ4JgQFGCY/////8WYOAbgVJgBAGAgoFSYCABkVBQYABgQFGAgwOBYACHgDsVgBVhCBRXYACA/VtQWvEVgBVhCChXPWAAgD49YAD9W1BQUFBQVluBc///////////////////////////FmEI/IKQgRUCkGBAUWAAYEBRgIMDgYWIiPGTUFBQUBWAFWEIdVc9YACAPj1gAP1bUGAAgJBUkGEBAAqQBHP//////////////////////////xZz//////////////////////////8WY6zvYDeDYAKEgWEIvlf+WwRgQFGDY/////8WYOAbgVJgBAGAg3P//////////////////////////xZz//////////////////////////8WgVJgIAGCgVJgIAGSUFBQYABgQFGAgwOBYACHgDsVgBVhCShXYACA/VtQWvEVgBVhCTxXPWAAgD49YAD9W1BQUFBgAWAAkFSQYQEACpAEc///////////////////////////FnP//////////////////////////xZjrO9gN4NgAoSBYQmJV/5bBGBAUYNj/////xZg4BuBUmAEAYCDc///////////////////////////FnP//////////////////////////xaBUmAgAYKBUmAgAZJQUFBgAGBAUYCDA4FgAIeAOxWAFWEJ81dgAID9W1Ba8RWAFWEKB1c9YACAPj1gAP1bUFBQUFBQVltgAGBgYACAkFSQYQEACpAEc///////////////////////////FnP//////////////////////////xaDYEBRYCQBgIKBUmAgAZFQUGBAUWAggYMDA4FSkGBAUn+N2YBDAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHv/////////////////////////////////////GRZgIIIBgFF7/////////////////////////////////////4OBgxYXg1JQUFBQYEBRgIKAUZBgIAGQgIODW2AggxBhCwxXgFGCUmAgggGRUGAggQGQUGAggwOSUGEK6VZbYAGDYCADYQEACgOAGYJRFoGEURaAgheFUlBQUFBQUJBQAZFQUGAAYEBRgIMDgYVa9JFQUD2AYACBFGELbFdgQFGRUGAfGWA/PQEWggFgQFI9glI9YABgIIQBPmELcVZbYGCRUFtQkVCRUGAAYGBgAWAAkFSQYQEACpAEc///////////////////////////FnP//////////////////////////xaFYEBRYCQBgIKBUmAgAZFQUGBAUWAggYMDA4FSkGBAUn+N2YBDAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHv/////////////////////////////////////GRZgIIIBgFF7/////////////////////////////////////4OBgxYXg1JQUFBQYEBRgIKAUZBgIAGQgIODW2AggxBhDHRXgFGCUmAgggGRUGAggQGQUGAggwOSUGEMUVZbYAGDYCADYQEACgOAGYJRFoGEURaAgheFUlBQUFBQUJBQAZFQUGAAYEBRgIMDgYVa9JFQUD2AYACBFGEM1FdgQFGRUGAfGWA/PQEWggFgQFI9glI9YABgIIQBPmEM2VZbYGCRUFtQkVCRUIMVgGEM6VdQgRVbFWENXFdgQFF/CMN5oAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACBUmAEAYCAYCABgoEDglJgHoFSYCABgH9EZWxlZ2F0ZSB0cmFuc2ZlciBjYWxsIGZhaWxlZCEAAIFSUGAgAZFQUGBAUYCRA5D9W1BQUFBQVv6iZWJ6enIxWCBhnVVSXNF/c5sMsm2G99GPFV6sRflTcNlk3qPHDbFNbmRzb2xjQwAFEQAyIoACAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACjAmgw6AxjzCEoWChQAAAAAAAAAAAAAAAAAAAAAAAAEc3IHCgMY8wgQAVJHCgkKAhgDEK7quBQKCgoCGGIQmJLjygMKCgoDGKAGEPLb0zcKCgoDGKEGEPLb0zcKCwoDGO0IEMnQxM4ECgkKAxjzCBCgnAE="},{"b64Body":"ChAKCQiG/v6sBhDuEBIDGO0IEgIYAxjgrLEDIgIIeDIgw4PCrsOCwrfDg8K5dEY4w4LCrkrDg8KLw4PCkMODwo46TwoDGPMIEKCNBiJEncfbEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAARuAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACg=","b64Record":"CiUIFiIDGPMIKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjCh0st2CTIOnJ3tVOZMTvgYz5Y5Fo+02vWWwXr5rm/H3rTE6LtUNsV5kdOztKrUxUIaDAjC/v6sBhDvhdr1ASIQCgkIhv7+rAYQ7hASAxjtCCogw4PCrsOCwrfDg8K5dEY4w4LCrkrDg8KLw4PCkMODwo4wgNfaAjqMAgoDGPMIIoACAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACiA8QRSPAoJCgIYYhCArrUFCgoKAxjtCBD/rbUFCggKAxjuCBCgAQoHCgMY8QgQJwoHCgMY8ggQJwoHCgMY8wgQTw=="}]},"sendHbarsFromAndToDifferentAddressess":{"placeholderNum":1140,"encodedItems":[{"b64Body":"Cg8KCQiL/v6sBhCKERICGAISAhgDGPuV9hQiAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjloyCiISIO+qO89LRbNcLl2QjXlab3CyAKgrG1ofUOLN1EB6JOoIEIDAqMqaOkoFCIDO2gM=","b64Record":"CiUIFhIDGPUIKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjCoHho1iFCEXWjiCuzJiL6yfIX7pft5yYe100V9VT+iMIRtY2F/oNWB7RZOLkMEMDkaCwjH/v6sBhCUm4p6Ig8KCQiL/v6sBhCKERICGAIqIMODwq7DgsK3w4PCuXRGOMOCwq5Kw4PCi8ODwpDDg8KOUhsKCwoCGAIQ///QlLV0CgwKAxj1CBCAgNGUtXQ="},{"b64Body":"Cg8KCQiL/v6sBhCMERICGAISAhgDGPuV9hQiAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjlouCiISIPfrq5QWrAvxf6HC2idvEhB1WSIsj9UCZIv9cp0jmE61EJBOSgUIgM7aAw==","b64Record":"CiUIFhIDGPYIKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjD36Q0LkkQaGzB7YtoGo+O+pl5rHZGCXeSAtMNox4ICgd5LFfv5HRDck3MY4zHUy9AaDAjH/v6sBhDJq8beAiIPCgkIi/7+rAYQjBESAhgCKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjlIVCggKAhgCEJ+cAQoJCgMY9ggQoJwB"},{"b64Body":"Cg8KCQiM/v6sBhCOERICGAISAhgDGPuV9hQiAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjlouCiISIP0R8/MVlQrTO0NShrPCnXIDe0XMYAlyhW92t9TsB8OgEJBOSgUIgM7aAw==","b64Record":"CiUIFhIDGPcIKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjDLiH7bqyp7bmehuiDTJYXfZNjbDpNP1XLJgxZFuvr7RkfRenXYiJDYPbVEtE5kVUYaDAjI/v6sBhCBzpWDASIPCgkIjP7+rAYQjhESAhgCKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjlIVCggKAhgCEJ+cAQoJCgMY9wgQoJwB"},{"b64Body":"Cg8KCQiM/v6sBhCQERICGAISAhgDGPuV9hQiAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjlouCiISIJsrJPM9JzNTHITAj3kKLNodq7L9oJzwXk88raM/0oS9EJBOSgUIgM7aAw==","b64Record":"CiUIFhIDGPgIKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjDytMLiy04dc+3l6w3AEmZ0PInmUBWUVYWbtZ3WSY8ahLJL6Fwcpx6oqrhMEWKS2L4aDAjI/v6sBhDi96XoAiIPCgkIjP7+rAYQkBESAhgCKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjlIVCggKAhgCEJ+cAQoJCgMY+AgQoJwB"},{"b64Body":"Cg8KCQiN/v6sBhCSERICGAISAhgDGP7LzSwiAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjooBjgESCwjJzNmwBhCXg5FzGm0KIhIgMn23655AWQ8q0hHYa3scbdK+ebNbQr55pthFOgvmV7wKIzohA3YI13kPCTubafKgM30LE490Bj2B1CF3eyFLPyiYhWNPCiISIFHTpmYk7nl3oHNiSClcbCBTBrkitceXKQhLjKViXZaKIgxIZWxsbyBXb3JsZCEqADIA","b64Record":"CiUIFhoDGPkIKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjD7Kh/YQTiX1iN83fePijoEI/R1Msa8j9K2G/ZnNYFeA0vY5v7ayh+Kllv/67B+CosaDAjJ/v6sBhCFvMGWASIPCgkIjf7+rAYQkhESAhgCKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjlIA"},{"b64Body":"Cg8KCQiN/v6sBhCWERICGAISAhgDGIydjj4iAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjpoBiCAKAxj5CCKAIDYwODA2MDQwNTI2MDQwNTE2MTBlNmIzODAzODA2MTBlNmI4MzM5ODE4MTAxNjA0MDUyNjA0MDgxMTAxNTYxMDAyNjU3NjAwMDgwZmQ1YjgxMDE5MDgwODA1MTkwNjAyMDAxOTA5MjkxOTA4MDUxOTA2MDIwMDE5MDkyOTE5MDUwNTA1MDgxNjAwMDgwNjEwMTAwMGE4MTU0ODE3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYwMjE5MTY5MDgzNzNmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmMTYwMjE3OTA1NTUwODA2MDAxNjAwMDYxMDEwMDBhODE1NDgxNzNmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmMDIxOTE2OTA4MzczZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjE2MDIxNzkwNTU1MDUwNTA2MTBkOTg4MDYxMDBkMzYwMDAzOTYwMDBmM2ZlNjA4MDYwNDA1MjYwMDQzNjEwNjEwMDNmNTc2MDAwMzU2MGUwMWM4MDYzNTU1NWQ3ZTQxNDYxMDA0MTU3ODA2MzVlY2YyOThhMTQ2MTAwY2Y1NzgwNjM5ZGM3ZGIxMDE0NjEwMGZkNTc4MDYzYTlhM2E2MzUxNDYxMDE0YjU3NWIwMDViNjEwMGNkNjAwNDgwMzYwMzYwODA4MTEwMTU2MTAwNTc1NzYwMDA4MGZkNWI4MTAxOTA4MDgwMzU3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNjkwNjAyMDAxOTA5MjkxOTA4MDM1NzNmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmMTY5MDYwMjAwMTkwOTI5MTkwODAzNTczZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjE2OTA2MDIwMDE5MDkyOTE5MDgwMzU5MDYwMjAwMTkwOTI5MTkwNTA1MDUwNjEwMTc5NTY1YjAwNWI2MTAwZmI2MDA0ODAzNjAzNjAyMDgxMTAxNTYxMDBlNTU3NjAwMDgwZmQ1YjgxMDE5MDgwODAzNTkwNjAyMDAxOTA5MjkxOTA1MDUwNTA2MTA3MTM1NjViMDA1YjYxMDE0OTYwMDQ4MDM2MDM2MDQwODExMDE1NjEwMTEzNTc2MDAwODBmZDViODEwMTkwODA4MDM1NzNmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmMTY5MDYwMjAwMTkwOTI5MTkwODAzNTkwNjAyMDAxOTA5MjkxOTA1MDUwNTA2MTA4MmY1NjViMDA1YjYxMDE3NzYwMDQ4MDM2MDM2MDIwODExMDE1NjEwMTYxNTc2MDAwODBmZDViODEwMTkwODA4MDM1OTA2MDIwMDE5MDkyOTE5MDUwNTA1MDYxMGEwZjU2NWIwMDViODM3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNjYxMDhmYzgyOTA4MTE1MDI5MDYwNDA1MTYwMDA2MDQwNTE4MDgzMDM4MTg1ODg4OGYxOTM1MDUwNTA1MDE1ODAxNTYxMDFiZjU3M2Q2MDAwODAzZTNkNjAwMGZkNWI1MDYwMDA4MDkwNTQ5MDYxMDEwMDBhOTAwNDczZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjE2NzNmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmMTY2M2FjZWY2MDM3ODU2MDAyODQ4MTYxMDIwODU3ZmU1YjA0NjA0MDUxODM2M2ZmZmZmZmZmMTY2MGUwMWI4MTUyNjAwNDAxODA4MzczZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjE2NzNmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmMTY4MTUyNjAyMDAxODI4MTUyNjAyMDAxOTI1MDUwNTA2MDAwNjA0MDUxODA4MzAzODE2MDAwODc4MDNiMTU4MDE1NjEwMjcyNTc2MDAwODBmZDViNTA1YWYxMTU4MDE1NjEwMjg2NTczZDYwMDA4MDNlM2Q2MDAwZmQ1YjUwNTA1MDUwNjAwMTYwMDA5MDU0OTA2MTAxMDAwYTkwMDQ3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNjczZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjE2NjNhY2VmNjAzNzg1NjAwMjg0ODE2MTAyZDM1N2ZlNWIwNDYwNDA1MTgzNjNmZmZmZmZmZjE2NjBlMDFiODE1MjYwMDQwMTgwODM3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNjczZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjE2ODE1MjYwMjAwMTgyODE1MjYwMjAwMTkyNTA1MDUwNjAwMDYwNDA1MTgwODMwMzgxNjAwMDg3ODAzYjE1ODAxNTYxMDMzZDU3NjAwMDgwZmQ1YjUwNWFmMTE1ODAxNTYxMDM1MTU3M2Q2MDAwODAzZTNkNjAwMGZkNWI1MDUwNTA1MDgyNzNmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmMTY2MTA4ZmM4MjkwODExNTAyOTA2MDQwNTE2MDAwNjA0MDUxODA4MzAzODE4NTg4ODhmMTkzNTA1MDUwNTAxNTgwMTU2MTAzOWI1NzNkNjAwMDgwM2UzZDYwMDBmZDViNTA2MDAwODA5MDU0OTA2MTAxMDAwYTkwMDQ3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNjczZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjE2NjNhY2VmNjAzNzg0NjAwMjg0ODE2MTAzZTQ1N2ZlNWIwNDYwNDA1MTgzNjNmZmZmZmZmZjE2NjBlMDFiODE1MjYwMDQwMTgwODM3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNjczZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjE2ODE1MjYwMjAwMTgyODE1MjYwMjAwMTkyNTA1MDUwNjAwMDYwNDA1MTgwODMwMzgxNjAwMDg3ODAzYjE1ODAxNTYxMDQ0ZTU3NjAwMDgwZmQ1YjUwNWFmMTE1ODAxNTYxMDQ2MjU3M2Q2MDAwODAzZTNkNjAwMGZkNWI1MDUwNTA1MDYwMDE2MDAwOTA1NDkwNjEwMTAwMGE5MDA0NzNmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmMTY3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNjYzYWNlZjYwMzc4NDYwMDI4NDgxNjEwNGFmNTdmZTViMDQ2MDQwNTE4MzYzZmZmZmZmZmYxNjYwZTAxYjgxNTI2MDA0MDE4MDgzNzNmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmMTY3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNjgxNTI2MDIwMDE4MjgxNTI2MDIwMDE5MjUwNTA1MDYwMDA2MDQwNTE4MDgzMDM4MTYwMDA4NzgwM2IxNTgwMTU2MTA1MTk1NzYwMDA4MGZkNWI1MDVhZjExNTgwMTU2MTA1MmQ1NzNkNjAwMDgwM2UzZDYwMDBmZDViNTA1MDUwNTA4MTczZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjE2NjEwOGZjODI5MDgxMTUwMjkwNjA0MDUxNjAwMDYwNDA1MTgwODMwMzgxODU4ODg4ZjE5MzUwNTA1MDUwMTU4MDE1NjEwNTc3NTczZDYwMDA4MDNlM2Q2MDAwZmQ1YjUwNjAwMDgwOTA1NDkwNjEwMTAwMGE5MDA0NzNmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmMTY3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNjYzYWNlZjYwMzc4MzYwMDI4NDgxNjEwNWMwNTdmZTViMDQ2MDQwNTE4MzYzZmZmZmZmZmYxNjYwZTAxYjgxNTI2MDA0MDE4MDgzNzNmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmMTY3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNjgxNTI2MDIwMDE4MjgxNTI2MDIwMDE5MjUwNTA1MDYwMDA2MDQwNTE4MDgzMDM4MTYwMDA4NzgwM2IxNTgwMTU2MTA2MmE1NzYwMDA4MGZkNWI1MDVhZjExNTgwMTU2MTA2M2U1NzNkNjAwMDgwM2UzZDYwMDBmZDViNTA1MDUwNTA2MDAxNjAwMDkwNTQ5MDYxMDEwMDBhOTAwNDczZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjE2NzNmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmMTY2M2FjZWY2MDM3ODM2MDAyODQ4MTYxMDY4YjU3ZmU1YjA0NjA0MDUxODM2M2ZmZmZmZmZmMTY2MGUwMWI4MTUyNjAwNDAxODA4MzczZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjE2NzNmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmMTY4MTUyNjAyMDAxODI4MTUyNjAyMDAxOTI1MDUwNTA2MDAwNjA0MDUxODA4MzAzODE2MDAwODc4MDNiMTU4MDE1NjEwNmY1NTc2MDAwODBmZDViNTA1YWYxMTU4MDE1NjEwNzA5NTczZDYwMDA4MDNlM2Q2MDAwZmQ1YjUwNTA1MDUwNTA1MDUwNTA1NjViNjAwMDgwOTA1NDkwNjEwMTAwMGE5MDA0NzNmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmY=","b64Record":"CiAIFiocCgwIARAMGgYIgK6ZpA8SDAgBEA8aBgiArpmkDxIw/h28eoq0i31zDmiKFRfCA3+cHc/MlrpJaYW2WaOkhhdtQi6IZD2BVuhjpLWSE9OwGgwIyf7+rAYQ17XM+wIiDwoJCI3+/qwGEJYREgIYAiogw4PCrsOCwrfDg8K5dEY4w4LCrkrDg8KLw4PCkMODwo5SAA=="},{"b64Body":"Cg8KCQiO/v6sBhCcERICGAISAhgDGPjsozsiAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjoIB3hkSAxj5CCLWGWZmZmZmZmZmZmZmZmZmZmYxNjczZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjE2NjM4ZGQ5ODA0MzgyNjA0MDUxODI2M2ZmZmZmZmZmMTY2MGUwMWI4MTUyNjAwNDAxODA4MjgxNTI2MDIwMDE5MTUwNTA2MDAwNjA0MDUxODA4MzAzODE2MDAwODc4MDNiMTU4MDE1NjEwNzg3NTc2MDAwODBmZDViNTA1YWYxMTU4MDE1NjEwNzliNTczZDYwMDA4MDNlM2Q2MDAwZmQ1YjUwNTA1MDUwNjAwMTYwMDA5MDU0OTA2MTAxMDAwYTkwMDQ3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNjczZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjE2NjM4ZGQ5ODA0MzgyNjA0MDUxODI2M2ZmZmZmZmZmMTY2MGUwMWI4MTUyNjAwNDAxODA4MjgxNTI2MDIwMDE5MTUwNTA2MDAwNjA0MDUxODA4MzAzODE2MDAwODc4MDNiMTU4MDE1NjEwODE0NTc2MDAwODBmZDViNTA1YWYxMTU4MDE1NjEwODI4NTczZDYwMDA4MDNlM2Q2MDAwZmQ1YjUwNTA1MDUwNTA1NjViODE3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNjYxMDhmYzgyOTA4MTE1MDI5MDYwNDA1MTYwMDA2MDQwNTE4MDgzMDM4MTg1ODg4OGYxOTM1MDUwNTA1MDE1ODAxNTYxMDg3NTU3M2Q2MDAwODAzZTNkNjAwMGZkNWI1MDYwMDA4MDkwNTQ5MDYxMDEwMDBhOTAwNDczZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjE2NzNmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmMTY2M2FjZWY2MDM3ODM2MDAyODQ4MTYxMDhiZTU3ZmU1YjA0NjA0MDUxODM2M2ZmZmZmZmZmMTY2MGUwMWI4MTUyNjAwNDAxODA4MzczZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjE2NzNmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmMTY4MTUyNjAyMDAxODI4MTUyNjAyMDAxOTI1MDUwNTA2MDAwNjA0MDUxODA4MzAzODE2MDAwODc4MDNiMTU4MDE1NjEwOTI4NTc2MDAwODBmZDViNTA1YWYxMTU4MDE1NjEwOTNjNTczZDYwMDA4MDNlM2Q2MDAwZmQ1YjUwNTA1MDUwNjAwMTYwMDA5MDU0OTA2MTAxMDAwYTkwMDQ3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNjczZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjE2NjNhY2VmNjAzNzgzNjAwMjg0ODE2MTA5ODk1N2ZlNWIwNDYwNDA1MTgzNjNmZmZmZmZmZjE2NjBlMDFiODE1MjYwMDQwMTgwODM3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNjczZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjE2ODE1MjYwMjAwMTgyODE1MjYwMjAwMTkyNTA1MDUwNjAwMDYwNDA1MTgwODMwMzgxNjAwMDg3ODAzYjE1ODAxNTYxMDlmMzU3NjAwMDgwZmQ1YjUwNWFmMTE1ODAxNTYxMGEwNzU3M2Q2MDAwODAzZTNkNjAwMGZkNWI1MDUwNTA1MDUwNTA1NjViNjAwMDYwNjA2MDAwODA5MDU0OTA2MTAxMDAwYTkwMDQ3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNjczZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjE2ODM2MDQwNTE2MDI0MDE4MDgyODE1MjYwMjAwMTkxNTA1MDYwNDA1MTYwMjA4MTgzMDMwMzgxNTI5MDYwNDA1MjdmOGRkOTgwNDMwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDdiZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxOTE2NjAyMDgyMDE4MDUxN2JmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjgzODE4MzE2MTc4MzUyNTA1MDUwNTA2MDQwNTE4MDgyODA1MTkwNjAyMDAxOTA4MDgzODM1YjYwMjA4MzEwNjEwYjBjNTc4MDUxODI1MjYwMjA4MjAxOTE1MDYwMjA4MTAxOTA1MDYwMjA4MzAzOTI1MDYxMGFlOTU2NWI2MDAxODM2MDIwMDM2MTAxMDAwYTAzODAxOTgyNTExNjgxODQ1MTE2ODA4MjE3ODU1MjUwNTA1MDUwNTA1MDkwNTAwMTkxNTA1MDYwMDA2MDQwNTE4MDgzMDM4MTg1NWFmNDkxNTA1MDNkODA2MDAwODExNDYxMGI2YzU3NjA0MDUxOTE1MDYwMWYxOTYwM2YzZDAxMTY4MjAxNjA0MDUyM2Q4MjUyM2Q2MDAwNjAyMDg0MDEzZTYxMGI3MTU2NWI2MDYwOTE1MDViNTA5MTUwOTE1MDYwMDA2MDYwNjAwMTYwMDA5MDU0OTA2MTAxMDAwYTkwMDQ3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNjczZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjE2ODU2MDQwNTE2MDI0MDE4MDgyODE1MjYwMjAwMTkxNTA1MDYwNDA1MTYwMjA4MTgzMDMwMzgxNTI5MDYwNDA1MjdmOGRkOTgwNDMwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDdiZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxOTE2NjAyMDgyMDE4MDUxN2JmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjgzODE4MzE2MTc4MzUyNTA1MDUwNTA2MDQwNTE4MDgyODA1MTkwNjAyMDAxOTA4MDgzODM1YjYwMjA4MzEwNjEwYzc0NTc4MDUxODI1MjYwMjA4MjAxOTE1MDYwMjA4MTAxOTA1MDYwMjA4MzAzOTI1MDYxMGM1MTU2NWI2MDAxODM2MDIwMDM2MTAxMDAwYTAzODAxOTgyNTExNjgxODQ1MTE2ODA4MjE3ODU1MjUwNTA1MDUwNTA1MDkwNTAwMTkxNTA1MDYwMDA2MDQwNTE4MDgzMDM4MTg1NWFmNDkxNTA1MDNkODA2MDAwODExNDYxMGNkNDU3NjA0MDUxOTE1MDYwMWYxOTYwM2YzZDAxMTY4MjAxNjA0MDUyM2Q4MjUyM2Q2MDAwNjAyMDg0MDEzZTYxMGNkOTU2NWI2MDYwOTE1MDViNTA5MTUwOTE1MDgzMTU4MDYxMGNlOTU3NTA4MTE1NWIxNTYxMGQ1YzU3NjA0MDUxN2YwOGMzNzlhMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwODE1MjYwMDQwMTgwODA2MDIwMDE4MjgxMDM4MjUyNjAxZTgxNTI2MDIwMDE4MDdmNDQ2NTZjNjU2NzYxNzQ2NTIwNzQ3MjYxNmU3MzY2NjU3MjIwNjM2MTZjNmMyMDY2NjE2OTZjNjU2NDIxMDAwMDgxNTI1MDYwMjAwMTkxNTA1MDYwNDA1MTgwOTEwMzkwZmQ1YjUwNTA1MDUwNTA1NmZlYTI2NTYyN2E3YTcyMzE1ODIwNjE5ZDU1NTI1Y2QxN2Y3MzliMGNiMjZkODZmN2QxOGYxNTVlYWM0NWY5NTM3MGQ5NjRkZWEzYzcwZGIxNGQ2ZTY0NzM2ZjZjNjM0MzAwMDUxMTAwMzI=","b64Record":"CiAIFiocCgwIARAMGgYIgK6ZpA8SDAgBEA8aBgiArpmkDxIwsvFS3Nf6wmviPStdGFuO7Sci8KU/M7m2PFhPDlT1wD/3vNdiX4ZHeoEuv6iAa3LJGgwIyv7+rAYQjJSigwEiDwoJCI7+/qwGEJwREgIYAiogw4PCrsOCwrfDg8K5dEY4w4LCrkrDg8KLw4PCkMODwo5SAA=="},{"b64Body":"Cg8KCQiO/v6sBhCeERICGAISAhgDGP7LzSwiAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjooBjwESDAjKzNmwBhDSiZ/wAhptCiISIGMLarCrM86prcPKlwqk1taGtWKFMLRg3nuIrO/2ObH5CiM6IQKDn99q2egsv7eOkp5FIKqaj098aYeekwjt0h/acp2w1AoiEiCB7mfXpqbcgqx/M7dFN0fgeIn6qURzbthDPRP2NlN5JSIMSGVsbG8gV29ybGQhKgAyAA==","b64Record":"CiUIFhoDGPoIKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjDwpOvbuLA+SiAhjFh4+zSNspmn0th4JKj+d4NEMA9tQ4ffylAJ2hwd4gQ2i6tRRPoaDAjK/v6sBhD08aqOAyIPCgkIjv7+rAYQnhESAhgCKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjlIA"},{"b64Body":"Cg8KCQiP/v6sBhCiERICGAISAhgDGM6Y0i8iAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjpoBkAYKAxj6CCKIBjYwODA2MDQwNTI2MTAxNzE4MDYxMDAxMzYwMDAzOTYwMDBmM2ZlNjA4MDYwNDA1MjYwMDQzNjEwNjEwMDI5NTc2MDAwMzU2MGUwMWM4MDYzOGRkOTgwNDMxNDYxMDAyYjU3ODA2M2FjZWY2MDM3MTQ2MTAwNTk1NzViMDA1YjYxMDA1NzYwMDQ4MDM2MDM2MDIwODExMDE1NjEwMDQxNTc2MDAwODBmZDViODEwMTkwODA4MDM1OTA2MDIwMDE5MDkyOTE5MDUwNTA1MDYxMDBhNzU2NWIwMDViNjEwMGE1NjAwNDgwMzYwMzYwNDA4MTEwMTU2MTAwNmY1NzYwMDA4MGZkNWI4MTAxOTA4MDgwMzU3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNjkwNjAyMDAxOTA5MjkxOTA4MDM1OTA2MDIwMDE5MDkyOTE5MDUwNTA1MDYxMDBmMTU2NWIwMDViMzM3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNjYxMDhmYzgyOTA4MTE1MDI5MDYwNDA1MTYwMDA2MDQwNTE4MDgzMDM4MTg1ODg4OGYxOTM1MDUwNTA1MDE1ODAxNTYxMDBlZDU3M2Q2MDAwODAzZTNkNjAwMGZkNWI1MDUwNTY1YjgxNzNmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmMTY2MTA4ZmM4MjkwODExNTAyOTA2MDQwNTE2MDAwNjA0MDUxODA4MzAzODE4NTg4ODhmMTkzNTA1MDUwNTAxNTgwMTU2MTAxMzc1NzNkNjAwMDgwM2UzZDYwMDBmZDViNTA1MDUwNTZmZWEyNjU2MjdhN2E3MjMxNTgyMDVlYjQwYmI2MjA4MzVmYjc5NjljZGQ3NWRlZDE4ODkyN2Q1YzkyN2Y5M2JhNjc1ZDY1YjljNTIzNDQ0MjQyODc2NDczNmY2YzYzNDMwMDA1MTEwMDMy","b64Record":"CiAIFiocCgwIARAMGgYIgK6ZpA8SDAgBEA8aBgiArpmkDxIwySEeGnJksNt6u8MlGdZB/Bk1mIwIHKXfuIADDyGp56HAQ1GKDk7rlBk/R5ZdhTLXGgwIy/7+rAYQ58TFlgEiDwoJCI/+/qwGEKIREgIYAiogw4PCrsOCwrfDg8K5dEY4w4LCrkrDg8KLw4PCkMODwo5SAA=="},{"b64Body":"ChAKCQiP/v6sBhCkERIDGPUIEgIYAxiW+66dAiICCHgyIMODwq7DgsK3w4PCuXRGOMOCwq5Kw4PCi8ODwpDDg8KOQkgKAxj6CBoiEiDGMTbjqjIxEdDd6nBEY25HLUDZP5iPMmGHp+2UlXohBSCQoQ8okE5CBQiAztoDUgBaAGoLY2VsbGFyIGRvb3I=","b64Record":"CiUIFiIDGPsIKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjBZyU+28MAzECPwVjwe1MNHTFpDmWYcGxYrhWtY8iSQ/5FyHA/ls5upV0xMnOpV0H0aDAjL/v6sBhD5kpv7AiIQCgkIj/7+rAYQpBESAxj1CCogw4PCrsOCwrfDg8K5dEY4w4LCrkrDg8KLw4PCkMODwo4w1tSRpAJCpgUKAxj7CBLxAmCAYEBSYAQ2EGEAKVdgADVg4ByAY43ZgEMUYQArV4BjrO9gNxRhAFlXWwBbYQBXYASANgNgIIEQFWEAQVdgAID9W4EBkICANZBgIAGQkpGQUFBQYQCnVlsAW2EApWAEgDYDYECBEBVhAG9XYACA/VuBAZCAgDVz//////////////////////////8WkGAgAZCSkZCANZBgIAGQkpGQUFBQYQDxVlsAWzNz//////////////////////////8WYQj8gpCBFQKQYEBRYABgQFGAgwOBhYiI8ZNQUFBQFYAVYQDtVz1gAIA+PWAA/VtQUFZbgXP//////////////////////////xZhCPyCkIEVApBgQFFgAGBAUYCDA4GFiIjxk1BQUFAVgBVhATdXPWAAgD49YAD9W1BQUFb+omVienpyMVggXrQLtiCDX7eWnN113tGIkn1ckn+TumddZbnFI0RCQodkc29sY0MABREAMiKAAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAowJoMOgMY+whKFgoUAAAAAAAAAAAAAAAAAAAAAAAABHtyBwoDGPsIEAFSRwoJCgIYAxCY4okUCgoKAhhiEODciMYDCgoKAxigBhCatYg3CgoKAxihBhCatYg3CgsKAxj1CBDLxaTIBAoJCgMY+wgQoJwB"},{"b64Body":"ChAKCQiQ/v6sBhCmERIDGPUIEgIYAxiW+66dAiICCHgyIMODwq7DgsK3w4PCuXRGOMOCwq5Kw4PCi8ODwpDDg8KOQkgKAxj6CBoiEiDBqQHCzU01HFFfiQRJ2IUJpzb2V6F2G7I+Ue4cYBP8lyCQoQ8okE5CBQiAztoDUgBaAGoLY2VsbGFyIGRvb3I=","b64Record":"CiUIFiIDGPwIKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjBBzWHC8Q8PoKidi3PGcq4ryqqyfyGhrHKwNr63OJjno9MiFZ8IT3NIk1XugQzod9EaDAjM/v6sBhCMq4ugASIQCgkIkP7+rAYQphESAxj1CCogw4PCrsOCwrfDg8K5dEY4w4LCrkrDg8KLw4PCkMODwo4w1tSRpAJCpgUKAxj8CBLxAmCAYEBSYAQ2EGEAKVdgADVg4ByAY43ZgEMUYQArV4BjrO9gNxRhAFlXWwBbYQBXYASANgNgIIEQFWEAQVdgAID9W4EBkICANZBgIAGQkpGQUFBQYQCnVlsAW2EApWAEgDYDYECBEBVhAG9XYACA/VuBAZCAgDVz//////////////////////////8WkGAgAZCSkZCANZBgIAGQkpGQUFBQYQDxVlsAWzNz//////////////////////////8WYQj8gpCBFQKQYEBRYABgQFGAgwOBhYiI8ZNQUFBQFYAVYQDtVz1gAIA+PWAA/VtQUFZbgXP//////////////////////////xZhCPyCkIEVApBgQFFgAGBAUYCDA4GFiIjxk1BQUFAVgBVhATdXPWAAgD49YAD9W1BQUFb+omVienpyMVggXrQLtiCDX7eWnN113tGIkn1ckn+TumddZbnFI0RCQodkc29sY0MABREAMiKAAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAowJoMOgMY/AhKFgoUAAAAAAAAAAAAAAAAAAAAAAAABHxyBwoDGPwIEAFSRwoJCgIYAxCY4okUCgoKAhhiEODciMYDCgoKAxigBhCatYg3CgoKAxihBhCatYg3CgsKAxj1CBDLxaTIBAoJCgMY/AgQoJwB"},{"b64Body":"ChAKCQiQ/v6sBhC6ERIDGPUIEgIYAxjVgL+gAiICCHgyIMODwq7DgsK3w4PCuXRGOMOCwq5Kw4PCi8ODwpDDg8KOQooBCgMY+QgaIhIgeHFRVBqmamnL5gk2MN7alrarbaXhUmM7F4hMKCrvvAwgkKEPKJBOQgUIgM7aA0pAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABHsAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEfFIAWgBqC2NlbGxhciBkb29y","b64Record":"CiUIFiIDGP0IKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjAxws7AHnCIytDnAdg2j83Wgj/Ksl6gyrwVmazUm95uotk78HBHbwbOPFQAk1OWINEaDAjM/v6sBhChzauhAyIQCgkIkP7+rAYQuhESAxj1CCogw4PCrsOCwrfDg8K5dEY4w4LCrkrDg8KLw4PCkMODwo4wldqhpwJCzR0KAxj9CBKYG2CAYEBSYAQ2EGEAP1dgADVg4ByAY1VV1+QUYQBBV4BjXs8pihRhAM9XgGOdx9sQFGEA/VeAY6mjpjUUYQFLV1sAW2EAzWAEgDYDYICBEBVhAFdXYACA/VuBAZCAgDVz//////////////////////////8WkGAgAZCSkZCANXP//////////////////////////xaQYCABkJKRkIA1c///////////////////////////FpBgIAGQkpGQgDWQYCABkJKRkFBQUGEBeVZbAFthAPtgBIA2A2AggRAVYQDlV2AAgP1bgQGQgIA1kGAgAZCSkZBQUFBhBxNWWwBbYQFJYASANgNgQIEQFWEBE1dgAID9W4EBkICANXP//////////////////////////xaQYCABkJKRkIA1kGAgAZCSkZBQUFBhCC9WWwBbYQF3YASANgNgIIEQFWEBYVdgAID9W4EBkICANZBgIAGQkpGQUFBQYQoPVlsAW4Nz//////////////////////////8WYQj8gpCBFQKQYEBRYABgQFGAgwOBhYiI8ZNQUFBQFYAVYQG/Vz1gAIA+PWAA/VtQYACAkFSQYQEACpAEc///////////////////////////FnP//////////////////////////xZjrO9gN4VgAoSBYQIIV/5bBGBAUYNj/////xZg4BuBUmAEAYCDc///////////////////////////FnP//////////////////////////xaBUmAgAYKBUmAgAZJQUFBgAGBAUYCDA4FgAIeAOxWAFWECcldgAID9W1Ba8RWAFWEChlc9YACAPj1gAP1bUFBQUGABYACQVJBhAQAKkARz//////////////////////////8Wc///////////////////////////FmOs72A3hWAChIFhAtNX/lsEYEBRg2P/////FmDgG4FSYAQBgINz//////////////////////////8Wc///////////////////////////FoFSYCABgoFSYCABklBQUGAAYEBRgIMDgWAAh4A7FYAVYQM9V2AAgP1bUFrxFYAVYQNRVz1gAIA+PWAA/VtQUFBQgnP//////////////////////////xZhCPyCkIEVApBgQFFgAGBAUYCDA4GFiIjxk1BQUFAVgBVhA5tXPWAAgD49YAD9W1BgAICQVJBhAQAKkARz//////////////////////////8Wc///////////////////////////FmOs72A3hGAChIFhA+RX/lsEYEBRg2P/////FmDgG4FSYAQBgINz//////////////////////////8Wc///////////////////////////FoFSYCABgoFSYCABklBQUGAAYEBRgIMDgWAAh4A7FYAVYQROV2AAgP1bUFrxFYAVYQRiVz1gAIA+PWAA/VtQUFBQYAFgAJBUkGEBAAqQBHP//////////////////////////xZz//////////////////////////8WY6zvYDeEYAKEgWEEr1f+WwRgQFGDY/////8WYOAbgVJgBAGAg3P//////////////////////////xZz//////////////////////////8WgVJgIAGCgVJgIAGSUFBQYABgQFGAgwOBYACHgDsVgBVhBRlXYACA/VtQWvEVgBVhBS1XPWAAgD49YAD9W1BQUFCBc///////////////////////////FmEI/IKQgRUCkGBAUWAAYEBRgIMDgYWIiPGTUFBQUBWAFWEFd1c9YACAPj1gAP1bUGAAgJBUkGEBAAqQBHP//////////////////////////xZz//////////////////////////8WY6zvYDeDYAKEgWEFwFf+WwRgQFGDY/////8WYOAbgVJgBAGAg3P//////////////////////////xZz//////////////////////////8WgVJgIAGCgVJgIAGSUFBQYABgQFGAgwOBYACHgDsVgBVhBipXYACA/VtQWvEVgBVhBj5XPWAAgD49YAD9W1BQUFBgAWAAkFSQYQEACpAEc///////////////////////////FnP//////////////////////////xZjrO9gN4NgAoSBYQaLV/5bBGBAUYNj/////xZg4BuBUmAEAYCDc///////////////////////////FnP//////////////////////////xaBUmAgAYKBUmAgAZJQUFBgAGBAUYCDA4FgAIeAOxWAFWEG9VdgAID9W1Ba8RWAFWEHCVc9YACAPj1gAP1bUFBQUFBQUFBWW2AAgJBUkGEBAAqQBHP//////////////////////////xZz//////////////////////////8WY43ZgEOCYEBRgmP/////FmDgG4FSYAQBgIKBUmAgAZFQUGAAYEBRgIMDgWAAh4A7FYAVYQeHV2AAgP1bUFrxFYAVYQebVz1gAIA+PWAA/VtQUFBQYAFgAJBUkGEBAAqQBHP//////////////////////////xZz//////////////////////////8WY43ZgEOCYEBRgmP/////FmDgG4FSYAQBgIKBUmAgAZFQUGAAYEBRgIMDgWAAh4A7FYAVYQgUV2AAgP1bUFrxFYAVYQgoVz1gAIA+PWAA/VtQUFBQUFZbgXP//////////////////////////xZhCPyCkIEVApBgQFFgAGBAUYCDA4GFiIjxk1BQUFAVgBVhCHVXPWAAgD49YAD9W1BgAICQVJBhAQAKkARz//////////////////////////8Wc///////////////////////////FmOs72A3g2AChIFhCL5X/lsEYEBRg2P/////FmDgG4FSYAQBgINz//////////////////////////8Wc///////////////////////////FoFSYCABgoFSYCABklBQUGAAYEBRgIMDgWAAh4A7FYAVYQkoV2AAgP1bUFrxFYAVYQk8Vz1gAIA+PWAA/VtQUFBQYAFgAJBUkGEBAAqQBHP//////////////////////////xZz//////////////////////////8WY6zvYDeDYAKEgWEJiVf+WwRgQFGDY/////8WYOAbgVJgBAGAg3P//////////////////////////xZz//////////////////////////8WgVJgIAGCgVJgIAGSUFBQYABgQFGAgwOBYACHgDsVgBVhCfNXYACA/VtQWvEVgBVhCgdXPWAAgD49YAD9W1BQUFBQUFZbYABgYGAAgJBUkGEBAAqQBHP//////////////////////////xZz//////////////////////////8Wg2BAUWAkAYCCgVJgIAGRUFBgQFFgIIGDAwOBUpBgQFJ/jdmAQwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB7/////////////////////////////////////xkWYCCCAYBRe/////////////////////////////////////+DgYMWF4NSUFBQUGBAUYCCgFGQYCABkICDg1tgIIMQYQsMV4BRglJgIIIBkVBgIIEBkFBgIIMDklBhCulWW2ABg2AgA2EBAAoDgBmCURaBhFEWgIIXhVJQUFBQUFCQUAGRUFBgAGBAUYCDA4GFWvSRUFA9gGAAgRRhC2xXYEBRkVBgHxlgPz0BFoIBYEBSPYJSPWAAYCCEAT5hC3FWW2BgkVBbUJFQkVBgAGBgYAFgAJBUkGEBAAqQBHP//////////////////////////xZz//////////////////////////8WhWBAUWAkAYCCgVJgIAGRUFBgQFFgIIGDAwOBUpBgQFJ/jdmAQwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB7/////////////////////////////////////xkWYCCCAYBRe/////////////////////////////////////+DgYMWF4NSUFBQUGBAUYCCgFGQYCABkICDg1tgIIMQYQx0V4BRglJgIIIBkVBgIIEBkFBgIIMDklBhDFFWW2ABg2AgA2EBAAoDgBmCURaBhFEWgIIXhVJQUFBQUFCQUAGRUFBgAGBAUYCDA4GFWvSRUFA9gGAAgRRhDNRXYEBRkVBgHxlgPz0BFoIBYEBSPYJSPWAAYCCEAT5hDNlWW2BgkVBbUJFQkVCDFYBhDOlXUIEVWxVhDVxXYEBRfwjDeaAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgVJgBAGAgGAgAYKBA4JSYB6BUmAgAYB/RGVsZWdhdGUgdHJhbnNmZXIgY2FsbCBmYWlsZWQhAACBUlBgIAGRUFBgQFGAkQOQ/VtQUFBQUFb+omVienpyMVggYZ1VUlzRf3ObDLJthvfRjxVerEX5U3DZZN6jxw2xTW5kc29sY0MABREAMiKAAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAowJoMOgMY/QhKFgoUAAAAAAAAAAAAAAAAAAAAAAAABH1yBwoDGP0IEAFSRwoJCgIYAxCu6rgUCgoKAhhiEJiS48oDCgoKAxigBhDy29M3CgoKAxihBhDy29M3CgsKAxj1CBDJ0MTOBAoJCgMY/QgQoJwB"},{"b64Body":"ChAKCQiR/v6sBhC8ERIDGPUIEgIYAxjAv+0hIgIIeDIgw4PCrsOCwrfDg8K5dEY4w4LCrkrDg8KLw4PCkMODwo46kAEKAxj9CBDAhD0ihAFVVdfkAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABHYAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEdwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAR4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACg=","b64Record":"CiUIFiIDGP0IKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjB6acNAyOPgVqLCoxKSXNaee688kZ8lIyRPmVZ36JDAPBulpyr+Tx7IABslbvAsm0UaDAjN/v6sBhDjq66pASIQCgkIkf7+rAYQvBESAxj1CCogw4PCrsOCwrfDg8K5dEY4w4LCrkrDg8KLw4PCkMODwo4wgOaKGzqMAgoDGP0IIoACAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACiA6jBSUQoJCgIYYhCAzJU2CgoKAxj1CBD/y5U2CggKAxj2CBCgAQoICgMY9wgQoAEKCAoDGPgIEKABCgcKAxj7CBB3CgcKAxj8CBB3CggKAxj9CBDvAQ=="}]},"transferNegativeAmountOfHbarsFails":{"placeholderNum":1150,"encodedItems":[{"b64Body":"Cg8KCQiW/v6sBhDYERICGAISAhgDGPuV9hQiAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjloxCiISIMnM8RhcMCKONH1X2j+gspRb7CQjvrbrCceOs3NcPR0FEIDIr6AlSgUIgM7aAw==","b64Record":"CiUIFhIDGP8IKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjDgVcAUPeyhmElI05ZaVHJrosSoqH6nk80mCxlpHA9eDUAR5ZM+ER6MLfgLzOfnrJgaCwjS/v6sBhD2qs8tIg8KCQiW/v6sBhDYERICGAIqIMODwq7DgsK3w4PCuXRGOMOCwq5Kw4PCi8ODwpDDg8KOUhkKCgoCGAIQ/4/fwEoKCwoDGP8IEICQ38BK"},{"b64Body":"Cg8KCQiW/v6sBhDaERICGAISAhgDGPuV9hQiAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjlouCiISILU+LBr1NmT5H3ROYK4p+F5euAFkdRV6+vNjv2CIRyWXEJBOSgUIgM7aAw==","b64Record":"CiUIFhIDGIAJKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjC1pano88o/ALlWHArZAQD/Nm/mhyN+nUEvafR1HVYDaW3Yf3zqvoJCh9Fpt9qoF5kaDAjS/v6sBhD/5bqSAiIPCgkIlv7+rAYQ2hESAhgCKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjlIVCggKAhgCEJ+cAQoJCgMYgAkQoJwB"},{"b64Body":"Cg8KCQiX/v6sBhDcERICGAISAhgDGP7LzSwiAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjooBjgESCwjTzNmwBhCU9OMYGm0KIhIgR52RKApJucoI+mdFENhYKUw8CgTF5gTmP2Zo/WWoG6sKIzohA3the98W6l34jAueuPxMaPg6H7Qlx55PyYi7zSq+q8mGCiISILAHZemSKqowStGFY3UGlgxg3nxjojwApQ0G9S70AFS3IgxIZWxsbyBXb3JsZCEqADIA","b64Record":"CiUIFhoDGIEJKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjBbXwmYc49/L+OZoUBMAWPr+SuTD92440+hs4bEpewsRZ77W3O+rXSaTfE2hMhzeNIaCwjT/v6sBhDUk8kaIg8KCQiX/v6sBhDcERICGAIqIMODwq7DgsK3w4PCuXRGOMOCwq5Kw4PCi8ODwpDDg8KOUgA="},{"b64Body":"Cg8KCQiX/v6sBhDgERICGAISAhgDGPOGlzoiAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjpoBghkKAxiBCSL6GDYwODA2MDQwNTI2MTA2MmE4MDYxMDAxMzYwMDAzOTYwMDBmM2ZlNjA4MDYwNDA1MjYwMDQzNjEwNjEwMDRhNTc2MDAwMzU2MGUwMWM4MDYzMjZhYjk3ZWExNDYxMDA0YzU3ODA2MzhkZDk4MDQzMTQ2MTAwOWE1NzgwNjNhY2VmNjAzNzE0NjEwMGM4NTc4MDYzZDJhMGZlMjYxNDYxMDExNjU3ODA2M2UxMmUzMjM4MTQ2MTAxYTQ1NzViMDA1YjYxMDA5ODYwMDQ4MDM2MDM2MDQwODExMDE1NjEwMDYyNTc2MDAwODBmZDViODEwMTkwODA4MDM1NzNmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmMTY5MDYwMjAwMTkwOTI5MTkwODAzNTkwNjAyMDAxOTA5MjkxOTA1MDUwNTA2MTAxZjI1NjViMDA1YjYxMDBjNjYwMDQ4MDM2MDM2MDIwODExMDE1NjEwMGIwNTc2MDAwODBmZDViODEwMTkwODA4MDM1OTA2MDIwMDE5MDkyOTE5MDUwNTA1MDYxMDQyMzU2NWIwMDViNjEwMTE0NjAwNDgwMzYwMzYwNDA4MTEwMTU2MTAwZGU1NzYwMDA4MGZkNWI4MTAxOTA4MDgwMzU3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNjkwNjAyMDAxOTA5MjkxOTA4MDM1OTA2MDIwMDE5MDkyOTE5MDUwNTA1MDYxMDQ2ZDU2NWIwMDViNjEwMWEyNjAwNDgwMzYwMzYwODA4MTEwMTU2MTAxMmM1NzYwMDA4MGZkNWI4MTAxOTA4MDgwMzU3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNjkwNjAyMDAxOTA5MjkxOTA4MDM1NzNmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmMTY5MDYwMjAwMTkwOTI5MTkwODAzNTczZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjE2OTA2MDIwMDE5MDkyOTE5MDgwMzU5MDYwMjAwMTkwOTI5MTkwNTA1MDUwNjEwNGI4NTY1YjAwNWI2MTAxZjA2MDA0ODAzNjAzNjA0MDgxMTAxNTYxMDFiYTU3NjAwMDgwZmQ1YjgxMDE5MDgwODAzNTczZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjE2OTA2MDIwMDE5MDkyOTE5MDgwMzU5MDYwMjAwMTkwOTI5MTkwNTA1MDUwNjEwNWE3NTY1YjAwNWI4MTczZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjE2NjEwOGZjODI5MDgxMTUwMjkwNjA0MDUxNjAwMDYwNDA1MTgwODMwMzgxODU4ODg4ZjE5MzUwNTA1MDUwMTU4MDE1NjEwMjM4NTczZDYwMDA4MDNlM2Q2MDAwZmQ1YjUwODE3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNjYxMDhmYzYwMDI4MzgxNjEwMjVkNTdmZTViMDQ5MDgxMTUwMjkwNjA0MDUxNjAwMDYwNDA1MTgwODMwMzgxODU4ODg4ZjE5MzUwNTA1MDUwMTU4MDE1NjEwMjg5NTczZDYwMDA4MDNlM2Q2MDAwZmQ1YjUwODE3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNjYxMDhmYzYwMDQ4MzgxNjEwMmFlNTdmZTViMDQ5MDgxMTUwMjkwNjA0MDUxNjAwMDYwNDA1MTgwODMwMzgxODU4ODg4ZjE5MzUwNTA1MDUwMTU4MDE1NjEwMmRhNTczZDYwMDA4MDNlM2Q2MDAwZmQ1YjUwODE3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNjYxMDhmYzYwMDg4MzgxNjEwMmZmNTdmZTViMDQ5MDgxMTUwMjkwNjA0MDUxNjAwMDYwNDA1MTgwODMwMzgxODU4ODg4ZjE5MzUwNTA1MDUwMTU4MDE1NjEwMzJiNTczZDYwMDA4MDNlM2Q2MDAwZmQ1YjUwODE3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNjYxMDhmYzYwMTA4MzgxNjEwMzUwNTdmZTViMDQ5MDgxMTUwMjkwNjA0MDUxNjAwMDYwNDA1MTgwODMwMzgxODU4ODg4ZjE5MzUwNTA1MDUwMTU4MDE1NjEwMzdjNTczZDYwMDA4MDNlM2Q2MDAwZmQ1YjUwODE3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNjYxMDhmYzYwMjA4MzgxNjEwM2ExNTdmZTViMDQ5MDgxMTUwMjkwNjA0MDUxNjAwMDYwNDA1MTgwODMwMzgxODU4ODg4ZjE5MzUwNTA1MDUwMTU4MDE1NjEwM2NkNTczZDYwMDA4MDNlM2Q2MDAwZmQ1YjUwODE3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNjYxMDhmYzYwNDA4MzgxNjEwM2YyNTdmZTViMDQ5MDgxMTUwMjkwNjA0MDUxNjAwMDYwNDA1MTgwODMwMzgxODU4ODg4ZjE5MzUwNTA1MDUwMTU4MDE1NjEwNDFlNTczZDYwMDA4MDNlM2Q2MDAwZmQ1YjUwNTA1MDU2NWIzMzczZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjE2NjEwOGZjODI5MDgxMTUwMjkwNjA0MDUxNjAwMDYwNDA1MTgwODMwMzgxODU4ODg4ZjE5MzUwNTA1MDUwMTU4MDE1NjEwNDY5NTczZDYwMDA4MDNlM2Q2MDAwZmQ1YjUwNTA1NjViODE3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNjYxMDhmYzgyOTA4MTE1MDI5MDYwNDA1MTYwMDA2MDQwNTE4MDgzMDM4MTg1ODg4OGYxOTM1MDUwNTA1MDE1ODAxNTYxMDRiMzU3M2Q2MDAwODAzZTNkNjAwMGZkNWI1MDUwNTA1NjViODM3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNjYxMDhmYzgyOTA4MTE1MDI5MDYwNDA1MTYwMDA2MDQwNTE4MDgzMDM4MTg1ODg4OGYxOTM1MDUwNTA1MDE1ODAxNTYxMDRmZTU3M2Q2MDAwODAzZTNkNjAwMGZkNWI1MDgyNzNmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmMTY2MTA4ZmM2MDAyODM4MTYxMDUyMzU3ZmU1YjA0OTA4MTE1MDI5MDYwNDA1MTYwMDA2MDQwNTE4MDgzMDM4MTg1ODg4OGYxOTM1MDUwNTA1MDE1ODAxNTYxMDU0ZjU3M2Q2MDAwODAzZTNkNjAwMGZkNWI1MDgxNzNmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmMTY2MTA4ZmM2MDA0ODM4MTYxMDU3NDU3ZmU1YjA0OTA4MTE1MDI5MDYwNDA1MTYwMDA2MDQwNTE4MDgzMDM4MTg1ODg4OGYxOTM1MDUwNTA1MDE1ODAxNTYxMDVhMDU3M2Q2MDAwODAzZTNkNjAwMGZkNWI1MDUwNTA1MDUwNTY1YjgxNzNmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmMTY2MTA4ZmM4MjYwMDAwMzkwODExNTAyOTA2MDQwNTE2MDAwNjA0MDUxODA4MzAzODE4NTg4ODhmMTkzNTA1MDUwNTAxNTgwMTU2MTA1ZjA1NzNkNjAwMDgwM2UzZDYwMDBmZDViNTA1MDUwNTZmZWEyNjU2MjdhN2E3MjMxNTgyMGNmNzkxYmJmNDdkY2I2MTM5Mzc0NDA1MmE2MjBmNzcxYjQyOWYwNzFmMDJlMzY1N2NlM2U1ZjgwYWIzZjk3YmQ2NDczNmY2YzYzNDMwMDA1MTEwMDMy","b64Record":"CiAIFiocCgwIARAMGgYIgK6ZpA8SDAgBEA8aBgiArpmkDxIwv1CNFzTBe9jzBTGe9MHFvy3KvvTZzd1KoCETGjzNjzNIhf8lPcvok/yXhpU+uyzjGgwI0/7+rAYQs9+bnAIiDwoJCJf+/qwGEOAREgIYAiogw4PCrsOCwrfDg8K5dEY4w4LCrkrDg8KLw4PCkMODwo5SAA=="},{"b64Body":"ChAKCQiY/v6sBhDiERIDGP8IEgIYAxiW+66dAiICCHgyIMODwq7DgsK3w4PCuXRGOMOCwq5Kw4PCi8ODwpDDg8KOQkgKAxiBCRoiEiCuXwuBvAJvtQIwMTG8GpUnHgtcEvwSfI20uQ9ucYQM3iCQoQ8okE5CBQiAztoDUgBaAGoLY2VsbGFyIGRvb3I=","b64Record":"CiUIFiIDGIIJKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjAAjh+fPpJVGsyeXIqRsClgnO0x9aUh+nJef+L2rw3cAiDDJKnBwitfxjt6s7yNObkaCwjU/v6sBhDg/Y0kIhAKCQiY/v6sBhDiERIDGP8IKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjjDW1JGkAkLfDgoDGIIJEqoMYIBgQFJgBDYQYQBKV2AANWDgHIBjJquX6hRhAExXgGON2YBDFGEAmleAY6zvYDcUYQDIV4Bj0qD+JhRhARZXgGPhLjI4FGEBpFdbAFthAJhgBIA2A2BAgRAVYQBiV2AAgP1bgQGQgIA1c///////////////////////////FpBgIAGQkpGQgDWQYCABkJKRkFBQUGEB8lZbAFthAMZgBIA2A2AggRAVYQCwV2AAgP1bgQGQgIA1kGAgAZCSkZBQUFBhBCNWWwBbYQEUYASANgNgQIEQFWEA3ldgAID9W4EBkICANXP//////////////////////////xaQYCABkJKRkIA1kGAgAZCSkZBQUFBhBG1WWwBbYQGiYASANgNggIEQFWEBLFdgAID9W4EBkICANXP//////////////////////////xaQYCABkJKRkIA1c///////////////////////////FpBgIAGQkpGQgDVz//////////////////////////8WkGAgAZCSkZCANZBgIAGQkpGQUFBQYQS4VlsAW2EB8GAEgDYDYECBEBVhAbpXYACA/VuBAZCAgDVz//////////////////////////8WkGAgAZCSkZCANZBgIAGQkpGQUFBQYQWnVlsAW4Fz//////////////////////////8WYQj8gpCBFQKQYEBRYABgQFGAgwOBhYiI8ZNQUFBQFYAVYQI4Vz1gAIA+PWAA/VtQgXP//////////////////////////xZhCPxgAoOBYQJdV/5bBJCBFQKQYEBRYABgQFGAgwOBhYiI8ZNQUFBQFYAVYQKJVz1gAIA+PWAA/VtQgXP//////////////////////////xZhCPxgBIOBYQKuV/5bBJCBFQKQYEBRYABgQFGAgwOBhYiI8ZNQUFBQFYAVYQLaVz1gAIA+PWAA/VtQgXP//////////////////////////xZhCPxgCIOBYQL/V/5bBJCBFQKQYEBRYABgQFGAgwOBhYiI8ZNQUFBQFYAVYQMrVz1gAIA+PWAA/VtQgXP//////////////////////////xZhCPxgEIOBYQNQV/5bBJCBFQKQYEBRYABgQFGAgwOBhYiI8ZNQUFBQFYAVYQN8Vz1gAIA+PWAA/VtQgXP//////////////////////////xZhCPxgIIOBYQOhV/5bBJCBFQKQYEBRYABgQFGAgwOBhYiI8ZNQUFBQFYAVYQPNVz1gAIA+PWAA/VtQgXP//////////////////////////xZhCPxgQIOBYQPyV/5bBJCBFQKQYEBRYABgQFGAgwOBhYiI8ZNQUFBQFYAVYQQeVz1gAIA+PWAA/VtQUFBWWzNz//////////////////////////8WYQj8gpCBFQKQYEBRYABgQFGAgwOBhYiI8ZNQUFBQFYAVYQRpVz1gAIA+PWAA/VtQUFZbgXP//////////////////////////xZhCPyCkIEVApBgQFFgAGBAUYCDA4GFiIjxk1BQUFAVgBVhBLNXPWAAgD49YAD9W1BQUFZbg3P//////////////////////////xZhCPyCkIEVApBgQFFgAGBAUYCDA4GFiIjxk1BQUFAVgBVhBP5XPWAAgD49YAD9W1CCc///////////////////////////FmEI/GACg4FhBSNX/lsEkIEVApBgQFFgAGBAUYCDA4GFiIjxk1BQUFAVgBVhBU9XPWAAgD49YAD9W1CBc///////////////////////////FmEI/GAEg4FhBXRX/lsEkIEVApBgQFFgAGBAUYCDA4GFiIjxk1BQUFAVgBVhBaBXPWAAgD49YAD9W1BQUFBQVluBc///////////////////////////FmEI/IJgAAOQgRUCkGBAUWAAYEBRgIMDgYWIiPGTUFBQUBWAFWEF8Fc9YACAPj1gAP1bUFBQVv6iZWJ6enIxWCDPeRu/R9y2E5N0QFKmIPdxtCnwcfAuNlfOPl+Aqz+XvWRzb2xjQwAFEQAyIoACAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACjAmgw6AxiCCUoWChQAAAAAAAAAAAAAAAAAAAAAAAAEgnIHCgMYggkQAVJHCgkKAhgDEJjiiRQKCgoCGGIQ4NyIxgMKCgoDGKAGEJq1iDcKCgoDGKEGEJq1iDcKCwoDGP8IEMvFpMgECgkKAxiCCRCgnAE="},{"b64Body":"ChAKCQiY/v6sBhDqERIDGP8IEgIYAxjgrLEDIgIIeDIgw4PCrsOCwrfDg8K5dEY4w4LCrkrDg8KLw4PCkMODwo46TwoDGIIJEKCNBiJE4S4yOAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAASAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAo=","b64Record":"CiUIISIDGIIJKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjCfL9NY+1JHXY8fHzXVm98Tt4SAn8UCYC3lG/mrSK6mElTVvpdXRlEWCPzE85Hwu50aDAjU/v6sBhD8w+ClAiIQCgkImP7+rAYQ6hESAxj/CCogw4PCrsOCwrfDg8K5dEY4w4LCrkrDg8KLw4PCkMODwo4wgNfaAjoIGgIweCiA8QRSFwoJCgIYYhCArrUFCgoKAxj/CBD/rbUF"},{"b64Body":"ChAKCQiZ/v6sBhDsERIDGP8IEgIYAxjgrLEDIgIIeDIgw4PCrsOCwrfDg8K5dEY4w4LCrkrDg8KLw4PCkMODwo46TwoDGIIJEKCNBiJE4S4yOAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAASAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=","b64Record":"CiUIFiIDGIIJKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjAKFTc5indBM14Ui71VXOgOyczhqWqtEejaWidna/f876JoeRwt8NmmGhxjCpuRNYIaCwjV/v6sBhDV2eYtIhAKCQiZ/v6sBhDsERIDGP8IKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjjCA19oCOowCCgMYggkigAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKIDxBFIXCgkKAhhiEICutQUKCgoDGP8IEP+ttQU="}]},"transferZeroHbars":{"placeholderNum":1155,"encodedItems":[{"b64Body":"Cg8KCQid/v6sBhCCEhICGAISAhgDGPuV9hQiAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjloxCiISIO5DHNHpXKDtJG4rYW54d8YNx3yeD/HAPAuKRLGExks/EIDIr6AlSgUIgM7aAw==","b64Record":"CiUIFhIDGIQJKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjCpXKnLst5XhhxHQV9PmO8rcE6OZnrN/Gz8/RMPv8KQZ2Pw+z9juxTMEN5N6FyFHOIaDAjZ/v6sBhDul5GrAyIPCgkInf7+rAYQghISAhgCKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjlIZCgoKAhgCEP+P38BKCgsKAxiECRCAkN/ASg=="},{"b64Body":"Cg8KCQie/v6sBhCEEhICGAISAhgDGPuV9hQiAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjlouCiISINEEQBzknqI0EpUniiCdNgmutNRwlhyAoALjKtUHtbp/EJBOSgUIgM7aAw==","b64Record":"CiUIFhIDGIUJKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjC57zTVla8GKsQP7Qh7cI7cMxTeVNCMuD6bnN4iSGV4snClIK1aRYMeAAxcdinjsBQaDAja/v6sBhCq/42zASIPCgkInv7+rAYQhBISAhgCKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjlIVCggKAhgCEJ+cAQoJCgMYhQkQoJwB"},{"b64Body":"Cg8KCQie/v6sBhCGEhICGAISAhgDGLXNziwiAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjooBjwESDAjazNmwBhDS/uqeAxptCiISIBvKRyjN7X4FPcc+RiLojXEbwHBWGjbLfFjXALVf0gQBCiM6IQOGSEr/hwDNpchUGuDE4n0QxZ+qGx1+FaVRQfpTECmeogoiEiADNjGqeryB1c3mcFiy+QlVAq8uTdR/mNddf2CrKl6VNyIMSGVsbG8gV29ybGQhKgAyAA==","b64Record":"CiUIFhoDGIYJKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjDKgVZzNWzWm8Ik83Pd5h8I87b/BitYKIcsL72xHdJ6GFSczYSyGcBOUqAxPHKlFUwaDAja/v6sBhCezte0AyIPCgkInv7+rAYQhhISAhgCKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjlIA"},{"b64Body":"Cg8KCQif/v6sBhCKEhICGAISAhgDGMSImDoiAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjpoBghkKAxiGCSL6GDYwODA2MDQwNTI2MTA2MmE4MDYxMDAxMzYwMDAzOTYwMDBmM2ZlNjA4MDYwNDA1MjYwMDQzNjEwNjEwMDRhNTc2MDAwMzU2MGUwMWM4MDYzMjZhYjk3ZWExNDYxMDA0YzU3ODA2MzhkZDk4MDQzMTQ2MTAwOWE1NzgwNjNhY2VmNjAzNzE0NjEwMGM4NTc4MDYzZDJhMGZlMjYxNDYxMDExNjU3ODA2M2UxMmUzMjM4MTQ2MTAxYTQ1NzViMDA1YjYxMDA5ODYwMDQ4MDM2MDM2MDQwODExMDE1NjEwMDYyNTc2MDAwODBmZDViODEwMTkwODA4MDM1NzNmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmMTY5MDYwMjAwMTkwOTI5MTkwODAzNTkwNjAyMDAxOTA5MjkxOTA1MDUwNTA2MTAxZjI1NjViMDA1YjYxMDBjNjYwMDQ4MDM2MDM2MDIwODExMDE1NjEwMGIwNTc2MDAwODBmZDViODEwMTkwODA4MDM1OTA2MDIwMDE5MDkyOTE5MDUwNTA1MDYxMDQyMzU2NWIwMDViNjEwMTE0NjAwNDgwMzYwMzYwNDA4MTEwMTU2MTAwZGU1NzYwMDA4MGZkNWI4MTAxOTA4MDgwMzU3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNjkwNjAyMDAxOTA5MjkxOTA4MDM1OTA2MDIwMDE5MDkyOTE5MDUwNTA1MDYxMDQ2ZDU2NWIwMDViNjEwMWEyNjAwNDgwMzYwMzYwODA4MTEwMTU2MTAxMmM1NzYwMDA4MGZkNWI4MTAxOTA4MDgwMzU3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNjkwNjAyMDAxOTA5MjkxOTA4MDM1NzNmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmMTY5MDYwMjAwMTkwOTI5MTkwODAzNTczZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjE2OTA2MDIwMDE5MDkyOTE5MDgwMzU5MDYwMjAwMTkwOTI5MTkwNTA1MDUwNjEwNGI4NTY1YjAwNWI2MTAxZjA2MDA0ODAzNjAzNjA0MDgxMTAxNTYxMDFiYTU3NjAwMDgwZmQ1YjgxMDE5MDgwODAzNTczZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjE2OTA2MDIwMDE5MDkyOTE5MDgwMzU5MDYwMjAwMTkwOTI5MTkwNTA1MDUwNjEwNWE3NTY1YjAwNWI4MTczZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjE2NjEwOGZjODI5MDgxMTUwMjkwNjA0MDUxNjAwMDYwNDA1MTgwODMwMzgxODU4ODg4ZjE5MzUwNTA1MDUwMTU4MDE1NjEwMjM4NTczZDYwMDA4MDNlM2Q2MDAwZmQ1YjUwODE3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNjYxMDhmYzYwMDI4MzgxNjEwMjVkNTdmZTViMDQ5MDgxMTUwMjkwNjA0MDUxNjAwMDYwNDA1MTgwODMwMzgxODU4ODg4ZjE5MzUwNTA1MDUwMTU4MDE1NjEwMjg5NTczZDYwMDA4MDNlM2Q2MDAwZmQ1YjUwODE3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNjYxMDhmYzYwMDQ4MzgxNjEwMmFlNTdmZTViMDQ5MDgxMTUwMjkwNjA0MDUxNjAwMDYwNDA1MTgwODMwMzgxODU4ODg4ZjE5MzUwNTA1MDUwMTU4MDE1NjEwMmRhNTczZDYwMDA4MDNlM2Q2MDAwZmQ1YjUwODE3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNjYxMDhmYzYwMDg4MzgxNjEwMmZmNTdmZTViMDQ5MDgxMTUwMjkwNjA0MDUxNjAwMDYwNDA1MTgwODMwMzgxODU4ODg4ZjE5MzUwNTA1MDUwMTU4MDE1NjEwMzJiNTczZDYwMDA4MDNlM2Q2MDAwZmQ1YjUwODE3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNjYxMDhmYzYwMTA4MzgxNjEwMzUwNTdmZTViMDQ5MDgxMTUwMjkwNjA0MDUxNjAwMDYwNDA1MTgwODMwMzgxODU4ODg4ZjE5MzUwNTA1MDUwMTU4MDE1NjEwMzdjNTczZDYwMDA4MDNlM2Q2MDAwZmQ1YjUwODE3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNjYxMDhmYzYwMjA4MzgxNjEwM2ExNTdmZTViMDQ5MDgxMTUwMjkwNjA0MDUxNjAwMDYwNDA1MTgwODMwMzgxODU4ODg4ZjE5MzUwNTA1MDUwMTU4MDE1NjEwM2NkNTczZDYwMDA4MDNlM2Q2MDAwZmQ1YjUwODE3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNjYxMDhmYzYwNDA4MzgxNjEwM2YyNTdmZTViMDQ5MDgxMTUwMjkwNjA0MDUxNjAwMDYwNDA1MTgwODMwMzgxODU4ODg4ZjE5MzUwNTA1MDUwMTU4MDE1NjEwNDFlNTczZDYwMDA4MDNlM2Q2MDAwZmQ1YjUwNTA1MDU2NWIzMzczZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjE2NjEwOGZjODI5MDgxMTUwMjkwNjA0MDUxNjAwMDYwNDA1MTgwODMwMzgxODU4ODg4ZjE5MzUwNTA1MDUwMTU4MDE1NjEwNDY5NTczZDYwMDA4MDNlM2Q2MDAwZmQ1YjUwNTA1NjViODE3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNjYxMDhmYzgyOTA4MTE1MDI5MDYwNDA1MTYwMDA2MDQwNTE4MDgzMDM4MTg1ODg4OGYxOTM1MDUwNTA1MDE1ODAxNTYxMDRiMzU3M2Q2MDAwODAzZTNkNjAwMGZkNWI1MDUwNTA1NjViODM3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNjYxMDhmYzgyOTA4MTE1MDI5MDYwNDA1MTYwMDA2MDQwNTE4MDgzMDM4MTg1ODg4OGYxOTM1MDUwNTA1MDE1ODAxNTYxMDRmZTU3M2Q2MDAwODAzZTNkNjAwMGZkNWI1MDgyNzNmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmMTY2MTA4ZmM2MDAyODM4MTYxMDUyMzU3ZmU1YjA0OTA4MTE1MDI5MDYwNDA1MTYwMDA2MDQwNTE4MDgzMDM4MTg1ODg4OGYxOTM1MDUwNTA1MDE1ODAxNTYxMDU0ZjU3M2Q2MDAwODAzZTNkNjAwMGZkNWI1MDgxNzNmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmMTY2MTA4ZmM2MDA0ODM4MTYxMDU3NDU3ZmU1YjA0OTA4MTE1MDI5MDYwNDA1MTYwMDA2MDQwNTE4MDgzMDM4MTg1ODg4OGYxOTM1MDUwNTA1MDE1ODAxNTYxMDVhMDU3M2Q2MDAwODAzZTNkNjAwMGZkNWI1MDUwNTA1MDUwNTY1YjgxNzNmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmMTY2MTA4ZmM4MjYwMDAwMzkwODExNTAyOTA2MDQwNTE2MDAwNjA0MDUxODA4MzAzODE4NTg4ODhmMTkzNTA1MDUwNTAxNTgwMTU2MTA1ZjA1NzNkNjAwMDgwM2UzZDYwMDBmZDViNTA1MDUwNTZmZWEyNjU2MjdhN2E3MjMxNTgyMGNmNzkxYmJmNDdkY2I2MTM5Mzc0NDA1MmE2MjBmNzcxYjQyOWYwNzFmMDJlMzY1N2NlM2U1ZjgwYWIzZjk3YmQ2NDczNmY2YzYzNDMwMDA1MTEwMDMy","b64Record":"CiAIFiocCgwIARAMGgYIgK6ZpA8SDAgBEA8aBgiArpmkDxIwEzAQ4Tpcs28xx8pqwEhkUzaOYa5iHBTRe6i5wBP6uo1Ss0rtbvggXRkxermZtPdrGgwI2/7+rAYQ47i3vAEiDwoJCJ/+/qwGEIoSEgIYAiogw4PCrsOCwrfDg8K5dEY4w4LCrkrDg8KLw4PCkMODwo5SAA=="},{"b64Body":"Cg8KCQif/v6sBhCMEhICGAISAhgDGJb7rp0CIgIIeDIgw4PCrsOCwrfDg8K5dEY4w4LCrkrDg8KLw4PCkMODwo5CSAoDGIYJGiISIGbrr9vg6WwVdIB1gMzJC/k9trLH+J5gks74e37Z4/ZOIJChDyiQTkIFCIDO2gNSAFoAagtjZWxsYXIgZG9vcg==","b64Record":"CiUIFiIDGIcJKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjCDAn052vK0JVK8pj0PRAecNjnptn1+N10/qj5rtvrQ4p3peq8yVEQJnqczVSuqBx4aDAjb/v6sBhDk3Im+AyIPCgkIn/7+rAYQjBISAhgCKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjjDA2eIGQt8OCgMYhwkSqgxggGBAUmAENhBhAEpXYAA1YOAcgGMmq5fqFGEATFeAY43ZgEMUYQCaV4BjrO9gNxRhAMhXgGPSoP4mFGEBFleAY+EuMjgUYQGkV1sAW2EAmGAEgDYDYECBEBVhAGJXYACA/VuBAZCAgDVz//////////////////////////8WkGAgAZCSkZCANZBgIAGQkpGQUFBQYQHyVlsAW2EAxmAEgDYDYCCBEBVhALBXYACA/VuBAZCAgDWQYCABkJKRkFBQUGEEI1ZbAFthARRgBIA2A2BAgRAVYQDeV2AAgP1bgQGQgIA1c///////////////////////////FpBgIAGQkpGQgDWQYCABkJKRkFBQUGEEbVZbAFthAaJgBIA2A2CAgRAVYQEsV2AAgP1bgQGQgIA1c///////////////////////////FpBgIAGQkpGQgDVz//////////////////////////8WkGAgAZCSkZCANXP//////////////////////////xaQYCABkJKRkIA1kGAgAZCSkZBQUFBhBLhWWwBbYQHwYASANgNgQIEQFWEBuldgAID9W4EBkICANXP//////////////////////////xaQYCABkJKRkIA1kGAgAZCSkZBQUFBhBadWWwBbgXP//////////////////////////xZhCPyCkIEVApBgQFFgAGBAUYCDA4GFiIjxk1BQUFAVgBVhAjhXPWAAgD49YAD9W1CBc///////////////////////////FmEI/GACg4FhAl1X/lsEkIEVApBgQFFgAGBAUYCDA4GFiIjxk1BQUFAVgBVhAolXPWAAgD49YAD9W1CBc///////////////////////////FmEI/GAEg4FhAq5X/lsEkIEVApBgQFFgAGBAUYCDA4GFiIjxk1BQUFAVgBVhAtpXPWAAgD49YAD9W1CBc///////////////////////////FmEI/GAIg4FhAv9X/lsEkIEVApBgQFFgAGBAUYCDA4GFiIjxk1BQUFAVgBVhAytXPWAAgD49YAD9W1CBc///////////////////////////FmEI/GAQg4FhA1BX/lsEkIEVApBgQFFgAGBAUYCDA4GFiIjxk1BQUFAVgBVhA3xXPWAAgD49YAD9W1CBc///////////////////////////FmEI/GAgg4FhA6FX/lsEkIEVApBgQFFgAGBAUYCDA4GFiIjxk1BQUFAVgBVhA81XPWAAgD49YAD9W1CBc///////////////////////////FmEI/GBAg4FhA/JX/lsEkIEVApBgQFFgAGBAUYCDA4GFiIjxk1BQUFAVgBVhBB5XPWAAgD49YAD9W1BQUFZbM3P//////////////////////////xZhCPyCkIEVApBgQFFgAGBAUYCDA4GFiIjxk1BQUFAVgBVhBGlXPWAAgD49YAD9W1BQVluBc///////////////////////////FmEI/IKQgRUCkGBAUWAAYEBRgIMDgYWIiPGTUFBQUBWAFWEEs1c9YACAPj1gAP1bUFBQVluDc///////////////////////////FmEI/IKQgRUCkGBAUWAAYEBRgIMDgYWIiPGTUFBQUBWAFWEE/lc9YACAPj1gAP1bUIJz//////////////////////////8WYQj8YAKDgWEFI1f+WwSQgRUCkGBAUWAAYEBRgIMDgYWIiPGTUFBQUBWAFWEFT1c9YACAPj1gAP1bUIFz//////////////////////////8WYQj8YASDgWEFdFf+WwSQgRUCkGBAUWAAYEBRgIMDgYWIiPGTUFBQUBWAFWEFoFc9YACAPj1gAP1bUFBQUFBWW4Fz//////////////////////////8WYQj8gmAAA5CBFQKQYEBRYABgQFGAgwOBhYiI8ZNQUFBQFYAVYQXwVz1gAIA+PWAA/VtQUFBW/qJlYnp6cjFYIM95G79H3LYTk3RAUqYg93G0KfBx8C42V84+X4CrP5e9ZHNvbGNDAAURADIigAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKMCaDDoDGIcJShYKFAAAAAAAAAAAAAAAAAAAAAAAAASHcgcKAxiHCRABUiEKCQoCGAIQn8/GDQoJCgIYYhCAs8UNCgkKAxiHCRCgnAE="},{"b64Body":"ChAKCQig/v6sBhCUEhIDGIQJEgIYAxjgrLEDIgIIeDIgw4PCrsOCwrfDg8K5dEY4w4LCrkrDg8KLw4PCkMODwo46TwoDGIcJEKCNBiJErO9gNwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAASFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=","b64Record":"CiUIFiIDGIcJKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjB/RW5TleuQW+qv9FSTbEx4Q14WV6ZPVb91RWcBT+2Yw6BQ7kZyP4OJvSQoDR7uBTIaDAjc/v6sBhDGz4PGASIQCgkIoP7+rAYQlBISAxiECSogw4PCrsOCwrfDg8K5dEY4w4LCrkrDg8KLw4PCkMODwo4wgNfaAjqMAgoDGIcJIoACAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACiA8QRSFwoJCgIYYhCArrUFCgoKAxiECRD/rbUF"}]},"sendHbarsToOuterContractFromDifferentAddresses":{"placeholderNum":1160,"encodedItems":[{"b64Body":"Cg8KCQil/v6sBhCqEhICGAISAhgDGPuV9hQiAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjloxCiISIOjs7ksiOB+IcupkSM/CLceN9kmF8YRaQQ0Djg49LO0mEIDIr6AlSgUIgM7aAw==","b64Record":"CiUIFhIDGIkJKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjBgtR4dcoFko+SVYW7jV6YCOr3M07/lHRXaYRmdG55PD+uh1EwDet0U3bvAm3ZgTCMaCwjh/v6sBhCB2txmIg8KCQil/v6sBhCqEhICGAIqIMODwq7DgsK3w4PCuXRGOMOCwq5Kw4PCi8ODwpDDg8KOUhkKCgoCGAIQ/4/fwEoKCwoDGIkJEICQ38BK"},{"b64Body":"Cg8KCQil/v6sBhCsEhICGAISAhgDGP7LzSwiAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjooBjwESDAjhzNmwBhDKqOjJAhptCiISIA5PNZ/1Z3t/Qgq64zEWX+nYBgGq/oLK8VAy+SWJXwBNCiM6IQOmVOUaSUJeIJiXppWDNpjf5cQEdm70lAz1ZUlMHvdZigoiEiCewnkk0jvk/kRWOvd+QW1yvbYgCM9lRzR/+bRF7+1hNCIMSGVsbG8gV29ybGQhKgAyAA==","b64Record":"CiUIFhoDGIoJKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjCT38LlShNkzhlurKfFwdGkgbfaLF+/XoqOjJdvl1en3I5Q5H5+uIemk3qTKngt6bgaDAjh/v6sBhCu67rLAiIPCgkIpf7+rAYQrBISAhgCKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjlIA"},{"b64Body":"Cg8KCQim/v6sBhCwEhICGAISAhgDGIudjj4iAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjpoBiCAKAxiKCSKAIDYwODA2MDQwNTI2MDQwNTE2MTBlNmIzODAzODA2MTBlNmI4MzM5ODE4MTAxNjA0MDUyNjA0MDgxMTAxNTYxMDAyNjU3NjAwMDgwZmQ1YjgxMDE5MDgwODA1MTkwNjAyMDAxOTA5MjkxOTA4MDUxOTA2MDIwMDE5MDkyOTE5MDUwNTA1MDgxNjAwMDgwNjEwMTAwMGE4MTU0ODE3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYwMjE5MTY5MDgzNzNmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmMTYwMjE3OTA1NTUwODA2MDAxNjAwMDYxMDEwMDBhODE1NDgxNzNmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmMDIxOTE2OTA4MzczZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjE2MDIxNzkwNTU1MDUwNTA2MTBkOTg4MDYxMDBkMzYwMDAzOTYwMDBmM2ZlNjA4MDYwNDA1MjYwMDQzNjEwNjEwMDNmNTc2MDAwMzU2MGUwMWM4MDYzNTU1NWQ3ZTQxNDYxMDA0MTU3ODA2MzVlY2YyOThhMTQ2MTAwY2Y1NzgwNjM5ZGM3ZGIxMDE0NjEwMGZkNTc4MDYzYTlhM2E2MzUxNDYxMDE0YjU3NWIwMDViNjEwMGNkNjAwNDgwMzYwMzYwODA4MTEwMTU2MTAwNTc1NzYwMDA4MGZkNWI4MTAxOTA4MDgwMzU3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNjkwNjAyMDAxOTA5MjkxOTA4MDM1NzNmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmMTY5MDYwMjAwMTkwOTI5MTkwODAzNTczZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjE2OTA2MDIwMDE5MDkyOTE5MDgwMzU5MDYwMjAwMTkwOTI5MTkwNTA1MDUwNjEwMTc5NTY1YjAwNWI2MTAwZmI2MDA0ODAzNjAzNjAyMDgxMTAxNTYxMDBlNTU3NjAwMDgwZmQ1YjgxMDE5MDgwODAzNTkwNjAyMDAxOTA5MjkxOTA1MDUwNTA2MTA3MTM1NjViMDA1YjYxMDE0OTYwMDQ4MDM2MDM2MDQwODExMDE1NjEwMTEzNTc2MDAwODBmZDViODEwMTkwODA4MDM1NzNmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmMTY5MDYwMjAwMTkwOTI5MTkwODAzNTkwNjAyMDAxOTA5MjkxOTA1MDUwNTA2MTA4MmY1NjViMDA1YjYxMDE3NzYwMDQ4MDM2MDM2MDIwODExMDE1NjEwMTYxNTc2MDAwODBmZDViODEwMTkwODA4MDM1OTA2MDIwMDE5MDkyOTE5MDUwNTA1MDYxMGEwZjU2NWIwMDViODM3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNjYxMDhmYzgyOTA4MTE1MDI5MDYwNDA1MTYwMDA2MDQwNTE4MDgzMDM4MTg1ODg4OGYxOTM1MDUwNTA1MDE1ODAxNTYxMDFiZjU3M2Q2MDAwODAzZTNkNjAwMGZkNWI1MDYwMDA4MDkwNTQ5MDYxMDEwMDBhOTAwNDczZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjE2NzNmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmMTY2M2FjZWY2MDM3ODU2MDAyODQ4MTYxMDIwODU3ZmU1YjA0NjA0MDUxODM2M2ZmZmZmZmZmMTY2MGUwMWI4MTUyNjAwNDAxODA4MzczZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjE2NzNmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmMTY4MTUyNjAyMDAxODI4MTUyNjAyMDAxOTI1MDUwNTA2MDAwNjA0MDUxODA4MzAzODE2MDAwODc4MDNiMTU4MDE1NjEwMjcyNTc2MDAwODBmZDViNTA1YWYxMTU4MDE1NjEwMjg2NTczZDYwMDA4MDNlM2Q2MDAwZmQ1YjUwNTA1MDUwNjAwMTYwMDA5MDU0OTA2MTAxMDAwYTkwMDQ3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNjczZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjE2NjNhY2VmNjAzNzg1NjAwMjg0ODE2MTAyZDM1N2ZlNWIwNDYwNDA1MTgzNjNmZmZmZmZmZjE2NjBlMDFiODE1MjYwMDQwMTgwODM3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNjczZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjE2ODE1MjYwMjAwMTgyODE1MjYwMjAwMTkyNTA1MDUwNjAwMDYwNDA1MTgwODMwMzgxNjAwMDg3ODAzYjE1ODAxNTYxMDMzZDU3NjAwMDgwZmQ1YjUwNWFmMTE1ODAxNTYxMDM1MTU3M2Q2MDAwODAzZTNkNjAwMGZkNWI1MDUwNTA1MDgyNzNmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmMTY2MTA4ZmM4MjkwODExNTAyOTA2MDQwNTE2MDAwNjA0MDUxODA4MzAzODE4NTg4ODhmMTkzNTA1MDUwNTAxNTgwMTU2MTAzOWI1NzNkNjAwMDgwM2UzZDYwMDBmZDViNTA2MDAwODA5MDU0OTA2MTAxMDAwYTkwMDQ3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNjczZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjE2NjNhY2VmNjAzNzg0NjAwMjg0ODE2MTAzZTQ1N2ZlNWIwNDYwNDA1MTgzNjNmZmZmZmZmZjE2NjBlMDFiODE1MjYwMDQwMTgwODM3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNjczZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjE2ODE1MjYwMjAwMTgyODE1MjYwMjAwMTkyNTA1MDUwNjAwMDYwNDA1MTgwODMwMzgxNjAwMDg3ODAzYjE1ODAxNTYxMDQ0ZTU3NjAwMDgwZmQ1YjUwNWFmMTE1ODAxNTYxMDQ2MjU3M2Q2MDAwODAzZTNkNjAwMGZkNWI1MDUwNTA1MDYwMDE2MDAwOTA1NDkwNjEwMTAwMGE5MDA0NzNmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmMTY3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNjYzYWNlZjYwMzc4NDYwMDI4NDgxNjEwNGFmNTdmZTViMDQ2MDQwNTE4MzYzZmZmZmZmZmYxNjYwZTAxYjgxNTI2MDA0MDE4MDgzNzNmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmMTY3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNjgxNTI2MDIwMDE4MjgxNTI2MDIwMDE5MjUwNTA1MDYwMDA2MDQwNTE4MDgzMDM4MTYwMDA4NzgwM2IxNTgwMTU2MTA1MTk1NzYwMDA4MGZkNWI1MDVhZjExNTgwMTU2MTA1MmQ1NzNkNjAwMDgwM2UzZDYwMDBmZDViNTA1MDUwNTA4MTczZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjE2NjEwOGZjODI5MDgxMTUwMjkwNjA0MDUxNjAwMDYwNDA1MTgwODMwMzgxODU4ODg4ZjE5MzUwNTA1MDUwMTU4MDE1NjEwNTc3NTczZDYwMDA4MDNlM2Q2MDAwZmQ1YjUwNjAwMDgwOTA1NDkwNjEwMTAwMGE5MDA0NzNmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmMTY3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNjYzYWNlZjYwMzc4MzYwMDI4NDgxNjEwNWMwNTdmZTViMDQ2MDQwNTE4MzYzZmZmZmZmZmYxNjYwZTAxYjgxNTI2MDA0MDE4MDgzNzNmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmMTY3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNjgxNTI2MDIwMDE4MjgxNTI2MDIwMDE5MjUwNTA1MDYwMDA2MDQwNTE4MDgzMDM4MTYwMDA4NzgwM2IxNTgwMTU2MTA2MmE1NzYwMDA4MGZkNWI1MDVhZjExNTgwMTU2MTA2M2U1NzNkNjAwMDgwM2UzZDYwMDBmZDViNTA1MDUwNTA2MDAxNjAwMDkwNTQ5MDYxMDEwMDBhOTAwNDczZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjE2NzNmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmMTY2M2FjZWY2MDM3ODM2MDAyODQ4MTYxMDY4YjU3ZmU1YjA0NjA0MDUxODM2M2ZmZmZmZmZmMTY2MGUwMWI4MTUyNjAwNDAxODA4MzczZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjE2NzNmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmMTY4MTUyNjAyMDAxODI4MTUyNjAyMDAxOTI1MDUwNTA2MDAwNjA0MDUxODA4MzAzODE2MDAwODc4MDNiMTU4MDE1NjEwNmY1NTc2MDAwODBmZDViNTA1YWYxMTU4MDE1NjEwNzA5NTczZDYwMDA4MDNlM2Q2MDAwZmQ1YjUwNTA1MDUwNTA1MDUwNTA1NjViNjAwMDgwOTA1NDkwNjEwMTAwMGE5MDA0NzNmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmY=","b64Record":"CiAIFiocCgwIARAMGgYIgK6ZpA8SDAgBEA8aBgiArpmkDxIwDEvRXmI0K+k60UE7fAhIrsjVkdDDeMJiC4WD+wrC2y0yYtm7xWgc7MheWMPDcQDpGgsI4v7+rAYQpfubcCIPCgkIpv7+rAYQsBISAhgCKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjlIA"},{"b64Body":"Cg8KCQim/v6sBhC2EhICGAISAhgDGPjsozsiAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjoIB3hkSAxiKCSLWGWZmZmZmZmZmZmZmZmZmZmYxNjczZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjE2NjM4ZGQ5ODA0MzgyNjA0MDUxODI2M2ZmZmZmZmZmMTY2MGUwMWI4MTUyNjAwNDAxODA4MjgxNTI2MDIwMDE5MTUwNTA2MDAwNjA0MDUxODA4MzAzODE2MDAwODc4MDNiMTU4MDE1NjEwNzg3NTc2MDAwODBmZDViNTA1YWYxMTU4MDE1NjEwNzliNTczZDYwMDA4MDNlM2Q2MDAwZmQ1YjUwNTA1MDUwNjAwMTYwMDA5MDU0OTA2MTAxMDAwYTkwMDQ3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNjczZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjE2NjM4ZGQ5ODA0MzgyNjA0MDUxODI2M2ZmZmZmZmZmMTY2MGUwMWI4MTUyNjAwNDAxODA4MjgxNTI2MDIwMDE5MTUwNTA2MDAwNjA0MDUxODA4MzAzODE2MDAwODc4MDNiMTU4MDE1NjEwODE0NTc2MDAwODBmZDViNTA1YWYxMTU4MDE1NjEwODI4NTczZDYwMDA4MDNlM2Q2MDAwZmQ1YjUwNTA1MDUwNTA1NjViODE3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNjYxMDhmYzgyOTA4MTE1MDI5MDYwNDA1MTYwMDA2MDQwNTE4MDgzMDM4MTg1ODg4OGYxOTM1MDUwNTA1MDE1ODAxNTYxMDg3NTU3M2Q2MDAwODAzZTNkNjAwMGZkNWI1MDYwMDA4MDkwNTQ5MDYxMDEwMDBhOTAwNDczZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjE2NzNmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmMTY2M2FjZWY2MDM3ODM2MDAyODQ4MTYxMDhiZTU3ZmU1YjA0NjA0MDUxODM2M2ZmZmZmZmZmMTY2MGUwMWI4MTUyNjAwNDAxODA4MzczZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjE2NzNmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmMTY4MTUyNjAyMDAxODI4MTUyNjAyMDAxOTI1MDUwNTA2MDAwNjA0MDUxODA4MzAzODE2MDAwODc4MDNiMTU4MDE1NjEwOTI4NTc2MDAwODBmZDViNTA1YWYxMTU4MDE1NjEwOTNjNTczZDYwMDA4MDNlM2Q2MDAwZmQ1YjUwNTA1MDUwNjAwMTYwMDA5MDU0OTA2MTAxMDAwYTkwMDQ3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNjczZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjE2NjNhY2VmNjAzNzgzNjAwMjg0ODE2MTA5ODk1N2ZlNWIwNDYwNDA1MTgzNjNmZmZmZmZmZjE2NjBlMDFiODE1MjYwMDQwMTgwODM3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNjczZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjE2ODE1MjYwMjAwMTgyODE1MjYwMjAwMTkyNTA1MDUwNjAwMDYwNDA1MTgwODMwMzgxNjAwMDg3ODAzYjE1ODAxNTYxMDlmMzU3NjAwMDgwZmQ1YjUwNWFmMTE1ODAxNTYxMGEwNzU3M2Q2MDAwODAzZTNkNjAwMGZkNWI1MDUwNTA1MDUwNTA1NjViNjAwMDYwNjA2MDAwODA5MDU0OTA2MTAxMDAwYTkwMDQ3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNjczZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjE2ODM2MDQwNTE2MDI0MDE4MDgyODE1MjYwMjAwMTkxNTA1MDYwNDA1MTYwMjA4MTgzMDMwMzgxNTI5MDYwNDA1MjdmOGRkOTgwNDMwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDdiZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxOTE2NjAyMDgyMDE4MDUxN2JmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjgzODE4MzE2MTc4MzUyNTA1MDUwNTA2MDQwNTE4MDgyODA1MTkwNjAyMDAxOTA4MDgzODM1YjYwMjA4MzEwNjEwYjBjNTc4MDUxODI1MjYwMjA4MjAxOTE1MDYwMjA4MTAxOTA1MDYwMjA4MzAzOTI1MDYxMGFlOTU2NWI2MDAxODM2MDIwMDM2MTAxMDAwYTAzODAxOTgyNTExNjgxODQ1MTE2ODA4MjE3ODU1MjUwNTA1MDUwNTA1MDkwNTAwMTkxNTA1MDYwMDA2MDQwNTE4MDgzMDM4MTg1NWFmNDkxNTA1MDNkODA2MDAwODExNDYxMGI2YzU3NjA0MDUxOTE1MDYwMWYxOTYwM2YzZDAxMTY4MjAxNjA0MDUyM2Q4MjUyM2Q2MDAwNjAyMDg0MDEzZTYxMGI3MTU2NWI2MDYwOTE1MDViNTA5MTUwOTE1MDYwMDA2MDYwNjAwMTYwMDA5MDU0OTA2MTAxMDAwYTkwMDQ3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNjczZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjE2ODU2MDQwNTE2MDI0MDE4MDgyODE1MjYwMjAwMTkxNTA1MDYwNDA1MTYwMjA4MTgzMDMwMzgxNTI5MDYwNDA1MjdmOGRkOTgwNDMwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDdiZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxOTE2NjAyMDgyMDE4MDUxN2JmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjgzODE4MzE2MTc4MzUyNTA1MDUwNTA2MDQwNTE4MDgyODA1MTkwNjAyMDAxOTA4MDgzODM1YjYwMjA4MzEwNjEwYzc0NTc4MDUxODI1MjYwMjA4MjAxOTE1MDYwMjA4MTAxOTA1MDYwMjA4MzAzOTI1MDYxMGM1MTU2NWI2MDAxODM2MDIwMDM2MTAxMDAwYTAzODAxOTgyNTExNjgxODQ1MTE2ODA4MjE3ODU1MjUwNTA1MDUwNTA1MDkwNTAwMTkxNTA1MDYwMDA2MDQwNTE4MDgzMDM4MTg1NWFmNDkxNTA1MDNkODA2MDAwODExNDYxMGNkNDU3NjA0MDUxOTE1MDYwMWYxOTYwM2YzZDAxMTY4MjAxNjA0MDUyM2Q4MjUyM2Q2MDAwNjAyMDg0MDEzZTYxMGNkOTU2NWI2MDYwOTE1MDViNTA5MTUwOTE1MDgzMTU4MDYxMGNlOTU3NTA4MTE1NWIxNTYxMGQ1YzU3NjA0MDUxN2YwOGMzNzlhMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwODE1MjYwMDQwMTgwODA2MDIwMDE4MjgxMDM4MjUyNjAxZTgxNTI2MDIwMDE4MDdmNDQ2NTZjNjU2NzYxNzQ2NTIwNzQ3MjYxNmU3MzY2NjU3MjIwNjM2MTZjNmMyMDY2NjE2OTZjNjU2NDIxMDAwMDgxNTI1MDYwMjAwMTkxNTA1MDYwNDA1MTgwOTEwMzkwZmQ1YjUwNTA1MDUwNTA1NmZlYTI2NTYyN2E3YTcyMzE1ODIwNjE5ZDU1NTI1Y2QxN2Y3MzliMGNiMjZkODZmN2QxOGYxNTVlYWM0NWY5NTM3MGQ5NjRkZWEzYzcwZGIxNGQ2ZTY0NzM2ZjZjNjM0MzAwMDUxMTAwMzI=","b64Record":"CiAIFiocCgwIARAMGgYIgK6ZpA8SDAgBEA8aBgiArpmkDxIw6CHL1g5Z4RsV2BgHrAFIFp+ewsXRa0VUSAGwQpos5fPgytw7EQxKQMQRWmb0ro7UGgwI4v7+rAYQo/G51QIiDwoJCKb+/qwGELYSEgIYAiogw4PCrsOCwrfDg8K5dEY4w4LCrkrDg8KLw4PCkMODwo5SAA=="},{"b64Body":"Cg8KCQin/v6sBhC4EhICGAISAhgDGP7LzSwiAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjooBjgESCwjjzNmwBhCw+ppnGm0KIhIgTfK+IPwEhNs7Ir/f6yFhtvfJ18xvHqV3qcVcnELv8CYKIzohAv9NeseDq7icLqHCh1gUGr/ccRMYNJzRXQZLmggdB8UrCiISIHa6UcXa+4Yv1NfYkfXqgWlKmVagHZo6SgWFLW2RE27CIgxIZWxsbyBXb3JsZCEqADIA","b64Record":"CiUIFhoDGIsJKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjCZdjjqJ9+uL9/M9uMskZpAMhQMCOau1RAICsrL6fdu8PwepbxJu3W+48l3PMNINXYaCwjj/v6sBhDwne15Ig8KCQin/v6sBhC4EhICGAIqIMODwq7DgsK3w4PCuXRGOMOCwq5Kw4PCi8ODwpDDg8KOUgA="},{"b64Body":"Cg8KCQin/v6sBhC8EhICGAISAhgDGM6Y0i8iAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjpoBkAYKAxiLCSKIBjYwODA2MDQwNTI2MTAxNzE4MDYxMDAxMzYwMDAzOTYwMDBmM2ZlNjA4MDYwNDA1MjYwMDQzNjEwNjEwMDI5NTc2MDAwMzU2MGUwMWM4MDYzOGRkOTgwNDMxNDYxMDAyYjU3ODA2M2FjZWY2MDM3MTQ2MTAwNTk1NzViMDA1YjYxMDA1NzYwMDQ4MDM2MDM2MDIwODExMDE1NjEwMDQxNTc2MDAwODBmZDViODEwMTkwODA4MDM1OTA2MDIwMDE5MDkyOTE5MDUwNTA1MDYxMDBhNzU2NWIwMDViNjEwMGE1NjAwNDgwMzYwMzYwNDA4MTEwMTU2MTAwNmY1NzYwMDA4MGZkNWI4MTAxOTA4MDgwMzU3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNjkwNjAyMDAxOTA5MjkxOTA4MDM1OTA2MDIwMDE5MDkyOTE5MDUwNTA1MDYxMDBmMTU2NWIwMDViMzM3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNjYxMDhmYzgyOTA4MTE1MDI5MDYwNDA1MTYwMDA2MDQwNTE4MDgzMDM4MTg1ODg4OGYxOTM1MDUwNTA1MDE1ODAxNTYxMDBlZDU3M2Q2MDAwODAzZTNkNjAwMGZkNWI1MDUwNTY1YjgxNzNmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmMTY2MTA4ZmM4MjkwODExNTAyOTA2MDQwNTE2MDAwNjA0MDUxODA4MzAzODE4NTg4ODhmMTkzNTA1MDUwNTAxNTgwMTU2MTAxMzc1NzNkNjAwMDgwM2UzZDYwMDBmZDViNTA1MDUwNTZmZWEyNjU2MjdhN2E3MjMxNTgyMDVlYjQwYmI2MjA4MzVmYjc5NjljZGQ3NWRlZDE4ODkyN2Q1YzkyN2Y5M2JhNjc1ZDY1YjljNTIzNDQ0MjQyODc2NDczNmY2YzYzNDMwMDA1MTEwMDMy","b64Record":"CiAIFiocCgwIARAMGgYIgK6ZpA8SDAgBEA8aBgiArpmkDxIw2J0T/sr0wnM1AvzktPGep53zh80IvV/CDYo3hZRCqOf//lIBbR5Jzf+0jRcpFULPGgwI4/7+rAYQk83k3gIiDwoJCKf+/qwGELwSEgIYAiogw4PCrsOCwrfDg8K5dEY4w4LCrkrDg8KLw4PCkMODwo5SAA=="},{"b64Body":"ChAKCQio/v6sBhC+EhIDGIkJEgIYAxiW+66dAiICCHgyIMODwq7DgsK3w4PCuXRGOMOCwq5Kw4PCi8ODwpDDg8KOQkgKAxiLCRoiEiAPPv+YffJNlbp/991SvlEcVgc9hxNiXoF8pelkVi/kcCCQoQ8okE5CBQiAztoDUgBaAGoLY2VsbGFyIGRvb3I=","b64Record":"CiUIFiIDGIwJKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjDFubMgN99EjQ5VK2nDe29yyI/IV7jHMvSCONnBBmSejSU7KOkGAl7t8d06MBwD8WIaDAjk/v6sBhDl/7iDASIQCgkIqP7+rAYQvhISAxiJCSogw4PCrsOCwrfDg8K5dEY4w4LCrkrDg8KLw4PCkMODwo4w1tSRpAJCpgUKAxiMCRLxAmCAYEBSYAQ2EGEAKVdgADVg4ByAY43ZgEMUYQArV4BjrO9gNxRhAFlXWwBbYQBXYASANgNgIIEQFWEAQVdgAID9W4EBkICANZBgIAGQkpGQUFBQYQCnVlsAW2EApWAEgDYDYECBEBVhAG9XYACA/VuBAZCAgDVz//////////////////////////8WkGAgAZCSkZCANZBgIAGQkpGQUFBQYQDxVlsAWzNz//////////////////////////8WYQj8gpCBFQKQYEBRYABgQFGAgwOBhYiI8ZNQUFBQFYAVYQDtVz1gAIA+PWAA/VtQUFZbgXP//////////////////////////xZhCPyCkIEVApBgQFFgAGBAUYCDA4GFiIjxk1BQUFAVgBVhATdXPWAAgD49YAD9W1BQUFb+omVienpyMVggXrQLtiCDX7eWnN113tGIkn1ckn+TumddZbnFI0RCQodkc29sY0MABREAMiKAAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAowJoMOgMYjAlKFgoUAAAAAAAAAAAAAAAAAAAAAAAABIxyBwoDGIwJEAFSRwoJCgIYAxCY4okUCgoKAhhiEODciMYDCgoKAxigBhCatYg3CgoKAxihBhCatYg3CgsKAxiJCRDLxaTIBAoJCgMYjAkQoJwB"},{"b64Body":"ChAKCQio/v6sBhDAEhIDGIkJEgIYAxiW+66dAiICCHgyIMODwq7DgsK3w4PCuXRGOMOCwq5Kw4PCi8ODwpDDg8KOQkgKAxiLCRoiEiCUAEV9FR1C8yRwlH+8DNiieX7wuvI/3NexK0WUO/DymSCQoQ8okE5CBQiAztoDUgBaAGoLY2VsbGFyIGRvb3I=","b64Record":"CiUIFiIDGI0JKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjBpKZCGXH0olCUzpVtAzIqO1pVZ8EfqsWFywnVSAUpkpOKcdvdowQniVx/WW5m434waDAjk/v6sBhCovLHoAiIQCgkIqP7+rAYQwBISAxiJCSogw4PCrsOCwrfDg8K5dEY4w4LCrkrDg8KLw4PCkMODwo4w1tSRpAJCpgUKAxiNCRLxAmCAYEBSYAQ2EGEAKVdgADVg4ByAY43ZgEMUYQArV4BjrO9gNxRhAFlXWwBbYQBXYASANgNgIIEQFWEAQVdgAID9W4EBkICANZBgIAGQkpGQUFBQYQCnVlsAW2EApWAEgDYDYECBEBVhAG9XYACA/VuBAZCAgDVz//////////////////////////8WkGAgAZCSkZCANZBgIAGQkpGQUFBQYQDxVlsAWzNz//////////////////////////8WYQj8gpCBFQKQYEBRYABgQFGAgwOBhYiI8ZNQUFBQFYAVYQDtVz1gAIA+PWAA/VtQUFZbgXP//////////////////////////xZhCPyCkIEVApBgQFFgAGBAUYCDA4GFiIjxk1BQUFAVgBVhATdXPWAAgD49YAD9W1BQUFb+omVienpyMVggXrQLtiCDX7eWnN113tGIkn1ckn+TumddZbnFI0RCQodkc29sY0MABREAMiKAAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAowJoMOgMYjQlKFgoUAAAAAAAAAAAAAAAAAAAAAAAABI1yBwoDGI0JEAFSRwoJCgIYAxCY4okUCgoKAhhiEODciMYDCgoKAxigBhCatYg3CgoKAxihBhCatYg3CgsKAxiJCRDLxaTIBAoJCgMYjQkQoJwB"},{"b64Body":"ChAKCQip/v6sBhDCEhIDGIkJEgIYAxjVgL+gAiICCHgyIMODwq7DgsK3w4PCuXRGOMOCwq5Kw4PCi8ODwpDDg8KOQooBCgMYigkaIhIgnGIqDbV8kzQkxYwbEbQpiv2VDQmkZ9sl56qY/mzPwukgkKEPKJBOQgUIgM7aA0pAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABIwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEjVIAWgBqC2NlbGxhciBkb29y","b64Record":"CiUIFiIDGI4JKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjD21oMrwfas615B9YLg/53aiNveygzus+vuBiMYKeXOSe+p4/3+WJE8n5Vgz3FbEIQaDAjl/v6sBhCqvN6MASIQCgkIqf7+rAYQwhISAxiJCSogw4PCrsOCwrfDg8K5dEY4w4LCrkrDg8KLw4PCkMODwo4wldqhpwJCzR0KAxiOCRKYG2CAYEBSYAQ2EGEAP1dgADVg4ByAY1VV1+QUYQBBV4BjXs8pihRhAM9XgGOdx9sQFGEA/VeAY6mjpjUUYQFLV1sAW2EAzWAEgDYDYICBEBVhAFdXYACA/VuBAZCAgDVz//////////////////////////8WkGAgAZCSkZCANXP//////////////////////////xaQYCABkJKRkIA1c///////////////////////////FpBgIAGQkpGQgDWQYCABkJKRkFBQUGEBeVZbAFthAPtgBIA2A2AggRAVYQDlV2AAgP1bgQGQgIA1kGAgAZCSkZBQUFBhBxNWWwBbYQFJYASANgNgQIEQFWEBE1dgAID9W4EBkICANXP//////////////////////////xaQYCABkJKRkIA1kGAgAZCSkZBQUFBhCC9WWwBbYQF3YASANgNgIIEQFWEBYVdgAID9W4EBkICANZBgIAGQkpGQUFBQYQoPVlsAW4Nz//////////////////////////8WYQj8gpCBFQKQYEBRYABgQFGAgwOBhYiI8ZNQUFBQFYAVYQG/Vz1gAIA+PWAA/VtQYACAkFSQYQEACpAEc///////////////////////////FnP//////////////////////////xZjrO9gN4VgAoSBYQIIV/5bBGBAUYNj/////xZg4BuBUmAEAYCDc///////////////////////////FnP//////////////////////////xaBUmAgAYKBUmAgAZJQUFBgAGBAUYCDA4FgAIeAOxWAFWECcldgAID9W1Ba8RWAFWEChlc9YACAPj1gAP1bUFBQUGABYACQVJBhAQAKkARz//////////////////////////8Wc///////////////////////////FmOs72A3hWAChIFhAtNX/lsEYEBRg2P/////FmDgG4FSYAQBgINz//////////////////////////8Wc///////////////////////////FoFSYCABgoFSYCABklBQUGAAYEBRgIMDgWAAh4A7FYAVYQM9V2AAgP1bUFrxFYAVYQNRVz1gAIA+PWAA/VtQUFBQgnP//////////////////////////xZhCPyCkIEVApBgQFFgAGBAUYCDA4GFiIjxk1BQUFAVgBVhA5tXPWAAgD49YAD9W1BgAICQVJBhAQAKkARz//////////////////////////8Wc///////////////////////////FmOs72A3hGAChIFhA+RX/lsEYEBRg2P/////FmDgG4FSYAQBgINz//////////////////////////8Wc///////////////////////////FoFSYCABgoFSYCABklBQUGAAYEBRgIMDgWAAh4A7FYAVYQROV2AAgP1bUFrxFYAVYQRiVz1gAIA+PWAA/VtQUFBQYAFgAJBUkGEBAAqQBHP//////////////////////////xZz//////////////////////////8WY6zvYDeEYAKEgWEEr1f+WwRgQFGDY/////8WYOAbgVJgBAGAg3P//////////////////////////xZz//////////////////////////8WgVJgIAGCgVJgIAGSUFBQYABgQFGAgwOBYACHgDsVgBVhBRlXYACA/VtQWvEVgBVhBS1XPWAAgD49YAD9W1BQUFCBc///////////////////////////FmEI/IKQgRUCkGBAUWAAYEBRgIMDgYWIiPGTUFBQUBWAFWEFd1c9YACAPj1gAP1bUGAAgJBUkGEBAAqQBHP//////////////////////////xZz//////////////////////////8WY6zvYDeDYAKEgWEFwFf+WwRgQFGDY/////8WYOAbgVJgBAGAg3P//////////////////////////xZz//////////////////////////8WgVJgIAGCgVJgIAGSUFBQYABgQFGAgwOBYACHgDsVgBVhBipXYACA/VtQWvEVgBVhBj5XPWAAgD49YAD9W1BQUFBgAWAAkFSQYQEACpAEc///////////////////////////FnP//////////////////////////xZjrO9gN4NgAoSBYQaLV/5bBGBAUYNj/////xZg4BuBUmAEAYCDc///////////////////////////FnP//////////////////////////xaBUmAgAYKBUmAgAZJQUFBgAGBAUYCDA4FgAIeAOxWAFWEG9VdgAID9W1Ba8RWAFWEHCVc9YACAPj1gAP1bUFBQUFBQUFBWW2AAgJBUkGEBAAqQBHP//////////////////////////xZz//////////////////////////8WY43ZgEOCYEBRgmP/////FmDgG4FSYAQBgIKBUmAgAZFQUGAAYEBRgIMDgWAAh4A7FYAVYQeHV2AAgP1bUFrxFYAVYQebVz1gAIA+PWAA/VtQUFBQYAFgAJBUkGEBAAqQBHP//////////////////////////xZz//////////////////////////8WY43ZgEOCYEBRgmP/////FmDgG4FSYAQBgIKBUmAgAZFQUGAAYEBRgIMDgWAAh4A7FYAVYQgUV2AAgP1bUFrxFYAVYQgoVz1gAIA+PWAA/VtQUFBQUFZbgXP//////////////////////////xZhCPyCkIEVApBgQFFgAGBAUYCDA4GFiIjxk1BQUFAVgBVhCHVXPWAAgD49YAD9W1BgAICQVJBhAQAKkARz//////////////////////////8Wc///////////////////////////FmOs72A3g2AChIFhCL5X/lsEYEBRg2P/////FmDgG4FSYAQBgINz//////////////////////////8Wc///////////////////////////FoFSYCABgoFSYCABklBQUGAAYEBRgIMDgWAAh4A7FYAVYQkoV2AAgP1bUFrxFYAVYQk8Vz1gAIA+PWAA/VtQUFBQYAFgAJBUkGEBAAqQBHP//////////////////////////xZz//////////////////////////8WY6zvYDeDYAKEgWEJiVf+WwRgQFGDY/////8WYOAbgVJgBAGAg3P//////////////////////////xZz//////////////////////////8WgVJgIAGCgVJgIAGSUFBQYABgQFGAgwOBYACHgDsVgBVhCfNXYACA/VtQWvEVgBVhCgdXPWAAgD49YAD9W1BQUFBQUFZbYABgYGAAgJBUkGEBAAqQBHP//////////////////////////xZz//////////////////////////8Wg2BAUWAkAYCCgVJgIAGRUFBgQFFgIIGDAwOBUpBgQFJ/jdmAQwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB7/////////////////////////////////////xkWYCCCAYBRe/////////////////////////////////////+DgYMWF4NSUFBQUGBAUYCCgFGQYCABkICDg1tgIIMQYQsMV4BRglJgIIIBkVBgIIEBkFBgIIMDklBhCulWW2ABg2AgA2EBAAoDgBmCURaBhFEWgIIXhVJQUFBQUFCQUAGRUFBgAGBAUYCDA4GFWvSRUFA9gGAAgRRhC2xXYEBRkVBgHxlgPz0BFoIBYEBSPYJSPWAAYCCEAT5hC3FWW2BgkVBbUJFQkVBgAGBgYAFgAJBUkGEBAAqQBHP//////////////////////////xZz//////////////////////////8WhWBAUWAkAYCCgVJgIAGRUFBgQFFgIIGDAwOBUpBgQFJ/jdmAQwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB7/////////////////////////////////////xkWYCCCAYBRe/////////////////////////////////////+DgYMWF4NSUFBQUGBAUYCCgFGQYCABkICDg1tgIIMQYQx0V4BRglJgIIIBkVBgIIEBkFBgIIMDklBhDFFWW2ABg2AgA2EBAAoDgBmCURaBhFEWgIIXhVJQUFBQUFCQUAGRUFBgAGBAUYCDA4GFWvSRUFA9gGAAgRRhDNRXYEBRkVBgHxlgPz0BFoIBYEBSPYJSPWAAYCCEAT5hDNlWW2BgkVBbUJFQkVCDFYBhDOlXUIEVWxVhDVxXYEBRfwjDeaAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgVJgBAGAgGAgAYKBA4JSYB6BUmAgAYB/RGVsZWdhdGUgdHJhbnNmZXIgY2FsbCBmYWlsZWQhAACBUlBgIAGRUFBgQFGAkQOQ/VtQUFBQUFb+omVienpyMVggYZ1VUlzRf3ObDLJthvfRjxVerEX5U3DZZN6jxw2xTW5kc29sY0MABREAMiKAAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAowJoMOgMYjglKFgoUAAAAAAAAAAAAAAAAAAAAAAAABI5yBwoDGI4JEAFSRwoJCgIYAxCu6rgUCgoKAhhiEJiS48oDCgoKAxigBhDy29M3CgoKAxihBhDy29M3CgsKAxiJCRDJ0MTOBAoJCgMYjgkQoJwB"},{"b64Body":"ChAKCQip/v6sBhDEEhIDGIkJEgIYAxjgrLEDIgIIeDIgw4PCrsOCwrfDg8K5dEY4w4LCrkrDg8KLw4PCkMODwo46LwoDGI4JEKCNBiIkXs8pigAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAy","b64Record":"CiUIFiIDGI4JKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjCdRV7F08O7nSMMB5YTtt18nQkCMBPv5w6g7Mzq7Z2/cfRgnpO0NH2ci259GhVbjccaDAjl/v6sBhDa9tfxAiIQCgkIqf7+rAYQxBISAxiJCSogw4PCrsOCwrfDg8K5dEY4w4LCrkrDg8KLw4PCkMODwo4wgNfaAjqMAgoDGI4JIoACAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACiA8QRSMwoJCgIYYhCArrUFCgoKAxiJCRD/rbUFCgcKAxiMCRBjCgcKAxiNCRBjCggKAxiOCRDIAQ=="}]},"sendHbarsToCallerFromDifferentAddresses":{"placeholderNum":1167,"encodedItems":[{"b64Body":"Cg8KCQiu/v6sBhDmEhICGAISAhgDGPuV9hQiAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjloxCiISIOaUQlz5P2r9MDy6ASivr3GxteaIrmdOjInFdM2LUxErEICU69wDSgUIgM7aAw==","b64Record":"CiUIFhIDGJAJKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjDhJyrTl12wmOu9GsmrqHJ57K6tl2IDq42EjUuyWn7t6catxaFOdb7gSxB+BodpV44aDAjq/v6sBhD7ruX1ASIPCgkIrv7+rAYQ5hISAhgCKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjlIZCgoKAhgCEP+n1rkHCgsKAxiQCRCAqNa5Bw=="},{"b64Body":"ChEKCQiv/v6sBhDoEhICGAIgAVpoCiM6IQMIls7RYpi0xCfekVbp7PiaTNimH6yAk5XF0m81utTrc0oFCIDO2gNqFGF1dG8tY3JlYXRlZCBhY2NvdW50kgEjOiEDCJbO0WKYtMQn3pFW6ez4mkzYph+sgJOVxdJvNbrU63M=","b64Record":"CgcIFhIDGJEJEjAv+2d3dmOFVr3RLAsOwjOTH9YgPZoIFy9n/xPjWdfKmI7hpTte+Hq/cWvf0BZrIEoaCwjr/v6sBhDWjtYVIhEKCQiv/v6sBhDoEhICGAIgASoUYXV0by1jcmVhdGVkIGFjY291bnRSAKoBFJJzP+0R+z6bJyL2nhnGo3nosPME"},{"b64Body":"Cg8KCQiv/v6sBhDoEhICGAISAhgDGKqQBSICCHgyIMODwq7DgsK3w4PCuXRGOMOCwq5Kw4PCi8ODwpDDg8KOcj0KOwoKCgIYAhD/j9/ASgotCiUiIzohAwiWztFimLTEJ96RVuns+JpM2KYfrICTlcXSbzW61OtzEICQ38BK","b64Record":"CiAIFiocCgwIARAMGgYIgK6ZpA8SDAgBEA8aBgiArpmkDxIwCRvv4gifpGIeu10x+1UNXke8wTLdQ7XmmbPQKsBnrGNWx+3lZ0g58fZZfFnCClggGgsI6/7+rAYQ147WFSIPCgkIr/7+rAYQ6BISAhgCKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjlIZCgoKAhgCEP+P38BKCgsKAxiRCRCAkN/ASg=="},{"b64Body":"Cg8KCQiv/v6sBhDqEhICGAISAhgDGLXNziwiAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjooBjwESDAjrzNmwBhCmuJjzARptCiISIE8xp/5QKyxp22simcTU95oxLQA/ZvSj5MR9MNEE2miMCiM6IQM19cAyJ+yWGR0ufcoLFjNLiuPD9XPwssg1yFbN4eDZfwoiEiAKHHeO67Qqy40bRGtAmcpBAB2MisscQTtY3NJukEOlAiIMSGVsbG8gV29ybGQhKgAyAA==","b64Record":"CiUIFhoDGJIJKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjCdxx7BObSRWvORgwmiUmVMnS620hQh7DxWx0/cAvDy987xNrwXUrIPhtEi4SbXGwYaDAjr/v6sBhDMsMn6ASIPCgkIr/7+rAYQ6hISAhgCKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjlIA"},{"b64Body":"Cg8KCQiw/v6sBhDuEhICGAISAhgDGNyejz4iAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjpoBiCAKAxiSCSKAIDYwODA2MDQwNTI2MDQwNTE2MTBlNmIzODAzODA2MTBlNmI4MzM5ODE4MTAxNjA0MDUyNjA0MDgxMTAxNTYxMDAyNjU3NjAwMDgwZmQ1YjgxMDE5MDgwODA1MTkwNjAyMDAxOTA5MjkxOTA4MDUxOTA2MDIwMDE5MDkyOTE5MDUwNTA1MDgxNjAwMDgwNjEwMTAwMGE4MTU0ODE3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYwMjE5MTY5MDgzNzNmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmMTYwMjE3OTA1NTUwODA2MDAxNjAwMDYxMDEwMDBhODE1NDgxNzNmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmMDIxOTE2OTA4MzczZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjE2MDIxNzkwNTU1MDUwNTA2MTBkOTg4MDYxMDBkMzYwMDAzOTYwMDBmM2ZlNjA4MDYwNDA1MjYwMDQzNjEwNjEwMDNmNTc2MDAwMzU2MGUwMWM4MDYzNTU1NWQ3ZTQxNDYxMDA0MTU3ODA2MzVlY2YyOThhMTQ2MTAwY2Y1NzgwNjM5ZGM3ZGIxMDE0NjEwMGZkNTc4MDYzYTlhM2E2MzUxNDYxMDE0YjU3NWIwMDViNjEwMGNkNjAwNDgwMzYwMzYwODA4MTEwMTU2MTAwNTc1NzYwMDA4MGZkNWI4MTAxOTA4MDgwMzU3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNjkwNjAyMDAxOTA5MjkxOTA4MDM1NzNmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmMTY5MDYwMjAwMTkwOTI5MTkwODAzNTczZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjE2OTA2MDIwMDE5MDkyOTE5MDgwMzU5MDYwMjAwMTkwOTI5MTkwNTA1MDUwNjEwMTc5NTY1YjAwNWI2MTAwZmI2MDA0ODAzNjAzNjAyMDgxMTAxNTYxMDBlNTU3NjAwMDgwZmQ1YjgxMDE5MDgwODAzNTkwNjAyMDAxOTA5MjkxOTA1MDUwNTA2MTA3MTM1NjViMDA1YjYxMDE0OTYwMDQ4MDM2MDM2MDQwODExMDE1NjEwMTEzNTc2MDAwODBmZDViODEwMTkwODA4MDM1NzNmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmMTY5MDYwMjAwMTkwOTI5MTkwODAzNTkwNjAyMDAxOTA5MjkxOTA1MDUwNTA2MTA4MmY1NjViMDA1YjYxMDE3NzYwMDQ4MDM2MDM2MDIwODExMDE1NjEwMTYxNTc2MDAwODBmZDViODEwMTkwODA4MDM1OTA2MDIwMDE5MDkyOTE5MDUwNTA1MDYxMGEwZjU2NWIwMDViODM3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNjYxMDhmYzgyOTA4MTE1MDI5MDYwNDA1MTYwMDA2MDQwNTE4MDgzMDM4MTg1ODg4OGYxOTM1MDUwNTA1MDE1ODAxNTYxMDFiZjU3M2Q2MDAwODAzZTNkNjAwMGZkNWI1MDYwMDA4MDkwNTQ5MDYxMDEwMDBhOTAwNDczZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjE2NzNmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmMTY2M2FjZWY2MDM3ODU2MDAyODQ4MTYxMDIwODU3ZmU1YjA0NjA0MDUxODM2M2ZmZmZmZmZmMTY2MGUwMWI4MTUyNjAwNDAxODA4MzczZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjE2NzNmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmMTY4MTUyNjAyMDAxODI4MTUyNjAyMDAxOTI1MDUwNTA2MDAwNjA0MDUxODA4MzAzODE2MDAwODc4MDNiMTU4MDE1NjEwMjcyNTc2MDAwODBmZDViNTA1YWYxMTU4MDE1NjEwMjg2NTczZDYwMDA4MDNlM2Q2MDAwZmQ1YjUwNTA1MDUwNjAwMTYwMDA5MDU0OTA2MTAxMDAwYTkwMDQ3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNjczZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjE2NjNhY2VmNjAzNzg1NjAwMjg0ODE2MTAyZDM1N2ZlNWIwNDYwNDA1MTgzNjNmZmZmZmZmZjE2NjBlMDFiODE1MjYwMDQwMTgwODM3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNjczZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjE2ODE1MjYwMjAwMTgyODE1MjYwMjAwMTkyNTA1MDUwNjAwMDYwNDA1MTgwODMwMzgxNjAwMDg3ODAzYjE1ODAxNTYxMDMzZDU3NjAwMDgwZmQ1YjUwNWFmMTE1ODAxNTYxMDM1MTU3M2Q2MDAwODAzZTNkNjAwMGZkNWI1MDUwNTA1MDgyNzNmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmMTY2MTA4ZmM4MjkwODExNTAyOTA2MDQwNTE2MDAwNjA0MDUxODA4MzAzODE4NTg4ODhmMTkzNTA1MDUwNTAxNTgwMTU2MTAzOWI1NzNkNjAwMDgwM2UzZDYwMDBmZDViNTA2MDAwODA5MDU0OTA2MTAxMDAwYTkwMDQ3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNjczZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjE2NjNhY2VmNjAzNzg0NjAwMjg0ODE2MTAzZTQ1N2ZlNWIwNDYwNDA1MTgzNjNmZmZmZmZmZjE2NjBlMDFiODE1MjYwMDQwMTgwODM3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNjczZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjE2ODE1MjYwMjAwMTgyODE1MjYwMjAwMTkyNTA1MDUwNjAwMDYwNDA1MTgwODMwMzgxNjAwMDg3ODAzYjE1ODAxNTYxMDQ0ZTU3NjAwMDgwZmQ1YjUwNWFmMTE1ODAxNTYxMDQ2MjU3M2Q2MDAwODAzZTNkNjAwMGZkNWI1MDUwNTA1MDYwMDE2MDAwOTA1NDkwNjEwMTAwMGE5MDA0NzNmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmMTY3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNjYzYWNlZjYwMzc4NDYwMDI4NDgxNjEwNGFmNTdmZTViMDQ2MDQwNTE4MzYzZmZmZmZmZmYxNjYwZTAxYjgxNTI2MDA0MDE4MDgzNzNmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmMTY3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNjgxNTI2MDIwMDE4MjgxNTI2MDIwMDE5MjUwNTA1MDYwMDA2MDQwNTE4MDgzMDM4MTYwMDA4NzgwM2IxNTgwMTU2MTA1MTk1NzYwMDA4MGZkNWI1MDVhZjExNTgwMTU2MTA1MmQ1NzNkNjAwMDgwM2UzZDYwMDBmZDViNTA1MDUwNTA4MTczZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjE2NjEwOGZjODI5MDgxMTUwMjkwNjA0MDUxNjAwMDYwNDA1MTgwODMwMzgxODU4ODg4ZjE5MzUwNTA1MDUwMTU4MDE1NjEwNTc3NTczZDYwMDA4MDNlM2Q2MDAwZmQ1YjUwNjAwMDgwOTA1NDkwNjEwMTAwMGE5MDA0NzNmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmMTY3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNjYzYWNlZjYwMzc4MzYwMDI4NDgxNjEwNWMwNTdmZTViMDQ2MDQwNTE4MzYzZmZmZmZmZmYxNjYwZTAxYjgxNTI2MDA0MDE4MDgzNzNmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmMTY3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNjgxNTI2MDIwMDE4MjgxNTI2MDIwMDE5MjUwNTA1MDYwMDA2MDQwNTE4MDgzMDM4MTYwMDA4NzgwM2IxNTgwMTU2MTA2MmE1NzYwMDA4MGZkNWI1MDVhZjExNTgwMTU2MTA2M2U1NzNkNjAwMDgwM2UzZDYwMDBmZDViNTA1MDUwNTA2MDAxNjAwMDkwNTQ5MDYxMDEwMDBhOTAwNDczZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjE2NzNmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmMTY2M2FjZWY2MDM3ODM2MDAyODQ4MTYxMDY4YjU3ZmU1YjA0NjA0MDUxODM2M2ZmZmZmZmZmMTY2MGUwMWI4MTUyNjAwNDAxODA4MzczZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjE2NzNmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmMTY4MTUyNjAyMDAxODI4MTUyNjAyMDAxOTI1MDUwNTA2MDAwNjA0MDUxODA4MzAzODE2MDAwODc4MDNiMTU4MDE1NjEwNmY1NTc2MDAwODBmZDViNTA1YWYxMTU4MDE1NjEwNzA5NTczZDYwMDA4MDNlM2Q2MDAwZmQ1YjUwNTA1MDUwNTA1MDUwNTA1NjViNjAwMDgwOTA1NDkwNjEwMTAwMGE5MDA0NzNmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmY=","b64Record":"CiAIFiocCgwIARAMGgYIgK6ZpA8SDAgBEA8aBgiArpmkDxIwkVrlvlu1X78v1PDQMKazxjLKh/S7juUHqEV0SrC2T55uc8D+9t9+wAktgxYT2asZGgsI7P7+rAYQ37jAHyIPCgkIsP7+rAYQ7hISAhgCKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjlIA"},{"b64Body":"Cg8KCQiw/v6sBhD0EhICGAISAhgDGKLvpDsiAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjoIB3hkSAxiSCSLWGWZmZmZmZmZmZmZmZmZmZmYxNjczZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjE2NjM4ZGQ5ODA0MzgyNjA0MDUxODI2M2ZmZmZmZmZmMTY2MGUwMWI4MTUyNjAwNDAxODA4MjgxNTI2MDIwMDE5MTUwNTA2MDAwNjA0MDUxODA4MzAzODE2MDAwODc4MDNiMTU4MDE1NjEwNzg3NTc2MDAwODBmZDViNTA1YWYxMTU4MDE1NjEwNzliNTczZDYwMDA4MDNlM2Q2MDAwZmQ1YjUwNTA1MDUwNjAwMTYwMDA5MDU0OTA2MTAxMDAwYTkwMDQ3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNjczZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjE2NjM4ZGQ5ODA0MzgyNjA0MDUxODI2M2ZmZmZmZmZmMTY2MGUwMWI4MTUyNjAwNDAxODA4MjgxNTI2MDIwMDE5MTUwNTA2MDAwNjA0MDUxODA4MzAzODE2MDAwODc4MDNiMTU4MDE1NjEwODE0NTc2MDAwODBmZDViNTA1YWYxMTU4MDE1NjEwODI4NTczZDYwMDA4MDNlM2Q2MDAwZmQ1YjUwNTA1MDUwNTA1NjViODE3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNjYxMDhmYzgyOTA4MTE1MDI5MDYwNDA1MTYwMDA2MDQwNTE4MDgzMDM4MTg1ODg4OGYxOTM1MDUwNTA1MDE1ODAxNTYxMDg3NTU3M2Q2MDAwODAzZTNkNjAwMGZkNWI1MDYwMDA4MDkwNTQ5MDYxMDEwMDBhOTAwNDczZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjE2NzNmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmMTY2M2FjZWY2MDM3ODM2MDAyODQ4MTYxMDhiZTU3ZmU1YjA0NjA0MDUxODM2M2ZmZmZmZmZmMTY2MGUwMWI4MTUyNjAwNDAxODA4MzczZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjE2NzNmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmMTY4MTUyNjAyMDAxODI4MTUyNjAyMDAxOTI1MDUwNTA2MDAwNjA0MDUxODA4MzAzODE2MDAwODc4MDNiMTU4MDE1NjEwOTI4NTc2MDAwODBmZDViNTA1YWYxMTU4MDE1NjEwOTNjNTczZDYwMDA4MDNlM2Q2MDAwZmQ1YjUwNTA1MDUwNjAwMTYwMDA5MDU0OTA2MTAxMDAwYTkwMDQ3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNjczZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjE2NjNhY2VmNjAzNzgzNjAwMjg0ODE2MTA5ODk1N2ZlNWIwNDYwNDA1MTgzNjNmZmZmZmZmZjE2NjBlMDFiODE1MjYwMDQwMTgwODM3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNjczZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjE2ODE1MjYwMjAwMTgyODE1MjYwMjAwMTkyNTA1MDUwNjAwMDYwNDA1MTgwODMwMzgxNjAwMDg3ODAzYjE1ODAxNTYxMDlmMzU3NjAwMDgwZmQ1YjUwNWFmMTE1ODAxNTYxMGEwNzU3M2Q2MDAwODAzZTNkNjAwMGZkNWI1MDUwNTA1MDUwNTA1NjViNjAwMDYwNjA2MDAwODA5MDU0OTA2MTAxMDAwYTkwMDQ3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNjczZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjE2ODM2MDQwNTE2MDI0MDE4MDgyODE1MjYwMjAwMTkxNTA1MDYwNDA1MTYwMjA4MTgzMDMwMzgxNTI5MDYwNDA1MjdmOGRkOTgwNDMwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDdiZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxOTE2NjAyMDgyMDE4MDUxN2JmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjgzODE4MzE2MTc4MzUyNTA1MDUwNTA2MDQwNTE4MDgyODA1MTkwNjAyMDAxOTA4MDgzODM1YjYwMjA4MzEwNjEwYjBjNTc4MDUxODI1MjYwMjA4MjAxOTE1MDYwMjA4MTAxOTA1MDYwMjA4MzAzOTI1MDYxMGFlOTU2NWI2MDAxODM2MDIwMDM2MTAxMDAwYTAzODAxOTgyNTExNjgxODQ1MTE2ODA4MjE3ODU1MjUwNTA1MDUwNTA1MDkwNTAwMTkxNTA1MDYwMDA2MDQwNTE4MDgzMDM4MTg1NWFmNDkxNTA1MDNkODA2MDAwODExNDYxMGI2YzU3NjA0MDUxOTE1MDYwMWYxOTYwM2YzZDAxMTY4MjAxNjA0MDUyM2Q4MjUyM2Q2MDAwNjAyMDg0MDEzZTYxMGI3MTU2NWI2MDYwOTE1MDViNTA5MTUwOTE1MDYwMDA2MDYwNjAwMTYwMDA5MDU0OTA2MTAxMDAwYTkwMDQ3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNjczZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjE2ODU2MDQwNTE2MDI0MDE4MDgyODE1MjYwMjAwMTkxNTA1MDYwNDA1MTYwMjA4MTgzMDMwMzgxNTI5MDYwNDA1MjdmOGRkOTgwNDMwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDdiZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxOTE2NjAyMDgyMDE4MDUxN2JmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjgzODE4MzE2MTc4MzUyNTA1MDUwNTA2MDQwNTE4MDgyODA1MTkwNjAyMDAxOTA4MDgzODM1YjYwMjA4MzEwNjEwYzc0NTc4MDUxODI1MjYwMjA4MjAxOTE1MDYwMjA4MTAxOTA1MDYwMjA4MzAzOTI1MDYxMGM1MTU2NWI2MDAxODM2MDIwMDM2MTAxMDAwYTAzODAxOTgyNTExNjgxODQ1MTE2ODA4MjE3ODU1MjUwNTA1MDUwNTA1MDkwNTAwMTkxNTA1MDYwMDA2MDQwNTE4MDgzMDM4MTg1NWFmNDkxNTA1MDNkODA2MDAwODExNDYxMGNkNDU3NjA0MDUxOTE1MDYwMWYxOTYwM2YzZDAxMTY4MjAxNjA0MDUyM2Q4MjUyM2Q2MDAwNjAyMDg0MDEzZTYxMGNkOTU2NWI2MDYwOTE1MDViNTA5MTUwOTE1MDgzMTU4MDYxMGNlOTU3NTA4MTE1NWIxNTYxMGQ1YzU3NjA0MDUxN2YwOGMzNzlhMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwODE1MjYwMDQwMTgwODA2MDIwMDE4MjgxMDM4MjUyNjAxZTgxNTI2MDIwMDE4MDdmNDQ2NTZjNjU2NzYxNzQ2NTIwNzQ3MjYxNmU3MzY2NjU3MjIwNjM2MTZjNmMyMDY2NjE2OTZjNjU2NDIxMDAwMDgxNTI1MDYwMjAwMTkxNTA1MDYwNDA1MTgwOTEwMzkwZmQ1YjUwNTA1MDUwNTA1NmZlYTI2NTYyN2E3YTcyMzE1ODIwNjE5ZDU1NTI1Y2QxN2Y3MzliMGNiMjZkODZmN2QxOGYxNTVlYWM0NWY5NTM3MGQ5NjRkZWEzYzcwZGIxNGQ2ZTY0NzM2ZjZjNjM0MzAwMDUxMTAwMzI=","b64Record":"CiAIFiocCgwIARAMGgYIgK6ZpA8SDAgBEA8aBgiArpmkDxIwbGUVWPygtuHPVvcxzaC8W263NgP8fXQRfr0pT2S2M9w56VhVuHhndb0nAD94acTQGgwI7P7+rAYQ9d+fhAIiDwoJCLD+/qwGEPQSEgIYAiogw4PCrsOCwrfDg8K5dEY4w4LCrkrDg8KLw4PCkMODwo5SAA=="},{"b64Body":"Cg8KCQix/v6sBhD2EhICGAISAhgDGP7LzSwiAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjooBjgESCwjtzNmwBhCjqO0NGm0KIhIgpJzBFgvOWu+NPIlUIth34HNvot7bKBUoAcVL8j//ouYKIzohA2baUZt4OMwmE3BvgeupxfQdNQgbC7c0VDzLLjc5OZ8FCiISINYIoeX/4sUPAsRr/zrF2+lkmoqvo9Jhs98x538YK9GkIgxIZWxsbyBXb3JsZCEqADIA","b64Record":"CiUIFhoDGJMJKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjAHbM+54oWIiA3mW2vddpTsnVNa6LtJqUX5KXKojO3tG3gqAuaTJ8RaIcjiaoPp+EAaCwjt/v6sBhDhw8koIg8KCQix/v6sBhD2EhICGAIqIMODwq7DgsK3w4PCuXRGOMOCwq5Kw4PCi8ODwpDDg8KOUgA="},{"b64Body":"Cg8KCQix/v6sBhD6EhICGAISAhgDGM6Y0i8iAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjpoBkAYKAxiTCSKIBjYwODA2MDQwNTI2MTAxNzE4MDYxMDAxMzYwMDAzOTYwMDBmM2ZlNjA4MDYwNDA1MjYwMDQzNjEwNjEwMDI5NTc2MDAwMzU2MGUwMWM4MDYzOGRkOTgwNDMxNDYxMDAyYjU3ODA2M2FjZWY2MDM3MTQ2MTAwNTk1NzViMDA1YjYxMDA1NzYwMDQ4MDM2MDM2MDIwODExMDE1NjEwMDQxNTc2MDAwODBmZDViODEwMTkwODA4MDM1OTA2MDIwMDE5MDkyOTE5MDUwNTA1MDYxMDBhNzU2NWIwMDViNjEwMGE1NjAwNDgwMzYwMzYwNDA4MTEwMTU2MTAwNmY1NzYwMDA4MGZkNWI4MTAxOTA4MDgwMzU3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNjkwNjAyMDAxOTA5MjkxOTA4MDM1OTA2MDIwMDE5MDkyOTE5MDUwNTA1MDYxMDBmMTU2NWIwMDViMzM3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNjYxMDhmYzgyOTA4MTE1MDI5MDYwNDA1MTYwMDA2MDQwNTE4MDgzMDM4MTg1ODg4OGYxOTM1MDUwNTA1MDE1ODAxNTYxMDBlZDU3M2Q2MDAwODAzZTNkNjAwMGZkNWI1MDUwNTY1YjgxNzNmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmMTY2MTA4ZmM4MjkwODExNTAyOTA2MDQwNTE2MDAwNjA0MDUxODA4MzAzODE4NTg4ODhmMTkzNTA1MDUwNTAxNTgwMTU2MTAxMzc1NzNkNjAwMDgwM2UzZDYwMDBmZDViNTA1MDUwNTZmZWEyNjU2MjdhN2E3MjMxNTgyMDVlYjQwYmI2MjA4MzVmYjc5NjljZGQ3NWRlZDE4ODkyN2Q1YzkyN2Y5M2JhNjc1ZDY1YjljNTIzNDQ0MjQyODc2NDczNmY2YzYzNDMwMDA1MTEwMDMy","b64Record":"CiAIFiocCgwIARAMGgYIgK6ZpA8SDAgBEA8aBgiArpmkDxIw9c2UimoI2rp8M855YPAMSQkjniDzqguvNa9AbrZBirW4RsOMoBlU/W72679eT7fnGgwI7f7+rAYQ5rbrjQIiDwoJCLH+/qwGEPoSEgIYAiogw4PCrsOCwrfDg8K5dEY4w4LCrkrDg8KLw4PCkMODwo5SAA=="},{"b64Body":"Cg8KCQiy/v6sBhD8EhICGAISAhgDGJb7rp0CIgIIeDIgw4PCrsOCwrfDg8K5dEY4w4LCrkrDg8KLw4PCkMODwo5CSAoDGJMJGiISIOgdq2oRDyz4MKmeeCIbRG3YAjfPnuY/fC9cEDO1sXdrIJChDyiQTkIFCIDO2gNSAFoAagtjZWxsYXIgZG9vcg==","b64Record":"CiUIFiIDGJQJKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjD+HY2xv8kL7z0HQ3mk/V1GyhNThaBJODFe2StNUXmbwZA47E/9ysvIhPjtfAuSUdwaCwju/v6sBhCj1PkVIg8KCQiy/v6sBhD8EhICGAIqIMODwq7DgsK3w4PCuXRGOMOCwq5Kw4PCi8ODwpDDg8KOMMDZ4gZCpgUKAxiUCRLxAmCAYEBSYAQ2EGEAKVdgADVg4ByAY43ZgEMUYQArV4BjrO9gNxRhAFlXWwBbYQBXYASANgNgIIEQFWEAQVdgAID9W4EBkICANZBgIAGQkpGQUFBQYQCnVlsAW2EApWAEgDYDYECBEBVhAG9XYACA/VuBAZCAgDVz//////////////////////////8WkGAgAZCSkZCANZBgIAGQkpGQUFBQYQDxVlsAWzNz//////////////////////////8WYQj8gpCBFQKQYEBRYABgQFGAgwOBhYiI8ZNQUFBQFYAVYQDtVz1gAIA+PWAA/VtQUFZbgXP//////////////////////////xZhCPyCkIEVApBgQFFgAGBAUYCDA4GFiIjxk1BQUFAVgBVhATdXPWAAgD49YAD9W1BQUFb+omVienpyMVggXrQLtiCDX7eWnN113tGIkn1ckn+TumddZbnFI0RCQodkc29sY0MABREAMiKAAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAowJoMOgMYlAlKFgoUAAAAAAAAAAAAAAAAAAAAAAAABJRyBwoDGJQJEAFSIQoJCgIYAhCfz8YNCgkKAhhiEICzxQ0KCQoDGJQJEKCcAQ=="},{"b64Body":"Cg8KCQiy/v6sBhD+EhICGAISAhgDGJb7rp0CIgIIeDIgw4PCrsOCwrfDg8K5dEY4w4LCrkrDg8KLw4PCkMODwo5CSAoDGJMJGiISIN9bgOf710npv/HIMnb4ykKuudCmIf4ZISiHi9wmr7deIJChDyiQTkIFCIDO2gNSAFoAagtjZWxsYXIgZG9vcg==","b64Record":"CiUIFiIDGJUJKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjD1vHaQgmYsqJ4i/Bg7ZLnJBO+SGXnT+54AoS6IHLU5zs9QyyWPZScTFzluy6pC1ZcaDAju/v6sBhD2wIWXAiIPCgkIsv7+rAYQ/hISAhgCKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjjDA2eIGQqYFCgMYlQkS8QJggGBAUmAENhBhAClXYAA1YOAcgGON2YBDFGEAK1eAY6zvYDcUYQBZV1sAW2EAV2AEgDYDYCCBEBVhAEFXYACA/VuBAZCAgDWQYCABkJKRkFBQUGEAp1ZbAFthAKVgBIA2A2BAgRAVYQBvV2AAgP1bgQGQgIA1c///////////////////////////FpBgIAGQkpGQgDWQYCABkJKRkFBQUGEA8VZbAFszc///////////////////////////FmEI/IKQgRUCkGBAUWAAYEBRgIMDgYWIiPGTUFBQUBWAFWEA7Vc9YACAPj1gAP1bUFBWW4Fz//////////////////////////8WYQj8gpCBFQKQYEBRYABgQFGAgwOBhYiI8ZNQUFBQFYAVYQE3Vz1gAIA+PWAA/VtQUFBW/qJlYnp6cjFYIF60C7Ygg1+3lpzddd7RiJJ9XJJ/k7pnXWW5xSNEQkKHZHNvbGNDAAURADIigAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKMCaDDoDGJUJShYKFAAAAAAAAAAAAAAAAAAAAAAAAASVcgcKAxiVCRABUiEKCQoCGAIQn8/GDQoJCgIYYhCAs8UNCgkKAxiVCRCgnAE="},{"b64Body":"Cg8KCQiz/v6sBhCAExICGAISAhgDGKqQBSICCHgyIMODwq7DgsK3w4PCuXRGOMOCwq5Kw4PCi8ODwpDDg8KOchkKFwoJCgIYAhD/2cQJCgoKAxiQCRCA2sQJ","b64Record":"CiAIFiocCgwIARAMGgYIgK6ZpA8SDAgBEA8aBgiArpmkDxIwJsRegmAOi4U6FQNtW1NaU//OkG7cHiR9xiy8jZt7CjHTN3sAvz21PfneBUnag007GgsI7/7+rAYQjJq/HyIPCgkIs/7+rAYQgBMSAhgCKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjlIXCgkKAhgCEP/ZxAkKCgoDGJAJEIDaxAk="},{"b64Body":"Cg8KCQiz/v6sBhCIExICGAISAhgDGNWAv6ACIgIIeDIgw4PCrsOCwrfDg8K5dEY4w4LCrkrDg8KLw4PCkMODwo5CigEKAxiSCRoiEiBGM1GHC5vkDX/m+zf4TdxIKS1nfZuWAp34kjlC5LScqiCQoQ8okE5CBQiAztoDSkAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAElAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAASVUgBaAGoLY2VsbGFyIGRvb3I=","b64Record":"CiUIFiIDGJYJKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjCDcQbsyL1RwRyMdiiov9ANY3vshegbuB270UwlGT+SyTMw8IJxqqBy6jse1Abt0HMaDAjv/v6sBhDimdOgAiIPCgkIs/7+rAYQiBMSAhgCKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjjDA2eIGQs0dCgMYlgkSmBtggGBAUmAENhBhAD9XYAA1YOAcgGNVVdfkFGEAQVeAY17PKYoUYQDPV4BjncfbEBRhAP1XgGOpo6Y1FGEBS1dbAFthAM1gBIA2A2CAgRAVYQBXV2AAgP1bgQGQgIA1c///////////////////////////FpBgIAGQkpGQgDVz//////////////////////////8WkGAgAZCSkZCANXP//////////////////////////xaQYCABkJKRkIA1kGAgAZCSkZBQUFBhAXlWWwBbYQD7YASANgNgIIEQFWEA5VdgAID9W4EBkICANZBgIAGQkpGQUFBQYQcTVlsAW2EBSWAEgDYDYECBEBVhARNXYACA/VuBAZCAgDVz//////////////////////////8WkGAgAZCSkZCANZBgIAGQkpGQUFBQYQgvVlsAW2EBd2AEgDYDYCCBEBVhAWFXYACA/VuBAZCAgDWQYCABkJKRkFBQUGEKD1ZbAFuDc///////////////////////////FmEI/IKQgRUCkGBAUWAAYEBRgIMDgYWIiPGTUFBQUBWAFWEBv1c9YACAPj1gAP1bUGAAgJBUkGEBAAqQBHP//////////////////////////xZz//////////////////////////8WY6zvYDeFYAKEgWECCFf+WwRgQFGDY/////8WYOAbgVJgBAGAg3P//////////////////////////xZz//////////////////////////8WgVJgIAGCgVJgIAGSUFBQYABgQFGAgwOBYACHgDsVgBVhAnJXYACA/VtQWvEVgBVhAoZXPWAAgD49YAD9W1BQUFBgAWAAkFSQYQEACpAEc///////////////////////////FnP//////////////////////////xZjrO9gN4VgAoSBYQLTV/5bBGBAUYNj/////xZg4BuBUmAEAYCDc///////////////////////////FnP//////////////////////////xaBUmAgAYKBUmAgAZJQUFBgAGBAUYCDA4FgAIeAOxWAFWEDPVdgAID9W1Ba8RWAFWEDUVc9YACAPj1gAP1bUFBQUIJz//////////////////////////8WYQj8gpCBFQKQYEBRYABgQFGAgwOBhYiI8ZNQUFBQFYAVYQObVz1gAIA+PWAA/VtQYACAkFSQYQEACpAEc///////////////////////////FnP//////////////////////////xZjrO9gN4RgAoSBYQPkV/5bBGBAUYNj/////xZg4BuBUmAEAYCDc///////////////////////////FnP//////////////////////////xaBUmAgAYKBUmAgAZJQUFBgAGBAUYCDA4FgAIeAOxWAFWEETldgAID9W1Ba8RWAFWEEYlc9YACAPj1gAP1bUFBQUGABYACQVJBhAQAKkARz//////////////////////////8Wc///////////////////////////FmOs72A3hGAChIFhBK9X/lsEYEBRg2P/////FmDgG4FSYAQBgINz//////////////////////////8Wc///////////////////////////FoFSYCABgoFSYCABklBQUGAAYEBRgIMDgWAAh4A7FYAVYQUZV2AAgP1bUFrxFYAVYQUtVz1gAIA+PWAA/VtQUFBQgXP//////////////////////////xZhCPyCkIEVApBgQFFgAGBAUYCDA4GFiIjxk1BQUFAVgBVhBXdXPWAAgD49YAD9W1BgAICQVJBhAQAKkARz//////////////////////////8Wc///////////////////////////FmOs72A3g2AChIFhBcBX/lsEYEBRg2P/////FmDgG4FSYAQBgINz//////////////////////////8Wc///////////////////////////FoFSYCABgoFSYCABklBQUGAAYEBRgIMDgWAAh4A7FYAVYQYqV2AAgP1bUFrxFYAVYQY+Vz1gAIA+PWAA/VtQUFBQYAFgAJBUkGEBAAqQBHP//////////////////////////xZz//////////////////////////8WY6zvYDeDYAKEgWEGi1f+WwRgQFGDY/////8WYOAbgVJgBAGAg3P//////////////////////////xZz//////////////////////////8WgVJgIAGCgVJgIAGSUFBQYABgQFGAgwOBYACHgDsVgBVhBvVXYACA/VtQWvEVgBVhBwlXPWAAgD49YAD9W1BQUFBQUFBQVltgAICQVJBhAQAKkARz//////////////////////////8Wc///////////////////////////FmON2YBDgmBAUYJj/////xZg4BuBUmAEAYCCgVJgIAGRUFBgAGBAUYCDA4FgAIeAOxWAFWEHh1dgAID9W1Ba8RWAFWEHm1c9YACAPj1gAP1bUFBQUGABYACQVJBhAQAKkARz//////////////////////////8Wc///////////////////////////FmON2YBDgmBAUYJj/////xZg4BuBUmAEAYCCgVJgIAGRUFBgAGBAUYCDA4FgAIeAOxWAFWEIFFdgAID9W1Ba8RWAFWEIKFc9YACAPj1gAP1bUFBQUFBWW4Fz//////////////////////////8WYQj8gpCBFQKQYEBRYABgQFGAgwOBhYiI8ZNQUFBQFYAVYQh1Vz1gAIA+PWAA/VtQYACAkFSQYQEACpAEc///////////////////////////FnP//////////////////////////xZjrO9gN4NgAoSBYQi+V/5bBGBAUYNj/////xZg4BuBUmAEAYCDc///////////////////////////FnP//////////////////////////xaBUmAgAYKBUmAgAZJQUFBgAGBAUYCDA4FgAIeAOxWAFWEJKFdgAID9W1Ba8RWAFWEJPFc9YACAPj1gAP1bUFBQUGABYACQVJBhAQAKkARz//////////////////////////8Wc///////////////////////////FmOs72A3g2AChIFhCYlX/lsEYEBRg2P/////FmDgG4FSYAQBgINz//////////////////////////8Wc///////////////////////////FoFSYCABgoFSYCABklBQUGAAYEBRgIMDgWAAh4A7FYAVYQnzV2AAgP1bUFrxFYAVYQoHVz1gAIA+PWAA/VtQUFBQUFBWW2AAYGBgAICQVJBhAQAKkARz//////////////////////////8Wc///////////////////////////FoNgQFFgJAGAgoFSYCABkVBQYEBRYCCBgwMDgVKQYEBSf43ZgEMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAe/////////////////////////////////////8ZFmAgggGAUXv/////////////////////////////////////g4GDFheDUlBQUFBgQFGAgoBRkGAgAZCAg4NbYCCDEGELDFeAUYJSYCCCAZFQYCCBAZBQYCCDA5JQYQrpVltgAYNgIANhAQAKA4AZglEWgYRRFoCCF4VSUFBQUFBQkFABkVBQYABgQFGAgwOBhVr0kVBQPYBgAIEUYQtsV2BAUZFQYB8ZYD89ARaCAWBAUj2CUj1gAGAghAE+YQtxVltgYJFQW1CRUJFQYABgYGABYACQVJBhAQAKkARz//////////////////////////8Wc///////////////////////////FoVgQFFgJAGAgoFSYCABkVBQYEBRYCCBgwMDgVKQYEBSf43ZgEMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAe/////////////////////////////////////8ZFmAgggGAUXv/////////////////////////////////////g4GDFheDUlBQUFBgQFGAgoBRkGAgAZCAg4NbYCCDEGEMdFeAUYJSYCCCAZFQYCCBAZBQYCCDA5JQYQxRVltgAYNgIANhAQAKA4AZglEWgYRRFoCCF4VSUFBQUFBQkFABkVBQYABgQFGAgwOBhVr0kVBQPYBgAIEUYQzUV2BAUZFQYB8ZYD89ARaCAWBAUj2CUj1gAGAghAE+YQzZVltgYJFQW1CRUJFQgxWAYQzpV1CBFVsVYQ1cV2BAUX8Iw3mgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIFSYAQBgIBgIAGCgQOCUmAegVJgIAGAf0RlbGVnYXRlIHRyYW5zZmVyIGNhbGwgZmFpbGVkIQAAgVJQYCABkVBQYEBRgJEDkP1bUFBQUFBW/qJlYnp6cjFYIGGdVVJc0X9zmwyybYb30Y8VXqxF+VNw2WTeo8cNsU1uZHNvbGNDAAURADIigAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKMCaDDoDGJYJShYKFAAAAAAAAAAAAAAAAAAAAAAAAASWcgcKAxiWCRABUiEKCQoCGAIQn8/GDQoJCgIYYhCAs8UNCgkKAxiWCRCgnAE="},{"b64Body":"ChAKCQi0/v6sBhCKExIDGJAJEgIYAxjgrLEDIgIIeDIgw4PCrsOCwrfDg8K5dEY4w4LCrkrDg8KLw4PCkMODwo46LwoDGJYJEKCNBiIkqaOmNQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABk","b64Record":"CiUIFiIDGJYJKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjDjVkGWQFih8G79tTmxrfdxMmS+4V3IaU6eTMZYeYptnh1xh2KbbfpvaF15y2BviRQaCwjw/v6sBhDllPsoIhAKCQi0/v6sBhCKExIDGJAJKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjjCA19oCOowCCgMYlgkigAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKIDxBFIhCgkKAhhiEICutQUKCgoDGJAJEO+qtQUKCAoDGJYJEI8D"}]},"whitelistingAliasedContract":{"placeholderNum":1181,"encodedItems":[{"b64Body":"Cg8KCQjC/v6sBhCeFBICGAISAhgDGL/0xiwiAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjooBgQESDAj+zNmwBhC+yu+KAxptCiISILfTmFbKPVx7nH1ht+PCZARbe57BBzln/Cl+NGxnz3JVCiM6IQNn4DEqFA7fN70+V5o1UmC2XpLkirrah3FSyKqKWA6VHAoiEiB9rD2YPfjQbi13T0Yx7Kdux6J1g7FuWLo6dvfTYJmtayoAMgA=","b64Record":"CiUIFhoDGJ4JKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjBNLqBGXVZcK0pkxyvP0vWEi3b1Eobq+tgw9hkVM4KzphGPmchuYLf9yPyfUHQ/1SsaDAj+/v6sBhDNmqKTAyIPCgkIwv7+rAYQnhQSAhgCKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjlIA"},{"b64Body":"Cg8KCQjD/v6sBhCiFBICGAISAhgDGNHBqDMiAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjpoB2gwKAxieCSLSDDYwODA2MDQwNTIzNDgwMTU2MTAwMTA1NzYwMDA4MGZkNWI1MDYxMDMwOTgwNjEwMDIwNjAwMDM5NjAwMGYzZmU2MDgwNjA0MDUyMzQ4MDE1NjEwMDEwNTc2MDAwODBmZDViNTA2MDA0MzYxMDYxMDA0YzU3NjAwMDM1NjBlMDFjODA2MzBhZDhmNGExMTQ2MTAwNTE1NzgwNjMzYWYzMmFiZjE0NjEwMDZmNTc4MDYzOWIxOTI1MWExNDYxMDA5ZjU3ODA2M2U0MzI1MmQ3MTQ2MTAwY2Y1NzViNjAwMDgwZmQ1YjYxMDA1OTYxMDBlYjU2NWI2MDQwNTE2MTAwNjY5MTkwNjEwMjVlNTY1YjYwNDA1MTgwOTEwMzkwZjM1YjYxMDA4OTYwMDQ4MDM2MDM4MTAxOTA2MTAwODQ5MTkwNjEwMjIyNTY1YjYxMDEzZTU2NWI2MDQwNTE2MTAwOTY5MTkwNjEwMjVlNTY1YjYwNDA1MTgwOTEwMzkwZjM1YjYxMDBiOTYwMDQ4MDM2MDM4MTAxOTA2MTAwYjQ5MTkwNjEwMjIyNTY1YjYxMDE5MzU2NWI2MDQwNTE2MTAwYzY5MTkwNjEwMjVlNTY1YjYwNDA1MTgwOTEwMzkwZjM1YjYxMDBlOTYwMDQ4MDM2MDM4MTAxOTA2MTAwZTQ5MTkwNjEwMjIyNTY1YjYxMDFiMzU2NWIwMDViNjAwMDgwNjAwMDMzNzNmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmMTY3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNjgxNTI2MDIwMDE5MDgxNTI2MDIwMDE2MDAwMjA2MDAwOTA1NDkwNjEwMTAwMGE5MDA0NjBmZjE2OTA1MDkwNTY1YjYwMDA4MDYwMDA4MzczZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjE2NzNmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmMTY4MTUyNjAyMDAxOTA4MTUyNjAyMDAxNjAwMDIwNjAwMDkwNTQ5MDYxMDEwMDBhOTAwNDYwZmYxNjkwNTA5MTkwNTA1NjViNjAwMDYwMjA1MjgwNjAwMDUyNjA0MDYwMDAyMDYwMDA5MTUwNTQ5MDYxMDEwMDBhOTAwNDYwZmYxNjgxNTY1YjYwMDE2MDAwODA4MzczZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjE2NzNmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmMTY4MTUyNjAyMDAxOTA4MTUyNjAyMDAxNjAwMDIwNjAwMDYxMDEwMDBhODE1NDgxNjBmZjAyMTkxNjkwODMxNTE1MDIxNzkwNTU1MDUwNTY1YjYwMDA4MTM1OTA1MDYxMDIxYzgxNjEwMmJjNTY1YjkyOTE1MDUwNTY1YjYwMDA2MDIwODI4NDAzMTIxNTYxMDIzODU3NjEwMjM3NjEwMmI3NTY1YjViNjAwMDYxMDI0Njg0ODI4NTAxNjEwMjBkNTY1YjkxNTA1MDkyOTE1MDUwNTY1YjYxMDI1ODgxNjEwMjhiNTY1YjgyNTI1MDUwNTY1YjYwMDA2MDIwODIwMTkwNTA2MTAyNzM2MDAwODMwMTg0NjEwMjRmNTY1YjkyOTE1MDUwNTY1YjYwMDA2MTAyODQ4MjYxMDI5NzU2NWI5MDUwOTE5MDUwNTY1YjYwMDA4MTE1MTU5MDUwOTE5MDUwNTY1YjYwMDA3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmY4MjE2OTA1MDkxOTA1MDU2NWI2MDAwODBmZDViNjEwMmM1ODE2MTAyNzk1NjViODExNDYxMDJkMDU3NjAwMDgwZmQ1YjUwNTZmZWEyNjQ2OTcwNjY3MzU4MjIxMjIwNmE1ZmJjMDAxMGQwY2JiMzQ1YjdkZGRkMzExYTI2M2ViYTUxZGM5ODNiNjQwMDJmNzYxNTM0ZWEyMjY0Nzc2MDY0NzM2ZjZjNjM0MzAwMDgwNzAwMzM=","b64Record":"CiAIFiocCgwIARAMGgYIgK6ZpA8SDAgBEA8aBgiArpmkDxIws1Tn88J4J8ui/il59S+qzRxaI7D9jmXDpJoKMUlzRUV0F51x3UDxD460Yx3ecAREGgwI//7+rAYQhKWLuAEiDwoJCMP+/qwGEKIUEgIYAiogw4PCrsOCwrfDg8K5dEY4w4LCrkrDg8KLw4PCkMODwo5SAA=="},{"b64Body":"Cg8KCQjD/v6sBhCkFBICGAISAhgDGL/0xiwiAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjooBgQESDAj/zNmwBhDJ/dWTAxptCiISIA3JsW7GuO7v54X7xEvJn/ms2adHgCPpbbo6ettlO7WsCiM6IQOsiBI9i2tU658BXA9OSArO+CojTDz8Hz1PqsiCMTbLqQoiEiDk3MrGN219Hxv1Ur206Js3aYIux3cYlf7kXMvtwNTJJCoAMgA=","b64Record":"CiUIFhoDGJ8JKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjBdsKWlbuQkIiVkvpVWjScCd+bvBAdHLp11SxxatptV5qMOvLuowYhhmxLHRQLAsVYaDAj//v6sBhC4u+KcAyIPCgkIw/7+rAYQpBQSAhgCKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjlIA"},{"b64Body":"Cg8KCQjE/v6sBhCoFBICGAISAhgDGKOkijoiAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjpoB6hgKAxifCSLiGDYwODA2MDQwNTIzNDgwMTU2MTAwMTA1NzYwMDA4MGZkNWI1MDYwMDA4MDYwNDA1MTgwNjAyMDAxNjEwMDI0OTA2MTAwYjc1NjViNjAyMDgyMDE4MTAzODI1MjYwMWYxOTYwMWY4MjAxMTY2MDQwNTI1MDkwNTA2MDAwNjA0MDUxNjAyMDAxNjEwMDRiOTA2MTAwZTc1NjViNjA0MDUxNjAyMDgxODMwMzAzODE1MjkwNjA0MDUyODA1MTkwNjAyMDAxMjA5MDUwODA4MjUxNjAyMDg0MDE2MDAwZjU5MjUwODI2MDAwODA2MTAxMDAwYTgxNTQ4MTczZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjAyMTkxNjkwODM3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNjAyMTc5MDU1NTA1MDUwNTA2MTAxMzA1NjViNjEwMjVlODA2MTAzZDM4MzM5MDE5MDU2NWI2MDAwNjEwMGQxNjAwNjgzNjEwMGZjNTY1YjkxNTA2MTAwZGM4MjYxMDEwNzU2NWI2MDA2ODIwMTkwNTA5MTkwNTA1NjViNjAwMDYxMDBmMjgyNjEwMGM0NTY1YjkxNTA4MTkwNTA5MTkwNTA1NjViNjAwMDgxOTA1MDkyOTE1MDUwNTY1YjdmNzA2NTcyNmQ2OTc0MDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDYwMDA4MjAxNTI1MDU2NWI2MTAyOTQ4MDYxMDEzZjYwMDAzOTYwMDBmM2ZlNjA4MDYwNDA1MjM0ODAxNTYxMDAxMDU3NjAwMDgwZmQ1YjUwNjAwNDM2MTA2MTAwMmI1NzYwMDAzNTYwZTAxYzgwNjMzYWYzMmFiZjE0NjEwMDMwNTc1YjYwMDA4MGZkNWI2MTAwNGE2MDA0ODAzNjAzODEwMTkwNjEwMDQ1OTE5MDYxMDEzZjU2NWI2MTAwNjA1NjViNjA0MDUxNjEwMDU3OTE5MDYxMDFkMjU2NWI2MDQwNTE4MDkxMDM5MGYzNWI2MDAwODA2MDAwOTA1NDkwNjEwMTAwMGE5MDA0NzNmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmMTY3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNjYzM2FmMzJhYmY4MzYwNDA1MTgyNjNmZmZmZmZmZjE2NjBlMDFiODE1MjYwMDQwMTYxMDBiYzkxOTA2MTAxYjc1NjViNjAyMDYwNDA1MTgwODMwMzgxNjAwMDg3ODAzYjE1ODAxNTYxMDBkNjU3NjAwMDgwZmQ1YjUwNWFmMTE1ODAxNTYxMDBlYTU3M2Q2MDAwODAzZTNkNjAwMGZkNWI1MDUwNTA1MDYwNDA1MTNkNjAxZjE5NjAxZjgyMDExNjgyMDE4MDYwNDA1MjUwODEwMTkwNjEwMTBlOTE5MDYxMDE2YzU2NWI5MDUwOTE5MDUwNTY1YjYwMDA4MTM1OTA1MDYxMDEyNDgxNjEwMjMwNTY1YjkyOTE1MDUwNTY1YjYwMDA4MTUxOTA1MDYxMDEzOTgxNjEwMjQ3NTY1YjkyOTE1MDUwNTY1YjYwMDA2MDIwODI4NDAzMTIxNTYxMDE1NTU3NjEwMTU0NjEwMjJiNTY1YjViNjAwMDYxMDE2Mzg0ODI4NTAxNjEwMTE1NTY1YjkxNTA1MDkyOTE1MDUwNTY1YjYwMDA2MDIwODI4NDAzMTIxNTYxMDE4MjU3NjEwMTgxNjEwMjJiNTY1YjViNjAwMDYxMDE5MDg0ODI4NTAxNjEwMTJhNTY1YjkxNTA1MDkyOTE1MDUwNTY1YjYxMDFhMjgxNjEwMWVkNTY1YjgyNTI1MDUwNTY1YjYxMDFiMTgxNjEwMWZmNTY1YjgyNTI1MDUwNTY1YjYwMDA2MDIwODIwMTkwNTA2MTAxY2M2MDAwODMwMTg0NjEwMTk5NTY1YjkyOTE1MDUwNTY1YjYwMDA2MDIwODIwMTkwNTA2MTAxZTc2MDAwODMwMTg0NjEwMWE4NTY1YjkyOTE1MDUwNTY1YjYwMDA2MTAxZjg4MjYxMDIwYjU2NWI5MDUwOTE5MDUwNTY1YjYwMDA4MTE1MTU5MDUwOTE5MDUwNTY1YjYwMDA3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmY4MjE2OTA1MDkxOTA1MDU2NWI2MDAwODBmZDViNjEwMjM5ODE2MTAxZWQ1NjViODExNDYxMDI0NDU3NjAwMDgwZmQ1YjUwNTY1YjYxMDI1MDgxNjEwMWZmNTY1YjgxMTQ2MTAyNWI1NzYwMDA4MGZkNWI1MDU2ZmVhMjY0Njk3MDY2NzM1ODIyMTIyMDFjNjJhZWJlM2M5ZDU5NTM1ZjYzMWI3MWQ5MjZlZGZhYzY3YzViYjcxN2Q4Mjc2Y2M3MjMzYTA0ODYwNzI2YzY2NDczNmY2YzYzNDMwMDA4MDcwMDMzNjA4MDYwNDA1MjM0ODAxNTYxMDAxMDU3NjAwMDgwZmQ1YjUwNjEwMjNlODA2MTAwMjA2MDAwMzk2MDAwZjNmZTYwODA2MDQwNTIzNDgwMTU2MTAwMTA1NzYwMDA4MGZkNWI1MDYwMDQzNjEwNjEwMDJiNTc2MDAwMzU2MGUwMWM4MDYzM2FmMzJhYmYxNDYxMDAzMDU3NWI2MDAwODBmZDViNjEwMDRhNjAwNDgwMzYwMzgxMDE5MDYxMDA0NTkxOTA2MTAxMTM1NjViNjEwMDYwNTY1YjYwNDA1MTYxMDA1NzkxOTA2MTAxN2M1NjViNjA0MDUxODA5MTAzOTBmMzViNjAwMDgxNzNmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmMTY2MzBhZDhmNGExNjA0MDUxODE2M2ZmZmZmZmZmMTY2MGUwMWI4MTUyNjAwNDAxNjAyMDYwNDA1MTgwODMwMzgxNjAwMDg3ODAzYjE1ODAxNTYxMDBhYTU3NjAwMDgwZmQ1YjUwNWFmMTE1ODAxNTYxMDBiZTU3M2Q2MDAwODAzZTNkNjAwMGZkNWI1MDUwNTA1MDYwNDA1MTNkNjAxZjE5NjAxZjgyMDExNjgyMDE4MDYwNDA1MjUwODEwMTkwNjEwMGUyOTE5MDYxMDE0MDU2NWI5MDUwOTE5MDUwNTY1YjYwMDA4MTM1OTA1MDYxMDBmODgxNjEwMWRhNTY1YjkyOTE1MDUwNTY1YjYwMDA4MTUxOTA1MDYxMDEwZDgxNjEwMWYxNTY1YjkyOTE1MDUwNTY1YjYwMDA2MDIwODI4NDAzMTIxNTYxMDEyOTU3NjEwMTI4NjEwMWQ1NTY1YjViNjAwMDYxMDEzNzg0ODI4NTAxNjEwMGU5NTY1YjkxNTA1MDkyOTE1MDUwNTY1YjYwMDA2MDIwODI4NDAzMTIxNTYxMDE1NjU3NjEwMTU1NjEwMWQ1NTY1YjViNjAwMDYxMDE2NDg0ODI4NTAxNjEwMGZlNTY1YjkxNTA1MDkyOTE1MDUwNTY1YjYxMDE3NjgxNjEwMWE5NTY1YjgyNTI1MDUwNTY1YjYwMDA2MDIwODIwMTkwNTA2MTAxOTE2MDAwODMwMTg0NjEwMTZkNTY1YjkyOTE1MDUwNTY1YjYwMDA2MTAxYTI4MjYxMDFiNTU2NWI5MDUwOTE5MDUwNTY1YjYwMDA4MTE1MTU5MDUwOTE5MDUwNTY1YjYwMDA3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmY4MjE2OTA1MDkxOTA1MDU2NWI2MDAwODBmZDViNjEwMWUzODE2MTAxOTc1NjViODExNDYxMDFlZTU3NjAwMDgwZmQ1YjUwNTY1YjYxMDFmYTgxNjEwMWE5NTY1YjgxMTQ2MTAyMDU1NzYwMDA4MGZkNWI1MDU2ZmVhMjY0Njk3MDY2NzM1ODIyMTIyMGU0NjQ1ODUzYTQyMDhlMTUzZDFiYjgxYWI1NmZiZDVjYzZjOTlmNWNjZjA4MWY5OWFjNGFmNzA4ZmNiNWE4ZTY2NDczNmY2YzYzNDMwMDA4MDcwMDMz","b64Record":"CiAIFiocCgwIARAMGgYIgK6ZpA8SDAgBEA8aBgiArpmkDxIwe8Td+HKEeTCTCk6WDD4BKEkfzpJsYJdXnjSMtAQOOexHPz8x0jQsGIifx4EDM7CDGgwIgP/+rAYQ3MzIwQEiDwoJCMT+/qwGEKgUEgIYAiogw4PCrsOCwrfDg8K5dEY4w4LCrkrDg8KLw4PCkMODwo5SAA=="},{"b64Body":"Cg8KCQjE/v6sBhCqFBICGAISAhgDGJb7rp0CIgIIeDIgw4PCrsOCwrfDg8K5dEY4w4LCrkrDg8KLw4PCkMODwo5CRQoDGJ4JGiISIJiFUToSzMphjJwYdtD+25B7yu5P9/tIO0MDjuWoG1ZtIICJekIFCIDO2gNSAFoAagtjZWxsYXIgZG9vcg==","b64Record":"CiUIFiIDGKAJKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjCSNgrRn+YgTbx2GohHLbSiIRablEvt2qAtECUbQTBLAH0oUDDnUO9YGa72Mf+DSKQaDAiA//6sBhDY7bSmAyIPCgkIxP7+rAYQqhQSAhgCKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjjCAzJU2Qr4ICgMYoAkSiQZggGBAUjSAFWEAEFdgAID9W1BgBDYQYQBMV2AANWDgHIBjCtj0oRRhAFFXgGM68yq/FGEAb1eAY5sZJRoUYQCfV4Bj5DJS1xRhAM9XW2AAgP1bYQBZYQDrVltgQFFhAGaRkGECXlZbYEBRgJEDkPNbYQCJYASANgOBAZBhAISRkGECIlZbYQE+VltgQFFhAJaRkGECXlZbYEBRgJEDkPNbYQC5YASANgOBAZBhALSRkGECIlZbYQGTVltgQFFhAMaRkGECXlZbYEBRgJEDkPNbYQDpYASANgOBAZBhAOSRkGECIlZbYQGzVlsAW2AAgGAAM3P//////////////////////////xZz//////////////////////////8WgVJgIAGQgVJgIAFgACBgAJBUkGEBAAqQBGD/FpBQkFZbYACAYACDc///////////////////////////FnP//////////////////////////xaBUmAgAZCBUmAgAWAAIGAAkFSQYQEACpAEYP8WkFCRkFBWW2AAYCBSgGAAUmBAYAAgYACRUFSQYQEACpAEYP8WgVZbYAFgAICDc///////////////////////////FnP//////////////////////////xaBUmAgAZCBUmAgAWAAIGAAYQEACoFUgWD/AhkWkIMVFQIXkFVQUFZbYACBNZBQYQIcgWECvFZbkpFQUFZbYABgIIKEAxIVYQI4V2ECN2ECt1ZbW2AAYQJGhIKFAWECDVZbkVBQkpFQUFZbYQJYgWECi1ZbglJQUFZbYABgIIIBkFBhAnNgAIMBhGECT1ZbkpFQUFZbYABhAoSCYQKXVluQUJGQUFZbYACBFRWQUJGQUFZbYABz//////////////////////////+CFpBQkZBQVltgAID9W2ECxYFhAnlWW4EUYQLQV2AAgP1bUFb+omRpcGZzWCISIGpfvAAQ0MuzRbfd3TEaJj66UdyYO2QAL3YVNOoiZHdgZHNvbGNDAAgHADMigAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKIDUYToDGKAJShYKFAAAAAAAAAAAAAAAAAAAAAAAAASgcgcKAxigCRABUhYKCQoCGAIQ/5erbAoJCgIYYhCAmKts"},{"b64Body":"Cg8KCQjF/v6sBhCsFBICGAISAhgDGJb7rp0CIgIIeDIgw4PCrsOCwrfDg8K5dEY4w4LCrkrDg8KLw4PCkMODwo5CRQoDGJ8JGiISIHWZQ8kz0OCsPweppYeVF5S7t5Q/Km0J5BHEnu6tHr+5IICJekIFCIDO2gNSAFoAagtjZWxsYXIgZG9vcg==","b64Record":"CiUIFiIDGKEJKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjCrYUVoxEIkWrsGb2RHTi4s6XnpJDirelR2w6/LX67qZiNGyaOOwXC+USxPIUslLisaDAiB//6sBhDqpq2uASIPCgkIxf7+rAYQrBQSAhgCKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjjCAzJU2QtcHCgMYoQkSlAVggGBAUjSAFWEAEFdgAID9W1BgBDYQYQArV2AANWDgHIBjOvMqvxRhADBXW2AAgP1bYQBKYASANgOBAZBhAEWRkGEBP1ZbYQBgVltgQFFhAFeRkGEB0lZbYEBRgJEDkPNbYACAYACQVJBhAQAKkARz//////////////////////////8Wc///////////////////////////FmM68yq/g2BAUYJj/////xZg4BuBUmAEAWEAvJGQYQG3VltgIGBAUYCDA4FgAIeAOxWAFWEA1ldgAID9W1Ba8RWAFWEA6lc9YACAPj1gAP1bUFBQUGBAUT1gHxlgH4IBFoIBgGBAUlCBAZBhAQ6RkGEBbFZbkFCRkFBWW2AAgTWQUGEBJIFhAjBWW5KRUFBWW2AAgVGQUGEBOYFhAkdWW5KRUFBWW2AAYCCChAMSFWEBVVdhAVRhAitWW1tgAGEBY4SChQFhARVWW5FQUJKRUFBWW2AAYCCChAMSFWEBgldhAYFhAitWW1tgAGEBkISChQFhASpWW5FQUJKRUFBWW2EBooFhAe1WW4JSUFBWW2EBsYFhAf9WW4JSUFBWW2AAYCCCAZBQYQHMYACDAYRhAZlWW5KRUFBWW2AAYCCCAZBQYQHnYACDAYRhAahWW5KRUFBWW2AAYQH4gmECC1ZbkFCRkFBWW2AAgRUVkFCRkFBWW2AAc///////////////////////////ghaQUJGQUFZbYACA/VthAjmBYQHtVluBFGECRFdgAID9W1BWW2ECUIFhAf9WW4EUYQJbV2AAgP1bUFb+omRpcGZzWCISIBxirr48nVlTX2Mbcdkm7frGfFu3F9gnbMcjOgSGBybGZHNvbGNDAAgHADMigAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKIDUYToDGKEJOgMYoglKFgoUAAAAAAAAAAAAAAAAAAAAAAAABKFyBwoDGKEJEAJyBwoDGKIJEAFSFgoJCgIYAhD/l6tsCgkKAhhiEICYq2w="},{"b64Body":"ChEKCQjF/v6sBhCsFBICGAIgAUI4GiISIHWZQ8kz0OCsPweppYeVF5S7t5Q/Km0J5BHEnu6tHr+5QgUIgM7aA2oLY2VsbGFyIGRvb3I=","b64Record":"CgcIFiIDGKIJEjBXJWNCGb3t22nLuuUZYUO9YB2yD9BWnVrvJgKLdzqbNObcSRo+V4PV7l3wq8qilZkaDAiB//6sBhDrpq2uASIRCgkIxf7+rAYQrBQSAhgCIAFCHQoDGKIJShYKFLLmJaHxTbIdUZogrV9kHfHOgEYTUgB6DAiB//6sBhDqpq2uAQ=="},{"b64Body":"Cg8KCQjF/v6sBhCyFBICGAISAhgDGOCssQMiAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjjovCgMYoAkQoI0GIiTkMlLXAAAAAAAAAAAAAAAAsuYlofFNsh1RmiCtX2Qd8c6ARhM=","b64Record":"CiUIFiIDGKAJKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjCuMXlJrH+hdQkAOGzaVmgA6SbfmsAo+FjKjzlBEes5q28yRtmPFnuzTkwVJXJm0fIaDAiB//6sBhCI4YCwAyIPCgkIxf7+rAYQshQSAhgCKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjjCA19oCOowCCgMYoAkigAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKIDxBFIWCgkKAhgCEP+ttQUKCQoCGGIQgK61BQ=="},{"b64Body":"Cg8KCQjG/v6sBhC0FBICGAISAhgDGOCssQMiAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjjovCgMYogkQoI0GIiQ68yq/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABKA=","b64Record":"CiUIFiIDGKIJKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjDLLmc/8HiNYf3aTX9wClteeLyryFJpBxp64NWy4jWPLNIbSedNN4h809fcttCegDgaDAiC//6sBhCjmPm3ASIPCgkIxv7+rAYQtBQSAhgCKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjjCA19oCOq4CCgMYogkSIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABIoACAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACiA8QRSFgoJCgIYAhD/rbUFCgkKAhhiEICutQU="},{"b64Body":"Cg8KCQjG/v6sBhC2FBICGAISAhgDGOCssQMiAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjjovCgMYoQkQoI0GIiQ68yq/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABKA=","b64Record":"CiUIFiIDGKEJKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjC2DDrWv3Yp3pH14j04OIT+P8PZs3DmfO1KqZyFlZdov/Ag6sSjUfRpdFj4RoQYPtMaDAiC//6sBhDwxMq5AyIPCgkIxv7+rAYQthQSAhgCKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjjCA19oCOq4CCgMYoQkSIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABIoACAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACiA8QRSFgoJCgIYAhD/rbUFCgkKAhhiEICutQU="}]},"cannotUseMirrorAddressOfAliasedContractInPrecompileMethod":{"placeholderNum":1187,"encodedItems":[{"b64Body":"Cg8KCQjL/v6sBhDOFBICGAISAhgDGPuV9hQiAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjloxCiISICFJ6tu6A5xUMrASFOLmw7D2KZZKttFJQKtQWynA+fR3EICU69wDSgUIgM7aAw==","b64Record":"CiUIFhIDGKQJKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjCWudZxm+wg6mu7YD6TM77Ipu/+Gmt8NGVCdbU7HAKtA8uhqa0nuNga2WbimWoPCFIaDAiH//6sBhCDrrm9AiIPCgkIy/7+rAYQzhQSAhgCKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjlIZCgoKAhgCEP+n1rkHCgsKAxikCRCAqNa5Bw=="},{"b64Body":"Cg8KCQjM/v6sBhDQFBICGAISAhgDGL/0xiwiAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjooBgAESCwiIzdmwBhCgwKhGGm0KIhIg3a1QQH+AFBlovb7BcMOLVTKpybHBnPXya7fjxv1QtfIKIzohA5e2zHdCm6biI6oKd2lgJJqN2Ag4hFEMK1gFtJKN8vtTCiISIIqbMteS53/qraUfPdKPl9hunaJkMjX7tedwFy3c8hG+KgAyAA==","b64Record":"CiUIFhoDGKUJKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjButk9v+HUk9FW6i+G1DhWk1mtayIe4gB+qQsGn5orA0zQGOwzFhAf6Aju5iC1y03gaCwiI//6sBhC/+I1iIg8KCQjM/v6sBhDQFBICGAIqIMODwq7DgsK3w4PCuXRGOMOCwq5Kw4PCi8ODwpDDg8KOUgA="},{"b64Body":"Cg8KCQjM/v6sBhDUFBICGAISAhgDGJPsjj4iAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjpoBiCAKAxilCSKAIDB4NjA4MDYwNDA1MjM0ODAxNTYxMDAxMDU3NjAwMDgwZmQ1YjUwNjAwMDgwNjA0MDUxODA2MDIwMDE2MTAwMjQ5MDYxMDBiNzU2NWI2MDIwODIwMTgxMDM4MjUyNjAxZjE5NjAxZjgyMDExNjYwNDA1MjUwOTA1MDYwMDA2MDQwNTE2MDIwMDE2MTAwNGI5MDYxMDExYjU2NWI2MDQwNTE2MDIwODE4MzAzMDM4MTUyOTA2MDQwNTI4MDUxOTA2MDIwMDEyMDkwNTA4MDgyNTE2MDIwODQwMTYwMDBmNTkyNTA4MjYwMDA4MDYxMDEwMDBhODE1NDgxNzNmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmMDIxOTE2OTA4MzczZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjE2MDIxNzkwNTU1MDUwNTA1MDYxMDEzMDU2NWI2MTA1MWQ4MDYxMDMyZDgzMzkwMTkwNTY1YjYwMDA4MTkwNTA5MjkxNTA1MDU2NWI3ZjcwNjU3MjZkNjk3NDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDA2MDAwODIwMTUyNTA1NjViNjAwMDYxMDEwNTYwMDY4MzYxMDBjNDU2NWI5MTUwNjEwMTEwODI2MTAwY2Y1NjViNjAwNjgyMDE5MDUwOTE5MDUwNTY1YjYwMDA2MTAxMjY4MjYxMDBmODU2NWI5MTUwODE5MDUwOTE5MDUwNTY1YjYxMDFlZTgwNjEwMTNmNjAwMDM5NjAwMGYzZmU2MDgwNjA0MDUyMzQ4MDE1NjEwMDEwNTc2MDAwODBmZDViNTA2MDA0MzYxMDYxMDAyYjU3NjAwMDM1NjBlMDFjODA2M2JmNDBjYmE5MTQ2MTAwMzA1NzViNjAwMDgwZmQ1YjYxMDA0YTYwMDQ4MDM2MDM4MTAxOTA2MTAwNDU5MTkwNjEwMTQwNTY1YjYxMDA0YzU2NWIwMDViNjAwMDgwNTQ5MDYxMDEwMDBhOTAwNDczZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjE2NzNmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmMTY2M2JmNDBjYmE5ODM4MzYwNDA1MTgzNjNmZmZmZmZmZjE2NjBlMDFiODE1MjYwMDQwMTYxMDBhNzkyOTE5MDYxMDE4ZjU2NWI2MDAwNjA0MDUxODA4MzAzODE2MDAwODc4MDNiMTU4MDE1NjEwMGMxNTc2MDAwODBmZDViNTA1YWYxMTU4MDE1NjEwMGQ1NTczZDYwMDA4MDNlM2Q2MDAwZmQ1YjUwNTA1MDUwNTA1MDU2NWI2MDAwODBmZDViNjAwMDczZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjgyMTY5MDUwOTE5MDUwNTY1YjYwMDA2MTAxMGQ4MjYxMDBlMjU2NWI5MDUwOTE5MDUwNTY1YjYxMDExZDgxNjEwMTAyNTY1YjgxMTQ2MTAxMjg1NzYwMDA4MGZkNWI1MDU2NWI2MDAwODEzNTkwNTA2MTAxM2E4MTYxMDExNDU2NWI5MjkxNTA1MDU2NWI2MDAwODA2MDQwODM4NTAzMTIxNTYxMDE1NzU3NjEwMTU2NjEwMGRkNTY1YjViNjAwMDYxMDE2NTg1ODI4NjAxNjEwMTJiNTY1YjkyNTA1MDYwMjA2MTAxNzY4NTgyODYwMTYxMDEyYjU2NWI5MTUwNTA5MjUwOTI5MDUwNTY1YjYxMDE4OTgxNjEwMTAyNTY1YjgyNTI1MDUwNTY1YjYwMDA2MDQwODIwMTkwNTA2MTAxYTQ2MDAwODMwMTg1NjEwMTgwNTY1YjYxMDFiMTYwMjA4MzAxODQ2MTAxODA1NjViOTM5MjUwNTA1MDU2ZmVhMjY0Njk3MDY2NzM1ODIyMTIyMGNhOGVmNjU1OTU3ZWNmM2Y2YjdmZjBiN2E1MWRhYzNhNzVjMzExYTQ5YzY2NDk0NjYxNGY5ODExNzEzZjkwMWM2NDczNmY2YzYzNDMwMDA4MGMwMDMzNjA4MDYwNDA1MjM0ODAxNTYxMDAxMDU3NjAwMDgwZmQ1YjUwNjEwNGZkODA2MTAwMjA2MDAwMzk2MDAwZjNmZTYwODA2MDQwNTIzNDgwMTU2MTAwMTA1NzYwMDA4MGZkNWI1MDYwMDQzNjEwNjEwMDM2NTc2MDAwMzU2MGUwMWM4MDYzM2FmMzJhYmYxNDYxMDAzYjU3ODA2M2JmNDBjYmE5MTQ2MTAwNmI1NzViNjAwMDgwZmQ1YjYxMDA1NTYwMDQ4MDM2MDM4MTAxOTA2MTAwNTA5MTkwNjEwMjljNTY1YjYxMDA4NzU2NWI2MDQwNTE2MTAwNjI5MTkwNjEwMmU0NTY1YjYwNDA1MTgwOTEwMzkwZjM1YjYxMDA4NTYwMDQ4MDM2MDM4MTAxOTA2MTAwODA5MTkwNjEwMmZmNTY1YjYxMDEwMTU2NWIwMDViNjAwMDgxNzNmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmMTY2MzYxMWI0MDk1NjA0MDUxODE2M2ZmZmZmZmZmMTY2MGUwMWI4MTUyNjAwNDAxNjAyMDYwNDA1MTgwODMwMzgxNjAwMDg3NWFmMTE1ODAxNTYxMDBkNjU3M2Q2MDAwODAzZTNkNjAwMGZkNWI1MDUwNTA1MDYwNDA1MTNkNjAxZjE5NjAxZjgyMDExNjgyMDE4MDYwNDA1MjUwODEwMTkwNjEwMGZhOTE5MDYxMDM2YjU2NWI5MDUwOTE5MDUwNTY1YjYwMDA2MTAxMGQ4MzgzNjEwMTIxNTY1YjkwNTA2MDE2ODExNDYxMDExYzU3NjAwMDgwZmQ1YjUwNTA1MDU2NWI2MDAwODA2MDAwNjEwMTY3NzNmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmMTY2MzQ5MTQ2YmRlNjBlMDFiODY4NjYwNDA1MTYwMjQwMTYxMDE1YTkyOTE5MDYxMDNhNzU2NWI2MDQwNTE2MDIwODE4MzAzMDM4MTUyOTA2MDQwNTI5MDdiZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxOTE2NjAyMDgyMDE4MDUxN2JmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjgzODE4MzE2MTc4MzUyNTA1MDUwNTA2MDQwNTE2MTAxYzQ5MTkwNjEwNDRhNTY1YjYwMDA2MDQwNTE4MDgzMDM4MTYwMDA4NjVhZjE5MTUwNTAzZDgwNjAwMDgxMTQ2MTAyMDE1NzYwNDA1MTkxNTA2MDFmMTk2MDNmM2QwMTE2ODIwMTYwNDA1MjNkODI1MjNkNjAwMDYwMjA4NDAxM2U2MTAyMDY1NjViNjA2MDkxNTA1YjUwOTE1MDkxNTA4MTYxMDIxNzU3NjAxNTYxMDIyYzU2NWI4MDgwNjAyMDAxOTA1MTgxMDE5MDYxMDIyYjkxOTA2MTA0OWE1NjViNWI2MDAzMGI5MjUwNTA1MDkyOTE1MDUwNTY1YjYwMDA4MGZkNWI2MDAwNzNmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmODIxNjkwNTA5MTkwNTA1NjViNjAwMDYxMDI2OTgyNjEwMjNlNTY1YjkwNTA5MTkwNTA1NjViNjEwMjc5ODE2MTAyNWU1NjViODExNDYxMDI4NDU3NjAwMDgwZmQ1YjUwNTY1YjYwMDA4MTM1OTA1MDYxMDI5NjgxNjEwMjcwNTY1YjkyOTE1MDUwNTY1YjYwMDA2MDIwODI4NDAzMTIxNTYxMDJiMjU3NjEwMmIxNjEwMjM5NTY1YjViNjAwMDYxMDJjMDg0ODI4NTAxNjEwMjg3NTY1YjkxNTA1MDkyOTE1MDUwNTY1YjYwMDA4MTE1MTU5MDUwOTE5MDUwNTY1YjYxMDJkZTgxNjEwMmM5NTY1YjgyNTI1MDUwNTY1YjYwMDA2MDIwODIwMTkwNTA2MTAyZjk2MDAwODMwMTg0NjEwMmQ1NTY1YjkyOTE1MDUwNTY1YjYwMDA4MDYwNDA4Mzg1MDMxMjE1NjEwMzE2NTc2MTAzMTU2MTAyMzk1NjViNWI2MDAwNjEwMzI0ODU4Mjg2MDE2MTAyODc1NjViOTI1MDUwNjAyMDYxMDMzNTg1ODI4NjAxNjEwMjg3NTY1YjkxNTA1MDkyNTA5MjkwNTA1NjViNjEwMzQ4ODE2MTAyYzk1NjViODExNDYxMDM1MzU3NjAwMDgwZmQ1YjUwNTY1YjYwMDA4MTUxOTA1MDYxMDM2NTgxNjEwMzNmNTY1YjkyOTE1MDUwNTY1YjYwMDA2MDIwODI4NDAzMTIxNTYxMDM4MTU3NjEwMzgwNjEwMjM5NTY1YjViNjAwMDYxMDM4Zjg0ODI4NTAxNjEwMzU2NTY1YjkxNTA1MDkyOTE1MDUwNTY1YjYxMDNhMTgxNjEwMjVlNTY1YjgyNTI1MDUwNTY1YjYwMDA2MDQwODIwMTkwNTA2MTAzYmM2MDAwODMwMTg1NjEwMzk4NTY1YjYxMDNjOTYwMjA4MzAxODQ2MTAzOTg1NjViOTM5MjUwNTA1MDU2NWI2MDAwODE1MTkwNTA5MTkwNTA1NjViNjAwMDgxOTA1MDkyOTE1MDUwNTY1YjYwMDA1YjgzODExMDE1NjEwNDA0NTc4MDgyMDE1MTgxODQwMTUyNjAyMDgxMDE5MDUwNjEwM2U5NTY1YjgzODExMTE1NjEwNDEzNTc2MDAwODQ4NDAxNTI1YjUwNTA1MDUwNTY1YjYwMDA2MTA0MjQ4MjYxMDNkMDU2NWI2MTA0MmU4MTg1NjEwM2RiNTY1YjkzNTA2MTA0M2U4MTg1NjAyMDg2MDE2MTAzZTY1NjViODA4NDAxOTE1MDUwOTI5MTUwNTA1NjViNjAwMDYxMDQ1NjgyODQ2MTA0MTk1NjViOTE1MDgxOTA1MDkyOTE1MDUwNTY1YjYwMDA4MTYwMDMwYjkwNTA5MTkwNTA1NjViNjEwNDc3ODE2MTA0NjE1NjViODExNDYxMDQ4MjU3NjAwMDgwZmQ1YjUwNTY1YjYwMDA4MTUxOTA1MDYxMDQ5NDgxNjEwNDZlNTY1YjkyOTE1MDUwNTY1YjYwMDA2MDIwODI4NDAzMTIxNTYxMDRiMDU3NjEwNGFmNjEwMjM5NTY1YjViNjA=","b64Record":"CiAIFiocCgwIARAMGgYIgK6ZpA8SDAgBEA8aBgiArpmkDxIwUe5A5qWNyRghONxuToP+RMDSXFzsfQEo17OLVTbD1fV1PYTEoa6EwR1cEwTnC7KbGgwIiP/+rAYQm4noxgIiDwoJCMz+/qwGENQUEgIYAiogw4PCrsOCwrfDg8K5dEY4w4LCrkrDg8KLw4PCkMODwo5SAA=="},{"b64Body":"Cg8KCQjN/v6sBhDaFBICGAISAhgDGJ2Lwy0iAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjoIBngESAxilCSKWATAwNjEwNGJlODQ4Mjg1MDE2MTA0ODU1NjViOTE1MDUwOTI5MTUwNTA1NmZlYTI2NDY5NzA2NjczNTgyMjEyMjA0OGVlZWQ2NmExMTJkMWZlYjI1NGFlMzFjOTI0ZWIxNDFkMDUxZjVjZTZmNjkxNGYzY2NlMWI0MjMzYWY5M2RhNjQ3MzZmNmM2MzQzMDAwODBjMDAzMw==","b64Record":"CiAIFiocCgwIARAMGgYIgK6ZpA8SDAgBEA8aBgiArpmkDxIw7+FkKF7AUq1nGjyuVX833dSkZ07L0jL3MXWdZ9Exbw97uqFtb5cKyyM39zS8N9g+GgsIif/+rAYQvt7+TiIPCgkIzf7+rAYQ2hQSAhgCKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjlIA"},{"b64Body":"Cg8KCQjN/v6sBhDcFBICGAISAhgDGJb7rp0CIgIIeDIgw4PCrsOCwrfDg8K5dEY4w4LCrkrDg8KLw4PCkMODwo5CRQoDGKUJGiISIGNm8FoCahFrv31m2H1jGd5/FdZ2If5RVmzOlno531KuIICJekIFCIDO2gNSAFoAagtjZWxsYXIgZG9vcg==","b64Record":"CiUIFiIDGKYJKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjCZ/T2DUamLFaQePL5BVjOPkXGk2eycS9W5dySJBRilZCRzGxwoXktj89Nl6m/jDVIaDAiJ//6sBhDR9NDQAiIPCgkIzf7+rAYQ3BQSAhgCKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjjCAzJU2QrEGCgMYpgkS7gNggGBAUjSAFWEAEFdgAID9W1BgBDYQYQArV2AANWDgHIBjv0DLqRRhADBXW2AAgP1bYQBKYASANgOBAZBhAEWRkGEBQFZbYQBMVlsAW2AAgFSQYQEACpAEc///////////////////////////FnP//////////////////////////xZjv0DLqYODYEBRg2P/////FmDgG4FSYAQBYQCnkpGQYQGPVltgAGBAUYCDA4FgAIeAOxWAFWEAwVdgAID9W1Ba8RWAFWEA1Vc9YACAPj1gAP1bUFBQUFBQVltgAID9W2AAc///////////////////////////ghaQUJGQUFZbYABhAQ2CYQDiVluQUJGQUFZbYQEdgWEBAlZbgRRhAShXYACA/VtQVltgAIE1kFBhATqBYQEUVluSkVBQVltgAIBgQIOFAxIVYQFXV2EBVmEA3VZbW2AAYQFlhYKGAWEBK1ZbklBQYCBhAXaFgoYBYQErVluRUFCSUJKQUFZbYQGJgWEBAlZbglJQUFZbYABgQIIBkFBhAaRgAIMBhWEBgFZbYQGxYCCDAYRhAYBWW5OSUFBQVv6iZGlwZnNYIhIgyo72VZV+zz9rf/C3pR2sOnXDEaScZklGYU+YEXE/kBxkc29sY0MACAwAMyKAAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAogNRhOgMYpgk6AxinCUoWChQAAAAAAAAAAAAAAAAAAAAAAAAEpnIHCgMYpgkQAnIHCgMYpwkQAVIWCgkKAhgCEP+Xq2wKCQoCGGIQgJirbA=="},{"b64Body":"ChEKCQjN/v6sBhDcFBICGAIgAUI4GiISIGNm8FoCahFrv31m2H1jGd5/FdZ2If5RVmzOlno531KuQgUIgM7aA2oLY2VsbGFyIGRvb3I=","b64Record":"CgcIFiIDGKcJEjBLP6BTqHTfU8skj4NlR485D7pFQhiV942mMDvtWJaQWCCawrZXBhGKWxA0APNkOgsaDAiJ//6sBhDS9NDQAiIRCgkIzf7+rAYQ3BQSAhgCIAFCHQoDGKcJShYKFF0vGaLGySh+cHJVCfJ3HRfJROShUgB6DAiJ//6sBhDR9NDQAg=="},{"b64Body":"Cg8KCQjO/v6sBhDiFBICGAISAhgDGKWr3ugCIgIIeDIgw4PCrsOCwrfDg8K5dEY4w4LCrkrDg8KLw4PCkMODwo7qASYKBlRva2VuQRIITkFRT0xaUUIgZCoDGKQJagsIis3ZsAYQnJOfVA==","b64Record":"CiUIFiocCgwIARAMGgYIgK6ZpA8SDAgBEA8aBgiArpmkD1IDGKgJEjAzcVXmC0V8lP3fErp6keTZtw2gvoeJgguW2SdYqGV+a0j00JWGddEr5pI7CaU1iuYaCwiK//6sBhCa9cBYIg8KCQjO/v6sBhDiFBICGAIqIMODwq7DgsK3w4PCuXRGOMOCwq5Kw4PCi8ODwpDDg8KOUgBaDwoDGKgJEggKAxikCRDIAXIKCgMYqAkSAxikCQ=="},{"b64Body":"Cg8KCQjO/v6sBhDkFBICGAISAhgDGID/2kMiAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjjpPCgMYpgkQgIl6IkS/QMupAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABKcAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEqA==","b64Record":"CiUIISIDGKYJKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjCu2UurkAele6bgkWfHLjs1GPDhQcO8IoS1NlLvA2wE4LtcjY5HFZcK7WK3MGH+s5caDAiK//6sBhDB+YbaAiIPCgkIzv7+rAYQ5BQSAhgCKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjjCAzJU2OggaAjB4KIDUYVIWCgkKAhgCEP+Xq2wKCQoCGGIQgJirbA=="},{"b64Body":"Cg8KCQjP/v6sBhDuFBICGAISAhgDGID/2kMiAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjjpPCgMYpgkQgIl6IkS/QMupAAAAAAAAAAAAAAAAXS8ZosbJKH5wclUJ8ncdF8lE5KEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEqA==","b64Record":"CiUIFiIDGKYJKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjD8a9T2pCCq9BZ/VIssJ8pVFRFdr2weF8qkDoUQhZflNZJwo6679OUJvIOcJqIo9fAaCwiL//6sBhCrrodiIg8KCQjP/v6sBhDuFBICGAIqIMODwq7DgsK3w4PCuXRGOMOCwq5Kw4PCi8ODwpDDg8KOMIDMlTY6jAIKAximCSKAAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAogNRhUhYKCQoCGAIQ/5erbAoJCgIYYhCAmKts"},{"b64Body":"ChEKCQjP/v6sBhDuFBICGAIgAcICCgoDGKcJEgMYqAk=","b64Record":"CgIIFhIwWhk/BYrEGG/5pzFLMKLxA2rgURvaM3LEzPn9irej0RdB1S1GE5KYWuuHXCV1DwWWGgsIi//+rAYQrK6HYiIRCgkIz/7+rAYQ7hQSAhgCIAE6egoDGOcCEiAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFiji/SpQs7J0YkRJFGveAAAAAAAAAAAAAAAAXS8ZosbJKH5wclUJ8ncdF8lE5KEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEqGoDGKcJUgB6CwiL//6sBhCrrodi"}]},"ExchangeRatePrecompileWorks":{"placeholderNum":1002,"encodedItems":[]},"NestedContractCannotOverSendValue":{"placeholderNum":1196,"encodedItems":[{"b64Body":"Cg8KCQjb/v6sBhCgFRICGAISAhgDGPuV9hQiAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjlozCiISIFibu5xHIQ2KCY/mZbOFrpMC5qJepUYWF4rFs84HS2IkEICA6YOx3hZKBQiAztoD","b64Record":"CiUIFhIDGK0JKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjCCAkgwtk5FuEKVTvKZjyD1JF2lUhayxxIOn9SlVbijd1Zq+J+wYxIhGGvRDtZviUsaDAiX//6sBhDNiPn/AiIPCgkI2/7+rAYQoBUSAhgCKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjlIdCgwKAhgCEP//0YfivC0KDQoDGK0JEICA0ofivC0="},{"b64Body":"Cg8KCQjc/v6sBhCiFRICGAISAhgDGPuV9hQiAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjlouCiISIFR12wErIPr/CScQqj/JWm8Tq8s6YgsJYKg4pgBnk0rvEJBOSgUIgM7aAw==","b64Record":"CiUIFhIDGK4JKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjCidvcPNvglkWQOE9mDM7nW/S/KWArbmfEnBEaFzW4Itqli2is6AVHlK4HK1dXhhboaDAiY//6sBhCcm62IASIPCgkI3P7+rAYQohUSAhgCKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjlIVCggKAhgCEJ+cAQoJCgMYrgkQoJwB"},{"b64Body":"Cg8KCQjc/v6sBhCkFRICGAISAhgDGP7LzSwiAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjooBjwESDAiYzdmwBhDhjL/3AhptCiISIFS84kMP9+2tHvO3K7sySNqPIK6O8y6oIh12Z2fVN6x9CiM6IQMVoQdaphpvtWoP3Icq4rUh+JjDcZ5yXjjkfeOrH18xhgoiEiAgBd1U+RNp4GZaCphn1wGoxW3jGprknZVHN+3Czs6T1SIMSGVsbG8gV29ybGQhKgAyAA==","b64Record":"CiUIFhoDGK8JKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjDGap0mG5P50VdpeYkdwtd8FB5qTtq8x4w2UpcwWhJ8EVuzEFm1BuxoA+UofKNyPg4aDAiY//6sBhD2jMeOAyIPCgkI3P7+rAYQpBUSAhgCKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjlIA"},{"b64Body":"Cg8KCQjd/v6sBhCoFRICGAISAhgDGIudjj4iAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjpoBiCAKAxivCSKAIDYwODA2MDQwNTI2MDQwNTE2MTBlNmIzODAzODA2MTBlNmI4MzM5ODE4MTAxNjA0MDUyNjA0MDgxMTAxNTYxMDAyNjU3NjAwMDgwZmQ1YjgxMDE5MDgwODA1MTkwNjAyMDAxOTA5MjkxOTA4MDUxOTA2MDIwMDE5MDkyOTE5MDUwNTA1MDgxNjAwMDgwNjEwMTAwMGE4MTU0ODE3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYwMjE5MTY5MDgzNzNmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmMTYwMjE3OTA1NTUwODA2MDAxNjAwMDYxMDEwMDBhODE1NDgxNzNmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmMDIxOTE2OTA4MzczZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjE2MDIxNzkwNTU1MDUwNTA2MTBkOTg4MDYxMDBkMzYwMDAzOTYwMDBmM2ZlNjA4MDYwNDA1MjYwMDQzNjEwNjEwMDNmNTc2MDAwMzU2MGUwMWM4MDYzNTU1NWQ3ZTQxNDYxMDA0MTU3ODA2MzVlY2YyOThhMTQ2MTAwY2Y1NzgwNjM5ZGM3ZGIxMDE0NjEwMGZkNTc4MDYzYTlhM2E2MzUxNDYxMDE0YjU3NWIwMDViNjEwMGNkNjAwNDgwMzYwMzYwODA4MTEwMTU2MTAwNTc1NzYwMDA4MGZkNWI4MTAxOTA4MDgwMzU3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNjkwNjAyMDAxOTA5MjkxOTA4MDM1NzNmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmMTY5MDYwMjAwMTkwOTI5MTkwODAzNTczZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjE2OTA2MDIwMDE5MDkyOTE5MDgwMzU5MDYwMjAwMTkwOTI5MTkwNTA1MDUwNjEwMTc5NTY1YjAwNWI2MTAwZmI2MDA0ODAzNjAzNjAyMDgxMTAxNTYxMDBlNTU3NjAwMDgwZmQ1YjgxMDE5MDgwODAzNTkwNjAyMDAxOTA5MjkxOTA1MDUwNTA2MTA3MTM1NjViMDA1YjYxMDE0OTYwMDQ4MDM2MDM2MDQwODExMDE1NjEwMTEzNTc2MDAwODBmZDViODEwMTkwODA4MDM1NzNmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmMTY5MDYwMjAwMTkwOTI5MTkwODAzNTkwNjAyMDAxOTA5MjkxOTA1MDUwNTA2MTA4MmY1NjViMDA1YjYxMDE3NzYwMDQ4MDM2MDM2MDIwODExMDE1NjEwMTYxNTc2MDAwODBmZDViODEwMTkwODA4MDM1OTA2MDIwMDE5MDkyOTE5MDUwNTA1MDYxMGEwZjU2NWIwMDViODM3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNjYxMDhmYzgyOTA4MTE1MDI5MDYwNDA1MTYwMDA2MDQwNTE4MDgzMDM4MTg1ODg4OGYxOTM1MDUwNTA1MDE1ODAxNTYxMDFiZjU3M2Q2MDAwODAzZTNkNjAwMGZkNWI1MDYwMDA4MDkwNTQ5MDYxMDEwMDBhOTAwNDczZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjE2NzNmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmMTY2M2FjZWY2MDM3ODU2MDAyODQ4MTYxMDIwODU3ZmU1YjA0NjA0MDUxODM2M2ZmZmZmZmZmMTY2MGUwMWI4MTUyNjAwNDAxODA4MzczZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjE2NzNmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmMTY4MTUyNjAyMDAxODI4MTUyNjAyMDAxOTI1MDUwNTA2MDAwNjA0MDUxODA4MzAzODE2MDAwODc4MDNiMTU4MDE1NjEwMjcyNTc2MDAwODBmZDViNTA1YWYxMTU4MDE1NjEwMjg2NTczZDYwMDA4MDNlM2Q2MDAwZmQ1YjUwNTA1MDUwNjAwMTYwMDA5MDU0OTA2MTAxMDAwYTkwMDQ3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNjczZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjE2NjNhY2VmNjAzNzg1NjAwMjg0ODE2MTAyZDM1N2ZlNWIwNDYwNDA1MTgzNjNmZmZmZmZmZjE2NjBlMDFiODE1MjYwMDQwMTgwODM3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNjczZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjE2ODE1MjYwMjAwMTgyODE1MjYwMjAwMTkyNTA1MDUwNjAwMDYwNDA1MTgwODMwMzgxNjAwMDg3ODAzYjE1ODAxNTYxMDMzZDU3NjAwMDgwZmQ1YjUwNWFmMTE1ODAxNTYxMDM1MTU3M2Q2MDAwODAzZTNkNjAwMGZkNWI1MDUwNTA1MDgyNzNmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmMTY2MTA4ZmM4MjkwODExNTAyOTA2MDQwNTE2MDAwNjA0MDUxODA4MzAzODE4NTg4ODhmMTkzNTA1MDUwNTAxNTgwMTU2MTAzOWI1NzNkNjAwMDgwM2UzZDYwMDBmZDViNTA2MDAwODA5MDU0OTA2MTAxMDAwYTkwMDQ3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNjczZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjE2NjNhY2VmNjAzNzg0NjAwMjg0ODE2MTAzZTQ1N2ZlNWIwNDYwNDA1MTgzNjNmZmZmZmZmZjE2NjBlMDFiODE1MjYwMDQwMTgwODM3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNjczZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjE2ODE1MjYwMjAwMTgyODE1MjYwMjAwMTkyNTA1MDUwNjAwMDYwNDA1MTgwODMwMzgxNjAwMDg3ODAzYjE1ODAxNTYxMDQ0ZTU3NjAwMDgwZmQ1YjUwNWFmMTE1ODAxNTYxMDQ2MjU3M2Q2MDAwODAzZTNkNjAwMGZkNWI1MDUwNTA1MDYwMDE2MDAwOTA1NDkwNjEwMTAwMGE5MDA0NzNmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmMTY3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNjYzYWNlZjYwMzc4NDYwMDI4NDgxNjEwNGFmNTdmZTViMDQ2MDQwNTE4MzYzZmZmZmZmZmYxNjYwZTAxYjgxNTI2MDA0MDE4MDgzNzNmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmMTY3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNjgxNTI2MDIwMDE4MjgxNTI2MDIwMDE5MjUwNTA1MDYwMDA2MDQwNTE4MDgzMDM4MTYwMDA4NzgwM2IxNTgwMTU2MTA1MTk1NzYwMDA4MGZkNWI1MDVhZjExNTgwMTU2MTA1MmQ1NzNkNjAwMDgwM2UzZDYwMDBmZDViNTA1MDUwNTA4MTczZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjE2NjEwOGZjODI5MDgxMTUwMjkwNjA0MDUxNjAwMDYwNDA1MTgwODMwMzgxODU4ODg4ZjE5MzUwNTA1MDUwMTU4MDE1NjEwNTc3NTczZDYwMDA4MDNlM2Q2MDAwZmQ1YjUwNjAwMDgwOTA1NDkwNjEwMTAwMGE5MDA0NzNmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmMTY3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNjYzYWNlZjYwMzc4MzYwMDI4NDgxNjEwNWMwNTdmZTViMDQ2MDQwNTE4MzYzZmZmZmZmZmYxNjYwZTAxYjgxNTI2MDA0MDE4MDgzNzNmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmMTY3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNjgxNTI2MDIwMDE4MjgxNTI2MDIwMDE5MjUwNTA1MDYwMDA2MDQwNTE4MDgzMDM4MTYwMDA4NzgwM2IxNTgwMTU2MTA2MmE1NzYwMDA4MGZkNWI1MDVhZjExNTgwMTU2MTA2M2U1NzNkNjAwMDgwM2UzZDYwMDBmZDViNTA1MDUwNTA2MDAxNjAwMDkwNTQ5MDYxMDEwMDBhOTAwNDczZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjE2NzNmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmMTY2M2FjZWY2MDM3ODM2MDAyODQ4MTYxMDY4YjU3ZmU1YjA0NjA0MDUxODM2M2ZmZmZmZmZmMTY2MGUwMWI4MTUyNjAwNDAxODA4MzczZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjE2NzNmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmMTY4MTUyNjAyMDAxODI4MTUyNjAyMDAxOTI1MDUwNTA2MDAwNjA0MDUxODA4MzAzODE2MDAwODc4MDNiMTU4MDE1NjEwNmY1NTc2MDAwODBmZDViNTA1YWYxMTU4MDE1NjEwNzA5NTczZDYwMDA4MDNlM2Q2MDAwZmQ1YjUwNTA1MDUwNTA1MDUwNTA1NjViNjAwMDgwOTA1NDkwNjEwMTAwMGE5MDA0NzNmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmY=","b64Record":"CiAIFiocCgwIARAMGgYIgK6ZpA8SDAgBEA8aBgiArpmkDxIwadyg7FLTAbedmZ4Y88TRzQelnjxcttCSpNdsbamx0Up+6FogiCVg5soonQL2o/lDGgwImf/+rAYQiNvElgEiDwoJCN3+/qwGEKgVEgIYAiogw4PCrsOCwrfDg8K5dEY4w4LCrkrDg8KLw4PCkMODwo5SAA=="},{"b64Body":"Cg8KCQjd/v6sBhCuFRICGAISAhgDGPjsozsiAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjoIB3hkSAxivCSLWGWZmZmZmZmZmZmZmZmZmZmYxNjczZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjE2NjM4ZGQ5ODA0MzgyNjA0MDUxODI2M2ZmZmZmZmZmMTY2MGUwMWI4MTUyNjAwNDAxODA4MjgxNTI2MDIwMDE5MTUwNTA2MDAwNjA0MDUxODA4MzAzODE2MDAwODc4MDNiMTU4MDE1NjEwNzg3NTc2MDAwODBmZDViNTA1YWYxMTU4MDE1NjEwNzliNTczZDYwMDA4MDNlM2Q2MDAwZmQ1YjUwNTA1MDUwNjAwMTYwMDA5MDU0OTA2MTAxMDAwYTkwMDQ3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNjczZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjE2NjM4ZGQ5ODA0MzgyNjA0MDUxODI2M2ZmZmZmZmZmMTY2MGUwMWI4MTUyNjAwNDAxODA4MjgxNTI2MDIwMDE5MTUwNTA2MDAwNjA0MDUxODA4MzAzODE2MDAwODc4MDNiMTU4MDE1NjEwODE0NTc2MDAwODBmZDViNTA1YWYxMTU4MDE1NjEwODI4NTczZDYwMDA4MDNlM2Q2MDAwZmQ1YjUwNTA1MDUwNTA1NjViODE3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNjYxMDhmYzgyOTA4MTE1MDI5MDYwNDA1MTYwMDA2MDQwNTE4MDgzMDM4MTg1ODg4OGYxOTM1MDUwNTA1MDE1ODAxNTYxMDg3NTU3M2Q2MDAwODAzZTNkNjAwMGZkNWI1MDYwMDA4MDkwNTQ5MDYxMDEwMDBhOTAwNDczZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjE2NzNmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmMTY2M2FjZWY2MDM3ODM2MDAyODQ4MTYxMDhiZTU3ZmU1YjA0NjA0MDUxODM2M2ZmZmZmZmZmMTY2MGUwMWI4MTUyNjAwNDAxODA4MzczZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjE2NzNmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmMTY4MTUyNjAyMDAxODI4MTUyNjAyMDAxOTI1MDUwNTA2MDAwNjA0MDUxODA4MzAzODE2MDAwODc4MDNiMTU4MDE1NjEwOTI4NTc2MDAwODBmZDViNTA1YWYxMTU4MDE1NjEwOTNjNTczZDYwMDA4MDNlM2Q2MDAwZmQ1YjUwNTA1MDUwNjAwMTYwMDA5MDU0OTA2MTAxMDAwYTkwMDQ3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNjczZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjE2NjNhY2VmNjAzNzgzNjAwMjg0ODE2MTA5ODk1N2ZlNWIwNDYwNDA1MTgzNjNmZmZmZmZmZjE2NjBlMDFiODE1MjYwMDQwMTgwODM3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNjczZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjE2ODE1MjYwMjAwMTgyODE1MjYwMjAwMTkyNTA1MDUwNjAwMDYwNDA1MTgwODMwMzgxNjAwMDg3ODAzYjE1ODAxNTYxMDlmMzU3NjAwMDgwZmQ1YjUwNWFmMTE1ODAxNTYxMGEwNzU3M2Q2MDAwODAzZTNkNjAwMGZkNWI1MDUwNTA1MDUwNTA1NjViNjAwMDYwNjA2MDAwODA5MDU0OTA2MTAxMDAwYTkwMDQ3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNjczZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjE2ODM2MDQwNTE2MDI0MDE4MDgyODE1MjYwMjAwMTkxNTA1MDYwNDA1MTYwMjA4MTgzMDMwMzgxNTI5MDYwNDA1MjdmOGRkOTgwNDMwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDdiZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxOTE2NjAyMDgyMDE4MDUxN2JmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjgzODE4MzE2MTc4MzUyNTA1MDUwNTA2MDQwNTE4MDgyODA1MTkwNjAyMDAxOTA4MDgzODM1YjYwMjA4MzEwNjEwYjBjNTc4MDUxODI1MjYwMjA4MjAxOTE1MDYwMjA4MTAxOTA1MDYwMjA4MzAzOTI1MDYxMGFlOTU2NWI2MDAxODM2MDIwMDM2MTAxMDAwYTAzODAxOTgyNTExNjgxODQ1MTE2ODA4MjE3ODU1MjUwNTA1MDUwNTA1MDkwNTAwMTkxNTA1MDYwMDA2MDQwNTE4MDgzMDM4MTg1NWFmNDkxNTA1MDNkODA2MDAwODExNDYxMGI2YzU3NjA0MDUxOTE1MDYwMWYxOTYwM2YzZDAxMTY4MjAxNjA0MDUyM2Q4MjUyM2Q2MDAwNjAyMDg0MDEzZTYxMGI3MTU2NWI2MDYwOTE1MDViNTA5MTUwOTE1MDYwMDA2MDYwNjAwMTYwMDA5MDU0OTA2MTAxMDAwYTkwMDQ3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNjczZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjE2ODU2MDQwNTE2MDI0MDE4MDgyODE1MjYwMjAwMTkxNTA1MDYwNDA1MTYwMjA4MTgzMDMwMzgxNTI5MDYwNDA1MjdmOGRkOTgwNDMwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDdiZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxOTE2NjAyMDgyMDE4MDUxN2JmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjgzODE4MzE2MTc4MzUyNTA1MDUwNTA2MDQwNTE4MDgyODA1MTkwNjAyMDAxOTA4MDgzODM1YjYwMjA4MzEwNjEwYzc0NTc4MDUxODI1MjYwMjA4MjAxOTE1MDYwMjA4MTAxOTA1MDYwMjA4MzAzOTI1MDYxMGM1MTU2NWI2MDAxODM2MDIwMDM2MTAxMDAwYTAzODAxOTgyNTExNjgxODQ1MTE2ODA4MjE3ODU1MjUwNTA1MDUwNTA1MDkwNTAwMTkxNTA1MDYwMDA2MDQwNTE4MDgzMDM4MTg1NWFmNDkxNTA1MDNkODA2MDAwODExNDYxMGNkNDU3NjA0MDUxOTE1MDYwMWYxOTYwM2YzZDAxMTY4MjAxNjA0MDUyM2Q4MjUyM2Q2MDAwNjAyMDg0MDEzZTYxMGNkOTU2NWI2MDYwOTE1MDViNTA5MTUwOTE1MDgzMTU4MDYxMGNlOTU3NTA4MTE1NWIxNTYxMGQ1YzU3NjA0MDUxN2YwOGMzNzlhMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwODE1MjYwMDQwMTgwODA2MDIwMDE4MjgxMDM4MjUyNjAxZTgxNTI2MDIwMDE4MDdmNDQ2NTZjNjU2NzYxNzQ2NTIwNzQ3MjYxNmU3MzY2NjU3MjIwNjM2MTZjNmMyMDY2NjE2OTZjNjU2NDIxMDAwMDgxNTI1MDYwMjAwMTkxNTA1MDYwNDA1MTgwOTEwMzkwZmQ1YjUwNTA1MDUwNTA1NmZlYTI2NTYyN2E3YTcyMzE1ODIwNjE5ZDU1NTI1Y2QxN2Y3MzliMGNiMjZkODZmN2QxOGYxNTVlYWM0NWY5NTM3MGQ5NjRkZWEzYzcwZGIxNGQ2ZTY0NzM2ZjZjNjM0MzAwMDUxMTAwMzI=","b64Record":"CiAIFiocCgwIARAMGgYIgK6ZpA8SDAgBEA8aBgiArpmkDxIwbSennKgpdl6uVnpqA3A35CjJFtDtpZKK/ZESSIgxCMar3PRFF4wEi0SQhfE+uUxdGgwImf/+rAYQhrn9lwMiDwoJCN3+/qwGEK4VEgIYAiogw4PCrsOCwrfDg8K5dEY4w4LCrkrDg8KLw4PCkMODwo5SAA=="},{"b64Body":"Cg8KCQje/v6sBhCwFRICGAISAhgDGP7LzSwiAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjooBjwESDAiazdmwBhCS8+ySARptCiISILZpiXrDM7gpade149CmjNt7TebQk3gTjOTDnuLIIt3ECiM6IQP48bvmGkXfFZ4+HU9uG4MhA9I8CWQ9fRyMsROjdttergoiEiCrzOGBwTDmS3ITola7gkoxPIjA/sB8ni3KQBsC3RN7DCIMSGVsbG8gV29ybGQhKgAyAA==","b64Record":"CiUIFhoDGLAJKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjAWwWga9xfb7Nc83EuihvUTsFcM3z69KszjA9bdiOw0dhvxiGWJ8NAOWcqETH6UQ7UaDAia//6sBhDk8P2fASIPCgkI3v7+rAYQsBUSAhgCKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjlIA"},{"b64Body":"Cg8KCQje/v6sBhC0FRICGAISAhgDGM6Y0i8iAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjpoBkAYKAxiwCSKIBjYwODA2MDQwNTI2MTAxNzE4MDYxMDAxMzYwMDAzOTYwMDBmM2ZlNjA4MDYwNDA1MjYwMDQzNjEwNjEwMDI5NTc2MDAwMzU2MGUwMWM4MDYzOGRkOTgwNDMxNDYxMDAyYjU3ODA2M2FjZWY2MDM3MTQ2MTAwNTk1NzViMDA1YjYxMDA1NzYwMDQ4MDM2MDM2MDIwODExMDE1NjEwMDQxNTc2MDAwODBmZDViODEwMTkwODA4MDM1OTA2MDIwMDE5MDkyOTE5MDUwNTA1MDYxMDBhNzU2NWIwMDViNjEwMGE1NjAwNDgwMzYwMzYwNDA4MTEwMTU2MTAwNmY1NzYwMDA4MGZkNWI4MTAxOTA4MDgwMzU3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNjkwNjAyMDAxOTA5MjkxOTA4MDM1OTA2MDIwMDE5MDkyOTE5MDUwNTA1MDYxMDBmMTU2NWIwMDViMzM3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNjYxMDhmYzgyOTA4MTE1MDI5MDYwNDA1MTYwMDA2MDQwNTE4MDgzMDM4MTg1ODg4OGYxOTM1MDUwNTA1MDE1ODAxNTYxMDBlZDU3M2Q2MDAwODAzZTNkNjAwMGZkNWI1MDUwNTY1YjgxNzNmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmMTY2MTA4ZmM4MjkwODExNTAyOTA2MDQwNTE2MDAwNjA0MDUxODA4MzAzODE4NTg4ODhmMTkzNTA1MDUwNTAxNTgwMTU2MTAxMzc1NzNkNjAwMDgwM2UzZDYwMDBmZDViNTA1MDUwNTZmZWEyNjU2MjdhN2E3MjMxNTgyMDVlYjQwYmI2MjA4MzVmYjc5NjljZGQ3NWRlZDE4ODkyN2Q1YzkyN2Y5M2JhNjc1ZDY1YjljNTIzNDQ0MjQyODc2NDczNmY2YzYzNDMwMDA1MTEwMDMy","b64Record":"CiAIFiocCgwIARAMGgYIgK6ZpA8SDAgBEA8aBgiArpmkDxIwUBDjJ4BUnBf8WLQS0GsGV+n47RXouOu9yG3CWJEYliJwgINN79DeyRcMLHFFfvs5GgwImv/+rAYQuPi2oQMiDwoJCN7+/qwGELQVEgIYAiogw4PCrsOCwrfDg8K5dEY4w4LCrkrDg8KLw4PCkMODwo5SAA=="},{"b64Body":"ChAKCQjf/v6sBhC2FRIDGK0JEgIYAxiW+66dAiICCHgyIMODwq7DgsK3w4PCuXRGOMOCwq5Kw4PCi8ODwpDDg8KOQkgKAxiwCRoiEiDRxfzVr6Sw1ZWIswyKZQrCVyZkn98w0ryxyK57QPk+aSCQoQ8okE5CBQiAztoDUgBaAGoLY2VsbGFyIGRvb3I=","b64Record":"CiUIFiIDGLEJKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjDLMXNMTZS1fa7un4G6Jmut/tTN3B734K15jM/GdNSDlP8p3/xmtnK2xWwx2xTMweAaDAib//6sBhCu4K+pASIQCgkI3/7+rAYQthUSAxitCSogw4PCrsOCwrfDg8K5dEY4w4LCrkrDg8KLw4PCkMODwo4w1tSRpAJCpgUKAxixCRLxAmCAYEBSYAQ2EGEAKVdgADVg4ByAY43ZgEMUYQArV4BjrO9gNxRhAFlXWwBbYQBXYASANgNgIIEQFWEAQVdgAID9W4EBkICANZBgIAGQkpGQUFBQYQCnVlsAW2EApWAEgDYDYECBEBVhAG9XYACA/VuBAZCAgDVz//////////////////////////8WkGAgAZCSkZCANZBgIAGQkpGQUFBQYQDxVlsAWzNz//////////////////////////8WYQj8gpCBFQKQYEBRYABgQFGAgwOBhYiI8ZNQUFBQFYAVYQDtVz1gAIA+PWAA/VtQUFZbgXP//////////////////////////xZhCPyCkIEVApBgQFFgAGBAUYCDA4GFiIjxk1BQUFAVgBVhATdXPWAAgD49YAD9W1BQUFb+omVienpyMVggXrQLtiCDX7eWnN113tGIkn1ckn+TumddZbnFI0RCQodkc29sY0MABREAMiKAAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAowJoMOgMYsQlKFgoUAAAAAAAAAAAAAAAAAAAAAAAABLFyBwoDGLEJEAFSRwoJCgIYAxCY4okUCgoKAhhiEODciMYDCgoKAxigBhCatYg3CgoKAxihBhCatYg3CgsKAxitCRDLxaTIBAoJCgMYsQkQoJwB"},{"b64Body":"ChAKCQjf/v6sBhC4FRIDGK0JEgIYAxiW+66dAiICCHgyIMODwq7DgsK3w4PCuXRGOMOCwq5Kw4PCi8ODwpDDg8KOQkgKAxiwCRoiEiAzb2meLC+bbH11ZPGbIL1eGo5nl05T3NqeU6LfytIzMyCQoQ8okE5CBQiAztoDUgBaAGoLY2VsbGFyIGRvb3I=","b64Record":"CiUIFiIDGLIJKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjBhWG5CJJX3+/0jC8JoznBvdQeN01zX/g2yptA2pZ+2BsHL7clBoTTcylrnS0iPjG4aDAib//6sBhCPrp6OAyIQCgkI3/7+rAYQuBUSAxitCSogw4PCrsOCwrfDg8K5dEY4w4LCrkrDg8KLw4PCkMODwo4w1tSRpAJCpgUKAxiyCRLxAmCAYEBSYAQ2EGEAKVdgADVg4ByAY43ZgEMUYQArV4BjrO9gNxRhAFlXWwBbYQBXYASANgNgIIEQFWEAQVdgAID9W4EBkICANZBgIAGQkpGQUFBQYQCnVlsAW2EApWAEgDYDYECBEBVhAG9XYACA/VuBAZCAgDVz//////////////////////////8WkGAgAZCSkZCANZBgIAGQkpGQUFBQYQDxVlsAWzNz//////////////////////////8WYQj8gpCBFQKQYEBRYABgQFGAgwOBhYiI8ZNQUFBQFYAVYQDtVz1gAIA+PWAA/VtQUFZbgXP//////////////////////////xZhCPyCkIEVApBgQFFgAGBAUYCDA4GFiIjxk1BQUFAVgBVhATdXPWAAgD49YAD9W1BQUFb+omVienpyMVggXrQLtiCDX7eWnN113tGIkn1ckn+TumddZbnFI0RCQodkc29sY0MABREAMiKAAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAowJoMOgMYsglKFgoUAAAAAAAAAAAAAAAAAAAAAAAABLJyBwoDGLIJEAFSRwoJCgIYAxCY4okUCgoKAhhiEODciMYDCgoKAxigBhCatYg3CgoKAxihBhCatYg3CgsKAxitCRDLxaTIBAoJCgMYsgkQoJwB"},{"b64Body":"ChAKCQjg/v6sBhDAFRIDGK0JEgIYAxjVgL+gAiICCHgyIMODwq7DgsK3w4PCuXRGOMOCwq5Kw4PCi8ODwpDDg8KOQooBCgMYrwkaIhIgkJ1Kbr3m+c0Ga4gmoZUkDHHiGlaoVQ16XAL4mEsXtSAgkKEPKJBOQgUIgM7aA0pAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABLEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEslIAWgBqC2NlbGxhciBkb29y","b64Record":"CiUIFiIDGLMJKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjDsIw8oKU04L0pst9HHvoBzjkTcPoF22NcW1lS/qY6oev+LBPInl2SJLtWW7WNdRq0aDAic//6sBhDO/pKzASIQCgkI4P7+rAYQwBUSAxitCSogw4PCrsOCwrfDg8K5dEY4w4LCrkrDg8KLw4PCkMODwo4wldqhpwJCzR0KAxizCRKYG2CAYEBSYAQ2EGEAP1dgADVg4ByAY1VV1+QUYQBBV4BjXs8pihRhAM9XgGOdx9sQFGEA/VeAY6mjpjUUYQFLV1sAW2EAzWAEgDYDYICBEBVhAFdXYACA/VuBAZCAgDVz//////////////////////////8WkGAgAZCSkZCANXP//////////////////////////xaQYCABkJKRkIA1c///////////////////////////FpBgIAGQkpGQgDWQYCABkJKRkFBQUGEBeVZbAFthAPtgBIA2A2AggRAVYQDlV2AAgP1bgQGQgIA1kGAgAZCSkZBQUFBhBxNWWwBbYQFJYASANgNgQIEQFWEBE1dgAID9W4EBkICANXP//////////////////////////xaQYCABkJKRkIA1kGAgAZCSkZBQUFBhCC9WWwBbYQF3YASANgNgIIEQFWEBYVdgAID9W4EBkICANZBgIAGQkpGQUFBQYQoPVlsAW4Nz//////////////////////////8WYQj8gpCBFQKQYEBRYABgQFGAgwOBhYiI8ZNQUFBQFYAVYQG/Vz1gAIA+PWAA/VtQYACAkFSQYQEACpAEc///////////////////////////FnP//////////////////////////xZjrO9gN4VgAoSBYQIIV/5bBGBAUYNj/////xZg4BuBUmAEAYCDc///////////////////////////FnP//////////////////////////xaBUmAgAYKBUmAgAZJQUFBgAGBAUYCDA4FgAIeAOxWAFWECcldgAID9W1Ba8RWAFWEChlc9YACAPj1gAP1bUFBQUGABYACQVJBhAQAKkARz//////////////////////////8Wc///////////////////////////FmOs72A3hWAChIFhAtNX/lsEYEBRg2P/////FmDgG4FSYAQBgINz//////////////////////////8Wc///////////////////////////FoFSYCABgoFSYCABklBQUGAAYEBRgIMDgWAAh4A7FYAVYQM9V2AAgP1bUFrxFYAVYQNRVz1gAIA+PWAA/VtQUFBQgnP//////////////////////////xZhCPyCkIEVApBgQFFgAGBAUYCDA4GFiIjxk1BQUFAVgBVhA5tXPWAAgD49YAD9W1BgAICQVJBhAQAKkARz//////////////////////////8Wc///////////////////////////FmOs72A3hGAChIFhA+RX/lsEYEBRg2P/////FmDgG4FSYAQBgINz//////////////////////////8Wc///////////////////////////FoFSYCABgoFSYCABklBQUGAAYEBRgIMDgWAAh4A7FYAVYQROV2AAgP1bUFrxFYAVYQRiVz1gAIA+PWAA/VtQUFBQYAFgAJBUkGEBAAqQBHP//////////////////////////xZz//////////////////////////8WY6zvYDeEYAKEgWEEr1f+WwRgQFGDY/////8WYOAbgVJgBAGAg3P//////////////////////////xZz//////////////////////////8WgVJgIAGCgVJgIAGSUFBQYABgQFGAgwOBYACHgDsVgBVhBRlXYACA/VtQWvEVgBVhBS1XPWAAgD49YAD9W1BQUFCBc///////////////////////////FmEI/IKQgRUCkGBAUWAAYEBRgIMDgYWIiPGTUFBQUBWAFWEFd1c9YACAPj1gAP1bUGAAgJBUkGEBAAqQBHP//////////////////////////xZz//////////////////////////8WY6zvYDeDYAKEgWEFwFf+WwRgQFGDY/////8WYOAbgVJgBAGAg3P//////////////////////////xZz//////////////////////////8WgVJgIAGCgVJgIAGSUFBQYABgQFGAgwOBYACHgDsVgBVhBipXYACA/VtQWvEVgBVhBj5XPWAAgD49YAD9W1BQUFBgAWAAkFSQYQEACpAEc///////////////////////////FnP//////////////////////////xZjrO9gN4NgAoSBYQaLV/5bBGBAUYNj/////xZg4BuBUmAEAYCDc///////////////////////////FnP//////////////////////////xaBUmAgAYKBUmAgAZJQUFBgAGBAUYCDA4FgAIeAOxWAFWEG9VdgAID9W1Ba8RWAFWEHCVc9YACAPj1gAP1bUFBQUFBQUFBWW2AAgJBUkGEBAAqQBHP//////////////////////////xZz//////////////////////////8WY43ZgEOCYEBRgmP/////FmDgG4FSYAQBgIKBUmAgAZFQUGAAYEBRgIMDgWAAh4A7FYAVYQeHV2AAgP1bUFrxFYAVYQebVz1gAIA+PWAA/VtQUFBQYAFgAJBUkGEBAAqQBHP//////////////////////////xZz//////////////////////////8WY43ZgEOCYEBRgmP/////FmDgG4FSYAQBgIKBUmAgAZFQUGAAYEBRgIMDgWAAh4A7FYAVYQgUV2AAgP1bUFrxFYAVYQgoVz1gAIA+PWAA/VtQUFBQUFZbgXP//////////////////////////xZhCPyCkIEVApBgQFFgAGBAUYCDA4GFiIjxk1BQUFAVgBVhCHVXPWAAgD49YAD9W1BgAICQVJBhAQAKkARz//////////////////////////8Wc///////////////////////////FmOs72A3g2AChIFhCL5X/lsEYEBRg2P/////FmDgG4FSYAQBgINz//////////////////////////8Wc///////////////////////////FoFSYCABgoFSYCABklBQUGAAYEBRgIMDgWAAh4A7FYAVYQkoV2AAgP1bUFrxFYAVYQk8Vz1gAIA+PWAA/VtQUFBQYAFgAJBUkGEBAAqQBHP//////////////////////////xZz//////////////////////////8WY6zvYDeDYAKEgWEJiVf+WwRgQFGDY/////8WYOAbgVJgBAGAg3P//////////////////////////xZz//////////////////////////8WgVJgIAGCgVJgIAGSUFBQYABgQFGAgwOBYACHgDsVgBVhCfNXYACA/VtQWvEVgBVhCgdXPWAAgD49YAD9W1BQUFBQUFZbYABgYGAAgJBUkGEBAAqQBHP//////////////////////////xZz//////////////////////////8Wg2BAUWAkAYCCgVJgIAGRUFBgQFFgIIGDAwOBUpBgQFJ/jdmAQwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB7/////////////////////////////////////xkWYCCCAYBRe/////////////////////////////////////+DgYMWF4NSUFBQUGBAUYCCgFGQYCABkICDg1tgIIMQYQsMV4BRglJgIIIBkVBgIIEBkFBgIIMDklBhCulWW2ABg2AgA2EBAAoDgBmCURaBhFEWgIIXhVJQUFBQUFCQUAGRUFBgAGBAUYCDA4GFWvSRUFA9gGAAgRRhC2xXYEBRkVBgHxlgPz0BFoIBYEBSPYJSPWAAYCCEAT5hC3FWW2BgkVBbUJFQkVBgAGBgYAFgAJBUkGEBAAqQBHP//////////////////////////xZz//////////////////////////8WhWBAUWAkAYCCgVJgIAGRUFBgQFFgIIGDAwOBUpBgQFJ/jdmAQwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB7/////////////////////////////////////xkWYCCCAYBRe/////////////////////////////////////+DgYMWF4NSUFBQUGBAUYCCgFGQYCABkICDg1tgIIMQYQx0V4BRglJgIIIBkVBgIIEBkFBgIIMDklBhDFFWW2ABg2AgA2EBAAoDgBmCURaBhFEWgIIXhVJQUFBQUFCQUAGRUFBgAGBAUYCDA4GFWvSRUFA9gGAAgRRhDNRXYEBRkVBgHxlgPz0BFoIBYEBSPYJSPWAAYCCEAT5hDNlWW2BgkVBbUJFQkVCDFYBhDOlXUIEVWxVhDVxXYEBRfwjDeaAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgVJgBAGAgGAgAYKBA4JSYB6BUmAgAYB/RGVsZWdhdGUgdHJhbnNmZXIgY2FsbCBmYWlsZWQhAACBUlBgIAGRUFBgQFGAkQOQ/VtQUFBQUFb+omVienpyMVggYZ1VUlzRf3ObDLJthvfRjxVerEX5U3DZZN6jxw2xTW5kc29sY0MABREAMiKAAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAowJoMOgMYswlKFgoUAAAAAAAAAAAAAAAAAAAAAAAABLNyBwoDGLMJEAFSRwoJCgIYAxCu6rgUCgoKAhhiEJiS48oDCgoKAxigBhDy29M3CgoKAxihBhDy29M3CgsKAxitCRDJ0MTOBAoJCgMYswkQoJwB"},{"b64Body":"ChAKCQjg/v6sBhDCFRIDGK0JEgIYAxjgrLEDIgIIeDIgw4PCrsOCwrfDg8K5dEY4w4LCrkrDg8KLw4PCkMODwo46TwoDGLMJEKCNBiJEncfbEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAASuAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAnEA=","b64Record":"CiUIISIDGLMJKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjAurlXjs8fyaDqySfHS80vls2QQzNzrDU24QZFvdAjuvDA99U0JQ0tvFRyI52SWTfEaDAic//6sBhC8wPaXAyIQCgkI4P7+rAYQwhUSAxitCSogw4PCrsOCwrfDg8K5dEY4w4LCrkrDg8KLw4PCkMODwo4wgNfaAjoIGgIweCiA8QRSFwoJCgIYYhCArrUFCgoKAxitCRD/rbUF"}]},"depositMoreThanBalanceFailsGracefully":{"placeholderNum":1204,"encodedItems":[{"b64Body":"Cg8KCQjl/v6sBhDeFRICGAISAhgDGLXNziwiAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjooBjwESDAihzdmwBhCahL+fAhptCiISIAp7XcMPqNqKtDyqaLArcwLtRL7st005cKs87Uh6aaMPCiM6IQNBRBmq9NwgWzrn0hxPzWMOeREkVwvcnwSqYVe88LwIfwoiEiA6InIRQcq8Grfl2ox7ENS+TPvmvgjoC5eDgVEtIxxllSIMSGVsbG8gV29ybGQhKgAyAA==","b64Record":"CiUIFhoDGLUJKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjB7QvCqkDjTR2EFUhANCRUVTJwjpgSn9+CqEY47oBBLiOoBeDBIkaVK3rEq4CE+Xg4aDAih//6sBhCEr9e4AiIPCgkI5f7+rAYQ3hUSAhgCKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjlIA"},{"b64Body":"Cg8KCQjm/v6sBhDiFRICGAISAhgDGMe6szEiAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjpoBogkKAxi1CSKaCTYwODA2MDQwNTI2MTAyM2E4MDYxMDAxMzYwMDAzOTYwMDBmM2ZlNjA4MDYwNDA1MjYwMDQzNjEwNjEwMDNmNTc2MDAwMzU2MGUwMWM4MDYzMTIwNjVmZTAxNDYxMDA4ZjU3ODA2MzNjY2ZkNjBiMTQ2MTAwYmE1NzgwNjM2ZjY0MjM0ZTE0NjEwMGQxNTc4MDYzYjZiNTVmMjUxNDYxMDEyYzU3NWIzMzczZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjE2N2ZmMWIwM2Y3MDhiOWMzOWY0NTNmZTNmMGNlZjg0MTY0YzdkNmY3ZGY4MzZkZjA3OTZlMWU5YzJiY2U2ZWUzOTdlMzQ2MDQwNTE4MDgyODE1MjYwMjAwMTkxNTA1MDYwNDA1MTgwOTEwMzkwYTIwMDViMzQ4MDE1NjEwMDliNTc2MDAwODBmZDViNTA2MTAwYTQ2MTAxNWE1NjViNjA0MDUxODA4MjgxNTI2MDIwMDE5MTUwNTA2MDQwNTE4MDkxMDM5MGYzNWIzNDgwMTU2MTAwYzY1NzYwMDA4MGZkNWI1MDYxMDBjZjYxMDE2MjU2NWIwMDViMzQ4MDE1NjEwMGRkNTc2MDAwODBmZDViNTA2MTAxMmE2MDA0ODAzNjAzNjA0MDgxMTAxNTYxMDBmNDU3NjAwMDgwZmQ1YjgxMDE5MDgwODAzNTczZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjE2OTA2MDIwMDE5MDkyOTE5MDgwMzU5MDYwMjAwMTkwOTI5MTkwNTA1MDUwNjEwMWFiNTY1YjAwNWI2MTAxNTg2MDA0ODAzNjAzNjAyMDgxMTAxNTYxMDE0MjU3NjAwMDgwZmQ1YjgxMDE5MDgwODAzNTkwNjAyMDAxOTA5MjkxOTA1MDUwNTA2MTAxZjY1NjViMDA1YjYwMDA0NzkwNTA5MDU2NWIzMzczZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjE2NjEwOGZjNDc5MDgxMTUwMjkwNjA0MDUxNjAwMDYwNDA1MTgwODMwMzgxODU4ODg4ZjE5MzUwNTA1MDUwMTU4MDE1NjEwMWE4NTczZDYwMDA4MDNlM2Q2MDAwZmQ1YjUwNTY1YjgxNzNmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmMTY2MTA4ZmM4MjkwODExNTAyOTA2MDQwNTE2MDAwNjA0MDUxODA4MzAzODE4NTg4ODhmMTkzNTA1MDUwNTAxNTgwMTU2MTAxZjE1NzNkNjAwMDgwM2UzZDYwMDBmZDViNTA1MDUwNTY1YjgwMzQxNDYxMDIwMjU3NjAwMDgwZmQ1YjUwNTZmZWEyNjU2MjdhN2E3MjMxNTgyMGY4Zjg0ZmMzMWE4NDUwNjRiNTc4MWU5MDgzMTZmM2M1OTExNTc5NjJkZWFiYjBmZDQyNGVkNTRmMjU2NDAwZjk2NDczNmY2YzYzNDMwMDA1MTEwMDMy","b64Record":"CiAIFiocCgwIARAMGgYIgK6ZpA8SDAgBEA8aBgiArpmkDxIwNkw5kJY8gK7Ezbl1t1pzOh/J2+VminJgkqZcelHHBs2FB+8hiKpwQGkfMY0yX102GgsIov/+rAYQp8TUQCIPCgkI5v7+rAYQ4hUSAhgCKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjlIA"},{"b64Body":"Cg8KCQjm/v6sBhDkFRICGAISAhgDGPuV9hQiAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjlowCiISIAfx73siuN6gkN5jzF7uOmGaqA8YD6HpA/HEMWmreGwGEP/B1y9KBQiAztoD","b64Record":"CiUIFhIDGLYJKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjC/4/ifUuKv4PxsiKqQ37IF9HIUMRsd/CGsbvBRZG93N/MbQU56gsBV08CwiGoX18gaDAii//6sBhDJ/pHCAiIPCgkI5v7+rAYQ5BUSAhgCKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjlIXCgkKAhgCEP2Dr18KCgoDGLYJEP6Dr18="},{"b64Body":"Cg8KCQjn/v6sBhDmFRICGAISAhgDGK/Dto0EIgIIeDIgw4PCrsOCwrfDg8K5dEY4w4LCrkrDg8KLw4PCkMODwo5ClgEKAxi1CRpzKnEIAhJtCiISIE2WPy/kQCtcfD4PxulwBxa0oM//jSGPkWiTqgB+v+7cCiM6IQMOioCzwFkAojYwh9Z+KJ/w3CBgiH+M58dHWAE/RVwS1goiEiAThz+pkt/aZpcYMSTawyiZM18hMSILUI3nukdQ1TDyISCQoQ9CBQiAztoDUgBaAGoLY2VsbGFyIGRvb3I=","b64Record":"CiUIFiIDGLcJKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjBg50CwzCQ7jlRoAtn+VJZkl+bIUVaxLaVlCSAubGzDkd+Mzmo5ZVb9wR87n2BhRawaCwij//6sBhD0wZ1KIg8KCQjn/v6sBhDmFRICGAIqIMODwq7DgsK3w4PCuXRGOMOCwq5Kw4PCi8ODwpDDg8KOMMDZ4gZC7wYKAxi3CRK6BGCAYEBSYAQ2EGEAP1dgADVg4ByAYxIGX+AUYQCPV4BjPM/WCxRhALpXgGNvZCNOFGEA0VeAY7a1XyUUYQEsV1szc///////////////////////////Fn/xsD9wi5w59FP+PwzvhBZMfW99+DbfB5bh6cK85u45fjRgQFGAgoFSYCABkVBQYEBRgJEDkKIAWzSAFWEAm1dgAID9W1BhAKRhAVpWW2BAUYCCgVJgIAGRUFBgQFGAkQOQ81s0gBVhAMZXYACA/VtQYQDPYQFiVlsAWzSAFWEA3VdgAID9W1BhASpgBIA2A2BAgRAVYQD0V2AAgP1bgQGQgIA1c///////////////////////////FpBgIAGQkpGQgDWQYCABkJKRkFBQUGEBq1ZbAFthAVhgBIA2A2AggRAVYQFCV2AAgP1bgQGQgIA1kGAgAZCSkZBQUFBhAfZWWwBbYABHkFCQVlszc///////////////////////////FmEI/EeQgRUCkGBAUWAAYEBRgIMDgYWIiPGTUFBQUBWAFWEBqFc9YACAPj1gAP1bUFZbgXP//////////////////////////xZhCPyCkIEVApBgQFFgAGBAUYCDA4GFiIjxk1BQUFAVgBVhAfFXPWAAgD49YAD9W1BQUFZbgDQUYQICV2AAgP1bUFb+omVienpyMVgg+PhPwxqEUGS1eB6QgxbzxZEVeWLeq7D9Qk7VTyVkAPlkc29sY0MABREAMiKAAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAowJoMOgMYtwlKFgoUAAAAAAAAAAAAAAAAAAAAAAAABLdyBwoDGLcJEAFSFgoJCgIYAhD/ssUNCgkKAhhiEICzxQ0="}]},"LowLevelEcrecCallBehavior":{"placeholderNum":1208,"encodedItems":[{"b64Body":"Cg8KCQjr/v6sBhD4FRICGAISAhgDGP7LzSwiAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjooBjwESDAinzdmwBhDOg9S1AxptCiISIA3x5I1hqhZqHXwK/HATF2OI6Nhg4JtsgQbhotqD7QR0CiM6IQOg93FxCTxBWSLLUUFvwgx5b9C+e6/9TiqJ7Ek4sVBoVQoiEiBmHgXQQTjvftSdB4G7MeexD8WL8ZXi03V1fkbAT6CNGyIMSGVsbG8gV29ybGQhKgAyAA==","b64Record":"CiUIFhoDGLkJKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjDY3M+syHFEjdfOJo03xodeeHad08k79+Ata/1jbOpWZzp9Vooh2JH1oblrzFa3M+caDAin//6sBhCHnb7HAyIPCgkI6/7+rAYQ+BUSAhgCKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjlIA"},{"b64Body":"Cg8KCQjs/v6sBhD8FRICGAISAhgDGIudjj4iAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjpoBiCAKAxi5CSKAIDYwODA2MDQwNTI2MDQwNTE2MTBiZjMzODAzODA2MTBiZjM4MzM5ODE4MTAxNjA0MDUyODEwMTkwNjEwMDI1OTE5MDYxMDEwYzU2NWI4MTYwMDA4MDYxMDEwMDBhODE1NDgxNzNmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmMDIxOTE2OTA4MzczZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjE2MDIxNzkwNTU1MDgwNjAwMTgxOTA1NTUwNTA1MDYxMDE0YzU2NWI2MDAwODBmZDViNjAwMDczZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjgyMTY5MDUwOTE5MDUwNTY1YjYwMDA2MTAwYTM4MjYxMDA3ODU2NWI5MDUwOTE5MDUwNTY1YjYxMDBiMzgxNjEwMDk4NTY1YjgxMTQ2MTAwYmU1NzYwMDA4MGZkNWI1MDU2NWI2MDAwODE1MTkwNTA2MTAwZDA4MTYxMDBhYTU2NWI5MjkxNTA1MDU2NWI2MDAwODE5MDUwOTE5MDUwNTY1YjYxMDBlOTgxNjEwMGQ2NTY1YjgxMTQ2MTAwZjQ1NzYwMDA4MGZkNWI1MDU2NWI2MDAwODE1MTkwNTA2MTAxMDY4MTYxMDBlMDU2NWI5MjkxNTA1MDU2NWI2MDAwODA2MDQwODM4NTAzMTIxNTYxMDEyMzU3NjEwMTIyNjEwMDczNTY1YjViNjAwMDYxMDEzMTg1ODI4NjAxNjEwMGMxNTY1YjkyNTA1MDYwMjA2MTAxNDI4NTgyODYwMTYxMDBmNzU2NWI5MTUwNTA5MjUwOTI5MDUwNTY1YjYxMGE5ODgwNjEwMTViNjAwMDM5NjAwMGYzZmU2MDgwNjA0MDUyNjAwNDM2MTA2MTAwOWM1NzYwMDAzNTYwZTAxYzgwNjM3ZDNkMWMyZjExNjEwMDY0NTc4MDYzN2QzZDFjMmYxNDYxMDEzNDU3ODA2MzhkYTVjYjViMTQ2MTAxNWQ1NzgwNjM5MzkyNTAxNTE0NjEwMTg4NTc4MDYzOWEyZDhkNWMxNDYxMDFjNTU3ODA2M2MyOTg1NTc4MTQ2MTAxZjU1NzgwNjNkNWJkNWU0OTE0NjEwMjIwNTc2MTAwOWM1NjViODA2MzA0MTg3OTUxMTQ2MTAwYTE1NzgwNjMxMjA2NWZlMDE0NjEwMGFiNTc4MDYzNWUwZmY5MDgxNDYxMDBkNjU3ODA2MzcwYTA4MjMxMTQ2MTAwZTA1NzgwNjM3MzdiYzNjOTE0NjEwMTFkNTc1YjYwMDA4MGZkNWI2MTAwYTk2MTAyNWQ1NjViMDA1YjM0ODAxNTYxMDBiNzU3NjAwMDgwZmQ1YjUwNjEwMGMwNjEwM2U0NTY1YjYwNDA1MTYxMDBjZDkxOTA2MTA3ODk1NjViNjA0MDUxODA5MTAzOTBmMzViNjEwMGRlNjEwM2VjNTY1YjAwNWIzNDgwMTU2MTAwZWM1NzYwMDA4MGZkNWI1MDYxMDEwNzYwMDQ4MDM2MDM4MTAxOTA2MTAxMDI5MTkwNjEwODA3NTY1YjYxMDU3NTU2NWI2MDQwNTE2MTAxMTQ5MTkwNjEwNzg5NTY1YjYwNDA1MTgwOTEwMzkwZjM1YjM0ODAxNTYxMDEyOTU3NjAwMDgwZmQ1YjUwNjEwMTMyNjEwNTk2NTY1YjAwNWIzNDgwMTU2MTAxNDA1NzYwMDA4MGZkNWI1MDYxMDE1YjYwMDQ4MDM2MDM4MTAxOTA2MTAxNTY5MTkwNjEwODcyNTY1YjYxMDVjZjU2NWIwMDViMzQ4MDE1NjEwMTY5NTc2MDAwODBmZDViNTA2MTAxNzI2MTA1ZTg1NjViNjA0MDUxNjEwMTdmOTE5MDYxMDhhZTU2NWI2MDQwNTE4MDkxMDM5MGYzNWIzNDgwMTU2MTAxOTQ1NzYwMDA4MGZkNWI1MDYxMDFhZjYwMDQ4MDM2MDM4MTAxOTA2MTAxYWE5MTkwNjEwODA3NTY1YjYxMDYwYzU2NWI2MDQwNTE2MTAxYmM5MTkwNjEwOGU0NTY1YjYwNDA1MTgwOTEwMzkwZjM1YjYxMDFkZjYwMDQ4MDM2MDM4MTAxOTA2MTAxZGE5MTkwNjEwODA3NTY1YjYxMDY3ZjU2NWI2MDQwNTE2MTAxZWM5MTkwNjEwOGU0NTY1YjYwNDA1MTgwOTEwMzkwZjM1YjM0ODAxNTYxMDIwMTU3NjAwMDgwZmQ1YjUwNjEwMjBhNjEwNmY1NTY1YjYwNDA1MTYxMDIxNzkxOTA2MTA3ODk1NjViNjA0MDUxODA5MTAzOTBmMzViMzQ4MDE1NjEwMjJjNTc2MDAwODBmZDViNTA2MTAyNDc2MDA0ODAzNjAzODEwMTkwNjEwMjQyOTE5MDYxMDgwNzU2NWI2MTA2ZmI1NjViNjA0MDUxNjEwMjU0OTE5MDYxMDhlNDU2NWI2MDQwNTE4MDkxMDM5MGYzNWI2MDAwN2Y2ODYxNzM2ODAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwOTA1MDYwMDA2MDAxOTA1MDYwMDA3ZjcyMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDA5MDUwNjAwMDdmNzMwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDkwNTA2MDAwNjAwMTczZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjE2ODU4NTg1ODU2MDQwNTE2MDI0MDE2MTAzMDM5NDkzOTI5MTkwNjEwOTM0NTY1YjYwNDA1MTYwMjA4MTgzMDMwMzgxNTI5MDYwNDA1MjdmOTZkMTA3ZjYwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDdiZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxOTE2NjAyMDgyMDE4MDUxN2JmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjgzODE4MzE2MTc4MzUyNTA1MDUwNTA2MDQwNTE2MTAzOGQ5MTkwNjEwOWVhNTY1YjYwMDA2MDQwNTE4MDgzMDM4MTYwMDA4NjVhZjE5MTUwNTAzZDgwNjAwMDgxMTQ2MTAzY2E1NzYwNDA1MTkxNTA2MDFmMTk2MDNmM2QwMTE2ODIwMTYwNDA1MjNkODI1MjNkNjAwMDYwMjA4NDAxM2U2MTAzY2Y1NjViNjA2MDkxNTA1YjUwNTA5MDUwODA2MTAzZGQ1NzYwMDA4MGZkNWI1MDUwNTA1MDUwNTY1YjYwMDA0NzkwNTA5MDU2NWI2MDAwN2Y2ODYxNzM2ODAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwOTA1MDYwMDA2MDAxOTA1MDYwMDA3ZjcyMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDA5MDUwNjAwMDdmNzMwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDkwNTA2MDAwNjAwMTczZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjE2NjAwMTg2ODY4Njg2NjA0MDUxNjAyNDAxNjEwNDk0OTQ5MzkyOTE5MDYxMDkzNDU2NWI2MDQwNTE2MDIwODE4MzAzMDM4MTUyOTA2MDQwNTI3Zjk2ZDEwN2Y2MDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDA3YmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmMTkxNjYwMjA4MjAxODA1MTdiZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmY4MzgxODMxNjE3ODM1MjUwNTA1MDUwNjA0MDUxNjEwNTFlOTE5MDYxMDllYTU2NWI2MDAwNjA0MDUxODA4MzAzODE4NTg3NWFmMTkyNTA1MDUwM2Q4MDYwMDA4MTE0NjEwNTViNTc2MDQwNTE5MTUwNjAxZjE5NjAzZjNkMDExNjgyMDE2MDQwNTIzZDgyNTIzZDYwMDA2MDIwODQwMTNlNjEwNTYwNTY1YjYwNjA5MTUwNWI1MDUwOTA1MDgwNjEwNTZlNTc2MDAwODBmZDViNTA1MDUwNTA1MDU2NWI2MDAwODE3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNjMxOTA1MDkxOTA1MDU2NWI2MDAwODA1NDkwNjEwMTAwMGE5MDA0NzNmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmMTY3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNmZmNWI4MDczZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjE2ZmY1YjYwMDA4MDU0OTA2MTAxMDAwYTkwMDQ3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNjgxNTY1YjYwMDA4MDgyNzNmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmMTY2MDQwNTE2MTA2MzI5MDYxMGE0ZDU2NWI2MDAwNjA0MDUxODA4MzAzODE4NTVhZjQ5MTUwNTAzZDgwNjAwMDgxMTQ2MTA2NmQ1NzYwNDA1MTkxNTA2MDFmMTk2MDNmM2QwMTE2ODIwMTYwNDA1MjNkODI1MjNkNjAwMDYwMjA4NDAxM2U2MTA2NzI1NjViNjA2MDkxNTA1YjUwNTA5MDUwODA5MTUwNTA5MTkwNTA1NjViNjAwMDgwODI3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNjM0NjA0MDUxNjEwNmE2OTA2MTBhNGQ=","b64Record":"CiAIFiocCgwIARAMGgYIgK6ZpA8SDAgBEA8aBgiArpmkDxIwuaAv0qR+u+bRENN9GxKsKbvQ0ZcoFP0uykGyMXOpLzDjD2UZSsM9V1Y3gJsdl23vGgwIqP/+rAYQwODUzwEiDwoJCOz+/qwGEPwVEgIYAiogw4PCrsOCwrfDg8K5dEY4w4LCrkrDg8KLw4PCkMODwo5SAA=="},{"b64Body":"Cg8KCQjs/v6sBhCCFhICGAISAhgDGPeT3jUiAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjoIB7g8SAxi5CSLmDzU2NWI2MDAwNjA0MDUxODA4MzAzODE4NTg3NWFmMTkyNTA1MDUwM2Q4MDYwMDA4MTE0NjEwNmUzNTc2MDQwNTE5MTUwNjAxZjE5NjAzZjNkMDExNjgyMDE2MDQwNTIzZDgyNTIzZDYwMDA2MDIwODQwMTNlNjEwNmU4NTY1YjYwNjA5MTUwNWI1MDUwOTA1MDgwOTE1MDUwOTE5MDUwNTY1YjYwMDE1NDgxNTY1YjYwMDA4MDgyNzNmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmMTY2MDQwNTE2MTA3MjE5MDYxMGE0ZDU2NWI2MDAwNjA0MDUxODA4MzAzODE2MDAwODY1YWYxOTE1MDUwM2Q4MDYwMDA4MTE0NjEwNzVlNTc2MDQwNTE5MTUwNjAxZjE5NjAzZjNkMDExNjgyMDE2MDQwNTIzZDgyNTIzZDYwMDA2MDIwODQwMTNlNjEwNzYzNTY1YjYwNjA5MTUwNWI1MDUwOTA1MDgwOTE1MDUwOTE5MDUwNTY1YjYwMDA4MTkwNTA5MTkwNTA1NjViNjEwNzgzODE2MTA3NzA1NjViODI1MjUwNTA1NjViNjAwMDYwMjA4MjAxOTA1MDYxMDc5ZTYwMDA4MzAxODQ2MTA3N2E1NjViOTI5MTUwNTA1NjViNjAwMDgwZmQ1YjYwMDA3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmY4MjE2OTA1MDkxOTA1MDU2NWI2MDAwNjEwN2Q0ODI2MTA3YTk1NjViOTA1MDkxOTA1MDU2NWI2MTA3ZTQ4MTYxMDdjOTU2NWI4MTE0NjEwN2VmNTc2MDAwODBmZDViNTA1NjViNjAwMDgxMzU5MDUwNjEwODAxODE2MTA3ZGI1NjViOTI5MTUwNTA1NjViNjAwMDYwMjA4Mjg0MDMxMjE1NjEwODFkNTc2MTA4MWM2MTA3YTQ1NjViNWI2MDAwNjEwODJiODQ4Mjg1MDE2MTA3ZjI1NjViOTE1MDUwOTI5MTUwNTA1NjViNjAwMDYxMDgzZjgyNjEwN2E5NTY1YjkwNTA5MTkwNTA1NjViNjEwODRmODE2MTA4MzQ1NjViODExNDYxMDg1YTU3NjAwMDgwZmQ1YjUwNTY1YjYwMDA4MTM1OTA1MDYxMDg2YzgxNjEwODQ2NTY1YjkyOTE1MDUwNTY1YjYwMDA2MDIwODI4NDAzMTIxNTYxMDg4ODU3NjEwODg3NjEwN2E0NTY1YjViNjAwMDYxMDg5Njg0ODI4NTAxNjEwODVkNTY1YjkxNTA1MDkyOTE1MDUwNTY1YjYxMDhhODgxNjEwN2M5NTY1YjgyNTI1MDUwNTY1YjYwMDA2MDIwODIwMTkwNTA2MTA4YzM2MDAwODMwMTg0NjEwODlmNTY1YjkyOTE1MDUwNTY1YjYwMDA4MTE1MTU5MDUwOTE5MDUwNTY1YjYxMDhkZTgxNjEwOGM5NTY1YjgyNTI1MDUwNTY1YjYwMDA2MDIwODIwMTkwNTA2MTA4Zjk2MDAwODMwMTg0NjEwOGQ1NTY1YjkyOTE1MDUwNTY1YjYwMDA4MTkwNTA5MTkwNTA1NjViNjEwOTEyODE2MTA4ZmY1NjViODI1MjUwNTA1NjViNjAwMDYwZmY4MjE2OTA1MDkxOTA1MDU2NWI2MTA5MmU4MTYxMDkxODU2NWI4MjUyNTA1MDU2NWI2MDAwNjA4MDgyMDE5MDUwNjEwOTQ5NjAwMDgzMDE4NzYxMDkwOTU2NWI2MTA5NTY2MDIwODMwMTg2NjEwOTI1NTY1YjYxMDk2MzYwNDA4MzAxODU2MTA5MDk1NjViNjEwOTcwNjA2MDgzMDE4NDYxMDkwOTU2NWI5NTk0NTA1MDUwNTA1MDU2NWI2MDAwODE1MTkwNTA5MTkwNTA1NjViNjAwMDgxOTA1MDkyOTE1MDUwNTY1YjYwMDA1YjgzODExMDE1NjEwOWFkNTc4MDgyMDE1MTgxODQwMTUyNjAyMDgxMDE5MDUwNjEwOTkyNTY1YjYwMDA4NDg0MDE1MjUwNTA1MDUwNTY1YjYwMDA2MTA5YzQ4MjYxMDk3OTU2NWI2MTA5Y2U4MTg1NjEwOTg0NTY1YjkzNTA2MTA5ZGU4MTg1NjAyMDg2MDE2MTA5OGY1NjViODA4NDAxOTE1MDUwOTI5MTUwNTA1NjViNjAwMDYxMDlmNjgyODQ2MTA5Yjk1NjViOTE1MDgxOTA1MDkyOTE1MDUwNTY1YjdmNjI2ZjZmMjg3NTY5NmU3NDMyMzUzNjI5MDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDYwMDA4MjAxNTI1MDU2NWI2MDAwNjEwYTM3NjAwYzgzNjEwOTg0NTY1YjkxNTA2MTBhNDI4MjYxMGEwMTU2NWI2MDBjODIwMTkwNTA5MTkwNTA1NjViNjAwMDYxMGE1ODgyNjEwYTJhNTY1YjkxNTA4MTkwNTA5MTkwNTA1NmZlYTI2NDY5NzA2NjczNTgyMjEyMjBlNWUwOWYyYzA3NGFmMDRlNWExNGJhMDkwMGUwZTg4MDE4NjgwZGQ3NDcwYmMyNjg4ODZlYWNiMDdmYzNlZDljNjQ3MzZmNmM2MzQzMDAwODEzMDAzMw==","b64Record":"CiAIFiocCgwIARAMGgYIgK6ZpA8SDAgBEA8aBgiArpmkDxIw1xUzZmpdID6m7uiw5+v6h10HfhtOrQZJru3Hwl259KxsuW00GjO28MP7Y4aDUzn8GgwIqP/+rAYQv7uV0QMiDwoJCOz+/qwGEIIWEgIYAiogw4PCrsOCwrfDg8K5dEY4w4LCrkrDg8KLw4PCkMODwo5SAA=="},{"b64Body":"Cg8KCQjt/v6sBhCEFhICGAISAhgDGNWAv6ACIgIIeDIgw4PCrsOCwrfDg8K5dEY4w4LCrkrDg8KLw4PCkMODwo5CjAEKAxi5CRoiEiAmRxO48gDohSidiGElGYWDtcjX7CcagU7Qi5Xe7LqZiiCQoQ8ogMLXL0IFCIDO2gNKQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFSAFoAagtjZWxsYXIgZG9vcg==","b64Record":"CiUIFiIDGLoJKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjBjrkie8Y2AuPuOvcvuKEitlx31UDlkNPJshyMwyCxk9Iapz7wIf/fNVpDTa2y5fOMaDAip//6sBhDH27vZASIPCgkI7f7+rAYQhBYSAhgCKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjjDA2eIGQs0XCgMYugkSmBVggGBAUmAENhBhAJxXYAA1YOAcgGN9PRwvEWEAZFeAY309HC8UYQE0V4BjjaXLWxRhAV1XgGOTklAVFGEBiFeAY5otjVwUYQHFV4BjwphVeBRhAfVXgGPVvV5JFGECIFdhAJxWW4BjBBh5URRhAKFXgGMSBl/gFGEAq1eAY14P+QgUYQDWV4BjcKCCMRRhAOBXgGNze8PJFGEBHVdbYACA/VthAKlhAl1WWwBbNIAVYQC3V2AAgP1bUGEAwGED5FZbYEBRYQDNkZBhB4lWW2BAUYCRA5DzW2EA3mED7FZbAFs0gBVhAOxXYACA/VtQYQEHYASANgOBAZBhAQKRkGEIB1ZbYQV1VltgQFFhARSRkGEHiVZbYEBRgJEDkPNbNIAVYQEpV2AAgP1bUGEBMmEFllZbAFs0gBVhAUBXYACA/VtQYQFbYASANgOBAZBhAVaRkGEIclZbYQXPVlsAWzSAFWEBaVdgAID9W1BhAXJhBehWW2BAUWEBf5GQYQiuVltgQFGAkQOQ81s0gBVhAZRXYACA/VtQYQGvYASANgOBAZBhAaqRkGEIB1ZbYQYMVltgQFFhAbyRkGEI5FZbYEBRgJEDkPNbYQHfYASANgOBAZBhAdqRkGEIB1ZbYQZ/VltgQFFhAeyRkGEI5FZbYEBRgJEDkPNbNIAVYQIBV2AAgP1bUGECCmEG9VZbYEBRYQIXkZBhB4lWW2BAUYCRA5DzWzSAFWECLFdgAID9W1BhAkdgBIA2A4EBkGECQpGQYQgHVlthBvtWW2BAUWECVJGQYQjkVltgQFGAkQOQ81tgAH9oYXNoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAJBQYABgAZBQYAB/cgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACQUGAAf3MAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAkFBgAGABc///////////////////////////FoWFhYVgQFFgJAFhAwOUk5KRkGEJNFZbYEBRYCCBgwMDgVKQYEBSf5bRB/YAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAe/////////////////////////////////////8ZFmAgggGAUXv/////////////////////////////////////g4GDFheDUlBQUFBgQFFhA42RkGEJ6lZbYABgQFGAgwOBYACGWvGRUFA9gGAAgRRhA8pXYEBRkVBgHxlgPz0BFoIBYEBSPYJSPWAAYCCEAT5hA89WW2BgkVBbUFCQUIBhA91XYACA/VtQUFBQUFZbYABHkFCQVltgAH9oYXNoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAJBQYABgAZBQYAB/cgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACQUGAAf3MAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAkFBgAGABc///////////////////////////FmABhoaGhmBAUWAkAWEElJSTkpGQYQk0VltgQFFgIIGDAwOBUpBgQFJ/ltEH9gAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB7/////////////////////////////////////xkWYCCCAYBRe/////////////////////////////////////+DgYMWF4NSUFBQUGBAUWEFHpGQYQnqVltgAGBAUYCDA4GFh1rxklBQUD2AYACBFGEFW1dgQFGRUGAfGWA/PQEWggFgQFI9glI9YABgIIQBPmEFYFZbYGCRUFtQUJBQgGEFbldgAID9W1BQUFBQVltgAIFz//////////////////////////8WMZBQkZBQVltgAIBUkGEBAAqQBHP//////////////////////////xZz//////////////////////////8W/1uAc///////////////////////////Fv9bYACAVJBhAQAKkARz//////////////////////////8WgVZbYACAgnP//////////////////////////xZgQFFhBjKQYQpNVltgAGBAUYCDA4GFWvSRUFA9gGAAgRRhBm1XYEBRkVBgHxlgPz0BFoIBYEBSPYJSPWAAYCCEAT5hBnJWW2BgkVBbUFCQUICRUFCRkFBWW2AAgIJz//////////////////////////8WNGBAUWEGppBhCk1WW2AAYEBRgIMDgYWHWvGSUFBQPYBgAIEUYQbjV2BAUZFQYB8ZYD89ARaCAWBAUj2CUj1gAGAghAE+YQboVltgYJFQW1BQkFCAkVBQkZBQVltgAVSBVltgAICCc///////////////////////////FmBAUWEHIZBhCk1WW2AAYEBRgIMDgWAAhlrxkVBQPYBgAIEUYQdeV2BAUZFQYB8ZYD89ARaCAWBAUj2CUj1gAGAghAE+YQdjVltgYJFQW1BQkFCAkVBQkZBQVltgAIGQUJGQUFZbYQeDgWEHcFZbglJQUFZbYABgIIIBkFBhB55gAIMBhGEHelZbkpFQUFZbYACA/VtgAHP//////////////////////////4IWkFCRkFBWW2AAYQfUgmEHqVZbkFCRkFBWW2EH5IFhB8lWW4EUYQfvV2AAgP1bUFZbYACBNZBQYQgBgWEH21ZbkpFQUFZbYABgIIKEAxIVYQgdV2EIHGEHpFZbW2AAYQgrhIKFAWEH8lZbkVBQkpFQUFZbYABhCD+CYQepVluQUJGQUFZbYQhPgWEINFZbgRRhCFpXYACA/VtQVltgAIE1kFBhCGyBYQhGVluSkVBQVltgAGAggoQDEhVhCIhXYQiHYQekVltbYABhCJaEgoUBYQhdVluRUFCSkVBQVlthCKiBYQfJVluCUlBQVltgAGAgggGQUGEIw2AAgwGEYQifVluSkVBQVltgAIEVFZBQkZBQVlthCN6BYQjJVluCUlBQVltgAGAgggGQUGEI+WAAgwGEYQjVVluSkVBQVltgAIGQUJGQUFZbYQkSgWEI/1ZbglJQUFZbYABg/4IWkFCRkFBWW2EJLoFhCRhWW4JSUFBWW2AAYICCAZBQYQlJYACDAYdhCQlWW2EJVmAggwGGYQklVlthCWNgQIMBhWEJCVZbYQlwYGCDAYRhCQlWW5WUUFBQUFBWW2AAgVGQUJGQUFZbYACBkFCSkVBQVltgAFuDgRAVYQmtV4CCAVGBhAFSYCCBAZBQYQmSVltgAISEAVJQUFBQVltgAGEJxIJhCXlWW2EJzoGFYQmEVluTUGEJ3oGFYCCGAWEJj1ZbgIQBkVBQkpFQUFZbYABhCfaChGEJuVZbkVCBkFCSkVBQVlt/Ym9vKHVpbnQyNTYpAAAAAAAAAAAAAAAAAAAAAAAAAABgAIIBUlBWW2AAYQo3YAyDYQmEVluRUGEKQoJhCgFWW2AMggGQUJGQUFZbYABhCliCYQoqVluRUIGQUJGQUFb+omRpcGZzWCISIOXgnywHSvBOWhS6CQDg6IAYaA3XRwvCaIhurLB/w+2cZHNvbGNDAAgTADMigAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKMCaDDoDGLoJShYKFAAAAAAAAAAAAAAAAAAAAAAAAAS6cgcKAxi6CRABUiIKCQoCGAIQ/7b0bAoJCgIYYhCAs8UNCgoKAxi6CRCAhK9f"},{"b64Body":"Cg8KCQjt/v6sBhCGFhICGAISAhgDGPuV9hQiAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjloxCiISIBv/Yyr64KJ2eGQfOYMEMATB5vHHlTzmHGrSdycR5YKVEICU69wDSgUIgM7aAw==","b64Record":"CiUIFhIDGLsJKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjAYoKEhg36J1kYq7OcKOvvVyjGmtsnDtPqR2aLB2PZgMro1vVfM1dlIIGMNx6at5ocaDAip//6sBhCihNTaAyIPCgkI7f7+rAYQhhYSAhgCKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjlIZCgoKAhgCEP+n1rkHCgsKAxi7CRCAqNa5Bw=="},{"b64Body":"Cg8KCQju/v6sBhCMFhICGAISAhgDGK32CiICCHgyIMODwq7DgsK3w4PCuXRGOMOCwq5Kw4PCi8ODwpDDg8KOeggSAhgBagIIAQ==","b64Record":"CiAIFiocCgwIARAMGgYIgK6ZpA8SDAgBEA8aBgiArpmkDxIwqbfLBLkeoKqiSln2tfrRlRLEmJAOMOtt8dZLgmnaDuraGdF+PXrEkZDOPTTLjYeJGgwIqv/+rAYQmdvN4gEiDwoJCO7+/qwGEIwWEgIYAiogw4PCrsOCwrfDg8K5dEY4w4LCrkrDg8KLw4PCkMODwo5SAA=="},{"b64Body":"ChAKCQju/v6sBhCOFhIDGLsJEgIYAxjgrLEDIgIIeDIgw4PCrsOCwrfDg8K5dEY4w4LCrkrDg8KLw4PCkMODwo46DwoDGLoJEKCNBiIEBBh5UQ==","b64Record":"CiUIFiIDGLoJKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjDqIUUMx2hR34L1asgwiJ4wQzV9vBN7RI78AKBtI7vdBFS3izGPHWUDAN74WoG0wj8aCwir//6sBhCj77QHIhAKCQju/v6sBhCOFhIDGLsJKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjjCA19oCOowCCgMYugkigAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKIDxBFIXCgkKAhhiEICutQUKCgoDGLsJEP+ttQU="},{"b64Body":"ChAKCQjv/v6sBhCQFhIDGLsJEgIYAxjgrLEDIgIIeDIgw4PCrsOCwrfDg8K5dEY4w4LCrkrDg8KLw4PCkMODwo46DwoDGLoJEKCNBiIEXg/5CA==","b64Record":"CiUIISIDGLoJKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjAfmJv7qLC98eEjOUR95FfcSYeanfn+qpB8K4Ftb0SsNT1TjUMoamjJtjTgB5PjZ6EaDAir//6sBhD/pJfxASIQCgkI7/7+rAYQkBYSAxi7CSogw4PCrsOCwrfDg8K5dEY4w4LCrkrDg8KLw4PCkMODwo4wv+CuAzoIGgIweCjJiAZSFwoJCgIYYhD+wN0GCgoKAxi7CRD9wN0G"},{"b64Body":"Cg8KCQjv/v6sBhCWFhICGAISAhgDGK32CiICCHgyIMODwq7DgsK3w4PCuXRGOMOCwq5Kw4PCi8ODwpDDg8KOegYSAhgBagA=","b64Record":"CiAIFiocCgwIARAMGgYIgK6ZpA8SDAgBEA8aBgiArpmkDxIwZrTPpuX5b+V25vvoPKHqsIi6Q8tCdHPr0OiateSL8zNuJCVkMsxSrcdqsIpZ/d21GgwIq//+rAYQ8p701QMiDwoJCO/+/qwGEJYWEgIYAiogw4PCrsOCwrfDg8K5dEY4w4LCrkrDg8KLw4PCkMODwo5SAA=="},{"b64Body":"ChAKCQjw/v6sBhCYFhIDGLsJEgIYAxjgrLEDIgIIeDIgw4PCrsOCwrfDg8K5dEY4w4LCrkrDg8KLw4PCkMODwo46DwoDGLoJEKCNBiIEBBh5UQ==","b64Record":"CiUIFiIDGLoJKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjB9+jX8/cHIB18Vi5WA2GeqaT8z4mohG6DZgStqa3U1c7FYpyCrRCrTM2AQFOGAzZQaDAis//6sBhCYwOn6ASIQCgkI8P7+rAYQmBYSAxi7CSogw4PCrsOCwrfDg8K5dEY4w4LCrkrDg8KLw4PCkMODwo4wgNfaAjqMAgoDGLoJIoACAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACiA8QRSFwoJCgIYYhCArrUFCgoKAxi7CRD/rbUF"},{"b64Body":"ChAKCQjw/v6sBhCaFhIDGLsJEgIYAxjgrLEDIgIIeDIgw4PCrsOCwrfDg8K5dEY4w4LCrkrDg8KLw4PCkMODwo46DwoDGLoJEKCNBiIEXg/5CA==","b64Record":"CiUIISIDGLoJKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjBiTbV+N/BaM8p5qQdSxbfk6vF/JX2C9ZS9dyD4SZsrQ4a04iYTzlEY7p+/iA7e+yYaCwit//6sBhCy/94CIhAKCQjw/v6sBhCaFhIDGLsJKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjjC/4K4DOggaAjB4KMmIBlIXCgkKAhhiEP7A3QYKCgoDGLsJEP3A3QY="}]},"callsToSystemEntityNumsAreTreatedAsPrecompileCalls":{"placeholderNum":1212,"encodedItems":[{"b64Body":"Cg8KCQj1/v6sBhCqFhICGAISAhgDGLXNziwiAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjooBjwESDAixzdmwBhDMmeLeAhptCiISIAM28Vj5H7U+O2iKUajc/wJQ3ZqR4/V+ltnYwahrJDmfCiM6IQNKYNP5wdnYjfOZGUnEcktMrFENoEdVXp5/F+R6iqC8TQoiEiA0svt3uDmgeLDac6cRgrMAFrz4M0gJGLmLHk5EWgzJnSIMSGVsbG8gV29ybGQhKgAyAA==","b64Record":"CiUIFhoDGL0JKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjDInPS/jlIqUMYI1U0RhWjqUkFELTB6/jvARgqQOIi5h2H1dDGIkb2Ik0qqxlHMeI4aDAix//6sBhCx7YroAiIPCgkI9f7+rAYQqhYSAhgCKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjlIA"},{"b64Body":"Cg8KCQj2/v6sBhCuFhICGAISAhgDGNyejz4iAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjpoBiCAKAxi9CSKAIDYwODA2MDQwNTI2MDQwNTE2MTBiZjMzODAzODA2MTBiZjM4MzM5ODE4MTAxNjA0MDUyODEwMTkwNjEwMDI1OTE5MDYxMDEwYzU2NWI4MTYwMDA4MDYxMDEwMDBhODE1NDgxNzNmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmMDIxOTE2OTA4MzczZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjE2MDIxNzkwNTU1MDgwNjAwMTgxOTA1NTUwNTA1MDYxMDE0YzU2NWI2MDAwODBmZDViNjAwMDczZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjgyMTY5MDUwOTE5MDUwNTY1YjYwMDA2MTAwYTM4MjYxMDA3ODU2NWI5MDUwOTE5MDUwNTY1YjYxMDBiMzgxNjEwMDk4NTY1YjgxMTQ2MTAwYmU1NzYwMDA4MGZkNWI1MDU2NWI2MDAwODE1MTkwNTA2MTAwZDA4MTYxMDBhYTU2NWI5MjkxNTA1MDU2NWI2MDAwODE5MDUwOTE5MDUwNTY1YjYxMDBlOTgxNjEwMGQ2NTY1YjgxMTQ2MTAwZjQ1NzYwMDA4MGZkNWI1MDU2NWI2MDAwODE1MTkwNTA2MTAxMDY4MTYxMDBlMDU2NWI5MjkxNTA1MDU2NWI2MDAwODA2MDQwODM4NTAzMTIxNTYxMDEyMzU3NjEwMTIyNjEwMDczNTY1YjViNjAwMDYxMDEzMTg1ODI4NjAxNjEwMGMxNTY1YjkyNTA1MDYwMjA2MTAxNDI4NTgyODYwMTYxMDBmNzU2NWI5MTUwNTA5MjUwOTI5MDUwNTY1YjYxMGE5ODgwNjEwMTViNjAwMDM5NjAwMGYzZmU2MDgwNjA0MDUyNjAwNDM2MTA2MTAwOWM1NzYwMDAzNTYwZTAxYzgwNjM3ZDNkMWMyZjExNjEwMDY0NTc4MDYzN2QzZDFjMmYxNDYxMDEzNDU3ODA2MzhkYTVjYjViMTQ2MTAxNWQ1NzgwNjM5MzkyNTAxNTE0NjEwMTg4NTc4MDYzOWEyZDhkNWMxNDYxMDFjNTU3ODA2M2MyOTg1NTc4MTQ2MTAxZjU1NzgwNjNkNWJkNWU0OTE0NjEwMjIwNTc2MTAwOWM1NjViODA2MzA0MTg3OTUxMTQ2MTAwYTE1NzgwNjMxMjA2NWZlMDE0NjEwMGFiNTc4MDYzNWUwZmY5MDgxNDYxMDBkNjU3ODA2MzcwYTA4MjMxMTQ2MTAwZTA1NzgwNjM3MzdiYzNjOTE0NjEwMTFkNTc1YjYwMDA4MGZkNWI2MTAwYTk2MTAyNWQ1NjViMDA1YjM0ODAxNTYxMDBiNzU3NjAwMDgwZmQ1YjUwNjEwMGMwNjEwM2U0NTY1YjYwNDA1MTYxMDBjZDkxOTA2MTA3ODk1NjViNjA0MDUxODA5MTAzOTBmMzViNjEwMGRlNjEwM2VjNTY1YjAwNWIzNDgwMTU2MTAwZWM1NzYwMDA4MGZkNWI1MDYxMDEwNzYwMDQ4MDM2MDM4MTAxOTA2MTAxMDI5MTkwNjEwODA3NTY1YjYxMDU3NTU2NWI2MDQwNTE2MTAxMTQ5MTkwNjEwNzg5NTY1YjYwNDA1MTgwOTEwMzkwZjM1YjM0ODAxNTYxMDEyOTU3NjAwMDgwZmQ1YjUwNjEwMTMyNjEwNTk2NTY1YjAwNWIzNDgwMTU2MTAxNDA1NzYwMDA4MGZkNWI1MDYxMDE1YjYwMDQ4MDM2MDM4MTAxOTA2MTAxNTY5MTkwNjEwODcyNTY1YjYxMDVjZjU2NWIwMDViMzQ4MDE1NjEwMTY5NTc2MDAwODBmZDViNTA2MTAxNzI2MTA1ZTg1NjViNjA0MDUxNjEwMTdmOTE5MDYxMDhhZTU2NWI2MDQwNTE4MDkxMDM5MGYzNWIzNDgwMTU2MTAxOTQ1NzYwMDA4MGZkNWI1MDYxMDFhZjYwMDQ4MDM2MDM4MTAxOTA2MTAxYWE5MTkwNjEwODA3NTY1YjYxMDYwYzU2NWI2MDQwNTE2MTAxYmM5MTkwNjEwOGU0NTY1YjYwNDA1MTgwOTEwMzkwZjM1YjYxMDFkZjYwMDQ4MDM2MDM4MTAxOTA2MTAxZGE5MTkwNjEwODA3NTY1YjYxMDY3ZjU2NWI2MDQwNTE2MTAxZWM5MTkwNjEwOGU0NTY1YjYwNDA1MTgwOTEwMzkwZjM1YjM0ODAxNTYxMDIwMTU3NjAwMDgwZmQ1YjUwNjEwMjBhNjEwNmY1NTY1YjYwNDA1MTYxMDIxNzkxOTA2MTA3ODk1NjViNjA0MDUxODA5MTAzOTBmMzViMzQ4MDE1NjEwMjJjNTc2MDAwODBmZDViNTA2MTAyNDc2MDA0ODAzNjAzODEwMTkwNjEwMjQyOTE5MDYxMDgwNzU2NWI2MTA2ZmI1NjViNjA0MDUxNjEwMjU0OTE5MDYxMDhlNDU2NWI2MDQwNTE4MDkxMDM5MGYzNWI2MDAwN2Y2ODYxNzM2ODAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwOTA1MDYwMDA2MDAxOTA1MDYwMDA3ZjcyMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDA5MDUwNjAwMDdmNzMwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDkwNTA2MDAwNjAwMTczZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjE2ODU4NTg1ODU2MDQwNTE2MDI0MDE2MTAzMDM5NDkzOTI5MTkwNjEwOTM0NTY1YjYwNDA1MTYwMjA4MTgzMDMwMzgxNTI5MDYwNDA1MjdmOTZkMTA3ZjYwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDdiZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxOTE2NjAyMDgyMDE4MDUxN2JmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjgzODE4MzE2MTc4MzUyNTA1MDUwNTA2MDQwNTE2MTAzOGQ5MTkwNjEwOWVhNTY1YjYwMDA2MDQwNTE4MDgzMDM4MTYwMDA4NjVhZjE5MTUwNTAzZDgwNjAwMDgxMTQ2MTAzY2E1NzYwNDA1MTkxNTA2MDFmMTk2MDNmM2QwMTE2ODIwMTYwNDA1MjNkODI1MjNkNjAwMDYwMjA4NDAxM2U2MTAzY2Y1NjViNjA2MDkxNTA1YjUwNTA5MDUwODA2MTAzZGQ1NzYwMDA4MGZkNWI1MDUwNTA1MDUwNTY1YjYwMDA0NzkwNTA5MDU2NWI2MDAwN2Y2ODYxNzM2ODAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwOTA1MDYwMDA2MDAxOTA1MDYwMDA3ZjcyMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDA5MDUwNjAwMDdmNzMwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDkwNTA2MDAwNjAwMTczZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjE2NjAwMTg2ODY4Njg2NjA0MDUxNjAyNDAxNjEwNDk0OTQ5MzkyOTE5MDYxMDkzNDU2NWI2MDQwNTE2MDIwODE4MzAzMDM4MTUyOTA2MDQwNTI3Zjk2ZDEwN2Y2MDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDA3YmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmMTkxNjYwMjA4MjAxODA1MTdiZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmY4MzgxODMxNjE3ODM1MjUwNTA1MDUwNjA0MDUxNjEwNTFlOTE5MDYxMDllYTU2NWI2MDAwNjA0MDUxODA4MzAzODE4NTg3NWFmMTkyNTA1MDUwM2Q4MDYwMDA4MTE0NjEwNTViNTc2MDQwNTE5MTUwNjAxZjE5NjAzZjNkMDExNjgyMDE2MDQwNTIzZDgyNTIzZDYwMDA2MDIwODQwMTNlNjEwNTYwNTY1YjYwNjA5MTUwNWI1MDUwOTA1MDgwNjEwNTZlNTc2MDAwODBmZDViNTA1MDUwNTA1MDU2NWI2MDAwODE3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNjMxOTA1MDkxOTA1MDU2NWI2MDAwODA1NDkwNjEwMTAwMGE5MDA0NzNmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmMTY3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNmZmNWI4MDczZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjE2ZmY1YjYwMDA4MDU0OTA2MTAxMDAwYTkwMDQ3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNjgxNTY1YjYwMDA4MDgyNzNmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmMTY2MDQwNTE2MTA2MzI5MDYxMGE0ZDU2NWI2MDAwNjA0MDUxODA4MzAzODE4NTVhZjQ5MTUwNTAzZDgwNjAwMDgxMTQ2MTA2NmQ1NzYwNDA1MTkxNTA2MDFmMTk2MDNmM2QwMTE2ODIwMTYwNDA1MjNkODI1MjNkNjAwMDYwMjA4NDAxM2U2MTA2NzI1NjViNjA2MDkxNTA1YjUwNTA5MDUwODA5MTUwNTA5MTkwNTA1NjViNjAwMDgwODI3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNjM0NjA0MDUxNjEwNmE2OTA2MTBhNGQ=","b64Record":"CiAIFiocCgwIARAMGgYIgK6ZpA8SDAgBEA8aBgiArpmkDxIwitqFop+OwWIMssa8oVrCvbyAYVutu254D7fAz52igbhYujWMg3C0NzaGvnNCuviNGgwIsv/+rAYQwN3yjAEiDwoJCPb+/qwGEK4WEgIYAiogw4PCrsOCwrfDg8K5dEY4w4LCrkrDg8KLw4PCkMODwo5SAA=="},{"b64Body":"Cg8KCQj2/v6sBhC0FhICGAISAhgDGKGW3zUiAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjoIB7g8SAxi9CSLmDzU2NWI2MDAwNjA0MDUxODA4MzAzODE4NTg3NWFmMTkyNTA1MDUwM2Q4MDYwMDA4MTE0NjEwNmUzNTc2MDQwNTE5MTUwNjAxZjE5NjAzZjNkMDExNjgyMDE2MDQwNTIzZDgyNTIzZDYwMDA2MDIwODQwMTNlNjEwNmU4NTY1YjYwNjA5MTUwNWI1MDUwOTA1MDgwOTE1MDUwOTE5MDUwNTY1YjYwMDE1NDgxNTY1YjYwMDA4MDgyNzNmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmMTY2MDQwNTE2MTA3MjE5MDYxMGE0ZDU2NWI2MDAwNjA0MDUxODA4MzAzODE2MDAwODY1YWYxOTE1MDUwM2Q4MDYwMDA4MTE0NjEwNzVlNTc2MDQwNTE5MTUwNjAxZjE5NjAzZjNkMDExNjgyMDE2MDQwNTIzZDgyNTIzZDYwMDA2MDIwODQwMTNlNjEwNzYzNTY1YjYwNjA5MTUwNWI1MDUwOTA1MDgwOTE1MDUwOTE5MDUwNTY1YjYwMDA4MTkwNTA5MTkwNTA1NjViNjEwNzgzODE2MTA3NzA1NjViODI1MjUwNTA1NjViNjAwMDYwMjA4MjAxOTA1MDYxMDc5ZTYwMDA4MzAxODQ2MTA3N2E1NjViOTI5MTUwNTA1NjViNjAwMDgwZmQ1YjYwMDA3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmY4MjE2OTA1MDkxOTA1MDU2NWI2MDAwNjEwN2Q0ODI2MTA3YTk1NjViOTA1MDkxOTA1MDU2NWI2MTA3ZTQ4MTYxMDdjOTU2NWI4MTE0NjEwN2VmNTc2MDAwODBmZDViNTA1NjViNjAwMDgxMzU5MDUwNjEwODAxODE2MTA3ZGI1NjViOTI5MTUwNTA1NjViNjAwMDYwMjA4Mjg0MDMxMjE1NjEwODFkNTc2MTA4MWM2MTA3YTQ1NjViNWI2MDAwNjEwODJiODQ4Mjg1MDE2MTA3ZjI1NjViOTE1MDUwOTI5MTUwNTA1NjViNjAwMDYxMDgzZjgyNjEwN2E5NTY1YjkwNTA5MTkwNTA1NjViNjEwODRmODE2MTA4MzQ1NjViODExNDYxMDg1YTU3NjAwMDgwZmQ1YjUwNTY1YjYwMDA4MTM1OTA1MDYxMDg2YzgxNjEwODQ2NTY1YjkyOTE1MDUwNTY1YjYwMDA2MDIwODI4NDAzMTIxNTYxMDg4ODU3NjEwODg3NjEwN2E0NTY1YjViNjAwMDYxMDg5Njg0ODI4NTAxNjEwODVkNTY1YjkxNTA1MDkyOTE1MDUwNTY1YjYxMDhhODgxNjEwN2M5NTY1YjgyNTI1MDUwNTY1YjYwMDA2MDIwODIwMTkwNTA2MTA4YzM2MDAwODMwMTg0NjEwODlmNTY1YjkyOTE1MDUwNTY1YjYwMDA4MTE1MTU5MDUwOTE5MDUwNTY1YjYxMDhkZTgxNjEwOGM5NTY1YjgyNTI1MDUwNTY1YjYwMDA2MDIwODIwMTkwNTA2MTA4Zjk2MDAwODMwMTg0NjEwOGQ1NTY1YjkyOTE1MDUwNTY1YjYwMDA4MTkwNTA5MTkwNTA1NjViNjEwOTEyODE2MTA4ZmY1NjViODI1MjUwNTA1NjViNjAwMDYwZmY4MjE2OTA1MDkxOTA1MDU2NWI2MTA5MmU4MTYxMDkxODU2NWI4MjUyNTA1MDU2NWI2MDAwNjA4MDgyMDE5MDUwNjEwOTQ5NjAwMDgzMDE4NzYxMDkwOTU2NWI2MTA5NTY2MDIwODMwMTg2NjEwOTI1NTY1YjYxMDk2MzYwNDA4MzAxODU2MTA5MDk1NjViNjEwOTcwNjA2MDgzMDE4NDYxMDkwOTU2NWI5NTk0NTA1MDUwNTA1MDU2NWI2MDAwODE1MTkwNTA5MTkwNTA1NjViNjAwMDgxOTA1MDkyOTE1MDUwNTY1YjYwMDA1YjgzODExMDE1NjEwOWFkNTc4MDgyMDE1MTgxODQwMTUyNjAyMDgxMDE5MDUwNjEwOTkyNTY1YjYwMDA4NDg0MDE1MjUwNTA1MDUwNTY1YjYwMDA2MTA5YzQ4MjYxMDk3OTU2NWI2MTA5Y2U4MTg1NjEwOTg0NTY1YjkzNTA2MTA5ZGU4MTg1NjAyMDg2MDE2MTA5OGY1NjViODA4NDAxOTE1MDUwOTI5MTUwNTA1NjViNjAwMDYxMDlmNjgyODQ2MTA5Yjk1NjViOTE1MDgxOTA1MDkyOTE1MDUwNTY1YjdmNjI2ZjZmMjg3NTY5NmU3NDMyMzUzNjI5MDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDYwMDA4MjAxNTI1MDU2NWI2MDAwNjEwYTM3NjAwYzgzNjEwOTg0NTY1YjkxNTA2MTBhNDI4MjYxMGEwMTU2NWI2MDBjODIwMTkwNTA5MTkwNTA1NjViNjAwMDYxMGE1ODgyNjEwYTJhNTY1YjkxNTA4MTkwNTA5MTkwNTA1NmZlYTI2NDY5NzA2NjczNTgyMjEyMjBlNWUwOWYyYzA3NGFmMDRlNWExNGJhMDkwMGUwZTg4MDE4NjgwZGQ3NDcwYmMyNjg4ODZlYWNiMDdmYzNlZDljNjQ3MzZmNmM2MzQzMDAwODEzMDAzMw==","b64Record":"CiAIFiocCgwIARAMGgYIgK6ZpA8SDAgBEA8aBgiArpmkDxIwpElTaJ9qyox/zJVrKZdf2IEmNu5XZZbQW5ohiuGeS1WkKA7Ul9vJefl5V+sNuSspGgwIsv/+rAYQ1PnP8QIiDwoJCPb+/qwGELQWEgIYAiogw4PCrsOCwrfDg8K5dEY4w4LCrkrDg8KLw4PCkMODwo5SAA=="},{"b64Body":"Cg8KCQj3/v6sBhC2FhICGAISAhgDGNWAv6ACIgIIeDIgw4PCrsOCwrfDg8K5dEY4w4LCrkrDg8KLw4PCkMODwo5CjAEKAxi9CRoiEiB6/N8Yr0AF4DVo3CB2mx9XEpxx42HiJ0ALVCRdY6AaNSCQoQ8ogMLXL0IFCIDO2gNKQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFSAFoAagtjZWxsYXIgZG9vcg==","b64Record":"CiUIFiIDGL4JKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjApwlyLSzbMB60mMCZEHKegH6EVB486NLgBI881grcF2HtOrLC5vx4ZFWMSAM1hEEMaDAiz//6sBhCsqdmWASIPCgkI9/7+rAYQthYSAhgCKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjjDA2eIGQs0XCgMYvgkSmBVggGBAUmAENhBhAJxXYAA1YOAcgGN9PRwvEWEAZFeAY309HC8UYQE0V4BjjaXLWxRhAV1XgGOTklAVFGEBiFeAY5otjVwUYQHFV4BjwphVeBRhAfVXgGPVvV5JFGECIFdhAJxWW4BjBBh5URRhAKFXgGMSBl/gFGEAq1eAY14P+QgUYQDWV4BjcKCCMRRhAOBXgGNze8PJFGEBHVdbYACA/VthAKlhAl1WWwBbNIAVYQC3V2AAgP1bUGEAwGED5FZbYEBRYQDNkZBhB4lWW2BAUYCRA5DzW2EA3mED7FZbAFs0gBVhAOxXYACA/VtQYQEHYASANgOBAZBhAQKRkGEIB1ZbYQV1VltgQFFhARSRkGEHiVZbYEBRgJEDkPNbNIAVYQEpV2AAgP1bUGEBMmEFllZbAFs0gBVhAUBXYACA/VtQYQFbYASANgOBAZBhAVaRkGEIclZbYQXPVlsAWzSAFWEBaVdgAID9W1BhAXJhBehWW2BAUWEBf5GQYQiuVltgQFGAkQOQ81s0gBVhAZRXYACA/VtQYQGvYASANgOBAZBhAaqRkGEIB1ZbYQYMVltgQFFhAbyRkGEI5FZbYEBRgJEDkPNbYQHfYASANgOBAZBhAdqRkGEIB1ZbYQZ/VltgQFFhAeyRkGEI5FZbYEBRgJEDkPNbNIAVYQIBV2AAgP1bUGECCmEG9VZbYEBRYQIXkZBhB4lWW2BAUYCRA5DzWzSAFWECLFdgAID9W1BhAkdgBIA2A4EBkGECQpGQYQgHVlthBvtWW2BAUWECVJGQYQjkVltgQFGAkQOQ81tgAH9oYXNoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAJBQYABgAZBQYAB/cgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACQUGAAf3MAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAkFBgAGABc///////////////////////////FoWFhYVgQFFgJAFhAwOUk5KRkGEJNFZbYEBRYCCBgwMDgVKQYEBSf5bRB/YAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAe/////////////////////////////////////8ZFmAgggGAUXv/////////////////////////////////////g4GDFheDUlBQUFBgQFFhA42RkGEJ6lZbYABgQFGAgwOBYACGWvGRUFA9gGAAgRRhA8pXYEBRkVBgHxlgPz0BFoIBYEBSPYJSPWAAYCCEAT5hA89WW2BgkVBbUFCQUIBhA91XYACA/VtQUFBQUFZbYABHkFCQVltgAH9oYXNoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAJBQYABgAZBQYAB/cgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACQUGAAf3MAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAkFBgAGABc///////////////////////////FmABhoaGhmBAUWAkAWEElJSTkpGQYQk0VltgQFFgIIGDAwOBUpBgQFJ/ltEH9gAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB7/////////////////////////////////////xkWYCCCAYBRe/////////////////////////////////////+DgYMWF4NSUFBQUGBAUWEFHpGQYQnqVltgAGBAUYCDA4GFh1rxklBQUD2AYACBFGEFW1dgQFGRUGAfGWA/PQEWggFgQFI9glI9YABgIIQBPmEFYFZbYGCRUFtQUJBQgGEFbldgAID9W1BQUFBQVltgAIFz//////////////////////////8WMZBQkZBQVltgAIBUkGEBAAqQBHP//////////////////////////xZz//////////////////////////8W/1uAc///////////////////////////Fv9bYACAVJBhAQAKkARz//////////////////////////8WgVZbYACAgnP//////////////////////////xZgQFFhBjKQYQpNVltgAGBAUYCDA4GFWvSRUFA9gGAAgRRhBm1XYEBRkVBgHxlgPz0BFoIBYEBSPYJSPWAAYCCEAT5hBnJWW2BgkVBbUFCQUICRUFCRkFBWW2AAgIJz//////////////////////////8WNGBAUWEGppBhCk1WW2AAYEBRgIMDgYWHWvGSUFBQPYBgAIEUYQbjV2BAUZFQYB8ZYD89ARaCAWBAUj2CUj1gAGAghAE+YQboVltgYJFQW1BQkFCAkVBQkZBQVltgAVSBVltgAICCc///////////////////////////FmBAUWEHIZBhCk1WW2AAYEBRgIMDgWAAhlrxkVBQPYBgAIEUYQdeV2BAUZFQYB8ZYD89ARaCAWBAUj2CUj1gAGAghAE+YQdjVltgYJFQW1BQkFCAkVBQkZBQVltgAIGQUJGQUFZbYQeDgWEHcFZbglJQUFZbYABgIIIBkFBhB55gAIMBhGEHelZbkpFQUFZbYACA/VtgAHP//////////////////////////4IWkFCRkFBWW2AAYQfUgmEHqVZbkFCRkFBWW2EH5IFhB8lWW4EUYQfvV2AAgP1bUFZbYACBNZBQYQgBgWEH21ZbkpFQUFZbYABgIIKEAxIVYQgdV2EIHGEHpFZbW2AAYQgrhIKFAWEH8lZbkVBQkpFQUFZbYABhCD+CYQepVluQUJGQUFZbYQhPgWEINFZbgRRhCFpXYACA/VtQVltgAIE1kFBhCGyBYQhGVluSkVBQVltgAGAggoQDEhVhCIhXYQiHYQekVltbYABhCJaEgoUBYQhdVluRUFCSkVBQVlthCKiBYQfJVluCUlBQVltgAGAgggGQUGEIw2AAgwGEYQifVluSkVBQVltgAIEVFZBQkZBQVlthCN6BYQjJVluCUlBQVltgAGAgggGQUGEI+WAAgwGEYQjVVluSkVBQVltgAIGQUJGQUFZbYQkSgWEI/1ZbglJQUFZbYABg/4IWkFCRkFBWW2EJLoFhCRhWW4JSUFBWW2AAYICCAZBQYQlJYACDAYdhCQlWW2EJVmAggwGGYQklVlthCWNgQIMBhWEJCVZbYQlwYGCDAYRhCQlWW5WUUFBQUFBWW2AAgVGQUJGQUFZbYACBkFCSkVBQVltgAFuDgRAVYQmtV4CCAVGBhAFSYCCBAZBQYQmSVltgAISEAVJQUFBQVltgAGEJxIJhCXlWW2EJzoGFYQmEVluTUGEJ3oGFYCCGAWEJj1ZbgIQBkVBQkpFQUFZbYABhCfaChGEJuVZbkVCBkFCSkVBQVlt/Ym9vKHVpbnQyNTYpAAAAAAAAAAAAAAAAAAAAAAAAAABgAIIBUlBWW2AAYQo3YAyDYQmEVluRUGEKQoJhCgFWW2AMggGQUJGQUFZbYABhCliCYQoqVluRUIGQUJGQUFb+omRpcGZzWCISIOXgnywHSvBOWhS6CQDg6IAYaA3XRwvCaIhurLB/w+2cZHNvbGNDAAgTADMigAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKMCaDDoDGL4JShYKFAAAAAAAAAAAAAAAAAAAAAAAAAS+cgcKAxi+CRABUiIKCQoCGAIQ/7b0bAoJCgIYYhCAs8UNCgoKAxi+CRCAhK9f"},{"b64Body":"Cg8KCQj3/v6sBhC4FhICGAISAhgDGOCssQMiAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjjovCgMYvgkQoI0GIiTVvV5JAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=","b64Record":"CiUIFiIDGL4JKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjASCrRTkCYBQ37Lc6CxrI5CtSe4wb2JYP8JV2anKUHLa+06PqinrhaI70X3MOl59P0aDAiz//6sBhDyiZj7AiIPCgkI9/7+rAYQuBYSAhgCKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjjD/rq0DOq4CCgMYvgkSIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIoACAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACiJhgZSFgoJCgIYAhD93doGCgkKAhhiEP7d2gY="},{"b64Body":"Cg8KCQj4/v6sBhC6FhICGAISAhgDGOCssQMiAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjjoyCgMYvgkQoI0GGPQDIiSaLY1cAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=","b64Record":"CiUIFiIDGL4JKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjDlAOl7Pz5j4DChgk7EyoM+dEiLgl7mzOO2d3ZlqPvKysrOFCJB0eWvg9aQTClEm7QaDAi0//6sBhDEmdCDASIPCgkI+P7+rAYQuhYSAhgCKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjjDS1q8DOq4CCgMYvgkSIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIoACAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACieigZSIAoJCgIYAhCLtd8GCgkKAhhiEKSt3wYKCAoDGL4JEOgH"},{"b64Body":"Cg8KCQj4/v6sBhC8FhICGAISAhgDGOCssQMiAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjjovCgMYvgkQoI0GIiTVvV5JAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADI=","b64Record":"CiUIFiIDGL4JKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjANEYNIYlm8wWNR/xBNjuHHCyAkHyVYIK2QjvxpZsU5yjGQu9VpGiOX4SfODcG9fN0aDAi0//6sBhCppoyFAyIPCgkI+P7+rAYQvBYSAhgCKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjjDGr60DOq4CCgMYvgkSIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIoACAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACiKhgZSFgoJCgIYAhCL39oGCgkKAhhiEIzf2gY="},{"b64Body":"Cg8KCQj5/v6sBhC+FhICGAISAhgDGOCssQMiAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjjoyCgMYvgkQoI0GGPQDIiSaLY1cAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADI=","b64Record":"CiUIFiIDGL4JKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjCLqkmUmJks07F5jdd6Y4MdJZFAYYRjRblWloPLOYdtmZfimMEsz8peuOSQcMo1EN8aDAi1//6sBhDMot2MASIPCgkI+f7+rAYQvhYSAhgCKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjjDS1q8DOq4CCgMYvgkSIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIoACAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACieigZSIAoJCgIYAhCLtd8GCgkKAhhiEKSt3wYKCAoDGL4JEOgH"},{"b64Body":"Cg8KCQj5/v6sBhDAFhICGAISAhgDGOCssQMiAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjjovCgMYvgkQoI0GIiTVvV5JAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAE=","b64Record":"CiUIFiIDGL4JKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjB7Qb8QavKRgQH/ZTjjc7Af35ZZ9BOD6E9jSnPo6Vb+BVH31oGnlnl2SjMIryPHdwgaDAi1//6sBhDQhqGOAyIPCgkI+f7+rAYQwBYSAhgCKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjjCA19oCOq4CCgMYvgkSIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABIoACAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACiA8QRSFgoJCgIYAhD/rbUFCgkKAhhiEICutQU="},{"b64Body":"Cg8KCQj6/v6sBhDCFhICGAISAhgDGOCssQMiAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjjoyCgMYvgkQoI0GGPQDIiSaLY1cAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAE=","b64Record":"CiUIFiIDGL4JKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjDu0vpBHXxt0mNtUM4/jBymHTnm9qN2GWuBgX8FA1ZqIvJK/Hy6MRJEr5RarOzCDFgaDAi2//6sBhDHvdKWASIPCgkI+v7+rAYQwhYSAhgCKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjjCBwa8DOq4CCgMYvgkSIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIoACAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACj3iQZSIAoJCgIYAhDpid8GCgkKAhhiEIKC3wYKCAoDGL4JEOgH"},{"b64Body":"Cg8KCQj6/v6sBhDEFhICGAISAhgDGOCssQMiAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjjovCgMYvgkQoI0GIiTVvV5JAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAVk=","b64Record":"CiUIFiIDGL4JKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjD6/ikBM0OdzVKV/C0JkXXoQ/ZIQyZj/fDcB+qqnXv4ptxez7jrbtcSphD3GitIV64aDAi2//6sBhC24qf7AiIPCgkI+v7+rAYQxBYSAhgCKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjjDGr60DOq4CCgMYvgkSIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIoACAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACiKhgZSFgoJCgIYAhCL39oGCgkKAhhiEIzf2gY="},{"b64Body":"Cg8KCQj7/v6sBhDGFhICGAISAhgDGOCssQMiAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjjoyCgMYvgkQoI0GGPQDIiSaLY1cAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAVk=","b64Record":"CiUIFiIDGL4JKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjAa0YxVcidYL9u0YaKxvmMr6hfYJ5Hqw2wfCxF1MZX34/hWnJDfuhLOz55m245BcxsaDAi3//6sBhCNmICgASIPCgkI+/7+rAYQxhYSAhgCKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjjDS1q8DOq4CCgMYvgkSIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIoACAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACieigZSIAoJCgIYAhCLtd8GCgkKAhhiEKSt3wYKCAoDGL4JEOgH"},{"b64Body":"Cg8KCQj7/v6sBhDIFhICGAISAhgDGOCssQMiAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjjovCgMYvgkQoI0GIiSTklAVAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAE=","b64Record":"CiUIFiIDGL4JKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjC4tgsPRJpokKLYZ0lloMZH4eKD7v1g7/ZnSw2GPLkvP3VoRgUwOrIHWK8N772JjpMaDAi3//6sBhCUh4qFAyIPCgkI+/7+rAYQyBYSAhgCKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjjCA19oCOq4CCgMYvgkSIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABIoACAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACiA8QRSFgoJCgIYAhD/rbUFCgkKAhhiEICutQU="},{"b64Body":"Cg8KCQj8/v6sBhDKFhICGAISAhgDGOCssQMiAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjjovCgMYvgkQoI0GIiSTklAVAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGI=","b64Record":"CiUIFiIDGL4JKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjDVJFhOUXcvi7Mxxy4qa0+zoaQ4MrCxxoFrmk+wfbJ8ge0EPMzwGBZPAKZu6zmbuJ8aDAi4//6sBhD5tqmpASIPCgkI/P7+rAYQyhYSAhgCKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjjD/rq0DOq4CCgMYvgkSIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIoACAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACiJhgZSFgoJCgIYAhD93doGCgkKAhhiEP7d2gY="},{"b64Body":"Cg8KCQj8/v6sBhDMFhICGAISAhgDGOCssQMiAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjjovCgMYvgkQoI0GIiSTklAVAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAH0=","b64Record":"CiUIFiIDGL4JKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjAnRF/vas6DIfPYuDNaSC9mnj4pnzh/2aYU6xMWjoh3rdMDr4FFnvev99rmGBjgY/waDAi4//6sBhCdpsOOAyIPCgkI/P7+rAYQzBYSAhgCKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjjD/rq0DOq4CCgMYvgkSIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIoACAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACiJhgZSFgoJCgIYAhD93doGCgkKAhhiEP7d2gY="},{"b64Body":"Cg8KCQj9/v6sBhDOFhICGAISAhgDGOCssQMiAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjjovCgMYvgkQoI0GIiR9PRwvAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAI=","b64Record":"CiUIHSIDGL4JKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjDp/JEE2oES2yitbssZ33Cm8biWrcYoOXp/1AAPgLliQXrLJP7Kx2URLvgaxyqLHaAaDAi5//6sBhCLtsWWASIPCgkI/f7+rAYQzhYSAhgCKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjjDgrLEDOh4aGElOVkFMSURfU09MSURJVFlfQUREUkVTUyigjQZSFgoJCgIYAhC/2eIGCgkKAhhiEMDZ4gY="},{"b64Body":"Cg8KCQj9/v6sBhDQFhICGAISAhgDGOCssQMiAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjjovCgMYvgkQoI0GIiRwoIIxAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAI=","b64Record":"CiUIFiIDGL4JKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjD5sipreqy5vmQ+7tl8WO0lCF+TLrWeZXGY/71XNaDRwknuz+PDTl2coWKmF7/vQh4aDAi5//6sBhDonY2YAyIPCgkI/f7+rAYQ0BYSAhgCKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjjCA19oCOq4CCgMYvgkSIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIoACAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACiA8QRSFgoJCgIYAhD/rbUFCgkKAhhiEICutQU="}]},"repeatedCreate2FailsWithInterpretableActionSidecars":{"placeholderNum":1218,"encodedItems":[{"b64Body":"Cg8KCQiK//6sBhCsFxICGAISAhgDGPuV9hQiAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjlozCiISIGqN5rX71qJwq2zF75HlO4zdMzSChZF7yuD7jwZy7eoLEICA6YOx3hZKBQiAztoD","b64Record":"CiUIFhIDGMMJKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjDOMvn+Z6tTjCs1eqaB1F7wfRqhcgdKMiov/WuyCmIrUHiN8XCn8vMLp/nIHLTYylQaCwjG//6sBhDUpOAtIg8KCQiK//6sBhCsFxICGAIqIMODwq7DgsK3w4PCuXRGOMOCwq5Kw4PCi8ODwpDDg8KOUh0KDAoCGAIQ///Rh+K8LQoNCgMYwwkQgIDSh+K8LQ=="},{"b64Body":"Cg8KCQiK//6sBhCuFxICGAISAhgDGP7LzSwiAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjooBjwESDAjGzdmwBhCtqqaVAhptCiISIB/DJKmmtu456r9MjimzPyPJJ9u8mqeA930l3EEern7KCiM6IQOi86ODQpgrCPlopcF1/5NcEhIgeTx9FYmUxBObMV6CmwoiEiBc5ukOxHYr0JmJHvFvors3kA0Qctn0yNJKz9OKonR8zSIMSGVsbG8gV29ybGQhKgAyAA==","b64Record":"CiUIFhoDGMQJKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjDmzgJfn+ynrxwW56PuShHZWoVCf8GCfkrKZ7u+SFjmn4Sgk10mkFpbzibqpUradmEaDAjG//6sBhCS+POuAiIPCgkIiv/+rAYQrhcSAhgCKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjlIA"},{"b64Body":"Cg8KCQiL//6sBhCyFxICGAISAhgDGIudjj4iAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjpoBiCAKAxjECSKAIDYwODA2MDQwNTIzNDgwMTU2MTAwMTA1NzYwMDA4MGZkNWI1MDYxMjgwMjgwNjEwMDIwNjAwMDM5NjAwMGYzZmU2MDgwNjA0MDUyMzQ4MDE1NjEwMDEwNTc2MDAwODBmZDViNTA2MDA0MzYxMDYxMDA1NzU3NjAwMDM1NjBlMDFjODA2MzQyNWRiZjk2MTQ2MTAwNWM1NzgwNjM2N2Q3MzFkMzE0NjEwMDhjNTc4MDYzNzA3YzUwMTkxNDYxMDBiYzU3ODA2M2FiZjdiZmQ4MTQ2MTAwZDg1NzgwNjNjYTk3MjE2MTE0NjEwMGY0NTc1YjYwMDA4MGZkNWI2MTAwNzY2MDA0ODAzNjAzODEwMTkwNjEwMDcxOTE5MDYxMDlhMTU2NWI2MTAxMTA1NjViNjA0MDUxNjEwMDgzOTE5MDYxMDlmYTU2NWI2MDQwNTE4MDkxMDM5MGYzNWI2MTAwYTY2MDA0ODAzNjAzODEwMTkwNjEwMGExOTE5MDYxMDlhMTU2NWI2MTAxNDY1NjViNjA0MDUxNjEwMGIzOTE5MDYxMDlmYTU2NWI2MDQwNTE4MDkxMDM5MGYzNWI2MTAwZDY2MDA0ODAzNjAzODEwMTkwNjEwMGQxOTE5MDYxMGExNTU2NWI2MTAxN2M1NjViMDA1YjYxMDBmMjYwMDQ4MDM2MDM4MTAxOTA2MTAwZWQ5MTkwNjEwYTc4NTY1YjYxMDI1ZjU2NWIwMDViNjEwMTBlNjAwNDgwMzYwMzgxMDE5MDYxMDEwOTkxOTA2MTBhMTU1NjViNjEwM2E5NTY1YjAwNWI2MDAwNjEwMTNlODMzMDYwMDA4MDU0OTA2MTAxMDAwYTkwMDQ3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNjg1NjEwNDhjNTY1YjkwNTA5MjkxNTA1MDU2NWI2MDAwNjEwMTc0ODMzMDYwMDA4MDU0OTA2MTAxMDAwYTkwMDQ3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNjg1NjEwNWFhNTY1YjkwNTA5MjkxNTA1MDU2NWI2MDAwNjEwMTg4MzA4MzYxMDZjODU2NWI5MDUwNjAxNjYwMDMwYjgxMTQ2MTAxZDA1NzYwNDA1MTdmMDhjMzc5YTAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDgxNTI2MDA0MDE2MTAxYzc5MDYxMGIwMjU2NWI2MDQwNTE4MDkxMDM5MGZkNWI2MDAwODA1NDkwNjEwMTAwMGE5MDA0NzNmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmMTY3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNjYzZDk0MjU0MDM4MzYwNDA1MTgyNjNmZmZmZmZmZjE2NjBlMDFiODE1MjYwMDQwMTYxMDIyOTkxOTA2MTBiMzE1NjViNjAwMDYwNDA1MTgwODMwMzgxNjAwMDg3ODAzYjE1ODAxNTYxMDI0MzU3NjAwMDgwZmQ1YjUwNWFmMTE1ODAxNTYxMDI1NzU3M2Q2MDAwODAzZTNkNjAwMGZkNWI1MDUwNTA1MDUwNTA1NjViNjAwMDYwZmY2MGY4MWIzMDgzNjA0MDUxODA2MDIwMDE2MTAyNzg5MDYxMDhmODU2NWI2MDIwODIwMTgxMDM4MjUyNjAxZjE5NjAxZjgyMDExNjYwNDA1MjUwNjA0MDUxNjAyMDAxNjEwMjljOTE5MDYxMGJjNjU2NWI2MDQwNTE2MDIwODE4MzAzMDM4MTUyOTA2MDQwNTI4MDUxOTA2MDIwMDEyMDYwNDA1MTYwMjAwMTYxMDJjNTk0OTM5MjkxOTA2MTBjOTM1NjViNjA0MDUxNjAyMDgxODMwMzAzODE1MjkwNjA0MDUyODA1MTkwNjAyMDAxMjA2MDAwMWM5MDUwODE2MDQwNTE2MTAyZWQ5MDYxMDhmODU2NWI4MTkwNjA0MDUxODA5MTAzOTA2MDAwZjU5MDUwODAxNTgwMTU2MTAzMGQ1NzNkNjAwMDgwM2UzZDYwMDBmZDViNTA2MDAwODA2MTAxMDAwYTgxNTQ4MTczZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjAyMTkxNjkwODM3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNjAyMTc5MDU1NTA4MDczZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjE2NjAwMDgwNTQ5MDYxMDEwMDBhOTAwNDczZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjE2NzNmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmMTYxNDYxMDNhNTU3NjAwMDgwZmQ1YjUwNTA1NjViNjAwMDYxMDNiNTMwODM2MTA3ZTA1NjViOTA1MDYwMTY2MDAzMGI4MTE0NjEwM2ZkNTc2MDQwNTE3ZjA4YzM3OWEwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDA4MTUyNjAwNDAxNjEwM2Y0OTA2MTBkMmQ1NjViNjA0MDUxODA5MTAzOTBmZDViNjAwMDgwNTQ5MDYxMDEwMDBhOTAwNDczZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjE2NzNmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmMTY2M2VhNThjZTIxODM2MDQwNTE4MjYzZmZmZmZmZmYxNjYwZTAxYjgxNTI2MDA0MDE2MTA0NTY5MTkwNjEwYjMxNTY1YjYwMDA2MDQwNTE4MDgzMDM4MTYwMDA4NzgwM2IxNTgwMTU2MTA0NzA1NzYwMDA4MGZkNWI1MDVhZjExNTgwMTU2MTA0ODQ1NzNkNjAwMDgwM2UzZDYwMDBmZDViNTA1MDUwNTA1MDUwNTY1YjYwMDA4MDYwMDA2MTAxNjc3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNjYzZWNhMzY5MTc2MGUwMWI4ODg4ODg4ODYwNDA1MTYwMjQwMTYxMDRjOTk0OTM5MjkxOTA2MTBkNWM1NjViNjA0MDUxNjAyMDgxODMwMzAzODE1MjkwNjA0MDUyOTA3YmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmMTkxNjYwMjA4MjAxODA1MTdiZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmY4MzgxODMxNjE3ODM1MjUwNTA1MDUwNjA0MDUxNjEwNTMzOTE5MDYxMGJjNjU2NWI2MDAwNjA0MDUxODA4MzAzODE2MDAwODY1YWYxOTE1MDUwM2Q4MDYwMDA4MTE0NjEwNTcwNTc2MDQwNTE5MTUwNjAxZjE5NjAzZjNkMDExNjgyMDE2MDQwNTIzZDgyNTIzZDYwMDA2MDIwODQwMTNlNjEwNTc1NTY1YjYwNjA5MTUwNWI1MDkxNTA5MTUwODE2MTA1ODY1NzYwMTU2MTA1OWI1NjViODA4MDYwMjAwMTkwNTE4MTAxOTA2MTA1OWE5MTkwNjEwZGRhNTY1YjViNjAwMzBiOTI1MDUwNTA5NDkzNTA1MDUwNTA1NjViNjAwMDgwNjAwMDYxMDE2NzczZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjE2NjM1Y2ZjOTAxMTYwZTAxYjg4ODg4ODg4NjA0MDUxNjAyNDAxNjEwNWU3OTQ5MzkyOTE5MDYxMGQ1YzU2NWI2MDQwNTE2MDIwODE4MzAzMDM4MTUyOTA2MDQwNTI5MDdiZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxOTE2NjAyMDgyMDE4MDUxN2JmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjgzODE4MzE2MTc4MzUyNTA1MDUwNTA2MDQwNTE2MTA2NTE5MTkwNjEwYmM2NTY1YjYwMDA2MDQwNTE4MDgzMDM4MTYwMDA4NjVhZjE5MTUwNTAzZDgwNjAwMDgxMTQ2MTA2OGU1NzYwNDA1MTkxNTA2MDFmMTk2MDNmM2QwMTE2ODIwMTYwNDA1MjNkODI1MjNkNjAwMDYwMjA4NDAxM2U2MTA2OTM1NjViNjA2MDkxNTA1YjUwOTE1MDkxNTA4MTYxMDZhNDU3NjAxNTYxMDZiOTU2NWI4MDgwNjAyMDAxOTA1MTgxMDE5MDYxMDZiODkxOTA2MTBkZGE1NjViNWI2MDAzMGI5MjUwNTA1MDk0OTM1MDUwNTA1MDU2NWI2MDAwODA2MDAwNjEwMTY3NzNmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmMTY2MzA5OTc5NGU4NjBlMDFiODY4NjYwNDA1MTYwMjQwMTYxMDcwMTkyOTE5MDYxMGUwNzU2NWI2MDQwNTE2MDIwODE4MzAzMDM4MTUyOTA2MDQwNTI5MDdiZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxOTE2NjAyMDgyMDE4MDUxN2JmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjgzODE4MzE2MTc4MzUyNTA1MDUwNTA2MDQwNTE2MTA3NmI5MTkwNjEwYmM2NTY1YjYwMDA2MDQwNTE4MDgzMDM4MTYwMDA4NjVhZjE5MTUwNTAzZDgwNjAwMDgxMTQ2MTA3YTg1NzYwNDA1MTkxNTA2MDFmMTk2MDNmM2QwMTE2ODIwMTYwNDA1MjNkODI1MjNkNjAwMDYwMjA4NDAxM2U2MTA3YWQ1NjViNjA2MDkxNTA1YjUwOTE1MDkxNTA4MTYxMDdiZTU3NjAxNTYxMDdkMzU2NWI4MDgwNjAyMDAxOTA1MTgxMDE5MDYxMDdkMjkxOTA2MTBkZGE1NjViNWI2MDAzMGI5MjUwNTA1MDkyOTE1MDUwNTY=","b64Record":"CiAIFiocCgwIARAMGgYIgK6ZpA8SDAgBEA8aBgiArpmkDxIwJwtNuGKjlV7R7tEPrz42Xije/vSXpjWeVCZui46h9e5Ps210VvR6m+r00zRJrb6lGgsIx//+rAYQkJmANyIPCgkIi//+rAYQshcSAhgCKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjlIA"},{"b64Body":"Cg8KCQiL//6sBhC4FxICGAISAhgDGPjR6j4iAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjoIBiCASAxjECSKAIDViNjAwMDgwNjAwMDYxMDE2NzczZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjE2NjM0OTE0NmJkZTYwZTAxYjg2ODY2MDQwNTE2MDI0MDE2MTA4MTk5MjkxOTA2MTBlMDc1NjViNjA0MDUxNjAyMDgxODMwMzAzODE1MjkwNjA0MDUyOTA3YmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmMTkxNjYwMjA4MjAxODA1MTdiZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmY4MzgxODMxNjE3ODM1MjUwNTA1MDUwNjA0MDUxNjEwODgzOTE5MDYxMGJjNjU2NWI2MDAwNjA0MDUxODA4MzAzODE2MDAwODY1YWYxOTE1MDUwM2Q4MDYwMDA4MTE0NjEwOGMwNTc2MDQwNTE5MTUwNjAxZjE5NjAzZjNkMDExNjgyMDE2MDQwNTIzZDgyNTIzZDYwMDA2MDIwODQwMTNlNjEwOGM1NTY1YjYwNjA5MTUwNWI1MDkxNTA5MTUwODE2MTA4ZDY1NzYwMTU2MTA4ZWI1NjViODA4MDYwMjAwMTkwNTE4MTAxOTA2MTA4ZWE5MTkwNjEwZGRhNTY1YjViNjAwMzBiOTI1MDUwNTA5MjkxNTA1MDU2NWI2MTE5OWM4MDYxMGUzMTgzMzkwMTkwNTY1YjYwMDA4MGZkNWI2MDAwNzNmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmODIxNjkwNTA5MTkwNTA1NjViNjAwMDYxMDkzNTgyNjEwOTBhNTY1YjkwNTA5MTkwNTA1NjViNjEwOTQ1ODE2MTA5MmE1NjViODExNDYxMDk1MDU3NjAwMDgwZmQ1YjUwNTY1YjYwMDA4MTM1OTA1MDYxMDk2MjgxNjEwOTNjNTY1YjkyOTE1MDUwNTY1YjYwMDA4MTYwMDcwYjkwNTA5MTkwNTA1NjViNjEwOTdlODE2MTA5Njg1NjViODExNDYxMDk4OTU3NjAwMDgwZmQ1YjUwNTY1YjYwMDA4MTM1OTA1MDYxMDk5YjgxNjEwOTc1NTY1YjkyOTE1MDUwNTY1YjYwMDA4MDYwNDA4Mzg1MDMxMjE1NjEwOWI4NTc2MTA5Yjc2MTA5MDU1NjViNWI2MDAwNjEwOWM2ODU4Mjg2MDE2MTA5NTM1NjViOTI1MDUwNjAyMDYxMDlkNzg1ODI4NjAxNjEwOThjNTY1YjkxNTA1MDkyNTA5MjkwNTA1NjViNjAwMDgxOTA1MDkxOTA1MDU2NWI2MTA5ZjQ4MTYxMDllMTU2NWI4MjUyNTA1MDU2NWI2MDAwNjAyMDgyMDE5MDUwNjEwYTBmNjAwMDgzMDE4NDYxMDllYjU2NWI5MjkxNTA1MDU2NWI2MDAwNjAyMDgyODQwMzEyMTU2MTBhMmI1NzYxMGEyYTYxMDkwNTU2NWI1YjYwMDA2MTBhMzk4NDgyODUwMTYxMDk1MzU2NWI5MTUwNTA5MjkxNTA1MDU2NWI2MDAwODE5MDUwOTE5MDUwNTY1YjYxMGE1NTgxNjEwYTQyNTY1YjgxMTQ2MTBhNjA1NzYwMDA4MGZkNWI1MDU2NWI2MDAwODEzNTkwNTA2MTBhNzI4MTYxMGE0YzU2NWI5MjkxNTA1MDU2NWI2MDAwNjAyMDgyODQwMzEyMTU2MTBhOGU1NzYxMGE4ZDYxMDkwNTU2NWI1YjYwMDA2MTBhOWM4NDgyODUwMTYxMGE2MzU2NWI5MTUwNTA5MjkxNTA1MDU2NWI2MDAwODI4MjUyNjAyMDgyMDE5MDUwOTI5MTUwNTA1NjViN2Y0ZTY1NzY2NTcyMjA2NTZlNjQ3MzIwNzc2NTZjNmMyZTAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwNjAwMDgyMDE1MjUwNTY1YjYwMDA2MTBhZWM2MDEwODM2MTBhYTU1NjViOTE1MDYxMGFmNzgyNjEwYWI2NTY1YjYwMjA4MjAxOTA1MDkxOTA1MDU2NWI2MDAwNjAyMDgyMDE5MDUwODE4MTAzNjAwMDgzMDE1MjYxMGIxYjgxNjEwYWRmNTY1YjkwNTA5MTkwNTA1NjViNjEwYjJiODE2MTA5MmE1NjViODI1MjUwNTA1NjViNjAwMDYwMjA4MjAxOTA1MDYxMGI0NjYwMDA4MzAxODQ2MTBiMjI1NjViOTI5MTUwNTA1NjViNjAwMDgxNTE5MDUwOTE5MDUwNTY1YjYwMDA4MTkwNTA5MjkxNTA1MDU2NWI2MDAwNWI4MzgxMTAxNTYxMGI4MDU3ODA4MjAxNTE4MTg0MDE1MjYwMjA4MTAxOTA1MDYxMGI2NTU2NWI4MzgxMTExNTYxMGI4ZjU3NjAwMDg0ODQwMTUyNWI1MDUwNTA1MDU2NWI2MDAwNjEwYmEwODI2MTBiNGM1NjViNjEwYmFhODE4NTYxMGI1NzU2NWI5MzUwNjEwYmJhODE4NTYwMjA4NjAxNjEwYjYyNTY1YjgwODQwMTkxNTA1MDkyOTE1MDUwNTY1YjYwMDA2MTBiZDI4Mjg0NjEwYjk1NTY1YjkxNTA4MTkwNTA5MjkxNTA1MDU2NWI2MDAwN2ZmZjAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwODIxNjkwNTA5MTkwNTA1NjViNjAwMDgxOTA1MDkxOTA1MDU2NWI2MTBjMjQ2MTBjMWY4MjYxMGJkZDU2NWI2MTBjMDk1NjViODI1MjUwNTA1NjViNjAwMDgxNjA2MDFiOTA1MDkxOTA1MDU2NWI2MDAwNjEwYzQyODI2MTBjMmE1NjViOTA1MDkxOTA1MDU2NWI2MDAwNjEwYzU0ODI2MTBjMzc1NjViOTA1MDkxOTA1MDU2NWI2MTBjNmM2MTBjNjc4MjYxMDkyYTU2NWI2MTBjNDk1NjViODI1MjUwNTA1NjViNjAwMDgxOTA1MDkxOTA1MDU2NWI2MTBjOGQ2MTBjODg4MjYxMGE0MjU2NWI2MTBjNzI1NjViODI1MjUwNTA1NjViNjAwMDYxMGM5ZjgyODc2MTBjMTM1NjViNjAwMTgyMDE5MTUwNjEwY2FmODI4NjYxMGM1YjU2NWI2MDE0ODIwMTkxNTA2MTBjYmY4Mjg1NjEwYzdjNTY1YjYwMjA4MjAxOTE1MDYxMGNjZjgyODQ2MTBjN2M1NjViNjAyMDgyMDE5MTUwODE5MDUwOTU5NDUwNTA1MDUwNTA1NjViN2Y1NzY1NmM2YzJjMjA0OTIwNmU2NTc2NjU3MjIxMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwNjAwMDgyMDE1MjUwNTY1YjYwMDA2MTBkMTc2MDBlODM2MTBhYTU1NjViOTE1MDYxMGQyMjgyNjEwY2UxNTY1YjYwMjA4MjAxOTA1MDkxOTA1MDU2NWI2MDAwNjAyMDgyMDE5MDUwODE4MTAzNjAwMDgzMDE1MjYxMGQ0NjgxNjEwZDBhNTY1YjkwNTA5MTkwNTA1NjViNjEwZDU2ODE2MTA5Njg1NjViODI1MjUwNTA1NjViNjAwMDYwODA4MjAxOTA1MDYxMGQ3MTYwMDA4MzAxODc2MTBiMjI1NjViNjEwZDdlNjAyMDgzMDE4NjYxMGIyMjU2NWI2MTBkOGI2MDQwODMwMTg1NjEwYjIyNTY1YjYxMGQ5ODYwNjA4MzAxODQ2MTBkNGQ1NjViOTU5NDUwNTA1MDUwNTA1NjViNjAwMDgxNjAwMzBiOTA1MDkxOTA1MDU2NWI2MTBkYjc4MTYxMGRhMTU2NWI4MTE0NjEwZGMyNTc2MDAwODBmZDViNTA1NjViNjAwMDgxNTE5MDUwNjEwZGQ0ODE2MTBkYWU1NjViOTI5MTUwNTA1NjViNjAwMDYwMjA4Mjg0MDMxMjE1NjEwZGYwNTc2MTBkZWY2MTA5MDU1NjViNWI2MDAwNjEwZGZlODQ4Mjg1MDE2MTBkYzU1NjViOTE1MDUwOTI5MTUwNTA1NjViNjAwMDYwNDA4MjAxOTA1MDYxMGUxYzYwMDA4MzAxODU2MTBiMjI1NjViNjEwZTI5NjAyMDgzMDE4NDYxMGIyMjU2NWI5MzkyNTA1MDUwNTZmZTYwODA2MDQwNTIzNDgwMTU2MTAwMTA1NzYwMDA4MGZkNWI1MDYxMTk3YzgwNjEwMDIwNjAwMDM5NjAwMGYzZmU2MDgwNjA0MDUyMzQ4MDE1NjEwMDEwNTc2MDAwODBmZDViNTA2MDA0MzYxMDYxMDA0YzU3NjAwMDM1NjBlMDFjODA2MzBhMjg0Y2I2MTQ2MTAwNTE1NzgwNjM0ODRhOTVhOTE0NjEwMDZkNTc4MDYzZDk0MjU0MDMxNDYxMDA4OTU3ODA2M2VhNThjZTIxMTQ2MTAwYTU1NzViNjAwMDgwZmQ1YjYxMDA2YjYwMDQ4MDM2MDM4MTAxOTA2MTAwNjY5MTkwNjEwOTM5NTY1YjYxMDBjMTU2NWIwMDViNjEwMDg3NjAwNDgwMzYwMzgxMDE5MDYxMDA4MjkxOTA2MTA5Mzk1NjViNjEwMTI1NTY1YjAwNWI2MTAwYTM2MDA0ODAzNjAzODEwMTkwNjEwMDllOTE5MDYxMDk5NTU2NWI2MTAyMzY1NjViMDA1YjYxMDBiZjYwMDQ4MDM2MDM4MTAxOTA2MTAwYmE5MTkwNjEwOTk1NTY1YjYxMDI4ZTU2NWIwMDViNjAwMDgwNjAwMDYxMDBkMjg1NjAwMDg2NjEwMmU2NTY1YjkyNTA5MjUwOTI1MDYwMTY2MDAzMGI4MzE0NjEwMTFlNTc2MDQwNTE3ZjA4YzM3OWEwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDA4MTUyNjAwNDAxNjEwMTE1OTA2MTBhMWY1NjViNjA0MDUxODA5MTAzOTBmZDViNTA1MDUwNTA1MDU2NWI2MDAwNjA0MDUxNjEwMTMzOTA2MTA2OGU1NjViNjA0MDUxODA5MTAzOTA2MDAwZjA4MDE1ODAxNTYxMDE0ZjU3M2Q2MDAwODAzZTNkNjAwMGZkNWI1MDkwNTA4MDczZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjE2NjMwYTI4NGNiNjYwZTAxYjg0ODQ2MDQwNTE2MDI0MDE2MTAxODQ5MjkxOTA2MTBiOTg1NjViNjA0MDUxNjAyMDgxODMwMzAzODE=","b64Record":"CiAIFiocCgwIARAMGgYIgK6ZpA8SDAgBEA8aBgiArpmkDxIwuEt2Tzdg2Lhrp1MeUoSv9D7WbE1rnMNDFs9aGSADk+D9YCf43JZa29RU146ZqXdYGgwIx//+rAYQr+TKuAIiDwoJCIv//qwGELgXEgIYAiogw4PCrsOCwrfDg8K5dEY4w4LCrkrDg8KLw4PCkMODwo5SAA=="},{"b64Body":"Cg8KCQiM//6sBhC+FxICGAISAhgDGPfR6j4iAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjoIBiCASAxjECSKAIDUyOTA2MDQwNTI5MDdiZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxOTE2NjAyMDgyMDE4MDUxN2JmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjgzODE4MzE2MTc4MzUyNTA1MDUwNTA2MDQwNTE2MTAxZWU5MTkwNjEwYzA0NTY1YjYwMDA2MDQwNTE4MDgzMDM4MTg1NWFmNDkxNTA1MDNkODA2MDAwODExNDYxMDIyOTU3NjA0MDUxOTE1MDYwMWYxOTYwM2YzZDAxMTY4MjAxNjA0MDUyM2Q4MjUyM2Q2MDAwNjAyMDg0MDEzZTYxMDIyZTU2NWI2MDYwOTE1MDViNTA1MDUwNTA1MDUwNTY1YjYwMDA2MTAyNDIzMDgzNjEwNDVlNTY1YjkwNTA2MDE2NjAwMzBiODExNDYxMDI4YTU3NjA0MDUxN2YwOGMzNzlhMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwODE1MjYwMDQwMTYxMDI4MTkwNjEwYzY3NTY1YjYwNDA1MTgwOTEwMzkwZmQ1YjUwNTA1NjViNjAwMDYxMDI5YTMwODM2MTA1NzY1NjViOTA1MDYwMTY2MDAzMGI4MTE0NjEwMmUyNTc2MDQwNTE3ZjA4YzM3OWEwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDA4MTUyNjAwNDAxNjEwMmQ5OTA2MTBjZDM1NjViNjA0MDUxODA5MTAzOTBmZDViNTA1MDU2NWI2MDAwODA2MDYwNjAwMDgwNjEwMTY3NzNmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmMTY2MzI3OGUwYjg4NjBlMDFiODk4OTg5NjA0MDUxNjAyNDAxNjEwMzI0OTM5MjkxOTA2MTBkMTY1NjViNjA0MDUxNjAyMDgxODMwMzAzODE1MjkwNjA0MDUyOTA3YmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmMTkxNjYwMjA4MjAxODA1MTdiZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmY4MzgxODMxNjE3ODM1MjUwNTA1MDUwNjA0MDUxNjEwMzhlOTE5MDYxMGMwNDU2NWI2MDAwNjA0MDUxODA4MzAzODE2MDAwODY1YWYxOTE1MDUwM2Q4MDYwMDA4MTE0NjEwM2NiNTc2MDQwNTE5MTUwNjAxZjE5NjAzZjNkMDExNjgyMDE2MDQwNTIzZDgyNTIzZDYwMDA2MDIwODQwMTNlNjEwM2QwNTY1YjYwNjA5MTUwNWI1MDkxNTA5MTUwODE2MTA0MmM1NzYwMTU2MDAwODA2N2ZmZmZmZmZmZmZmZmZmZmY4MTExMTU2MTAzZjg1NzYxMDNmNzYxMDcyMzU2NWI1YjYwNDA1MTkwODA4MjUyODA2MDIwMDI2MDIwMDE4MjAxNjA0MDUyODAxNTYxMDQyNjU3ODE2MDIwMDE2MDIwODIwMjgwMzY4MzM3ODA4MjAxOTE1MDUwOTA1MDViNTA2MTA0NDE1NjViODA4MDYwMjAwMTkwNTE4MTAxOTA2MTA0NDA5MTkwNjEwZWIyNTY1YjViODI2MDAzMGI5MjUwODA5NTUwODE5NjUwODI5NzUwNTA1MDUwNTA1MDkzNTA5MzUwOTM5MDUwNTY1YjYwMDA4MDYwMDA2MTAxNjc3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNjYzMDk5Nzk0ZTg2MGUwMWI4Njg2NjA0MDUxNjAyNDAxNjEwNDk3OTI5MTkwNjEwZjIxNTY1YjYwNDA1MTYwMjA4MTgzMDMwMzgxNTI5MDYwNDA1MjkwN2JmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjE5MTY2MDIwODIwMTgwNTE3YmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmODM4MTgzMTYxNzgzNTI1MDUwNTA1MDYwNDA1MTYxMDUwMTkxOTA2MTBjMDQ1NjViNjAwMDYwNDA1MTgwODMwMzgxNjAwMDg2NWFmMTkxNTA1MDNkODA2MDAwODExNDYxMDUzZTU3NjA0MDUxOTE1MDYwMWYxOTYwM2YzZDAxMTY4MjAxNjA0MDUyM2Q4MjUyM2Q2MDAwNjAyMDg0MDEzZTYxMDU0MzU2NWI2MDYwOTE1MDViNTA5MTUwOTE1MDgxNjEwNTU0NTc2MDE1NjEwNTY5NTY1YjgwODA2MDIwMDE5MDUxODEwMTkwNjEwNTY4OTE5MDYxMGY0YTU2NWI1YjYwMDMwYjkyNTA1MDUwOTI5MTUwNTA1NjViNjAwMDgwNjAwMDYxMDE2NzczZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjE2NjM0OTE0NmJkZTYwZTAxYjg2ODY2MDQwNTE2MDI0MDE2MTA1YWY5MjkxOTA2MTBmMjE1NjViNjA0MDUxNjAyMDgxODMwMzAzODE1MjkwNjA0MDUyOTA3YmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmMTkxNjYwMjA4MjAxODA1MTdiZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmY4MzgxODMxNjE3ODM1MjUwNTA1MDUwNjA0MDUxNjEwNjE5OTE5MDYxMGMwNDU2NWI2MDAwNjA0MDUxODA4MzAzODE2MDAwODY1YWYxOTE1MDUwM2Q4MDYwMDA4MTE0NjEwNjU2NTc2MDQwNTE5MTUwNjAxZjE5NjAzZjNkMDExNjgyMDE2MDQwNTIzZDgyNTIzZDYwMDA2MDIwODQwMTNlNjEwNjViNTY1YjYwNjA5MTUwNWI1MDkxNTA5MTUwODE2MTA2NmM1NzYwMTU2MTA2ODE1NjViODA4MDYwMjAwMTkwNTE4MTAxOTA2MTA2ODA5MTkwNjEwZjRhNTY1YjViNjAwMzBiOTI1MDUwNTA5MjkxNTA1MDU2NWI2MTA5Y2Y4MDYxMGY3ODgzMzkwMTkwNTY1YjYwMDA2MDQwNTE5MDUwOTA1NjViNjAwMDgwZmQ1YjYwMDA4MGZkNWI2MDAwNzNmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmODIxNjkwNTA5MTkwNTA1NjViNjAwMDYxMDZkYTgyNjEwNmFmNTY1YjkwNTA5MTkwNTA1NjViNjEwNmVhODE2MTA2Y2Y1NjViODExNDYxMDZmNTU3NjAwMDgwZmQ1YjUwNTY1YjYwMDA4MTM1OTA1MDYxMDcwNzgxNjEwNmUxNTY1YjkyOTE1MDUwNTY1YjYwMDA4MGZkNWI2MDAwNjAxZjE5NjAxZjgzMDExNjkwNTA5MTkwNTA1NjViN2Y0ZTQ4N2I3MTAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwNjAwMDUyNjA0MTYwMDQ1MjYwMjQ2MDAwZmQ1YjYxMDc1YjgyNjEwNzEyNTY1YjgxMDE4MTgxMTA2N2ZmZmZmZmZmZmZmZmZmZmY4MjExMTcxNTYxMDc3YTU3NjEwNzc5NjEwNzIzNTY1YjViODA2MDQwNTI1MDUwNTA1NjViNjAwMDYxMDc4ZDYxMDY5YjU2NWI5MDUwNjEwNzk5ODI4MjYxMDc1MjU2NWI5MTkwNTA1NjViNjAwMDY3ZmZmZmZmZmZmZmZmZmZmZjgyMTExNTYxMDdiOTU3NjEwN2I4NjEwNzIzNTY1YjViNjAyMDgyMDI5MDUwNjAyMDgxMDE5MDUwOTE5MDUwNTY1YjYwMDA4MGZkNWI2MDAwODBmZDViNjAwMDY3ZmZmZmZmZmZmZmZmZmZmZjgyMTExNTYxMDdlZjU3NjEwN2VlNjEwNzIzNTY1YjViNjEwN2Y4ODI2MTA3MTI1NjViOTA1MDYwMjA4MTAxOTA1MDkxOTA1MDU2NWI4MjgxODMzNzYwMDA4MzgzMDE1MjUwNTA1MDU2NWI2MDAwNjEwODI3NjEwODIyODQ2MTA3ZDQ1NjViNjEwNzgzNTY1YjkwNTA4MjgxNTI2MDIwODEwMTg0ODQ4NDAxMTExNTYxMDg0MzU3NjEwODQyNjEwN2NmNTY1YjViNjEwODRlODQ4Mjg1NjEwODA1NTY1YjUwOTM5MjUwNTA1MDU2NWI2MDAwODI2MDFmODMwMTEyNjEwODZiNTc2MTA4NmE2MTA3MGQ1NjViNWI4MTM1NjEwODdiODQ4MjYwMjA4NjAxNjEwODE0NTY1YjkxNTA1MDkyOTE1MDUwNTY1YjYwMDA2MTA4OTc2MTA4OTI4NDYxMDc5ZTU2NWI2MTA3ODM1NjViOTA1MDgwODM4MjUyNjAyMDgyMDE5MDUwNjAyMDg0MDI4MzAxODU4MTExMTU2MTA4YmE1NzYxMDhiOTYxMDdjYTU2NWI1YjgzNWI4MTgxMTAxNTYxMDkwMTU3ODAzNTY3ZmZmZmZmZmZmZmZmZmZmZjgxMTExNTYxMDhkZjU3NjEwOGRlNjEwNzBkNTY1YjViODA4NjAxNjEwOGVjODk4MjYxMDg1NjU2NWI4NTUyNjAyMDg1MDE5NDUwNTA1MDYwMjA4MTAxOTA1MDYxMDhiYzU2NWI1MDUwNTA5MzkyNTA1MDUwNTY1YjYwMDA4MjYwMWY4MzAxMTI2MTA5MjA1NzYxMDkxZjYxMDcwZDU2NWI1YjgxMzU2MTA5MzA4NDgyNjAyMDg2MDE2MTA4ODQ1NjViOTE1MDUwOTI5MTUwNTA1NjViNjAwMDgwNjA0MDgzODUwMzEyMTU2MTA5NTA1NzYxMDk0ZjYxMDZhNTU2NWI1YjYwMDA2MTA5NWU4NTgyODYwMTYxMDZmODU2NWI5MjUwNTA2MDIwODMwMTM1NjdmZmZmZmZmZmZmZmZmZmZmODExMTE1NjEwOTdmNTc2MTA5N2U2MTA2YWE1NjViNWI2MTA5OGI4NTgyODYwMTYxMDkwYjU2NWI5MTUwNTA=","b64Record":"CiAIFiocCgwIARAMGgYIgK6ZpA8SDAgBEA8aBgiArpmkDxIw2UiE7E9+rGE6mcm/f6RVB7rcRR/4vaU7oguh+CqRbuCngDq4y8rc6f1t2IwS76zZGgsIyP/+rAYQuLXPQCIPCgkIjP/+rAYQvhcSAhgCKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjlIA"},{"b64Body":"Cg8KCQiM//6sBhDEFxICGAISAhgDGPfR6j4iAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjoIBiCASAxjECSKAIDkyNTA5MjkwNTA1NjViNjAwMDYwMjA4Mjg0MDMxMjE1NjEwOWFiNTc2MTA5YWE2MTA2YTU1NjViNWI2MDAwNjEwOWI5ODQ4Mjg1MDE2MTA2Zjg1NjViOTE1MDUwOTI5MTUwNTA1NjViNjAwMDgyODI1MjYwMjA4MjAxOTA1MDkyOTE1MDUwNTY1YjdmNDM2MTZlMjc3NDIxMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDYwMDA4MjAxNTI1MDU2NWI2MDAwNjEwYTA5NjAwNjgzNjEwOWMyNTY1YjkxNTA2MTBhMTQ4MjYxMDlkMzU2NWI2MDIwODIwMTkwNTA5MTkwNTA1NjViNjAwMDYwMjA4MjAxOTA1MDgxODEwMzYwMDA4MzAxNTI2MTBhMzg4MTYxMDlmYzU2NWI5MDUwOTE5MDUwNTY1YjYxMGE0ODgxNjEwNmNmNTY1YjgyNTI1MDUwNTY1YjYwMDA4MTUxOTA1MDkxOTA1MDU2NWI2MDAwODI4MjUyNjAyMDgyMDE5MDUwOTI5MTUwNTA1NjViNjAwMDgxOTA1MDYwMjA4MjAxOTA1MDkxOTA1MDU2NWI2MDAwODE1MTkwNTA5MTkwNTA1NjViNjAwMDgyODI1MjYwMjA4MjAxOTA1MDkyOTE1MDUwNTY1YjYwMDA1YjgzODExMDE1NjEwYWI0NTc4MDgyMDE1MTgxODQwMTUyNjAyMDgxMDE5MDUwNjEwYTk5NTY1YjgzODExMTE1NjEwYWMzNTc2MDAwODQ4NDAxNTI1YjUwNTA1MDUwNTY1YjYwMDA2MTBhZDQ4MjYxMGE3YTU2NWI2MTBhZGU4MTg1NjEwYTg1NTY1YjkzNTA2MTBhZWU4MTg1NjAyMDg2MDE2MTBhOTY1NjViNjEwYWY3ODE2MTA3MTI1NjViODQwMTkxNTA1MDkyOTE1MDUwNTY1YjYwMDA2MTBiMGU4MzgzNjEwYWM5NTY1YjkwNTA5MjkxNTA1MDU2NWI2MDAwNjAyMDgyMDE5MDUwOTE5MDUwNTY1YjYwMDA2MTBiMmU4MjYxMGE0ZTU2NWI2MTBiMzg4MTg1NjEwYTU5NTY1YjkzNTA4MzYwMjA4MjAyODUwMTYxMGI0YTg1NjEwYTZhNTY1YjgwNjAwMDViODU4MTEwMTU2MTBiODY1Nzg0ODQwMzg5NTI4MTUxNjEwYjY3ODU4MjYxMGIwMjU2NWI5NDUwNjEwYjcyODM2MTBiMTY1NjViOTI1MDYwMjA4YTAxOTk1MDUwNjAwMTgxMDE5MDUwNjEwYjRlNTY1YjUwODI5NzUwODc5NTUwNTA1MDUwNTA1MDkyOTE1MDUwNTY1YjYwMDA2MDQwODIwMTkwNTA2MTBiYWQ2MDAwODMwMTg1NjEwYTNmNTY1YjgxODEwMzYwMjA4MzAxNTI2MTBiYmY4MTg0NjEwYjIzNTY1YjkwNTA5MzkyNTA1MDUwNTY1YjYwMDA4MTkwNTA5MjkxNTA1MDU2NWI2MDAwNjEwYmRlODI2MTBhN2E1NjViNjEwYmU4ODE4NTYxMGJjODU2NWI5MzUwNjEwYmY4ODE4NTYwMjA4NjAxNjEwYTk2NTY1YjgwODQwMTkxNTA1MDkyOTE1MDUwNTY1YjYwMDA2MTBjMTA4Mjg0NjEwYmQzNTY1YjkxNTA4MTkwNTA5MjkxNTA1MDU2NWI3ZjUzNmYyMDc1NmU2NjYxNjk3MjAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDA2MDAwODIwMTUyNTA1NjViNjAwMDYxMGM1MTYwMDk4MzYxMDljMjU2NWI5MTUwNjEwYzVjODI2MTBjMWI1NjViNjAyMDgyMDE5MDUwOTE5MDUwNTY1YjYwMDA2MDIwODIwMTkwNTA4MTgxMDM2MDAwODMwMTUyNjEwYzgwODE2MTBjNDQ1NjViOTA1MDkxOTA1MDU2NWI3ZjQ5NzQyNzczMjA3NTZlNjg2NTYxNzI2NDIwNmY2NjJlMmUyZTAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDA2MDAwODIwMTUyNTA1NjViNjAwMDYxMGNiZDYwMTI4MzYxMDljMjU2NWI5MTUwNjEwY2M4ODI2MTBjODc1NjViNjAyMDgyMDE5MDUwOTE5MDUwNTY1YjYwMDA2MDIwODIwMTkwNTA4MTgxMDM2MDAwODMwMTUyNjEwY2VjODE2MTBjYjA1NjViOTA1MDkxOTA1MDU2NWI2MDAwNjdmZmZmZmZmZmZmZmZmZmZmODIxNjkwNTA5MTkwNTA1NjViNjEwZDEwODE2MTBjZjM1NjViODI1MjUwNTA1NjViNjAwMDYwNjA4MjAxOTA1MDYxMGQyYjYwMDA4MzAxODY2MTBhM2Y1NjViNjEwZDM4NjAyMDgzMDE4NTYxMGQwNzU2NWI4MTgxMDM2MDQwODMwMTUyNjEwZDRhODE4NDYxMGIyMzU2NWI5MDUwOTQ5MzUwNTA1MDUwNTY1YjYwMDA4MTYwMDMwYjkwNTA5MTkwNTA1NjViNjEwZDZhODE2MTBkNTQ1NjViODExNDYxMGQ3NTU3NjAwMDgwZmQ1YjUwNTY1YjYwMDA4MTUxOTA1MDYxMGQ4NzgxNjEwZDYxNTY1YjkyOTE1MDUwNTY1YjYxMGQ5NjgxNjEwY2YzNTY1YjgxMTQ2MTBkYTE1NzYwMDA4MGZkNWI1MDU2NWI2MDAwODE1MTkwNTA2MTBkYjM4MTYxMGQ4ZDU2NWI5MjkxNTA1MDU2NWI2MDAwNjdmZmZmZmZmZmZmZmZmZmZmODIxMTE1NjEwZGQ0NTc2MTBkZDM2MTA3MjM1NjViNWI2MDIwODIwMjkwNTA2MDIwODEwMTkwNTA5MTkwNTA1NjViNjAwMDgxOTA1MDkxOTA1MDU2NWI2MTBkZjg4MTYxMGRlNTU2NWI4MTE0NjEwZTAzNTc2MDAwODBmZDViNTA1NjViNjAwMDgxNTE5MDUwNjEwZTE1ODE2MTBkZWY1NjViOTI5MTUwNTA1NjViNjAwMDYxMGUyZTYxMGUyOTg0NjEwZGI5NTY1YjYxMDc4MzU2NWI5MDUwODA4MzgyNTI2MDIwODIwMTkwNTA2MDIwODQwMjgzMDE4NTgxMTExNTYxMGU1MTU3NjEwZTUwNjEwN2NhNTY1YjViODM1YjgxODExMDE1NjEwZTdhNTc4MDYxMGU2Njg4ODI2MTBlMDY1NjViODQ1MjYwMjA4NDAxOTM1MDUwNjAyMDgxMDE5MDUwNjEwZTUzNTY1YjUwNTA1MDkzOTI1MDUwNTA1NjViNjAwMDgyNjAxZjgzMDExMjYxMGU5OTU3NjEwZTk4NjEwNzBkNTY1YjViODE1MTYxMGVhOTg0ODI2MDIwODYwMTYxMGUxYjU2NWI5MTUwNTA5MjkxNTA1MDU2NWI2MDAwODA2MDAwNjA2MDg0ODYwMzEyMTU2MTBlY2I1NzYxMGVjYTYxMDZhNTU2NWI1YjYwMDA2MTBlZDk4NjgyODcwMTYxMGQ3ODU2NWI5MzUwNTA2MDIwNjEwZWVhODY4Mjg3MDE2MTBkYTQ1NjViOTI1MDUwNjA0MDg0MDE1MTY3ZmZmZmZmZmZmZmZmZmZmZjgxMTExNTYxMGYwYjU3NjEwZjBhNjEwNmFhNTY1YjViNjEwZjE3ODY4Mjg3MDE2MTBlODQ1NjViOTE1MDUwOTI1MDkyNTA5MjU2NWI2MDAwNjA0MDgyMDE5MDUwNjEwZjM2NjAwMDgzMDE4NTYxMGEzZjU2NWI2MTBmNDM2MDIwODMwMTg0NjEwYTNmNTY1YjkzOTI1MDUwNTA1NjViNjAwMDYwMjA4Mjg0MDMxMjE1NjEwZjYwNTc2MTBmNWY2MTA2YTU1NjViNWI2MDAwNjEwZjZlODQ4Mjg1MDE2MTBkNzg1NjViOTE1MDUwOTI5MTUwNTA1NmZlNjA4MDYwNDA1MjM0ODAxNTYxMDAxMDU3NjAwMDgwZmQ1YjUwNjEwOWFmODA2MTAwMjA2MDAwMzk2MDAwZjNmZTYwODA2MDQwNTIzNDgwMTU2MTAwMTA1NzYwMDA4MGZkNWI1MDYwMDQzNjEwNjEwMDJiNTc2MDAwMzU2MGUwMWM4MDYzMGEyODRjYjYxNDYxMDAzMDU3NWI2MDAwODBmZDViNjEwMDRhNjAwNDgwMzYwMzgxMDE5MDYxMDA0NTkxOTA2MTA0YzY1NjViNjEwMDRjNTY1YjAwNWI2MDAwODA2MDAwNjEwMDVkODU2MDAwODY2MTAwYjA1NjViOTI1MDkyNTA5MjUwNjAxNjYwMDMwYjgzMTQ2MTAwYTk1NzYwNDA1MTdmMDhjMzc5YTAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDgxNTI2MDA0MDE2MTAwYTA5MDYxMDU3ZjU2NWI2MDQwNTE4MDkxMDM5MGZkNWI1MDUwNTA1MDUwNTY1YjYwMDA4MDYwNjA2MDAwODA2MTAxNjc3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNjYzMjc4ZTBiODg2MGUwMWI4OTg5ODk2MDQwNTE2MDI0MDE2MTAwZWU5MzkyOTE5MDYxMDcxYjU2NWI2MDQwNTE2MDIwODE4MzAzMDM4MTUyOTA2MDQwNTI5MDdiZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxOTE2NjAyMDgyMDE4MDUxN2JmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjgzODE4MzE2MTc4MzUyNTA1MDUwNTA2MDQwNTE2MTAxNTg5MTkwNjEwNzk1NTY1YjYwMDA2MDQwNTE4MDgzMDM4MTYwMDA4NjVhZjE5MTUwNTAzZDgwNjAwMDgxMTQ2MTAxOTU1NzYwNDA1MTkxNTA2MDFmMTk2MDNmM2QwMTE2ODIwMTYwNDA1MjNkODI1MjNkNjAwMDYwMjA4NDAxM2U2MTAxOWE1NjViNjA2MDkxNTA1YjUwOTE1MDkxNTA4MTYxMDFmNjU3NjAxNTYwMDA4MDY3ZmZmZmZmZmZmZmZmZmZmZjgxMTExNTYxMDFjMjU3NjEwMWMxNjEwMmIwNTY1YjViNjA0MDUxOTA4MDgyNTI4MDYwMjAwMjYwMjAwMTgyMDE2MDQwNTI4MDE1NjEwMWYwNTc4MTYwMjAwMTYwMjA4MjAyODAzNjgzMzc4MDgyMDE5MTUwNTA5MDUwNWI1MDYxMDIwYjU2NWI=","b64Record":"CiAIFiocCgwIARAMGgYIgK6ZpA8SDAgBEA8aBgiArpmkDxIw1Sys7Z45SLCs19NrLoR1IyGsqz26d+dDs8BK2IgpjTUv9NQINhgArC/AUhNfB429GgwIyP/+rAYQt4bIpQIiDwoJCIz//qwGEMQXEgIYAiogw4PCrsOCwrfDg8K5dEY4w4LCrkrDg8KLw4PCkMODwo5SAA=="},{"b64Body":"Cg8KCQiN//6sBhDKFxICGAISAhgDGPbR6j4iAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjoIBiCASAxjECSKAIDgwODA2MDIwMDE5MDUxODEwMTkwNjEwMjBhOTE5MDYxMDkwYTU2NWI1YjgyNjAwMzBiOTI1MDgwOTU1MDgxOTY1MDgyOTc1MDUwNTA1MDUwNTA5MzUwOTM1MDkzOTA1MDU2NWI2MDAwNjA0MDUxOTA1MDkwNTY1YjYwMDA4MGZkNWI2MDAwODBmZDViNjAwMDczZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjgyMTY5MDUwOTE5MDUwNTY1YjYwMDA2MTAyNjc4MjYxMDIzYzU2NWI5MDUwOTE5MDUwNTY1YjYxMDI3NzgxNjEwMjVjNTY1YjgxMTQ2MTAyODI1NzYwMDA4MGZkNWI1MDU2NWI2MDAwODEzNTkwNTA2MTAyOTQ4MTYxMDI2ZTU2NWI5MjkxNTA1MDU2NWI2MDAwODBmZDViNjAwMDYwMWYxOTYwMWY4MzAxMTY5MDUwOTE5MDUwNTY1YjdmNGU0ODdiNzEwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDYwMDA1MjYwNDE2MDA0NTI2MDI0NjAwMGZkNWI2MTAyZTg4MjYxMDI5ZjU2NWI4MTAxODE4MTEwNjdmZmZmZmZmZmZmZmZmZmZmODIxMTE3MTU2MTAzMDc1NzYxMDMwNjYxMDJiMDU2NWI1YjgwNjA0MDUyNTA1MDUwNTY1YjYwMDA2MTAzMWE2MTAyMjg1NjViOTA1MDYxMDMyNjgyODI2MTAyZGY1NjViOTE5MDUwNTY1YjYwMDA2N2ZmZmZmZmZmZmZmZmZmZmY4MjExMTU2MTAzNDY1NzYxMDM0NTYxMDJiMDU2NWI1YjYwMjA4MjAyOTA1MDYwMjA4MTAxOTA1MDkxOTA1MDU2NWI2MDAwODBmZDViNjAwMDgwZmQ1YjYwMDA2N2ZmZmZmZmZmZmZmZmZmZmY4MjExMTU2MTAzN2M1NzYxMDM3YjYxMDJiMDU2NWI1YjYxMDM4NTgyNjEwMjlmNTY1YjkwNTA2MDIwODEwMTkwNTA5MTkwNTA1NjViODI4MTgzMzc2MDAwODM4MzAxNTI1MDUwNTA1NjViNjAwMDYxMDNiNDYxMDNhZjg0NjEwMzYxNTY1YjYxMDMxMDU2NWI5MDUwODI4MTUyNjAyMDgxMDE4NDg0ODQwMTExMTU2MTAzZDA1NzYxMDNjZjYxMDM1YzU2NWI1YjYxMDNkYjg0ODI4NTYxMDM5MjU2NWI1MDkzOTI1MDUwNTA1NjViNjAwMDgyNjAxZjgzMDExMjYxMDNmODU3NjEwM2Y3NjEwMjlhNTY1YjViODEzNTYxMDQwODg0ODI2MDIwODYwMTYxMDNhMTU2NWI5MTUwNTA5MjkxNTA1MDU2NWI2MDAwNjEwNDI0NjEwNDFmODQ2MTAzMmI1NjViNjEwMzEwNTY1YjkwNTA4MDgzODI1MjYwMjA4MjAxOTA1MDYwMjA4NDAyODMwMTg1ODExMTE1NjEwNDQ3NTc2MTA0NDY2MTAzNTc1NjViNWI4MzViODE4MTEwMTU2MTA0OGU1NzgwMzU2N2ZmZmZmZmZmZmZmZmZmZmY4MTExMTU2MTA0NmM1NzYxMDQ2YjYxMDI5YTU2NWI1YjgwODYwMTYxMDQ3OTg5ODI2MTAzZTM1NjViODU1MjYwMjA4NTAxOTQ1MDUwNTA2MDIwODEwMTkwNTA2MTA0NDk1NjViNTA1MDUwOTM5MjUwNTA1MDU2NWI2MDAwODI2MDFmODMwMTEyNjEwNGFkNTc2MTA0YWM2MTAyOWE1NjViNWI4MTM1NjEwNGJkODQ4MjYwMjA4NjAxNjEwNDExNTY1YjkxNTA1MDkyOTE1MDUwNTY1YjYwMDA4MDYwNDA4Mzg1MDMxMjE1NjEwNGRkNTc2MTA0ZGM2MTAyMzI1NjViNWI2MDAwNjEwNGViODU4Mjg2MDE2MTAyODU1NjViOTI1MDUwNjAyMDgzMDEzNTY3ZmZmZmZmZmZmZmZmZmZmZjgxMTExNTYxMDUwYzU3NjEwNTBiNjEwMjM3NTY1YjViNjEwNTE4ODU4Mjg2MDE2MTA0OTg1NjViOTE1MDUwOTI1MDkyOTA1MDU2NWI2MDAwODI4MjUyNjAyMDgyMDE5MDUwOTI5MTUwNTA1NjViN2Y0MzYxNmUyNzc0MjA2NTc2NjU2ZTIxMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwNjAwMDgyMDE1MjUwNTY1YjYwMDA2MTA1Njk2MDBiODM2MTA1MjI1NjViOTE1MDYxMDU3NDgyNjEwNTMzNTY1YjYwMjA4MjAxOTA1MDkxOTA1MDU2NWI2MDAwNjAyMDgyMDE5MDUwODE4MTAzNjAwMDgzMDE1MjYxMDU5ODgxNjEwNTVjNTY1YjkwNTA5MTkwNTA1NjViNjEwNWE4ODE2MTAyNWM1NjViODI1MjUwNTA1NjViNjAwMDY3ZmZmZmZmZmZmZmZmZmZmZjgyMTY5MDUwOTE5MDUwNTY1YjYxMDVjYjgxNjEwNWFlNTY1YjgyNTI1MDUwNTY1YjYwMDA4MTUxOTA1MDkxOTA1MDU2NWI2MDAwODI4MjUyNjAyMDgyMDE5MDUwOTI5MTUwNTA1NjViNjAwMDgxOTA1MDYwMjA4MjAxOTA1MDkxOTA1MDU2NWI2MDAwODE1MTkwNTA5MTkwNTA1NjViNjAwMDgyODI1MjYwMjA4MjAxOTA1MDkyOTE1MDUwNTY1YjYwMDA1YjgzODExMDE1NjEwNjM3NTc4MDgyMDE1MTgxODQwMTUyNjAyMDgxMDE5MDUwNjEwNjFjNTY1YjgzODExMTE1NjEwNjQ2NTc2MDAwODQ4NDAxNTI1YjUwNTA1MDUwNTY1YjYwMDA2MTA2NTc4MjYxMDVmZDU2NWI2MTA2NjE4MTg1NjEwNjA4NTY1YjkzNTA2MTA2NzE4MTg1NjAyMDg2MDE2MTA2MTk1NjViNjEwNjdhODE2MTAyOWY1NjViODQwMTkxNTA1MDkyOTE1MDUwNTY1YjYwMDA2MTA2OTE4MzgzNjEwNjRjNTY1YjkwNTA5MjkxNTA1MDU2NWI2MDAwNjAyMDgyMDE5MDUwOTE5MDUwNTY1YjYwMDA2MTA2YjE4MjYxMDVkMTU2NWI2MTA2YmI4MTg1NjEwNWRjNTY1YjkzNTA4MzYwMjA4MjAyODUwMTYxMDZjZDg1NjEwNWVkNTY1YjgwNjAwMDViODU4MTEwMTU2MTA3MDk1Nzg0ODQwMzg5NTI4MTUxNjEwNmVhODU4MjYxMDY4NTU2NWI5NDUwNjEwNmY1ODM2MTA2OTk1NjViOTI1MDYwMjA4YTAxOTk1MDUwNjAwMTgxMDE5MDUwNjEwNmQxNTY1YjUwODI5NzUwODc5NTUwNTA1MDUwNTA1MDkyOTE1MDUwNTY1YjYwMDA2MDYwODIwMTkwNTA2MTA3MzA2MDAwODMwMTg2NjEwNTlmNTY1YjYxMDczZDYwMjA4MzAxODU2MTA1YzI1NjViODE4MTAzNjA0MDgzMDE1MjYxMDc0ZjgxODQ2MTA2YTY1NjViOTA1MDk0OTM1MDUwNTA1MDU2NWI2MDAwODE5MDUwOTI5MTUwNTA1NjViNjAwMDYxMDc2ZjgyNjEwNWZkNTY1YjYxMDc3OTgxODU2MTA3NTk1NjViOTM1MDYxMDc4OTgxODU2MDIwODYwMTYxMDYxOTU2NWI4MDg0MDE5MTUwNTA5MjkxNTA1MDU2NWI2MDAwNjEwN2ExODI4NDYxMDc2NDU2NWI5MTUwODE5MDUwOTI5MTUwNTA1NjViNjAwMDgxNjAwMzBiOTA1MDkxOTA1MDU2NWI2MTA3YzI4MTYxMDdhYzU2NWI4MTE0NjEwN2NkNTc2MDAwODBmZDViNTA1NjViNjAwMDgxNTE5MDUwNjEwN2RmODE2MTA3Yjk1NjViOTI5MTUwNTA1NjViNjEwN2VlODE2MTA1YWU1NjViODExNDYxMDdmOTU3NjAwMDgwZmQ1YjUwNTY1YjYwMDA4MTUxOTA1MDYxMDgwYjgxNjEwN2U1NTY1YjkyOTE1MDUwNTY1YjYwMDA2N2ZmZmZmZmZmZmZmZmZmZmY4MjExMTU2MTA4MmM1NzYxMDgyYjYxMDJiMDU2NWI1YjYwMjA4MjAyOTA1MDYwMjA4MTAxOTA1MDkxOTA1MDU2NWI2MDAwODE5MDUwOTE5MDUwNTY1YjYxMDg1MDgxNjEwODNkNTY1YjgxMTQ2MTA4NWI1NzYwMDA4MGZkNWI1MDU2NWI2MDAwODE1MTkwNTA2MTA4NmQ4MTYxMDg0NzU2NWI5MjkxNTA1MDU2NWI2MDAwNjEwODg2NjEwODgxODQ2MTA4MTE1NjViNjEwMzEwNTY1YjkwNTA4MDgzODI1MjYwMjA4MjAxOTA1MDYwMjA4NDAyODMwMTg1ODExMTE1NjEwOGE5NTc2MTA4YTg2MTAzNTc1NjViNWI4MzViODE4MTEwMTU2MTA4ZDI1NzgwNjEwOGJlODg4MjYxMDg1ZTU2NWI4NDUyNjAyMDg0MDE5MzUwNTA2MDIwODEwMTkwNTA2MTA4YWI1NjViNTA1MDUwOTM5MjUwNTA1MDU2NWI2MDAwODI2MDFmODMwMTEyNjEwOGYxNTc2MTA4ZjA2MTAyOWE1NjViNWI4MTUxNjEwOTAxODQ4MjYwMjA4NjAxNjEwODczNTY1YjkxNTA1MDkyOTE1MDUwNTY1YjYwMDA4MDYwMDA2MDYwODQ4NjAzMTIxNTYxMDkyMzU3NjEwOTIyNjEwMjMyNTY1YjViNjAwMDYxMDkzMTg2ODI4NzAxNjEwN2QwNTY1YjkzNTA1MDYwMjA2MTA5NDI4NjgyODcwMTYxMDdmYzU2NWI5MjUwNTA2MDQwODQwMTUxNjdmZmZmZmZmZmZmZmZmZmZmODExMTE1NjEwOTYzNTc2MTA5NjI2MTAyMzc1NjViNWI2MTA5NmY4NjgyODcwMTYxMDhkYzU2NWI5MTUwNTA5MjUwOTI1MDkyNTZmZWEyNjQ2OTcwNjY3MzU4MjIxMjIwYTNkZmY5MDJkODg2Y2ZmNDUzZTY2MWQyODAzODIwYWUyNmFiNGY0NWE0OGZhMzE5NTBiZDAzMWYwOGQzZWJhYzY0NzM2ZjZjNjM0MzAwMDgwYzAwMzNhMjY0Njk3MDY2NzM1ODIyMTIyMDU1MDQ1OTE4MjMwMjQ4NTQ0MjA2NThkNDk0NThiNGFlZDg0ZWYyYWZhZTI4OTg5ZTAzMTU2ODdmNTk5MTRiM2Y2NDczNmY2YzYzNDMwMDA4MGMwMDMzYTI2NDY5NzA2NjczNTgyMjEyMjA5OTA0NDBlYTM0MmY5YzgzMTE=","b64Record":"CiAIFiocCgwIARAMGgYIgK6ZpA8SDAgBEA8aBgiArpmkDxIwXviK7M0q7fDVSGAEGayIO4bwgRDMmjq4tAcHaGd9j0BO7LQQnHE4HHYEmq7DKV51GgsIyf/+rAYQh/aOSiIPCgkIjf/+rAYQyhcSAhgCKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjlIA"},{"b64Body":"Cg8KCQiN//6sBhDQFxICGAISAhgDGImGlS0iAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjoIBSxIDGMQJIkRhYTkzOTZkOTY0YmMyM2RjYjU2MjY2MjVmNTBjMGYyZDZmM2U3ODA4NDMzY2U2NjQ3MzZmNmM2MzQzMDAwODBjMDAzMw==","b64Record":"CiAIFiocCgwIARAMGgYIgK6ZpA8SDAgBEA8aBgiArpmkDxIwd1ab/do5aiDFZpuFXm1PVCfBjYYtgb6/STwNzGu7QdmQycSITXNFR6BVuWXjfh0FGgwIyf/+rAYQjruLrwIiDwoJCI3//qwGENAXEgIYAiogw4PCrsOCwrfDg8K5dEY4w4LCrkrDg8KLw4PCkMODwo5SAA=="},{"b64Body":"Cg8KCQiO//6sBhDSFxICGAISAhgDGJb7rp0CIgIIeDIgw4PCrsOCwrfDg8K5dEY4w4LCrkrDg8KLw4PCkMODwo5CRQoDGMQJGiISIAFIuFAN3KwzuAdFGJDChqGklU3FMswGG3POn//A7TVbIJChD0IFCIDO2gNSAFoAagtjZWxsYXIgZG9vcg==","b64Record":"CiUIFiIDGMUJKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjBsMqucglIgtpJgClsQwelG5Liaw45diMTCoND2bZzm6mza50Go93rw/mdJhDT1rY8aCwjK//6sBhC+uedTIg8KCQiO//6sBhDSFxICGAIqIMODwq7DgsK3w4PCuXRGOMOCwq5Kw4PCi8ODwpDDg8KOMIL4kAdCt1IKAxjFCRKCUGCAYEBSNIAVYQAQV2AAgP1bUGAENhBhAFdXYAA1YOAcgGNCXb+WFGEAXFeAY2fXMdMUYQCMV4BjcHxQGRRhALxXgGOr97/YFGEA2FeAY8qXIWEUYQD0V1tgAID9W2EAdmAEgDYDgQGQYQBxkZBhCaFWW2EBEFZbYEBRYQCDkZBhCfpWW2BAUYCRA5DzW2EApmAEgDYDgQGQYQChkZBhCaFWW2EBRlZbYEBRYQCzkZBhCfpWW2BAUYCRA5DzW2EA1mAEgDYDgQGQYQDRkZBhChVWW2EBfFZbAFthAPJgBIA2A4EBkGEA7ZGQYQp4VlthAl9WWwBbYQEOYASANgOBAZBhAQmRkGEKFVZbYQOpVlsAW2AAYQE+gzBgAIBUkGEBAAqQBHP//////////////////////////xaFYQSMVluQUJKRUFBWW2AAYQF0gzBgAIBUkGEBAAqQBHP//////////////////////////xaFYQWqVluQUJKRUFBWW2AAYQGIMINhBshWW5BQYBZgAwuBFGEB0FdgQFF/CMN5oAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACBUmAEAWEBx5BhCwJWW2BAUYCRA5D9W2AAgFSQYQEACpAEc///////////////////////////FnP//////////////////////////xZj2UJUA4NgQFGCY/////8WYOAbgVJgBAFhAimRkGELMVZbYABgQFGAgwOBYACHgDsVgBVhAkNXYACA/VtQWvEVgBVhAldXPWAAgD49YAD9W1BQUFBQUFZbYABg/2D4GzCDYEBRgGAgAWECeJBhCPhWW2AgggGBA4JSYB8ZYB+CARZgQFJQYEBRYCABYQKckZBhC8ZWW2BAUWAggYMDA4FSkGBAUoBRkGAgASBgQFFgIAFhAsWUk5KRkGEMk1ZbYEBRYCCBgwMDgVKQYEBSgFGQYCABIGAAHJBQgWBAUWEC7ZBhCPhWW4GQYEBRgJEDkGAA9ZBQgBWAFWEDDVc9YACAPj1gAP1bUGAAgGEBAAqBVIFz//////////////////////////8CGRaQg3P//////////////////////////xYCF5BVUIBz//////////////////////////8WYACAVJBhAQAKkARz//////////////////////////8Wc///////////////////////////FhRhA6VXYACA/VtQUFZbYABhA7Uwg2EH4FZbkFBgFmADC4EUYQP9V2BAUX8Iw3mgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIFSYAQBYQP0kGENLVZbYEBRgJEDkP1bYACAVJBhAQAKkARz//////////////////////////8Wc///////////////////////////FmPqWM4hg2BAUYJj/////xZg4BuBUmAEAWEEVpGQYQsxVltgAGBAUYCDA4FgAIeAOxWAFWEEcFdgAID9W1Ba8RWAFWEEhFc9YACAPj1gAP1bUFBQUFBQVltgAIBgAGEBZ3P//////////////////////////xZj7KNpF2DgG4iIiIhgQFFgJAFhBMmUk5KRkGENXFZbYEBRYCCBgwMDgVKQYEBSkHv/////////////////////////////////////GRZgIIIBgFF7/////////////////////////////////////4OBgxYXg1JQUFBQYEBRYQUzkZBhC8ZWW2AAYEBRgIMDgWAAhlrxkVBQPYBgAIEUYQVwV2BAUZFQYB8ZYD89ARaCAWBAUj2CUj1gAGAghAE+YQV1VltgYJFQW1CRUJFQgWEFhldgFWEFm1ZbgIBgIAGQUYEBkGEFmpGQYQ3aVltbYAMLklBQUJSTUFBQUFZbYACAYABhAWdz//////////////////////////8WY1z8kBFg4BuIiIiIYEBRYCQBYQXnlJOSkZBhDVxWW2BAUWAggYMDA4FSkGBAUpB7/////////////////////////////////////xkWYCCCAYBRe/////////////////////////////////////+DgYMWF4NSUFBQUGBAUWEGUZGQYQvGVltgAGBAUYCDA4FgAIZa8ZFQUD2AYACBFGEGjldgQFGRUGAfGWA/PQEWggFgQFI9glI9YABgIIQBPmEGk1ZbYGCRUFtQkVCRUIFhBqRXYBVhBrlWW4CAYCABkFGBAZBhBriRkGEN2lZbW2ADC5JQUFCUk1BQUFBWW2AAgGAAYQFnc///////////////////////////FmMJl5ToYOAbhoZgQFFgJAFhBwGSkZBhDgdWW2BAUWAggYMDA4FSkGBAUpB7/////////////////////////////////////xkWYCCCAYBRe/////////////////////////////////////+DgYMWF4NSUFBQUGBAUWEHa5GQYQvGVltgAGBAUYCDA4FgAIZa8ZFQUD2AYACBFGEHqFdgQFGRUGAfGWA/PQEWggFgQFI9glI9YABgIIQBPmEHrVZbYGCRUFtQkVCRUIFhB75XYBVhB9NWW4CAYCABkFGBAZBhB9KRkGEN2lZbW2ADC5JQUFCSkVBQVltgAIBgAGEBZ3P//////////////////////////xZjSRRr3mDgG4aGYEBRYCQBYQgZkpGQYQ4HVltgQFFgIIGDAwOBUpBgQFKQe/////////////////////////////////////8ZFmAgggGAUXv/////////////////////////////////////g4GDFheDUlBQUFBgQFFhCIORkGELxlZbYABgQFGAgwOBYACGWvGRUFA9gGAAgRRhCMBXYEBRkVBgHxlgPz0BFoIBYEBSPYJSPWAAYCCEAT5hCMVWW2BgkVBbUJFQkVCBYQjWV2AVYQjrVluAgGAgAZBRgQGQYQjqkZBhDdpWW1tgAwuSUFBQkpFQUFZbYRmcgGEOMYM5AZBWW2AAgP1bYABz//////////////////////////+CFpBQkZBQVltgAGEJNYJhCQpWW5BQkZBQVlthCUWBYQkqVluBFGEJUFdgAID9W1BWW2AAgTWQUGEJYoFhCTxWW5KRUFBWW2AAgWAHC5BQkZBQVlthCX6BYQloVluBFGEJiVdgAID9W1BWW2AAgTWQUGEJm4FhCXVWW5KRUFBWW2AAgGBAg4UDEhVhCbhXYQm3YQkFVltbYABhCcaFgoYBYQlTVluSUFBgIGEJ14WChgFhCYxWW5FQUJJQkpBQVltgAIGQUJGQUFZbYQn0gWEJ4VZbglJQUFZbYABgIIIBkFBhCg9gAIMBhGEJ61ZbkpFQUFZbYABgIIKEAxIVYQorV2EKKmEJBVZbW2AAYQo5hIKFAWEJU1ZbkVBQkpFQUFZbYACBkFCRkFBWW2EKVYFhCkJWW4EUYQpgV2AAgP1bUFZbYACBNZBQYQpygWEKTFZbkpFQUFZbYABgIIKEAxIVYQqOV2EKjWEJBVZbW2AAYQqchIKFAWEKY1ZbkVBQkpFQUFZbYACCglJgIIIBkFCSkVBQVlt/TmV2ZXIgZW5kcyB3ZWxsLgAAAAAAAAAAAAAAAAAAAABgAIIBUlBWW2AAYQrsYBCDYQqlVluRUGEK94JhCrZWW2AgggGQUJGQUFZbYABgIIIBkFCBgQNgAIMBUmELG4FhCt9WW5BQkZBQVlthCyuBYQkqVluCUlBQVltgAGAgggGQUGELRmAAgwGEYQsiVluSkVBQVltgAIFRkFCRkFBWW2AAgZBQkpFQUFZbYABbg4EQFWELgFeAggFRgYQBUmAggQGQUGELZVZbg4ERFWELj1dgAISEAVJbUFBQUFZbYABhC6CCYQtMVlthC6qBhWELV1Zbk1BhC7qBhWAghgFhC2JWW4CEAZFQUJKRUFBWW2AAYQvSgoRhC5VWW5FQgZBQkpFQUFZbYAB//wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACCFpBQkZBQVltgAIGQUJGQUFZbYQwkYQwfgmEL3VZbYQwJVluCUlBQVltgAIFgYBuQUJGQUFZbYABhDEKCYQwqVluQUJGQUFZbYABhDFSCYQw3VluQUJGQUFZbYQxsYQxngmEJKlZbYQxJVluCUlBQVltgAIGQUJGQUFZbYQyNYQyIgmEKQlZbYQxyVluCUlBQVltgAGEMn4KHYQwTVltgAYIBkVBhDK+ChmEMW1ZbYBSCAZFQYQy/goVhDHxWW2AgggGRUGEMz4KEYQx8VltgIIIBkVCBkFCVlFBQUFBQVlt/V2VsbCwgSSBuZXZlciEAAAAAAAAAAAAAAAAAAAAAAABgAIIBUlBWW2AAYQ0XYA6DYQqlVluRUGENIoJhDOFWW2AgggGQUJGQUFZbYABgIIIBkFCBgQNgAIMBUmENRoFhDQpWW5BQkZBQVlthDVaBYQloVluCUlBQVltgAGCAggGQUGENcWAAgwGHYQsiVlthDX5gIIMBhmELIlZbYQ2LYECDAYVhCyJWW2ENmGBggwGEYQ1NVluVlFBQUFBQVltgAIFgAwuQUJGQUFZbYQ23gWENoVZbgRRhDcJXYACA/VtQVltgAIFRkFBhDdSBYQ2uVluSkVBQVltgAGAggoQDEhVhDfBXYQ3vYQkFVltbYABhDf6EgoUBYQ3FVluRUFCSkVBQVltgAGBAggGQUGEOHGAAgwGFYQsiVlthDilgIIMBhGELIlZbk5JQUFBW/mCAYEBSNIAVYQAQV2AAgP1bUGEZfIBhACBgADlgAPP+YIBgQFI0gBVhABBXYACA/VtQYAQ2EGEATFdgADVg4ByAYwooTLYUYQBRV4BjSEqVqRRhAG1XgGPZQlQDFGEAiVeAY+pYziEUYQClV1tgAID9W2EAa2AEgDYDgQGQYQBmkZBhCTlWW2EAwVZbAFthAIdgBIA2A4EBkGEAgpGQYQk5VlthASVWWwBbYQCjYASANgOBAZBhAJ6RkGEJlVZbYQI2VlsAW2EAv2AEgDYDgQGQYQC6kZBhCZVWW2ECjlZbAFtgAIBgAGEA0oVgAIZhAuZWW5JQklCSUGAWYAMLgxRhAR5XYEBRfwjDeaAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgVJgBAFhARWQYQofVltgQFGAkQOQ/VtQUFBQUFZbYABgQFFhATOQYQaOVltgQFGAkQOQYADwgBWAFWEBT1c9YACAPj1gAP1bUJBQgHP//////////////////////////xZjCihMtmDgG4SEYEBRYCQBYQGEkpGQYQuYVltgQFFgIIGDAwOBUpBgQFKQe/////////////////////////////////////8ZFmAgggGAUXv/////////////////////////////////////g4GDFheDUlBQUFBgQFFhAe6RkGEMBFZbYABgQFGAgwOBhVr0kVBQPYBgAIEUYQIpV2BAUZFQYB8ZYD89ARaCAWBAUj2CUj1gAGAghAE+YQIuVltgYJFQW1BQUFBQUFZbYABhAkIwg2EEXlZbkFBgFmADC4EUYQKKV2BAUX8Iw3mgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIFSYAQBYQKBkGEMZ1ZbYEBRgJEDkP1bUFBWW2AAYQKaMINhBXZWW5BQYBZgAwuBFGEC4ldgQFF/CMN5oAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACBUmAEAWEC2ZBhDNNWW2BAUYCRA5D9W1BQVltgAIBgYGAAgGEBZ3P//////////////////////////xZjJ44LiGDgG4mJiWBAUWAkAWEDJJOSkZBhDRZWW2BAUWAggYMDA4FSkGBAUpB7/////////////////////////////////////xkWYCCCAYBRe/////////////////////////////////////+DgYMWF4NSUFBQUGBAUWEDjpGQYQwEVltgAGBAUYCDA4FgAIZa8ZFQUD2AYACBFGEDy1dgQFGRUGAfGWA/PQEWggFgQFI9glI9YABgIIQBPmED0FZbYGCRUFtQkVCRUIFhBCxXYBVgAIBn//////////+BERVhA/hXYQP3YQcjVltbYEBRkICCUoBgIAJgIAGCAWBAUoAVYQQmV4FgIAFgIIICgDaDN4CCAZFQUJBQW1BhBEFWW4CAYCABkFGBAZBhBECRkGEOslZbW4JgAwuSUICVUIGWUIKXUFBQUFBQk1CTUJOQUFZbYACAYABhAWdz//////////////////////////8WYwmXlOhg4BuGhmBAUWAkAWEEl5KRkGEPIVZbYEBRYCCBgwMDgVKQYEBSkHv/////////////////////////////////////GRZgIIIBgFF7/////////////////////////////////////4OBgxYXg1JQUFBQYEBRYQUBkZBhDARWW2AAYEBRgIMDgWAAhlrxkVBQPYBgAIEUYQU+V2BAUZFQYB8ZYD89ARaCAWBAUj2CUj1gAGAghAE+YQVDVltgYJFQW1CRUJFQgWEFVFdgFWEFaVZbgIBgIAGQUYEBkGEFaJGQYQ9KVltbYAMLklBQUJKRUFBWW2AAgGAAYQFnc///////////////////////////FmNJFGveYOAbhoZgQFFgJAFhBa+SkZBhDyFWW2BAUWAggYMDA4FSkGBAUpB7/////////////////////////////////////xkWYCCCAYBRe/////////////////////////////////////+DgYMWF4NSUFBQUGBAUWEGGZGQYQwEVltgAGBAUYCDA4FgAIZa8ZFQUD2AYACBFGEGVldgQFGRUGAfGWA/PQEWggFgQFI9glI9YABgIIQBPmEGW1ZbYGCRUFtQkVCRUIFhBmxXYBVhBoFWW4CAYCABkFGBAZBhBoCRkGEPSlZbW2ADC5JQUFCSkVBQVlthCc+AYQ94gzkBkFZbYABgQFGQUJBWW2AAgP1bYACA/VtgAHP//////////////////////////4IWkFCRkFBWW2AAYQbagmEGr1ZbkFCRkFBWW2EG6oFhBs9WW4EUYQb1V2AAgP1bUFZbYACBNZBQYQcHgWEG4VZbkpFQUFZbYACA/VtgAGAfGWAfgwEWkFCRkFBWW39OSHtxAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGAAUmBBYARSYCRgAP1bYQdbgmEHElZbgQGBgRBn//////////+CERcVYQd6V2EHeWEHI1ZbW4BgQFJQUFBWW2AAYQeNYQabVluQUGEHmYKCYQdSVluRkFBWW2AAZ///////////ghEVYQe5V2EHuGEHI1ZbW2AgggKQUGAggQGQUJGQUFZbYACA/VtgAID9W2AAZ///////////ghEVYQfvV2EH7mEHI1ZbW2EH+IJhBxJWW5BQYCCBAZBQkZBQVluCgYM3YACDgwFSUFBQVltgAGEIJ2EIIoRhB9RWW2EHg1ZbkFCCgVJgIIEBhISEAREVYQhDV2EIQmEHz1ZbW2EIToSChWEIBVZbUJOSUFBQVltgAIJgH4MBEmEIa1dhCGphBw1WW1uBNWEIe4SCYCCGAWEIFFZbkVBQkpFQUFZbYABhCJdhCJKEYQeeVlthB4NWW5BQgIOCUmAgggGQUGAghAKDAYWBERVhCLpXYQi5YQfKVltbg1uBgRAVYQkBV4A1Z///////////gREVYQjfV2EI3mEHDVZbW4CGAWEI7ImCYQhWVluFUmAghQGUUFBQYCCBAZBQYQi8VltQUFCTklBQUFZbYACCYB+DARJhCSBXYQkfYQcNVltbgTVhCTCEgmAghgFhCIRWW5FQUJKRUFBWW2AAgGBAg4UDEhVhCVBXYQlPYQalVltbYABhCV6FgoYBYQb4VluSUFBgIIMBNWf//////////4ERFWEJf1dhCX5hBqpWW1thCYuFgoYBYQkLVluRUFCSUJKQUFZbYABgIIKEAxIVYQmrV2EJqmEGpVZbW2AAYQm5hIKFAWEG+FZbkVBQkpFQUFZbYACCglJgIIIBkFCSkVBQVlt/Q2FuJ3QhAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABgAIIBUlBWW2AAYQoJYAaDYQnCVluRUGEKFIJhCdNWW2AgggGQUJGQUFZbYABgIIIBkFCBgQNgAIMBUmEKOIFhCfxWW5BQkZBQVlthCkiBYQbPVluCUlBQVltgAIFRkFCRkFBWW2AAgoJSYCCCAZBQkpFQUFZbYACBkFBgIIIBkFCRkFBWW2AAgVGQUJGQUFZbYACCglJgIIIBkFCSkVBQVltgAFuDgRAVYQq0V4CCAVGBhAFSYCCBAZBQYQqZVluDgREVYQrDV2AAhIQBUltQUFBQVltgAGEK1IJhCnpWW2EK3oGFYQqFVluTUGEK7oGFYCCGAWEKllZbYQr3gWEHElZbhAGRUFCSkVBQVltgAGELDoODYQrJVluQUJKRUFBWW2AAYCCCAZBQkZBQVltgAGELLoJhCk5WW2ELOIGFYQpZVluTUINgIIIChQFhC0qFYQpqVluAYABbhYEQFWELhleEhAOJUoFRYQtnhYJhCwJWW5RQYQtyg2ELFlZbklBgIIoBmVBQYAGBAZBQYQtOVltQgpdQh5VQUFBQUFCSkVBQVltgAGBAggGQUGELrWAAgwGFYQo/VluBgQNgIIMBUmELv4GEYQsjVluQUJOSUFBQVltgAIGQUJKRUFBWW2AAYQvegmEKelZbYQvogYVhC8hWW5NQYQv4gYVgIIYBYQqWVluAhAGRUFCSkVBQVltgAGEMEIKEYQvTVluRUIGQUJKRUFBWW39TbyB1bmZhaXIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGAAggFSUFZbYABhDFFgCYNhCcJWW5FQYQxcgmEMG1ZbYCCCAZBQkZBQVltgAGAgggGQUIGBA2AAgwFSYQyAgWEMRFZbkFCRkFBWW39JdCdzIHVuaGVhcmQgb2YuLi4AAAAAAAAAAAAAAAAAAGAAggFSUFZbYABhDL1gEoNhCcJWW5FQYQzIgmEMh1ZbYCCCAZBQkZBQVltgAGAgggGQUIGBA2AAgwFSYQzsgWEMsFZbkFCRkFBWW2AAZ///////////ghaQUJGQUFZbYQ0QgWEM81ZbglJQUFZbYABgYIIBkFBhDStgAIMBhmEKP1ZbYQ04YCCDAYVhDQdWW4GBA2BAgwFSYQ1KgYRhCyNWW5BQlJNQUFBQVltgAIFgAwuQUJGQUFZbYQ1qgWENVFZbgRRhDXVXYACA/VtQVltgAIFRkFBhDYeBYQ1hVluSkVBQVlthDZaBYQzzVluBFGENoVdgAID9W1BWW2AAgVGQUGENs4FhDY1WW5KRUFBWW2AAZ///////////ghEVYQ3UV2EN02EHI1ZbW2AgggKQUGAggQGQUJGQUFZbYACBkFCRkFBWW2EN+IFhDeVWW4EUYQ4DV2AAgP1bUFZbYACBUZBQYQ4VgWEN71ZbkpFQUFZbYABhDi5hDimEYQ25VlthB4NWW5BQgIOCUmAgggGQUGAghAKDAYWBERVhDlFXYQ5QYQfKVltbg1uBgRAVYQ56V4BhDmaIgmEOBlZbhFJgIIQBk1BQYCCBAZBQYQ5TVltQUFCTklBQUFZbYACCYB+DARJhDplXYQ6YYQcNVltbgVFhDqmEgmAghgFhDhtWW5FQUJKRUFBWW2AAgGAAYGCEhgMSFWEOy1dhDsphBqVWW1tgAGEO2YaChwFhDXhWW5NQUGAgYQ7qhoKHAWENpFZbklBQYECEAVFn//////////+BERVhDwtXYQ8KYQaqVltbYQ8XhoKHAWEOhFZbkVBQklCSUJJWW2AAYECCAZBQYQ82YACDAYVhCj9WW2EPQ2AggwGEYQo/VluTklBQUFZbYABgIIKEAxIVYQ9gV2EPX2EGpVZbW2AAYQ9uhIKFAWENeFZbkVBQkpFQUFb+YIBgQFI0gBVhABBXYACA/VtQYQmvgGEAIGAAOWAA8/5ggGBAUjSAFWEAEFdgAID9W1BgBDYQYQArV2AANWDgHIBjCihMthRhADBXW2AAgP1bYQBKYASANgOBAZBhAEWRkGEExlZbYQBMVlsAW2AAgGAAYQBdhWAAhmEAsFZbklCSUJJQYBZgAwuDFGEAqVdgQFF/CMN5oAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACBUmAEAWEAoJBhBX9WW2BAUYCRA5D9W1BQUFBQVltgAIBgYGAAgGEBZ3P//////////////////////////xZjJ44LiGDgG4mJiWBAUWAkAWEA7pOSkZBhBxtWW2BAUWAggYMDA4FSkGBAUpB7/////////////////////////////////////xkWYCCCAYBRe/////////////////////////////////////+DgYMWF4NSUFBQUGBAUWEBWJGQYQeVVltgAGBAUYCDA4FgAIZa8ZFQUD2AYACBFGEBlVdgQFGRUGAfGWA/PQEWggFgQFI9glI9YABgIIQBPmEBmlZbYGCRUFtQkVCRUIFhAfZXYBVgAIBn//////////+BERVhAcJXYQHBYQKwVltbYEBRkICCUoBgIAJgIAGCAWBAUoAVYQHwV4FgIAFgIIICgDaDN4CCAZFQUJBQW1BhAgtWW4CAYCABkFGBAZBhAgqRkGEJClZbW4JgAwuSUICVUIGWUIKXUFBQUFBQk1CTUJOQUFZbYABgQFGQUJBWW2AAgP1bYACA/VtgAHP//////////////////////////4IWkFCRkFBWW2AAYQJngmECPFZbkFCRkFBWW2ECd4FhAlxWW4EUYQKCV2AAgP1bUFZbYACBNZBQYQKUgWECblZbkpFQUFZbYACA/VtgAGAfGWAfgwEWkFCRkFBWW39OSHtxAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGAAUmBBYARSYCRgAP1bYQLogmECn1ZbgQGBgRBn//////////+CERcVYQMHV2EDBmECsFZbW4BgQFJQUFBWW2AAYQMaYQIoVluQUGEDJoKCYQLfVluRkFBWW2AAZ///////////ghEVYQNGV2EDRWECsFZbW2AgggKQUGAggQGQUJGQUFZbYACA/VtgAID9W2AAZ///////////ghEVYQN8V2EDe2ECsFZbW2EDhYJhAp9WW5BQYCCBAZBQkZBQVluCgYM3YACDgwFSUFBQVltgAGEDtGEDr4RhA2FWW2EDEFZbkFCCgVJgIIEBhISEAREVYQPQV2EDz2EDXFZbW2ED24SChWEDklZbUJOSUFBQVltgAIJgH4MBEmED+FdhA/dhAppWW1uBNWEECISCYCCGAWEDoVZbkVBQkpFQUFZbYABhBCRhBB+EYQMrVlthAxBWW5BQgIOCUmAgggGQUGAghAKDAYWBERVhBEdXYQRGYQNXVltbg1uBgRAVYQSOV4A1Z///////////gREVYQRsV2EEa2ECmlZbW4CGAWEEeYmCYQPjVluFUmAghQGUUFBQYCCBAZBQYQRJVltQUFCTklBQUFZbYACCYB+DARJhBK1XYQSsYQKaVltbgTVhBL2EgmAghgFhBBFWW5FQUJKRUFBWW2AAgGBAg4UDEhVhBN1XYQTcYQIyVltbYABhBOuFgoYBYQKFVluSUFBgIIMBNWf//////////4ERFWEFDFdhBQthAjdWW1thBRiFgoYBYQSYVluRUFCSUJKQUFZbYACCglJgIIIBkFCSkVBQVlt/Q2FuJ3QgZXZlbiEAAAAAAAAAAAAAAAAAAAAAAAAAAABgAIIBUlBWW2AAYQVpYAuDYQUiVluRUGEFdIJhBTNWW2AgggGQUJGQUFZbYABgIIIBkFCBgQNgAIMBUmEFmIFhBVxWW5BQkZBQVlthBaiBYQJcVluCUlBQVltgAGf//////////4IWkFCRkFBWW2EFy4FhBa5WW4JSUFBWW2AAgVGQUJGQUFZbYACCglJgIIIBkFCSkVBQVltgAIGQUGAgggGQUJGQUFZbYACBUZBQkZBQVltgAIKCUmAgggGQUJKRUFBWW2AAW4OBEBVhBjdXgIIBUYGEAVJgIIEBkFBhBhxWW4OBERVhBkZXYACEhAFSW1BQUFBWW2AAYQZXgmEF/VZbYQZhgYVhBghWW5NQYQZxgYVgIIYBYQYZVlthBnqBYQKfVluEAZFQUJKRUFBWW2AAYQaRg4NhBkxWW5BQkpFQUFZbYABgIIIBkFCRkFBWW2AAYQaxgmEF0VZbYQa7gYVhBdxWW5NQg2AgggKFAWEGzYVhBe1WW4BgAFuFgRAVYQcJV4SEA4lSgVFhBuqFgmEGhVZblFBhBvWDYQaZVluSUGAgigGZUFBgAYEBkFBhBtFWW1CCl1CHlVBQUFBQUJKRUFBWW2AAYGCCAZBQYQcwYACDAYZhBZ9WW2EHPWAggwGFYQXCVluBgQNgQIMBUmEHT4GEYQamVluQUJSTUFBQUFZbYACBkFCSkVBQVltgAGEHb4JhBf1WW2EHeYGFYQdZVluTUGEHiYGFYCCGAWEGGVZbgIQBkVBQkpFQUFZbYABhB6GChGEHZFZbkVCBkFCSkVBQVltgAIFgAwuQUJGQUFZbYQfCgWEHrFZbgRRhB81XYACA/VtQVltgAIFRkFBhB9+BYQe5VluSkVBQVlthB+6BYQWuVluBFGEH+VdgAID9W1BWW2AAgVGQUGEIC4FhB+VWW5KRUFBWW2AAZ///////////ghEVYQgsV2EIK2ECsFZbW2AgggKQUGAggQGQUJGQUFZbYACBkFCRkFBWW2EIUIFhCD1WW4EUYQhbV2AAgP1bUFZbYACBUZBQYQhtgWEIR1ZbkpFQUFZbYABhCIZhCIGEYQgRVlthAxBWW5BQgIOCUmAgggGQUGAghAKDAYWBERVhCKlXYQioYQNXVltbg1uBgRAVYQjSV4BhCL6IgmEIXlZbhFJgIIQBk1BQYCCBAZBQYQirVltQUFCTklBQUFZbYACCYB+DARJhCPFXYQjwYQKaVltbgVFhCQGEgmAghgFhCHNWW5FQUJKRUFBWW2AAgGAAYGCEhgMSFWEJI1dhCSJhAjJWW1tgAGEJMYaChwFhB9BWW5NQUGAgYQlChoKHAWEH/FZbklBQYECEAVFn//////////+BERVhCWNXYQliYQI3VltbYQlvhoKHAWEI3FZbkVBQklCSUJJW/qJkaXBmc1giEiCj3/kC2IbP9FPmYdKAOCCuJqtPRaSPoxlQvQMfCNPrrGRzb2xjQwAIDAAzomRpcGZzWCISIFUEWRgjAkhUQgZY1JRYtK7YTvKvriiYngMVaH9ZkUs/ZHNvbGNDAAgMADOiZGlwZnNYIhIgmQRA6jQvnIMRqpOW2WS8I9y1YmYl9QwPLW8+eAhDPOZkc29sY0MACAwAMyKAAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAo7u0MOgMYxQlKFgoUAAAAAAAAAAAAAAAAAAAAAAAABMVyBwoDGMUJEAFSFgoJCgIYAhCD8KEOCgkKAhhiEITwoQ4="},{"b64Body":"ChAKCQiO//6sBhDUFxIDGMMJEgIYAxiA/rWHASICCHgyIMODwq7DgsK3w4PCuXRGOMOCwq5Kw4PCi8ODwpDDg8KOOjAKAxjFCRCAkvQBIiSr97/YqrvM3e7/ABGqu8zd7v8AEaq7zN3u/wARqrvM3e7/ABE=","b64Record":"CiUIFiIDGMUJKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjAmy27S2YyE45V6BDIPmepxl/4IiA3gaibHMnpzqeNFrR3Mdt/RkLeUhR2k3MfDQ6QaDAjK//6sBhDG8cy4AiIQCgkIjv/+rAYQ1BcSAxjDCSogw4PCrsOCwrfDg8K5dEY4w4LCrkrDg8KLw4PCkMODwo4wgJirbDqkAgoDGMUJIoACAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACiAqMMBOgMYxglyBwoDGMUJEAJyBwoDGMYJEAFSGQoKCgIYYhCAsNbYAQoLCgMYwwkQ/6/W2AE="},{"b64Body":"ChIKCQiO//6sBhDUFxIDGMMJIAFCOBoiEiABSLhQDdysM7gHRRiQwoahpJVNxTLMBhtzzp//wO01W0IFCIDO2gNqC2NlbGxhciBkb29y","b64Record":"CgcIFiIDGMYJEjAHCYEpIfWDER6dLecNNS5UbdKmOXmOBCFJzI8lmFw53a4p3kJoX68QsKQ2bEU+rP0aDAjK//6sBhDH8cy4AiISCgkIjv/+rAYQ1BcSAxjDCSABQh0KAxjGCUoWChSbalfbIH86LRNoPcd+Kxk8fVyKR1IAegwIyv/+rAYQxvHMuAI="},{"b64Body":"ChAKCQiP//6sBhDWFxIDGMMJEgIYAxiA/rWHASICCHgyIMODwq7DgsK3w4PCuXRGOMOCwq5Kw4PCi8ODwpDDg8KOOjAKAxjFCRCAkvQBIiSr97/YqrvM3e7/ABGqu8zd7v8AEaq7zN3u/wARqrvM3e7/ABE=","b64Record":"CiUIISIDGMUJKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjDRoYovNRSToKO4f1hJ2+9n3VD7Pyprr6L+I7R5be5lO+aLEf8/YdeOJg0nNl/XI3YaCwjL//6sBhDXl7NdIhAKCQiP//6sBhDWFxIDGMMJKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjjCHyayFAToJGgIweCjBs/ABUhkKCgoCGGIQjpLZigIKCwoDGMMJEI2S2YoC"}]}}} \ No newline at end of file diff --git a/hedera-node/test-clients/src/eet/java/EndToEndTests.java b/hedera-node/test-clients/src/eet/java/EndToEndTests.java deleted file mode 100644 index 164ec25f1a03..000000000000 --- a/hedera-node/test-clients/src/eet/java/EndToEndTests.java +++ /dev/null @@ -1,502 +0,0 @@ -/* - * Copyright (C) 2022-2024 Hedera Hashgraph, LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import com.hedera.services.bdd.suites.autorenew.GracePeriodRestrictionsSuite; -// import com.hedera.services.bdd.suites.crypto.TransferWithCustomFractionalFees; -import com.hedera.services.bdd.suites.fees.CongestionPricingSuite; -import com.hedera.services.bdd.suites.file.ExchangeRateControlSuite; -import com.hedera.services.bdd.suites.file.FileUpdateSuite; -import com.hedera.services.bdd.suites.file.ProtectedFilesUpdateSuite; -import com.hedera.services.bdd.suites.leaky.FeatureFlagSuite; -import com.hedera.services.bdd.suites.records.ContractRecordsSanityCheckSuite; -import com.hedera.services.bdd.suites.records.CryptoRecordsSanityCheckSuite; -import com.hedera.services.bdd.suites.records.FileRecordsSanityCheckSuite; -import com.hedera.services.bdd.suites.records.RecordCreationSuite; -import com.hedera.services.bdd.suites.regression.AddressAliasIdFuzzing; -import com.hedera.services.bdd.suites.regression.HollowAccountCompletionFuzzing; -import com.hedera.services.bdd.suites.regression.TargetNetworkPrep; -import com.hedera.services.bdd.suites.regression.UmbrellaRedux; -import com.hedera.services.bdd.suites.schedule.ScheduleCreateSpecs; -import com.hedera.services.bdd.suites.schedule.ScheduleDeleteSpecs; -import com.hedera.services.bdd.suites.schedule.ScheduleExecutionSpecs; -import com.hedera.services.bdd.suites.schedule.ScheduleRecordSpecs; -import com.hedera.services.bdd.suites.schedule.ScheduleSignSpecs; -import com.hedera.services.bdd.suites.throttling.GasLimitThrottlingSuite; -import com.hedera.services.bdd.suites.throttling.ThrottleDefValidationSuite; -import java.util.Collection; -import java.util.List; -import org.junit.jupiter.api.DynamicContainer; -import org.junit.jupiter.api.MethodOrderer; -import org.junit.jupiter.api.Order; -import org.junit.jupiter.api.Tag; -import org.junit.jupiter.api.TestFactory; -import org.junit.jupiter.api.TestMethodOrder; - -@TestMethodOrder(MethodOrderer.OrderAnnotation.class) -class EndToEndTests extends E2ETestBase { - @Order(0) - @Tag("setup") - @TestFactory - Collection networkSetup() { - return List.of(extractSpecsFromSuite(TargetNetworkPrep::new), extractSpecsFromSuite(FeatureFlagSuite::new)); - } - - // These tests need to run first since they are hyper-sensitive to the tests in the - // contractPrecompile group. - // Running these after the the contractPrecompile group will cause the - // GasLimitOverMaxGasLimitFailsPrecheck & - // KvLimitsEnforced tests to fail. - @Tag("file") - @TestFactory - Collection file() { - return List.of( - // extractSpecsFromSuite(DiverseStateCreation::new), - // extractSpecsFromSuite(DiverseStateValidation::new), - extractSpecsFromSuite(ExchangeRateControlSuite::new), - // extractSpecsFromSuite(FetchSystemFiles::new), - // extractSpecsFromSuite(FileAppendSuite::new), - // extractSpecsFromSuite(FileCreateSuite::new), - // extractSpecsFromSuite(FileDeleteSuite::new), - extractSpecsFromSuite(FileUpdateSuite::new), - // extractSpecsFromSuite(PermissionSemanticsSpec::new), - extractSpecsFromSuite(ProtectedFilesUpdateSuite::new) - // extractSpecsFromSuite(ValidateNewAddressBook::new) - ); - } - - @Tag("autorenew") - @TestFactory - Collection autorenew() { - return List.of( - // extractSpecsFromSuite(AccountAutoRenewalSuite::new), - // extractSpecsFromSuite(AutoRemovalCasesSuite::new), - extractSpecsFromSuite(GracePeriodRestrictionsSuite::new) - // extractSpecsFromSuite(MacroFeesChargedSanityCheckSuite::new), TODO FAILS - // extractSpecsFromSuite(NoGprIfNoAutoRenewSuite::new), - // extractSpecsFromSuite(TopicAutoRenewalSuite::new) - ); - } - - @Tag("compose") - @TestFactory - Collection compose() { - return List.of( - // extractSpecsFromSuite(LocalNetworkCheck::new), - // extractSpecsFromSuite(PerpetualLocalCalls::new) - ); - } - - @Tag("consensus") - @TestFactory - Collection consensus() { - return List.of( - // extractSpecsFromSuite(AssortedHcsOps::new), - // extractSpecsFromSuite(ChunkingSuite::new), - // extractSpecsFromSuite(SubmitMessageSuite::new), - // extractSpecsFromSuite(TopicCreateSuite::new), - // extractSpecsFromSuite(TopicDeleteSuite::new), - // extractSpecsFromSuite(TopicGetInfoSuite::new), - // extractSpecsFromSuite(TopicUpdateSuite::new) - ); - } - - @Tag("contract") - @Tag("contract.precompile") - @Tag("contract.precompile.part1") - @TestFactory - Collection contractPrecompile() { - return List.of( - // extractSpecsFromSuite(AssociatePrecompileSuite::new), - // extractSpecsFromSuite(ContractBurnHTSSuite::new), - // extractSpecsFromSuite(ContractHTSSuite::new), - // extractSpecsFromSuite(ContractKeysHTSSuite::new), - // extractSpecsFromSuite(ContractMintHTSSuite::new) - // extractSpecsFromSuite(CreatePrecompileSuite::new) - ); - } - - @Tag("contract") - @Tag("contract.precompile") - @Tag("contract.precompile.part2") - @TestFactory - Collection contractPrecompile2() { - return List.of( - new DynamicContainer[] { - // extractSpecsFromSuite(CryptoTransferHTSSuite::new), - // extractSpecsFromSuite(DelegatePrecompileSuite::new), - // extractSpecsFromSuite(DynamicGasCostSuite::new), - // extractSpecsFromSuite(MixedHTSPrecompileTestsSuite::new) - }); - } - - @Tag("contract") - @Tag("contract.openzeppelin") - @TestFactory - Collection contractOpenZeppelin() { - return List.of( - // extractSpecsFromSuite(ERC20ContractInteractions::new), - // extractSpecsFromSuite(ERC721ContractInteractions::new), - // extractSpecsFromSuite(ERC1155ContractInteractions::new) - ); - } - - @Tag("contract") - @Tag("contract.records") - @TestFactory - Collection contractRecords() { - return List.of( - // extractSpecsFromSuite(LogsSuite::new), - // extractSpecsFromSuite(RecordsSuite::new) - ); - } - - @Tag("contract") - @Tag("contract.opcodes") - @TestFactory - Collection contractOpcodes() { - return List.of( - // extractSpecsFromSuite(BalanceOperationSuite::new), - // extractSpecsFromSuite(CallCodeOperationSuite::new), - // extractSpecsFromSuite(CallOperationSuite::new), - // extractSpecsFromSuite(CreateOperationSuite::new), - // extractSpecsFromSuite(DelegateCallOperationSuite::new), - // extractSpecsFromSuite(ExtCodeCopyOperationSuite::new), - // extractSpecsFromSuite(ExtCodeHashOperationSuite::new), - // extractSpecsFromSuite(ExtCodeSizeOperationSuite::new), - // extractSpecsFromSuite(GlobalPropertiesSuite::new), - // extractSpecsFromSuite(SelfDestructSuite::new), - // extractSpecsFromSuite(SStoreSuite::new), - // extractSpecsFromSuite(StaticCallOperationSuite::new) - ); - } - - @Tag("contract") - @Tag("contract.hapi") - @TestFactory - Collection contractHapi() { - return List.of( - new DynamicContainer[] { - // extractSpecsFromSuite(ContractCallLocalSuite::new), TODO FAILS - // extractSpecsFromSuite(ContractCallSuite::new), TODO FAILS - // extractSpecsFromSuite(ContractCreateSuite::new), - // extractSpecsFromSuite(ContractDeleteSuite::new), TODO FAILS - // extractSpecsFromSuite(ContractGetBytecodeSuite::new), - // extractSpecsFromSuite(ContractGetInfoSuite::new), - // extractSpecsFromSuite(ContractMusicalChairsSuite::new), - // extractSpecsFromSuite(ContractUpdateSuite::new) - }); - } - - @Tag("crypto") - @TestFactory - Collection crypto() { - return List.of( - // extractSpecsFromSuite(AutoAccountCreationSuite::new), // TODO Fails BUT SHOULD - // PASS - // extractSpecsFromSuite(AutoAccountUpdateSuite::new), // TODO Fails BUT SHOULD - // PASS - // extractSpecsFromSuite(CryptoApproveAllowanceSuite::new), - // extractSpecsFromSuite(CryptoDeleteAllowanceSuite::new), - // extractSpecsFromSuite(CryptoCornerCasesSuite::new), - // extractSpecsFromSuite(CryptoCreateSuite::new), - // extractSpecsFromSuite(CryptoDeleteSuite::new), - // extractSpecsFromSuite(CryptoGetInfoRegression::new), - // extractSpecsFromSuite(CryptoGetRecordsRegression::new), // TODO Fails - // extractSpecsFromSuite(CryptoTransferSuite::new), - // extractSpecsFromSuite(CryptoUpdateSuite::new) - // extractSpecsFromSuite(CrytoCreateSuiteWithUTF8::new), - // extractSpecsFromSuite(HelloWorldSpec::new), - // extractSpecsFromSuite(MiscCryptoSuite::new), - // extractSpecsFromSuite(QueryPaymentSuite::new), - // extractSpecsFromSuite(RandomOps::new), // TODO Fails - // extractSpecsFromSuite(TransferWithCustomFixedFees::new), - // extractSpecsFromSuite(TransferWithCustomFractionalFees::new), - // extractSpecsFromSuite(TxnReceiptRegression::new), - // extractSpecsFromSuite(TxnRecordRegression::new), // TODO Fails - // extractSpecsFromSuite(UnsupportedQueriesRegression::new) // TODO Fails - ); - } - - @Tag("fees") - @TestFactory - Collection fees() { - return List.of( - // extractSpecsFromSuite(AllBaseOpFeesSuite::new), - extractSpecsFromSuite(CongestionPricingSuite::new) - // extractSpecsFromSuite(CostOfEverythingSuite::new), - // extractSpecsFromSuite(CreateAndUpdateOps::new), - // extractSpecsFromSuite(OverlappingKeysSuite::new), - // extractSpecsFromSuite(QueryPaymentExploitsSuite::new), - // extractSpecsFromSuite(SpecialAccountsAreExempted::new) - // extractSpecsFromSuite(TransferListServiceFeesSuite::new) - ); - } - - @Tag("file") - @Tag("file.positive") - @TestFactory - Collection filePositive() { - return List.of( - // extractSpecsFromSuite(CreateSuccessSpec::new), - // extractSpecsFromSuite(SysDelSysUndelSpec::new) - ); - } - - @Tag("file") - @Tag("file.negative") - @TestFactory - Collection fileNegative() { - return List.of( - // extractSpecsFromSuite(AppendFailuresSpec::new), - // extractSpecsFromSuite(CreateFailuresSpec::new), - // extractSpecsFromSuite(DeleteFailuresSpec::new), - // extractSpecsFromSuite(QueryFailuresSpec::new), - // extractSpecsFromSuite(UpdateFailuresSpec::new) - ); - } - - // TODO MISSING: NewOpInConstructorSpecs, ChildStorageSpecs, BigArraySpec, - // SmartContractInlineAssemblySpec, OCTokenSpec, SmartContractFailFirstSpec, - // SmartContractSelfDestructSpec, DeprecatedContractKeySpecs - - @Tag("issues") - @TestFactory - Collection issues() { - return List.of( - // extractSpecsFromSuite(Issue305Spec::new), - // extractSpecsFromSuite(Issue310Suite::new), - // extractSpecsFromSuite(Issue1648Suite::new), - // extractSpecsFromSuite(Issue1741Suite::new), - // extractSpecsFromSuite(Issue1742Suite::new), - // extractSpecsFromSuite(Issue1744Suite::new), - // extractSpecsFromSuite(Issue1758Suite::new), - // extractSpecsFromSuite(Issue1765Suite::new), - // extractSpecsFromSuite(Issue2051Spec::new), - // extractSpecsFromSuite(Issue2098Spec::new), - // extractSpecsFromSuite(Issue2143Spec::new), - // extractSpecsFromSuite(Issue2150Spec::new), - // extractSpecsFromSuite(Issue2319Spec::new) - ); - } - - @Tag("meta") - @TestFactory - Collection meta() { - return List.of( - // extractSpecsFromSuite(VersionInfoSpec::new) - ); - } - - @Tag("meta") - @TestFactory - Collection misc() { - return List.of( - // extractSpecsFromSuite(CannotDeleteSystemEntitiesSuite::new) - ); - } - - @Tag("perf") - @TestFactory - Collection perf() { - return List.of( - // extractSpecsFromSuite(AccountBalancesClientSaveLoadTest::new), - // extractSpecsFromSuite(AdjustFeeScheduleSuite::new), - // extractSpecsFromSuite(FileContractMemoPerfSuite::new), - // extractSpecsFromSuite(QueryOnlyLoadTest::new) - ); - } - - @Tag("perf") - @Tag("perf.contract") - @TestFactory - Collection perfContract() { - return List.of( - // extractSpecsFromSuite(ContractCallLoadTest::new), - // extractSpecsFromSuite(ContractCallLocalPerfSuite::new), - // extractSpecsFromSuite(ContractCallPerfSuite::new), - // extractSpecsFromSuite(ContractPerformanceSuite::new), - // extractSpecsFromSuite(FibonacciPlusLoadProvider::new), - // extractSpecsFromSuite(MixedSmartContractOpsLoadTest::new) - ); - } - - @Tag("perf") - @Tag("perf.contract.opcodes") - @TestFactory - Collection perfContractOpcodes() { - return List.of( - // extractSpecsFromSuite(SStoreOperationLoadTest::new) - ); - } - - @Tag("perf") - @Tag("perf.crypto") - @TestFactory - Collection perfCrypto() { - return List.of( - // extractSpecsFromSuite(CryptoAllowancePerfSuite::new), - // extractSpecsFromSuite(CryptoCreatePerfSuite::new), - // extractSpecsFromSuite(CryptoTransferLoadTest::new), - // extractSpecsFromSuite(CryptoTransferLoadTestWithAutoAccounts::new), - // extractSpecsFromSuite(CryptoTransferLoadTestWithInvalidAccounts::new), - // extractSpecsFromSuite(CryptoTransferPerfSuite::new), - // extractSpecsFromSuite(CryptoTransferPerfSuiteWOpProvider::new), - // extractSpecsFromSuite(SimpleXfersAvoidingHotspot::new) - ); - } - - @Tag("perf") - @Tag("perf.file") - @TestFactory - Collection perfFile() { - return List.of( - // extractSpecsFromSuite(FileExpansionLoadProvider::new), - // extractSpecsFromSuite(FileUpdateLoadTest::new), - // extractSpecsFromSuite(MixedFileOpsLoadTest::new) - ); - } - - @Tag("perf") - @Tag("perf.mixedops") - @TestFactory - Collection perfMixedOps() { - return List.of( - // extractSpecsFromSuite(MixedFileOpsLoadTest::new), - // extractSpecsFromSuite(MixedOpsMemoPerfSuite::new), - // extractSpecsFromSuite(MixedTransferAndSubmitLoadTest::new), - // extractSpecsFromSuite(MixedTransferCallAndSubmitLoadTest::new) - ); - } - - @Tag("perf") - @Tag("perf.schedule") - @TestFactory - Collection perfSchedule() { - return List.of( - // extractSpecsFromSuite(OnePendingSigScheduledXfersLoad::new), - // extractSpecsFromSuite(ReadyToRunScheduledXfersLoad::new) - ); - } - - @Tag("perf") - @Tag("perf.token") - @TestFactory - Collection perfToken() { - return List.of( - // extractSpecsFromSuite(TokenCreatePerfSuite::new), - // extractSpecsFromSuite(TokenRelStatusChanges::new), - // extractSpecsFromSuite(TokenTransferBasicLoadTest::new), - // extractSpecsFromSuite(TokenTransfersLoadProvider::new), - // extractSpecsFromSuite(UniqueTokenStateSetup::new) - ); - } - - @Tag("perf") - @Tag("perf.topic") - @TestFactory - Collection perfTopic() { - return List.of( - // extractSpecsFromSuite(createTopicLoadTest::new), - // extractSpecsFromSuite(CreateTopicPerfSuite::new), - // extractSpecsFromSuite(HCSChunkingRealisticPerfSuite::new), - // extractSpecsFromSuite(SubmitMessageLoadTest::new), - // extractSpecsFromSuite(SubmitMessagePerfSuite::new) - ); - } - - @Tag("records") - @TestFactory - Collection records() { - return List.of( - extractSpecsFromSuite(RecordCreationSuite::new), - extractSpecsFromSuite(FileRecordsSanityCheckSuite::new), - extractSpecsFromSuite(ContractRecordsSanityCheckSuite::new), - extractSpecsFromSuite(CryptoRecordsSanityCheckSuite::new) - // extractSpecsFromSuite(DuplicateManagementTest::new), - // extractSpecsFromSuite(SignedTransactionBytesRecordsSuite::new) - ); - } - - @Tag("regression") - @TestFactory - Collection regression() { - return List.of( - // extractSpecsFromSuite(SplittingThrottlesWorks::new), - // extractSpecsFromSuite(SteadyStateThrottlingCheck::new), - extractSpecsFromSuite(UmbrellaRedux::new), - extractSpecsFromSuite(AddressAliasIdFuzzing::new), - extractSpecsFromSuite(HollowAccountCompletionFuzzing::new)); - } - - @Tag("throttling") - @TestFactory - Collection throttling() { - return List.of( - extractSpecsFromSuite(GasLimitThrottlingSuite::new), - // extractSpecsFromSuite(PrivilegedOpsSuite::new), TODO FAILS - extractSpecsFromSuite(ThrottleDefValidationSuite::new)); - } - - @Tag("schedule") - @TestFactory - Collection schedule() { - return List.of( - extractSpecsFromSuite(ScheduleCreateSpecs::new), - extractSpecsFromSuite(ScheduleSignSpecs::new), - extractSpecsFromSuite(ScheduleRecordSpecs::new), - extractSpecsFromSuite(ScheduleDeleteSpecs::new), - extractSpecsFromSuite(ScheduleExecutionSpecs::new) - // extractSpecsFromSuite(ScheduleExecutionSpecStateful::new), - ); - } - - @Tag("streaming") - @TestFactory - Collection streaming() { - return List.of( - // extractSpecsFromSuite(RunTransfers::new) - ); - } - - @Tag("token") - @TestFactory - Collection token() { - return List.of( - // extractSpecsFromSuite(Hip17UnhappyAccountsSuite::new), - // extractSpecsFromSuite(Hip17UnhappyTokensSuite::new), - // extractSpecsFromSuite(TokenAssociationSpecs::new), - // extractSpecsFromSuite(TokenCreateSpecs::new), - // extractSpecsFromSuite(TokenDeleteSpecs::new), - // extractSpecsFromSuite(TokenFeeScheduleUpdateSpecs::new), - // extractSpecsFromSuite(TokenManagementSpecs::new), - // extractSpecsFromSuite(TokenManagementSpecsStateful::new), - // extractSpecsFromSuite(TokenMiscOps::new), - // extractSpecsFromSuite(TokenPauseSpecs::new), - // extractSpecsFromSuite(TokenTotalSupplyAfterMintBurnWipeSuite::new), - // extractSpecsFromSuite(TokenTransactSpecs::new), - // extractSpecsFromSuite(TokenUpdateSpecs::new) - // extractSpecsFromSuite(UniqueTokenManagementSpecs::new) - ); - } - - @Tag("utils") - @TestFactory - Collection utils() { - return List.of( - // extractSpecsFromSuite(SubmitMessagePerfSuite::new) - ); - } -} diff --git a/hedera-node/test-clients/src/itest/java/ConcurrentSuites.java b/hedera-node/test-clients/src/itest/java/ConcurrentSuites.java index 2ed6be791a1a..0ed4d2b07040 100644 --- a/hedera-node/test-clients/src/itest/java/ConcurrentSuites.java +++ b/hedera-node/test-clients/src/itest/java/ConcurrentSuites.java @@ -15,11 +15,9 @@ */ import com.hedera.services.bdd.suites.HapiSuite; -import com.hedera.services.bdd.suites.consensus.ChunkingSuite; import com.hedera.services.bdd.suites.consensus.SubmitMessageSuite; import com.hedera.services.bdd.suites.consensus.TopicCreateSuite; import com.hedera.services.bdd.suites.consensus.TopicDeleteSuite; -import com.hedera.services.bdd.suites.consensus.TopicGetInfoSuite; import com.hedera.services.bdd.suites.consensus.TopicUpdateSuite; import com.hedera.services.bdd.suites.contract.evm.Evm46ValidationSuite; import com.hedera.services.bdd.suites.contract.hapi.ContractCallLocalSuite; @@ -28,7 +26,6 @@ import com.hedera.services.bdd.suites.contract.hapi.ContractDeleteSuite; import com.hedera.services.bdd.suites.contract.hapi.ContractGetBytecodeSuite; import com.hedera.services.bdd.suites.contract.hapi.ContractGetInfoSuite; -import com.hedera.services.bdd.suites.contract.hapi.ContractMusicalChairsSuite; import com.hedera.services.bdd.suites.contract.hapi.ContractUpdateSuite; import com.hedera.services.bdd.suites.contract.opcodes.CreateOperationSuite; import com.hedera.services.bdd.suites.contract.opcodes.GlobalPropertiesSuite; @@ -79,11 +76,9 @@ import com.hedera.services.bdd.suites.file.FileAppendSuite; import com.hedera.services.bdd.suites.file.FileCreateSuite; import com.hedera.services.bdd.suites.file.PermissionSemanticsSpec; -import com.hedera.services.bdd.suites.file.negative.QueryFailuresSpec; import com.hedera.services.bdd.suites.file.negative.UpdateFailuresSpec; import com.hedera.services.bdd.suites.file.positive.SysDelSysUndelSpec; import com.hedera.services.bdd.suites.meta.VersionInfoSpec; -import com.hedera.services.bdd.suites.records.SignedTransactionBytesRecordsSuite; import com.hedera.services.bdd.suites.token.TokenAssociationSpecs; import com.hedera.services.bdd.suites.token.TokenCreateSpecs; import com.hedera.services.bdd.suites.token.TokenDeleteSpecs; @@ -91,11 +86,7 @@ import com.hedera.services.bdd.suites.token.TokenPauseSpecs; import com.hedera.services.bdd.suites.token.TokenTransactSpecs; import com.hedera.services.bdd.suites.token.TokenUpdateSpecs; -import com.hederahashgraph.api.proto.java.HederaFunctionality; -import java.util.Map; -import java.util.Set; import java.util.function.Supplier; -import java.util.stream.Collectors; /** The set of BDD tests that we can execute in parallel. */ public class ConcurrentSuites { @@ -107,7 +98,6 @@ static Supplier[] all() { CryptoApproveAllowanceSuite::new, TokenPauseSpecs::new, FileAppendSuite::new, - TopicGetInfoSuite::new, AutoAccountCreationSuite::new, HollowAccountFinalizationSuite::new, TokenAssociationSpecs::new, @@ -117,16 +107,13 @@ static Supplier[] all() { TokenManagementSpecs::new, TokenTransactSpecs::new, FileCreateSuite::new, - QueryFailuresSpec::new, PermissionSemanticsSpec::new, SysDelSysUndelSpec::new, UpdateFailuresSpec::new, - SignedTransactionBytesRecordsSuite::new, TopicCreateSuite::new, TopicDeleteSuite::new, TopicUpdateSuite::new, SubmitMessageSuite::new, - ChunkingSuite::new, CryptoTransferSuite::new, CryptoUpdateSuite::new, SelfDestructSuite::new, @@ -137,7 +124,6 @@ static Supplier[] all() { ContractDeleteSuite::new, ContractGetBytecodeSuite::new, ContractGetInfoSuite::new, - ContractMusicalChairsSuite::new, ContractUpdateSuite::new, // contract.opcode CreateOperationSuite::new, @@ -188,23 +174,6 @@ static Supplier[] all() { }; } - /** - * Wrap a suite supplier with a call to set the auto-scheduling override for the given functions. - * - * @param suiteSupplier the suite supplier to wrap - * @param functions the functions to auto-schedule - * @return the wrapped suite supplier - */ - private static Supplier withAutoScheduling( - final Supplier suiteSupplier, final Set functions) { - return () -> { - final var suite = suiteSupplier.get(); - final var commaSeparated = functions.stream().map(Enum::toString).collect(Collectors.joining(",")); - suite.setOverrides(Map.of("spec.autoScheduledTxns", commaSeparated)); - return suite; - }; - } - /* All the EVM suites that should be executed with Ethereum calls to verify Ethereum compatibility. @@ -249,7 +218,6 @@ static Supplier[] ethereumSuites() { ContractDeleteSuite::new, ContractGetBytecodeSuite::new, ContractGetInfoSuite::new, - ContractMusicalChairsSuite::new, ContractUpdateSuite::new, // contracts.openZeppelin ERC20ContractInteractions::new, diff --git a/hedera-node/test-clients/src/itest/java/SequentialSuites.java b/hedera-node/test-clients/src/itest/java/SequentialSuites.java index d6f40d97e62c..532f9d5ea782 100644 --- a/hedera-node/test-clients/src/itest/java/SequentialSuites.java +++ b/hedera-node/test-clients/src/itest/java/SequentialSuites.java @@ -19,7 +19,7 @@ import com.hedera.services.bdd.suites.contract.opcodes.Create2OperationSuite; import com.hedera.services.bdd.suites.contract.traceability.TraceabilitySuite; import com.hedera.services.bdd.suites.crypto.staking.StakingSuite; -import com.hedera.services.bdd.suites.fees.SpecialAccountsAreExempted; +import com.hedera.services.bdd.suites.fees.FeeScheduleUpdateWaiverTest; import com.hedera.services.bdd.suites.leaky.FeatureFlagSuite; import com.hedera.services.bdd.suites.leaky.LeakyContractTestsSuite; import com.hedera.services.bdd.suites.leaky.LeakyCryptoTestsSuite; @@ -44,7 +44,7 @@ static Supplier[] globalPrerequisiteSuites() { @SuppressWarnings("unchecked") static Supplier[] sequentialSuites() { return (Supplier[]) new Supplier[] { - SpecialAccountsAreExempted::new, + FeeScheduleUpdateWaiverTest::new, PrivilegedOpsSuite::new, TraceabilitySuite::new, LeakyContractTestsSuite::new, diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/junit/ContextRequirement.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/junit/ContextRequirement.java new file mode 100644 index 000000000000..71f3b5aa1e8f --- /dev/null +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/junit/ContextRequirement.java @@ -0,0 +1,65 @@ +/* + * Copyright (C) 2024 Hedera Hashgraph, LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.hedera.services.bdd.junit; + +public enum ContextRequirement { + /** + * The test expects a predictable relationship between its created entity numbers, + * meaning that it will fail if other tests are concurrently creating entities. + */ + NO_CONCURRENT_CREATIONS, + /** + * The test requires that its transactions are guaranteed to be the first in a + * staking period. + */ + NO_CONCURRENT_STAKE_PERIOD_BOUNDARY_CROSSINGS, + /** + * The test requires changes to the network properties, which might break other + * concurrent tests if they expect the default properties. + */ + PROPERTY_OVERRIDES, + /** + * The test requires changes to the network permissions, which might break other + * concurrent tests if they expect the default permissions. + */ + PERMISSION_OVERRIDES, + /** + * The test requires changes to the network throttle definitions, which might break + * other concurrent tests if they expect the default throttles. + */ + THROTTLE_OVERRIDES, + /** + * The test requires changes to the network fee schedules, which might break + * other concurrent tests if they expect the default fees. + */ + FEE_SCHEDULE_OVERRIDES, + /** + * The test requires the upgrade files to be in a specific state, which could + * be violated by other concurrent tests. + */ + UPGRADE_FILE_CONTENT, + /** + * The test depends on system account balances being affected exclusively by its + * own operations, and not by those of other concurrent tests. + */ + SYSTEM_ACCOUNT_BALANCES, + /** + * The test depends on system account keys being affected exclusively by its + * own operations, and not by those of other concurrent tests. + */ + SYSTEM_ACCOUNT_KEYS +} diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/junit/HapiTest.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/junit/HapiTest.java index 5fcf9f005539..11a77b1a7fbf 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/junit/HapiTest.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/junit/HapiTest.java @@ -16,20 +16,26 @@ package com.hedera.services.bdd.junit; -import com.hedera.services.bdd.spec.HapiSpec; -import java.lang.annotation.Documented; +import static org.junit.jupiter.api.parallel.ResourceAccessMode.READ; + +import com.hedera.services.bdd.junit.extensions.NetworkTargetingExtension; +import com.hedera.services.bdd.junit.extensions.SpecNamingExtension; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; -import org.junit.platform.commons.annotation.Testable; +import org.junit.jupiter.api.TestFactory; +import org.junit.jupiter.api.extension.ExtendWith; +import org.junit.jupiter.api.parallel.ResourceLock; /** - * {@link HapiTest} is used to signal that the annotated method is a HapiSpec test method. This method may - * be private, but it must take zero args, and return a {@link HapiSpec}. + * The main annotation in this package; marks a method as a factory for dynamic tests + * that will target the shared test network and use the {@link SpecNamingExtension} to + * name the tests. */ -@Target({ElementType.ANNOTATION_TYPE, ElementType.METHOD}) +@Target({ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) -@Documented -@Testable +@TestFactory +@ExtendWith({NetworkTargetingExtension.class, SpecNamingExtension.class}) +@ResourceLock(value = "NETWORK", mode = READ) public @interface HapiTest {} diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/junit/HapiTestEngine.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/junit/HapiTestEngine.java deleted file mode 100644 index 330864b94b9b..000000000000 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/junit/HapiTestEngine.java +++ /dev/null @@ -1,590 +0,0 @@ -/* - * Copyright (C) 2023-2024 Hedera Hashgraph, LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.hedera.services.bdd.junit; - -import static com.hedera.services.bdd.junit.HapiTestEnv.HapiTestNodesType.IN_PROCESS_ALICE; -import static com.hedera.services.bdd.junit.HapiTestEnv.HapiTestNodesType.OUT_OF_PROCESS_ALICE; -import static com.hedera.services.bdd.junit.RecordStreamAccess.RECORD_STREAM_ACCESS; -import static org.junit.platform.commons.support.AnnotationSupport.findAnnotation; -import static org.junit.platform.commons.support.AnnotationSupport.isAnnotated; -import static org.junit.platform.commons.support.HierarchyTraversalMode.TOP_DOWN; - -import com.hedera.node.app.Hedera; -import com.hedera.services.bdd.junit.validators.BlockNoValidator; -import com.hedera.services.bdd.spec.HapiSpec; -import com.hedera.services.bdd.spec.props.JutilPropertySource; -import com.hedera.services.bdd.suites.BddMethodIsNotATest; -import com.hedera.services.bdd.suites.HapiSuite; -import com.hedera.services.bdd.suites.TargetNetworkType; -import com.hedera.services.bdd.suites.records.ClosingTime; -import edu.umd.cs.findbugs.annotations.NonNull; -import java.io.File; -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; -import java.net.URI; -import java.nio.file.Path; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Comparator; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.function.Predicate; -import java.util.stream.Stream; -import org.apache.logging.log4j.Level; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; -import org.junit.jupiter.api.Disabled; -import org.junit.jupiter.api.MethodOrderer; -import org.junit.jupiter.api.Order; -import org.junit.jupiter.api.Tag; -import org.junit.jupiter.api.Tags; -import org.junit.jupiter.api.TestMethodOrder; -import org.junit.platform.commons.support.ReflectionSupport; -import org.junit.platform.engine.EngineDiscoveryRequest; -import org.junit.platform.engine.ExecutionRequest; -import org.junit.platform.engine.Filter; -import org.junit.platform.engine.TestDescriptor; -import org.junit.platform.engine.TestEngine; -import org.junit.platform.engine.TestTag; -import org.junit.platform.engine.UniqueId; -import org.junit.platform.engine.discovery.ClassSelector; -import org.junit.platform.engine.discovery.ClasspathRootSelector; -import org.junit.platform.engine.discovery.MethodSelector; -import org.junit.platform.engine.discovery.PackageNameFilter; -import org.junit.platform.engine.support.descriptor.AbstractTestDescriptor; -import org.junit.platform.engine.support.descriptor.ClassSource; -import org.junit.platform.engine.support.descriptor.EngineDescriptor; -import org.junit.platform.engine.support.descriptor.MethodSource; -import org.junit.platform.engine.support.hierarchical.HierarchicalTestEngine; -import org.junit.platform.engine.support.hierarchical.Node; - -/** - * An implementation of a JUnit {@link TestEngine} to execute HAPI Specification Tests. - * - *

    This implementation automatically locates all test classes annotated with {@link HapiTestSuite}. Within those - * classes, any methods that take no args and return a {@link HapiSpec} will be picked up as test methods, but will be - * skipped. Any of those methods that are also annotated with {@link HapiTest} will be executed. Such methods also - * support the JUnit Jupiter {@link Disabled} annotation. - */ -public class HapiTestEngine extends HierarchicalTestEngine /* implements TestEngine */ { - - public static final int NODE_COUNT = 4; - - static { - // This is really weird, but it exists because we have to force JUL to use Log4J as early as possible. - System.setProperty("java.util.logging.manager", "org.apache.logging.log4j.jul.LogManager"); - } - - /** - * Tests whether a class is annotated with {@link HapiTestSuite}. - */ - private static final Predicate> IS_HAPI_TEST_SUITE = - classCandidate -> isAnnotated(classCandidate, HapiTestSuite.class); - - /** - * Tests whether a method is annotated with {@link HapiTest}, or whether it is a no-arg method that returns a - * {@link HapiSpec}. Any of the former type of method will be executed, while any of the latter will be skipped. - */ - private static final Predicate IS_HAPI_TEST = - methodCandidate -> isAnnotated(methodCandidate, HapiTest.class) - || (!isAnnotated(methodCandidate, BddMethodIsNotATest.class) - && methodCandidate.getParameterCount() == 0 - && methodCandidate.getReturnType() == HapiSpec.class); - - private static final Comparator SUITE_DESCRIPTOR_COMPARATOR = - Comparator.comparingInt(ClassTestDescriptor::order); - private static final Comparator noSorting = (m1, m2) -> 0; - private static final Comparator sortMethodsAscByOrderNumber = (m1, m2) -> { - final var m1Order = m1.getAnnotation(Order.class); - final var m1OrderValue = m1Order != null ? m1Order.value() : 0; - final var m2Order = m2.getAnnotation(Order.class); - final var m2OrderValue = m2Order != null ? m2Order.value() : 0; - return m1OrderValue - m2OrderValue; - }; - - @Override - public String getId() { - return "hapi-suite-test"; - } - - /** - * {@inheritDoc} - * - *

    This method is responsible for discovering the classes and methods that form our tests. It constructs a tree - * of {@link TestDescriptor}s, one for each class annotated with {@link HapiTestSuite}, where each such - * {@link ClassTestDescriptor} has a child for each spec method (whether, or not annotated with {@link HapiTest}). - */ - @Override - public TestDescriptor discover(EngineDiscoveryRequest discoveryRequest, UniqueId uniqueId) { - final var engineDescriptor = new HapiEngineDescriptor(uniqueId); - final List suites = new ArrayList<>(); - - discoveryRequest.getSelectorsByType(MethodSelector.class).forEach(selector -> { - final var javaClass = selector.getJavaClass(); - addChildToSuites(javaClass, discoveryRequest, engineDescriptor, suites); - }); - - discoveryRequest.getSelectorsByType(ClassSelector.class).forEach(selector -> { - final var javaClass = selector.getJavaClass(); - addChildToSuites(javaClass, discoveryRequest, engineDescriptor, suites); - }); - - discoveryRequest.getSelectorsByType(ClasspathRootSelector.class).forEach(selector -> { - appendSuitesInClasspathRoot(selector.getClasspathRoot(), engineDescriptor, discoveryRequest, suites); - }); - - suites.sort(SUITE_DESCRIPTOR_COMPARATOR); - suites.forEach(engineDescriptor::addChild); - - // if hapi test suites are being run, add record stream validation to the engineDescriptor as well - if (!suites.isEmpty()) { - engineDescriptor.addChild(new RecordStreamValidationTestDescriptor(engineDescriptor)); - } - - return engineDescriptor; - } - - private static void addChildToSuites( - @NonNull final Class javaClass, - @NonNull final EngineDiscoveryRequest discoveryRequest, - @NonNull final EngineDescriptor engineDescriptor, - @NonNull final List orderedSuites) { - if (IS_HAPI_TEST_SUITE.test(javaClass)) { - final var classDescriptor = new ClassTestDescriptor(javaClass, engineDescriptor, discoveryRequest); - if (!classDescriptor.skip) { - orderedSuites.add(classDescriptor); - } - } - } - - private void appendSuitesInClasspathRoot( - @NonNull final URI uri, - @NonNull final TestDescriptor engineDescriptor, - @NonNull final EngineDiscoveryRequest discoveryRequest, - List orderedSuites) { - ReflectionSupport.findAllClassesInClasspathRoot(uri, IS_HAPI_TEST_SUITE, name -> true).stream() - .filter(aClass -> discoveryRequest.getFiltersByType(PackageNameFilter.class).stream() - .map(Filter::toPredicate) - .allMatch(predicate -> predicate.test(aClass.getPackageName()))) - .map(aClass -> new ClassTestDescriptor(aClass, engineDescriptor, discoveryRequest)) - .filter(classTestDescriptor -> !classTestDescriptor.skip) - .forEach(orderedSuites::add); - } - - /** - * {@inheritDoc} - * - *

    We don't need to do anything here, just return our phony context - */ - @Override - protected HapiTestEngineExecutionContext createExecutionContext(ExecutionRequest request) { - // Populating the data needed for the context - return new HapiTestEngineExecutionContext(); - } - - private static final class HapiEngineDescriptor extends EngineDescriptor - implements Node { - - /** The Hedera test environment to use. We start it once at the start of all tests and reuse it. */ - private HapiTestEnv env; - - public HapiEngineDescriptor(UniqueId uniqueId) { - super(uniqueId, "Hapi Test"); - } - - @Override - public HapiTestEngineExecutionContext before(HapiTestEngineExecutionContext context) { - // If there are no children, then there is nothing to do. - if (super.getChildren().isEmpty()) { - return context; - } - - // Allow for a simple switch to enable in-process Alice node for debugging - final String debugEnv = System.getenv("HAPI_DEBUG_NODE"); - final boolean debugMode = Boolean.parseBoolean(debugEnv); - final var nodesType = debugMode ? IN_PROCESS_ALICE : OUT_OF_PROCESS_ALICE; - // For now, switching to non-in process servers, because in process doesn't work for the - // restart and reconnect testing. - env = new HapiTestEnv("HAPI Tests", NODE_COUNT, nodesType); - context.setEnv(env); - - final var tmpDir = Path.of("data"); - final var defaultProperties = JutilPropertySource.getDefaultInstance(); - final String recordStreamPath = tmpDir.resolve("recordStream").toString(); - final var parameters = - Map.of("recordStream.path", recordStreamPath, "ci.properties.map", "secondsWaitingServerUp=300"); - HapiSpec.runInCiMode( - String.valueOf(env.getNodeInfo()), - defaultProperties.get("default.payer"), - defaultProperties.get("default.node").split("\\.")[2], - defaultProperties.get("tls"), - defaultProperties.get("txn.proto.structure"), - defaultProperties.get("node.selector"), - parameters); - return context; - } - - @Override - public void after(HapiTestEngineExecutionContext context) throws Exception { - if (env != null && env.started()) { - env.terminate(); - env = null; - } - } - } - - /** - * Represents a class annotated with {@link HapiTestSuite}. A fresh, new consensus node will be started for each - * such test class, and terminated after it has been run. Each instance of this class is used both during discovery - * (thanks to {@link AbstractTestDescriptor}, and during test execution (thanks to the {@link Node} interface). - */ - private static final class ClassTestDescriptor extends AbstractTestDescriptor - implements Node { - /** The class annotated with {@link HapiTestSuite} */ - private final Class testClass; - /** We will skip initialization of a {@link Hedera} instance if there are no test methods */ - private final boolean skip; - /** Whether a separate cluster of nodes should be created for this test class (or reset the normal cluster) */ - private final boolean isolated; - - private final boolean fuzzyMatch; - private final int order; - - private final Set testTags; - - /** Creates a new descriptor for the given test class. */ - public ClassTestDescriptor(Class testClass, TestDescriptor parent, EngineDiscoveryRequest discoveryRequest) { - super( - parent.getUniqueId().append("class", testClass.getName()), - testClass.getSimpleName(), - ClassSource.from(testClass)); - this.testClass = testClass; - this.testTags = getTagsIfAny(testClass); - setParent(parent); - - // Currently we support only ASC MethodOrderer.OrderAnnotation sorting - final var sort = testClass.getAnnotation(TestMethodOrder.class) != null - && testClass.getAnnotation(TestMethodOrder.class).value() == MethodOrderer.OrderAnnotation.class; - - // Look for any methods supported by this class. - ReflectionSupport.findMethods(testClass, IS_HAPI_TEST, TOP_DOWN).stream() - .filter(method -> { - // The selectors tell me if some specific method was selected by the IDE or command line, - // so I will filter out and only include test methods that were in the selectors, if there - // are any such selectors. NOTE: We're not doing class selectors and such, a more robust - // implementation probably should. - final var selectors = discoveryRequest.getSelectorsByType(MethodSelector.class); - for (final var selector : selectors) { - if (!selector.getJavaMethod().equals(method)) { - return false; - } - } - return true; - }) - .sorted(sort ? sortMethodsAscByOrderNumber : noSorting) - .map(method -> new MethodTestDescriptor(method, this)) - .forEach(this::addChild); - - // Skip construction of the Hedera instance if there are no test methods - skip = getChildren().isEmpty(); - - // Determine whether this test class (suite) should be isolated in its own cluster of nodes - final var annotation = - findAnnotation(testClass, HapiTestSuite.class).orElseThrow(); - this.isolated = annotation.isolated(); - this.fuzzyMatch = annotation.fuzzyMatch(); - this.order = annotation.order(); - } - - public int order() { - return order; - } - - @Override - public Set getTags() { - return this.testTags; - } - - @Override - public Type getType() { - return Type.CONTAINER; - } - - @Override - public SkipResult shouldBeSkipped(HapiTestEngineExecutionContext context) { - return skip ? SkipResult.skip("No test methods") : SkipResult.doNotSkip(); - } - - @Override - public HapiTestEngineExecutionContext execute( - HapiTestEngineExecutionContext context, DynamicTestExecutor dynamicTestExecutor) throws Exception { - - // If we are isolated AND there is already a started set of nodes, then we have to stop and destroy them - // and get rid of all state, so when we bring them back up, it is genesis again. - final var env = context.getEnv(); - if (isolated && (env.started())) { - env.terminate(); - env.getNodes().forEach(HapiTestNode::clearState); - } - - // If it hasn't been started, start it. The very first suite will find it isn't started, or any suite that - // follows one that was isolated will find it wasn't started. - if (!env.started()) { - env.start(); - } - - return Node.super.execute(context, dynamicTestExecutor); - } - - @Override - public void after(HapiTestEngineExecutionContext context) throws Exception { - // If we are isolated, then stop and destroy everything to prepare for a new genesis startup. - final var env = context.getEnv(); - if (env != null && env.started() && isolated) { - env.terminate(); - env.getNodes().forEach(HapiTestNode::clearState); - } - } - } - - /** - * Describes a {@link HapiSpec} test method, and contains logic for running the actual test. - */ - private static final class MethodTestDescriptor extends AbstractTestDescriptor - implements Node { - private final Logger classLogger = LogManager.getLogger(getClass()); - - /** The method under test */ - private final Method testMethod; - - private final Set testTags; - - public MethodTestDescriptor(Method testMethod, ClassTestDescriptor parent) { - super( - parent.getUniqueId().append("method", testMethod.getName()), - testMethod.getName(), - MethodSource.from(testMethod)); - this.testMethod = testMethod; - this.testTags = getTagsIfAny(testMethod); - this.testTags.addAll(parent.getTags()); - setParent(parent); - } - - @Override - public Type getType() { - return Type.TEST; - } - - @Override - public SkipResult shouldBeSkipped(HapiTestEngineExecutionContext context) { - final var isHapiTestAnnotated = isAnnotated(testMethod, HapiTest.class); - final var disabledAnnotation = findAnnotation(testMethod, Disabled.class); - - if (!isHapiTestAnnotated) { - return SkipResult.skip(testMethod.getName() + " No @HapiTest annotation"); - } else if (disabledAnnotation.isPresent()) { - final var msg = disabledAnnotation.get().value(); - return SkipResult.skip(msg == null || msg.isBlank() ? "Disabled" : msg); - } else if (testMethod.getParameterCount() != 0) { - final String message = - "%s requires %d parameters.".formatted(testMethod.getName(), testMethod.getParameterCount()); - return SkipResult.skip(message); - } - return SkipResult.doNotSkip(); - } - - @Override - public HapiTestEngineExecutionContext execute( - HapiTestEngineExecutionContext context, DynamicTestExecutor dynamicTestExecutor) - throws NoSuchMethodException, InvocationTargetException, InstantiationException, - IllegalAccessException { - // First, create an instance of the HapiSuite class (the class that owns this method). - final var parent = (ClassTestDescriptor) getParent().get(); - final var suite = - (HapiSuite) parent.testClass.getDeclaredConstructor().newInstance(); - // Second, call the method to get the HapiSpec - testMethod.setAccessible(true); - if (testMethod.getParameterCount() == 0) { - final var spec = (HapiSpec) testMethod.invoke(suite); - spec.setTargetNetworkType(TargetNetworkType.HAPI_TEST_NETWORK); - // Disabling fuzzy matching in CI until we can make them stable - if (parent.fuzzyMatch && System.getenv("CI") == null) { - spec.addOverrideProperties(Map.of("recordStream.autoSnapshotManagement", "true")); - } - final var env = context.getEnv(); - // Third, call `runSuite` with just the one HapiSpec. - final var result = suite.runSpecSync(spec, env.getNodes()); - // Fourth, report the result. YAY!! - if (result == HapiSuite.FinalOutcome.SUITE_FAILED) { - throw new AssertionError(); - } - } else { - final String message = "Not running spec {}. Method requires {} parameters."; - classLogger.log(Level.INFO, message, testMethod.getName(), testMethod.getParameterCount()); - } - return context; - } - - @Override - public Set getTags() { - return this.testTags; - } - } - - private static final class RecordStreamValidationTestDescriptor extends AbstractTestDescriptor - implements Node { - - private final Logger classLogger = LogManager.getLogger(getClass()); - - private static final long MIN_GZIP_SIZE_IN_BYTES = 26; - - private static final String HAPI_TEST_STREAMS_LOC_TPL = - "hedera-node/test-clients/build/hapi-test/node%d/data/recordStreams/record0.0.%d"; - - private static final List validators = List.of( - new BlockNoValidator(), - new TransactionBodyValidator(), - new ExpiryRecordsValidator(), - new BalanceReconciliationValidator(), - new TokenReconciliationValidator()); - - public RecordStreamValidationTestDescriptor(TestDescriptor parent) { - super(parent.getUniqueId().append("validation", "recordStream"), "recordStreamValidation"); - setParent(parent); - } - - @Override - public Set getTags() { - return Set.of(TestTag.create("RECORD_STREAM_VALIDATION")); - } - - @Override - public Type getType() { - return Type.TEST; - } - - @Override - public HapiTestEngineExecutionContext execute( - HapiTestEngineExecutionContext context, DynamicTestExecutor dynamicTestExecutor) throws Exception { - final var env = context.getEnv(); - // run closing time specs - runSpec(env, new ClosingTime(), "closeLastStreamFileWithNoBalanceImpact"); - - // read record stream data - var recordLocs = hapiTestStreamLocs(); - RecordStreamAccess.Data data = RecordStreamAccess.Data.EMPTY_DATA; - for (final var recordLoc : recordLocs) { - try { - classLogger.info("Trying to read record files from {}", recordLoc); - data = RECORD_STREAM_ACCESS.readStreamDataFrom( - recordLoc, "sidecar", f -> new File(f).length() > MIN_GZIP_SIZE_IN_BYTES); - classLogger.info( - "Read {} record files from {}", data.records().size(), recordLoc); - } catch (Exception ignore) { - // We will try the next location, if any - } - if (!data.records().isEmpty()) { - break; - } - } - - // assert validators pass - final var streamData = data; - final var errorsIfAny = validators.stream() - .flatMap(v -> { - try { - // The validator will complete silently if no errors are - // found - v.validateFiles(streamData.files()); - v.validateRecordsAndSidecarsHapi(env, streamData.records()); - return Stream.empty(); - } catch (final Throwable t) { - return Stream.of(t); - } - }) - .map(Throwable::getMessage) - .toList(); - if (!errorsIfAny.isEmpty()) { - throw new AssertionError("Record stream validation failed with the following errors:\n - " - + String.join("\n - ", errorsIfAny)); - } - - return Node.super.execute(context, dynamicTestExecutor); - } - - private List hapiTestStreamLocs() { - final List locs = new ArrayList<>(HapiTestEngine.NODE_COUNT); - for (int i = 0; i < HapiTestEngine.NODE_COUNT; i++) { - locs.add(String.format(HAPI_TEST_STREAMS_LOC_TPL, i, i + 3)); - } - return locs; - } - } - - public static void runSpec(HapiTestEnv env, HapiSuite suite, String specName) - throws InvocationTargetException, IllegalAccessException { - // Get the method - final var testMethod = - ReflectionSupport.findMethod(suite.getClass(), specName).get(); - // Call the method to get the HapiSpec - testMethod.setAccessible(true); - final var spec = (HapiSpec) testMethod.invoke(suite); - spec.setTargetNetworkType(TargetNetworkType.HAPI_TEST_NETWORK); - final var result = suite.runSpecSync(spec, env.getNodes()); - // Report the result. YAY!! - if (result == HapiSuite.FinalOutcome.SUITE_FAILED) { - throw new AssertionError(spec.getName() + ": " + spec.getCause()); - } - } - - private static Set getTagsIfAny(Class testClass) { - // When a class has a single @Tag annotation, we retrieve it by filtering for Tag.class. - // In cases where a class has multiple @Tag annotations, we use Tags.class to access all of them. - // Ideally, Tags.class should encompass both single and multiple @Tag annotations, - // but the current implementation does not support this. - final var tagsAnnotation = testClass.getAnnotation(Tags.class); - final var tagAnnotation = testClass.getAnnotation(Tag.class); - - return extractTags(tagsAnnotation, tagAnnotation); - } - - private static Set getTagsIfAny(Method testMethod) { - // When a method has a single @Tag annotation, we retrieve it by filtering for Tag.class. - // In cases where a method has multiple @Tag annotations, we use Tags.class to access all of them. - // Ideally, Tags.class should encompass both single and multiple @Tag annotations, - // but the current implementation does not support this. - final var tagsAnnotation = testMethod.getAnnotation(Tags.class); - final var tagAnnotation = testMethod.getAnnotation(Tag.class); - - return extractTags(tagsAnnotation, tagAnnotation); - } - - // A helper method that extracts the value from either a @Tags annotation or a @Tag annotation - private static Set extractTags(Tags tagsAnnotation, Tag tagAnnotation) { - final var tags = new HashSet(); - if (tagsAnnotation != null) { - tags.addAll(Arrays.stream(tagsAnnotation.value()) - .map(t -> TestTag.create(t.value())) - .toList()); - } else if (tagAnnotation != null) { - tags.add(TestTag.create(tagAnnotation.value())); - } - return tags; - } -} diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/junit/HapiTestEngineExecutionContext.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/junit/HapiTestEngineExecutionContext.java deleted file mode 100644 index 7f185060747b..000000000000 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/junit/HapiTestEngineExecutionContext.java +++ /dev/null @@ -1,45 +0,0 @@ -/* - * Copyright (C) 2023-2024 Hedera Hashgraph, LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.hedera.services.bdd.junit; - -import org.junit.platform.engine.support.hierarchical.EngineExecutionContext; - -/** - * An {@link EngineExecutionContext} for HAPI tests. - * - *

    The context is a place to store any information we need per test. For example, we could store the port of the - * server here. But the HAPI test system stores that information statically, so we don't need to do it here. So for - * now this class is just empty (we need it to satisfy the API, but we don't use it). - */ -public class HapiTestEngineExecutionContext implements EngineExecutionContext { - - private HapiTestEnv env; - - /** - * Set the {@link HapiTestEnv} associated with this test run. - */ - public void setEnv(HapiTestEnv env) { - this.env = env; - } - - /** - * Get the {@link HapiTestEnv} associated with this test run. - */ - public HapiTestEnv getEnv() { - return env; - } -} diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/junit/HapiTestEnv.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/junit/HapiTestEnv.java deleted file mode 100644 index 91cab11bcc00..000000000000 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/junit/HapiTestEnv.java +++ /dev/null @@ -1,238 +0,0 @@ -/* - * Copyright (C) 2023-2024 Hedera Hashgraph, LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.hedera.services.bdd.junit; - -import static com.hedera.services.bdd.junit.HapiTestEnv.HapiTestNodesType.IN_PROCESS_ALICE; - -import com.hedera.hapi.node.base.AccountID; -import edu.umd.cs.findbugs.annotations.NonNull; -import java.io.File; -import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.StandardOpenOption; -import java.util.ArrayList; -import java.util.Comparator; -import java.util.List; -import java.util.concurrent.TimeoutException; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; - -public class HapiTestEnv { - private static final Logger logger = LogManager.getLogger(HapiTestEnv.class); - private static final String[] NODE_NAMES = new String[] {"Alice", "Bob", "Carol", "Dave"}; - private static final int FIRST_GOSSIP_PORT = 60000; - private static final int FIRST_GOSSIP_TLS_PORT = 60001; - private static final int FIRST_GRPC_PORT = 50211; - private static final int CAPTIVE_NODE_STARTUP_TIME_LIMIT = 300; - private final List nodes = new ArrayList<>(); - private final List nodeHosts = new ArrayList<>(); - private boolean started = false; - - public HapiTestEnv( - @NonNull final String testName, final int nodeCount, @NonNull final HapiTestNodesType nodesType) { - try { - final var sb = new StringBuilder(); - sb.append("swirld, ") - .append(testName) - .append("\n") - .append("\n# This next line is, hopefully, ignored.\n") - .append("app, HederaNode.jar\n\n#The following nodes make up this network\n"); - for (int nodeId = 0; nodeId < nodeCount; nodeId++) { - final var nodeName = NODE_NAMES[nodeId]; - final var firstChar = nodeName.charAt(0); - final var account = "0.0." + (3 + nodeId); - sb.append("address, ") - .append(nodeId) - .append(", ") - .append(firstChar) - .append(", ") - .append(nodeName) - .append(", 1, 127.0.0.1, ") - .append(FIRST_GOSSIP_PORT + (nodeId * 2)) - .append(", 127.0.0.1, ") - .append(FIRST_GOSSIP_TLS_PORT + (nodeId * 2)) - .append(", ") - .append(account) - .append("\n"); - nodeHosts.add("127.0.0.1:" + (FIRST_GRPC_PORT + (nodeId * 2)) + ":" + account); - } - sb.append("\nnextNodeId, ").append(nodeCount).append("\n"); - final String configText = sb.toString(); - - for (int nodeId = 0; nodeId < nodeCount; nodeId++) { - final Path workingDir = - Path.of("./build/hapi-test/node" + nodeId).normalize(); - setupWorkingDirectory(workingDir, configText); - final String nodeName = NODE_NAMES[nodeId]; - final AccountID acct = - AccountID.newBuilder().accountNum(3L + nodeId).build(); - boolean currentNodeAlice = nodeId == 0; - if (IN_PROCESS_ALICE == nodesType && currentNodeAlice) { - nodes.add(new InProcessHapiTestNode(nodeName, nodeId, acct, workingDir, FIRST_GRPC_PORT)); - } else { - nodes.add(new SubProcessHapiTestNode( - nodeName, nodeId, acct, workingDir, FIRST_GRPC_PORT + (nodeId * 2))); - } - } - } catch (Exception e) { - throw new RuntimeException(e); - } - } - - // Defines the types of nodes(InProcessHapiTestNode or SubProcessHapiTestNode) for the test executing nodes. - enum HapiTestNodesType { - // Makes the fist starting node(Alice, id=0) to use InProcessHapiTestNode. This gives us the ability to debug - IN_PROCESS_ALICE, - OUT_OF_PROCESS_ALICE - } - - /** - * Starts all nodes in the environment. - */ - public void start() throws TimeoutException { - started = true; - for (final var node : nodes) { - logger.info("Starting node {}", node.getName()); - try { - node.start(); - } catch (RuntimeException e) { - logger.error( - "Node {} failed to start within {} seconds", node.getName(), CAPTIVE_NODE_STARTUP_TIME_LIMIT); - throw e; - } - } - for (final var node : nodes) { - try { - node.waitForActive(CAPTIVE_NODE_STARTUP_TIME_LIMIT); - } catch (TimeoutException e) { - logger.error( - "Node {} failed to ACTIVE within {} seconds", node.getName(), CAPTIVE_NODE_STARTUP_TIME_LIMIT); - throw e; - } - } - } - - /** - * Forcibly terminates all nodes in the environment. Once terminated, an environment can be started again. - */ - public void terminate() { - for (final var node : nodes) { - node.terminate(); - } - started = false; - } - - /** - * Gets whether this environment has been started and not terminated. - */ - public boolean started() { - return started; - } - - /** - * Gets node info suitable for the HAPI test system's configuration - */ - public String getNodeInfo() { - return String.join(",", nodeHosts); - } - - /** - * Gets the list of nodes that make up this test environment. - */ - public List getNodes() { - return nodes; - } - - private void setupWorkingDirectory(@NonNull final Path workingDir, @NonNull final String configText) { - try { - if (Files.exists(workingDir)) { - Files.walk(workingDir) - .sorted(Comparator.reverseOrder()) - .map(Path::toFile) - .forEach(File::delete); - } - - Files.createDirectories(workingDir); - Files.createDirectories(workingDir.resolve("data").resolve("keys")); - Files.createDirectories(workingDir.resolve("data").resolve("config")); - - final var configTextFile = workingDir.resolve("config.txt"); - Files.writeString(configTextFile, configText); - - final var configDir = - Path.of("../configuration/dev").toAbsolutePath().normalize(); - Files.walk(configDir).filter(file -> !file.equals(configDir)).forEach(file -> { - try { - if (file.getFileName().toString().contains(".properties")) { - Files.copy( - file, - workingDir - .resolve("data") - .resolve("config") - .resolve(file.getFileName().toString())); - } else { - Files.copy(file, workingDir.resolve(file.getFileName().toString())); - } - } catch (Exception e) { - throw new RuntimeException(e); - } - }); - - // Update the log4j2.xml, so it contains absolute paths to the files instead of relative paths, - // because node0 is always relative to the test-clients base dir instead of the appropriate node0 - // dir in the build directory - final var log4j2File = workingDir.resolve("log4j2.xml"); - final var logConfig = Files.readString(log4j2File); - final var updatedLogConfig = logConfig - .replace( - "\n" + " ", - """ - - - %d{yyyy-MM-dd HH:mm:ss.SSS} %-5p %-4L %c{1} - %m{nolookups}%n - - - - - - - - - - - - - - - - - - - - - """) - .replace( - "output/", - workingDir.resolve("output").toAbsolutePath().normalize() + "/"); - - Files.writeString(log4j2File, updatedLogConfig, StandardOpenOption.WRITE); - } catch (Exception e) { - throw new RuntimeException(e); - } - } -} diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/junit/HapiTestLifecycle.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/junit/HapiTestLifecycle.java new file mode 100644 index 000000000000..84bf6be94fbb --- /dev/null +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/junit/HapiTestLifecycle.java @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2024 Hedera Hashgraph, LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.hedera.services.bdd.junit; + +import com.hedera.services.bdd.junit.extensions.SpecManagerExtension; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; +import org.junit.jupiter.api.extension.ExtendWith; + +/** + * Marks a test class whose {@link org.junit.jupiter.api.BeforeAll} and {@link org.junit.jupiter.api.AfterAll} + * lifecycle methods want to have an injected {@link com.hedera.services.bdd.junit.support.SpecManager}. + */ +@Target({ElementType.TYPE}) +@Retention(RetentionPolicy.RUNTIME) +@ExtendWith(SpecManagerExtension.class) +public @interface HapiTestLifecycle {} diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/junit/HapiTestNode.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/junit/HapiTestNode.java deleted file mode 100644 index 64c9e3632bc6..000000000000 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/junit/HapiTestNode.java +++ /dev/null @@ -1,98 +0,0 @@ -/* - * Copyright (C) 2023-2024 Hedera Hashgraph, LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.hedera.services.bdd.junit; - -import com.hedera.hapi.node.base.AccountID; -import java.util.concurrent.TimeoutException; - -/** - * Defines a node in the network for running Hapi tests. There are implementations for running locally in this - * JVM started by JUnit, and for running out of process. - */ -public interface HapiTestNode { - /** - * Gets the node ID, such as 0, 1, 2, or 3. - * @return the node ID - */ - long getId(); - - /** - * The name of the node, such as "Alice" or "Bob". - * @return the node name - */ - String getName(); - - /** - * Gets the node Account ID - * @return the node account ID - */ - AccountID getAccountId(); - - /** - * Starts the node software. - */ - void start(); - - /** - * Waits for the node to become active. - * - * @param seconds the number of seconds to wait for the server to start before throwing an exception. - */ - void waitForActive(long seconds) throws TimeoutException; - - void waitForBehind(long seconds) throws TimeoutException; - - void waitForReconnectComplete(long seconds) throws TimeoutException; - - /** - * Stops the node software gracefully - */ - void shutdown(); - - /** - * Blocks the network port on the node so that it cannot communicate with other nodes. - */ - void blockNetworkPort(); - - /** - * Unblocks the network port on the node so that it can communicate with other nodes. - */ - void unblockNetworkPort(); - /** - * Waits for the node to shut down. - * - * @param seconds the number of seconds to wait for the server to shut down before throwing an exception. - */ - void waitForShutdown(long seconds) throws TimeoutException; - - /** - * Waits for the node to become fully frozen - * - * @param seconds the number of seconds to wait for the server to freeze throwing an exception. - */ - void waitForFreeze(long seconds) throws TimeoutException; - - /** - * Attempts to hard-terminate the node software - */ - void terminate(); - - /** - * Deletes all saved state. Useful for testing genesis reconnects. - */ - void clearState(); -} diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/junit/HapiTestSuite.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/junit/HapiTestSuite.java deleted file mode 100644 index 3321d089f845..000000000000 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/junit/HapiTestSuite.java +++ /dev/null @@ -1,47 +0,0 @@ -/* - * Copyright (C) 2023-2024 Hedera Hashgraph, LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.hedera.services.bdd.junit; - -import java.lang.annotation.Documented; -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; -import org.junit.platform.commons.annotation.Testable; - -/** - * {@link HapiTestSuite} is used to mark a file as containing a suite of HAPI tests. Each individual test method is - * annotated with {@link HapiTest}. Classes with this annotation must extend from HapiSuite. - */ -@Target({ElementType.ANNOTATION_TYPE, ElementType.TYPE}) -@Retention(RetentionPolicy.RUNTIME) -@Documented -@Testable -public @interface HapiTestSuite { - /** If true, then a new cluster is created for this test suite */ - boolean isolated() default false; - - /** - * If true, we will set recordStream.autoSnapshotManagement property to true and enable fuzzy - * matching or every spec in the suite - * @return true if we want to enable fuzzy matching for every spec in the suite - */ - boolean fuzzyMatch() default false; - - /** The order in which this suite should be run; smaller values come first. */ - int order() default 0; -} diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/junit/InProcessHapiTestNode.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/junit/InProcessHapiTestNode.java deleted file mode 100644 index 9e08a6c4b04e..000000000000 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/junit/InProcessHapiTestNode.java +++ /dev/null @@ -1,442 +0,0 @@ -/* - * Copyright (C) 2023-2024 Hedera Hashgraph, LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.hedera.services.bdd.junit; - -import static com.swirlds.platform.builder.PlatformBuildConstants.DEFAULT_CONFIG_FILE_NAME; -import static com.swirlds.platform.builder.PlatformBuildConstants.DEFAULT_SETTINGS_FILE_NAME; -import static com.swirlds.platform.system.status.PlatformStatus.BEHIND; -import static com.swirlds.platform.system.status.PlatformStatus.FREEZE_COMPLETE; -import static com.swirlds.platform.system.status.PlatformStatus.RECONNECT_COMPLETE; -import static java.lang.String.format; -import static java.util.Objects.requireNonNull; -import static java.util.concurrent.TimeUnit.MILLISECONDS; - -import com.hedera.hapi.node.base.AccountID; -import com.hedera.node.app.Hedera; -import com.swirlds.base.state.Stoppable; -import com.swirlds.common.constructable.ConstructableRegistry; -import com.swirlds.common.platform.NodeId; -import com.swirlds.config.api.ConfigurationBuilder; -import com.swirlds.platform.builder.PlatformBuilder; -import com.swirlds.platform.system.Platform; -import com.swirlds.platform.system.status.PlatformStatus; -import com.swirlds.platform.util.BootstrapUtils; -import edu.umd.cs.findbugs.annotations.NonNull; -import java.io.File; -import java.io.IOException; -import java.net.MalformedURLException; -import java.net.SocketTimeoutException; -import java.net.URL; -import java.nio.file.Files; -import java.nio.file.Path; -import java.util.Comparator; -import java.util.Map; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.TimeoutException; -import java.util.stream.Stream; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; - -/** - * An implementation of {@link HapiTestNode} that runs the node in this JVM process. The advantage of the in-process - * node is that it can be debugged when run through the IDE or through Gradle. Just launch JUnit using a debugger, and - * place a breakpoint in the code for the node, and it will work! - * - *

    Ideally we would host the node with classloader isolation, which would allow us to bring this node down and up - * again. Unfortunately, the {@link ConstructableRegistry} thwarted my attempt, because it ignores the classloader - * isolation, discovers all the classloaders (including the system classloader), and chooses its own rank order for what - * order to look classes up in. That makes it impossible to do classloader isolation. We need to fix that. Until then, - * in process nodes simply will not work well when stopped. - * - *

    NOTE!! This class will not work generally, There are several problems that must be fixed. We must have a clean - * way to shut things down, which we don't have today. We also need a way to specify the config properties to use - * with the Hedera config (which is DIFFERENT from the platform config). Right now that involves setting an environment - * variable, which we cannot do when running in process. See ConfigProviderBase. - */ -public class InProcessHapiTestNode implements HapiTestNode { - private static final Logger logger = LogManager.getLogger(InProcessHapiTestNode.class); - /** The thread in which the Hedera node will run */ - private WorkerThread th; - /** The name of the node, such as Alice or Bob */ - private final String name; - /** The ID of the node. This is probably always 0. */ - private final long nodeId; - /** The account ID of the node, such as 0.0.3 */ - private final AccountID accountId; - /** The directory in which the config.txt, settings.txt, and other files live. */ - private final Path workingDir; - /** The port on which the grpc server will be listening */ - private final int grpcPort; - - public static final int START_PORT = 10000; - public static final int STOP_PORT = 65535; - - /** - * Create a new in-process node. - * - * @param name the name of the node, like Alice, Bob - * @param nodeId The node ID - * @param accountId The account ID of the node, such as 0.0.3. - * @param workingDir The working directory. Must already be created and setup with all the files. - * @param grpcPort The grpc port to configure the server with. - */ - public InProcessHapiTestNode( - @NonNull final String name, - final long nodeId, - @NonNull final AccountID accountId, - @NonNull final Path workingDir, - final int grpcPort) { - this.name = requireNonNull(name); - this.nodeId = nodeId; - this.accountId = requireNonNull(accountId); - this.workingDir = requireNonNull(workingDir); - this.grpcPort = grpcPort; - } - - @Override - public long getId() { - return nodeId; - } - - @Override - public String getName() { - return name; - } - - @Override - public AccountID getAccountId() { - return accountId; - } - - @Override - public String toString() { - return "InProcessHapiTestNode{" + "name='" - + name + '\'' + ", nodeId=" - + nodeId + ", accountId=" - + accountId + '}'; - } - - @Override - public void start() { - if (th != null && th.hedera.isActive()) { - throw new IllegalStateException("Node is not stopped, cannot start it!"); - } - try { - th = new WorkerThread(workingDir, nodeId, grpcPort); - th.setDaemon(false); - th.start(); - } catch (Exception e) { - throw new RuntimeException("Failed to start the node! Check the logs", e); - } - } - - @Override - public void waitForActive(long seconds) throws TimeoutException { - final var waitUntil = System.currentTimeMillis() + (seconds * 1000); - while (System.currentTimeMillis() < waitUntil) { - if (th != null && th.hedera != null && th.hedera.isActive()) { - // Actually try to open a connection with the node, to make sure it is really up and running. - // The platform may be active, but the node may not be listening. - try { - final var url = new URL("http://localhost:" + grpcPort + "/"); - final var connection = url.openConnection(); - connection.connect(); - return; - } catch (MalformedURLException e) { - throw new RuntimeException("Should never happen", e); - } catch (SocketTimeoutException ignored) { - // This is expected, the node is not up yet. - } catch (IOException e) { - throw new RuntimeException(e); - } - } - - try { - MILLISECONDS.sleep(10); - } catch (InterruptedException e) { - Thread.currentThread().interrupt(); - throw new RuntimeException( - "node " + nodeId + ": Interrupted while sleeping in waitForActive busy loop", e); - } - } - - // We timed out. - throw new TimeoutException( - "node " + nodeId + ": Waited " + seconds + " seconds, but node did not become active!"); - } - - public void blockNetworkPort() { - if (th != null && th.hedera.isActive()) { - final String[] cmd = new String[] { - "sudo", - "-n", - "iptables", - "-A", - "INPUT", - "-p", - "tcp", - "--dport", - format("%d:%d", START_PORT, STOP_PORT), - "-j", - "DROP;", - "sudo", - "-n", - "iptables", - "-A", - "OUTPUT", - "-p", - "tcp", - "--sport", - format("%d:%d", START_PORT, STOP_PORT), - "-j", - "DROP;" - }; - try { - final Process process = Runtime.getRuntime().exec(cmd); - logger.info("Blocking Network port {} for node {}", grpcPort, nodeId); - process.waitFor(75, TimeUnit.SECONDS); - } catch (IOException | InterruptedException e) { - throw new RuntimeException(e); - } - - try { - MILLISECONDS.sleep(10); - } catch (InterruptedException e) { - Thread.currentThread().interrupt(); - throw new RuntimeException( - "node " + nodeId + ": Interrupted while sleeping in waitForActive busy loop", e); - } - } - } - - public void unblockNetworkPort() { - if (th != null && th.hedera.isActive()) { - final String[] cmd = new String[] { - "sudo", - "-n", - "iptables", - "-D", - "INPUT", - "-p", - "tcp", - "--dport", - format("%d:%d", START_PORT, STOP_PORT), - "-j", - "DROP;", - "sudo", - "-n", - "iptables", - "-D", - "OUTPUT", - "-p", - "tcp", - "--sport", - format("%d:%d", START_PORT, STOP_PORT), - "-j", - "DROP;" - }; - try { - final Process process = Runtime.getRuntime().exec(cmd); - logger.info("Unblocking Network port {} for node {}", grpcPort, nodeId); - process.waitFor(75, TimeUnit.SECONDS); - } catch (IOException | InterruptedException e) { - throw new RuntimeException(e); - } - } - } - - @Override - public void shutdown() { - if (th != null && (th.hedera.isFrozen() || th.hedera.isActive())) { - th.hedera.shutdown(); - th.interrupt(); - - // This is a hack, but it's the best I can do without classloader isolation and without a systematic - // way to shut down a node. Normally, nodes are shutdown by existing the JVM. However, we don't want to - // do that here, because we're in-process! So what I'm going to do is: - // 1. Search through all the threads in the JVM to find all of them with a callstack with "WorkerThread" - // in the stack. This means they were called by the WorkerThread. Only objects that are part of the - // node will be in this callstack. - // 2. For each such thread, stop it. Thread.stop is deprecated and discouraged, because it is almost - // always the wrong thing. In our case though, NOTHING in the Junit JVM is working with any locks or - // semaphores, etc. in the node. So we should be safe. - // 3. There are some places in the node software that uses statics -- like MerkleDb. I've got to try to - // reset those. - // - // This is an error-prone approach, because at any time someone might add new static state to the node and - // fail to update this code accordingly. But it's the best we can do without removing all static state and - // having a clean shutdown procedure for the node. Fixing that should be a priority, allowing us to simplify - // this code and make it more foolproof. - - //noinspection deprecation - final var threadsToStop = Thread.getAllStackTraces().entrySet().stream() - .filter(entry -> { - for (final var stackTraceElement : entry.getValue()) { - final var className = stackTraceElement.getClassName(); - if (className.contains("WorkerThread") || className.contains("com.swirlds")) { - return true; - } - } - return false; - }) - .map(Map.Entry::getKey) - .toList(); - - threadsToStop.forEach(th -> { - if (th instanceof Stoppable s) { - s.stop(); - } - }); - threadsToStop.forEach(Thread::interrupt); - threadsToStop.forEach(Thread::stop); - - ConstructableRegistry.getInstance().reset(); - } - } - - public void waitForBehind(long seconds) throws TimeoutException { - waitFor(BEHIND, seconds); - } - - public void waitForReconnectComplete(long seconds) throws TimeoutException { - waitFor(RECONNECT_COMPLETE, seconds); - } - - @Override - public void waitForShutdown(long seconds) throws TimeoutException { - final var waitUntil = System.currentTimeMillis() + (seconds * 1000); - while (th != null && th.hedera != null && th.hedera.isActive()) { - if (System.currentTimeMillis() > waitUntil) { - throw new TimeoutException( - "node " + nodeId + ": Waited " + seconds + " seconds, but node did not shut down!"); - } - - try { - MILLISECONDS.sleep(10); - } catch (InterruptedException e) { - Thread.currentThread().interrupt(); - throw new RuntimeException( - "node " + nodeId + ": Interrupted while sleeping in waitForShutdown busy loop", e); - } - } - } - - @Override - public void waitForFreeze(long seconds) throws TimeoutException { - waitFor(FREEZE_COMPLETE, seconds); - } - - private void waitFor(PlatformStatus status, long seconds) throws TimeoutException { - final var waitUntil = System.currentTimeMillis() + (seconds * 1000); - while (th != null && th.hedera != null && th.hedera.getPlatformStatus().equals(status)) { - if (System.currentTimeMillis() > waitUntil) { - throw new TimeoutException( - "node " + nodeId + ": Waited " + seconds + " seconds, but node did not reach status " + status); - } - - try { - MILLISECONDS.sleep(10); - } catch (InterruptedException e) { - Thread.currentThread().interrupt(); - throw new RuntimeException( - "node " + nodeId + ": Interrupted while sleeping in waitForFreeze busy loop", e); - } - } - } - - @Override - public void terminate() { - // There really isn't anything better I can do without classloader isolation - shutdown(); - } - - @Override - public void clearState() { - if (th != null && th.hedera.isActive()) { - throw new IllegalStateException("Cannot clear state from a running node. At least, not yet."); - } - - final var saved = workingDir.resolve("data/saved").toAbsolutePath().normalize(); - if (Files.exists(saved)) { - try (Stream paths = Files.walk(saved)) { - paths.sorted(Comparator.reverseOrder()).map(Path::toFile).forEach(File::delete); - - } catch (IOException e) { - throw new RuntimeException("Could not delete saved state " + saved, e); - } - } - } - - /** - * A helper thread class within which the node is started. - */ - public static final class WorkerThread extends Thread { - private Hedera hedera; - private final long nodeId; - private final int grpcPort; - private final Path workingDir; - - public WorkerThread(Path workingDir, long nodeId, int grpcPort) { - this.workingDir = workingDir; - this.nodeId = nodeId; - this.grpcPort = grpcPort; - } - - public Hedera getHedera() { - return hedera; - } - - @Override - public void run() { - BootstrapUtils.setupConstructableRegistry(); - final var cr = ConstructableRegistry.getInstance(); - - hedera = new Hedera(cr); - - final PlatformBuilder builder = PlatformBuilder.create( - Hedera.APP_NAME, - Hedera.SWIRLD_NAME, - hedera.getSoftwareVersion(), - hedera::newState, - new NodeId(nodeId)); - - final ConfigurationBuilder configBuilder = ConfigurationBuilder.create() - .withValue("paths.settingsUsedDir", path(".")) - .withValue("paths.keysDirPath", path("data/keys")) - .withValue("paths.appsDirPath", path("data/apps")) - .withValue("paths.logPath", path("log4j2.xml")) - .withValue("metrics.csvOutputFolder", path(".")) - .withValue("emergencyRecoveryFileLoadDir", path("data/saved")) - .withValue("state.savedStateDirectory", path("data/saved")) - .withValue("loadKeysFromPfxFiles", "false") - .withValue("grpc.port", Integer.toString(grpcPort)); - - builder.withConfigurationBuilder(configBuilder) - .withSettingsPath(Path.of(path(DEFAULT_SETTINGS_FILE_NAME))) - .withConfigPath(Path.of(path(DEFAULT_CONFIG_FILE_NAME))); - - final Platform platform = builder.build(); - hedera.init(platform, new NodeId(nodeId)); - platform.start(); - hedera.run(); - } - - private String path(String path) { - return workingDir.resolve(path).toAbsolutePath().normalize().toString(); - } - } -} diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/junit/LeakyHapiTest.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/junit/LeakyHapiTest.java new file mode 100644 index 000000000000..04fc013b67aa --- /dev/null +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/junit/LeakyHapiTest.java @@ -0,0 +1,43 @@ +/* + * Copyright (C) 2024 Hedera Hashgraph, LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.hedera.services.bdd.junit; + +import static org.junit.jupiter.api.parallel.ResourceAccessMode.READ_WRITE; + +import com.hedera.services.bdd.junit.extensions.NetworkTargetingExtension; +import com.hedera.services.bdd.junit.extensions.SpecNamingExtension; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; +import org.junit.jupiter.api.TestFactory; +import org.junit.jupiter.api.extension.ExtendWith; +import org.junit.jupiter.api.parallel.ResourceLock; + +/** + * Annotation for a {@link HapiTest} that "leaks" side effects into the test context (or + * is permeable to such side effects itself). The {@link ContextRequirement} annotation + * enumerates common types of leakage and permeability. + */ +@Target({ElementType.METHOD}) +@Retention(RetentionPolicy.RUNTIME) +@TestFactory +@ExtendWith({NetworkTargetingExtension.class, SpecNamingExtension.class}) +@ResourceLock(value = "NETWORK", mode = READ_WRITE) +public @interface LeakyHapiTest { + ContextRequirement[] value() default {}; +} diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/BddTestNameDoesNotMatchMethodName.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/junit/OrderedInIsolation.java similarity index 59% rename from hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/BddTestNameDoesNotMatchMethodName.java rename to hedera-node/test-clients/src/main/java/com/hedera/services/bdd/junit/OrderedInIsolation.java index 5fd05c874e5c..f84af863694c 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/BddTestNameDoesNotMatchMethodName.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/junit/OrderedInIsolation.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023-2024 Hedera Hashgraph, LLC + * Copyright (C) 2024 Hedera Hashgraph, LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,16 +14,22 @@ * limitations under the License. */ -package com.hedera.services.bdd.suites; +package com.hedera.services.bdd.junit; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +import org.junit.jupiter.api.MethodOrderer; +import org.junit.jupiter.api.TestMethodOrder; +import org.junit.jupiter.api.parallel.Isolated; -/** Used in BDD suites to mark methods that produce one or more `HapiSpecs` whose - * name does not match this method's name. +/** + * Convenience annotation to mark a test class that requires strictly sequential execution, + * both with respect to other test classes and within its own methods. */ +@Target({ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) -@Target(ElementType.METHOD) -public @interface BddTestNameDoesNotMatchMethodName {} +@Isolated +@TestMethodOrder(MethodOrderer.OrderAnnotation.class) +public @interface OrderedInIsolation {} diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/junit/SharedNetworkLauncherSessionListener.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/junit/SharedNetworkLauncherSessionListener.java new file mode 100644 index 000000000000..86b02b90d76e --- /dev/null +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/junit/SharedNetworkLauncherSessionListener.java @@ -0,0 +1,61 @@ +/* + * Copyright (C) 2024 Hedera Hashgraph, LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.hedera.services.bdd.junit; + +import com.hedera.services.bdd.junit.hedera.HederaNetwork; +import com.hedera.services.bdd.spec.infrastructure.HapiApiClients; +import edu.umd.cs.findbugs.annotations.NonNull; +import java.time.Duration; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.junit.platform.launcher.LauncherSession; +import org.junit.platform.launcher.LauncherSessionListener; +import org.junit.platform.launcher.TestExecutionListener; +import org.junit.platform.launcher.TestPlan; + +/** + * Registers a {@link TestExecutionListener} when the {@link LauncherSession} is opened to + * start the shared test network before the test plan is executed; and stop it after test + * plan execution finishes. + */ +public class SharedNetworkLauncherSessionListener implements LauncherSessionListener { + private static final Logger log = LogManager.getLogger(SharedNetworkLauncherSessionListener.class); + + public static final int DEFAULT_SHARED_NETWORK_SIZE = 4; + + @Override + public void launcherSessionOpened(@NonNull final LauncherSession session) { + session.getLauncher().registerTestExecutionListeners(new SharedNetworkExecutionListener()); + } + + private static class SharedNetworkExecutionListener implements TestExecutionListener { + private static final Duration SHARED_NETWORK_STARTUP_TIMEOUT = Duration.ofSeconds(300); + + @Override + public void testPlanExecutionStarted(@NonNull final TestPlan testPlan) { + final var sharedNetwork = HederaNetwork.newSharedSubProcessNetwork(DEFAULT_SHARED_NETWORK_SIZE); + log.info("Waiting for shared network to start within {}", SHARED_NETWORK_STARTUP_TIMEOUT); + sharedNetwork.startWithin(SHARED_NETWORK_STARTUP_TIMEOUT); + } + + @Override + public void testPlanExecutionFinished(@NonNull final TestPlan testPlan) { + HapiApiClients.tearDown(); + HederaNetwork.SHARED_NETWORK.get().terminate(); + } + } +} diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/junit/SubProcessHapiTestNode.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/junit/SubProcessHapiTestNode.java deleted file mode 100644 index ebb46e5c1104..000000000000 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/junit/SubProcessHapiTestNode.java +++ /dev/null @@ -1,458 +0,0 @@ -/* - * Copyright (C) 2023-2024 Hedera Hashgraph, LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.hedera.services.bdd.junit; - -import static com.hedera.services.bdd.junit.InProcessHapiTestNode.START_PORT; -import static com.hedera.services.bdd.junit.InProcessHapiTestNode.STOP_PORT; -import static java.lang.String.format; -import static java.util.Objects.requireNonNull; -import static java.util.concurrent.TimeUnit.MILLISECONDS; - -import com.hedera.hapi.node.base.AccountID; -import edu.umd.cs.findbugs.annotations.NonNull; -import java.io.BufferedReader; -import java.io.File; -import java.io.IOException; -import java.io.StringReader; -import java.net.MalformedURLException; -import java.net.URI; -import java.net.URISyntaxException; -import java.net.URL; -import java.net.http.HttpClient; -import java.net.http.HttpRequest; -import java.net.http.HttpResponse; -import java.nio.file.Files; -import java.nio.file.Path; -import java.util.Arrays; -import java.util.Comparator; -import java.util.HashMap; -import java.util.Map; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.TimeoutException; -import java.util.regex.Pattern; -import java.util.stream.Collectors; -import java.util.stream.Stream; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; - -/** - * An implementation of {@link HapiTestNode} that will shell-out to a sub-process for running the node. The advantage - * of using a sub-process node is that the node is must closer to the "real thing". Each node has its own directory - * with its own files and logs. The disadvantage of sub-process nodes is that they are harder to debug, and they are - * harder to control (make sure we don't leave zombie processes around!). - * - *

    Normally, nodes 1, 2 and 3 are done as sub-process nodes, and node 0 is done as in-process, so you still debug. - * The {@code stdout} and {@code stderr} files will be written into the working directory. - */ -final class SubProcessHapiTestNode implements HapiTestNode { - private static final Logger logger = LogManager.getLogger(SubProcessHapiTestNode.class); - private static final Pattern PROM_PLATFORM_STATUS_HELP_PATTERN = - Pattern.compile("# HELP platform_PlatformStatus (.*)"); - private static final Pattern PROM_PLATFORM_STATUS_PATTERN = - Pattern.compile("platform_PlatformStatus\\{.*\\} (\\d+)\\.\\d+"); - private static final String[] EMPTY_STRING_ARRAY = new String[0]; - /** The Hedera instance we are testing */ - private ProcessHandle handle; - /** The name of the node, such as Alice or Bob */ - private final String name; - /** The ID of the node */ - private final long nodeId; - /** The account ID of the node, such as 0.0.3 */ - private final AccountID accountId; - /** The directory in which the config.txt, settings.txt, and other files live. */ - private final Path workingDir; - /** The port on which the grpc server will be listening */ - private final int grpcPort; - /** The HTTP Request to use for accessing prometheus to get the current node status (ACTIVE, CHECKING, etc) */ - private final HttpRequest prometheusRequest; - /** The client used to make prometheus HTTP Requests */ - private final HttpClient httpClient; - - /** - * Create a new sub-process node. - * - * @param name the name of the node, like Alice, Bob - * @param nodeId The node ID - * @param accountId The account ID of the node, such as 0.0.3. - * @param workingDir The working directory. Must already be created and setup with all the files. - * @param grpcPort The grpc port to configure the server with. - */ - public SubProcessHapiTestNode( - @NonNull final String name, - final long nodeId, - @NonNull final AccountID accountId, - @NonNull final Path workingDir, - final int grpcPort) { - this.name = requireNonNull(name); - this.nodeId = nodeId; - this.accountId = requireNonNull(accountId); - this.workingDir = requireNonNull(workingDir); - this.grpcPort = grpcPort; - - try { - prometheusRequest = HttpRequest.newBuilder() - .uri(new URI("http://localhost:" + (10000 + nodeId))) - .GET() - .build(); - } catch (URISyntaxException e) { - throw new RuntimeException("Bad URI. Should not happen", e); - } - - httpClient = HttpClient.newHttpClient(); - } - - @Override - public long getId() { - return nodeId; - } - - @Override - public String getName() { - return name; - } - - @Override - public AccountID getAccountId() { - return accountId; - } - - @Override - public String toString() { - return "SubProcessHapiTestNode{" + "name='" - + name + '\'' + ", nodeId=" - + nodeId + ", accountId=" - + accountId + '}'; - } - - @Override - public void start() { - if (handle != null) throw new IllegalStateException("Node is not stopped, cannot start it!"); - - try { - final var javaCmd = getJavaCommand(); - final var classPath = getClasspath(); - - final var stdout = workingDir.resolve("stdout.log").toAbsolutePath().normalize(); - final var stderr = workingDir.resolve("stderr.log").toAbsolutePath().normalize(); - - // When tests are terminated, they may leave nodes up and running. These "zombies" have to be terminated, - // or we will fail to start due to a bind error (two servers cannot use the same port). So we will look - // through all processes for any that were started with java and were passed this node id as their last - // argument, and terminate them forcibly (kill -9 style). - ProcessHandle.allProcesses() - .filter(p -> p.info().command().orElse("").contains("java")) - .filter(p -> p.info().arguments().orElse(EMPTY_STRING_ARRAY).length > 0) - .filter(p -> p.info() - .arguments() - .orElseThrow()[p.info().arguments().orElse(EMPTY_STRING_ARRAY).length - 1] - .equals(Long.toString(nodeId))) - .findFirst() - .ifPresent(ProcessHandle::destroyForcibly); - - // Now we can start the new process - final var builder = new ProcessBuilder(); - final var environment = builder.environment(); - environment.put("LC_ALL", "en.UTF-8"); - environment.put("LANG", "en_US.UTF-8"); - environment.put("grpc.port", Integer.toString(grpcPort)); - builder.command( - javaCmd, - // You can attach to any node. Node 0 at 5005, node 1 at 5006, etc. But if you need the - // node to stop at startup, you can change the below line so nodeId == the node you want - // to suspend at startup and the first "n" to "y". - "-agentlib:jdwp=transport=dt_socket,server=y,suspend=" + (nodeId == 0 ? "n" : "n") - + ",address=*:" + (5005 + nodeId), - "-Dhedera.recordStream.logDir=data/recordStreams", - "-Dhedera.profiles.active=DEV", - "-classpath", - classPath, - "-Dfile.encoding=UTF-8", - "-Dhedera.workflows.enabled=true", - "-Dprometheus.endpointPortNumber=" + (10000 + nodeId), - "com.hedera.node.app.ServicesMain", - "-local", - Long.toString(nodeId)) - .directory(workingDir.toFile()) - .redirectOutput(stdout.toFile()) - .redirectError(stderr.toFile()) - .inheritIO(); - handle = builder.start().toHandle(); - } catch (Exception e) { - throw new RuntimeException("node " + nodeId + ": Unable to start!", e); - } - } - - @Override - public void waitForActive(long seconds) throws TimeoutException { - final var waitUntil = System.currentTimeMillis() + (seconds * 1000); - while (handle != null) { - if (System.currentTimeMillis() > waitUntil) { - throw new TimeoutException( - "node " + nodeId + ": Waited " + seconds + " seconds, but node did not become active!"); - } - - if ("ACTIVE".equals(getPlatformStatus())) { - // Actually try to open a connection with the node, to make sure it is really up and running. - // The platform may be active, but the node may not be listening. - try { - final var url = new URL("http://localhost:" + grpcPort + "/"); - final var connection = url.openConnection(); - connection.connect(); - return; - } catch (MalformedURLException e) { - throw new RuntimeException("Should never happen", e); - } catch (IOException ignored) { - // This is expected, the node is not up yet. - } - } - - try { - MILLISECONDS.sleep(10); - } catch (InterruptedException e) { - Thread.currentThread().interrupt(); - throw new RuntimeException( - "node " + nodeId + ": Interrupted while sleeping in waitForActive busy loop", e); - } - } - } - - public void shutdown() { - if (handle != null) { - handle.destroy(); - handle = null; - } - } - - @Override - public void blockNetworkPort() { - if (handle != null && handle.isAlive()) { - final String[] cmd = new String[] { - "sudo", - "-n", - "iptables", - "-A", - "INPUT", - "-p", - "tcp", - "--dport", - format("%d:%d", START_PORT, STOP_PORT), - "-j", - "DROP;", - "sudo", - "-n", - "iptables", - "-A", - "OUTPUT", - "-p", - "tcp", - "--sport", - format("%d:%d", START_PORT, STOP_PORT), - "-j", - "DROP;" - }; - try { - final Process process = Runtime.getRuntime().exec(cmd); - logger.info("Blocking Network port {} for node {}", grpcPort, nodeId); - process.waitFor(75, TimeUnit.SECONDS); - } catch (IOException | InterruptedException e) { - throw new RuntimeException(e); - } - } - } - - public void unblockNetworkPort() { - if (handle != null && handle.isAlive()) { - final String[] cmd = new String[] { - "sudo", - "-n", - "iptables", - "-D", - "INPUT", - "-p", - "tcp", - "--dport", - format("%d:%d", START_PORT, STOP_PORT), - "-j", - "DROP;", - "sudo", - "-n", - "iptables", - "-D", - "OUTPUT", - "-p", - "tcp", - "--sport", - format("%d:%d", START_PORT, STOP_PORT), - "-j", - "DROP;" - }; - try { - final Process process = Runtime.getRuntime().exec(cmd); - logger.info("Unblocking Network port {} for node {}", grpcPort, nodeId); - process.waitFor(75, TimeUnit.SECONDS); - } catch (IOException | InterruptedException e) { - throw new RuntimeException(e); - } - } - } - - @Override - public void waitForShutdown(long seconds) throws TimeoutException { - final var waitUntil = System.currentTimeMillis() + (seconds * 1000); - while (handle != null && handle.isAlive()) { - if (System.currentTimeMillis() > waitUntil) { - throw new TimeoutException( - "node " + nodeId + ": Waited " + seconds + " seconds, but node did not shut down!"); - } - - try { - MILLISECONDS.sleep(10); - } catch (InterruptedException e) { - Thread.currentThread().interrupt(); - throw new RuntimeException( - "node " + nodeId + ": Interrupted while sleeping in waitForShutdown busy loop", e); - } - } - } - - @Override - public void waitForFreeze(long seconds) throws TimeoutException { - waitFor("FREEZE_COMPLETE", seconds); - } - - private void waitFor(String status, long seconds) throws TimeoutException { - final var waitUntil = System.currentTimeMillis() + (seconds * 1000); - while (handle != null && handle.isAlive()) { - if (System.currentTimeMillis() > waitUntil) { - throw new TimeoutException( - "node " + nodeId + ": Waited " + seconds + " seconds, but node did not reach status " + status); - } - - if (status.equals(getPlatformStatus())) { - return; - } - - try { - MILLISECONDS.sleep(10); - } catch (InterruptedException e) { - Thread.currentThread().interrupt(); - throw new RuntimeException( - "node " + nodeId + ": Interrupted while sleeping in waitForFreeze busy loop", e); - } - } - } - - @Override - public void waitForBehind(final long seconds) throws TimeoutException { - waitFor("BEHIND", seconds); - } - - @Override - public void waitForReconnectComplete(final long seconds) throws TimeoutException { - waitFor("RECONNECT_COMPLETE", seconds); - } - - public void terminate() { - if (handle != null) { - handle.destroyForcibly(); - handle = null; - } - } - - @Override - public void clearState() { - if (handle != null) { - throw new IllegalStateException("Cannot clear state from a running node. At least, not yet."); - } - - final var saved = workingDir.resolve("data/saved").toAbsolutePath().normalize(); - if (Files.exists(saved)) { - try (Stream paths = Files.walk(saved)) { - paths.sorted(Comparator.reverseOrder()).map(Path::toFile).forEach(File::delete); - } catch (IOException e) { - throw new RuntimeException("Could not delete saved state " + saved, e); - } - } - } - - private String getJavaCommand() { - final var me = ProcessHandle.current(); - return me.info().command().orElseThrow(); - } - - private String getClasspath() { - // Could have been launched with -cp, or -classpath, or @/path/to/classpathFile.txt, or maybe module path? - final var me = ProcessHandle.current(); - final var args = me.info().arguments().orElse(EMPTY_STRING_ARRAY); - - String classpath = ""; - for (int i = 0; i < args.length; i++) { - final var arg = args[i]; - if (arg.startsWith("@")) { - try { - final var fileContents = Files.readString(Path.of(arg.substring(1))); - classpath = fileContents.substring(fileContents.indexOf('/')); - break; - } catch (Exception e) { - throw new RuntimeException("Unable to read classpath " + arg, e); - } - } else if (arg.equals("-cp") || arg.equals("-classpath")) { - classpath = args[i + 1]; - break; - } - } - - if (classpath.isBlank()) { - throw new RuntimeException("Cannot discover the classpath. Was --module-path used instead?"); - } - - return Arrays.stream(classpath.split(":")) - .filter(s -> !s.contains("test-clients")) - .collect(Collectors.joining(":")); - } - - @SuppressWarnings("java:S2142") - // sonar doesn't like the fact that we are catching and ignoring an exception - private String getPlatformStatus() { - Map statusMap = new HashMap(); - String statusKey = ""; - try { - final var response = httpClient.send(prometheusRequest, HttpResponse.BodyHandlers.ofString()); - final var reader = new BufferedReader(new StringReader(response.body())); - String line; - while ((line = reader.readLine()) != null) { - final var helpMatcher = PROM_PLATFORM_STATUS_HELP_PATTERN.matcher(line); - if (helpMatcher.matches()) { - final var kvPairs = helpMatcher.group(1).split(" "); - for (final var kvPair : kvPairs) { - final var parts = kvPair.split("="); - statusMap.put(parts[0], parts[1]); - } - } - - final var matcher = PROM_PLATFORM_STATUS_PATTERN.matcher(line); - if (matcher.matches()) { - // This line always comes AFTER the # HELP line. - statusKey = matcher.group(1); - break; - } - } - } catch (IOException | InterruptedException ignored) { - } - return statusMap.get(statusKey); - } -} diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/junit/TestBase.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/junit/TestBase.java index 71fa8c41fa2b..897fa5635ba0 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/junit/TestBase.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/junit/TestBase.java @@ -16,7 +16,7 @@ package com.hedera.services.bdd.junit; -import static com.hedera.services.bdd.junit.RecordStreamAccess.RECORD_STREAM_ACCESS; +import static com.hedera.services.bdd.junit.support.RecordStreamAccess.RECORD_STREAM_ACCESS; import static com.hedera.services.bdd.suites.HapiSuite.ETH_SUFFIX; import static com.hedera.services.bdd.suites.SuiteRunner.SUITE_NAME_WIDTH; import static com.hedera.services.bdd.suites.SuiteRunner.rightPadded; @@ -25,11 +25,11 @@ import static org.junit.jupiter.api.DynamicContainer.dynamicContainer; import static org.junit.jupiter.api.DynamicTest.dynamicTest; -import com.hedera.services.bdd.junit.validators.HgcaaLogValidator; -import com.hedera.services.bdd.junit.validators.QueryLogValidator; +import com.hedera.services.bdd.junit.support.RecordStreamValidator; +import com.hedera.services.bdd.junit.support.validators.HgcaaLogValidator; +import com.hedera.services.bdd.junit.support.validators.QueryLogValidator; import com.hedera.services.bdd.spec.HapiSpec; import com.hedera.services.bdd.suites.HapiSuite; -import com.hedera.services.bdd.suites.records.ClosingTime; import java.io.IOException; import java.util.Arrays; import java.util.List; @@ -104,16 +104,6 @@ protected final DynamicTest queriesLogValidation(final String loc) { return dynamicTest("queriesLogValidation", () -> new QueryLogValidator(loc).validate()); } - @SuppressWarnings("java:S1181") - protected final DynamicTest recordStreamValidation(final String loc, final RecordStreamValidator... validators) { - return dynamicTest("recordStreamValidation", () -> { - final var closingTimeSpecs = TestBase.extractContextualizedSpecsFrom( - List.of(ClosingTime::new), TestBase::contextualizedSpecsFromConcurrent); - concurrentExecutionOf(closingTimeSpecs); - assertValidatorsPass(loc, Arrays.asList(validators)); - }); - } - @SuppressWarnings("java:S1181") public static void assertValidatorsPass(final String loc, final List validators) throws IOException { diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/junit/TestTags.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/junit/TestTags.java index 5c052094547b..c4264d855295 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/junit/TestTags.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/junit/TestTags.java @@ -24,7 +24,7 @@ private TestTags() { public static final String CRYPTO = "CRYPTO"; public static final String SMART_CONTRACT = "SMART_CONTRACT"; - public static final String TIME_CONSUMING = "TIME_CONSUMING"; + public static final String LONG_RUNNING = "LONG_RUNNING"; public static final String TOKEN = "TOKEN"; public static final String RESTART = "RESTART"; public static final String ND_RECONNECT = "ND_RECONNECT"; diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/junit/extensions/ExtensionUtils.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/junit/extensions/ExtensionUtils.java new file mode 100644 index 000000000000..95c19a2b40c4 --- /dev/null +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/junit/extensions/ExtensionUtils.java @@ -0,0 +1,36 @@ +/* + * Copyright (C) 2024 Hedera Hashgraph, LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.hedera.services.bdd.junit.extensions; + +import static org.junit.platform.commons.support.AnnotationSupport.isAnnotated; + +import com.hedera.services.bdd.junit.HapiTest; +import com.hedera.services.bdd.junit.LeakyHapiTest; +import edu.umd.cs.findbugs.annotations.NonNull; +import java.lang.reflect.Method; +import java.util.Optional; +import org.junit.jupiter.api.extension.ExtensionContext; + +public class ExtensionUtils { + public static Optional hapiTestMethodOf(@NonNull final ExtensionContext extensionContext) { + return extensionContext.getTestMethod().filter(ExtensionUtils::isHapiTest); + } + + private static boolean isHapiTest(@NonNull final Method method) { + return isAnnotated(method, HapiTest.class) || isAnnotated(method, LeakyHapiTest.class); + } +} diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/junit/extensions/NetworkTargetingExtension.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/junit/extensions/NetworkTargetingExtension.java new file mode 100644 index 000000000000..2f18e9dd2e7e --- /dev/null +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/junit/extensions/NetworkTargetingExtension.java @@ -0,0 +1,48 @@ +/* + * Copyright (C) 2024 Hedera Hashgraph, LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.hedera.services.bdd.junit.extensions; + +import static com.hedera.services.bdd.junit.extensions.ExtensionUtils.hapiTestMethodOf; + +import com.hedera.services.bdd.junit.HapiTest; +import com.hedera.services.bdd.junit.hedera.HederaNetwork; +import com.hedera.services.bdd.spec.HapiSpec; +import edu.umd.cs.findbugs.annotations.NonNull; +import org.junit.jupiter.api.extension.AfterEachCallback; +import org.junit.jupiter.api.extension.BeforeEachCallback; +import org.junit.jupiter.api.extension.ExtensionContext; + +/** + * An extension that binds the target network to the current thread before invoking + * each {@link HapiTest}-annotated test method. + * + *

    (FUTURE) - implement {@link org.junit.jupiter.api.extension.BeforeAllCallback} + * and {@link org.junit.jupiter.api.extension.AfterAllCallback} to handle creating "private" + * networks for annotated test classes and targeting them instead of the shared network. + */ +public class NetworkTargetingExtension implements BeforeEachCallback, AfterEachCallback { + @Override + public void beforeEach(@NonNull final ExtensionContext extensionContext) { + hapiTestMethodOf(extensionContext) + .ifPresent(ignore -> HapiSpec.TARGET_NETWORK.set(HederaNetwork.SHARED_NETWORK.get())); + } + + @Override + public void afterEach(@NonNull final ExtensionContext extensionContext) { + HapiSpec.TARGET_NETWORK.remove(); + } +} diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/junit/extensions/SpecManagerExtension.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/junit/extensions/SpecManagerExtension.java new file mode 100644 index 000000000000..01d21468f1fb --- /dev/null +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/junit/extensions/SpecManagerExtension.java @@ -0,0 +1,99 @@ +/* + * Copyright (C) 2024 Hedera Hashgraph, LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.hedera.services.bdd.junit.extensions; + +import static com.hedera.services.bdd.junit.extensions.ExtensionUtils.hapiTestMethodOf; + +import com.hedera.services.bdd.junit.hedera.HederaNetwork; +import com.hedera.services.bdd.junit.support.SpecManager; +import com.hedera.services.bdd.spec.HapiSpec; +import edu.umd.cs.findbugs.annotations.NonNull; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.extension.AfterAllCallback; +import org.junit.jupiter.api.extension.AfterEachCallback; +import org.junit.jupiter.api.extension.BeforeAllCallback; +import org.junit.jupiter.api.extension.BeforeEachCallback; +import org.junit.jupiter.api.extension.ExtensionContext; +import org.junit.jupiter.api.extension.ParameterContext; +import org.junit.jupiter.api.extension.ParameterResolver; + +public class SpecManagerExtension + implements BeforeAllCallback, AfterAllCallback, BeforeEachCallback, AfterEachCallback, ParameterResolver { + private static final String SPEC_MANAGER = "specManager"; + + @Override + public void beforeAll(@NonNull final ExtensionContext extensionContext) { + if (isRootTestClass(extensionContext)) { + getStore(extensionContext).put(SPEC_MANAGER, new SpecManager(HederaNetwork.SHARED_NETWORK.get())); + } + } + + @Override + public void afterAll(@NonNull final ExtensionContext extensionContext) { + if (isRootTestClass(extensionContext)) { + getStore(extensionContext).remove(SPEC_MANAGER); + } + } + + @Override + public void beforeEach(@NonNull final ExtensionContext extensionContext) { + hapiTestMethodOf(extensionContext) + .ifPresent(ignore -> + HapiSpec.SPEC_MANAGER.set(getStore(extensionContext).get(SPEC_MANAGER, SpecManager.class))); + } + + @Override + public void afterEach(@NonNull final ExtensionContext extensionContext) { + HapiSpec.SPEC_MANAGER.remove(); + } + + @Override + public boolean supportsParameter( + @NonNull final ParameterContext parameterContext, @NonNull final ExtensionContext extensionContext) { + return parameterContext.getParameter().getType() == SpecManager.class + && (parameterContext.getDeclaringExecutable().isAnnotationPresent(BeforeAll.class) + || parameterContext.getDeclaringExecutable().isAnnotationPresent(AfterAll.class)); + } + + @Override + public Object resolveParameter( + @NonNull final ParameterContext parameterContext, @NonNull final ExtensionContext extensionContext) { + return getStore(extensionContext).get(SPEC_MANAGER, SpecManager.class); + } + + private ExtensionContext.Store getStore(@NonNull final ExtensionContext extensionContext) { + return extensionContext.getStore( + ExtensionContext.Namespace.create(getClass(), rootTestClassOf(extensionContext))); + } + + private static boolean isRootTestClass(@NonNull final ExtensionContext extensionContext) { + return extensionContext.getRequiredTestClass().equals(rootTestClassOf(extensionContext)); + } + + private static Class rootTestClassOf(@NonNull final ExtensionContext extensionContext) { + final var maybeParentTestClass = extensionContext + .getParent() + .flatMap(ExtensionContext::getTestClass) + .orElse(null); + if (maybeParentTestClass != null) { + return rootTestClassOf(extensionContext.getParent().get()); + } else { + return extensionContext.getRequiredTestClass(); + } + } +} diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/junit/extensions/SpecNamingExtension.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/junit/extensions/SpecNamingExtension.java new file mode 100644 index 000000000000..fa008fe6818b --- /dev/null +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/junit/extensions/SpecNamingExtension.java @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2024 Hedera Hashgraph, LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.hedera.services.bdd.junit.extensions; + +import static com.hedera.services.bdd.junit.extensions.ExtensionUtils.hapiTestMethodOf; + +import com.hedera.services.bdd.spec.HapiSpec; +import edu.umd.cs.findbugs.annotations.NonNull; +import org.junit.jupiter.api.extension.BeforeEachCallback; +import org.junit.jupiter.api.extension.ExtensionContext; + +public class SpecNamingExtension implements BeforeEachCallback { + @Override + public void beforeEach(@NonNull final ExtensionContext extensionContext) { + hapiTestMethodOf(extensionContext) + .ifPresent(method -> HapiSpec.SPEC_NAME.set( + extensionContext.getRequiredTestClass().getSimpleName() + "." + method.getName())); + } +} diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/junit/hedera/AbstractNode.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/junit/hedera/AbstractNode.java new file mode 100644 index 000000000000..e50bcb86761b --- /dev/null +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/junit/hedera/AbstractNode.java @@ -0,0 +1,60 @@ +/* + * Copyright (C) 2024 Hedera Hashgraph, LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.hedera.services.bdd.junit.hedera; + +import static com.hedera.services.bdd.junit.hedera.live.ProcessUtils.OVERRIDE_RECORD_STREAM_FOLDER; +import static com.hedera.services.bdd.junit.hedera.live.WorkingDirUtils.DATA_DIR; + +import com.hedera.hapi.node.base.AccountID; +import edu.umd.cs.findbugs.annotations.NonNull; +import java.nio.file.Path; + +public abstract class AbstractNode implements HederaNode { + protected final NodeMetadata metadata; + + protected AbstractNode(@NonNull final NodeMetadata metadata) { + this.metadata = metadata; + } + + @Override + public int getPort() { + return metadata.grpcPort(); + } + + @Override + public long getNodeId() { + return metadata.nodeId(); + } + + @Override + public String getName() { + return metadata.name(); + } + + @Override + public AccountID getAccountId() { + return metadata.accountId(); + } + + @Override + public Path getRecordStreamPath() { + return metadata.workingDir() + .resolve(DATA_DIR) + .resolve(OVERRIDE_RECORD_STREAM_FOLDER) + .resolve("record0.0." + getAccountId().accountNumOrThrow()); + } +} diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/junit/hedera/HederaNetwork.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/junit/hedera/HederaNetwork.java new file mode 100644 index 000000000000..abbb2fbbfa4c --- /dev/null +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/junit/hedera/HederaNetwork.java @@ -0,0 +1,261 @@ +/* + * Copyright (C) 2024 Hedera Hashgraph, LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.hedera.services.bdd.junit.hedera; + +import static com.hedera.services.bdd.junit.hedera.live.WorkingDirUtils.workingDirFor; +import static com.hedera.services.bdd.suites.TargetNetworkType.SHARED_HAPI_TEST_NETWORK; +import static com.swirlds.platform.system.status.PlatformStatus.ACTIVE; +import static java.util.Objects.requireNonNull; +import static java.util.concurrent.CompletableFuture.runAsync; +import static java.util.concurrent.TimeUnit.MILLISECONDS; + +import com.hedera.hapi.node.base.AccountID; +import com.hedera.services.bdd.junit.hedera.live.GrpcPinger; +import com.hedera.services.bdd.junit.hedera.live.PrometheusClient; +import com.hedera.services.bdd.junit.hedera.live.SubProcessNode; +import com.hedera.services.bdd.suites.TargetNetworkType; +import com.swirlds.platform.system.status.PlatformStatus; +import edu.umd.cs.findbugs.annotations.NonNull; +import edu.umd.cs.findbugs.annotations.Nullable; +import java.time.Duration; +import java.util.List; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.atomic.AtomicReference; +import java.util.stream.IntStream; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +/** + * A network of Hedera nodes. For now, assumed to be accessed via + * a live gRPC connection. In the future, we will abstract the + * submission and querying operations to allow for an embedded + * "network". + */ +public class HederaNetwork { + private static final Logger log = LogManager.getLogger(HederaNetwork.class); + + private static final int FIRST_GRPC_PORT = 50211; + private static final int FIRST_GOSSIP_PORT = 60000; + private static final int FIRST_GOSSIP_TLS_PORT = 60001; + private static final int FIRST_PROMETHEUS_PORT = 10000; + private static final long FIRST_NODE_ACCOUNT_NUM = 3; + private static final String SHARED_NETWORK_NAME = "LAUNCHER_SESSION_SCOPE"; + private static final String[] NODE_NAMES = new String[] {"Alice", "Bob", "Carol", "Dave"}; + private static final GrpcPinger GRPC_PINGER = new GrpcPinger(); + private static final PrometheusClient PROMETHEUS_CLIENT = new PrometheusClient(); + + private static int nextGrpcPort = FIRST_GRPC_PORT; + private static int nextGossipPort = FIRST_GOSSIP_PORT; + private static int nextGossipTlsPort = FIRST_GOSSIP_TLS_PORT; + private static int nextPrometheusPort = FIRST_PROMETHEUS_PORT; + + public static final AtomicReference SHARED_NETWORK = new AtomicReference<>(); + + private final String configTxt; + /** + * If null, this is the shared network (only one is allowed per launcher session). + */ + @Nullable + private final String networkName; + + private final List nodes; + + @Nullable + private CompletableFuture ready; + + private HederaNetwork(@Nullable final String networkName, @NonNull final List nodes) { + this.nodes = requireNonNull(nodes); + this.networkName = networkName; + this.configTxt = configTxtFor(name(), nodes); + } + + /** + * Creates a shared network of sub-process nodes with the given size. + * + * @param size the number of nodes in the network + * @return the shared network + */ + public static synchronized HederaNetwork newSharedSubProcessNetwork(final int size) { + if (SHARED_NETWORK.get() != null) { + throw new UnsupportedOperationException("Only one shared network allowed per launcher session"); + } + final var sharedNetwork = liveNetwork(null, size); + SHARED_NETWORK.set(sharedNetwork); + return sharedNetwork; + } + + /** + * Creates a network of sub-process nodes with the given name and size. Unlike the shared + * network, this network's nodes will have working directories scoped to the given name. + * + * @param name the name of the network + * @param size the number of nodes in the network + * @return the network + */ + public static HederaNetwork newSubProcessNetwork(@NonNull final String name, final int size) { + return liveNetwork(name, size); + } + + /** + * Creates a network of live (sub-process) nodes with the given name and size. This method is + * synchronized because we don't want to re-use any ports across different networks. + * + * @param name the name of the network + * @param size the number of nodes in the network + * @return the network + */ + private static synchronized HederaNetwork liveNetwork(@Nullable final String name, final int size) { + final var network = new HederaNetwork( + name, + IntStream.range(0, size) + .mapToObj( + nodeId -> new SubProcessNode(metadataFor(nodeId, name), GRPC_PINGER, PROMETHEUS_CLIENT)) + .toList()); + // Reserve ports for the next network + nextGrpcPort += size * 2; + nextGossipPort += size * 2; + nextGossipTlsPort += size * 2; + nextPrometheusPort += size; + Runtime.getRuntime().addShutdownHook(new Thread(network::terminate)); + return network; + } + + /** + * Returns the network type; for now this is always + * {@link TargetNetworkType#SHARED_HAPI_TEST_NETWORK}. + * + * @return the network type + */ + public TargetNetworkType type() { + return SHARED_HAPI_TEST_NETWORK; + } + + /** + * Returns the nodes of the network. + * + * @return the nodes of the network + */ + public List nodes() { + return nodes; + } + + /** + * Returns the nodes of the network that match the given selector. + * + * @param selector the selector + * @return the nodes that match the selector + */ + public List nodesFor(@NonNull final NodeSelector selector) { + return nodes.stream().filter(selector).toList(); + } + + /** + * Returns the name of the network. + * + * @return the name of the network + */ + public String name() { + return networkName == null ? SHARED_NETWORK_NAME : networkName; + } + + /** + * Starts all nodes in the network and waits for them to reach the + * {@link PlatformStatus#ACTIVE} status, or times out. + * + * @param timeout the maximum time to wait for all nodes to start + */ + public void startWithin(@NonNull final Duration timeout) { + final var latch = new CountDownLatch(nodes.size()); + CompletableFuture.allOf(nodes.stream() + .map(node -> runAsync(() -> { + node.initWorkingDir(configTxt); + node.start(); + }) + .thenCompose(nothing -> node.statusFuture(ACTIVE, timeout) + .thenRun(() -> { + log.info("Node '{}' is ready", node.getName()); + latch.countDown(); + }))) + .toArray(CompletableFuture[]::new)) + .orTimeout(timeout.toMillis(), MILLISECONDS); + ready = runAsync(() -> { + try { + latch.await(); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + throw new IllegalStateException(e); + } + }) + .thenRun(() -> log.info("All nodes in network '{}' are ready", name())); + } + + /** + * Forcibly stops all nodes in the network. + */ + public void terminate() { + nodes.forEach(HederaNode::terminate); + } + + /** + * Waits for all nodes in the network to be ready. + */ + public void waitForReady() { + requireNonNull(ready).join(); + } + + private static String configTxtFor(@NonNull final String networkName, @NonNull final List nodes) { + final var sb = new StringBuilder(); + sb.append("swirld, ") + .append(networkName) + .append("\n") + .append("\n# This next line is, hopefully, ignored.\n") + .append("app, HederaNode.jar\n\n#The following nodes make up this network\n"); + for (final var node : nodes) { + sb.append("address, ") + .append(node.getNodeId()) + .append(", ") + .append(node.getName().charAt(0)) + .append(", ") + .append(node.getName()) + .append(", 1, 127.0.0.1, ") + .append(FIRST_GOSSIP_PORT + (node.getNodeId() * 2)) + .append(", 127.0.0.1, ") + .append(FIRST_GOSSIP_TLS_PORT + (node.getNodeId() * 2)) + .append(", ") + .append("0.0.") + .append(node.getAccountId().accountNumOrThrow()) + .append("\n"); + } + sb.append("\nnextNodeId, ").append(nodes.size()).append("\n"); + return sb.toString(); + } + + private static NodeMetadata metadataFor(final int nodeId, @Nullable final String networkName) { + return new NodeMetadata( + nodeId, + NODE_NAMES[nodeId], + AccountID.newBuilder() + .accountNum(FIRST_NODE_ACCOUNT_NUM + nodeId) + .build(), + nextGrpcPort + nodeId * 2, + nextGossipPort + nodeId * 2, + nextGossipTlsPort + nodeId * 2, + nextPrometheusPort + nodeId, + workingDirFor(nodeId, networkName)); + } +} diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/junit/hedera/HederaNode.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/junit/hedera/HederaNode.java new file mode 100644 index 000000000000..c7f01c484fe6 --- /dev/null +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/junit/hedera/HederaNode.java @@ -0,0 +1,115 @@ +/* + * Copyright (C) 2024 Hedera Hashgraph, LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.hedera.services.bdd.junit.hedera; + +import com.hedera.hapi.node.base.AccountID; +import com.hedera.services.bdd.spec.HapiSpec; +import com.swirlds.platform.system.status.PlatformStatus; +import edu.umd.cs.findbugs.annotations.NonNull; +import java.nio.file.Path; +import java.time.Duration; +import java.util.concurrent.CompletableFuture; + +public interface HederaNode { + /** + * Gets the port number of the gRPC service. + * + * @return the port number of the gRPC service + */ + int getPort(); + + /** + * Gets the node ID, such as 0, 1, 2, or 3. + * @return the node ID + */ + long getNodeId(); + + /** + * The name of the node, such as "Alice" or "Bob". + * @return the node name + */ + String getName(); + + /** + * Gets the node Account ID + * @return the node account ID + */ + AccountID getAccountId(); + + /** + * Gets the path to the node's record stream. + * + * @return the path to the node's record stream + */ + Path getRecordStreamPath(); + + /** + * Initializes the working directory for the node. Must be called before the node is started. + * + * @param configTxt the address book the node should start with + */ + void initWorkingDir(String configTxt); + + /** + * Starts the node software. + * + * @throws IllegalStateException if the working directory was not initialized + */ + void start(); + + /** + * Stops the node software gracefully + */ + boolean stop(); + + /** + * Stops the node software forcibly. + */ + boolean terminate(); + + /** + * Returns a future that resolves when the node has the given status. + * + * @param status the status to wait for + * @param timeout the maximum time to wait for the node to reach the status + * @return a future that resolves when the node has the given status + */ + CompletableFuture statusFuture(@NonNull PlatformStatus status, @NonNull Duration timeout); + + /** + * Returns a future that resolves when the node has stopped. + * + * @return a future that resolves when the node has stopped + */ + CompletableFuture stopFuture(@NonNull Duration timeout); + + /** + * Returns the string that would identify this node as a target + * server in a {@link HapiSpec} that is submitting transactions + * and sending queries via gRPC. + * + *

    (FUTURE) Remove this method once {@link HapiSpec} is + * refactored to be agnostic about how a target node should + * receive transactions and queries. (E.g. an embedded node + * can have its workflows directly invoked.) + * + * @return this node's HAPI spec identifier + */ + default String hapiSpecIdentifier() { + return "127.0.0.1:" + getPort() + ":0.0." + getAccountId().accountNumOrThrow(); + } +} diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/junit/hedera/NodeMetadata.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/junit/hedera/NodeMetadata.java new file mode 100644 index 000000000000..138dbcb353b1 --- /dev/null +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/junit/hedera/NodeMetadata.java @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2024 Hedera Hashgraph, LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.hedera.services.bdd.junit.hedera; + +import com.hedera.hapi.node.base.AccountID; +import java.nio.file.Path; + +public record NodeMetadata( + long nodeId, + String name, + AccountID accountId, + int grpcPort, + int gossipPort, + int tlsGossipPort, + int prometheusPort, + Path workingDir) {} diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/junit/hedera/NodeSelector.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/junit/hedera/NodeSelector.java new file mode 100644 index 000000000000..7f756ada913c --- /dev/null +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/junit/hedera/NodeSelector.java @@ -0,0 +1,95 @@ +/* + * Copyright (C) 2024 Hedera Hashgraph, LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.hedera.services.bdd.junit.hedera; + +import com.hedera.hapi.node.base.AccountID; +import edu.umd.cs.findbugs.annotations.NonNull; +import java.util.function.Predicate; + +/** + * Defines the criteria by which to select {@link HederaNode}s from a {@link HederaNetwork}. + */ +public interface NodeSelector extends Predicate { + /** + * Returns true if the given node should be selected. + * + * @param node the input argument + * @return true if the node should be selected. + */ + @Override + boolean test(@NonNull HederaNode node); + + /** Gets a {@link NodeSelector} that selects nodes by name in a case-insensitive way, such as Alice or Bob. */ + static NodeSelector byName(@NonNull final String name) { + return new NodeSelector() { + @Override + public boolean test(@NonNull final HederaNode node) { + return name.equalsIgnoreCase(node.getName()); + } + + @Override + public String toString() { + return "by name '" + name + "'"; + } + }; + } + + /** Gets a {@link NodeSelector} that selects nodes by operator account ID. Does not work with aliases. */ + static NodeSelector byOperatorAccountId(@NonNull final AccountID operatorAccountID) { + return new NodeSelector() { + @Override + public boolean test(@NonNull final HederaNode node) { + return operatorAccountID.equals(node.getAccountId()); + } + + @Override + public String toString() { + return "by operator account id '0.0." + operatorAccountID.accountNumOrThrow() + "'"; + } + }; + } + + /** Gets a {@link NodeSelector} that selects nodes by nodeId in a case-insensitive way */ + static NodeSelector byNodeId(final long nodeId) { + return new NodeSelector() { + @Override + public boolean test(@NonNull final HederaNode node) { + return node.getNodeId() == nodeId; + } + + @Override + public String toString() { + return "by nodeId '" + nodeId + "'"; + } + }; + } + + /** Gets a {@link NodeSelector} that selects all nodes */ + static NodeSelector allNodes() { + return new NodeSelector() { + @Override + public boolean test(@NonNull final HederaNode node) { + return true; + } + + @Override + public String toString() { + return "including all nodes"; + } + }; + } +} diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/junit/hedera/embedded/package-info.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/junit/hedera/embedded/package-info.java new file mode 100644 index 000000000000..93a24534c47b --- /dev/null +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/junit/hedera/embedded/package-info.java @@ -0,0 +1,6 @@ +/** + * (FUTURE) Will provide a {@link com.hedera.services.bdd.junit.hedera.HederaNode} + * implementation that synchronously invokes the Hedera workflows using state + * backed by in-memory data structures instead of Merkle state. + */ +package com.hedera.services.bdd.junit.hedera.embedded; diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/junit/hedera/live/GrpcPinger.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/junit/hedera/live/GrpcPinger.java new file mode 100644 index 000000000000..e62abfd386c4 --- /dev/null +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/junit/hedera/live/GrpcPinger.java @@ -0,0 +1,37 @@ +/* + * Copyright (C) 2024 Hedera Hashgraph, LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.hedera.services.bdd.junit.hedera.live; + +import java.io.IOException; +import java.net.MalformedURLException; +import java.net.URL; + +public class GrpcPinger { + public boolean isLive(final int port) { + try { + final var url = new URL("http://localhost:" + port + "/"); + final var connection = url.openConnection(); + connection.connect(); + return true; + } catch (MalformedURLException impossible) { + throw new IllegalStateException(impossible); + } catch (IOException ignored) { + // This is expected, the node is not up yet + } + return false; + } +} diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/junit/hedera/live/ProcessUtils.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/junit/hedera/live/ProcessUtils.java new file mode 100644 index 000000000000..a5e450c209a8 --- /dev/null +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/junit/hedera/live/ProcessUtils.java @@ -0,0 +1,124 @@ +/* + * Copyright (C) 2024 Hedera Hashgraph, LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.hedera.services.bdd.junit.hedera.live; + +import static com.hedera.services.bdd.junit.hedera.live.WorkingDirUtils.DATA_DIR; + +import com.hedera.services.bdd.junit.hedera.NodeMetadata; +import edu.umd.cs.findbugs.annotations.NonNull; +import java.io.IOException; +import java.io.UncheckedIOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Arrays; +import java.util.stream.Collectors; + +public class ProcessUtils { + private static final int FIRST_AGENT_PORT = 5005; + private static final long NODE_ID_TO_SUSPEND = -1; + private static final String[] EMPTY_STRING_ARRAY = new String[0]; + public static final String OVERRIDE_RECORD_STREAM_FOLDER = "recordStreams"; + + private ProcessUtils() { + throw new UnsupportedOperationException("Utility Class"); + } + + /** + * Destroys any process that appears to be a node started from the given metadata, based on the + * process command being {@code java} and having a last argument matching the node ID. + * + * @param nodeId the id of the node whose processes should be destroyed + */ + public static void destroyAnySubProcessNodeWithId(final long nodeId) { + ProcessHandle.allProcesses() + .filter(p -> p.info().command().orElse("").contains("java")) + .filter(p -> endsWith(p.info().arguments().orElse(EMPTY_STRING_ARRAY), Long.toString(nodeId))) + .forEach(ProcessHandle::destroyForcibly); + } + + /** + * Starts a sub-process node from the given metadata and returns its {@link ProcessHandle}. + * + * @param metadata the metadata of the node to start + * @return the {@link ProcessHandle} of the started node + */ + public static ProcessHandle startSubProcessNodeFrom(@NonNull final NodeMetadata metadata) { + final var builder = new ProcessBuilder(); + final var environment = builder.environment(); + environment.put("LC_ALL", "en.UTF-8"); + environment.put("LANG", "en_US.UTF-8"); + environment.put("grpc.port", Integer.toString(metadata.grpcPort())); + try { + return builder.command( + // Use the same java command that started this process + ProcessHandle.current().info().command().orElseThrow(), + "-agentlib:jdwp=transport=dt_socket,server=y,suspend=" + + (metadata.nodeId() == NODE_ID_TO_SUSPEND ? "y" : "n") + ",address=*:" + + (FIRST_AGENT_PORT + metadata.nodeId()), + "-classpath", + // Use the same classpath that started this process, excluding test-clients + currentNonTestClientClasspath(), + // JVM system + "-Dfile.encoding=UTF-8", + "-Dprometheus.endpointPortNumber=" + metadata.prometheusPort(), + "-Dhedera.recordStream.logDir=" + DATA_DIR + "/" + OVERRIDE_RECORD_STREAM_FOLDER, + "-Dhedera.profiles.active=DEV", + "-Dhedera.workflows.enabled=true", + "com.hedera.node.app.ServicesMain", + "-local", + Long.toString(metadata.nodeId())) + .directory(metadata.workingDir().toFile()) + .inheritIO() + .start() + .toHandle(); + } catch (IOException e) { + throw new UncheckedIOException(e); + } + } + + private static String currentNonTestClientClasspath() { + // Could have been launched with -cp, or -classpath, or @/path/to/classpathFile.txt, or maybe module path? + final var args = ProcessHandle.current().info().arguments().orElse(EMPTY_STRING_ARRAY); + + String classpath = ""; + for (int i = 0; i < args.length; i++) { + final var arg = args[i]; + if (arg.startsWith("@")) { + try { + final var fileContents = Files.readString(Path.of(arg.substring(1))); + classpath = fileContents.substring(fileContents.indexOf('/')); + break; + } catch (IOException e) { + throw new UncheckedIOException(e); + } + } else if (arg.equals("-cp") || arg.equals("-classpath")) { + classpath = args[i + 1]; + break; + } + } + if (classpath.isBlank()) { + throw new IllegalStateException("Cannot discover the classpath. Was --module-path used instead?"); + } + return Arrays.stream(classpath.split(":")) + .filter(s -> !s.contains("test-clients")) + .collect(Collectors.joining(":")); + } + + private static boolean endsWith(final String[] args, final String lastArg) { + return args.length > 0 && args[args.length - 1].equals(lastArg); + } +} diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/junit/hedera/live/PrometheusClient.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/junit/hedera/live/PrometheusClient.java new file mode 100644 index 000000000000..a7b43b4be77b --- /dev/null +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/junit/hedera/live/PrometheusClient.java @@ -0,0 +1,114 @@ +/* + * Copyright (C) 2024 Hedera Hashgraph, LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.hedera.services.bdd.junit.hedera.live; + +import com.swirlds.platform.system.status.PlatformStatus; +import edu.umd.cs.findbugs.annotations.NonNull; +import edu.umd.cs.findbugs.annotations.Nullable; +import java.io.BufferedReader; +import java.io.IOException; +import java.io.StringReader; +import java.net.URI; +import java.net.URISyntaxException; +import java.net.http.HttpClient; +import java.net.http.HttpRequest; +import java.net.http.HttpResponse; +import java.util.HashMap; +import java.util.Map; +import java.util.Optional; +import java.util.concurrent.atomic.AtomicReference; +import java.util.regex.Pattern; + +/** + * A client for querying the status of a Hedera node via its Prometheus endpoint. + * The Prometheus endpoint returns text with lines such as, + *

    + * # HELP platform_PlatformStatus 0=NO_VALUE 1=STARTING_UP 2=ACTIVE 4=BEHIND 5=FREEZING 6=FREEZE_COMPLETE 7=REPLAYING_EVENTS 8=OBSERVING 9=CHECKING 10=RECONNECT_COMPLETE 11=CATASTROPHIC_FAILURE
    + * # TYPE platform_PlatformStatus gauge
    + * platform_PlatformStatus{node="0",} 2.0
    + * 
    + * Which means we can get the platform status as the value of the {@code platform_PlatformStatus} gauge. + */ +public class PrometheusClient { + private static final Pattern PROM_PLATFORM_STATUS_HELP_PATTERN = + Pattern.compile("# HELP platform_PlatformStatus (.*)"); + private static final Pattern PROM_PLATFORM_STATUS_PATTERN = + Pattern.compile("platform_PlatformStatus\\{.*\\} (\\d+)\\.\\d+"); + + private final HttpClient httpClient = HttpClient.newHttpClient(); + + /** + * Returns the Platform status of the node with Prometheus endpoint at the given + * port; or null if the node is not available. + * + * @param port the port of the node's Prometheus endpoint + * @return the status of the node; or null if the node is not available + */ + public @Nullable PlatformStatus statusFromLocalEndpoint(final int port) { + return Optional.ofNullable(platformStatusAt(port)) + .map(PlatformStatus::valueOf) + .orElse(null); + } + + private @Nullable String platformStatusAt(final int port) { + final Map statusMap = new HashMap<>(); + final AtomicReference statusKey = new AtomicReference<>(); + try { + final var request = prometheusRequest(port); + final var response = httpClient.send(request, HttpResponse.BodyHandlers.ofString()); + final var reader = new BufferedReader(new StringReader(response.body())); + reader.lines().forEach(line -> { + fillStatusMapIfHelp(line, statusMap); + setStatusIfCurrent(line, statusKey); + }); + } catch (IOException ignore) { + // We allow unavailable statuses + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + } + return statusMap.get(statusKey.get()); + } + + private void setStatusIfCurrent(@NonNull final String line, @NonNull final AtomicReference statusKey) { + final var matcher = PROM_PLATFORM_STATUS_PATTERN.matcher(line); + if (matcher.matches()) { + statusKey.set(matcher.group(1)); + } + } + + private void fillStatusMapIfHelp(@NonNull final String line, @NonNull final Map statusMap) { + final var helpMatcher = PROM_PLATFORM_STATUS_HELP_PATTERN.matcher(line); + if (helpMatcher.matches()) { + final var kvPairs = helpMatcher.group(1).split(" "); + for (final var kvPair : kvPairs) { + final var parts = kvPair.split("="); + statusMap.put(parts[0], parts[1]); + } + } + } + + private HttpRequest prometheusRequest(final int port) { + try { + return HttpRequest.newBuilder() + .uri(new URI("http://localhost:" + port)) + .GET() + .build(); + } catch (URISyntaxException impossible) { + throw new IllegalStateException(impossible); + } + } +} diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/junit/hedera/live/SubProcessNode.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/junit/hedera/live/SubProcessNode.java new file mode 100644 index 000000000000..5b8b4390ada1 --- /dev/null +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/junit/hedera/live/SubProcessNode.java @@ -0,0 +1,147 @@ +/* + * Copyright (C) 2024 Hedera Hashgraph, LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.hedera.services.bdd.junit.hedera.live; + +import static com.hedera.services.bdd.junit.hedera.live.ProcessUtils.destroyAnySubProcessNodeWithId; +import static com.hedera.services.bdd.junit.hedera.live.ProcessUtils.startSubProcessNodeFrom; +import static com.hedera.services.bdd.junit.hedera.live.WorkingDirUtils.recreateWorkingDir; +import static java.util.Objects.requireNonNull; +import static java.util.concurrent.TimeUnit.MILLISECONDS; + +import com.hedera.node.app.Hedera; +import com.hedera.services.bdd.junit.hedera.AbstractNode; +import com.hedera.services.bdd.junit.hedera.HederaNode; +import com.hedera.services.bdd.junit.hedera.NodeMetadata; +import com.swirlds.base.function.BooleanFunction; +import com.swirlds.platform.system.status.PlatformStatus; +import edu.umd.cs.findbugs.annotations.NonNull; +import edu.umd.cs.findbugs.annotations.Nullable; +import java.time.Duration; +import java.util.concurrent.CompletableFuture; +import java.util.function.BooleanSupplier; + +/** + * A node running in its own OS process as a subprocess of the JUnit test runner. + */ +public class SubProcessNode extends AbstractNode implements HederaNode { + private static final long WAIT_SLEEP_MILLIS = 10L; + + private final GrpcPinger grpcPinger; + private final PrometheusClient prometheusClient; + /** + * If this node is running, the {@link ProcessHandle} of the node's process; null otherwise. + */ + @Nullable + private ProcessHandle processHandle; + /** + * Whether the working directory has been initialized. + */ + private boolean workingDirInitialized; + + public SubProcessNode( + @NonNull final NodeMetadata metadata, + @NonNull final GrpcPinger grpcPinger, + @NonNull final PrometheusClient prometheusClient) { + super(metadata); + this.grpcPinger = requireNonNull(grpcPinger); + this.prometheusClient = requireNonNull(prometheusClient); + // Just something to keep checkModuleInfo from claiming we don't require com.hedera.node.app + requireNonNull(Hedera.class); + } + + @Override + public void initWorkingDir(@NonNull final String configTxt) { + recreateWorkingDir(metadata.workingDir(), configTxt); + workingDirInitialized = true; + } + + @Override + public void start() { + assertStopped(); + assertWorkingDirInitialized(); + destroyAnySubProcessNodeWithId(metadata.nodeId()); + processHandle = startSubProcessNodeFrom(metadata); + } + + @Override + public boolean stop() { + return stopWith(ProcessHandle::destroy); + } + + @Override + public boolean terminate() { + return stopWith(ProcessHandle::destroyForcibly); + } + + @Override + public CompletableFuture statusFuture(@NonNull final PlatformStatus status, @NonNull final Duration timeout) { + return waitUntil( + () -> { + final var currentStatus = prometheusClient.statusFromLocalEndpoint(metadata.prometheusPort()); + if (!status.equals(currentStatus)) { + return false; + } + return status != PlatformStatus.ACTIVE || grpcPinger.isLive(metadata.grpcPort()); + }, + timeout); + } + + @Override + public CompletableFuture stopFuture(@NonNull final Duration timeout) { + return waitUntil(() -> processHandle == null, timeout); + } + + @Override + public String toString() { + return "SubProcessNode{" + "metadata=" + metadata + ", workingDirInitialized=" + workingDirInitialized + '}'; + } + + private CompletableFuture waitUntil(@NonNull final BooleanSupplier condition, @NonNull Duration timeout) { + return CompletableFuture.runAsync(() -> { + while (!condition.getAsBoolean()) { + try { + MILLISECONDS.sleep(WAIT_SLEEP_MILLIS); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + throw new IllegalStateException("Interrupted while waiting for condition", e); + } + } + }) + .orTimeout(timeout.toMillis(), MILLISECONDS); + } + + private boolean stopWith(@NonNull final BooleanFunction stop) { + if (processHandle == null) { + return false; + } + final var result = stop.apply(processHandle); + processHandle = null; + return result; + } + + private void assertStopped() { + if (processHandle != null) { + throw new IllegalStateException("Node is still running"); + } + } + + private void assertWorkingDirInitialized() { + if (!workingDirInitialized) { + throw new IllegalStateException("Working directory not initialized"); + } + } +} diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/junit/hedera/live/WorkingDirUtils.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/junit/hedera/live/WorkingDirUtils.java new file mode 100644 index 000000000000..ea1da2161f2b --- /dev/null +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/junit/hedera/live/WorkingDirUtils.java @@ -0,0 +1,185 @@ +/* + * Copyright (C) 2024 Hedera Hashgraph, LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.hedera.services.bdd.junit.hedera.live; + +import edu.umd.cs.findbugs.annotations.NonNull; +import edu.umd.cs.findbugs.annotations.Nullable; +import java.io.File; +import java.io.IOException; +import java.io.UncheckedIOException; +import java.nio.file.Files; +import java.nio.file.OpenOption; +import java.nio.file.Path; +import java.nio.file.StandardOpenOption; +import java.util.Comparator; +import java.util.List; +import java.util.stream.Stream; + +public class WorkingDirUtils { + private static final String OUTPUT_DIR = "output"; + private static final String KEYS_FOLDER = "keys"; + private static final String CONFIG_FOLDER = "config"; + private static final List WORKING_DIR_DATA_FOLDERS = List.of(KEYS_FOLDER, CONFIG_FOLDER); + private static final String CONFIG_TXT = "config.txt"; + private static final String LOG4J2_XML = "log4j2.xml"; + private static final String BASE_WORKING_DIR = "./build/hapi-test/node"; + private static final String BOOTSTRAP_ASSETS_LOC = "../configuration/dev"; + + public static final String DATA_DIR = "data"; + + private WorkingDirUtils() { + throw new UnsupportedOperationException("Utility Class"); + } + + /** + * Returns the path to the working directory for the given node ID. + * + * @param nodeId the ID of the node + * @param scope if non-null, an additional scope to use for the working directory + * @return the path to the working directory + */ + public static Path workingDirFor(final long nodeId, @Nullable String scope) { + final var nodeWorkingDir = Path.of(BASE_WORKING_DIR + nodeId); + return (scope == null ? nodeWorkingDir : nodeWorkingDir.resolve(scope)).normalize(); + } + + /** + * Initializes the working directory by deleting it and creating a new one + * with the given config.txt file. + * + * @param workingDir the path to the working directory + * @param configTxt the contents of the config.txt file + */ + public static void recreateWorkingDir(@NonNull final Path workingDir, @NonNull final String configTxt) { + // Clean up any existing directory structure + rm(workingDir); + // Initialize the data folders + WORKING_DIR_DATA_FOLDERS.forEach(folder -> + createDirectoriesUnchecked(workingDir.resolve(DATA_DIR).resolve(folder))); + // Write the address book (config.txt) + writeStringUnchecked(workingDir.resolve(CONFIG_TXT), configTxt); + // Copy the bootstrap assets into the working directory + copyBootstrapAssets(Path.of(BOOTSTRAP_ASSETS_LOC).toAbsolutePath().normalize(), workingDir); + // Update the log4j2.xml file with the correct output directory + updateLog4j2XmlOutputDir(workingDir); + } + + private static void updateLog4j2XmlOutputDir(@NonNull final Path workingDir) { + final var path = workingDir.resolve(LOG4J2_XML); + final var log4j2Xml = readStringUnchecked(path); + final var updatedLog4j2Xml = log4j2Xml + .replace( + "\n" + " ", + """ + + + %d{yyyy-MM-dd HH:mm:ss.SSS} %-5p %-4L %c{1} - %m{nolookups}%n + + + + + + + + + + + + + + + + + + + + + """) + .replace( + "output/", + workingDir.resolve(OUTPUT_DIR).toAbsolutePath().normalize() + "/"); + writeStringUnchecked(path, updatedLog4j2Xml, StandardOpenOption.WRITE); + } + + /** + * Recursively deletes the given path. + * + * @param path the path to delete + */ + public static void rm(@NonNull final Path path) { + if (Files.exists(path)) { + try (Stream paths = Files.walk(path)) { + paths.sorted(Comparator.reverseOrder()).map(Path::toFile).forEach(File::delete); + } catch (IOException e) { + throw new UncheckedIOException(e); + } + } + } + + private static String readStringUnchecked(@NonNull final Path path) { + try { + return Files.readString(path); + } catch (IOException e) { + throw new UncheckedIOException(e); + } + } + + private static void writeStringUnchecked( + @NonNull final Path path, @NonNull final String content, @NonNull final OpenOption... options) { + try { + Files.writeString(path, content, options); + } catch (IOException e) { + throw new UncheckedIOException(e); + } + } + + private static void createDirectoriesUnchecked(@NonNull final Path path) { + try { + Files.createDirectories(path); + } catch (IOException e) { + throw new UncheckedIOException(e); + } + } + + private static void copyBootstrapAssets(@NonNull final Path assetDir, @NonNull final Path workingDir) { + try (final var files = Files.walk(assetDir)) { + files.filter(file -> !file.equals(assetDir)).forEach(file -> { + if (file.getFileName().toString().endsWith(".properties")) { + copyUnchecked( + file, + workingDir + .resolve(DATA_DIR) + .resolve(CONFIG_FOLDER) + .resolve(file.getFileName().toString())); + } else { + copyUnchecked(file, workingDir.resolve(file.getFileName().toString())); + } + }); + } catch (IOException e) { + throw new UncheckedIOException(e); + } + } + + private static void copyUnchecked(@NonNull final Path source, @NonNull final Path target) { + try { + Files.copy(source, target); + } catch (IOException e) { + throw new UncheckedIOException(e); + } + } +} diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/junit/hedera/live/package-info.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/junit/hedera/live/package-info.java new file mode 100644 index 000000000000..12cee48dcc4a --- /dev/null +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/junit/hedera/live/package-info.java @@ -0,0 +1,6 @@ +/** + * Provides a {@link com.hedera.services.bdd.junit.hedera.HederaNode} + * implementation that runs a Hedera node in a subprocess of the JUnit + * test runner. + */ +package com.hedera.services.bdd.junit.hedera.live; diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/junit/BroadcastingRecordStreamListener.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/junit/support/BroadcastingRecordStreamListener.java similarity index 98% rename from hedera-node/test-clients/src/main/java/com/hedera/services/bdd/junit/BroadcastingRecordStreamListener.java rename to hedera-node/test-clients/src/main/java/com/hedera/services/bdd/junit/support/BroadcastingRecordStreamListener.java index cbe9610fe04d..d8498baf1a1a 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/junit/BroadcastingRecordStreamListener.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/junit/support/BroadcastingRecordStreamListener.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.hedera.services.bdd.junit; +package com.hedera.services.bdd.junit.support; import static com.hedera.node.app.hapi.utils.exports.recordstreaming.RecordStreamingUtils.isRecordFile; import static com.hedera.node.app.hapi.utils.exports.recordstreaming.RecordStreamingUtils.isSidecarFile; diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/junit/RecordStreamAccess.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/junit/support/RecordStreamAccess.java similarity index 97% rename from hedera-node/test-clients/src/main/java/com/hedera/services/bdd/junit/RecordStreamAccess.java rename to hedera-node/test-clients/src/main/java/com/hedera/services/bdd/junit/support/RecordStreamAccess.java index 38b6ef95c1be..51187d6fafbb 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/junit/RecordStreamAccess.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/junit/support/RecordStreamAccess.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.hedera.services.bdd.junit; +package com.hedera.services.bdd.junit.support; import static com.hedera.node.app.hapi.utils.exports.recordstreaming.RecordStreamingUtils.orderedRecordFilesFrom; import static com.hedera.node.app.hapi.utils.exports.recordstreaming.RecordStreamingUtils.orderedSidecarFilesFrom; @@ -164,7 +164,7 @@ public Data readStreamDataFrom( return new Data(recordsWithSideCars, fullRecordFiles); } - static RecordStreamFile ensurePresentRecordFile(final String f) { + public static RecordStreamFile ensurePresentRecordFile(final String f) { try { final var contents = readMaybeCompressedRecordStreamFile(f); if (contents.getRight().isEmpty()) { @@ -176,7 +176,7 @@ static RecordStreamFile ensurePresentRecordFile(final String f) { } } - static SidecarFile ensurePresentSidecarFile(final String f) { + public static SidecarFile ensurePresentSidecarFile(final String f) { try { return RecordStreamingUtils.readMaybeCompressedSidecarFile(f); } catch (IOException e) { diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/junit/RecordStreamValidator.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/junit/support/RecordStreamValidator.java similarity index 67% rename from hedera-node/test-clients/src/main/java/com/hedera/services/bdd/junit/RecordStreamValidator.java rename to hedera-node/test-clients/src/main/java/com/hedera/services/bdd/junit/support/RecordStreamValidator.java index f06777290b3c..a59c46ba6507 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/junit/RecordStreamValidator.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/junit/support/RecordStreamValidator.java @@ -14,23 +14,29 @@ * limitations under the License. */ -package com.hedera.services.bdd.junit; +package com.hedera.services.bdd.junit.support; import com.hedera.services.stream.proto.RecordStreamFile; -import java.lang.reflect.InvocationTargetException; +import edu.umd.cs.findbugs.annotations.NonNull; import java.util.List; +import java.util.stream.Stream; public interface RecordStreamValidator { - default void validateFiles(List files) { - // No-op + default Stream validationErrorsIn(@NonNull final RecordStreamAccess.Data data) { + try { + validateFiles(data.files()); + validateRecordsAndSidecars(data.records()); + } catch (final Throwable t) { + return Stream.of(t); + } + return Stream.empty(); } - default void validateRecordsAndSidecars(List records) { + default void validateFiles(List files) { // No-op } - default void validateRecordsAndSidecarsHapi(HapiTestEnv env, List records) - throws InvocationTargetException, IllegalAccessException { + default void validateRecordsAndSidecars(List records) { // No-op } } diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/junit/RecordWithSidecars.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/junit/support/RecordWithSidecars.java similarity index 95% rename from hedera-node/test-clients/src/main/java/com/hedera/services/bdd/junit/RecordWithSidecars.java rename to hedera-node/test-clients/src/main/java/com/hedera/services/bdd/junit/support/RecordWithSidecars.java index 1ace509a96d9..13d2685ec48a 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/junit/RecordWithSidecars.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/junit/support/RecordWithSidecars.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.hedera.services.bdd.junit; +package com.hedera.services.bdd.junit.support; import com.hedera.services.stream.proto.RecordStreamFile; import com.hedera.services.stream.proto.SidecarFile; diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/junit/support/SpecManager.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/junit/support/SpecManager.java new file mode 100644 index 000000000000..6901f5aae819 --- /dev/null +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/junit/support/SpecManager.java @@ -0,0 +1,57 @@ +/* + * Copyright (C) 2024 Hedera Hashgraph, LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.hedera.services.bdd.junit.support; + +import static com.hedera.services.bdd.spec.HapiSpec.doTargetSpec; +import static java.util.Objects.requireNonNull; + +import com.hedera.services.bdd.junit.hedera.HederaNetwork; +import com.hedera.services.bdd.spec.HapiSpec; +import com.hedera.services.bdd.spec.HapiSpecOperation; +import com.hedera.services.bdd.spec.infrastructure.SpecStateObserver; +import edu.umd.cs.findbugs.annotations.NonNull; +import java.util.ArrayList; +import java.util.List; + +public class SpecManager { + private static final String SPEC_NAME = ""; + + private final List sharedStates = new ArrayList<>(); + + private final HederaNetwork targetNetwork; + + public SpecManager(@NonNull final HederaNetwork targetNetwork) { + this.targetNetwork = requireNonNull(targetNetwork); + } + + public void setup(@NonNull final HapiSpecOperation... ops) throws Throwable { + final var spec = new HapiSpec(SPEC_NAME, ops); + doTargetSpec(spec, targetNetwork); + spec.setSpecStateObserver(sharedStates::add); + spec.execute(); + } + + public void teardown(@NonNull final HapiSpecOperation... ops) throws Throwable { + final var spec = new HapiSpec(SPEC_NAME, ops); + doTargetSpec(spec, targetNetwork); + spec.execute(); + } + + public List getSharedStates() { + return sharedStates; + } +} diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/junit/StreamDataListener.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/junit/support/StreamDataListener.java similarity index 95% rename from hedera-node/test-clients/src/main/java/com/hedera/services/bdd/junit/StreamDataListener.java rename to hedera-node/test-clients/src/main/java/com/hedera/services/bdd/junit/support/StreamDataListener.java index b7e460757082..88b90c6dc9bd 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/junit/StreamDataListener.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/junit/support/StreamDataListener.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.hedera.services.bdd.junit; +package com.hedera.services.bdd.junit.support; import com.hedera.services.stream.proto.RecordStreamItem; import com.hedera.services.stream.proto.TransactionSidecarRecord; diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/junit/validators/AccountExistenceValidator.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/junit/support/validators/AccountExistenceValidator.java similarity index 77% rename from hedera-node/test-clients/src/main/java/com/hedera/services/bdd/junit/validators/AccountExistenceValidator.java rename to hedera-node/test-clients/src/main/java/com/hedera/services/bdd/junit/support/validators/AccountExistenceValidator.java index 1173c84371f9..b3ed7072384f 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/junit/validators/AccountExistenceValidator.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/junit/support/validators/AccountExistenceValidator.java @@ -14,12 +14,10 @@ * limitations under the License. */ -package com.hedera.services.bdd.junit.validators; +package com.hedera.services.bdd.junit.support.validators; -import static com.hedera.services.bdd.junit.BalanceReconciliationValidator.streamOfItemsFrom; - -import com.hedera.services.bdd.junit.RecordStreamValidator; -import com.hedera.services.bdd.junit.RecordWithSidecars; +import com.hedera.services.bdd.junit.support.RecordStreamValidator; +import com.hedera.services.bdd.junit.support.RecordWithSidecars; import com.hedera.services.stream.proto.RecordStreamItem; import java.time.Instant; import java.util.List; @@ -39,12 +37,14 @@ public AccountExistenceValidator(final String name, final Instant consensusTimes @Override public void validateRecordsAndSidecars(final List recordFiles) { final var accountExists = new AtomicBoolean(); - streamOfItemsFrom(recordFiles).filter(this::isAtConsensusTime).forEach(item -> { - final var receipt = item.getRecord().getReceipt(); - if (receipt.hasAccountID()) { - accountExists.set(true); - } - }); + BalanceReconciliationValidator.streamOfItemsFrom(recordFiles) + .filter(this::isAtConsensusTime) + .forEach(item -> { + final var receipt = item.getRecord().getReceipt(); + if (receipt.hasAccountID()) { + accountExists.set(true); + } + }); if (!accountExists.get()) { Assertions.fail("Expected '" + name + "' to be created at " + consensusTimestamp + ", but it was not"); } diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/junit/validators/AccountNumTokenNum.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/junit/support/validators/AccountNumTokenNum.java similarity index 92% rename from hedera-node/test-clients/src/main/java/com/hedera/services/bdd/junit/validators/AccountNumTokenNum.java rename to hedera-node/test-clients/src/main/java/com/hedera/services/bdd/junit/support/validators/AccountNumTokenNum.java index 30d0e590f35c..decfa19fb5ae 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/junit/validators/AccountNumTokenNum.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/junit/support/validators/AccountNumTokenNum.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.hedera.services.bdd.junit.validators; +package com.hedera.services.bdd.junit.support.validators; /** * Contains a single account number and a single associated token number. diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/junit/BalanceReconciliationValidator.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/junit/support/validators/BalanceReconciliationValidator.java similarity index 84% rename from hedera-node/test-clients/src/main/java/com/hedera/services/bdd/junit/BalanceReconciliationValidator.java rename to hedera-node/test-clients/src/main/java/com/hedera/services/bdd/junit/support/validators/BalanceReconciliationValidator.java index a588b61f78c9..92eaf25f22d3 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/junit/BalanceReconciliationValidator.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/junit/support/validators/BalanceReconciliationValidator.java @@ -14,15 +14,16 @@ * limitations under the License. */ -package com.hedera.services.bdd.junit; +package com.hedera.services.bdd.junit.support.validators; -import static com.hedera.services.bdd.junit.HapiTestEngine.runSpec; import static com.hedera.services.bdd.junit.TestBase.concurrentExecutionOf; -import com.hedera.services.bdd.junit.utils.AccountClassifier; +import com.hedera.services.bdd.junit.TestBase; +import com.hedera.services.bdd.junit.support.RecordStreamValidator; +import com.hedera.services.bdd.junit.support.RecordWithSidecars; +import com.hedera.services.bdd.junit.support.validators.utils.AccountClassifier; import com.hedera.services.bdd.suites.records.BalanceValidation; import com.hedera.services.stream.proto.RecordStreamItem; -import java.lang.reflect.InvocationTargetException; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -56,16 +57,6 @@ public void validateRecordsAndSidecars(final List recordsWit concurrentExecutionOf(validationSpecs); } - @Override - public void validateRecordsAndSidecarsHapi( - final HapiTestEnv env, final List recordsWithSidecars) - throws InvocationTargetException, IllegalAccessException { - getExpectedBalanceFrom(recordsWithSidecars); - System.out.println("Expected balances: " + expectedBalances); - - runSpec(env, new BalanceValidation(expectedBalances, accountClassifier), "validateBalances"); - } - private void getExpectedBalanceFrom(final List recordsWithSidecars) { for (final var recordWithSidecars : recordsWithSidecars) { final var items = recordWithSidecars.recordFile().getRecordStreamItemsList(); diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/junit/validators/BlockNoValidator.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/junit/support/validators/BlockNoValidator.java similarity index 91% rename from hedera-node/test-clients/src/main/java/com/hedera/services/bdd/junit/validators/BlockNoValidator.java rename to hedera-node/test-clients/src/main/java/com/hedera/services/bdd/junit/support/validators/BlockNoValidator.java index 52b2ef94bea8..f0d97725973c 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/junit/validators/BlockNoValidator.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/junit/support/validators/BlockNoValidator.java @@ -14,9 +14,9 @@ * limitations under the License. */ -package com.hedera.services.bdd.junit.validators; +package com.hedera.services.bdd.junit.support.validators; -import com.hedera.services.bdd.junit.RecordStreamValidator; +import com.hedera.services.bdd.junit.support.RecordStreamValidator; import com.hedera.services.stream.proto.RecordStreamFile; import java.util.List; import org.junit.jupiter.api.Assertions; diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/junit/validators/ContractExistenceValidator.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/junit/support/validators/ContractExistenceValidator.java similarity index 77% rename from hedera-node/test-clients/src/main/java/com/hedera/services/bdd/junit/validators/ContractExistenceValidator.java rename to hedera-node/test-clients/src/main/java/com/hedera/services/bdd/junit/support/validators/ContractExistenceValidator.java index 530e17f3c400..ad09eba3de07 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/junit/validators/ContractExistenceValidator.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/junit/support/validators/ContractExistenceValidator.java @@ -14,12 +14,10 @@ * limitations under the License. */ -package com.hedera.services.bdd.junit.validators; +package com.hedera.services.bdd.junit.support.validators; -import static com.hedera.services.bdd.junit.BalanceReconciliationValidator.streamOfItemsFrom; - -import com.hedera.services.bdd.junit.RecordStreamValidator; -import com.hedera.services.bdd.junit.RecordWithSidecars; +import com.hedera.services.bdd.junit.support.RecordStreamValidator; +import com.hedera.services.bdd.junit.support.RecordWithSidecars; import com.hedera.services.stream.proto.RecordStreamItem; import java.time.Instant; import java.util.List; @@ -40,12 +38,14 @@ public ContractExistenceValidator(final String name, final Instant consensusTime public void validateRecordsAndSidecars(final List recordFiles) { final var contractExists = new AtomicBoolean(); - streamOfItemsFrom(recordFiles).filter(this::isAtConsensusTime).forEach(item -> { - final var receipt = item.getRecord().getReceipt(); - if (receipt.hasContractID()) { - contractExists.set(true); - } - }); + BalanceReconciliationValidator.streamOfItemsFrom(recordFiles) + .filter(this::isAtConsensusTime) + .forEach(item -> { + final var receipt = item.getRecord().getReceipt(); + if (receipt.hasContractID()) { + contractExists.set(true); + } + }); if (!contractExists.get()) { Assertions.fail("Expected '" + name + "' to be created at " + consensusTimestamp + ", but it was not"); } diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/junit/ExpiryRecordsValidator.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/junit/support/validators/ExpiryRecordsValidator.java similarity index 97% rename from hedera-node/test-clients/src/main/java/com/hedera/services/bdd/junit/ExpiryRecordsValidator.java rename to hedera-node/test-clients/src/main/java/com/hedera/services/bdd/junit/support/validators/ExpiryRecordsValidator.java index 6f60fcf79f5e..c8ba09f11a87 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/junit/ExpiryRecordsValidator.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/junit/support/validators/ExpiryRecordsValidator.java @@ -14,12 +14,14 @@ * limitations under the License. */ -package com.hedera.services.bdd.junit; +package com.hedera.services.bdd.junit.support.validators; import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.SUCCESS; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertTrue; +import com.hedera.services.bdd.junit.support.RecordStreamValidator; +import com.hedera.services.bdd.junit.support.RecordWithSidecars; import com.hederahashgraph.api.proto.java.TransactionRecord; import java.util.ArrayList; import java.util.List; diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/junit/validators/HgcaaLogValidator.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/junit/support/validators/HgcaaLogValidator.java similarity index 98% rename from hedera-node/test-clients/src/main/java/com/hedera/services/bdd/junit/validators/HgcaaLogValidator.java rename to hedera-node/test-clients/src/main/java/com/hedera/services/bdd/junit/support/validators/HgcaaLogValidator.java index d9e15279d320..d45b7f736dd1 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/junit/validators/HgcaaLogValidator.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/junit/support/validators/HgcaaLogValidator.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.hedera.services.bdd.junit.validators; +package com.hedera.services.bdd.junit.support.validators; import java.io.IOException; import java.nio.file.Files; diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/junit/validators/QueryLogValidator.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/junit/support/validators/QueryLogValidator.java similarity index 96% rename from hedera-node/test-clients/src/main/java/com/hedera/services/bdd/junit/validators/QueryLogValidator.java rename to hedera-node/test-clients/src/main/java/com/hedera/services/bdd/junit/support/validators/QueryLogValidator.java index 0ee63a2dfd74..2551e25c11a7 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/junit/validators/QueryLogValidator.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/junit/support/validators/QueryLogValidator.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.hedera.services.bdd.junit.validators; +package com.hedera.services.bdd.junit.support.validators; import java.io.IOException; import java.nio.file.Files; diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/junit/TokenReconciliationValidator.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/junit/support/validators/TokenReconciliationValidator.java similarity index 82% rename from hedera-node/test-clients/src/main/java/com/hedera/services/bdd/junit/TokenReconciliationValidator.java rename to hedera-node/test-clients/src/main/java/com/hedera/services/bdd/junit/support/validators/TokenReconciliationValidator.java index 923a2d2317d4..db91fd480e3d 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/junit/TokenReconciliationValidator.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/junit/support/validators/TokenReconciliationValidator.java @@ -14,15 +14,15 @@ * limitations under the License. */ -package com.hedera.services.bdd.junit; +package com.hedera.services.bdd.junit.support.validators; -import static com.hedera.services.bdd.junit.HapiTestEngine.runSpec; import static com.hedera.services.bdd.junit.TestBase.concurrentExecutionOf; -import com.hedera.services.bdd.junit.utils.AccountClassifier; -import com.hedera.services.bdd.junit.validators.AccountNumTokenNum; +import com.hedera.services.bdd.junit.TestBase; +import com.hedera.services.bdd.junit.support.RecordStreamValidator; +import com.hedera.services.bdd.junit.support.RecordWithSidecars; +import com.hedera.services.bdd.junit.support.validators.utils.AccountClassifier; import com.hedera.services.bdd.suites.records.TokenBalanceValidation; -import java.lang.reflect.InvocationTargetException; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -54,15 +54,6 @@ public void validateRecordsAndSidecars(final List recordsWit concurrentExecutionOf(validationSpecs); } - @Override - public void validateRecordsAndSidecarsHapi( - final HapiTestEnv env, final List recordsWithSidecars) - throws InvocationTargetException, IllegalAccessException { - getExpectedBalanceFrom(recordsWithSidecars); - - runSpec(env, new TokenBalanceValidation(expectedTokenBalances, accountClassifier), "validateTokenBalances"); - } - private void getExpectedBalanceFrom(final List recordsWithSidecars) { for (final var recordWithSidecars : recordsWithSidecars) { final var items = recordWithSidecars.recordFile().getRecordStreamItemsList(); diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/junit/TransactionBodyValidator.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/junit/support/validators/TransactionBodyValidator.java similarity index 59% rename from hedera-node/test-clients/src/main/java/com/hedera/services/bdd/junit/TransactionBodyValidator.java rename to hedera-node/test-clients/src/main/java/com/hedera/services/bdd/junit/support/validators/TransactionBodyValidator.java index d21ea70fa713..59523d647d75 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/junit/TransactionBodyValidator.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/junit/support/validators/TransactionBodyValidator.java @@ -14,15 +14,27 @@ * limitations under the License. */ -package com.hedera.services.bdd.junit; +package com.hedera.services.bdd.junit.support.validators; +import static com.hedera.node.app.hapi.utils.CommonUtils.functionOf; +import static com.hederahashgraph.api.proto.java.HederaFunctionality.NONE; +import static com.hederahashgraph.api.proto.java.HederaFunctionality.NodeStakeUpdate; +import static com.hederahashgraph.api.proto.java.TransactionBody.DataCase.NODE_STAKE_UPDATE; import static java.util.Objects.requireNonNull; import com.google.protobuf.InvalidProtocolBufferException; -import com.hedera.services.bdd.junit.utils.TransactionBodyClassifier; +import com.hedera.node.app.hapi.utils.CommonUtils; +import com.hedera.node.app.hapi.utils.exception.UnknownHederaFunctionality; +import com.hedera.services.bdd.junit.support.RecordStreamValidator; +import com.hedera.services.bdd.junit.support.RecordWithSidecars; import com.hedera.services.bdd.suites.records.TransactionBodyValidation; +import com.hedera.services.stream.proto.RecordStreamItem; +import com.hederahashgraph.api.proto.java.HederaFunctionality; +import com.hederahashgraph.api.proto.java.TransactionBody; import edu.umd.cs.findbugs.annotations.NonNull; +import java.util.HashSet; import java.util.List; +import java.util.Set; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; @@ -62,4 +74,30 @@ private void validateTransactionBody(final List recordsWithS } } } + + public static class TransactionBodyClassifier { + private final Set transactionType = new HashSet<>(); + + public void incorporate(@NonNull final RecordStreamItem item) throws InvalidProtocolBufferException { + requireNonNull(item); + var txnType = NONE; + TransactionBody txnBody = CommonUtils.extractTransactionBody(item.getTransaction()); + + try { + txnType = functionOf(txnBody); + } catch (UnknownHederaFunctionality ex) { + txnType = checkNodeStakeUpdate(txnBody); + } + transactionType.add(txnType); + } + + private HederaFunctionality checkNodeStakeUpdate(final TransactionBody txn) { + TransactionBody.DataCase dataCase = txn.getDataCase(); + return dataCase.equals(NODE_STAKE_UPDATE) ? NodeStakeUpdate : NONE; + } + + public boolean isInvalid() { + return transactionType.contains(NONE); + } + } } diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/junit/utils/AccountClassifier.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/junit/support/validators/utils/AccountClassifier.java similarity index 96% rename from hedera-node/test-clients/src/main/java/com/hedera/services/bdd/junit/utils/AccountClassifier.java rename to hedera-node/test-clients/src/main/java/com/hedera/services/bdd/junit/support/validators/utils/AccountClassifier.java index 37ac95366169..aff80cf29eb2 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/junit/utils/AccountClassifier.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/junit/support/validators/utils/AccountClassifier.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.hedera.services.bdd.junit.utils; +package com.hedera.services.bdd.junit.support.validators.utils; import com.google.protobuf.InvalidProtocolBufferException; import com.hedera.node.app.hapi.utils.CommonUtils; diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/junit/utils/TransactionBodyClassifier.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/junit/utils/TransactionBodyClassifier.java deleted file mode 100644 index d628bea26a0a..000000000000 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/junit/utils/TransactionBodyClassifier.java +++ /dev/null @@ -1,60 +0,0 @@ -/* - * Copyright (C) 2022-2024 Hedera Hashgraph, LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.hedera.services.bdd.junit.utils; - -import static com.hedera.node.app.hapi.utils.CommonUtils.functionOf; -import static com.hederahashgraph.api.proto.java.HederaFunctionality.NONE; -import static com.hederahashgraph.api.proto.java.HederaFunctionality.NodeStakeUpdate; -import static com.hederahashgraph.api.proto.java.TransactionBody.DataCase.NODE_STAKE_UPDATE; -import static java.util.Objects.requireNonNull; - -import com.google.protobuf.InvalidProtocolBufferException; -import com.hedera.node.app.hapi.utils.CommonUtils; -import com.hedera.node.app.hapi.utils.exception.UnknownHederaFunctionality; -import com.hedera.services.stream.proto.RecordStreamItem; -import com.hederahashgraph.api.proto.java.HederaFunctionality; -import com.hederahashgraph.api.proto.java.TransactionBody; -import com.hederahashgraph.api.proto.java.TransactionBody.DataCase; -import edu.umd.cs.findbugs.annotations.NonNull; -import java.util.HashSet; -import java.util.Set; - -public class TransactionBodyClassifier { - private final Set transactionType = new HashSet<>(); - - public void incorporate(@NonNull final RecordStreamItem item) throws InvalidProtocolBufferException { - requireNonNull(item); - var txnType = NONE; - TransactionBody txnBody = CommonUtils.extractTransactionBody(item.getTransaction()); - - try { - txnType = functionOf(txnBody); - } catch (UnknownHederaFunctionality ex) { - txnType = checkNodeStakeUpdate(txnBody); - } - transactionType.add(txnType); - } - - private HederaFunctionality checkNodeStakeUpdate(final TransactionBody txn) { - DataCase dataCase = txn.getDataCase(); - return dataCase.equals(NODE_STAKE_UPDATE) ? NodeStakeUpdate : NONE; - } - - public boolean isInvalid() { - return transactionType.contains(NONE); - } -} diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/HapiSpec.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/HapiSpec.java index 399c157cd8fe..90413a205323 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/HapiSpec.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/HapiSpec.java @@ -16,9 +16,7 @@ package com.hedera.services.bdd.spec; -import static com.hedera.services.bdd.junit.RecordStreamAccess.RECORD_STREAM_ACCESS; -import static com.hedera.services.bdd.spec.HapiPropertySource.asSources; -import static com.hedera.services.bdd.spec.HapiPropertySource.inPriorityOrder; +import static com.hedera.services.bdd.junit.support.RecordStreamAccess.RECORD_STREAM_ACCESS; import static com.hedera.services.bdd.spec.HapiSpec.CostSnapshotMode.COMPARE; import static com.hedera.services.bdd.spec.HapiSpec.CostSnapshotMode.TAKE; import static com.hedera.services.bdd.spec.HapiSpec.SpecStatus.ERROR; @@ -28,10 +26,12 @@ import static com.hedera.services.bdd.spec.HapiSpec.SpecStatus.PASSED_UNEXPECTEDLY; import static com.hedera.services.bdd.spec.HapiSpec.SpecStatus.PENDING; import static com.hedera.services.bdd.spec.HapiSpec.SpecStatus.RUNNING; +import static com.hedera.services.bdd.spec.HapiSpecSetup.setupFrom; import static com.hedera.services.bdd.spec.assertions.TransactionRecordAsserts.recordWith; import static com.hedera.services.bdd.spec.infrastructure.HapiApiClients.clientsFor; import static com.hedera.services.bdd.spec.queries.QueryVerbs.getScheduleInfo; import static com.hedera.services.bdd.spec.queries.QueryVerbs.getTxnRecord; +import static com.hedera.services.bdd.spec.transactions.TxnUtils.turnLoggingOff; import static com.hedera.services.bdd.spec.transactions.TxnVerbs.cryptoTransfer; import static com.hedera.services.bdd.spec.transactions.TxnVerbs.scheduleCreate; import static com.hedera.services.bdd.spec.transactions.TxnVerbs.scheduleSign; @@ -42,14 +42,17 @@ import static com.hedera.services.bdd.spec.utilops.UtilVerbs.overridingAllOf; import static com.hedera.services.bdd.spec.utilops.UtilVerbs.remembering; import static com.hedera.services.bdd.spec.utilops.streams.RecordAssertions.triggerAndCloseAtLeastOneFileIfNotInterrupted; +import static com.hedera.services.bdd.suites.HapiSuite.DEFAULT_CONTRACT_SENDER; import static com.hedera.services.bdd.suites.HapiSuite.DEFAULT_PAYER; import static com.hedera.services.bdd.suites.HapiSuite.ETH_SUFFIX; import static com.hedera.services.bdd.suites.HapiSuite.ONE_HBAR; +import static com.hedera.services.bdd.suites.HapiSuite.SECP_256K1_SOURCE_KEY; import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.INVALID_SIGNATURE; import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.NO_NEW_VALID_SIGNATURES; import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.OK; import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.SUCCESS; import static java.util.Collections.emptyList; +import static java.util.Objects.requireNonNull; import static java.util.concurrent.CompletableFuture.allOf; import static java.util.concurrent.CompletableFuture.runAsync; import static java.util.concurrent.TimeUnit.MILLISECONDS; @@ -59,12 +62,15 @@ import com.google.common.io.ByteSource; import com.google.common.io.CharSink; import com.google.common.io.Files; -import com.hedera.services.bdd.junit.HapiTestNode; +import com.hedera.services.bdd.junit.hedera.HederaNetwork; +import com.hedera.services.bdd.junit.hedera.HederaNode; +import com.hedera.services.bdd.junit.support.SpecManager; import com.hedera.services.bdd.spec.fees.FeeCalculator; import com.hedera.services.bdd.spec.fees.FeesAndRatesProvider; import com.hedera.services.bdd.spec.fees.Payment; import com.hedera.services.bdd.spec.infrastructure.HapiApiClients; import com.hedera.services.bdd.spec.infrastructure.HapiSpecRegistry; +import com.hedera.services.bdd.spec.infrastructure.SpecStateObserver; import com.hedera.services.bdd.spec.keys.KeyFactory; import com.hedera.services.bdd.spec.persistence.EntityManager; import com.hedera.services.bdd.spec.props.MapPropertySource; @@ -78,8 +84,6 @@ import com.hedera.services.bdd.spec.utilops.streams.RecordAssertions; import com.hedera.services.bdd.spec.utilops.streams.assertions.EventualRecordStreamAssertion; import com.hedera.services.bdd.suites.TargetNetworkType; -import com.hedera.services.stream.proto.AllAccountBalances; -import com.hedera.services.stream.proto.SingleAccountBalances; import com.hederahashgraph.api.proto.java.AccountAmount; import com.hederahashgraph.api.proto.java.AccountID; import com.hederahashgraph.api.proto.java.Key; @@ -88,7 +92,6 @@ import edu.umd.cs.findbugs.annotations.NonNull; import edu.umd.cs.findbugs.annotations.Nullable; import java.io.File; -import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.Writer; @@ -114,17 +117,30 @@ import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicLong; import java.util.concurrent.atomic.AtomicReference; -import java.util.function.Consumer; import java.util.function.Function; -import java.util.function.Supplier; import java.util.stream.IntStream; import java.util.stream.LongStream; import java.util.stream.Stream; -import org.apache.commons.io.FileUtils; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; +import org.junit.jupiter.api.DynamicTest; +import org.junit.jupiter.api.function.Executable; + +public class HapiSpec implements Runnable, Executable { + private static final String CI_CHECK_NAME_SYSTEM_PROPERTY = "ci.check.name"; + private static final String QUIET_MODE_SYSTEM_PROPERTY = "hapi.spec.quiet.mode"; + + /** + * The name of the DynamicTest that executes the HapiSpec as written, + * without modifications such as replacing ContractCall and ContractCreate + * operations with equivalent EthereumTransactions + */ + private static final String AS_WRITTEN_DISPLAY_NAME = "as written"; + + public static final ThreadLocal TARGET_NETWORK = new ThreadLocal<>(); + public static final ThreadLocal SPEC_MANAGER = new ThreadLocal<>(); + public static final ThreadLocal SPEC_NAME = new ThreadLocal<>(); -public class HapiSpec implements Runnable { private static final long FIRST_NODE_ACCOUNT_NUM = 3L; private static final int NUM_IN_USE_NODE_ACCOUNTS = 4; private static final TransferList DEFAULT_NODE_BALANCE_FUNDING = TransferList.newBuilder() @@ -210,19 +226,32 @@ public boolean isOnlySpecToRunInSuite() { HapiSpecOperation[] when; HapiSpecOperation[] then; AtomicInteger adhoc = new AtomicInteger(0); - AtomicInteger numLedgerOpsExecuted = new AtomicInteger(0); AtomicBoolean allOpsSubmitted = new AtomicBoolean(false); ThreadPoolExecutor finalizingExecutor; - List> ledgerOpCountCallbacks = new ArrayList<>(); CompletableFuture finalizingFuture; AtomicReference> finishingError = new AtomicReference<>(Optional.empty()); BlockingQueue pendingOps = new PriorityBlockingQueue<>(); EnumMap precheckStatusCounts = new EnumMap<>(ResponseCodeEnum.class); EnumMap finalizedStatusCounts = new EnumMap<>(ResponseCodeEnum.class); - /** These nodes can be used by some ops for controlling the nodes, such as restart, reconnect, etc. */ - List nodes = emptyList(); - List accountBalances = new ArrayList<>(); + /** + * If non-null, the network created for this JUnit5 LauncherSession; supports direct manipulation + * of the node lifecycle (stop, restart, wait for status, etc). + */ + @Nullable + private HederaNetwork targetNetwork; + /** + * If non-null, an observer to receive the final state of this spec's register and key factory + * after it has executed. + */ + @Nullable + private SpecStateObserver specStateObserver; + + @Nullable + private List sharedStates; + + boolean quietMode; + private final SnapshotMatchMode[] snapshotMatchModes; /** @@ -248,19 +277,6 @@ public int numPendingOps() { return pendingOps.size(); } - public int numLedgerOps() { - return numLedgerOpsExecuted.get(); - } - - public synchronized void addLedgerOpCountCallback(Consumer callback) { - ledgerOpCountCallbacks.add(callback); - } - - public void incrementNumLedgerOps() { - int newNumLedgerOps = numLedgerOpsExecuted.incrementAndGet(); - ledgerOpCountCallbacks.forEach(c -> c.accept(newNumLedgerOps)); - } - public TargetNetworkType targetNetworkType() { return targetNetworkType; } @@ -270,21 +286,8 @@ public HapiSpec setTargetNetworkType(TargetNetworkType targetNetworkType) { return this; } - public synchronized void saveSingleAccountBalances(SingleAccountBalances sab) { - accountBalances.add(sab); - } - - public void exportAccountBalances(Supplier dir) { - AllAccountBalances.Builder allAccountBalancesBuilder = - AllAccountBalances.newBuilder().addAllAllAccounts(accountBalances); - try (FileOutputStream fout = FileUtils.openOutputStream(new File(dir.get()))) { - allAccountBalancesBuilder.build().writeTo(fout); - } catch (IOException e) { - log.error(String.format("Could not export to '%s'!", dir), e); - return; - } - - log.info("Export {} account balances registered to file {}", accountBalances.size(), dir); + public void setSpecStateObserver(@NonNull final SpecStateObserver specStateObserver) { + this.specStateObserver = specStateObserver; } public void updatePrecheckCounts(ResponseCodeEnum finalStatus) { @@ -311,8 +314,9 @@ public String getName() { return name; } - public void appendToName(String postfix) { + public HapiSpec appendToName(String postfix) { this.name = this.name + postfix; + return this; } public String getSuitePrefix() { @@ -336,12 +340,20 @@ public HapiSpec setSuitePrefix(String suitePrefix) { return this; } - public void setNodes(List nodes) { - if (nodes != null) this.nodes = nodes; + public void setTargetNetwork(@NonNull final HederaNetwork targetNetwork) { + this.targetNetwork = requireNonNull(targetNetwork); + } + + public void setSharedStates(@NonNull final List sharedStates) { + this.sharedStates = sharedStates; } - public List getNodes() { - return nodes; + public HederaNetwork targetNetworkOrThrow() { + return requireNonNull(targetNetwork); + } + + public List getNetworkNodes() { + return requireNonNull(targetNetwork).nodes(); } public static boolean ok(HapiSpec spec) { @@ -352,6 +364,16 @@ public static boolean notOk(HapiSpec spec) { return !ok(spec); } + @Override + public void execute() throws Throwable { + // Only JUnit will use execute(), and in that case the target network must be set + requireNonNull(targetNetwork).waitForReady(); + run(); + if (failure != null) { + throw failure.cause; + } + } + @Override public void run() { if (!init()) { @@ -366,8 +388,13 @@ public void run() { if (!isEthereumAccountCreatedForSpec(this)) { ops.addAll(createEthereumAccountForSpec(this)); } + final var adminKey = this.registry().getKey(DEFAULT_CONTRACT_SENDER); ops.addAll(UtilVerbs.convertHapiCallsToEthereumCalls( - Stream.of(given, when, then).flatMap(Arrays::stream).toList())); + Stream.of(given, when, then).flatMap(Arrays::stream).toList(), + SECP_256K1_SOURCE_KEY, + adminKey, + hapiSetup.defaultCreateGas(), + this)); } try { @@ -385,6 +412,9 @@ public void run() { compareWithSnapshot(); } + if (specStateObserver != null) { + specStateObserver.observe(new SpecStateObserver.SpecState(hapiRegistry, keyFactory)); + } nullOutInfrastructure(); } @@ -446,6 +476,24 @@ public boolean tryReinitializingFees() { } private boolean init() { + hapiClients = clientsFor(hapiSetup); + try { + hapiRegistry = new HapiSpecRegistry(hapiSetup); + if (sharedStates != null) { + sharedStates.forEach(sharedState -> { + hapiRegistry.include(sharedState.registry()); + }); + } + keyFactory = new KeyFactory(hapiSetup, hapiRegistry); + txnFactory = new TxnFactory(hapiSetup, keyFactory); + FeesAndRatesProvider scheduleProvider = + new FeesAndRatesProvider(txnFactory, keyFactory, hapiSetup, hapiClients, hapiRegistry); + feeCalculator = new FeeCalculator(hapiSetup, scheduleProvider); + this.ratesProvider = scheduleProvider; + } catch (Throwable t) { + log.error("Initialization failed for spec '{}'!", name, t); + status = ERROR; + } if (!tryReinitializingFees()) { status = ERROR; return false; @@ -485,11 +533,15 @@ private void exec(List ops) { if (hapiSetup.requiresPersistentEntities()) { List creationOps = entities.requiredCreations(); if (!creationOps.isEmpty()) { - log.info("Inserting {} required creations to establish persistent entities.", creationOps.size()); + if (!quietMode) { + log.info("Inserting {} required creations to establish persistent entities.", creationOps.size()); + } ops = Stream.concat(creationOps.stream(), ops.stream()).toList(); } } - log.info("{} test suite started !", logPrefix()); + if (!quietMode) { + log.info("{} test suite started !", logPrefix()); + } status = RUNNING; if (hapiSetup.statusDeferredResolvesDoAsync()) { @@ -534,6 +586,9 @@ private void exec(List ops) { } snapshotOp = snapshotModeOp; } + if (quietMode) { + turnLoggingOff(op); + } Optional error = op.execFor(this); Failure asyncFailure = null; if (error.isPresent() || (asyncFailure = finishingError.get().orElse(null)) != null) { @@ -542,7 +597,9 @@ private void exec(List ops) { failure = error.map(t -> new Failure(t, failedOp.toString())).orElse(asyncFailure); break; } else { - log.info("'{}' finished initial execution of {}", name, op); + if (!quietMode) { + log.info("'{}' finished initial execution of {}", name, op); + } } } allOpsSubmitted.set(true); @@ -585,7 +642,9 @@ private void exec(List ops) { } tearDown(); - log.info("{}final status: {}!", logPrefix(), status); + if (!quietMode) { + log.info("{}final status: {}!", logPrefix(), status); + } if (hapiSetup.requiresPersistentEntities() && hapiSetup.updateManifestsForCreatedPersistentEntities()) { entities.updateCreatedEntityManifests(); @@ -722,7 +781,9 @@ private Optional checkRecordStream(@Nullable final List answer = Optional.empty(); // Keep submitting transactions to close record files (in almost every case, just // one file will need to be closed, since it's very rare to have a long-running spec) @@ -737,7 +798,9 @@ private Optional checkRecordStream(@Nullable final List Def.Sourced customizedHapiSpec( return (isOnly ? onlyHapiSpec(name, propertiesToPreserve, snapshotMatchModes) : hapiSpec(name, propertiesToPreserve, snapshotMatchModes)) - .withSetup(setupFrom(allSources)); + .withSetup(HapiSpecSetup.setupFrom(allSources)); }; } - private static HapiSpecSetup setupFrom(Object... objs) { - return new HapiSpecSetup(inPriorityOrder(asSources(objs))); - } - public static Def.Setup hapiSpec( String name, List propertiesToPreserve, @NonNull final SnapshotMatchMode... snapshotMatchModes) { - return setup -> given -> when -> - then -> new HapiSpec(name, false, setup, given, when, then, propertiesToPreserve, snapshotMatchModes); + return setup -> given -> when -> then -> Stream.of(DynamicTest.dynamicTest( + name + " " + AS_WRITTEN_DISPLAY_NAME, + targeted(new HapiSpec( + name, false, setup, given, when, then, propertiesToPreserve, snapshotMatchModes)))); } public static Def.Setup onlyHapiSpec( final String name, final List propertiesToPreserve, @NonNull final SnapshotMatchMode... snapshotMatchModes) { - return setup -> given -> when -> - then -> new HapiSpec(name, true, setup, given, when, then, propertiesToPreserve, snapshotMatchModes); - } - - private HapiSpec( + return setup -> given -> when -> then -> Stream.of(DynamicTest.dynamicTest( + AS_WRITTEN_DISPLAY_NAME, + targeted( + new HapiSpec(name, true, setup, given, when, then, propertiesToPreserve, snapshotMatchModes)))); + } + + public static Stream hapiTest(@NonNull final HapiSpecOperation... ops) { + return Stream.of(DynamicTest.dynamicTest( + AS_WRITTEN_DISPLAY_NAME, + targeted(new HapiSpec( + SPEC_NAME.get(), + false, + HapiSpecSetup.setupFrom(HapiSpecSetup.getDefaultPropertySource()), + new HapiSpecOperation[0], + new HapiSpecOperation[0], + ops, + List.of(), + new SnapshotMatchMode[0])))); + } + + private static HapiSpec targeted(@NonNull final HapiSpec spec) { + final var targetNetwork = TARGET_NETWORK.get(); + if (targetNetwork != null) { + log.info("Targeting network '{}' for spec '{}'", targetNetwork.name(), spec.name); + doTargetSpec(spec, targetNetwork); + } + Optional.ofNullable(SPEC_MANAGER.get()) + .map(SpecManager::getSharedStates) + .ifPresent(spec::setSharedStates); + return spec; + } + + public static void doTargetSpec(@NonNull final HapiSpec spec, @NonNull final HederaNetwork targetNetwork) { + spec.setTargetNetwork(targetNetwork); + spec.setTargetNetworkType(targetNetwork.type()); + final var specNodes = targetNetwork.nodes().stream() + .map(HederaNode::hapiSpecIdentifier) + .collect(joining(",")); + spec.addOverrideProperties(Map.of("nodes", specNodes)); + } + + public HapiSpec(String name, HapiSpecOperation[] ops) { + this( + name, + false, + setupFrom(HapiSpecSetup.getDefaultPropertySource()), + new HapiSpecOperation[0], + new HapiSpecOperation[0], + ops, + List.of(), + new SnapshotMatchMode[0]); + } + + // too many parameters + @SuppressWarnings("java:S107") + public HapiSpec( String name, boolean onlySpecToRunInSuite, HapiSpecSetup hapiSetup, @@ -1014,19 +1127,9 @@ private HapiSpec( this.then = then; this.onlySpecToRunInSuite = onlySpecToRunInSuite; this.propertiesToPreserve = propertiesToPreserve; - hapiClients = clientsFor(hapiSetup); - try { - hapiRegistry = new HapiSpecRegistry(hapiSetup); - keyFactory = new KeyFactory(hapiSetup, hapiRegistry); - txnFactory = new TxnFactory(hapiSetup, keyFactory); - FeesAndRatesProvider scheduleProvider = - new FeesAndRatesProvider(txnFactory, keyFactory, hapiSetup, hapiClients, hapiRegistry); - feeCalculator = new FeeCalculator(hapiSetup, scheduleProvider); - this.ratesProvider = scheduleProvider; - } catch (Throwable t) { - log.error("Initialization failed for spec '{}'!", name, t); - status = ERROR; - } + final var quiet = System.getProperty(QUIET_MODE_SYSTEM_PROPERTY); + final var isCiCheck = System.getProperty(CI_CHECK_NAME_SYSTEM_PROPERTY) != null; + this.quietMode = "true".equalsIgnoreCase(quiet) || (!"false".equalsIgnoreCase(quiet) && isCiCheck); } interface Def { @@ -1057,7 +1160,7 @@ interface When { @FunctionalInterface interface Then { - HapiSpec then(HapiSpecOperation... ops); + Stream then(HapiSpecOperation... ops); } } diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/HapiSpecOperation.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/HapiSpecOperation.java index c4f22df1d753..a33b54fab799 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/HapiSpecOperation.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/HapiSpecOperation.java @@ -39,9 +39,7 @@ import com.hedera.services.bdd.spec.keys.SigMapGenerator; import com.hedera.services.bdd.spec.props.NodeConnectInfo; import com.hedera.services.bdd.spec.queries.meta.HapiGetTxnRecord; -import com.hedera.services.bdd.spec.stats.OpObs; import com.hedera.services.bdd.spec.transactions.TxnUtils; -import com.hedera.services.bdd.spec.utilops.UtilOp; import com.hedera.services.bdd.spec.utilops.mod.BodyMutation; import com.hedera.services.bdd.suites.HapiSuite; import com.hederahashgraph.api.proto.java.AccountID; @@ -98,7 +96,6 @@ public abstract class HapiSpecOperation { protected boolean omitTxnId = false; protected boolean loggingOff = false; protected boolean yahcliLogger = false; - protected boolean suppressStats = false; protected boolean omitNodeAccount = false; protected boolean verboseLoggingOn = false; protected boolean shouldRegisterTxn = false; @@ -129,7 +126,6 @@ public abstract class HapiSpecOperation { protected Map overrides = Collections.EMPTY_MAP; protected Optional fee = Optional.empty(); - protected Optional submitDelay = Optional.empty(); protected Optional validDurationSecs = Optional.empty(); protected Optional customTxnId = Optional.empty(); protected Optional memo = Optional.empty(); @@ -239,15 +235,10 @@ private AccountID randomNodeFrom(final HapiSpec spec) { } public Optional execFor(final HapiSpec spec) { - pauseIfRequested(); configureProtoStructureFor(spec); try { final boolean hasCompleteLifecycle = submitOp(spec); - if (!(this instanceof UtilOp)) { - spec.incrementNumLedgerOps(); - } - if (shouldRegisterTxn) { registerTxnSubmitted(spec); } @@ -280,15 +271,6 @@ public Optional execFor(final HapiSpec spec) { return Optional.empty(); } - private void pauseIfRequested() { - submitDelay.ifPresent(l -> { - try { - Thread.sleep(l); - } catch (final InterruptedException ignore) { - } - }); - } - private void registerTxnSubmitted(final HapiSpec spec) throws Throwable { if (txnSubmitted != Transaction.getDefaultInstance()) { spec.registry().saveBytes(txnName, txnSubmitted.toByteString()); @@ -368,7 +350,8 @@ protected Transaction finalizedTxn( .andThen(opDef); setKeyControlOverrides(spec); - final List keys = signersToUseFor(spec); + List keys = signersToUseFor(spec); + final Transaction.Builder builder = spec.txns().getReadyToSign(netDef, spec, bodyMutation); final Transaction provisional = getSigned(spec, builder, keys); if (fee.isPresent()) { @@ -456,7 +439,7 @@ public Map setKeyControlOverrides(final HapiSpec spec) { public List signersToUseFor(final HapiSpec spec) { final List active = signers.orElse(defaultSigners()).stream() .map(f -> f.apply(spec)) - .filter(k -> k != Key.getDefaultInstance()) + .filter(k -> k != null && k != Key.getDefaultInstance()) .collect(toList()); if (!signers.isPresent()) { active.addAll(variableDefaultSigners().apply(spec)); @@ -484,7 +467,6 @@ protected void lookupSubmissionRecord(final HapiSpec spec) throws Throwable { final HapiGetTxnRecord subOp = getTxnRecord(extractTxnId(txnSubmitted)) .noLogging() .assertingNothing() - .suppressStats(true) .nodePayment(spec.setup().defaultNodePaymentTinyBars()); final Optional error = subOp.execFor(spec); if (error.isPresent()) { @@ -512,12 +494,6 @@ public Optional getPayer() { return payer; } - protected void considerRecording(final HapiSpec spec, final OpObs obs) { - if (!suppressStats) { - spec.registry().record(obs); - } - } - protected ByteString rationalize(final String expectedLedgerId) { final var hex = expectedLedgerId.substring(2); final var bytes = HexFormat.of().parseHex(hex); diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/HapiSpecSetup.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/HapiSpecSetup.java index 21e7cbd47e9f..67a314faaa2f 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/HapiSpecSetup.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/HapiSpecSetup.java @@ -82,6 +82,10 @@ public static HapiSpecSetup getDefaultInstance() { private HapiPropertySource props; + public static HapiSpecSetup setupFrom(Object... objs) { + return new HapiSpecSetup(inPriorityOrder(asSources(objs))); + } + public enum NodeSelection { FIXED, RANDOM @@ -420,7 +424,7 @@ public String exchangeRatesControlName() { return props.get("exchange.rates.controlAccount.name"); } - public HapiSpec.SpecStatus expectedFinalStatus() { + final HapiSpec.SpecStatus expectedFinalStatus() { return props.getSpecStatus("expected.final.status"); } diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/annotations/LeakyFeeSchedule.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/annotations/LeakyFeeSchedule.java deleted file mode 100644 index 4fd22130c940..000000000000 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/annotations/LeakyFeeSchedule.java +++ /dev/null @@ -1,33 +0,0 @@ -/* - * Copyright (C) 2022-2024 Hedera Hashgraph, LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.hedera.services.bdd.spec.annotations; - -/** - * Used to annotate a {@link com.hedera.services.bdd.spec.HapiSpec} that changes the fee schedule - * system file {@code 0.0.111}. Because this file is larger than 6kb (the maximum size of a - * transaction), it cannot be changed atomically. Instead, it is changed in parts, beginning with a - * {@code fileUpdate} and followed by {@code fileAppend} operations. - * - *

    This means that any concurrent spec that downloads the fee schedule file will see it in an - * inconsistent state, and will fail because it cannot compute fees correctly. (For example, if the - * file is 10kb, and only the first 6kb are updated, then the second 4kb will be missing and the - * file will be invalid.) - * - *

    Therefore, any spec that changes the fee schedule file should be annotated with this - * annotation. - */ -public @interface LeakyFeeSchedule {} diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/assertions/matchers/MatcherUtils.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/assertions/matchers/MatcherUtils.java index ea1e896afb1c..f02404023e2b 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/assertions/matchers/MatcherUtils.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/assertions/matchers/MatcherUtils.java @@ -44,8 +44,8 @@ public static FieldByFieldMatcher withEqualFields(final T expected, final * @param expectedGas the expected gas * @return {@link GasMatcher} checking for equality within a range of 32 units */ - public static GasMatcher within32Units(final Long expectedGas) { - return new GasMatcher(expectedGas, false, 32L); + public static GasMatcher within64Units(final Long expectedGas) { + return new GasMatcher(expectedGas, false, 64L); } /** diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/assertions/matchers/TransactionSidecarRecordMatcher.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/assertions/matchers/TransactionSidecarRecordMatcher.java index 66d57ae25839..c6cc0a507a3e 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/assertions/matchers/TransactionSidecarRecordMatcher.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/assertions/matchers/TransactionSidecarRecordMatcher.java @@ -18,7 +18,7 @@ import static com.hedera.services.bdd.spec.assertions.matchers.MatcherUtils.getMismatchedItems; import static com.hedera.services.bdd.spec.assertions.matchers.MatcherUtils.withEqualFields; -import static com.hedera.services.bdd.spec.assertions.matchers.MatcherUtils.within32Units; +import static com.hedera.services.bdd.spec.assertions.matchers.MatcherUtils.within64Units; import com.hedera.services.stream.proto.ContractAction; import com.hedera.services.stream.proto.ContractActions; @@ -78,8 +78,8 @@ public final class TransactionSidecarRecordMatcher extends TypeSafeDiagnosingMat final Class stopClass = action.getClass().getSuperclass(); return withEqualFields(action, stopClass) .withCustomMatchersForFields(Map.of( - "gas", within32Units(action.getGas()), - "gasUsed", within32Units(action.getGasUsed()))); + "gas", within64Units(action.getGas()), + "gasUsed", within64Units(action.getGasUsed()))); }; /** diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/infrastructure/HapiSpecRegistry.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/infrastructure/HapiSpecRegistry.java index de5f58ce5ca1..29494b58a778 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/infrastructure/HapiSpecRegistry.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/infrastructure/HapiSpecRegistry.java @@ -34,8 +34,6 @@ import com.hedera.services.bdd.spec.infrastructure.meta.ActionableContractCall; import com.hedera.services.bdd.spec.infrastructure.meta.ActionableContractCallLocal; import com.hedera.services.bdd.spec.infrastructure.meta.SupportedContract; -import com.hedera.services.bdd.spec.stats.OpObs; -import com.hedera.services.bdd.spec.stats.ThroughputObs; import com.hedera.services.bdd.suites.HapiSuite; import com.hederahashgraph.api.proto.java.AccountID; import com.hederahashgraph.api.proto.java.ConsensusCreateTopicTransactionBody; @@ -64,16 +62,10 @@ import java.util.Map; import java.util.Optional; import java.util.function.Function; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; public class HapiSpecRegistry { - static final Logger log = LogManager.getLogger(HapiSpecRegistry.class); - private final Map registry = new HashMap<>(); private final HapiSpecSetup setup; - private final List obs = new ArrayList<>(); - private final List throughputObs = new ArrayList<>(); private Map> listenersByType = new HashMap<>(); private static final Integer ZERO = 0; @@ -145,6 +137,10 @@ public HapiSpecRegistry(HapiSpecSetup setup) throws Exception { saveKey(HapiSuite.NONSENSE_KEY, nonsenseKey()); } + public void include(@NonNull final HapiSpecRegistry that) { + this.registry.putAll(that.registry); + } + private Key nonsenseKey() { return Key.getDefaultInstance(); } @@ -164,27 +160,6 @@ public void register(RegistryChangeListener listener) { listenersByType.computeIfAbsent(type, ignore -> new ArrayList<>()).add(listener); } - public synchronized void record(OpObs stat) { - obs.add(stat); - } - - public List stats() { - return obs; - } - - public void saveThroughputObs(ThroughputObs obs) { - put(obs.getName(), obs); - throughputObs.add(obs); - } - - public ThroughputObs getThroughputObs(String name) { - return get(name, ThroughputObs.class); - } - - public List throughputObs() { - return throughputObs; - } - public void saveContractChoice(String name, SupportedContract choice) { put(name, choice); } @@ -364,6 +339,18 @@ public void saveName(String token, String name) { put(token + "Name", name, String.class); } + public void saveEVMAddress(String name, String address) { + put(name + "-EVMAddress", address, String.class); + } + + public String getEVMAddress(String name) { + return get(name + "-EVMAddress", String.class); + } + + public boolean hasEVMAddress(String name) { + return has(name + "-EVMAddress", String.class); + } + public void saveMemo(String entity, String memo) { put(entity + "Memo", memo, String.class); } @@ -913,6 +900,10 @@ public List stringValues() { .collect(toList()); } + public void forgetMetadataKey(String name) { + remove(name + "Metadata", Key.class); + } + public void saveMetadataKey(String name, Key metadataKey) { put(name + "Metadata", metadataKey, Key.class); } diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/infrastructure/OpProvider.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/infrastructure/OpProvider.java index b9b8170e5ab8..0ddee02970f0 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/infrastructure/OpProvider.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/infrastructure/OpProvider.java @@ -55,7 +55,13 @@ public interface OpProvider { }; ResponseCodeEnum[] STANDARD_PERMISSIBLE_OUTCOMES = { - SUCCESS, LIVE_HASH_NOT_FOUND, INSUFFICIENT_PAYER_BALANCE, UNKNOWN, INSUFFICIENT_TX_FEE, INVALID_SIGNATURE + SUCCESS, + LIVE_HASH_NOT_FOUND, + INSUFFICIENT_PAYER_BALANCE, + UNKNOWN, + INSUFFICIENT_TX_FEE, + INVALID_SIGNATURE, + PAYER_ACCOUNT_DELETED }; default List suggestedInitializers() { diff --git a/platform-sdk/swirlds-common/src/main/java/com/swirlds/common/io/OptionalSelfSerializable.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/infrastructure/SpecStateObserver.java similarity index 60% rename from platform-sdk/swirlds-common/src/main/java/com/swirlds/common/io/OptionalSelfSerializable.java rename to hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/infrastructure/SpecStateObserver.java index 04f32936d796..95cbff49ecff 100644 --- a/platform-sdk/swirlds-common/src/main/java/com/swirlds/common/io/OptionalSelfSerializable.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/infrastructure/SpecStateObserver.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2020-2024 Hedera Hashgraph, LLC + * Copyright (C) 2024 Hedera Hashgraph, LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,11 +14,13 @@ * limitations under the License. */ -package com.swirlds.common.io; +package com.hedera.services.bdd.spec.infrastructure; -import com.swirlds.common.io.streams.SerializableDataOutputStream; -import java.io.IOException; +import com.hedera.services.bdd.spec.keys.KeyFactory; +import edu.umd.cs.findbugs.annotations.NonNull; -public interface OptionalSelfSerializable> extends SelfSerializable { - void serialize(SerializableDataOutputStream out, E option) throws IOException; +public interface SpecStateObserver { + record SpecState(@NonNull HapiSpecRegistry registry, @NonNull KeyFactory keyFactory) {} + + void observe(@NonNull SpecState specState); } diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/infrastructure/providers/ops/crypto/EthereumTransferToRandomEVMAddress.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/infrastructure/providers/ops/crypto/EthereumTransferToRandomEVMAddress.java index e6ea9edf5e40..8e84fd9ba5b2 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/infrastructure/providers/ops/crypto/EthereumTransferToRandomEVMAddress.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/infrastructure/providers/ops/crypto/EthereumTransferToRandomEVMAddress.java @@ -70,7 +70,7 @@ private HapiEthereumCall generateLazyCreateEthereumTransaction(String addressRec // Since the receiver _could_ have receiverSigRequired=true (c.f. the // InitialAccountIdentifiers.customize() method), INVALID_SIGNATURE is a valid // response code - .hasKnownStatusFrom(SUCCESS, INVALID_SIGNATURE); + .hasKnownStatusFrom(SUCCESS, INVALID_SIGNATURE, WRONG_NONCE); incrementNonce(); diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/infrastructure/providers/ops/hollow/RandomTokenHollowAccount.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/infrastructure/providers/ops/hollow/RandomTokenHollowAccount.java index ed70947af595..a72dd1abdc44 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/infrastructure/providers/ops/hollow/RandomTokenHollowAccount.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/infrastructure/providers/ops/hollow/RandomTokenHollowAccount.java @@ -47,7 +47,6 @@ public Optional get() { int id = opNo.getAndIncrement(); HapiTokenCreate op = tokenCreate(my("token" + id)) - .advertisingCreation() .hasPrecheckFrom(STANDARD_PERMISSIBLE_PRECHECKS) .hasKnownStatusFrom(INVALID_SIGNATURE) .signedBy(signers); diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/infrastructure/providers/ops/token/RandomToken.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/infrastructure/providers/ops/token/RandomToken.java index 0ece28fe4ef4..3ab25b26df8c 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/infrastructure/providers/ops/token/RandomToken.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/infrastructure/providers/ops/token/RandomToken.java @@ -86,7 +86,6 @@ public Optional get() { int id = opNo.getAndIncrement(); HapiTokenCreate op = tokenCreate(my("token" + id)) - .advertisingCreation() .hasPrecheckFrom(STANDARD_PERMISSIBLE_PRECHECKS) .hasKnownStatusFrom(permissibleOutcomes); diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/infrastructure/providers/ops/token/RandomTokenAccountWipe.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/infrastructure/providers/ops/token/RandomTokenAccountWipe.java index 4786b9da7a0f..38cad6af9a10 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/infrastructure/providers/ops/token/RandomTokenAccountWipe.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/infrastructure/providers/ops/token/RandomTokenAccountWipe.java @@ -43,7 +43,6 @@ public RandomTokenAccountWipe( this.customOutcomes = customOutcomes; } - private final ResponseCodeEnum[] permissiblePrechecks = standardPrechecksAnd(TOKEN_HAS_NO_WIPE_KEY); private final ResponseCodeEnum[] permissibleOutcomes = standardOutcomesAnd( TOKEN_WAS_DELETED, ACCOUNT_FROZEN_FOR_TOKEN, diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/infrastructure/providers/ops/token/RandomTokenDissociation.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/infrastructure/providers/ops/token/RandomTokenDissociation.java index 7d53127e0675..b82d6e86c5ac 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/infrastructure/providers/ops/token/RandomTokenDissociation.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/infrastructure/providers/ops/token/RandomTokenDissociation.java @@ -19,6 +19,7 @@ import static com.hedera.services.bdd.spec.transactions.TxnVerbs.tokenDissociate; import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.ACCOUNT_FROZEN_FOR_TOKEN; import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.ACCOUNT_IS_TREASURY; +import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.ACCOUNT_KYC_NOT_GRANTED_FOR_TOKEN; import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.TOKEN_NOT_ASSOCIATED_TO_ACCOUNT; import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.TOKEN_WAS_DELETED; import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.TRANSACTION_REQUIRES_ZERO_TOKEN_BALANCES; @@ -47,6 +48,7 @@ public RandomTokenDissociation( ACCOUNT_IS_TREASURY, ACCOUNT_FROZEN_FOR_TOKEN, TOKEN_NOT_ASSOCIATED_TO_ACCOUNT, + ACCOUNT_KYC_NOT_GRANTED_FOR_TOKEN, TRANSACTION_REQUIRES_ZERO_TOKEN_BALANCES); @Override diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/infrastructure/providers/ops/token/RandomTokenKycRevoke.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/infrastructure/providers/ops/token/RandomTokenKycRevoke.java index 4257183ff089..30d29c443e1f 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/infrastructure/providers/ops/token/RandomTokenKycRevoke.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/infrastructure/providers/ops/token/RandomTokenKycRevoke.java @@ -19,6 +19,7 @@ import static com.hedera.services.bdd.spec.infrastructure.providers.ops.token.RandomTokenDissociation.explicit; import static com.hedera.services.bdd.spec.transactions.TxnVerbs.revokeTokenKyc; import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.ACCOUNT_FROZEN_FOR_TOKEN; +import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.ACCOUNT_KYC_NOT_GRANTED_FOR_TOKEN; import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.TOKEN_HAS_NO_KYC_KEY; import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.TOKEN_NOT_ASSOCIATED_TO_ACCOUNT; import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.TOKEN_WAS_DELETED; @@ -34,7 +35,11 @@ public class RandomTokenKycRevoke implements OpProvider { private final RegistrySourcedNameProvider tokenRels; private final ResponseCodeEnum[] customOutcomes; private final ResponseCodeEnum[] permissibleOutcomes = standardOutcomesAnd( - TOKEN_WAS_DELETED, TOKEN_HAS_NO_KYC_KEY, TOKEN_NOT_ASSOCIATED_TO_ACCOUNT, ACCOUNT_FROZEN_FOR_TOKEN); + TOKEN_WAS_DELETED, + TOKEN_HAS_NO_KYC_KEY, + TOKEN_NOT_ASSOCIATED_TO_ACCOUNT, + ACCOUNT_FROZEN_FOR_TOKEN, + ACCOUNT_KYC_NOT_GRANTED_FOR_TOKEN); public RandomTokenKycRevoke( RegistrySourcedNameProvider tokenRels, ResponseCodeEnum[] customOutcomes) { diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/infrastructure/providers/ops/token/RandomTokenMint.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/infrastructure/providers/ops/token/RandomTokenMint.java index 0d2bd4889eca..c1696cf5a275 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/infrastructure/providers/ops/token/RandomTokenMint.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/infrastructure/providers/ops/token/RandomTokenMint.java @@ -19,6 +19,7 @@ import static com.hedera.services.bdd.spec.infrastructure.providers.ops.token.RandomToken.DEFAULT_MAX_SUPPLY; import static com.hedera.services.bdd.spec.infrastructure.providers.ops.token.RandomTokenDissociation.explicit; import static com.hedera.services.bdd.spec.transactions.TxnVerbs.mintToken; +import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.ACCOUNT_KYC_NOT_GRANTED_FOR_TOKEN; import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.INVALID_TOKEN_MINT_AMOUNT; import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.TOKEN_HAS_NO_SUPPLY_KEY; import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.TOKEN_WAS_DELETED; @@ -33,8 +34,8 @@ public class RandomTokenMint implements OpProvider { private final RegistrySourcedNameProvider tokenRels; - private final ResponseCodeEnum[] permissibleOutcomes = - standardOutcomesAnd(TOKEN_WAS_DELETED, TOKEN_HAS_NO_SUPPLY_KEY, INVALID_TOKEN_MINT_AMOUNT); + private final ResponseCodeEnum[] permissibleOutcomes = standardOutcomesAnd( + TOKEN_WAS_DELETED, TOKEN_HAS_NO_SUPPLY_KEY, INVALID_TOKEN_MINT_AMOUNT, ACCOUNT_KYC_NOT_GRANTED_FOR_TOKEN); private final ResponseCodeEnum[] customOutcomes; public RandomTokenMint( diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/infrastructure/providers/ops/token/RandomTokenTransfer.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/infrastructure/providers/ops/token/RandomTokenTransfer.java index 8e19e206674b..4b7054d2c6e5 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/infrastructure/providers/ops/token/RandomTokenTransfer.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/infrastructure/providers/ops/token/RandomTokenTransfer.java @@ -73,8 +73,7 @@ public Optional get() { op = cryptoTransfer(moving(1, token).between(ignore -> rel.getLeft(), spec -> spec.registry() .getTreasury(token))) .hasPrecheckFrom(plus(STANDARD_PERMISSIBLE_PRECHECKS, customOutcomes)) - .hasKnownStatusFrom(plus(permissibleOutcomes, customOutcomes)) - .showingResolvedStatus(); + .hasKnownStatusFrom(plus(permissibleOutcomes, customOutcomes)); } return Optional.of(op); diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/infrastructure/providers/ops/token/RandomTokenUnfreeze.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/infrastructure/providers/ops/token/RandomTokenUnfreeze.java index 0a56f7213c96..64a269fc1c0a 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/infrastructure/providers/ops/token/RandomTokenUnfreeze.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/infrastructure/providers/ops/token/RandomTokenUnfreeze.java @@ -18,9 +18,9 @@ import static com.hedera.services.bdd.spec.infrastructure.providers.ops.token.RandomTokenDissociation.explicit; import static com.hedera.services.bdd.spec.transactions.TxnVerbs.tokenUnfreeze; -import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.INVALID_SIGNATURE; import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.TOKEN_HAS_NO_FREEZE_KEY; import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.TOKEN_NOT_ASSOCIATED_TO_ACCOUNT; +import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.TOKEN_WAS_DELETED; import com.hedera.services.bdd.spec.HapiSpecOperation; import com.hedera.services.bdd.spec.infrastructure.OpProvider; @@ -33,9 +33,8 @@ public class RandomTokenUnfreeze implements OpProvider { private final RegistrySourcedNameProvider tokenRels; private final ResponseCodeEnum[] permissibleOutcomes = - standardOutcomesAnd(TOKEN_HAS_NO_FREEZE_KEY, TOKEN_NOT_ASSOCIATED_TO_ACCOUNT); + standardOutcomesAnd(TOKEN_HAS_NO_FREEZE_KEY, TOKEN_NOT_ASSOCIATED_TO_ACCOUNT, TOKEN_WAS_DELETED); private final ResponseCodeEnum[] customOutcomes; - private final ResponseCodeEnum[] customPrechecks = new ResponseCodeEnum[] {INVALID_SIGNATURE}; public RandomTokenUnfreeze( RegistrySourcedNameProvider tokenRels, ResponseCodeEnum[] customOutcomes) { diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/queries/HapiQueryOp.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/queries/HapiQueryOp.java index de62b9f60643..24d99542c9cc 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/queries/HapiQueryOp.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/queries/HapiQueryOp.java @@ -40,7 +40,6 @@ import com.hedera.services.bdd.spec.fees.Payment; import com.hedera.services.bdd.spec.keys.ControlForKey; import com.hedera.services.bdd.spec.keys.SigMapGenerator; -import com.hedera.services.bdd.spec.stats.QueryObs; import com.hedera.services.bdd.spec.transactions.crypto.HapiCryptoTransfer; import com.hedera.services.bdd.spec.utilops.mod.QueryMutation; import com.hederahashgraph.api.proto.java.CryptoTransferTransactionBody; @@ -50,7 +49,6 @@ import com.hederahashgraph.api.proto.java.Query; import com.hederahashgraph.api.proto.java.Response; import com.hederahashgraph.api.proto.java.ResponseCodeEnum; -import com.hederahashgraph.api.proto.java.ResponseType; import com.hederahashgraph.api.proto.java.Transaction; import com.hederahashgraph.api.proto.java.TransactionBody; import com.hederahashgraph.api.proto.java.TransactionReceipt; @@ -110,7 +108,16 @@ private ResponseCodeEnum expectedAnswerOnlyPrecheck() { protected abstract boolean needsPayment(); + /** + * Returns the modified version of the query in the context of the given spec, if a mutation + * is present; otherwise, returns the query as is. + * + * @param query the query to be modified + * @param spec the spec in which the query is to be modified + * @return the modified query + */ protected Query maybeModified(@NonNull final Query query, @NonNull final HapiSpec spec) { + // Save the unmodified version of the query this.query = query; return queryMutation != null ? queryMutation.apply(query, spec) : query; } @@ -186,7 +193,7 @@ protected boolean submitOp(HapiSpec spec) throws Throwable { String message = String.format("%sPaying for %s with %s", spec.logPrefix(), this, txnToString(payment)); log.info(message); } - timedSubmitWith(spec, payment); + submitWith(spec, payment); actualPrecheck = reflectForPrecheck(response); if (answerOnlyRetryPrechecks.isPresent() @@ -227,21 +234,6 @@ && isWithInRetryLimit(retryCount)) { return true; } - private void timedSubmitWith(HapiSpec spec, Transaction payment) throws Throwable { - if (suppressStats) { - submitWith(spec, payment); - } else { - long before = System.currentTimeMillis(); - submitWith(spec, payment); - long after = System.currentTimeMillis(); - - QueryObs stats = new QueryObs(ResponseType.ANSWER_ONLY, type()); - stats.setAccepted(reflectForPrecheck(response) == OK); - stats.setResponseLatency(after - before); - considerRecording(spec, stats); - } - } - @Override protected long feeFor(HapiSpec spec, Transaction txn, int numPayerKeys) throws Throwable { return spec.fees() @@ -271,13 +263,10 @@ private Transaction fittedPayment(HapiSpec spec) throws Throwable { "%sPaying for COST_ANSWER of %s with %s", spec.logPrefix(), this, txnToString(payment)); log.info(message); } - long realNodePayment = timedCostLookupWith(spec, payment); + long realNodePayment = lookupCostWith(spec, payment); if (recordsNodePayment) { spec.registry().saveAmount(nodePaymentName, realNodePayment); } - if (!suppressStats) { - spec.incrementNumLedgerOps(); - } if (expectedCostAnswerPrecheck() != OK) { return null; } @@ -312,23 +301,6 @@ private Transaction fittedPayment(HapiSpec spec) throws Throwable { } } - private long timedCostLookupWith(HapiSpec spec, Transaction payment) throws Throwable { - if (suppressStats) { - return lookupCostWith(spec, payment); - } else { - long before = System.currentTimeMillis(); - long cost = lookupCostWith(spec, payment); - long after = System.currentTimeMillis(); - - QueryObs stats = new QueryObs(ResponseType.COST_ANSWER, type()); - stats.setAccepted(expectedCostAnswerPrecheck() == OK); - stats.setResponseLatency(after - before); - considerRecording(spec, stats); - - return cost; - } - } - private Consumer opDef(HapiSpec spec, long amount) throws Throwable { TransferList transfers = asTransferList( tinyBarsFromTo(amount, spec.registry().getAccountID(effectivePayer(spec)), targetNodeFor(spec))); @@ -443,16 +415,6 @@ public T hasEncodedLedgerId(ByteString ledgerId) { return self(); } - public T delayBy(long pauseMs) { - submitDelay = Optional.of(pauseMs); - return self(); - } - - public T suppressStats(boolean flag) { - suppressStats = flag; - return self(); - } - public T noLogging() { loggingOff = true; return self(); diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/queries/consensus/HapiGetTopicInfo.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/queries/consensus/HapiGetTopicInfo.java index 897da9ee1b63..827fb3858e26 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/queries/consensus/HapiGetTopicInfo.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/queries/consensus/HapiGetTopicInfo.java @@ -205,7 +205,7 @@ protected boolean needsPayment() { @Override protected long lookupCostWith(HapiSpec spec, Transaction payment) throws Throwable { - Query query = getTopicInfoQuery(spec, payment, true); + Query query = maybeModified(getTopicInfoQuery(spec, payment, true), spec); Response response = spec.clients().getConsSvcStub(targetNodeFor(spec), useTls).getTopicInfo(query); return costFrom(response); diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/queries/contract/HapiContractCallLocal.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/queries/contract/HapiContractCallLocal.java index 2bb589ef861b..f80682cc1d40 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/queries/contract/HapiContractCallLocal.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/queries/contract/HapiContractCallLocal.java @@ -212,7 +212,7 @@ protected void submitWith(HapiSpec spec, Transaction payment) throws Throwable { @Override protected long lookupCostWith(HapiSpec spec, Transaction payment) throws Throwable { - Query query = getContractCallLocal(spec, payment, true); + Query query = maybeModified(getContractCallLocal(spec, payment, true), spec); Response response = spec.clients().getScSvcStub(targetNodeFor(spec), useTls).contractCallLocalMethod(query); return costFrom(response); diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/queries/contract/HapiGetContractBytecode.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/queries/contract/HapiGetContractBytecode.java index 47a60576c605..f8615324d0cd 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/queries/contract/HapiGetContractBytecode.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/queries/contract/HapiGetContractBytecode.java @@ -101,7 +101,7 @@ protected void submitWith(HapiSpec spec, Transaction payment) throws Throwable { @Override protected long lookupCostWith(HapiSpec spec, Transaction payment) throws Throwable { - Query query = getContractBytecodeQuery(spec, payment, true); + Query query = maybeModified(getContractBytecodeQuery(spec, payment, true), spec); Response response = spec.clients().getScSvcStub(targetNodeFor(spec), useTls).contractGetBytecode(query); return costFrom(response); diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/queries/contract/HapiGetContractInfo.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/queries/contract/HapiGetContractInfo.java index b04785ac3dc9..9e9b453940ef 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/queries/contract/HapiGetContractInfo.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/queries/contract/HapiGetContractInfo.java @@ -67,6 +67,7 @@ public class HapiGetContractInfo extends HapiQueryOp { private Optional contractInfoPath = Optional.empty(); private Optional validateDirPath = Optional.empty(); private Optional registryEntry = Optional.empty(); + private Optional saveEVMAddressToRegistry = Optional.empty(); private List absentRelationships = new ArrayList<>(); private List relationships = new ArrayList<>(); private Optional expectations = Optional.empty(); @@ -104,6 +105,11 @@ public HapiGetContractInfo saveToRegistry(String registryEntry) { return this; } + public HapiGetContractInfo saveEVMAddressToRegistry(String registryEntry) { + this.saveEVMAddressToRegistry = Optional.of(registryEntry); + return this; + } + public HapiGetContractInfo checkingAgainst(String dirPath) { validateDirPath = Optional.of(dirPath); return this; @@ -176,6 +182,18 @@ protected void assertExpectationsGiven(HapiSpec spec) throws Throwable { } } + @Override + protected void updateStateOf(HapiSpec spec) throws Throwable { + ContractInfo actualInfo = response.getContractGetInfo().getContractInfo(); + + if (saveEVMAddressToRegistry.isPresent()) { + spec.registry() + .saveEVMAddress( + String.valueOf(actualInfo.getContractID().getContractNum()), + actualInfo.getContractAccountID()); + } + } + @Override protected void submitWith(HapiSpec spec, Transaction payment) throws Throwable { Query query = maybeModified(getContractInfoQuery(spec, payment, false), spec); @@ -198,7 +216,7 @@ protected void submitWith(HapiSpec spec, Transaction payment) throws Throwable { @Override protected long lookupCostWith(HapiSpec spec, Transaction payment) throws Throwable { - Query query = getContractInfoQuery(spec, payment, true); + Query query = maybeModified(getContractInfoQuery(spec, payment, true), spec); Response response = spec.clients().getScSvcStub(targetNodeFor(spec), useTls).getContractInfo(query); return costFrom(response); diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/queries/contract/HapiGetContractRecords.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/queries/contract/HapiGetContractRecords.java index 2006a570b120..80a9c85aaac6 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/queries/contract/HapiGetContractRecords.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/queries/contract/HapiGetContractRecords.java @@ -133,7 +133,7 @@ protected void submitWith(HapiSpec spec, Transaction payment) throws Throwable { @Override protected long lookupCostWith(HapiSpec spec, Transaction payment) throws Throwable { - Query query = getContractRecordsQuery(spec, payment, true); + Query query = maybeModified(getContractRecordsQuery(spec, payment, true), spec); Response response = spec.clients().getScSvcStub(targetNodeFor(spec), useTls).getTxRecordByContractID(query); return costFrom(response); diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/queries/crypto/HapiGetAccountBalance.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/queries/crypto/HapiGetAccountBalance.java index f3ebbef6b960..4cb8d38bc413 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/queries/crypto/HapiGetAccountBalance.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/queries/crypto/HapiGetAccountBalance.java @@ -30,8 +30,6 @@ import com.hedera.services.bdd.spec.queries.QueryVerbs; import com.hedera.services.bdd.spec.transactions.TxnUtils; import com.hedera.services.bdd.spec.utilops.CustomSpecAssert; -import com.hedera.services.stream.proto.SingleAccountBalances; -import com.hedera.services.stream.proto.TokenUnitBalance; import com.hederahashgraph.api.proto.java.AccountID; import com.hederahashgraph.api.proto.java.ContractID; import com.hederahashgraph.api.proto.java.CryptoGetAccountBalanceQuery; @@ -69,8 +67,6 @@ public class HapiGetAccountBalance extends HapiQueryOp { private static final Logger log = LogManager.getLogger(HapiGetAccountBalance.class); private String account; - private Optional accountID = Optional.empty(); - private boolean exportAccount = false; Optional expected = Optional.empty(); Optional> entityFn = Optional.empty(); Optional>>> expectedCondition = Optional.empty(); @@ -167,11 +163,6 @@ public HapiGetAccountBalance exposingBalanceTo(final LongConsumer obs) { return this; } - public HapiGetAccountBalance persists(boolean toExport) { - exportAccount = toExport; - return this; - } - public HapiGetAccountBalance hasExpectedAccountID() { assertAccountIDIsNotAlias = true; return this; @@ -274,21 +265,6 @@ protected void assertExpectationsGiven(HapiSpec spec) throws Throwable { } } } - - if (exportAccount && accountID.isPresent()) { - SingleAccountBalances.Builder sab = SingleAccountBalances.newBuilder(); - List tokenUnitBalanceList = - response.getCryptogetAccountBalance().getTokenBalancesList().stream() - .map(a -> TokenUnitBalance.newBuilder() - .setTokenId(a.getTokenId()) - .setBalance(a.getBalance()) - .build()) - .collect(Collectors.toList()); - sab.setAccountID(accountID.get()) - .setHbarBalance(response.getCryptogetAccountBalance().getBalance()) - .addAllTokenUnitBalances(tokenUnitBalanceList); - spec.saveSingleAccountBalances(sab.build()); - } } @Override @@ -339,7 +315,6 @@ private Query getAccountBalanceQuery(HapiSpec spec, Transaction payment, boolean id = spec.registry().keyAliasIdFor(aliasKeySource); } config = b -> b.setAccountID(id); - accountID = Optional.of(id); } CryptoGetAccountBalanceQuery.Builder query = CryptoGetAccountBalanceQuery.newBuilder() .setHeader(costOnly ? answerCostHeader(payment) : answerHeader(payment)); diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/queries/crypto/HapiGetAccountDetails.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/queries/crypto/HapiGetAccountDetails.java index 13c1ac14cea4..9ff4e6a49e3a 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/queries/crypto/HapiGetAccountDetails.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/queries/crypto/HapiGetAccountDetails.java @@ -235,7 +235,7 @@ protected void submitWith(HapiSpec spec, Transaction payment) throws Throwable { @Override protected long lookupCostWith(HapiSpec spec, Transaction payment) throws Throwable { - Query query = getAccountDetailsQuery(spec, payment, true); + Query query = maybeModified(getAccountDetailsQuery(spec, payment, true), spec); Response response = spec.clients().getNetworkSvcStub(targetNodeFor(spec), useTls).getAccountDetails(query); return costFrom(response); diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/queries/crypto/HapiGetAccountInfo.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/queries/crypto/HapiGetAccountInfo.java index af045eda64f3..5864bc409f98 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/queries/crypto/HapiGetAccountInfo.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/queries/crypto/HapiGetAccountInfo.java @@ -33,7 +33,13 @@ import com.hedera.services.bdd.spec.queries.QueryVerbs; import com.hedera.services.bdd.spec.transactions.TxnUtils; import com.hedera.services.bdd.spec.utilops.CustomSpecAssert; -import com.hederahashgraph.api.proto.java.*; +import com.hederahashgraph.api.proto.java.AccountID; +import com.hederahashgraph.api.proto.java.CryptoGetInfoQuery; +import com.hederahashgraph.api.proto.java.HederaFunctionality; +import com.hederahashgraph.api.proto.java.Key; +import com.hederahashgraph.api.proto.java.Query; +import com.hederahashgraph.api.proto.java.Response; +import com.hederahashgraph.api.proto.java.Transaction; import com.swirlds.common.utility.CommonUtils; import edu.umd.cs.findbugs.annotations.Nullable; import java.nio.file.Files; @@ -332,7 +338,7 @@ protected void submitWith(HapiSpec spec, Transaction payment) throws Throwable { @Override protected long lookupCostWith(HapiSpec spec, Transaction payment) throws Throwable { - Query query = getAccountInfoQuery(spec, payment, true); + Query query = maybeModified(getAccountInfoQuery(spec, payment, true), spec); Response response = spec.clients().getCryptoSvcStub(targetNodeFor(spec), useTls).getAccountInfo(query); return costFrom(response); diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/queries/crypto/HapiGetAccountRecords.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/queries/crypto/HapiGetAccountRecords.java index f851cc3287bd..c12cb51ca24b 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/queries/crypto/HapiGetAccountRecords.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/queries/crypto/HapiGetAccountRecords.java @@ -171,7 +171,7 @@ private void saveSnapshots(HapiSpec spec, List records) { @Override protected long lookupCostWith(HapiSpec spec, Transaction payment) throws Throwable { - Query query = getRecordsQuery(spec, payment, true); + Query query = maybeModified(getRecordsQuery(spec, payment, true), spec); Response response = spec.clients().getCryptoSvcStub(targetNodeFor(spec), useTls).getAccountRecords(query); return costFrom(response); diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/queries/file/HapiGetFileContents.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/queries/file/HapiGetFileContents.java index f2fa47291e80..dd078ebc753a 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/queries/file/HapiGetFileContents.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/queries/file/HapiGetFileContents.java @@ -34,6 +34,7 @@ import com.hederahashgraph.api.proto.java.Response; import com.hederahashgraph.api.proto.java.ServicesConfigurationList; import com.hederahashgraph.api.proto.java.Transaction; +import edu.umd.cs.findbugs.annotations.Nullable; import java.io.File; import java.nio.charset.Charset; import java.util.ArrayList; @@ -60,6 +61,10 @@ public class HapiGetFileContents extends HapiQueryOp { private boolean saveIn4kChunks = false; private int sizeLookup = -1; + + @Nullable + private Consumer validator = null; + private Function parser = bytes -> ""; private final String fileName; Optional readablePath = Optional.empty(); @@ -126,6 +131,11 @@ public HapiGetFileContents saveReadableTo(Function parser, Strin return this; } + public HapiGetFileContents andValidate(Consumer validator) { + this.validator = validator; + return this; + } + public HapiGetFileContents saveToRegistry(String registryEntry) { this.registryEntry = Optional.of(registryEntry); return this; @@ -235,6 +245,9 @@ protected void submitWith(HapiSpec spec, Transaction payment) throws Throwable { "Saved parsed contents of '%s' to %s", fileName, readableFile.getAbsolutePath()); log.info(message); } + if (validator != null) { + validator.accept(bytes); + } } catch (Exception e) { String message = String.format("Couldn't save '%s' snapshot!", fileName); log.error(message, e); @@ -252,7 +265,7 @@ protected void submitWith(HapiSpec spec, Transaction payment) throws Throwable { @Override protected long lookupCostWith(HapiSpec spec, Transaction payment) throws Throwable { - Query query = getFileContentQuery(spec, payment, true); + Query query = maybeModified(getFileContentQuery(spec, payment, true), spec); Response response = spec.clients().getFileSvcStub(targetNodeFor(spec), useTls).getFileContent(query); return costFrom(response); diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/queries/file/HapiGetFileInfo.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/queries/file/HapiGetFileInfo.java index 01bb3d56bd8b..6212b9f331e2 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/queries/file/HapiGetFileInfo.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/queries/file/HapiGetFileInfo.java @@ -139,7 +139,7 @@ protected void submitWith(HapiSpec spec, Transaction payment) throws Throwable { @Override protected long lookupCostWith(HapiSpec spec, Transaction payment) throws Throwable { - Query query = getFileInfoQuery(spec, payment, true); + Query query = maybeModified(getFileInfoQuery(spec, payment, true), spec); Response response = spec.clients().getFileSvcStub(targetNodeFor(spec), useTls).getFileInfo(query); return costFrom(response); diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/queries/meta/AccountCreationDetails.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/queries/meta/AccountCreationDetails.java new file mode 100644 index 000000000000..576392e3a183 --- /dev/null +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/queries/meta/AccountCreationDetails.java @@ -0,0 +1,22 @@ +/* + * Copyright (C) 2024 Hedera Hashgraph, LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.hedera.services.bdd.spec.queries.meta; + +import com.esaulpaugh.headlong.abi.Address; +import com.hederahashgraph.api.proto.java.AccountID; + +public record AccountCreationDetails(AccountID createdId, Address evmAddress) {} diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/queries/meta/HapiGetExecTime.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/queries/meta/HapiGetExecTime.java index 0e0f3e2b2021..a0bc3eed8188 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/queries/meta/HapiGetExecTime.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/queries/meta/HapiGetExecTime.java @@ -140,7 +140,7 @@ private String aligned(String s, int len, boolean right) { @Override protected long lookupCostWith(HapiSpec spec, Transaction payment) throws Throwable { - Query query = getExecTimesQuery(spec, payment, true); + Query query = maybeModified(getExecTimesQuery(spec, payment, true), spec); Response response = spec.clients().getNetworkSvcStub(targetNodeFor(spec), useTls).getExecutionTime(query); return costFrom(response); diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/queries/meta/HapiGetTxnRecord.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/queries/meta/HapiGetTxnRecord.java index 27ba611c3058..1e55e611d007 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/queries/meta/HapiGetTxnRecord.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/queries/meta/HapiGetTxnRecord.java @@ -25,6 +25,7 @@ import static com.hedera.services.bdd.spec.transactions.TxnUtils.asIdForKeyLookUp; import static com.hedera.services.bdd.spec.transactions.TxnUtils.asTokenId; import static com.hedera.services.bdd.spec.transactions.TxnUtils.isEndOfStakingPeriodRecord; +import static com.hedera.services.bdd.spec.transactions.contract.HapiParserUtil.asHeadlongAddress; import static com.hedera.services.bdd.spec.transactions.schedule.HapiScheduleCreate.correspondingScheduledTxnId; import static com.hedera.services.bdd.suites.HapiSuite.HBAR_TOKEN_SENTINEL; import static com.hedera.services.bdd.suites.crypto.CryptoTransferSuite.sdec; @@ -144,6 +145,9 @@ public class HapiGetTxnRecord extends HapiQueryOp { @Nullable private Consumer> createdIdsObserver = null; + @Nullable + private Consumer> creationDetailsObserver = null; + @Nullable private Consumer> createdTokenIdsObserver = null; @@ -214,9 +218,14 @@ public HapiGetTxnRecord exposingCreationsTo(final Consumer> creatio return this; } + public HapiGetTxnRecord exposingCreationDetailsTo(final Consumer> observer) { + creationDetailsObserver = observer; + return andAllChildRecords(); + } + public HapiGetTxnRecord exposingTokenCreationsTo(final Consumer> createdTokenIdsObserver) { this.createdTokenIdsObserver = createdTokenIdsObserver; - return this; + return andAllChildRecords(); } public HapiGetTxnRecord exposingFilteredCallResultVia( @@ -903,11 +912,20 @@ protected void submitWith(final HapiSpec spec, final Transaction payment) throws allRecordsObserver.accept(allRecords); } final List creations = (createdIdsObserver != null) ? new ArrayList<>() : null; + final List creationDetails = + (creationDetailsObserver != null) ? new ArrayList<>() : null; final List tokenCreations = (createdTokenIdsObserver != null) ? new ArrayList<>() : null; for (final var rec : childRecords) { - if (rec.getReceipt().hasAccountID() && creations != null) { - creations.add( - HapiPropertySource.asAccountString(rec.getReceipt().getAccountID())); + if (rec.getReceipt().hasAccountID()) { + if (creations != null) { + creations.add( + HapiPropertySource.asAccountString(rec.getReceipt().getAccountID())); + } + if (creationDetails != null) { + creationDetails.add(new AccountCreationDetails( + rec.getReceipt().getAccountID(), + asHeadlongAddress(rec.getEvmAddress().toByteArray()))); + } } if (rec.getReceipt().hasTokenID() && tokenCreations != null) { tokenCreations.add(rec.getReceipt().getTokenID()); @@ -936,6 +954,9 @@ protected void submitWith(final HapiSpec spec, final Transaction payment) throws if (createdTokenIdsObserver != null) { createdTokenIdsObserver.accept(tokenCreations); } + if (creationDetailsObserver != null) { + creationDetailsObserver.accept(creationDetails); + } if (loggingOnlyFee && spec.ratesProvider().hasRateSet()) { final var priceInUsd = sdec(spec.ratesProvider().toUsdWithActiveRates(rcd.getTransactionFee()), 5); @@ -999,7 +1020,7 @@ private void exposeRequestedEventsFrom(final TransactionRecord rcd) { @Override protected long lookupCostWith(final HapiSpec spec, final Transaction payment) throws Throwable { - final Query query = getRecordQuery(spec, payment, true); + final Query query = maybeModified(getRecordQuery(spec, payment, true), spec); final Response response = spec.clients().getCryptoSvcStub(targetNodeFor(spec), useTls).getTxRecordByTxID(query); return costFrom(response); diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/queries/meta/HapiGetVersionInfo.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/queries/meta/HapiGetVersionInfo.java index 7826926d66c0..4373348e8450 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/queries/meta/HapiGetVersionInfo.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/queries/meta/HapiGetVersionInfo.java @@ -134,7 +134,7 @@ private String asReadable(SemanticVersion semver) { @Override protected long lookupCostWith(HapiSpec spec, Transaction payment) throws Throwable { - Query query = getVersionInfoQuery(payment, true); + Query query = maybeModified(getVersionInfoQuery(payment, true), spec); Response response = spec.clients().getNetworkSvcStub(targetNodeFor(spec), useTls).getVersionInfo(query); return costFrom(response); diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/queries/schedule/HapiGetScheduleInfo.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/queries/schedule/HapiGetScheduleInfo.java index a5d1c65208ec..b00cea5c6459 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/queries/schedule/HapiGetScheduleInfo.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/queries/schedule/HapiGetScheduleInfo.java @@ -256,7 +256,7 @@ protected void submitWith(HapiSpec spec, Transaction payment) throws Throwable { @Override protected long lookupCostWith(HapiSpec spec, Transaction payment) throws Throwable { - Query query = getScheduleInfoQuery(spec, payment, true); + Query query = maybeModified(getScheduleInfoQuery(spec, payment, true), spec); Response response = spec.clients().getScheduleSvcStub(targetNodeFor(spec), useTls).getScheduleInfo(query); return costFrom(response); diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/queries/token/HapiGetAccountNftInfos.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/queries/token/HapiGetAccountNftInfos.java index 37ccc1f1c4be..d59df6993131 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/queries/token/HapiGetAccountNftInfos.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/queries/token/HapiGetAccountNftInfos.java @@ -105,7 +105,7 @@ protected void submitWith(HapiSpec spec, Transaction payment) { @Override protected long lookupCostWith(HapiSpec spec, Transaction payment) throws Throwable { - Query query = getAccountNftInfosQuery(spec, payment, true); + Query query = maybeModified(getAccountNftInfosQuery(spec, payment, true), spec); Response response = spec.clients().getTokenSvcStub(targetNodeFor(spec), useTls).getAccountNftInfos(query); return costFrom(response); diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/queries/token/HapiGetTokenInfo.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/queries/token/HapiGetTokenInfo.java index 3ffdf717ed2c..1a095fc9ac66 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/queries/token/HapiGetTokenInfo.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/queries/token/HapiGetTokenInfo.java @@ -28,6 +28,7 @@ import com.hedera.services.bdd.suites.hip796.operations.TokenFeature; import com.hederahashgraph.api.proto.java.CustomFee; import com.hederahashgraph.api.proto.java.HederaFunctionality; +import com.hederahashgraph.api.proto.java.Key; import com.hederahashgraph.api.proto.java.Query; import com.hederahashgraph.api.proto.java.Response; import com.hederahashgraph.api.proto.java.Timestamp; @@ -89,6 +90,23 @@ public HapiGetTokenInfo(String token) { private Optional expectedWipeKey = Optional.empty(); private Optional expectedFeeScheduleKey = Optional.empty(); private Optional expectedPauseKey = Optional.empty(); + private boolean emptyAdminKey = false; + private boolean emptyWipeKey = false; + private boolean emptyKycKey = false; + private boolean emptySupplyKey = false; + private boolean emptyFreezeKey = false; + private boolean emptyFeeScheduleKey = false; + private boolean emptyMetadataKey = false; + private boolean emptyPauseKey = false; + + private boolean invalidAdminKey = false; + private boolean invalidWipeKey = false; + private boolean invalidKycKey = false; + private boolean invalidSupplyKey = false; + private boolean invalidFreezeKey = false; + private boolean invalidFeeScheduleKey = false; + private boolean invalidMetadataKey = false; + private boolean invalidPauseKey = false; @Nullable private String expectedLockKey = null; @@ -299,6 +317,86 @@ public HapiGetTokenInfo searchKeysGlobally() { return this; } + public HapiGetTokenInfo hasEmptyAdminKey() { + emptyAdminKey = true; + return this; + } + + public HapiGetTokenInfo hasEmptyPauseKey() { + emptyPauseKey = true; + return this; + } + + public HapiGetTokenInfo hasEmptyFreezeKey() { + emptyFreezeKey = true; + return this; + } + + public HapiGetTokenInfo hasEmptyKycKey() { + emptyKycKey = true; + return this; + } + + public HapiGetTokenInfo hasEmptySupplyKey() { + emptySupplyKey = true; + return this; + } + + public HapiGetTokenInfo hasEmptyWipeKey() { + emptyWipeKey = true; + return this; + } + + public HapiGetTokenInfo hasEmptyFeeScheduleKey() { + emptyFeeScheduleKey = true; + return this; + } + + public HapiGetTokenInfo hasEmptyMetadataKey() { + emptyMetadataKey = true; + return this; + } + + public HapiGetTokenInfo hasInvalidAdminKey() { + invalidAdminKey = true; + return this; + } + + public HapiGetTokenInfo hasInvalidPauseKey() { + invalidPauseKey = true; + return this; + } + + public HapiGetTokenInfo hasInvalidFreezeKey() { + invalidFreezeKey = true; + return this; + } + + public HapiGetTokenInfo hasInvalidKycKey() { + invalidKycKey = true; + return this; + } + + public HapiGetTokenInfo hasInvalidSupplyKey() { + invalidSupplyKey = true; + return this; + } + + public HapiGetTokenInfo hasInvalidWipeKey() { + invalidWipeKey = true; + return this; + } + + public HapiGetTokenInfo hasInvalidFeeScheduleKey() { + invalidFeeScheduleKey = true; + return this; + } + + public HapiGetTokenInfo hasInvalidMetadataKey() { + invalidMetadataKey = true; + return this; + } + @Override @SuppressWarnings("java:S5960") protected void assertExpectationsGiven(HapiSpec spec) { @@ -371,60 +469,110 @@ protected void assertExpectationsGiven(HapiSpec spec) { (n, r) -> Timestamp.newBuilder().setSeconds(r.getExpiry(token)).build(), "Wrong token expiry!", registry); - assertFor( - actualInfo.getFreezeKey(), - expectedFreezeKey, - (n, r) -> searchKeysGlobally ? r.getKey(n) : r.getFreezeKey(n), - "Wrong token freeze key!", - registry); - assertFor( - actualInfo.getAdminKey(), - expectedAdminKey, - (n, r) -> searchKeysGlobally ? r.getKey(n) : r.getAdminKey(n), - "Wrong token admin key!", - registry); - assertFor( - actualInfo.getWipeKey(), - expectedWipeKey, - (n, r) -> searchKeysGlobally ? r.getKey(n) : r.getWipeKey(n), - "Wrong token wipe key!", - registry); + if (emptyFreezeKey) { + assertForRemovedKey(actualInfo.getFreezeKey()); + } else if (invalidFreezeKey) { + assertForAllZerosInvalidKey(actualInfo.getFreezeKey()); + } else { + assertFor( + actualInfo.getFreezeKey(), + expectedFreezeKey, + (n, r) -> searchKeysGlobally ? r.getKey(n) : r.getFreezeKey(n), + "Wrong token freeze key!", + registry); + } - assertFor( - actualInfo.getKycKey(), - expectedKycKey, - (n, r) -> searchKeysGlobally ? r.getKey(n) : r.getKycKey(n), - "Wrong token KYC key!", - registry); + if (emptyAdminKey) { + assertForRemovedKey(actualInfo.getAdminKey()); + } else if (invalidAdminKey) { + assertForAllZerosInvalidKey(actualInfo.getAdminKey()); + } else { + assertFor( + actualInfo.getAdminKey(), + expectedAdminKey, + (n, r) -> searchKeysGlobally ? r.getKey(n) : r.getAdminKey(n), + "Wrong token admin key!", + registry); + } - assertFor( - actualInfo.getSupplyKey(), - expectedSupplyKey, - (n, r) -> searchKeysGlobally ? r.getKey(n) : r.getSupplyKey(n), - "Wrong token supply key!", - registry); + if (emptyWipeKey) { + assertForRemovedKey(actualInfo.getWipeKey()); + } else if (invalidWipeKey) { + assertForAllZerosInvalidKey(actualInfo.getWipeKey()); + } else { + assertFor( + actualInfo.getWipeKey(), + expectedWipeKey, + (n, r) -> searchKeysGlobally ? r.getKey(n) : r.getWipeKey(n), + "Wrong token wipe key!", + registry); + } - assertFor( - actualInfo.getFeeScheduleKey(), - expectedFeeScheduleKey, - (n, r) -> searchKeysGlobally ? r.getKey(n) : r.getFeeScheduleKey(n), - "Wrong token fee schedule key!", - registry); + if (emptyKycKey) { + assertForRemovedKey(actualInfo.getKycKey()); + } else if (invalidKycKey) { + assertForAllZerosInvalidKey(actualInfo.getKycKey()); + } else { + assertFor( + actualInfo.getKycKey(), + expectedKycKey, + (n, r) -> searchKeysGlobally ? r.getKey(n) : r.getKycKey(n), + "Wrong token KYC key!", + registry); + } - assertFor( - actualInfo.getPauseKey(), - expectedPauseKey, - (n, r) -> searchKeysGlobally ? r.getKey(n) : r.getPauseKey(n), - "Wrong token pause key!", - registry); + if (emptySupplyKey) { + assertForRemovedKey(actualInfo.getSupplyKey()); + } else if (invalidSupplyKey) { + assertForAllZerosInvalidKey(actualInfo.getSupplyKey()); + } else { + assertFor( + actualInfo.getSupplyKey(), + expectedSupplyKey, + (n, r) -> searchKeysGlobally ? r.getKey(n) : r.getSupplyKey(n), + "Wrong token supply key!", + registry); + } - assertFor( - actualInfo.getMetadataKey(), - expectedMetadataKey, - (n, r) -> searchKeysGlobally ? r.getKey(n) : r.getMetadataKey(n), - "Wrong token metadata key!", - registry); + if (emptyFeeScheduleKey) { + assertForRemovedKey(actualInfo.getFeeScheduleKey()); + } else if (invalidFeeScheduleKey) { + assertForAllZerosInvalidKey(actualInfo.getFeeScheduleKey()); + } else { + assertFor( + actualInfo.getFeeScheduleKey(), + expectedFeeScheduleKey, + (n, r) -> searchKeysGlobally ? r.getKey(n) : r.getFeeScheduleKey(n), + "Wrong token fee schedule key!", + registry); + } + + if (emptyPauseKey) { + assertForRemovedKey(actualInfo.getPauseKey()); + } else if (invalidPauseKey) { + assertForAllZerosInvalidKey(actualInfo.getPauseKey()); + } else { + assertFor( + actualInfo.getPauseKey(), + expectedPauseKey, + (n, r) -> searchKeysGlobally ? r.getKey(n) : r.getPauseKey(n), + "Wrong token pause key!", + registry); + } + + if (emptyMetadataKey) { + assertForRemovedKey(actualInfo.getMetadataKey()); + } else if (invalidMetadataKey) { + assertForAllZerosInvalidKey(actualInfo.getMetadataKey()); + } else { + assertFor( + actualInfo.getMetadataKey(), + expectedMetadataKey, + (n, r) -> searchKeysGlobally ? r.getKey(n) : r.getMetadataKey(n), + "Wrong token metadata key!", + registry); + } expectedLedgerId.ifPresent(id -> Assertions.assertEquals(id, actualInfo.getLedgerId())); } @@ -441,6 +589,17 @@ private void assertFor( } } + private void assertForAllZerosInvalidKey(Key actual) { + Assertions.assertEquals( + TxnUtils.ALL_ZEROS_INVALID_KEY, + actual, + "Does not equal to zero address `0x0000000000000000000000000000000000000000`"); + } + + private void assertForRemovedKey(Key actual) { + Assertions.assertEquals(Key.getDefaultInstance(), actual, "Does not equal to a removed key"); + } + @Override protected void submitWith(HapiSpec spec, Transaction payment) { Query query = maybeModified(getTokenInfoQuery(spec, payment, false), spec); @@ -452,7 +611,7 @@ protected void submitWith(HapiSpec spec, Transaction payment) { @Override protected long lookupCostWith(HapiSpec spec, Transaction payment) throws Throwable { - Query query = getTokenInfoQuery(spec, payment, true); + Query query = maybeModified(getTokenInfoQuery(spec, payment, true), spec); Response response = spec.clients().getTokenSvcStub(targetNodeFor(spec), useTls).getTokenInfo(query); return costFrom(response); diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/queries/token/HapiGetTokenNftInfo.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/queries/token/HapiGetTokenNftInfo.java index 4a60652c7de2..54b8f3760bdc 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/queries/token/HapiGetTokenNftInfo.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/queries/token/HapiGetTokenNftInfo.java @@ -170,7 +170,7 @@ protected void submitWith(HapiSpec spec, Transaction payment) { @Override protected long lookupCostWith(HapiSpec spec, Transaction payment) throws Throwable { - Query query = getTokenNftInfoQuery(spec, payment, true); + Query query = maybeModified(getTokenNftInfoQuery(spec, payment, true), spec); Response response = spec.clients().getTokenSvcStub(targetNodeFor(spec), useTls).getTokenNftInfo(query); return costFrom(response); diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/queries/token/HapiGetTokenNftInfos.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/queries/token/HapiGetTokenNftInfos.java index b2f9fa533a52..88c4e9cec8cb 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/queries/token/HapiGetTokenNftInfos.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/queries/token/HapiGetTokenNftInfos.java @@ -101,7 +101,7 @@ protected void submitWith(HapiSpec spec, Transaction payment) throws Throwable { @Override protected long lookupCostWith(HapiSpec spec, Transaction payment) throws Throwable { - Query query = getTokenNftInfosQuery(spec, payment, true); + Query query = maybeModified(getTokenNftInfosQuery(spec, payment, true), spec); Response response = spec.clients().getTokenSvcStub(targetNodeFor(spec), useTls).getTokenNftInfos(query); return costFrom(response); diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/stats/HapiStats.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/stats/HapiStats.java deleted file mode 100644 index 4dffd888f146..000000000000 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/stats/HapiStats.java +++ /dev/null @@ -1,118 +0,0 @@ -/* - * Copyright (C) 2020-2024 Hedera Hashgraph, LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.hedera.services.bdd.spec.stats; - -import static java.util.stream.Collectors.counting; -import static java.util.stream.Collectors.groupingBy; -import static java.util.stream.Collectors.toList; -import static java.util.stream.Collectors.toSet; - -import com.google.common.math.Stats; -import com.hederahashgraph.api.proto.java.HederaFunctionality; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.function.Function; -import java.util.function.Predicate; - -public class HapiStats { - private final boolean hasConsensusLatencies; - private final List obs; - - public HapiStats(boolean hasConsensusLatencies, List obs) { - this.hasConsensusLatencies = hasConsensusLatencies; - this.obs = obs; - } - - public Stats queryResponseLatencyStats() { - return Stats.of(queryObs().stream().map(QueryObs::getResponseLatency).collect(toList())); - } - - public Stats txnResponseLatencyStats() { - return Stats.of(txnObs().stream().map(TxnObs::getResponseLatency).collect(toList())); - } - - public Stats txnResponseLatencyStatsFor(HederaFunctionality txnType) { - return Stats.of(txnObs().stream() - .filter(obs -> obs.functionality().equals(txnType)) - .map(TxnObs::getResponseLatency) - .collect(toList())); - } - - public Stats statsProjectedFor( - Class tClass, Predicate relevancy, Function projection) { - return Stats.of((tClass.equals(TxnObs.class) ? txnObs() : queryObs()) - .stream() - .filter(obs -> relevancy.test(tClass.cast(obs))) - .map(obs -> projection.apply(tClass.cast(obs))) - .collect(toList())); - } - - public Map countDetails() { - return obs.stream().collect(groupingBy(OpObs::functionality, counting())); - } - - public List txnObs() { - return obs.stream() - .filter(op -> op instanceof TxnObs) - .map(op -> (TxnObs) op) - .collect(toList()); - } - - public Set queryTypes() { - return queryObs().stream().map(QueryObs::functionality).collect(toSet()); - } - - public Set txnTypes() { - return txnObs().stream().map(TxnObs::functionality).collect(toSet()); - } - - public List acceptedTxnObs() { - return txnObs().stream().filter(TxnObs::wasAccepted).collect(toList()); - } - - public List queryObs() { - return obs.stream() - .filter(op -> op instanceof QueryObs) - .map(op -> (QueryObs) op) - .collect(toList()); - } - - public Stats txnResponseLatencyStatsFor() { - assertLatencyWasMeasured(); - return Stats.of( - acceptedTxnObs().stream().map(TxnObs::getConsensusLatency).collect(toList())); - } - - private void assertLatencyWasMeasured() { - if (!hasConsensusLatencies) { - throw new IllegalStateException("Consensus latencies were not measured!"); - } - } - - public int numTxns() { - return txnObs().size(); - } - - public int numQueries() { - return queryObs().size(); - } - - public int numOps() { - return obs.size(); - } -} diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/stats/ThroughputObs.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/stats/ThroughputObs.java deleted file mode 100644 index 89762c1590bc..000000000000 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/stats/ThroughputObs.java +++ /dev/null @@ -1,90 +0,0 @@ -/* - * Copyright (C) 2020-2024 Hedera Hashgraph, LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.hedera.services.bdd.spec.stats; - -import com.hedera.services.bdd.spec.HapiSpec; - -public class ThroughputObs { - private int numOpsAtObservationStart; - private int numOpsAtObservationFinish = -1; - private int numOpsAtExpectedQueueSaturation = -1; - private long obsLengthMs = -1L; - private final long creationTime; - private final long expectedQueueSaturationTime; - private final String name; - - public ThroughputObs(String name, long expectedQueueSaturationTime, HapiSpec spec) { - this.name = name; - this.creationTime = System.currentTimeMillis(); - this.expectedQueueSaturationTime = expectedQueueSaturationTime; - spec.addLedgerOpCountCallback(count -> { - if (numOpsAtExpectedQueueSaturation == -1) { - long now = System.currentTimeMillis(); - if (now >= expectedQueueSaturationTime) { - numOpsAtExpectedQueueSaturation = count; - } - } - }); - this.numOpsAtObservationStart = spec.numLedgerOps(); - } - - public long getCreationTime() { - return creationTime; - } - - public void setObsLengthMs(long obsLengthMs) { - this.obsLengthMs = obsLengthMs; - } - - public void setNumOpsAtObservationFinish(int numOpsAtObservationFinish) { - this.numOpsAtObservationFinish = numOpsAtObservationFinish; - } - - public long getExpectedQueueSaturationTime() { - return expectedQueueSaturationTime; - } - - public int getNumOpsAtExpectedQueueSaturation() { - return numOpsAtExpectedQueueSaturation; - } - - public int getNumOpsAtObservationStart() { - return numOpsAtObservationStart; - } - - public String getName() { - return name; - } - - public String summary() { - if (!summarizable()) { - return "Cannot be summarized, incomplete observation!"; - } else { - int n = (numOpsAtObservationFinish - numOpsAtExpectedQueueSaturation); - double opsPerSecond = (double) n / obsLengthMs * 1_000L; - return String.format("~%.2f ledger ops/sec", opsPerSecond); - } - } - - private boolean summarizable() { - return pos(numOpsAtExpectedQueueSaturation) && pos(numOpsAtObservationFinish) && pos(obsLengthMs); - } - - private boolean pos(long v) { - return v > 0L; - } -} diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/stats/TxnObs.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/stats/TxnObs.java deleted file mode 100644 index d84650730277..000000000000 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/stats/TxnObs.java +++ /dev/null @@ -1,41 +0,0 @@ -/* - * Copyright (C) 2020-2024 Hedera Hashgraph, LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.hedera.services.bdd.spec.stats; - -import com.hederahashgraph.api.proto.java.HederaFunctionality; - -public class TxnObs extends OpObs { - private final HederaFunctionality txnType; - private long consensusLatency; - - public long getConsensusLatency() { - return consensusLatency; - } - - public void setConsensusLatency(long consensusLatency) { - this.consensusLatency = consensusLatency; - } - - public TxnObs(HederaFunctionality txnType) { - this.txnType = txnType; - } - - @Override - HederaFunctionality functionality() { - return txnType; - } -} diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/transactions/HapiTxnOp.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/transactions/HapiTxnOp.java index 648776077d10..03556346a265 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/transactions/HapiTxnOp.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/transactions/HapiTxnOp.java @@ -25,7 +25,6 @@ import static com.hedera.services.bdd.spec.transactions.crypto.HapiCryptoTransfer.tinyBarsFromTo; import static com.hedera.services.bdd.spec.utilops.CustomSpecAssert.allRunFor; import static com.hedera.services.bdd.suites.HapiSuite.DEFAULT_PAYER; -import static com.hederahashgraph.api.proto.java.HederaFunctionality.TransactionGetReceipt; import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.INSUFFICIENT_PAYER_BALANCE; import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.INSUFFICIENT_TX_FEE; import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.INVALID_ALIAS_KEY; @@ -33,8 +32,8 @@ import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.OK; import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.SUCCESS; import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.UNKNOWN; -import static com.hederahashgraph.api.proto.java.ResponseType.ANSWER_ONLY; import static java.lang.Thread.sleep; +import static java.util.Objects.requireNonNull; import static java.util.stream.Collectors.toList; import com.esaulpaugh.headlong.abi.Tuple; @@ -48,19 +47,15 @@ import com.hedera.services.bdd.spec.fees.Payment; import com.hedera.services.bdd.spec.infrastructure.DelegatingOpFinisher; import com.hedera.services.bdd.spec.infrastructure.HapiApiClients; -import com.hedera.services.bdd.spec.infrastructure.HapiSpecRegistry; import com.hedera.services.bdd.spec.keys.ControlForKey; import com.hedera.services.bdd.spec.keys.KeyGenerator; import com.hedera.services.bdd.spec.keys.OverlappingKeyGenerator; import com.hedera.services.bdd.spec.keys.SigMapGenerator; -import com.hedera.services.bdd.spec.stats.QueryObs; -import com.hedera.services.bdd.spec.stats.TxnObs; import com.hedera.services.bdd.spec.utilops.mod.BodyMutation; import com.hederahashgraph.api.proto.java.Key; import com.hederahashgraph.api.proto.java.Query; import com.hederahashgraph.api.proto.java.Response; import com.hederahashgraph.api.proto.java.ResponseCodeEnum; -import com.hederahashgraph.api.proto.java.Timestamp; import com.hederahashgraph.api.proto.java.Transaction; import com.hederahashgraph.api.proto.java.TransactionBody; import com.hederahashgraph.api.proto.java.TransactionGetReceiptResponse; @@ -93,7 +88,6 @@ public abstract class HapiTxnOp> extends HapiSpecOperatio .build(); private long submitTime = 0L; - private TxnObs stats; private boolean ensureResolvedStatusIsntFromDuplicate = false; private final TupleType LONG_TUPLE = TupleType.parse("(int64)"); @@ -148,7 +142,6 @@ public Transaction signedTxnFor(HapiSpec spec) throws Throwable { @Override protected boolean submitOp(HapiSpec spec) throws Throwable { - stats = new TxnObs(type()); fixNodeFor(spec); configureTlsFor(spec); int retryCount = 1; @@ -165,7 +158,7 @@ protected boolean submitOp(HapiSpec spec) throws Throwable { if (fiddler.isPresent()) { txn = fiddler.get().apply(txn); } - response = timedCall(spec, txn); + response = callToUse(spec).apply(txn); } catch (StatusRuntimeException e) { if (respondToSRE(e, "submitting transaction")) { continue; @@ -210,7 +203,6 @@ && isWithInRetryLimit(retryCount)) { } } spec.updatePrecheckCounts(actualPrecheck); - stats.setAccepted(actualPrecheck == OK); if (actualPrecheck == INSUFFICIENT_PAYER_BALANCE || actualPrecheck == INSUFFICIENT_TX_FEE) { if (payerIsRechargingFor(spec)) { addIpbToPermissiblePrechecks(); @@ -257,16 +249,12 @@ && isWithInRetryLimit(retryCount)) { } } if (actualPrecheck != OK) { - considerRecording(spec, stats); return false; } spec.adhocIncrement(); if (!deferStatusResolution) { resolveStatus(spec); - if (!hasStatsToCollectDuringFinalization(spec)) { - considerRecording(spec, stats); - } } if (requiresFinalization(spec)) { spec.offerFinisher(new DelegatingOpFinisher(this)); @@ -275,14 +263,6 @@ && isWithInRetryLimit(retryCount)) { return !deferStatusResolution; } - private TransactionResponse timedCall(HapiSpec spec, Transaction txn) { - submitTime = System.currentTimeMillis(); - TransactionResponse response = callToUse(spec).apply(txn); - long after = System.currentTimeMillis(); - stats.setResponseLatency(after - submitTime); - return response; - } - private void resolveStatus(HapiSpec spec) throws Throwable { actualStatus = resolvedStatusOfSubmission(spec); spec.updateResolvedCounts(actualStatus); @@ -404,11 +384,7 @@ private void assertRecordHasExpectedMemo(HapiSpec spec) throws Throwable { @Override public boolean requiresFinalization(HapiSpec spec) { - return (actualPrecheck == OK) && (deferStatusResolution || hasStatsToCollectDuringFinalization(spec)); - } - - private boolean hasStatsToCollectDuringFinalization(HapiSpec spec) { - return (!suppressStats && spec.setup().measureConsensusLatency()); + return actualPrecheck == OK && deferStatusResolution; } @Override @@ -422,32 +398,10 @@ protected void lookupSubmissionRecord(HapiSpec spec) throws Throwable { @Override public void finalizeExecFor(HapiSpec spec) throws Throwable { - boolean explicitStatSuppression = suppressStats; - suppressStats = true; if (deferStatusResolution) { resolveStatus(spec); updateStateOf(spec); } - if (!explicitStatSuppression) { - if (spec.setup().measureConsensusLatency()) { - measureConsensusLatency(spec); - } - spec.registry().record(stats); - } - } - - private void measureConsensusLatency(HapiSpec spec) throws Throwable { - if (acceptAnyStatus) { - acceptAnyStatus = false; - acceptAnyKnownStatus = true; - actualStatus = resolvedStatusOfSubmission(spec); - } - if (recordOfSubmission == null) { - lookupSubmissionRecord(spec); - } - Timestamp stamp = recordOfSubmission.getConsensusTimestamp(); - long consensusTime = stamp.getSeconds() * 1_000L + stamp.getNanos() / 1_000_000L; - stats.setConsensusLatency(consensusTime - submitTime); } private ResponseCodeEnum resolvedStatusOfSubmission(HapiSpec spec) throws Throwable { @@ -477,7 +431,6 @@ private ResponseCodeEnum resolvedStatusOfSubmission(HapiSpec spec) throws Throwa } private Response statusResponse(HapiSpec spec, Query receiptQuery) { - long before = System.currentTimeMillis(); Response response = null; int allowedUnrecognizedExceptions = 10; while (response == null) { @@ -501,8 +454,6 @@ private Response statusResponse(HapiSpec spec, Query receiptQuery) { } } } - long after = System.currentTimeMillis(); - considerRecordingAdHocReceiptQueryStats(spec.registry(), after - before); return response; } @@ -538,15 +489,6 @@ private boolean isRecognizedRecoverable(String msg) { || msg.contains("UNAVAILABLE: Channel shutdown invoked"); } - private void considerRecordingAdHocReceiptQueryStats(HapiSpecRegistry registry, long responseLatency) { - if (!suppressStats && !deferStatusResolution) { - QueryObs adhocStats = new QueryObs(ANSWER_ONLY, TransactionGetReceipt); - adhocStats.setAccepted(true); - adhocStats.setResponseLatency(responseLatency); - registry.record(adhocStats); - } - } - private void pause(long forMs) { if (forMs > 0L) { try { @@ -642,8 +584,15 @@ public T orUnavailableStatus() { return self(); } - public T payingWith(String name) { + public T payingWith(@NonNull final String name) { + requireNonNull(name); payer = Optional.of(name); + if (signers.isPresent()) { + final List> payerAndSigners = new ArrayList<>(); + payerAndSigners.add(spec -> spec.registry().getKey(name)); + payerAndSigners.addAll(signers.get()); + signers = Optional.of(payerAndSigners); + } return self(); } @@ -728,16 +677,6 @@ public T deferStatusResolution() { return self(); } - public T delayBy(long pauseMs) { - submitDelay = Optional.of(pauseMs); - return self(); - } - - public T suppressStats(boolean flag) { - suppressStats = flag; - return self(); - } - public T noLogging() { loggingOff = true; return self(); diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/transactions/TxnUtils.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/transactions/TxnUtils.java index 0f833f76acc4..1d1945a0c128 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/transactions/TxnUtils.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/transactions/TxnUtils.java @@ -30,6 +30,7 @@ import static com.hedera.services.bdd.spec.HapiPropertySource.asTopic; import static com.hedera.services.bdd.spec.queries.QueryVerbs.getContractInfo; import static com.hedera.services.bdd.spec.queries.QueryVerbs.getFileInfo; +import static com.hedera.services.bdd.spec.transactions.contract.HapiParserUtil.encodeParametersForConstructor; import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.BUSY; import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.DUPLICATE_TRANSACTION; import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.OK; @@ -49,9 +50,11 @@ import com.hedera.node.app.hapi.utils.fee.SigValueObj; import com.hedera.services.bdd.spec.HapiPropertySource; import com.hedera.services.bdd.spec.HapiSpec; +import com.hedera.services.bdd.spec.HapiSpecOperation; import com.hedera.services.bdd.spec.keys.KeyFactory; import com.hedera.services.bdd.spec.keys.KeyGenerator; import com.hedera.services.bdd.spec.keys.SigControl; +import com.hedera.services.bdd.spec.queries.HapiQueryOp; import com.hedera.services.bdd.spec.queries.contract.HapiGetContractInfo; import com.hedera.services.bdd.spec.queries.file.HapiGetFileInfo; import com.hedera.services.bdd.spec.transactions.contract.HapiContractCall; @@ -79,8 +82,10 @@ import com.hederahashgraph.api.proto.java.TransactionRecord; import com.hederahashgraph.api.proto.java.TransferList; import com.swirlds.common.utility.CommonUtils; +import edu.umd.cs.findbugs.annotations.NonNull; import java.io.IOException; import java.io.UncheckedIOException; +import java.io.UnsupportedEncodingException; import java.math.BigInteger; import java.nio.file.Files; import java.nio.file.Paths; @@ -101,6 +106,7 @@ import java.util.stream.IntStream; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; +import org.apache.tuweni.bytes.Bytes; public class TxnUtils { private static final Logger log = LogManager.getLogger(TxnUtils.class); @@ -117,6 +123,9 @@ public class TxnUtils { Key.newBuilder().setThresholdKey(ThresholdKey.getDefaultInstance()).build(); public static Key EMPTY_KEY_LIST = Key.newBuilder().setKeyList(KeyList.getDefaultInstance()).build(); + public static Key ALL_ZEROS_INVALID_KEY = Key.newBuilder() + .setEd25519(ByteString.fromHex("0000000000000000000000000000000000000000")) + .build(); public static Key netOf( final HapiSpec spec, @@ -126,6 +135,14 @@ public static Key netOf( return netOf(spec, keyName, keyShape, Optional.empty(), keyGenSupplier); } + public static void turnLoggingOff(@NonNull final HapiSpecOperation op) { + if (op instanceof HapiTxnOp txnOp) { + txnOp.noLogging(); + } else if (op instanceof HapiQueryOp queryOp) { + queryOp.noLogging(); + } + } + public static List> defaultUpdateSigners( final String owningEntity, final Optional newKeyName, @@ -633,4 +650,18 @@ public static boolean isEndOfStakingPeriodRecord(final TransactionRecord record) public static boolean isNotEndOfStakingPeriodRecord(final TransactionRecord record) { return !isEndOfStakingPeriodRecord(record); } + + public static ByteString constructorArgsToByteString(final String abi, final Object[] args) { + var params = encodeParametersForConstructor(args, abi); + + var paramsAsHex = Bytes.wrap(params).toHexString(); + // remove the 0x prefix + var paramsToUse = paramsAsHex.substring(2); + + try { + return ByteString.copyFrom(paramsToUse, "UTF-8"); + } catch (UnsupportedEncodingException e) { + throw new UncheckedIOException(e); + } + } } diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/transactions/TxnVerbs.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/transactions/TxnVerbs.java index 4b1ff3d5f653..346081088b26 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/transactions/TxnVerbs.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/transactions/TxnVerbs.java @@ -614,6 +614,30 @@ public static HapiSpecOperation uploadInitCode(final Optional payer, fin }); } + /** + * This method enables uploading a contract bytecode with the constructor parameters (if present) appended at the end of the file + * Used for ethereum create conversion when we need to pass constructor arguments + * @param contractName + * @param abi + * @param args + * @return + */ + public static HapiSpecOperation updateInitCodeWithConstructorArgs( + final Optional payer, final String contractName, final String abi, final Object... args) { + return withOpContext((spec, ctxLog) -> { + List ops = new ArrayList<>(); + + final var path = getResourcePath(contractName, ".bin"); + + // concatenate bytecode with params + final var bytecode = extractByteCode(path).concat(TxnUtils.constructorArgsToByteString(abi, args)); + final var updatedFile = updateLargeFile(payer.orElse(GENESIS), contractName, bytecode); + ops.add(updatedFile); + + allRunFor(spec, ops); + }); + } + public static HapiSpecOperation uploadSingleInitCode( final String contractName, final long expiry, final String payingWith, final LongConsumer exposingTo) { return withOpContext((spec, ctxLog) -> { diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/transactions/contract/HapiBaseCall.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/transactions/contract/HapiBaseCall.java index 807901b7cc8d..1d1c4750dd61 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/transactions/contract/HapiBaseCall.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/transactions/contract/HapiBaseCall.java @@ -29,8 +29,8 @@ public abstract class HapiBaseCall> extends HapiTxnOp public static final int HEXED_EVM_ADDRESS_LEN = 40; public static final String FALLBACK_ABI = ""; protected boolean tryAsHexedAddressIfLenMatches = true; - protected Object[] params; - protected String abi; + protected Optional params = Optional.empty(); + protected Optional abi = Optional.empty(); protected String contract; protected Optional gas = Optional.empty(); protected Optional> explicitHexedParams = Optional.empty(); @@ -44,7 +44,7 @@ protected byte[] initializeCallData() { .map(CommonUtils::unhex) .orElseThrow(); } else { - callData = encodeParametersForCall(params, abi); + callData = encodeParametersForCall(params.orElse(null), abi.orElse(null)); } return callData; diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/transactions/contract/HapiBaseContractCreate.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/transactions/contract/HapiBaseContractCreate.java index 17230f884a45..3aa41c813837 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/transactions/contract/HapiBaseContractCreate.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/transactions/contract/HapiBaseContractCreate.java @@ -57,7 +57,7 @@ public abstract class HapiBaseContractCreate> extends Hap protected boolean advertiseCreation = false; protected boolean shouldAlsoRegisterAsAccount = true; protected boolean useDeprecatedAdminKey = false; - protected final String contract; + protected String contract; protected OptionalLong gas = OptionalLong.empty(); Optional key = Optional.empty(); Optional autoRenewPeriodSecs = Optional.empty(); diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/transactions/contract/HapiContractCall.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/transactions/contract/HapiContractCall.java index bc0d3950f65d..886a9df03101 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/transactions/contract/HapiContractCall.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/transactions/contract/HapiContractCall.java @@ -31,6 +31,7 @@ import com.hedera.services.bdd.spec.transactions.TxnUtils; import com.hederahashgraph.api.proto.java.AccountID; import com.hederahashgraph.api.proto.java.ContractCallTransactionBody; +import com.hederahashgraph.api.proto.java.ContractID; import com.hederahashgraph.api.proto.java.HederaFunctionality; import com.hederahashgraph.api.proto.java.Key; import com.hederahashgraph.api.proto.java.ResponseCodeEnum; @@ -54,6 +55,8 @@ import java.util.function.Supplier; public class HapiContractCall extends HapiBaseCall { + public static final String DEFAULT_ID_SENTINEL = ""; + protected List otherSigs = Collections.emptyList(); private Optional details = Optional.empty(); private Optional> paramsFn = Optional.empty(); @@ -84,8 +87,8 @@ public static HapiContractCall fromDetails(String actionable) { private HapiContractCall() {} public HapiContractCall(String contract) { - this.abi = FALLBACK_ABI; - this.params = new Object[0]; + this.abi = Optional.of(FALLBACK_ABI); + this.params = Optional.of(new Object[0]); this.contract = contract; } @@ -95,8 +98,8 @@ public HapiContractCall notTryingAsHexedliteral() { } public HapiContractCall(String abi, String contract, Object... params) { - this.abi = abi; - this.params = params; + this.abi = Optional.of(abi); + this.params = Optional.of(params); this.contract = contract; } @@ -159,6 +162,12 @@ public boolean isConvertableToEthCall() { return convertableToEthCall; } + // To convert to Eth call, the key must be SECP256K1 + public boolean isKeySECP256K1(HapiSpec spec) { + var key = spec.registry().getKey(privateKeyRef); + return key == null || key.hasECDSASecp256K1(); + } + public Consumer getResultObserver() { return resultObserver; } @@ -168,11 +177,15 @@ public String getContract() { } public String getAbi() { - return abi; + return abi.orElse(null); } public Object[] getParams() { - return params; + return params.orElse(null); + } + + public void setParams(Object[] params) { + this.params = Optional.of(params); } public String getTxnName() { @@ -208,10 +221,6 @@ public Optional getFee() { return fee; } - public Optional getSubmitDelay() { - return submitDelay; - } - public Optional getValidDurationSeconds() { return validDurationSecs; } @@ -273,17 +282,17 @@ protected Consumer opBodyDef(HapiSpec spec) throws Thro if (details.isPresent()) { ActionableContractCall actionable = spec.registry().getActionableCall(details.get()); contract = actionable.getContract(); - abi = actionable.getDetails().getAbi(); - params = actionable.getDetails().getExampleArgs(); + abi = Optional.of(actionable.getDetails().getAbi()); + params = Optional.of(actionable.getDetails().getExampleArgs()); } else { - paramsFn.ifPresent(hapiApiSpecFunction -> params = hapiApiSpecFunction.apply(spec)); + paramsFn.ifPresent(hapiApiSpecFunction -> params = Optional.of(hapiApiSpecFunction.apply(spec))); } final byte[] callData; if (tupleFn == null) { callData = initializeCallData(); } else { - final var abiFunction = com.esaulpaugh.headlong.abi.Function.fromJson(abi); + final var abiFunction = com.esaulpaugh.headlong.abi.Function.fromJson(abi.orElse(null)); final var inputTuple = tupleFn.apply(spec); callData = abiFunction.encodeCall(inputTuple).array(); } @@ -292,7 +301,9 @@ protected Consumer opBodyDef(HapiSpec spec) throws Thro .body( ContractCallTransactionBody.class, builder -> { if (contract != null) { - if (!tryAsHexedAddressIfLenMatches) { + if (DEFAULT_ID_SENTINEL.equals(contract)) { + builder.setContractID(ContractID.getDefaultInstance()); + } else if (!tryAsHexedAddressIfLenMatches) { builder.setContractID(spec.registry().getContractId(contract)); } else { builder.setContractID(TxnUtils.asContractId(contract, spec)); @@ -312,7 +323,7 @@ protected void updateStateOf(HapiSpec spec) throws Throwable { } if (resultObserver != null) { doObservedLookup(spec, txnSubmitted, txnRecord -> { - final var function = com.esaulpaugh.headlong.abi.Function.fromJson(abi); + final var function = com.esaulpaugh.headlong.abi.Function.fromJson(abi.orElse(null)); final var result = function.decodeReturn(txnRecord .getContractCallResult() .getContractCallResult() @@ -357,6 +368,9 @@ static void doObservedLookup(final HapiSpec spec, final Transaction txn, Consume @Override protected MoreObjects.ToStringHelper toStringHelper() { - return super.toStringHelper().add("contract", contract).add("abi", abi).add("params", Arrays.toString(params)); + return super.toStringHelper() + .add("contract", contract) + .add("abi", abi) + .add("params", Arrays.toString(params.orElse(null))); } } diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/transactions/contract/HapiContractCreate.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/transactions/contract/HapiContractCreate.java index 9dd1570121f6..23998b503d0c 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/transactions/contract/HapiContractCreate.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/transactions/contract/HapiContractCreate.java @@ -31,6 +31,7 @@ import com.hedera.services.bdd.spec.keys.KeyFactory; import com.hedera.services.bdd.spec.keys.SigControl; import com.hedera.services.bdd.spec.transactions.TxnUtils; +import com.hederahashgraph.api.proto.java.AccountID; import com.hederahashgraph.api.proto.java.ContractCreateTransactionBody; import com.hederahashgraph.api.proto.java.ContractGetInfoResponse; import com.hederahashgraph.api.proto.java.ContractID; @@ -47,9 +48,11 @@ import edu.umd.cs.findbugs.annotations.Nullable; import java.lang.reflect.InvocationTargetException; import java.util.ArrayList; +import java.util.EnumSet; import java.util.List; import java.util.Objects; import java.util.Optional; +import java.util.OptionalDouble; import java.util.OptionalLong; import java.util.function.BiConsumer; import java.util.function.Consumer; @@ -81,6 +84,7 @@ public HapiContractCreate( private Optional autoRenewAccount = Optional.empty(); private Optional maxAutomaticTokenAssociations = Optional.empty(); private Optional inlineInitcode = Optional.empty(); + private boolean convertableToEthCreate = true; @Nullable private BiConsumer spec; @@ -201,6 +205,10 @@ public HapiContractCreate autoRenewAccountId(String id) { return this; } + public void args(Optional args) { + this.args = args; + } + @Override public HederaFunctionality type() { return HederaFunctionality.ContractCreate; @@ -226,6 +234,15 @@ public HapiContractCreate declinedReward(boolean isDeclined) { return this; } + public boolean isConvertableToEthCreate() { + return convertableToEthCreate; + } + + public HapiContractCreate refusingEthConversion() { + convertableToEthCreate = false; + return this; + } + @Override protected List> defaultSigners() { if (omitAdminKey || useDeprecatedAdminKey) { @@ -381,4 +398,56 @@ public long numOfCreatedContract() { .map(receipt -> receipt.getContractID().getContractNum()) .orElse(-1L); } + + public boolean getDeferStatusResolution() { + return deferStatusResolution; + } + + public String getTxnName() { + return txnName; + } + + public Optional getAbi() { + return abi; + } + + public Optional getArgs() { + return args; + } + + public String getContract() { + return contract; + } + + public Optional> getFiddler() { + return fiddler; + } + + public Optional getValidDurationSecs() { + return validDurationSecs; + } + + public Optional getCustomTxnId() { + return customTxnId; + } + + public Optional getNode() { + return node; + } + + public OptionalDouble getUsdFee() { + return usdFee; + } + + public Optional getRetryLimits() { + return retryLimits; + } + + public Optional> getPermissiblePrechecks() { + return permissiblePrechecks; + } + + public Optional getFee() { + return fee; + } } diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/transactions/contract/HapiEthereumCall.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/transactions/contract/HapiEthereumCall.java index bd4163427430..7483a49342d7 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/transactions/contract/HapiEthereumCall.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/transactions/contract/HapiEthereumCall.java @@ -107,8 +107,8 @@ public static HapiEthereumCall fromDetails(String actionable) { private HapiEthereumCall() {} public HapiEthereumCall(String contract) { - this.abi = FALLBACK_ABI; - this.params = new Object[0]; + this.abi = Optional.of(FALLBACK_ABI); + this.params = Optional.of(new Object[0]); this.contract = contract; this.payer = Optional.of(RELAYER); } @@ -116,16 +116,16 @@ public HapiEthereumCall(String contract) { public HapiEthereumCall(String account, long amount) { this.account = account; this.valueSent = Optional.of(WEIBARS_TO_TINYBARS.multiply(BigInteger.valueOf(amount))); - this.abi = FALLBACK_ABI; - this.params = new Object[0]; + this.abi = Optional.of(FALLBACK_ABI); + this.params = Optional.of(new Object[0]); this.payer = Optional.of(RELAYER); } public HapiEthereumCall(ByteString account, long amount) { this.alias = account; this.valueSent = Optional.of(WEIBARS_TO_TINYBARS.multiply(BigInteger.valueOf(amount))); - this.abi = FALLBACK_ABI; - this.params = new Object[0]; + this.abi = Optional.of(FALLBACK_ABI); + this.params = Optional.of(new Object[0]); this.payer = Optional.of(RELAYER); } @@ -133,26 +133,24 @@ public static HapiEthereumCall explicitlyTo(@NonNull final byte[] to, long amoun final var call = new HapiEthereumCall(); call.explicitTo = to; call.valueSent = Optional.of(WEIBARS_TO_TINYBARS.multiply(BigInteger.valueOf(amount))); - call.abi = FALLBACK_ABI; - call.params = new Object[0]; + call.abi = Optional.of(FALLBACK_ABI); + call.params = Optional.of(new Object[0]); call.payer = Optional.of(RELAYER); return call; } public HapiEthereumCall(final HapiContractCall contractCall) { - this.abi = contractCall.getAbi(); - this.params = contractCall.getParams(); + this.abi = Optional.of(contractCall.getAbi()); + this.params = Optional.of(contractCall.getParams()); this.contract = contractCall.getContract(); this.txnName = contractCall.getTxnName(); this.gas = contractCall.getGas(); this.expectedStatus = Optional.of(contractCall.getExpectedStatus()); - this.permissibleStatuses = contractCall.getPermissibleStatuses(); this.payer = contractCall.getPayer(); this.expectedPrecheck = Optional.of(contractCall.getExpectedPrecheck()); this.fiddler = contractCall.getFiddler(); this.memo = contractCall.getMemo(); this.fee = contractCall.getFee(); - this.submitDelay = contractCall.getSubmitDelay(); this.validDurationSecs = contractCall.getValidDurationSeconds(); this.customTxnId = contractCall.getCustomTxnId(); this.node = contractCall.getNode(); @@ -180,14 +178,14 @@ public HapiEthereumCall notTryingAsHexedliteral() { } public HapiEthereumCall(String abi, String contract, Object... params) { - this.abi = abi; - this.params = params; + this.abi = Optional.of(abi); + this.params = Optional.of(params); this.contract = contract; } public HapiEthereumCall(boolean isTokenFlow, String abi, String contract, Object... params) { - this.abi = abi; - this.params = params; + this.abi = Optional.of(abi); + this.params = Optional.of(params); this.contract = contract; this.isTokenFlow = isTokenFlow; } @@ -303,9 +301,11 @@ protected Consumer opBodyDef(HapiSpec spec) throws Thro if (details.isPresent()) { ActionableContractCall actionable = spec.registry().getActionableCall(details.get()); contract = actionable.getContract(); - abi = actionable.getDetails().getAbi(); - params = actionable.getDetails().getExampleArgs(); - } else paramsFn.ifPresent(hapiApiSpecFunction -> params = hapiApiSpecFunction.apply(spec)); + abi = Optional.of(actionable.getDetails().getAbi()); + params = Optional.of(actionable.getDetails().getExampleArgs()); + } else { + paramsFn.ifPresent(hapiApiSpecFunction -> params = Optional.of(hapiApiSpecFunction.apply(spec))); + } byte[] callData = initializeCallData(); @@ -388,7 +388,7 @@ protected void updateStateOf(final HapiSpec spec) throws Throwable { } if (resultObserver != null) { doObservedLookup(spec, txnSubmitted, rcd -> { - final var function = com.esaulpaugh.headlong.abi.Function.fromJson(abi); + final var function = com.esaulpaugh.headlong.abi.Function.fromJson(abi.orElse(null)); final var result = function.decodeReturn( rcd.getContractCallResult().getContractCallResult().toByteArray()); resultObserver.accept(result.toList().toArray()); @@ -438,6 +438,9 @@ static void doObservedLookup(final HapiSpec spec, final Transaction txn, Consume @Override protected MoreObjects.ToStringHelper toStringHelper() { - return super.toStringHelper().add("contract", contract).add("abi", abi).add("params", Arrays.toString(params)); + return super.toStringHelper() + .add("contract", contract) + .add("abi", abi) + .add("params", Arrays.toString(params.orElse(null))); } } diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/transactions/contract/HapiEthereumContractCreate.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/transactions/contract/HapiEthereumContractCreate.java index fc8578c7eb2a..3193090365bd 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/transactions/contract/HapiEthereumContractCreate.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/transactions/contract/HapiEthereumContractCreate.java @@ -20,6 +20,7 @@ import static com.hedera.services.bdd.suites.HapiSuite.CHAIN_ID; import static com.hedera.services.bdd.suites.HapiSuite.ETH_HASH_KEY; import static com.hedera.services.bdd.suites.HapiSuite.ETH_SENDER_ADDRESS; +import static com.hedera.services.bdd.suites.HapiSuite.FIVE_HBARS; import static com.hedera.services.bdd.suites.HapiSuite.MAX_CALL_DATA_SIZE; import static com.hedera.services.bdd.suites.HapiSuite.ONE_HUNDRED_HBARS; import static com.hedera.services.bdd.suites.HapiSuite.RELAYER; @@ -29,6 +30,7 @@ import com.esaulpaugh.headlong.util.Integers; import com.google.protobuf.ByteString; import com.hedera.node.app.hapi.utils.ethereum.EthTxData; +import com.hedera.node.app.hapi.utils.ethereum.EthTxData.EthTransactionType; import com.hedera.node.app.hapi.utils.ethereum.EthTxSigs; import com.hedera.services.bdd.spec.HapiSpec; import com.hedera.services.bdd.spec.transactions.TxnUtils; @@ -56,13 +58,14 @@ public class HapiEthereumContractCreate extends HapiBaseContractCreate { private EthTxData.EthTransactionType type; - private long nonce; + private long nonce = 0L; + private boolean useSpecNonce = true; private BigInteger gasPrice = WEIBARS_TO_TINYBARS.multiply(BigInteger.valueOf(50L)); private BigInteger maxFeePerGas = WEIBARS_TO_TINYBARS.multiply(BigInteger.valueOf(50L)); private long maxPriorityGas = 20_000L; private Optional ethFileID = Optional.empty(); private boolean invalidateEthData = false; - private Optional maxGasAllowance = Optional.of(ONE_HUNDRED_HBARS); + private Long maxGasAllowance = ONE_HUNDRED_HBARS; private String privateKeyRef = SECP_256K1_SOURCE_KEY; private Integer chainId = CHAIN_ID; @@ -95,6 +98,56 @@ public HapiEthereumContractCreate(String contract) { super.omitAdminKey = true; } + public HapiEthereumContractCreate( + HapiContractCreate contractCreate, String privateKeyRef, Key adminKey, long defaultGas) { + super(contractCreate.contract); + this.adminKey = adminKey; + this.privateKeyRef = privateKeyRef; + this.type = EthTransactionType.EIP1559; + this.omitAdminKey = contractCreate.omitAdminKey; + this.makeImmutable = contractCreate.makeImmutable; + this.advertiseCreation = contractCreate.advertiseCreation; + this.shouldAlsoRegisterAsAccount = contractCreate.shouldAlsoRegisterAsAccount; + this.useDeprecatedAdminKey = contractCreate.useDeprecatedAdminKey; + this.contract = contractCreate.contract; + this.key = contractCreate.key; + this.autoRenewPeriodSecs = contractCreate.autoRenewPeriodSecs; + this.balance = contractCreate.balance; + this.adminKeyControl = contractCreate.adminKeyControl; + this.adminKeyType = contractCreate.adminKeyType; + this.memo = contractCreate.memo; + this.bytecodeFile = contractCreate.bytecodeFile; + this.bytecodeFileFn = contractCreate.bytecodeFileFn; + this.successCb = contractCreate.successCb; + this.abi = contractCreate.abi; + this.args = contractCreate.args; + this.gasObserver = contractCreate.gasObserver; + this.newNumObserver = contractCreate.newNumObserver; + this.proxy = contractCreate.proxy; + this.explicitHexedParams = contractCreate.explicitHexedParams; + this.stakedAccountId = contractCreate.stakedAccountId; + this.stakedNodeId = contractCreate.stakedNodeId; + this.isDeclinedReward = contractCreate.isDeclinedReward; + final var gas = contractCreate.gas.isPresent() ? contractCreate.gas.getAsLong() : defaultGas; + this.gas(gas); + this.shouldRegisterTxn = true; + this.deferStatusResolution = contractCreate.getDeferStatusResolution(); + this.txnName = contractCreate.getTxnName(); + this.expectedStatus = Optional.of(contractCreate.getExpectedStatus()); + this.expectedPrecheck = Optional.of(contractCreate.getExpectedPrecheck()); + this.fiddler = contractCreate.getFiddler(); + this.validDurationSecs = contractCreate.getValidDurationSecs(); + this.customTxnId = contractCreate.getCustomTxnId(); + this.node = contractCreate.getNode(); + this.usdFee = contractCreate.getUsdFee(); + this.retryLimits = contractCreate.getRetryLimits(); + this.permissibleStatuses = contractCreate.getPermissibleStatuses(); + this.permissiblePrechecks = contractCreate.getPermissiblePrechecks(); + this.payer = contractCreate.getPayer(); + this.fee = contractCreate.getFee(); + this.maxGasAllowance = FIVE_HBARS; + } + public HapiEthereumContractCreate( @NonNull final String contract, @NonNull final BiConsumer spec) { super(contract); @@ -139,8 +192,7 @@ public HapiEthereumContractCreate autoRenewSecs(long period) { } public HapiEthereumContractCreate balance(long initial) { - balance = Optional.of( - WEIBARS_TO_TINYBARS.multiply(BigInteger.valueOf(initial)).longValueExact()); + balance = Optional.of(initial); return this; } @@ -155,7 +207,7 @@ public HapiEthereumContractCreate entityMemo(String s) { } public HapiEthereumContractCreate maxGasAllowance(long maxGasAllowance) { - this.maxGasAllowance = Optional.of(maxGasAllowance); + this.maxGasAllowance = maxGasAllowance; return this; } @@ -171,6 +223,7 @@ public HapiEthereumContractCreate type(EthTxData.EthTransactionType type) { public HapiEthereumContractCreate nonce(long nonce) { this.nonce = nonce; + useSpecNonce = false; return this; } @@ -213,13 +266,22 @@ protected Consumer opBodyDef(HapiSpec spec) throws Thro final var filePath = Utils.getResourcePath(bytecodeFile.get(), ".bin"); final var fileContents = Utils.extractByteCode(filePath); - final byte[] callData = - Bytes.fromHexString(new String(fileContents.toByteArray())).toArray(); + ByteString bytecode = fileContents; + if (args.isPresent() && abi.isPresent()) { + bytecode = bytecode.concat(TxnUtils.constructorArgsToByteString(abi.get(), args.get())); + } + final var callData = + Bytes.fromHexString(new String(bytecode.toByteArray())).toArray(); + final var gasPriceBytes = gasLongToBytes(gasPrice.longValueExact()); final var maxFeePerGasBytes = gasLongToBytes(maxFeePerGas.longValueExact()); final var maxPriorityGasBytes = gasLongToBytes(maxPriorityGas); + if (useSpecNonce) { + nonce = spec.getNonce(privateKeyRef); + spec.incrementNonce(privateKeyRef); + } final var ethTxData = new EthTxData( null, type, @@ -230,7 +292,7 @@ protected Consumer opBodyDef(HapiSpec spec) throws Thro maxFeePerGasBytes, gas.orElse(0L), new byte[] {}, - BigInteger.valueOf(balance.orElse(0L)), + weibarsToTinybars(balance).orElse(BigInteger.ZERO), callData, new byte[] {}, 0, @@ -261,9 +323,25 @@ protected Consumer opBodyDef(HapiSpec spec) throws Thro builder.setEthereumData(ByteString.copyFrom(ethData.encodeTx())); } ethFileID.ifPresent(builder::setCallData); - maxGasAllowance.ifPresent(builder::setMaxGasAllowance); + builder.setMaxGasAllowance(maxGasAllowance); }); - return b -> b.setEthereumTransaction(opBody); + + return b -> { + this.fee.ifPresent(b::setTransactionFee); + this.memo.ifPresent(b::setMemo); + b.setEthereumTransaction(opBody); + }; + } + + private Optional weibarsToTinybars(Optional balance) { + if (balance.isEmpty()) { + return Optional.empty(); + } + try { + return Optional.of(WEIBARS_TO_TINYBARS.multiply(BigInteger.valueOf(balance.get()))); + } catch (ArithmeticException e) { + return Optional.empty(); + } } @Override diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/transactions/crypto/HapiCryptoCreate.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/transactions/crypto/HapiCryptoCreate.java index d48bfa69e0db..b3b3f4bf36b9 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/transactions/crypto/HapiCryptoCreate.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/transactions/crypto/HapiCryptoCreate.java @@ -16,11 +16,13 @@ package com.hedera.services.bdd.spec.transactions.crypto; +import static com.hedera.services.bdd.spec.HapiPropertySource.idAsHeadlongAddress; import static com.hedera.services.bdd.spec.keys.KeyFactory.KeyType; import static com.hedera.services.bdd.spec.transactions.TxnFactory.bannerWith; import static com.hedera.services.bdd.spec.transactions.TxnUtils.asId; import static com.hedera.services.bdd.spec.transactions.TxnUtils.netOf; import static com.hedera.services.bdd.spec.transactions.TxnUtils.suFrom; +import static com.hedera.services.bdd.spec.transactions.contract.HapiParserUtil.evmAddressFromSecp256k1Key; import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.SUCCESS; import com.esaulpaugh.headlong.abi.Address; @@ -44,6 +46,8 @@ import com.hederahashgraph.api.proto.java.FeeData; import com.hederahashgraph.api.proto.java.HederaFunctionality; import com.hederahashgraph.api.proto.java.Key; +import com.hederahashgraph.api.proto.java.RealmID; +import com.hederahashgraph.api.proto.java.ShardID; import com.hederahashgraph.api.proto.java.TokenID; import com.hederahashgraph.api.proto.java.Transaction; import com.hederahashgraph.api.proto.java.TransactionBody; @@ -94,6 +98,8 @@ public class HapiCryptoCreate extends HapiTxnOp { private Consumer

    addressObserver; private boolean fuzzingIdentifiers = false; private boolean setEvmAddressAliasFromKey = false; + private Optional shardId = Optional.empty(); + private Optional realmId = Optional.empty(); @Override public HederaFunctionality type() { @@ -229,6 +235,16 @@ public HapiCryptoCreate alias(final ByteString alias) { return this; } + public HapiCryptoCreate shardId(final ShardID shardID) { + this.shardId = Optional.of(shardID); + return this; + } + + public HapiCryptoCreate realmId(final RealmID realmID) { + this.realmId = Optional.of(realmID); + return this; + } + public HapiCryptoCreate evmAddress(final ByteString evmAddress) { this.evmAddress = Optional.of(evmAddress); return this; @@ -291,7 +307,8 @@ protected Consumer opBodyDef(final HapiSpec spec) throw autoRenewDurationSecs.ifPresent(s -> b.setAutoRenewPeriod( Duration.newBuilder().setSeconds(s).build())); maxAutomaticTokenAssociations.ifPresent(b::setMaxAutomaticTokenAssociations); - + shardId.ifPresent(b::setShardID); + realmId.ifPresent(b::setRealmID); if (stakedAccountId.isPresent()) { b.setStakedAccountId(asId(stakedAccountId.get(), spec)); } else if (stakedNodeId.isPresent()) { @@ -334,7 +351,10 @@ protected void updateStateOf(final HapiSpec spec) { Optional.ofNullable(addressObserver) .ifPresent(obs -> evmAddress.ifPresentOrElse( address -> obs.accept(HapiParserUtil.asHeadlongAddress(address.toByteArray())), - () -> obs.accept(HapiParserUtil.evmAddressFromSecp256k1Key(key)))); + () -> obs.accept( + key.hasECDSASecp256K1() + ? evmAddressFromSecp256k1Key(key) + : idAsHeadlongAddress(lastReceipt.getAccountID())))); if (advertiseCreation) { final String banner = "\n\n" diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/transactions/token/HapiTokenUpdate.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/transactions/token/HapiTokenUpdate.java index 5cb09b4b1016..94305e6d7f82 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/transactions/token/HapiTokenUpdate.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/transactions/token/HapiTokenUpdate.java @@ -87,9 +87,23 @@ public class HapiTokenUpdate extends HapiTxnOp { private Optional> newSupplyKeySupplier = Optional.empty(); private Optional> newSymbolFn = Optional.empty(); private Optional> newNameFn = Optional.empty(); - private boolean useImproperEmptyKey = false; - private boolean useEmptyAdminKeyList = false; + private boolean useEmptyAdminKey = false; + private boolean useEmptyWipeKey = false; + private boolean useEmptyKycKey = false; + private boolean useEmptySupplyKey = false; + private boolean useEmptyFreezeKey = false; + private boolean useEmptyFeeScheduleKey = false; + private boolean useEmptyMetadataKey = false; + private boolean useEmptyPauseKey = false; + private boolean useInvalidAdminKey = false; + private boolean useInvalidWipeKey = false; + private boolean useInvalidKycKey = false; + private boolean useInvalidSupplyKey = false; + private boolean useInvalidFreezeKey = false; private boolean useInvalidFeeScheduleKey = false; + private boolean useInvalidMetadataKey = false; + private boolean useInvalidPauseKey = false; + private boolean noKeyValidation = false; private Optional contractKeyName = Optional.empty(); private Set contractKeyAppliedTo = Set.of(); @@ -219,13 +233,68 @@ public HapiTokenUpdate expiry(long at) { return this; } - public HapiTokenUpdate improperlyEmptyingAdminKey() { - useImproperEmptyKey = true; + public HapiTokenUpdate properlyEmptyingAdminKey() { + useEmptyAdminKey = true; return this; } - public HapiTokenUpdate properlyEmptyingAdminKey() { - useEmptyAdminKeyList = true; + public HapiTokenUpdate properlyEmptyingWipeKey() { + useEmptyWipeKey = true; + return this; + } + + public HapiTokenUpdate properlyEmptyingKycKey() { + useEmptyKycKey = true; + return this; + } + + public HapiTokenUpdate properlyEmptyingSupplyKey() { + useEmptySupplyKey = true; + return this; + } + + public HapiTokenUpdate properlyEmptyingFreezeKey() { + useEmptyFreezeKey = true; + return this; + } + + public HapiTokenUpdate properlyEmptyingPauseKey() { + useEmptyPauseKey = true; + return this; + } + + public HapiTokenUpdate properlyEmptyingFeeScheduleKey() { + useEmptyFeeScheduleKey = true; + return this; + } + + public HapiTokenUpdate properlyEmptyingMetadataKey() { + useEmptyMetadataKey = true; + return this; + } + + public HapiTokenUpdate usingInvalidAdminKey() { + useInvalidAdminKey = true; + return this; + } + + public HapiTokenUpdate usingInvalidWipeKey() { + useInvalidWipeKey = true; + return this; + } + + public HapiTokenUpdate usingInvalidKycKey() { + useInvalidKycKey = true; + return this; + } + + public HapiTokenUpdate usingInvalidSupplyKey() { + useInvalidSupplyKey = true; + return this; + } + + public HapiTokenUpdate usingInvalidFreezeKey() { + useInvalidFreezeKey = true; return this; } @@ -234,6 +303,21 @@ public HapiTokenUpdate usingInvalidFeeScheduleKey() { return this; } + public HapiTokenUpdate usingInvalidMetadataKey() { + useInvalidMetadataKey = true; + return this; + } + + public HapiTokenUpdate usingInvalidPauseKey() { + useInvalidPauseKey = true; + return this; + } + + public HapiTokenUpdate applyNoValidationToKeys() { + noKeyValidation = true; + return this; + } + public HapiTokenUpdate contractKey(final Set contractKeyAppliedTo, final String contractKeyName) { this.contractKeyName = Optional.of(contractKeyName); this.contractKeyAppliedTo = contractKeyAppliedTo; @@ -306,31 +390,79 @@ protected Consumer opBodyDef(HapiSpec spec) throws Thro newName.ifPresent(b::setName); newMemo.ifPresent(s -> b.setMemo( StringValue.newBuilder().setValue(s).build())); - if (useImproperEmptyKey) { - b.setAdminKey(TxnUtils.EMPTY_THRESHOLD_KEY); - } else if (useEmptyAdminKeyList) { + if (useInvalidAdminKey) { + b.setAdminKey(TxnUtils.ALL_ZEROS_INVALID_KEY); + } else if (useEmptyAdminKey) { b.setAdminKey(TxnUtils.EMPTY_KEY_LIST); } else { newAdminKey.ifPresent( a -> b.setAdminKey(spec.registry().getKey(a))); } newTreasury.ifPresent(a -> b.setTreasury(asId(a, spec))); - newSupplyKey.ifPresent( - k -> b.setSupplyKey(spec.registry().getKey(k))); + if (useInvalidSupplyKey) { + b.setSupplyKey(TxnUtils.ALL_ZEROS_INVALID_KEY); + } else if (useEmptySupplyKey) { + b.setSupplyKey(TxnUtils.EMPTY_KEY_LIST); + } else { + newSupplyKey.ifPresent( + k -> b.setSupplyKey(spec.registry().getKey(k))); + } newSupplyKeySupplier.ifPresent(s -> b.setSupplyKey(s.get())); - newWipeKey.ifPresent( - k -> b.setWipeKey(spec.registry().getKey(k))); - newKycKey.ifPresent(k -> b.setKycKey(spec.registry().getKey(k))); + if (useInvalidWipeKey) { + b.setWipeKey(TxnUtils.ALL_ZEROS_INVALID_KEY); + } else if (useEmptyWipeKey) { + b.setWipeKey(TxnUtils.EMPTY_KEY_LIST); + } else { + newWipeKey.ifPresent( + k -> b.setWipeKey(spec.registry().getKey(k))); + } + if (useInvalidKycKey) { + b.setKycKey(TxnUtils.ALL_ZEROS_INVALID_KEY); + } else if (useEmptyKycKey) { + b.setKycKey(TxnUtils.EMPTY_KEY_LIST); + } else { + newKycKey.ifPresent( + k -> b.setKycKey(spec.registry().getKey(k))); + } if (useInvalidFeeScheduleKey) { - b.setFeeScheduleKey(TxnUtils.EMPTY_THRESHOLD_KEY); + b.setFeeScheduleKey(TxnUtils.ALL_ZEROS_INVALID_KEY); + } else if (useEmptyFeeScheduleKey) { + b.setFeeScheduleKey(TxnUtils.EMPTY_KEY_LIST); } else { newFeeScheduleKey.ifPresent( k -> b.setFeeScheduleKey(spec.registry().getKey(k))); } - newFreezeKey.ifPresent( - k -> b.setFreezeKey(spec.registry().getKey(k))); - newPauseKey.ifPresent( - k -> b.setPauseKey(spec.registry().getKey(k))); + if (useInvalidFreezeKey) { + b.setFreezeKey(TxnUtils.ALL_ZEROS_INVALID_KEY); + } else if (useEmptyFreezeKey) { + b.setFreezeKey(TxnUtils.EMPTY_KEY_LIST); + } else { + newFreezeKey.ifPresent( + k -> b.setFreezeKey(spec.registry().getKey(k))); + } + if (useInvalidPauseKey) { + b.setPauseKey(TxnUtils.ALL_ZEROS_INVALID_KEY); + } else if (useEmptyPauseKey) { + b.setPauseKey(TxnUtils.EMPTY_KEY_LIST); + } else { + newPauseKey.ifPresent( + k -> b.setPauseKey(spec.registry().getKey(k))); + } + if (useInvalidMetadataKey) { + b.setMetadataKey(TxnUtils.ALL_ZEROS_INVALID_KEY); + } else if (useEmptyMetadataKey) { + b.setMetadataKey(TxnUtils.EMPTY_KEY_LIST); + } else { + newMetadataKey.ifPresent( + k -> b.setMetadataKey(spec.registry().getKey(k))); + } + if (newMetadata.isPresent()) { + var metadataValue = BytesValue.newBuilder() + .setValue(ByteString.copyFrom( + newMetadata.orElseThrow().getBytes())) + .build(); + b.setMetadata(metadataValue); + } if (autoRenewAccount.isPresent()) { var autoRenewId = TxnUtils.asId(autoRenewAccount.get(), spec); b.setAutoRenewAccount(autoRenewId); @@ -339,6 +471,11 @@ protected Consumer opBodyDef(HapiSpec spec) throws Thro Timestamp.newBuilder().setSeconds(t).build())); autoRenewPeriod.ifPresent(secs -> b.setAutoRenewPeriod( Duration.newBuilder().setSeconds(secs).build())); + if (noKeyValidation) { + b.setKeyVerificationMode(TokenKeyValidation.NO_VALIDATION); + } else { + b.setKeyVerificationMode(TokenKeyValidation.FULL_VALIDATION); + } // We often want to use an existing contract to control the keys of various types (supply, // freeze etc.) // of a token, and in this case we need to use a Key{contractID=0.0.X} as the key; so for @@ -362,15 +499,6 @@ protected Consumer opBodyDef(HapiSpec spec) throws Thro } } } - newMetadataKey.ifPresent( - k -> b.setMetadataKey(spec.registry().getKey(k))); - if (newMetadata.isPresent()) { - var metadataValue = BytesValue.newBuilder() - .setValue(ByteString.copyFrom( - newMetadata.orElseThrow().getBytes())) - .build(); - b.setMetadata(metadataValue); - } }); return b -> b.setTokenUpdate(opBody); } @@ -396,9 +524,30 @@ protected void updateStateOf(HapiSpec spec) { return; } var registry = spec.registry(); - if (useEmptyAdminKeyList) { + if (useEmptyAdminKey) { registry.forgetAdminKey(token); } + if (useEmptyKycKey) { + registry.forgetKycKey(token); + } + if (useEmptyWipeKey) { + registry.forgetWipeKey(token); + } + if (useEmptySupplyKey) { + registry.forgetSupplyKey(token); + } + if (useEmptyFreezeKey) { + registry.forgetFreezeKey(token); + } + if (useEmptyFeeScheduleKey) { + registry.forgetFeeScheduleKey(token); + } + if (useEmptyPauseKey) { + registry.forgetPauseKey(token); + } + if (useEmptyMetadataKey) { + registry.forgetMetadataKey(token); + } newMemo.ifPresent(m -> registry.saveMemo(token, m)); newAdminKey.ifPresent(n -> registry.saveAdminKey(token, registry.getKey(n))); newSymbol.ifPresent(s -> registry.saveSymbol(token, s)); @@ -409,6 +558,8 @@ protected void updateStateOf(HapiSpec spec) { newKycKey.ifPresent(n -> registry.saveKycKey(token, registry.getKey(n))); newFeeScheduleKey.ifPresent(n -> registry.saveFeeScheduleKey(token, registry.getKey(n))); newPauseKey.ifPresent(n -> registry.savePauseKey(token, registry.getKey(n))); + newMetadataKey.ifPresent(n -> registry.saveMetadataKey(token, registry.getKey(n))); + newMetadata.ifPresent(n -> registry.saveMetadata(token, n)); } @Override diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/utilops/CustomSpecAssert.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/utilops/CustomSpecAssert.java index c9234b49ff52..4258124722c8 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/utilops/CustomSpecAssert.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/utilops/CustomSpecAssert.java @@ -19,6 +19,7 @@ import static com.hedera.services.bdd.spec.utilops.UtilStateChange.createEthereumAccountForSpec; import static com.hedera.services.bdd.spec.utilops.UtilStateChange.isEthereumAccountCreatedForSpec; import static com.hedera.services.bdd.spec.utilops.UtilVerbs.convertHapiCallsToEthereumCalls; +import static com.hedera.services.bdd.suites.HapiSuite.SECP_256K1_SOURCE_KEY; import com.hedera.services.bdd.spec.HapiSpec; import com.hedera.services.bdd.spec.HapiSpecOperation; @@ -48,7 +49,12 @@ private static void executeHederaOps(final HapiSpec spec, final List ops) { - final var convertedOps = convertHapiCallsToEthereumCalls(ops); + final var convertedOps = convertHapiCallsToEthereumCalls( + ops, + SECP_256K1_SOURCE_KEY, + spec.registry().getKey(SECP_256K1_SOURCE_KEY), + spec.setup().defaultCreateGas(), + spec); for (final var op : convertedOps) { handleExec(spec, op); } diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/utilops/LoadTest.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/utilops/LoadTest.java index c2491c617152..80d098bec204 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/utilops/LoadTest.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/utilops/LoadTest.java @@ -25,7 +25,6 @@ import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.PLATFORM_TRANSACTION_NOT_CREATED; import static java.util.concurrent.TimeUnit.MINUTES; -import com.hedera.services.bdd.spec.HapiSpec; import com.hedera.services.bdd.spec.HapiSpecOperation; import com.hedera.services.bdd.suites.HapiSuite; import com.hedera.services.bdd.suites.perf.PerfTestLoadSettings; @@ -36,8 +35,10 @@ import java.util.OptionalInt; import java.util.OptionalLong; import java.util.function.Supplier; +import java.util.stream.Stream; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; +import org.junit.jupiter.api.DynamicTest; public class LoadTest extends HapiSuite { private static final Logger log = LogManager.getLogger(LoadTest.class); @@ -185,7 +186,7 @@ protected Logger getResultsLogger() { } @Override - public List getSpecsInSuite() { + public List> getSpecsInSuite() { return null; } } diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/utilops/ProviderRun.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/utilops/ProviderRun.java index b97fff9e9566..b18d323adeca 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/utilops/ProviderRun.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/utilops/ProviderRun.java @@ -16,6 +16,7 @@ package com.hedera.services.bdd.spec.utilops; +import static com.hedera.services.bdd.spec.transactions.TxnUtils.turnLoggingOff; import static com.hedera.services.bdd.spec.utilops.CustomSpecAssert.allRunFor; import static com.hedera.services.bdd.spec.utilops.UtilVerbs.inParallel; import static java.util.concurrent.TimeUnit.MILLISECONDS; @@ -58,6 +59,7 @@ public class ProviderRun extends UtilOp { private LongSupplier durationSupplier = () -> DEFAULT_DURATION; private Supplier unitSupplier = () -> DEFAULT_UNIT; private IntSupplier totalOpsToSubmit = () -> DEFAULT_TOTAL_OPS_TO_SUBMIT; + private boolean loggingOff = false; private Map counts = new HashMap<>(); @@ -78,8 +80,13 @@ public ProviderRun lasting(LongSupplier durationSupplier, Supplier uni return this; } - public ProviderRun totalOpsToSumbit(IntSupplier totalOpsSupplier) { - this.totalOpsToSubmit = totalOpsSupplier; + public ProviderRun loggingOff() { + this.loggingOff = true; + return this; + } + + public ProviderRun maxOpsPerSec(final int maxOpsPerSec) { + this.maxOpsPerSecSupplier = () -> maxOpsPerSec; return this; } @@ -108,7 +115,9 @@ protected boolean submitOp(HapiSpec spec) { OpProvider provider = providerFn.apply(spec); allRunFor(spec, provider.suggestedInitializers().toArray(new HapiSpecOperation[0])); - log.info("Finished initialization for provider run..."); + if (!loggingOff) { + log.info("Finished initialization for provider run..."); + } TimeUnit unit = unitSupplier.get(); Stopwatch stopwatch = Stopwatch.createStarted(); @@ -132,7 +141,7 @@ protected boolean submitOp(HapiSpec spec) { if (elapsedMs > nextLogTargetMs) { nextLogTargetMs += logIncrementMs; long delta = duration - stopwatch.elapsed(unit); - if (delta != lastDeltaLogged) { + if (delta != lastDeltaLogged && !loggingOff) { String message = String.format( "%d %s%s left in test - %d ops submitted so far (%d pending).", delta, @@ -152,7 +161,9 @@ protected boolean submitOp(HapiSpec spec) { if (numPending > 0) { continue; } - log.info("Finished submission of total {} operations", totalOpsToSubmit.getAsInt()); + if (!loggingOff) { + log.info("Finished submission of total {} operations", totalOpsToSubmit.getAsInt()); + } break; } if (numPending < MAX_PENDING_OPS) { @@ -167,8 +178,12 @@ protected boolean submitOp(HapiSpec spec) { : MAX_OPS_PER_SEC - opsThisSecond.get())) .mapToObj(ignore -> provider.get()) .flatMap(Optional::stream) - .filter(op -> op != Stream.empty()) - .peek(op -> counts.get(op.type()).getAndIncrement()) + .peek(op -> { + counts.get(op.type()).getAndIncrement(); + if (loggingOff) { + turnLoggingOff(op); + } + }) .toArray(HapiSpecOperation[]::new); if (burst.length > 0) { allRunFor(spec, inParallel(burst)); diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/utilops/UtilVerbs.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/utilops/UtilVerbs.java index 436652a89b68..494e33b22fbd 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/utilops/UtilVerbs.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/utilops/UtilVerbs.java @@ -16,6 +16,9 @@ package com.hedera.services.bdd.spec.utilops; +import static com.hedera.node.app.service.contract.impl.utils.ConversionUtils.explicitFromHeadlong; +import static com.hedera.node.app.service.contract.impl.utils.ConversionUtils.isLongZeroAddress; +import static com.hedera.node.app.service.contract.impl.utils.ConversionUtils.numberOfLongZero; import static com.hedera.services.bdd.spec.HapiPropertySource.asAccount; import static com.hedera.services.bdd.spec.HapiPropertySource.asAccountString; import static com.hedera.services.bdd.spec.assertions.ContractInfoAsserts.contractWith; @@ -34,26 +37,23 @@ import static com.hedera.services.bdd.spec.transactions.TxnVerbs.fileUpdate; import static com.hedera.services.bdd.spec.transactions.TxnVerbs.submitMessageTo; import static com.hedera.services.bdd.spec.transactions.TxnVerbs.tokenDissociate; +import static com.hedera.services.bdd.spec.transactions.TxnVerbs.updateInitCodeWithConstructorArgs; import static com.hedera.services.bdd.spec.transactions.crypto.HapiCryptoTransfer.tinyBarsFromTo; import static com.hedera.services.bdd.spec.transactions.file.HapiFileUpdate.getUpdated121; import static com.hedera.services.bdd.spec.transactions.token.TokenMovement.moving; import static com.hedera.services.bdd.spec.utilops.CustomSpecAssert.allRunFor; import static com.hedera.services.bdd.spec.utilops.CustomSpecAssert.log; -import static com.hedera.services.bdd.spec.utilops.lifecycle.selectors.NodeSelector.allNodes; -import static com.hedera.services.bdd.spec.utilops.lifecycle.selectors.NodeSelector.byName; import static com.hedera.services.bdd.spec.utilops.pauses.HapiSpecWaitUntil.untilJustBeforeStakingPeriod; import static com.hedera.services.bdd.spec.utilops.pauses.HapiSpecWaitUntil.untilStartOfNextAdhocPeriod; import static com.hedera.services.bdd.spec.utilops.pauses.HapiSpecWaitUntil.untilStartOfNextStakingPeriod; import static com.hedera.services.bdd.suites.HapiSuite.APP_PROPERTIES; import static com.hedera.services.bdd.suites.HapiSuite.EXCHANGE_RATE_CONTROL; -import static com.hedera.services.bdd.suites.HapiSuite.FALSE_VALUE; import static com.hedera.services.bdd.suites.HapiSuite.FEE_SCHEDULE; import static com.hedera.services.bdd.suites.HapiSuite.GENESIS; import static com.hedera.services.bdd.suites.HapiSuite.ONE_HBAR; import static com.hedera.services.bdd.suites.HapiSuite.ONE_HUNDRED_HBARS; -import static com.hedera.services.bdd.suites.TargetNetworkType.HAPI_TEST_NETWORK; +import static com.hedera.services.bdd.suites.TargetNetworkType.SHARED_HAPI_TEST_NETWORK; import static com.hedera.services.bdd.suites.contract.Utils.asAddress; -import static com.hedera.services.bdd.suites.contract.traceability.TraceabilitySuite.SIDECARS_PROP; import static com.hederahashgraph.api.proto.java.FreezeType.FREEZE_ABORT; import static com.hederahashgraph.api.proto.java.FreezeType.FREEZE_ONLY; import static com.hederahashgraph.api.proto.java.FreezeType.FREEZE_UPGRADE; @@ -67,7 +67,10 @@ import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.PLATFORM_TRANSACTION_NOT_CREATED; import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.SUCCESS; import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.SUCCESS_BUT_MISSING_EXPECTED_OPERATION; -import static com.swirlds.common.stream.LinkedObjectStreamUtilities.generateStreamFileNameFromInstant; +import static com.swirlds.platform.system.status.PlatformStatus.ACTIVE; +import static com.swirlds.platform.system.status.PlatformStatus.BEHIND; +import static com.swirlds.platform.system.status.PlatformStatus.FREEZE_COMPLETE; +import static com.swirlds.platform.system.status.PlatformStatus.RECONNECT_COMPLETE; import static java.util.Objects.requireNonNull; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertTrue; @@ -75,7 +78,8 @@ import com.esaulpaugh.headlong.abi.Address; import com.esaulpaugh.headlong.abi.Tuple; import com.google.protobuf.ByteString; -import com.hedera.services.bdd.junit.RecordStreamValidator; +import com.hedera.services.bdd.junit.hedera.NodeSelector; +import com.hedera.services.bdd.junit.support.RecordStreamValidator; import com.hedera.services.bdd.spec.HapiPropertySource; import com.hedera.services.bdd.spec.HapiSpec; import com.hedera.services.bdd.spec.HapiSpecOperation; @@ -87,7 +91,9 @@ import com.hedera.services.bdd.spec.transactions.HapiTxnOp; import com.hedera.services.bdd.spec.transactions.consensus.HapiMessageSubmit; import com.hedera.services.bdd.spec.transactions.contract.HapiContractCall; +import com.hedera.services.bdd.spec.transactions.contract.HapiContractCreate; import com.hedera.services.bdd.spec.transactions.contract.HapiEthereumCall; +import com.hedera.services.bdd.spec.transactions.contract.HapiEthereumContractCreate; import com.hedera.services.bdd.spec.transactions.contract.HapiParserUtil; import com.hedera.services.bdd.spec.transactions.crypto.HapiCryptoTransfer; import com.hedera.services.bdd.spec.transactions.file.HapiFileAppend; @@ -115,13 +121,9 @@ import com.hedera.services.bdd.spec.utilops.inventory.SpecKeyFromMutation; import com.hedera.services.bdd.spec.utilops.inventory.SpecKeyFromPem; import com.hedera.services.bdd.spec.utilops.inventory.UsableTxnId; -import com.hedera.services.bdd.spec.utilops.lifecycle.ops.ShutDownNodesOp; -import com.hedera.services.bdd.spec.utilops.lifecycle.ops.StartNodesOp; -import com.hedera.services.bdd.spec.utilops.lifecycle.ops.WaitForActiveOp; -import com.hedera.services.bdd.spec.utilops.lifecycle.ops.WaitForBehindOp; -import com.hedera.services.bdd.spec.utilops.lifecycle.ops.WaitForFreezeOp; -import com.hedera.services.bdd.spec.utilops.lifecycle.ops.WaitForReconnectOp; -import com.hedera.services.bdd.spec.utilops.lifecycle.ops.WaitForShutdownOp; +import com.hedera.services.bdd.spec.utilops.lifecycle.ops.ShutdownWithinOp; +import com.hedera.services.bdd.spec.utilops.lifecycle.ops.TryToStartNodesOp; +import com.hedera.services.bdd.spec.utilops.lifecycle.ops.WaitForStatusOp; import com.hedera.services.bdd.spec.utilops.mod.QueryModification; import com.hedera.services.bdd.spec.utilops.mod.QueryModificationsOp; import com.hedera.services.bdd.spec.utilops.mod.SubmitModificationsOp; @@ -133,7 +135,6 @@ import com.hedera.services.bdd.spec.utilops.records.SnapshotMode; import com.hedera.services.bdd.spec.utilops.records.SnapshotModeOp; import com.hedera.services.bdd.spec.utilops.streams.RecordAssertions; -import com.hedera.services.bdd.spec.utilops.streams.RecordFileChecker; import com.hedera.services.bdd.spec.utilops.streams.RecordStreamVerification; import com.hedera.services.bdd.spec.utilops.streams.assertions.AssertingBiConsumer; import com.hedera.services.bdd.spec.utilops.streams.assertions.CryptoCreateAssertion; @@ -142,13 +143,9 @@ import com.hedera.services.bdd.spec.utilops.streams.assertions.RecordStreamAssertion; import com.hedera.services.bdd.spec.utilops.streams.assertions.TransactionBodyAssertion; import com.hedera.services.bdd.spec.utilops.streams.assertions.ValidContractIdsAssertion; -import com.hedera.services.bdd.spec.utilops.throughput.FinishThroughputObs; -import com.hedera.services.bdd.spec.utilops.throughput.StartThroughputObs; import com.hedera.services.bdd.suites.HapiSuite; import com.hedera.services.bdd.suites.crypto.CryptoTransferSuite; import com.hedera.services.bdd.suites.perf.PerfTestLoadSettings; -import com.hedera.services.bdd.suites.perf.topic.HCSChunkingRealisticPerfSuite; -import com.hedera.services.bdd.suites.utils.RecordStreamType; import com.hedera.services.bdd.suites.utils.sysfiles.serdes.FeesJsonToGrpcBytes; import com.hedera.services.bdd.suites.utils.sysfiles.serdes.SysFileSerde; import com.hederahashgraph.api.proto.java.AccountAmount; @@ -158,10 +155,10 @@ import com.hederahashgraph.api.proto.java.FeeData; import com.hederahashgraph.api.proto.java.FeeSchedule; import com.hederahashgraph.api.proto.java.HederaFunctionality; +import com.hederahashgraph.api.proto.java.Key; import com.hederahashgraph.api.proto.java.Query; import com.hederahashgraph.api.proto.java.ResponseCodeEnum; import com.hederahashgraph.api.proto.java.Setting; -import com.hederahashgraph.api.proto.java.Timestamp; import com.hederahashgraph.api.proto.java.TokenID; import com.hederahashgraph.api.proto.java.Transaction; import com.hederahashgraph.api.proto.java.TransactionBody; @@ -205,6 +202,8 @@ public class UtilVerbs { + public static final int DEFAULT_COLLISION_AVOIDANCE_FACTOR = 2; + /** * Private constructor to prevent instantiation. * @@ -253,11 +252,11 @@ public static RecordSystemProperty recordSystemProperty( } public static NetworkTypeFilterOp ifHapiTest(@NonNull final HapiSpecOperation... ops) { - return new NetworkTypeFilterOp(EnumSet.of(HAPI_TEST_NETWORK), ops); + return new NetworkTypeFilterOp(EnumSet.of(SHARED_HAPI_TEST_NETWORK), ops); } public static NetworkTypeFilterOp ifNotHapiTest(@NonNull final HapiSpecOperation... ops) { - return new NetworkTypeFilterOp(EnumSet.complementOf(EnumSet.of(HAPI_TEST_NETWORK)), ops); + return new NetworkTypeFilterOp(EnumSet.complementOf(EnumSet.of(SHARED_HAPI_TEST_NETWORK)), ops); } public static EnvFilterOp ifCi(@NonNull final HapiSpecOperation... ops) { @@ -289,6 +288,12 @@ public static SubmitModificationsOp submitModified( return new SubmitModificationsOp(txnOpSupplier, modificationsFn); } + public static SubmitModificationsOp submitModifiedWithFixedPayer( + @NonNull final Function> modificationsFn, + @NonNull final Supplier> txnOpSupplier) { + return new SubmitModificationsOp(false, txnOpSupplier, modificationsFn); + } + /** * Returns an operation that repeatedly sends a query from the given * supplier, but each time after modifying the query with one of the @@ -308,6 +313,12 @@ public static QueryModificationsOp sendModified( return new QueryModificationsOp(queryOpSupplier, modificationsFn); } + public static QueryModificationsOp sendModifiedWithFixedPayer( + @NonNull final Function> modificationsFn, + @NonNull final Supplier> queryOpSupplier) { + return new QueryModificationsOp(false, queryOpSupplier, modificationsFn); + } + public static SourcedOp sourcing(Supplier source) { return new SourcedOp(source); } @@ -320,56 +331,44 @@ public static ContextualActionOp doingContextual(Consumer action) { return new ContextualActionOp(action); } - public static WaitForActiveOp waitForNodeToBecomeActive(String name, int waitSeconds) { - return new WaitForActiveOp(byName(name), waitSeconds); - } - - public static WaitForActiveOp waitForNodesToBecomeActive(int waitSeconds) { - return new WaitForActiveOp(allNodes(), waitSeconds); - } - - public static WaitForActiveOp waitForNodeToBecomeBehind(String name, int waitSeconds) { - return new WaitForActiveOp(byName(name), waitSeconds); - } - - public static WaitForFreezeOp waitForNodeToFreeze(String name, int waitSeconds) { - return new WaitForFreezeOp(byName(name), waitSeconds); + public static WaitForStatusOp waitForActive(String name, Duration timeout) { + return new WaitForStatusOp(NodeSelector.byName(name), ACTIVE, timeout); } - public static StartNodesOp startAllNodes() { - return new StartNodesOp(allNodes()); + public static WaitForStatusOp waitForBehind(String name, Duration timeout) { + return new WaitForStatusOp(NodeSelector.byName(name), BEHIND, timeout); } - public static StartNodesOp startNode(String name) { - return new StartNodesOp(byName(name)); + public static WaitForStatusOp waitForReconnectComplete(String name, Duration timeout) { + return new WaitForStatusOp(NodeSelector.byName(name), RECONNECT_COMPLETE, timeout); } - public static ShutDownNodesOp shutDownAllNodes() { - return new ShutDownNodesOp(allNodes()); + public static WaitForStatusOp waitForFreezeComplete(String name, Duration timeout) { + return new WaitForStatusOp(NodeSelector.byName(name), FREEZE_COMPLETE, timeout); } - public static ShutDownNodesOp shutDownNode(String name) { - return new ShutDownNodesOp(byName(name)); + public static TryToStartNodesOp restartNode(String name) { + return new TryToStartNodesOp(NodeSelector.byName(name)); } - public static WaitForFreezeOp waitForNodesToFreeze(int waitSeconds) { - return new WaitForFreezeOp(allNodes(), waitSeconds); + public static WaitForStatusOp waitForActiveNetwork(@NonNull final Duration timeout) { + return new WaitForStatusOp(NodeSelector.allNodes(), ACTIVE, timeout); } - public static WaitForBehindOp waitForNodeToBeBehind(String name, int waitSeconds) { - return new WaitForBehindOp(byName(name), waitSeconds); + public static TryToStartNodesOp restartNetwork() { + return new TryToStartNodesOp(NodeSelector.allNodes()); } - public static WaitForReconnectOp waitForNodeToFinishReconnect(String name, int waitSeconds) { - return new WaitForReconnectOp(byName(name), waitSeconds); + public static ShutdownWithinOp shutdownWithin(String name, Duration timeout) { + return new ShutdownWithinOp(NodeSelector.byName(name), timeout); } - public static WaitForShutdownOp waitForNodeToShutDown(String name, int waitSeconds) { - return new WaitForShutdownOp(byName(name), waitSeconds); + public static ShutdownWithinOp shutdownNetworkWithin(@NonNull final Duration timeout) { + return new ShutdownWithinOp(NodeSelector.allNodes(), timeout); } - public static WaitForShutdownOp waitForNodesToShutDown(int waitSeconds) { - return new WaitForShutdownOp(allNodes(), waitSeconds); + public static WaitForStatusOp waitForFrozenNetwork(@NonNull final Duration timeout) { + return new WaitForStatusOp(NodeSelector.allNodes(), FREEZE_COMPLETE, timeout); } public static HapiSpecSleep sleepFor(long timeMs) { @@ -520,14 +519,6 @@ public static BalanceSnapshot balanceSnapshot(Function nameFn, return new BalanceSnapshot(forAccount, nameFn); } - public static StartThroughputObs startThroughputObs(String name) { - return new StartThroughputObs(name); - } - - public static FinishThroughputObs finishThroughputObs(String name) { - return new FinishThroughputObs(name); - } - public static VerifyGetLiveHashNotSupported getClaimNotSupported() { return new VerifyGetLiveHashNotSupported(); } @@ -609,19 +600,6 @@ public static HapiSpecOperation resetToDefault(String... properties) { return overridingAllOf(defaultValues); } - public static HapiSpecOperation enableAllFeatureFlagsAndDisableContractThrottles( - @NonNull final String... exceptFeatures) { - final Map allOverrides = new HashMap<>(FeatureFlags.FEATURE_FLAGS.allEnabled(exceptFeatures)); - allOverrides.putAll(Map.of( - "contracts.throttle.throttleByGas", - FALSE_VALUE, - "contracts.enforceCreationThrottle", - FALSE_VALUE, - SIDECARS_PROP, - "CONTRACT_STATE_CHANGE,CONTRACT_ACTION,CONTRACT_BYTECODE")); - return overridingAllOf(allOverrides); - } - /** * Returns an operation that computes and executes a list of {@link HapiSpecOperation}s * returned by a function whose input is a map from the names of requested registry entities @@ -708,10 +686,6 @@ public static HapiSpecOperation remembering(final Map props, fin })); } - public static CustomSpecAssert exportAccountBalances(Supplier acctBalanceFile) { - return new CustomSpecAssert((spec, log) -> spec.exportAccountBalances(acctBalanceFile)); - } - /* Stream validation. */ public static RecordStreamVerification verifyRecordStreams(Supplier baseDir) { return new RecordStreamVerification(baseDir); @@ -753,14 +727,6 @@ public static Function recordedChildBodyWithId( return spec -> new TransactionBodyAssertion(specTxnId, spec, txnId -> txnId.getNonce() == nonce, assertion); } - public static RecordFileChecker verifyRecordFile( - Timestamp timestamp, List transactions, TransactionRecord... transactionRecord) { - var recordFileName = generateStreamFileNameFromInstant( - Instant.ofEpochSecond(timestamp.getSeconds(), timestamp.getNanos()), new RecordStreamType()); - - return new RecordFileChecker(recordFileName, transactions, transactionRecord); - } - /* Some more complicated ops built from primitive sub-ops */ public static CustomSpecAssert recordFeeAmount(String forTxn, String byName) { return new CustomSpecAssert((spec, workLog) -> { @@ -847,7 +813,7 @@ public static HapiSpecOperation chunkAFile( if (ciProperties.has("threads")) { threads = ciProperties.getInteger("threads"); } - int factor = HCSChunkingRealisticPerfSuite.DEFAULT_COLLISION_AVOIDANCE_FACTOR; + int factor = DEFAULT_COLLISION_AVOIDANCE_FACTOR; if (ciProperties.has("collisionAvoidanceFactor")) { factor = ciProperties.getInteger("collisionAvoidanceFactor"); } @@ -875,8 +841,7 @@ public static HapiSpecOperation chunkAFile( DUPLICATE_TRANSACTION, PLATFORM_TRANSACTION_NOT_CREATED, INSUFFICIENT_PAYER_BALANCE) - .noLogging() - .suppressStats(true); + .noLogging(); if (1 == currentChunk) { subOp = subOp.usePresetTimestamp(); } @@ -929,10 +894,6 @@ public static HapiSpecOperation ensureDissociated(String account, List t }); } - public static HapiSpecOperation makeFree(HederaFunctionality function) { - return reduceFeeFor(function, 0L, 0L, 0L); - } - public static HapiSpecOperation reduceFeeFor( HederaFunctionality function, long tinyBarMaxNodeFee, @@ -1292,12 +1253,12 @@ public static HapiSpecOperation updateLargeFile(String payer, String fileName, S }); } - public static HapiSpecOperation saveFileToRegistry(String fileName, String registryEntry) { - return getFileContents(fileName).payingWith(GENESIS).saveToRegistry(registryEntry); - } - - public static HapiSpecOperation restoreFileFromRegistry(String fileName, String registryEntry) { - return updateLargeFile(GENESIS, fileName, registryEntry); + public static HapiSpecOperation updateLargeFileWithZeroOfferedFee( + String payer, String fileName, String registryEntry) { + return withOpContext((spec, ctxLog) -> { + ByteString bt = ByteString.copyFrom(spec.registry().getBytes(registryEntry)); + CustomSpecAssert.allRunFor(spec, updateLargeFile(payer, fileName, bt, false, OptionalLong.of(0L))); + }); } @SuppressWarnings("java:S5960") @@ -1666,11 +1627,49 @@ public static Tuple nftTransfer( isApproval); } - public static List convertHapiCallsToEthereumCalls(final List ops) { + public static List convertHapiCallsToEthereumCalls( + final List ops, + final String privateKeyRef, + final Key adminKey, + final long defaultGas, + final HapiSpec spec) { final var convertedOps = new ArrayList(ops.size()); for (final var op : ops) { - if (op instanceof HapiContractCall callOp && callOp.isConvertableToEthCall()) { + if (op instanceof HapiContractCall callOp + && callOp.isConvertableToEthCall() + && callOp.isKeySECP256K1(spec)) { + // if we have function params, try to swap the long zero address with the EVM address + if (callOp.getParams().length > 0 && callOp.getAbi() != null) { + var convertedParams = tryToSwapLongZeroToEVMAddresses(callOp.getParams(), spec); + callOp.setParams(convertedParams); + } convertedOps.add(new HapiEthereumCall(callOp)); + + } else if (op instanceof HapiContractCreate callOp && callOp.isConvertableToEthCreate()) { + // if we have constructor args, update the bytecode file with one containing the args + if (callOp.getArgs().isPresent() && callOp.getAbi().isPresent()) { + var convertedArgs = + tryToSwapLongZeroToEVMAddresses(callOp.getArgs().get(), spec); + callOp.args(Optional.of(convertedArgs)); + convertedOps.add(updateInitCodeWithConstructorArgs( + Optional.empty(), + callOp.getContract(), + callOp.getAbi().get(), + callOp.getArgs().get())); + } + + var createEthereum = withOpContext((spec1, logger) -> { + var createTxn = new HapiEthereumContractCreate(callOp, privateKeyRef, adminKey, defaultGas); + allRunFor(spec1, createTxn); + // if create was successful, save the EVM address to the registry, so we can use it in future calls + if (spec1.registry().hasContractId(callOp.getContract())) { + allRunFor( + spec1, + getContractInfo(callOp.getContract()).saveEVMAddressToRegistry(callOp.getContract())); + } + }); + convertedOps.add(createEthereum); + } else { convertedOps.add(op); } @@ -1678,6 +1677,28 @@ public static List convertHapiCallsToEthereumCalls(final List return convertedOps; } + private static Object[] tryToSwapLongZeroToEVMAddresses(Object[] args, HapiSpec spec) { + return Arrays.stream(args) + .map(arg -> { + if (arg instanceof Address address) { + return swapLongZeroToEVMAddresses(spec, arg, address); + } + return arg; + }) + .toArray(); + } + + private static Object swapLongZeroToEVMAddresses(HapiSpec spec, Object arg, Address address) { + var explicitFromHeadlong = explicitFromHeadlong(address); + if (isLongZeroAddress(explicitFromHeadlong)) { + var contractNum = numberOfLongZero(explicitFromHeadlong(address)); + if (spec.registry().hasEVMAddress(String.valueOf(contractNum))) { + return HapiParserUtil.asHeadlongAddress(spec.registry().getEVMAddress(String.valueOf(contractNum))); + } + } + return arg; + } + public static byte[] getPrivateKeyFromSpec(final HapiSpec spec, final String privateKeyRef) { var key = spec.registry().getKey(privateKeyRef); final var privateKey = spec.keys() diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/utilops/inventory/NewSpecKey.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/utilops/inventory/NewSpecKey.java index 04f781223efd..65e4124252e0 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/utilops/inventory/NewSpecKey.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/utilops/inventory/NewSpecKey.java @@ -19,6 +19,7 @@ import static com.hedera.services.bdd.spec.keys.DefaultKeyGen.DEFAULT_KEY_GEN; import static com.hedera.services.bdd.spec.keys.KeyFactory.KeyType; import static com.swirlds.common.utility.CommonUtils.hex; +import static java.util.Objects.requireNonNull; import com.google.common.base.MoreObjects; import com.google.protobuf.ByteString; @@ -31,10 +32,13 @@ import com.hedera.services.bdd.spec.persistence.SpecKey; import com.hedera.services.bdd.spec.utilops.UtilOp; import com.hederahashgraph.api.proto.java.Key; +import edu.umd.cs.findbugs.annotations.NonNull; +import edu.umd.cs.findbugs.annotations.Nullable; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Paths; import java.util.Optional; +import java.util.function.Consumer; import net.i2p.crypto.eddsa.EdDSAPrivateKey; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; @@ -46,6 +50,10 @@ public class NewSpecKey extends UtilOp { private boolean verboseLoggingOn = false; private boolean exportEd25519Mnemonic = false; private final String name; + + @Nullable + private Consumer keyObserver; + private Optional immediateExportLoc = Optional.empty(); private Optional immediateExportPass = Optional.empty(); private Optional type = Optional.empty(); @@ -63,6 +71,11 @@ public NewSpecKey exportingTo(String loc, String pass) { return this; } + public NewSpecKey exposingKeyTo(@NonNull final Consumer observer) { + keyObserver = requireNonNull(observer); + return this; + } + public NewSpecKey includingEd25519Mnemonic() { exportEd25519Mnemonic = true; return this; @@ -138,6 +151,9 @@ protected boolean submitOp(HapiSpec spec) throws Throwable { key = spec.keys().generate(spec, type.orElse(KeyType.SIMPLE), keyGen); } spec.registry().saveKey(name, key); + if (keyObserver != null) { + keyObserver.accept(key); + } if (immediateExportLoc.isPresent() && immediateExportPass.isPresent()) { final var exportLoc = immediateExportLoc.get(); final var exportPass = immediateExportPass.get(); diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/utilops/lifecycle/AbstractLifecycleOp.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/utilops/lifecycle/AbstractLifecycleOp.java new file mode 100644 index 000000000000..336af528db47 --- /dev/null +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/utilops/lifecycle/AbstractLifecycleOp.java @@ -0,0 +1,52 @@ +/* + * Copyright (C) 2024 Hedera Hashgraph, LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.hedera.services.bdd.spec.utilops.lifecycle; + +import static java.util.Objects.requireNonNull; + +import com.hedera.services.bdd.junit.hedera.HederaNode; +import com.hedera.services.bdd.junit.hedera.NodeSelector; +import com.hedera.services.bdd.spec.HapiSpec; +import com.hedera.services.bdd.spec.utilops.UtilOp; +import edu.umd.cs.findbugs.annotations.NonNull; +import java.util.concurrent.CompletableFuture; + +/** + * A convenient base class for dealing with node lifecycle operations. + */ +public abstract class AbstractLifecycleOp extends UtilOp { + /** + * The {@link com.hedera.services.bdd.junit.hedera.NodeSelector} to use to choose which node(s) to operate on + */ + private final NodeSelector selector; + + protected AbstractLifecycleOp(@NonNull final NodeSelector selector) { + this.selector = requireNonNull(selector); + } + + @Override + protected boolean submitOp(@NonNull final HapiSpec spec) throws Throwable { + CompletableFuture.allOf(spec.targetNetworkOrThrow().nodesFor(selector).stream() + .map(node -> CompletableFuture.runAsync(() -> run(node))) + .toArray(CompletableFuture[]::new)) + .join(); + // This operation has nothing more to do after all nodes have finished their run() methods + return false; + } + + protected abstract void run(@NonNull final HederaNode node); +} diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/utilops/lifecycle/LifecycleOp.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/utilops/lifecycle/LifecycleOp.java deleted file mode 100644 index 7f9b7330e307..000000000000 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/utilops/lifecycle/LifecycleOp.java +++ /dev/null @@ -1,63 +0,0 @@ -/* - * Copyright (C) 2023-2024 Hedera Hashgraph, LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.hedera.services.bdd.spec.utilops.lifecycle; - -import static java.util.Objects.requireNonNull; - -import com.hedera.services.bdd.junit.HapiTestNode; -import com.hedera.services.bdd.spec.HapiSpec; -import com.hedera.services.bdd.spec.utilops.UtilOp; -import com.hedera.services.bdd.spec.utilops.lifecycle.selectors.NodeSelector; -import edu.umd.cs.findbugs.annotations.NonNull; -import java.util.stream.Collectors; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; - -/** - * A convenient base class for dealing with node lifecycle operations. - */ -public abstract class LifecycleOp extends UtilOp { - static final Logger log = LogManager.getLogger(LifecycleOp.class); - - /** The {@link NodeSelector} to use to choose which node(s) to operate on */ - private final NodeSelector selector; - - protected LifecycleOp(@NonNull final NodeSelector selector) { - this.selector = requireNonNull(selector); - } - - @Override - protected final boolean submitOp(HapiSpec spec) throws Throwable { - final var nodes = spec.getNodes().stream().filter(selector).collect(Collectors.toList()); - - if (nodes.isEmpty()) { - log.warn("Unable to find any node with criteria {}", selector); - return false; - } - - // Run the op for each node, and if ANY node returns "true", then the test is over and we should also - // return true. If they all return false, then we also return false. - return nodes.stream().anyMatch(this::run); - } - - /** - * Executes the operation on the given node. - * @param node The node to operate on. - * @return true if the test is over and should be terminated, false if the test should continue. - */ - protected abstract boolean run(@NonNull final HapiTestNode node); -} diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/utilops/lifecycle/ops/ShutDownNodesOp.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/utilops/lifecycle/ops/ShutDownNodesOp.java deleted file mode 100644 index f4fe814223b8..000000000000 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/utilops/lifecycle/ops/ShutDownNodesOp.java +++ /dev/null @@ -1,42 +0,0 @@ -/* - * Copyright (C) 2023-2024 Hedera Hashgraph, LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.hedera.services.bdd.spec.utilops.lifecycle.ops; - -import com.hedera.services.bdd.junit.HapiTestNode; -import com.hedera.services.bdd.spec.utilops.lifecycle.LifecycleOp; -import com.hedera.services.bdd.spec.utilops.lifecycle.selectors.NodeSelector; -import edu.umd.cs.findbugs.annotations.NonNull; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; - -/** - * Shuts down the selected node or nodes specified by the {@link NodeSelector}. - */ -public class ShutDownNodesOp extends LifecycleOp { - private static final Logger logger = LogManager.getLogger(ShutDownNodesOp.class); - - public ShutDownNodesOp(@NonNull final NodeSelector selector) { - super(selector); - } - - @Override - protected boolean run(@NonNull final HapiTestNode node) { - logger.info("Shutting down node {}...", node); - node.shutdown(); - return false; - } -} diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/utilops/lifecycle/ops/ShutdownWithinOp.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/utilops/lifecycle/ops/ShutdownWithinOp.java new file mode 100644 index 000000000000..232568cefce9 --- /dev/null +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/utilops/lifecycle/ops/ShutdownWithinOp.java @@ -0,0 +1,50 @@ +/* + * Copyright (C) 2024 Hedera Hashgraph, LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.hedera.services.bdd.spec.utilops.lifecycle.ops; + +import static java.util.Objects.requireNonNull; + +import com.hedera.services.bdd.junit.hedera.HederaNode; +import com.hedera.services.bdd.junit.hedera.NodeSelector; +import com.hedera.services.bdd.spec.utilops.lifecycle.AbstractLifecycleOp; +import edu.umd.cs.findbugs.annotations.NonNull; +import java.time.Duration; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +/** + * Shuts down the selected node or nodes specified by the {@link NodeSelector}. + */ +public class ShutdownWithinOp extends AbstractLifecycleOp { + private static final Logger log = LogManager.getLogger(ShutdownWithinOp.class); + + private final Duration timeout; + + public ShutdownWithinOp(@NonNull final NodeSelector selector, @NonNull final Duration timeout) { + super(selector); + this.timeout = requireNonNull(timeout); + } + + @Override + protected void run(@NonNull final HederaNode node) { + log.info("Asking node '{}' to stop", node.getName()); + node.stop(); + log.info("Waiting for '{}' to stop", node.getName()); + node.stopFuture(timeout).join(); + log.info("Stopped node '{}'", node.getName()); + } +} diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/utilops/lifecycle/ops/StartNodesOp.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/utilops/lifecycle/ops/TryToStartNodesOp.java similarity index 51% rename from hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/utilops/lifecycle/ops/StartNodesOp.java rename to hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/utilops/lifecycle/ops/TryToStartNodesOp.java index eb5d9fdf9f5c..0463935de505 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/utilops/lifecycle/ops/StartNodesOp.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/utilops/lifecycle/ops/TryToStartNodesOp.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023-2024 Hedera Hashgraph, LLC + * Copyright (C) 2024 Hedera Hashgraph, LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,33 +16,33 @@ package com.hedera.services.bdd.spec.utilops.lifecycle.ops; -import com.hedera.services.bdd.junit.HapiTestNode; -import com.hedera.services.bdd.spec.utilops.lifecycle.LifecycleOp; -import com.hedera.services.bdd.spec.utilops.lifecycle.selectors.NodeSelector; +import com.hedera.services.bdd.junit.hedera.HederaNode; +import com.hedera.services.bdd.junit.hedera.NodeSelector; +import com.hedera.services.bdd.spec.utilops.lifecycle.AbstractLifecycleOp; import edu.umd.cs.findbugs.annotations.NonNull; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; +import org.junit.jupiter.api.Assertions; /** - * Starts the selected node or nodes specified by the {@link NodeSelector}. + * Shuts down the selected node or nodes specified by the {@link NodeSelector}. */ -public class StartNodesOp extends LifecycleOp { - private static final Logger logger = LogManager.getLogger(StartNodesOp.class); +public class TryToStartNodesOp extends AbstractLifecycleOp { + private static final Logger log = LogManager.getLogger(TryToStartNodesOp.class); - public StartNodesOp(@NonNull final NodeSelector selector) { + public TryToStartNodesOp(@NonNull final NodeSelector selector) { super(selector); } @Override - protected boolean run(@NonNull final HapiTestNode node) { - logger.info("Starting node {}...", node); + protected void run(@NonNull final HederaNode node) { + log.info("Starting node '{}'", node.getName()); try { node.start(); } catch (Exception e) { - logger.error("Node {} failed to start", node); - throw e; + log.error("Node '{}' failed to start", node); + Assertions.fail("Node " + node + " failed to start"); } - logger.info("Node {} has started...", node); - return false; + log.info("Node '{}' has started", node.getName()); } } diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/utilops/lifecycle/ops/WaitForActiveOp.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/utilops/lifecycle/ops/WaitForActiveOp.java deleted file mode 100644 index d5705f6e81f8..000000000000 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/utilops/lifecycle/ops/WaitForActiveOp.java +++ /dev/null @@ -1,53 +0,0 @@ -/* - * Copyright (C) 2023-2024 Hedera Hashgraph, LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.hedera.services.bdd.spec.utilops.lifecycle.ops; - -import com.hedera.services.bdd.junit.HapiTestNode; -import com.hedera.services.bdd.spec.utilops.lifecycle.LifecycleOp; -import com.hedera.services.bdd.spec.utilops.lifecycle.selectors.NodeSelector; -import edu.umd.cs.findbugs.annotations.NonNull; -import java.util.concurrent.TimeoutException; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; - -/** - * Blocks waiting until the selected node or nodes are active, or until a timeout of {@code waitSeconds} has happened. - */ -public class WaitForActiveOp extends LifecycleOp { - private static final Logger logger = LogManager.getLogger(WaitForActiveOp.class); - - /** The number of seconds to wait for the node(s) to become active */ - private final int waitSeconds; - - public WaitForActiveOp(@NonNull final NodeSelector selector, int waitSeconds) { - super(selector); - this.waitSeconds = waitSeconds; - } - - @Override - protected boolean run(@NonNull final HapiTestNode node) { - logger.info("Waiting for node {} to become active, waiting up to {}s...", node, waitSeconds); - try { - node.waitForActive(waitSeconds); - logger.info("Node {} started and is active", node); - return false; // Do not stop the test, all is well. - } catch (TimeoutException e) { - logger.info("Node {} did not become active within {}s", node, waitSeconds); - return true; // Stop the test, we're toast. - } - } -} diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/utilops/lifecycle/ops/WaitForBehindOp.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/utilops/lifecycle/ops/WaitForBehindOp.java deleted file mode 100644 index 5e000fc95df5..000000000000 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/utilops/lifecycle/ops/WaitForBehindOp.java +++ /dev/null @@ -1,53 +0,0 @@ -/* - * Copyright (C) 2023-2024 Hedera Hashgraph, LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.hedera.services.bdd.spec.utilops.lifecycle.ops; - -import com.hedera.services.bdd.junit.HapiTestNode; -import com.hedera.services.bdd.spec.utilops.lifecycle.LifecycleOp; -import com.hedera.services.bdd.spec.utilops.lifecycle.selectors.NodeSelector; -import edu.umd.cs.findbugs.annotations.NonNull; -import java.util.concurrent.TimeoutException; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; - -/** - * Blocks waiting until the selected node or nodes are in behind status, or until a timeout of {@code waitSeconds} has happened. - */ -public class WaitForBehindOp extends LifecycleOp { - private static final Logger logger = LogManager.getLogger(WaitForBehindOp.class); - - /** The number of seconds to wait for the node(s) to become active */ - private final int waitSeconds; - - public WaitForBehindOp(@NonNull final NodeSelector selector, int waitSeconds) { - super(selector); - this.waitSeconds = waitSeconds; - } - - @Override - protected boolean run(@NonNull final HapiTestNode node) { - logger.info("Waiting for node {} to become behind, waiting up to {}s...", node, waitSeconds); - try { - node.waitForBehind(waitSeconds); - logger.info("Node {} fell behind other nodes", node); - return false; // Do not stop the test, all is well. - } catch (TimeoutException e) { - logger.info("Node {} did not fall behind {}s", node, waitSeconds); - return true; // Stop the test, we're toast. - } - } -} diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/utilops/lifecycle/ops/WaitForFreezeOp.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/utilops/lifecycle/ops/WaitForFreezeOp.java deleted file mode 100644 index 76755f4c48f8..000000000000 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/utilops/lifecycle/ops/WaitForFreezeOp.java +++ /dev/null @@ -1,54 +0,0 @@ -/* - * Copyright (C) 2023-2024 Hedera Hashgraph, LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.hedera.services.bdd.spec.utilops.lifecycle.ops; - -import com.hedera.services.bdd.junit.HapiTestNode; -import com.hedera.services.bdd.spec.utilops.lifecycle.LifecycleOp; -import com.hedera.services.bdd.spec.utilops.lifecycle.selectors.NodeSelector; -import edu.umd.cs.findbugs.annotations.NonNull; -import java.util.concurrent.TimeoutException; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; - -/** - * Blocks waiting until the selected node or nodes are frozen, or until a timeout of {@code waitSeconds} has - * happened. - */ -public class WaitForFreezeOp extends LifecycleOp { - private static final Logger logger = LogManager.getLogger(WaitForFreezeOp.class); - - /** The number of seconds to wait for the node(s) to freeze */ - private final int waitSeconds; - - public WaitForFreezeOp(@NonNull final NodeSelector selector, int waitSeconds) { - super(selector); - this.waitSeconds = waitSeconds; - } - - @Override - protected boolean run(@NonNull final HapiTestNode node) { - logger.info("Waiting for node {} to freeze, waiting up to {}s...", node, waitSeconds); - try { - node.waitForFreeze(waitSeconds); - logger.info("Node {} is frozen", node); - return false; // Do not stop the test, all is well. - } catch (TimeoutException e) { - logger.info("Node {} did not freeze within {}s", node, waitSeconds); - return true; // Stop the test, we're toast. - } - } -} diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/utilops/lifecycle/ops/WaitForReconnectOp.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/utilops/lifecycle/ops/WaitForReconnectOp.java deleted file mode 100644 index d47a41469bcf..000000000000 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/utilops/lifecycle/ops/WaitForReconnectOp.java +++ /dev/null @@ -1,53 +0,0 @@ -/* - * Copyright (C) 2023-2024 Hedera Hashgraph, LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.hedera.services.bdd.spec.utilops.lifecycle.ops; - -import com.hedera.services.bdd.junit.HapiTestNode; -import com.hedera.services.bdd.spec.utilops.lifecycle.LifecycleOp; -import com.hedera.services.bdd.spec.utilops.lifecycle.selectors.NodeSelector; -import edu.umd.cs.findbugs.annotations.NonNull; -import java.util.concurrent.TimeoutException; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; - -/** - * Blocks waiting until the selected node or nodes are reconnected, or until a timeout of {@code waitSeconds} has happened. - */ -public class WaitForReconnectOp extends LifecycleOp { - private static final Logger logger = LogManager.getLogger(WaitForReconnectOp.class); - - /** The number of seconds to wait for the node(s) to become active */ - private final int waitSeconds; - - public WaitForReconnectOp(@NonNull final NodeSelector selector, int waitSeconds) { - super(selector); - this.waitSeconds = waitSeconds; - } - - @Override - protected boolean run(@NonNull final HapiTestNode node) { - logger.info("Waiting for node {} to finish reconnect, waiting up to {}s...", node, waitSeconds); - try { - node.waitForReconnectComplete(waitSeconds); - logger.info("Node {} started and have reconnected successfully", node); - return false; // Do not stop the test, all is well. - } catch (TimeoutException e) { - logger.info("Node {} did not reconnect within {}s", node, waitSeconds); - return true; // Stop the test, we're toast. - } - } -} diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/utilops/lifecycle/ops/WaitForShutdownOp.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/utilops/lifecycle/ops/WaitForShutdownOp.java deleted file mode 100644 index 20d9dcd330f5..000000000000 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/utilops/lifecycle/ops/WaitForShutdownOp.java +++ /dev/null @@ -1,53 +0,0 @@ -/* - * Copyright (C) 2023-2024 Hedera Hashgraph, LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.hedera.services.bdd.spec.utilops.lifecycle.ops; - -import com.hedera.services.bdd.junit.HapiTestNode; -import com.hedera.services.bdd.spec.utilops.lifecycle.LifecycleOp; -import com.hedera.services.bdd.spec.utilops.lifecycle.selectors.NodeSelector; -import edu.umd.cs.findbugs.annotations.NonNull; -import java.util.concurrent.TimeoutException; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; - -/** - * Blocks waiting until the selected node or nodes are shut down, or until a timeout of {@code waitSeconds} has - * happened. - */ -public class WaitForShutdownOp extends LifecycleOp { - private static final Logger logger = LogManager.getLogger(WaitForShutdownOp.class); - - /** The number of seconds to wait for the node(s) to shut down */ - private final int waitSeconds; - - public WaitForShutdownOp(@NonNull final NodeSelector selector, int waitSeconds) { - super(selector); - this.waitSeconds = waitSeconds; - } - - @Override - protected boolean run(@NonNull final HapiTestNode node) { - try { - node.waitForShutdown(waitSeconds); - logger.info("Waiting for node {} to shut down, waiting up to {}s...", node, waitSeconds); - return false; // Do not stop the test, all is well. - } catch (TimeoutException e) { - logger.info("Node {} did not shut down within {}s", node, waitSeconds); - return true; // Stop the test, we're toast. - } - } -} diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/utilops/lifecycle/ops/WaitForStatusOp.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/utilops/lifecycle/ops/WaitForStatusOp.java new file mode 100644 index 000000000000..865483da58ee --- /dev/null +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/utilops/lifecycle/ops/WaitForStatusOp.java @@ -0,0 +1,58 @@ +/* + * Copyright (C) 2024 Hedera Hashgraph, LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.hedera.services.bdd.spec.utilops.lifecycle.ops; + +import static java.util.Objects.requireNonNull; + +import com.hedera.services.bdd.junit.hedera.HederaNode; +import com.hedera.services.bdd.junit.hedera.NodeSelector; +import com.hedera.services.bdd.spec.utilops.lifecycle.AbstractLifecycleOp; +import com.swirlds.platform.system.status.PlatformStatus; +import edu.umd.cs.findbugs.annotations.NonNull; +import java.time.Duration; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +/** + * Waits for the selected node or nodes specified by the {@link NodeSelector} to + * reach the specified status within the given timeout. + */ +public class WaitForStatusOp extends AbstractLifecycleOp { + private static final Logger log = LogManager.getLogger(WaitForStatusOp.class); + + private final Duration timeout; + private final PlatformStatus status; + + public WaitForStatusOp( + @NonNull NodeSelector selector, @NonNull final PlatformStatus status, @NonNull final Duration timeout) { + super(selector); + this.timeout = requireNonNull(timeout); + this.status = requireNonNull(status); + } + + @Override + public void run(@NonNull final HederaNode node) { + log.info("Waiting for node '{}' to be {} within {}", node.getName(), status, timeout); + node.statusFuture(status, timeout).join(); + log.info("Node '{}' is {}", node.getName(), status); + } + + @Override + public String toString() { + return "WaitFor" + status + "Within" + timeout; + } +} diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/utilops/lifecycle/selectors/NodeSelector.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/utilops/lifecycle/selectors/NodeSelector.java deleted file mode 100644 index d5344f933078..000000000000 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/utilops/lifecycle/selectors/NodeSelector.java +++ /dev/null @@ -1,57 +0,0 @@ -/* - * Copyright (C) 2023-2024 Hedera Hashgraph, LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.hedera.services.bdd.spec.utilops.lifecycle.selectors; - -import com.hedera.hapi.node.base.AccountID; -import com.hedera.services.bdd.junit.HapiTestNode; -import edu.umd.cs.findbugs.annotations.NonNull; -import java.util.function.Predicate; - -/** - * Defines the criteria by which {@link HapiTestNode}s will be used by the operations of this package. - */ -public interface NodeSelector extends Predicate { - - /** - * Returns true if the given node should be selected. - * - * @param hapiTestNode the input argument - * @return true if the node should be selected. - */ - @Override - boolean test(@NonNull HapiTestNode hapiTestNode); - - /** Gets a {@link NodeSelector} that selects nodes by name in a case-insensitive way, such as Alice or Bob. */ - static NodeSelector byName(@NonNull final String name) { - return new SelectByName(name); - } - - /** Gets a {@link NodeSelector} that selects nodes by operator account ID. Does not work with aliases. */ - static NodeSelector byOperatorAccountId(@NonNull final AccountID operatorAccountID) { - return new SelectByOperatorAccountId(operatorAccountID); - } - - /** Gets a {@link NodeSelector} that selects nodes by nodeId in a case-insensitive way */ - static NodeSelector byNodeId(final long nodeId) { - return new SelectByNodeId(nodeId); - } - - /** Gets a {@link NodeSelector} that selects all nodes */ - static NodeSelector allNodes() { - return new SelectAll(); - } -} diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/utilops/lifecycle/selectors/SelectByName.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/utilops/lifecycle/selectors/SelectByName.java deleted file mode 100644 index 90a108736f6c..000000000000 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/utilops/lifecycle/selectors/SelectByName.java +++ /dev/null @@ -1,43 +0,0 @@ -/* - * Copyright (C) 2023-2024 Hedera Hashgraph, LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.hedera.services.bdd.spec.utilops.lifecycle.selectors; - -import static java.util.Objects.requireNonNull; - -import com.hedera.services.bdd.junit.HapiTestNode; -import edu.umd.cs.findbugs.annotations.NonNull; - -/** - * Selects the node that has the given name, like Alice or Bob. - */ -public class SelectByName implements NodeSelector { - private final String name; - - public SelectByName(@NonNull final String name) { - this.name = requireNonNull(name); - } - - @Override - public boolean test(@NonNull final HapiTestNode hapiTestNode) { - return name.equalsIgnoreCase(hapiTestNode.getName()); - } - - @Override - public String toString() { - return "by name '" + name + "'"; - } -} diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/utilops/lifecycle/selectors/SelectByNodeId.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/utilops/lifecycle/selectors/SelectByNodeId.java deleted file mode 100644 index 28e0382afbc0..000000000000 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/utilops/lifecycle/selectors/SelectByNodeId.java +++ /dev/null @@ -1,44 +0,0 @@ -/* - * Copyright (C) 2023-2024 Hedera Hashgraph, LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.hedera.services.bdd.spec.utilops.lifecycle.selectors; - -import com.hedera.services.bdd.junit.HapiTestNode; -import edu.umd.cs.findbugs.annotations.NonNull; - -/** - * Selects a node based on it's nodeId, like 0, 1, 2, 3. - */ -public class SelectByNodeId implements NodeSelector { - private final long nodeId; - - public SelectByNodeId(final long nodeId) { - if (nodeId < 0) { - throw new IllegalArgumentException("Node IDs are non-negative. Cannot be " + nodeId); - } - this.nodeId = nodeId; - } - - @Override - public boolean test(@NonNull final HapiTestNode hapiTestNode) { - return hapiTestNode.getId() == nodeId; - } - - @Override - public String toString() { - return "by nodeId '" + nodeId + "'"; - } -} diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/utilops/lifecycle/selectors/SelectByOperatorAccountId.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/utilops/lifecycle/selectors/SelectByOperatorAccountId.java deleted file mode 100644 index 6bdb68c420b4..000000000000 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/utilops/lifecycle/selectors/SelectByOperatorAccountId.java +++ /dev/null @@ -1,52 +0,0 @@ -/* - * Copyright (C) 2023-2024 Hedera Hashgraph, LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.hedera.services.bdd.spec.utilops.lifecycle.selectors; - -import static java.util.Objects.requireNonNull; - -import com.hedera.hapi.node.base.AccountID; -import com.hedera.pbj.runtime.io.buffer.Bytes; -import com.hedera.services.bdd.junit.HapiTestNode; -import edu.umd.cs.findbugs.annotations.NonNull; - -/** - * Selects the node with the given operator account ID, like 0.0.3, 0.0.4, etc. - */ -public class SelectByOperatorAccountId implements NodeSelector { - private final AccountID operatorAccountId; - - public SelectByOperatorAccountId(@NonNull final AccountID operatorAccountId) { - this.operatorAccountId = requireNonNull(operatorAccountId); - } - - @Override - public boolean test(@NonNull final HapiTestNode hapiTestNode) { - return operatorAccountId.equals(hapiTestNode.getAccountId()); - } - - @Override - public String toString() { - final var numOrAlias = operatorAccountId.hasAccountNum() - ? operatorAccountId.accountNumOrThrow() - : operatorAccountId.aliasOrElse(Bytes.EMPTY); - - return "by operator accountId '" - + operatorAccountId.shardNum() + "." - + operatorAccountId.realmNum() + "." - + numOrAlias + "'"; - } -} diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/utilops/mod/ExpectedAnswer.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/utilops/mod/ExpectedAnswer.java index ad5dd3be21f6..a62d12f5929a 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/utilops/mod/ExpectedAnswer.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/utilops/mod/ExpectedAnswer.java @@ -17,7 +17,6 @@ package com.hedera.services.bdd.spec.utilops.mod; import static java.util.Arrays.asList; -import static java.util.Objects.requireNonNull; import com.hedera.services.bdd.spec.queries.HapiQueryOp; import com.hederahashgraph.api.proto.java.ResponseCodeEnum; @@ -35,9 +34,9 @@ * @param answerOnlyStatus a failure status if just the ANSWER_ONLY query should fail */ public record ExpectedAnswer( - @Nullable ResponseCodeEnum costAnswerStatus, @Nullable Set answerOnlyStatus) { - public static ExpectedAnswer onCostAnswer(@NonNull ResponseCodeEnum status) { - return new ExpectedAnswer(status, null); + @Nullable Set costAnswerStatus, @Nullable Set answerOnlyStatus) { + public static ExpectedAnswer onCostAnswer(@NonNull ResponseCodeEnum... statuses) { + return new ExpectedAnswer(EnumSet.copyOf(asList(statuses)), null); } public static ExpectedAnswer onAnswerOnly(@NonNull ResponseCodeEnum... statuses) { @@ -46,9 +45,9 @@ public static ExpectedAnswer onAnswerOnly(@NonNull ResponseCodeEnum... statuses) public void customize(@NonNull final HapiQueryOp op) { if (costAnswerStatus != null) { - op.hasCostAnswerPrecheck(costAnswerStatus); - } else { - requireNonNull(answerOnlyStatus); + op.hasCostAnswerPrecheckFrom(costAnswerStatus.toArray(ResponseCodeEnum[]::new)); + } + if (answerOnlyStatus != null) { op.hasAnswerOnlyPrecheckFrom(answerOnlyStatus.toArray(ResponseCodeEnum[]::new)); } } diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/utilops/mod/ExpectedResponse.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/utilops/mod/ExpectedResponse.java index c5e9a84b0f04..1eb6410cd8e2 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/utilops/mod/ExpectedResponse.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/utilops/mod/ExpectedResponse.java @@ -35,6 +35,7 @@ */ public record ExpectedResponse( @Nullable Set permissiblePrechecks, @Nullable Set permissibleOutcomes) { + private static final Set SUCCESS = EnumSet.of(ResponseCodeEnum.SUCCESS); public static ExpectedResponse atIngest(@NonNull final ResponseCodeEnum status) { return new ExpectedResponse(EnumSet.of(status), null); @@ -60,4 +61,8 @@ public void customize(@NonNull final HapiTxnOp op) { op.hasKnownStatusFrom(permissibleOutcomes.toArray(ResponseCodeEnum[]::new)); } } + + public boolean isSuccess() { + return SUCCESS.equals(permissibleOutcomes); + } } diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/utilops/mod/QueryIdClearingStrategy.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/utilops/mod/QueryIdClearingStrategy.java index 8f1ed32492cb..94ca3757ae2f 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/utilops/mod/QueryIdClearingStrategy.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/utilops/mod/QueryIdClearingStrategy.java @@ -27,26 +27,33 @@ import static java.util.Objects.requireNonNull; import edu.umd.cs.findbugs.annotations.NonNull; +import java.util.EnumSet; import java.util.Map; public class QueryIdClearingStrategy extends IdClearingStrategy implements QueryModificationStrategy { private static final Map CLEARED_ID_ANSWERS = Map.ofEntries( - Map.entry("proto.ContractGetInfoQuery.contractID", ExpectedAnswer.onAnswerOnly(INVALID_CONTRACT_ID)), - Map.entry("proto.ContractCallLocalQuery.contractID", ExpectedAnswer.onAnswerOnly(INVALID_CONTRACT_ID)), + Map.entry("proto.ContractGetInfoQuery.contractID", ExpectedAnswer.onCostAnswer(INVALID_CONTRACT_ID)), + Map.entry("proto.ContractCallLocalQuery.contractID", ExpectedAnswer.onCostAnswer(INVALID_CONTRACT_ID)), Map.entry("proto.CryptoGetAccountBalanceQuery.accountID", ExpectedAnswer.onAnswerOnly(INVALID_ACCOUNT_ID)), - Map.entry("proto.CryptoGetAccountRecordsQuery.accountID", ExpectedAnswer.onAnswerOnly(INVALID_ACCOUNT_ID)), - Map.entry("proto.CryptoGetInfoQuery.accountID", ExpectedAnswer.onAnswerOnly(INVALID_ACCOUNT_ID)), - Map.entry("proto.ContractGetBytecodeQuery.contractID", ExpectedAnswer.onAnswerOnly(INVALID_CONTRACT_ID)), - Map.entry("proto.FileGetContentsQuery.fileID", ExpectedAnswer.onAnswerOnly(INVALID_FILE_ID)), - Map.entry("proto.FileGetInfoQuery.fileID", ExpectedAnswer.onAnswerOnly(INVALID_FILE_ID)), + Map.entry("proto.CryptoGetAccountRecordsQuery.accountID", ExpectedAnswer.onCostAnswer(INVALID_ACCOUNT_ID)), + Map.entry("proto.CryptoGetInfoQuery.accountID", ExpectedAnswer.onCostAnswer(INVALID_ACCOUNT_ID)), + Map.entry("proto.ContractGetBytecodeQuery.contractID", ExpectedAnswer.onCostAnswer(INVALID_CONTRACT_ID)), + Map.entry("proto.FileGetContentsQuery.fileID", ExpectedAnswer.onCostAnswer(INVALID_FILE_ID)), + Map.entry("proto.FileGetInfoQuery.fileID", ExpectedAnswer.onCostAnswer(INVALID_FILE_ID)), + // Since both the free getTxnReceipt query AND the paid getTxnRecord query use the same + // TransactionID.accountID field, we have two possible expected answers: For the free query, + // a failure when using the ANSWER_ONLY response type; and for the paid query, a failure + // one step earlier when using the COST_ANSWER response type Map.entry( "proto.TransactionID.accountID", - ExpectedAnswer.onAnswerOnly(INVALID_ACCOUNT_ID, INVALID_TRANSACTION_ID)), - Map.entry("proto.ConsensusGetTopicInfoQuery.topicID", ExpectedAnswer.onAnswerOnly(INVALID_TOPIC_ID)), - Map.entry("proto.TokenGetInfoQuery.token", ExpectedAnswer.onAnswerOnly(INVALID_TOKEN_ID)), - Map.entry("proto.ScheduleGetInfoQuery.scheduleID", ExpectedAnswer.onAnswerOnly(INVALID_SCHEDULE_ID)), - Map.entry("proto.NftID.token_ID", ExpectedAnswer.onAnswerOnly(INVALID_TOKEN_ID)), + new ExpectedAnswer( + EnumSet.of(INVALID_ACCOUNT_ID, INVALID_TRANSACTION_ID), + EnumSet.of(INVALID_ACCOUNT_ID, INVALID_TRANSACTION_ID))), + Map.entry("proto.ConsensusGetTopicInfoQuery.topicID", ExpectedAnswer.onCostAnswer(INVALID_TOPIC_ID)), + Map.entry("proto.TokenGetInfoQuery.token", ExpectedAnswer.onCostAnswer(INVALID_TOKEN_ID)), + Map.entry("proto.ScheduleGetInfoQuery.scheduleID", ExpectedAnswer.onCostAnswer(INVALID_SCHEDULE_ID)), + Map.entry("proto.NftID.token_ID", ExpectedAnswer.onCostAnswer(INVALID_TOKEN_ID)), Map.entry("proto.GetAccountDetailsQuery.account_id", ExpectedAnswer.onAnswerOnly(INVALID_ACCOUNT_ID))); @NonNull diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/utilops/mod/QueryModificationsOp.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/utilops/mod/QueryModificationsOp.java index b314a5d9a1d8..a99885da9ed5 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/utilops/mod/QueryModificationsOp.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/utilops/mod/QueryModificationsOp.java @@ -16,9 +16,11 @@ package com.hedera.services.bdd.spec.utilops.mod; +import static com.hedera.services.bdd.spec.transactions.TxnVerbs.cryptoCreate; import static com.hedera.services.bdd.spec.utilops.CustomSpecAssert.allRunFor; import static com.hedera.services.bdd.spec.utilops.UtilVerbs.blockingOrder; import static com.hedera.services.bdd.spec.utilops.UtilVerbs.logIt; +import static com.hedera.services.bdd.spec.utilops.UtilVerbs.noOp; import static com.hedera.services.bdd.spec.utilops.UtilVerbs.sourcing; import com.hedera.services.bdd.spec.HapiSpec; @@ -33,29 +35,52 @@ import java.util.stream.Stream; public class QueryModificationsOp extends UtilOp { + // An account to create as part of the send-with-modifications process + // to ensure fees are charged (the default payer is a superuser account + // that is never charged fees); this ensures we cover calculateFees() + // code paths in handlers + private static final String MODIFIED_CIVILIAN_PAYER = "modifiedCivilianPayer"; + private final boolean useCivilianPayer; private final Supplier> queryOpSupplier; private final Function> modificationsFn; public QueryModificationsOp( @NonNull final Supplier> queryOpSupplier, @NonNull final Function> modificationsFn) { + this(true, queryOpSupplier, modificationsFn); + } + + public QueryModificationsOp( + final boolean useCivilianPayer, + @NonNull final Supplier> queryOpSupplier, + @NonNull final Function> modificationsFn) { + this.useCivilianPayer = useCivilianPayer; this.queryOpSupplier = queryOpSupplier; this.modificationsFn = modificationsFn; } @Override protected boolean submitOp(HapiSpec spec) throws Throwable { - final var unmodifiedOp = queryOpSupplier.get(); + final var unmodifiedOp = originalQuery(); allRunFor( spec, + sourcing(() -> useCivilianPayer ? cryptoCreate(MODIFIED_CIVILIAN_PAYER) : noOp()), unmodifiedOp, sourcing(() -> blockingOrder(modificationsFn.apply(unmodifiedOp.getQuery()).stream() .flatMap(modification -> { - final var op = queryOpSupplier.get(); + final var op = originalQuery(); modification.customize(op); return Stream.of(logIt(modification.summary()), op); }) .toArray(HapiSpecOperation[]::new)))); return false; } + + private HapiQueryOp originalQuery() { + final var op = queryOpSupplier.get(); + if (useCivilianPayer) { + op.payingWith(MODIFIED_CIVILIAN_PAYER); + } + return op; + } } diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/utilops/mod/SubmitModificationsOp.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/utilops/mod/SubmitModificationsOp.java index 4a790ab2ebd9..3b1c0fe07493 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/utilops/mod/SubmitModificationsOp.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/utilops/mod/SubmitModificationsOp.java @@ -16,10 +16,13 @@ package com.hedera.services.bdd.spec.utilops.mod; +import static com.hedera.services.bdd.spec.transactions.TxnVerbs.cryptoCreate; import static com.hedera.services.bdd.spec.utilops.CustomSpecAssert.allRunFor; import static com.hedera.services.bdd.spec.utilops.UtilVerbs.blockingOrder; import static com.hedera.services.bdd.spec.utilops.UtilVerbs.logIt; +import static com.hedera.services.bdd.spec.utilops.UtilVerbs.noOp; import static com.hedera.services.bdd.spec.utilops.UtilVerbs.sourcing; +import static com.hedera.services.bdd.suites.HapiSuite.THOUSAND_HBAR; import com.hedera.services.bdd.spec.HapiSpec; import com.hedera.services.bdd.spec.HapiSpecOperation; @@ -39,12 +42,26 @@ * original transaction. */ public class SubmitModificationsOp extends UtilOp { + // An account to create as part of the submit-with-modifications process + // to ensure fees are charged (the default payer is a superuser account + // that is never charged fees); this ensures we cover calculateFees() + // code paths in handlers + private static final String MODIFIED_CIVILIAN_PAYER = "modifiedCivilianPayer"; + private final boolean useCivilianPayer; private final Supplier> txnOpSupplier; private final Function> modificationsFn; public SubmitModificationsOp( @NonNull final Supplier> txnOpSupplier, @NonNull final Function> modificationsFn) { + this(true, txnOpSupplier, modificationsFn); + } + + public SubmitModificationsOp( + final boolean useCivilianPayer, + @NonNull final Supplier> txnOpSupplier, + @NonNull final Function> modificationsFn) { + this.useCivilianPayer = useCivilianPayer; this.txnOpSupplier = txnOpSupplier; this.modificationsFn = modificationsFn; } @@ -54,7 +71,9 @@ protected boolean submitOp(@NonNull final HapiSpec spec) throws Throwable { final List modifications = new ArrayList<>(); allRunFor( spec, - txnOpSupplier.get().withTxnTransform(txn -> { + sourcing(() -> + useCivilianPayer ? cryptoCreate(MODIFIED_CIVILIAN_PAYER).balance(10 * THOUSAND_HBAR) : noOp()), + preModifiedTransaction().withTxnTransform(txn -> { modifications.addAll(modificationsFn.apply(txn)); return txn; }), @@ -67,4 +86,12 @@ protected boolean submitOp(@NonNull final HapiSpec spec) throws Throwable { .toArray(HapiSpecOperation[]::new)))); return false; } + + private HapiTxnOp preModifiedTransaction() { + final var op = txnOpSupplier.get(); + if (useCivilianPayer) { + op.payingWith(MODIFIED_CIVILIAN_PAYER); + } + return op; + } } diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/utilops/records/SnapshotMode.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/utilops/records/SnapshotMode.java index d24a5e132ae0..ccafd7d86b67 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/utilops/records/SnapshotMode.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/utilops/records/SnapshotMode.java @@ -32,7 +32,7 @@ public enum SnapshotMode { * Takes a snapshot of the record stream generated by running a {@link HapiSpec} against a * {@link com.hedera.services.bdd.junit.HapiTest} network. */ - TAKE_FROM_HAPI_TEST_STREAMS(TargetNetworkType.HAPI_TEST_NETWORK), + TAKE_FROM_HAPI_TEST_STREAMS(TargetNetworkType.SHARED_HAPI_TEST_NETWORK), /** * Fuzzy-matches the record stream generated by running a {@link HapiSpec} against a standalone * mono-service node with a saved snapshot. @@ -42,7 +42,7 @@ public enum SnapshotMode { * Fuzzy-matches the record stream generated by running a {@link HapiSpec} against a * {@link com.hedera.services.bdd.junit.HapiTest} network with a saved snapshot. */ - FUZZY_MATCH_AGAINST_HAPI_TEST_STREAMS(TargetNetworkType.HAPI_TEST_NETWORK); + FUZZY_MATCH_AGAINST_HAPI_TEST_STREAMS(TargetNetworkType.SHARED_HAPI_TEST_NETWORK); private final TargetNetworkType targetNetworkType; diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/utilops/records/SnapshotModeOp.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/utilops/records/SnapshotModeOp.java index 82a329243566..f7f48bb80054 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/utilops/records/SnapshotModeOp.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/utilops/records/SnapshotModeOp.java @@ -17,7 +17,7 @@ package com.hedera.services.bdd.spec.utilops.records; import static com.hedera.node.app.hapi.utils.exports.recordstreaming.RecordStreamingUtils.parseRecordFileConsensusTime; -import static com.hedera.services.bdd.junit.RecordStreamAccess.RECORD_STREAM_ACCESS; +import static com.hedera.services.bdd.junit.support.RecordStreamAccess.RECORD_STREAM_ACCESS; import static com.hedera.services.bdd.spec.queries.QueryVerbs.getTxnRecord; import static com.hedera.services.bdd.spec.transactions.TxnVerbs.cryptoCreate; import static com.hedera.services.bdd.spec.utilops.CustomSpecAssert.allRunFor; @@ -43,13 +43,14 @@ import com.fasterxml.jackson.databind.ObjectMapper; import com.google.protobuf.Descriptors; import com.google.protobuf.GeneratedMessageV3; -import com.hedera.services.bdd.junit.HapiTestEngine; -import com.hedera.services.bdd.junit.RecordStreamAccess; +import com.hedera.services.bdd.junit.SharedNetworkLauncherSessionListener; +import com.hedera.services.bdd.junit.support.RecordStreamAccess; import com.hedera.services.bdd.spec.HapiSpec; import com.hedera.services.bdd.spec.utilops.UtilOp; import com.hedera.services.bdd.spec.utilops.domain.ParsedItem; import com.hedera.services.bdd.spec.utilops.domain.RecordSnapshot; import com.hedera.services.bdd.spec.utilops.domain.SuiteSnapshots; +import com.hedera.services.bdd.suites.HapiSuite; import com.hederahashgraph.api.proto.java.AccountID; import com.hederahashgraph.api.proto.java.ContractID; import com.hederahashgraph.api.proto.java.FileID; @@ -718,7 +719,12 @@ private void writeSnapshotOf(@NonNull final List postPlaceholderItem om.writeValue(fout, suiteSnapshots); } - private static Path resourceLocOf(@NonNull final String snapshotLoc, @NonNull final String suiteName) { + private static Path resourceLocOf(@NonNull final String snapshotLoc, @NonNull String suiteName) { + // If we start a test with Ethereum context we are adding a "_Eth" suffix to test name. + // Before we start a test we need to remove this suffix to get the correct snapshot file name. + if (suiteName.endsWith(HapiSuite.ETH_SUFFIX)) { + suiteName = suiteName.replace(HapiSuite.ETH_SUFFIX, ""); + } return Paths.get(snapshotLoc, suiteName + ".json"); } @@ -782,8 +788,8 @@ private List monoStreamLocs() { } private List hapiTestStreamLocs() { - final List locs = new ArrayList<>(HapiTestEngine.NODE_COUNT); - for (int i = 0; i < HapiTestEngine.NODE_COUNT; i++) { + final List locs = new ArrayList<>(SharedNetworkLauncherSessionListener.DEFAULT_SHARED_NETWORK_SIZE); + for (int i = 0; i < SharedNetworkLauncherSessionListener.DEFAULT_SHARED_NETWORK_SIZE; i++) { locs.add(String.format(HAPI_TEST_STREAMS_LOC_TPL, i, i + 3)); } return locs; diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/utilops/streams/RecordAssertions.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/utilops/streams/RecordAssertions.java index a0e1a2072ca9..3564c9354e48 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/utilops/streams/RecordAssertions.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/utilops/streams/RecordAssertions.java @@ -22,8 +22,8 @@ import static com.hedera.services.bdd.suites.HapiSuite.DEFAULT_PAYER; import static com.hedera.services.bdd.suites.HapiSuite.FUNDING; -import com.hedera.services.bdd.junit.RecordStreamValidator; import com.hedera.services.bdd.junit.TestBase; +import com.hedera.services.bdd.junit.support.RecordStreamValidator; import com.hedera.services.bdd.spec.HapiSpec; import com.hedera.services.bdd.spec.utilops.UtilOp; import edu.umd.cs.findbugs.annotations.Nullable; diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/utilops/streams/RecordFileChecker.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/utilops/streams/RecordFileChecker.java deleted file mode 100644 index df895eb96aa5..000000000000 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/utilops/streams/RecordFileChecker.java +++ /dev/null @@ -1,126 +0,0 @@ -/* - * Copyright (C) 2022-2024 Hedera Hashgraph, LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.hedera.services.bdd.spec.utilops.streams; - -import static com.hedera.services.bdd.spec.queries.QueryVerbs.getFileContents; -import static com.hedera.services.bdd.spec.utilops.CustomSpecAssert.allRunFor; - -import com.google.protobuf.InvalidProtocolBufferException; -import com.hedera.node.app.hapi.utils.exports.recordstreaming.RecordStreamingUtils; -import com.hedera.services.bdd.spec.HapiSpec; -import com.hedera.services.bdd.spec.queries.file.HapiGetFileContents; -import com.hedera.services.bdd.spec.utilops.UtilOp; -import com.hederahashgraph.api.proto.java.NodeAddressBook; -import com.hederahashgraph.api.proto.java.Transaction; -import com.hederahashgraph.api.proto.java.TransactionRecord; -import java.io.IOException; -import java.nio.file.Path; -import java.util.Arrays; -import java.util.List; -import org.apache.commons.lang3.tuple.Pair; -import org.junit.jupiter.api.Assertions; - -public class RecordFileChecker extends UtilOp { - - private static final String PATH_TO_LOCAL_STREAMS = "../hedera-node/data/recordstreams/record%d.%d.%d"; - private static final String ERROR_MESSAGE = "Record file %s could not be read in node %d.%d.%d stream files."; - private static final String TRANSACTION_RECORD_ERROR_MESSAGE = "Transaction record validation from file failed. "; - private static final String TRANSACTION_ERROR_MESSAGE = "Transaction validation from file failed"; - private static final String SIGNATURE_FILE_MISSING_ERROR = "Missing signature file."; - - private final String recordFileName; - private final List transactionRecord; - private final List transactions; - - public RecordFileChecker( - String recordFileName, List transactions, TransactionRecord... transactionRecord) { - this.recordFileName = recordFileName; - this.transactionRecord = Arrays.asList(transactionRecord); - this.transactions = transactions; - } - - @Override - protected boolean submitOp(HapiSpec spec) throws Throwable { - lookForFile(spec); - return false; - } - - @SuppressWarnings("java:S5960") - private void lookForFile(HapiSpec spec) throws IOException { - var addressBook = downloadBook(spec); - - for (var address : addressBook.getNodeAddressList()) { - var nodeAccountId = address.getNodeAccountId(); - var pathToFile = Path.of( - String.format( - PATH_TO_LOCAL_STREAMS, - nodeAccountId.getShardNum(), - nodeAccountId.getRealmNum(), - nodeAccountId.getAccountNum()), - recordFileName); - - var pathToSig = Path.of( - String.format( - PATH_TO_LOCAL_STREAMS, - nodeAccountId.getShardNum(), - nodeAccountId.getRealmNum(), - nodeAccountId.getAccountNum()), - recordFileName + "_sig"); - - final var signatureFilePair = RecordStreamingUtils.readSignatureFile(pathToSig.toString()); - Assertions.assertTrue(signatureFilePair.getRight().isPresent(), SIGNATURE_FILE_MISSING_ERROR); - - final var recordFileVersionAndProto = RecordStreamingUtils.readRecordStreamFile(pathToFile.toString()); - var recordStreamFileOptional = recordFileVersionAndProto.getRight(); - - Assertions.assertTrue( - recordStreamFileOptional.isPresent(), - String.format( - ERROR_MESSAGE, - pathToFile, - nodeAccountId.getShardNum(), - nodeAccountId.getRealmNum(), - nodeAccountId.getAccountNum())); - - final var recordStreamFile = recordStreamFileOptional.orElseThrow(); - final var actualRecordsInFile = recordStreamFile.getRecordStreamItemsList().stream() - .map(rsi -> Pair.of(rsi.getTransaction(), rsi.getRecord())) - .toList(); - - for (int i = 0; i < actualRecordsInFile.size(); i++) { - Assertions.assertEquals( - actualRecordsInFile.get(i).getRight(), - transactionRecord.get(i), - TRANSACTION_RECORD_ERROR_MESSAGE); - Assertions.assertEquals( - actualRecordsInFile.get(i).getLeft(), transactions.get(i), TRANSACTION_ERROR_MESSAGE); - } - } - } - - private NodeAddressBook downloadBook(HapiSpec spec) throws InvalidProtocolBufferException { - String addressBook = spec.setup().nodeDetailsName(); - HapiGetFileContents op = getFileContents(addressBook); - allRunFor(spec, op); - byte[] serializedBook = op.getResponse() - .getFileGetContents() - .getFileContents() - .getContents() - .toByteArray(); - return NodeAddressBook.parseFrom(serializedBook); - } -} diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/utilops/streams/RecordStreamVerification.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/utilops/streams/RecordStreamVerification.java index 54b2f6fa3f22..2049b07ac407 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/utilops/streams/RecordStreamVerification.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/utilops/streams/RecordStreamVerification.java @@ -44,6 +44,9 @@ import org.apache.logging.log4j.Logger; import org.junit.jupiter.api.Assertions; +/** + * (FUTURE) Integrate this operation to CI in some form. + */ public class RecordStreamVerification extends UtilOp { private static final Logger log = LogManager.getLogger(RecordStreamVerification.class); diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/utilops/streams/assertions/EventualRecordStreamAssertion.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/utilops/streams/assertions/EventualRecordStreamAssertion.java index 4fe52c1dee75..b00869a16f89 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/utilops/streams/assertions/EventualRecordStreamAssertion.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/utilops/streams/assertions/EventualRecordStreamAssertion.java @@ -16,10 +16,10 @@ package com.hedera.services.bdd.spec.utilops.streams.assertions; -import static com.hedera.services.bdd.junit.RecordStreamAccess.RECORD_STREAM_ACCESS; +import static com.hedera.services.bdd.junit.support.RecordStreamAccess.RECORD_STREAM_ACCESS; -import com.hedera.services.bdd.junit.RecordStreamAccess; -import com.hedera.services.bdd.junit.StreamDataListener; +import com.hedera.services.bdd.junit.support.RecordStreamAccess; +import com.hedera.services.bdd.junit.support.StreamDataListener; import com.hedera.services.bdd.spec.HapiSpec; import com.hedera.services.stream.proto.RecordStreamItem; import com.hedera.services.stream.proto.TransactionSidecarRecord; @@ -76,7 +76,7 @@ public static EventualRecordStreamAssertion eventuallyAssertingNoFailures( public static String recordStreamLocFor(@NonNull final HapiSpec spec) { Objects.requireNonNull(spec); return switch (spec.targetNetworkType()) { - case HAPI_TEST_NETWORK -> HAPI_TEST_STREAMS_LOC_TEST_NETWORK; + case SHARED_HAPI_TEST_NETWORK -> HAPI_TEST_STREAMS_LOC_TEST_NETWORK; case CI_DOCKER_NETWORK -> TEST_CONTAINER_NODE0_STREAMS; case STANDALONE_MONO_NETWORK -> spec.setup().defaultRecordLoc(); }; diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/utilops/streams/assertions/ValidContractIdsAssertion.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/utilops/streams/assertions/ValidContractIdsAssertion.java index ac16b0f44801..7519be0744e6 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/utilops/streams/assertions/ValidContractIdsAssertion.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/utilops/streams/assertions/ValidContractIdsAssertion.java @@ -16,8 +16,9 @@ package com.hedera.services.bdd.spec.utilops.streams.assertions; -import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; +import com.hedera.services.stream.proto.ContractActionType; import com.hedera.services.stream.proto.TransactionSidecarRecord; import com.hederahashgraph.api.proto.java.AccountID; import com.hederahashgraph.api.proto.java.ContractID; @@ -55,6 +56,7 @@ private void validateStateChangeIds(@NonNull final TransactionSidecarRecord side private void validateActionIds(@NonNull final TransactionSidecarRecord sidecar) { final var actions = sidecar.getActions().getContractActionsList(); + System.out.println(actions); for (final var action : actions) { if (action.hasCallingAccount()) { assertValid(action.getCallingAccount(), "action#callingAccount", sidecar); @@ -65,11 +67,17 @@ private void validateActionIds(@NonNull final TransactionSidecarRecord sidecar) if (action.hasRecipientAccount()) { assertValid(action.getRecipientAccount(), "action#recipientAccount", sidecar); } else if (action.hasRecipientContract()) { - assertValid(action.getRecipientContract(), "action#recipientContract", sidecar, this::isValidId); + assertValid(action.getRecipientContract(), "action#recipientContract", sidecar, this::isValidRecipient); } - final var isMissingResult = !action.hasOutput() && !action.hasError() && !action.hasRevertReason(); - assertFalse(isMissingResult, "action is missing result (output, error, or revertReason)"); + if (action.getCallType() != ContractActionType.CREATE || action.hasOutput()) { + final var recipientIsSet = + (action.hasRecipientAccount() || action.hasRecipientContract() || action.hasTargetedAddress()); + assertTrue(recipientIsSet, "action is missing recipient (account, contract, or targetedAddress)"); + } + + final var resultIsSet = (action.hasOutput() || action.hasError() || action.hasRevertReason()); + assertTrue(resultIsSet, "action is missing result (output, error, or revertReason) - " + action); } } @@ -117,10 +125,14 @@ private void assertValid( } private boolean isValidId(long shard, long realm, long num) { - return shard == 0L && realm == 0L && num >= 1; + return shard == 0L && realm == 0L && num >= 1 && num < Integer.MAX_VALUE; + } + + private boolean isValidRecipient(long shard, long realm, long num) { + return shard == 0L && realm == 0L && num >= 0 && num < Integer.MAX_VALUE; } private boolean isValidOrFailedBytecodeCreationId(long shard, long realm, long num) { - return shard == 0L && realm == 0L && num >= 0; + return shard == 0L && realm == 0L && num >= 0 && num < Integer.MAX_VALUE; } } diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/utilops/throughput/FinishThroughputObs.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/utilops/throughput/FinishThroughputObs.java deleted file mode 100644 index 65563cdeab2f..000000000000 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/utilops/throughput/FinishThroughputObs.java +++ /dev/null @@ -1,144 +0,0 @@ -/* - * Copyright (C) 2020-2024 Hedera Hashgraph, LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.hedera.services.bdd.spec.utilops.throughput; - -import static java.util.concurrent.TimeUnit.MILLISECONDS; - -import com.google.common.base.MoreObjects; -import com.hedera.services.bdd.spec.HapiSpec; -import com.hedera.services.bdd.spec.queries.HapiQueryOp; -import com.hedera.services.bdd.spec.stats.ThroughputObs; -import com.hedera.services.bdd.spec.utilops.CustomSpecAssert; -import com.hedera.services.bdd.spec.utilops.UtilOp; -import java.util.Optional; -import java.util.function.Supplier; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; - -public class FinishThroughputObs extends UtilOp { - private static final Logger LOG = LogManager.getLogger(FinishThroughputObs.class); - - private final String name; - Optional sleepMs = Optional.empty(); - Optional maxObsLengthMs = Optional.empty(); - Optional[]>> gateSupplier = Optional.empty(); - - public FinishThroughputObs(String name) { - this.name = name; - } - - public FinishThroughputObs gatedByQueries(Supplier[]> queriesSupplier) { - gateSupplier = Optional.of(queriesSupplier); - return this; - } - - public FinishThroughputObs gatedByQuery(Supplier> querySupplier) { - gateSupplier = Optional.of(() -> new HapiQueryOp[] {querySupplier.get()}); - return this; - } - - public FinishThroughputObs sleepMs(long length) { - sleepMs = Optional.of(length); - return this; - } - - public FinishThroughputObs expiryMs(long length) { - maxObsLengthMs = Optional.of(length); - return this; - } - - @Override - @SuppressWarnings("java:S3516") - protected boolean submitOp(HapiSpec spec) throws Throwable { - ThroughputObs baseObs = spec.registry().getThroughputObs(name); - - if (gateSupplier.isEmpty()) { - int n = spec.numLedgerOps() - baseObs.getNumOpsAtObservationStart(); - long timeToOpenGate = System.currentTimeMillis() - baseObs.getCreationTime(); - LOG.info("{}{} saw {} ops for its throughput measurement.", spec::logPrefix, () -> this, () -> n); - baseObs.setNumOpsAtObservationFinish(spec.numLedgerOps()); - baseObs.setObsLengthMs(timeToOpenGate); - return false; - } - - sleepUntil(baseObs.getExpectedQueueSaturationTime()); - if (baseObs.getNumOpsAtExpectedQueueSaturation() == -1) { - LOG.warn("{}{} saw no ops executed after the expected queue saturation time!", spec::logPrefix, () -> this); - return false; - } - LOG.info( - "{}{} {} ops had been executed at queue saturation time.", - spec::logPrefix, - () -> this, - baseObs::getNumOpsAtExpectedQueueSaturation); - - long now = System.currentTimeMillis(); - long sleepTimeMs = sleepMs.orElse(spec.setup().defaultThroughputObsSleepMs()); - long obsExpirationTime = now + maxObsLengthMs.orElse(spec.setup().defaultThroughputObsExpiryMs()); - HapiQueryOp[] gatingQueries = gateSupplier.get().get(); - while (gatingQueries.length > 0 && now < obsExpirationTime) { - try { - CustomSpecAssert.allRunFor(spec, gatingQueries); - break; - } catch (final Exception ignored) { - // Intentionally ignored - } - LOG.info("{}{} sleeping {}ms before retrying gate!", spec::logPrefix, () -> this, () -> sleepTimeMs); - try { - MILLISECONDS.sleep(sleepTimeMs); - } catch (InterruptedException ignored) { - Thread.currentThread().interrupt(); - } - now = System.currentTimeMillis(); - gatingQueries = gateSupplier.get().get(); - } - - if (now < obsExpirationTime) { - int n = spec.numLedgerOps() - baseObs.getNumOpsAtExpectedQueueSaturation(); - long timeToOpenGate = now - baseObs.getExpectedQueueSaturationTime(); - LOG.info( - "{}{} observed {} ops before gating queries passed in {}ms.", - spec::logPrefix, - () -> this, - () -> n, - () -> timeToOpenGate); - baseObs.setNumOpsAtObservationFinish(spec.numLedgerOps()); - baseObs.setObsLengthMs(timeToOpenGate); - } else { - LOG.warn("{}{} never saw its gating queries pass!", spec::logPrefix, () -> this); - } - - return false; - } - - private void sleepUntil(long t) { - long now = System.currentTimeMillis(); - while (now < t) { - try { - Thread.sleep(t - now + 1L); - } catch (InterruptedException ignored) { - Thread.currentThread().interrupt(); - } - now = System.currentTimeMillis(); - } - } - - @Override - public String toString() { - return MoreObjects.toStringHelper(this).add("finishing", name).toString(); - } -} diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/utilops/throughput/StartThroughputObs.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/utilops/throughput/StartThroughputObs.java deleted file mode 100644 index 8c1765bcce39..000000000000 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/utilops/throughput/StartThroughputObs.java +++ /dev/null @@ -1,51 +0,0 @@ -/* - * Copyright (C) 2020-2024 Hedera Hashgraph, LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.hedera.services.bdd.spec.utilops.throughput; - -import com.google.common.base.MoreObjects; -import com.hedera.services.bdd.spec.HapiSpec; -import com.hedera.services.bdd.spec.stats.ThroughputObs; -import com.hedera.services.bdd.spec.utilops.UtilOp; -import java.util.Optional; - -public class StartThroughputObs extends UtilOp { - private final String name; - Optional expectedTimeToSaturateQueue = Optional.empty(); - - public StartThroughputObs(String name) { - this.name = name; - } - - public StartThroughputObs msToSaturateQueues(long period) { - expectedTimeToSaturateQueue = Optional.of(period); - return this; - } - - @Override - protected boolean submitOp(HapiSpec spec) { - long saturationTime = System.currentTimeMillis() - + expectedTimeToSaturateQueue.orElse(spec.setup().defaultQueueSaturationMs()); - ThroughputObs baseObs = new ThroughputObs(name, saturationTime, spec); - spec.registry().saveThroughputObs(baseObs); - return false; - } - - @Override - public String toString() { - return MoreObjects.toStringHelper(this).add("starting", name).toString(); - } -} diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/verification/traceability/MismatchedSidecar.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/verification/traceability/MismatchedSidecar.java index e881f0b1fbc3..b80face5393d 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/verification/traceability/MismatchedSidecar.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/verification/traceability/MismatchedSidecar.java @@ -20,21 +20,4 @@ import com.hedera.services.stream.proto.TransactionSidecarRecord; public record MismatchedSidecar( - TransactionSidecarRecordMatcher expectedSidecarRecordMatcher, TransactionSidecarRecord actualSidecarRecord) { - - /** - * Check if the expected or actual sidecar record has actions. - * @return {@code true} if either of the records has actions. - */ - public boolean hasActions() { - return expectedSidecarRecordMatcher.hasActions() || actualSidecarRecord.hasActions(); - } - - /** - * Check if the expected or actual sidecar record has state changes. - * @return {@code true} if either of the records has state changes. - */ - public boolean hasStateChanges() { - return expectedSidecarRecordMatcher.hasStateChanges() || actualSidecarRecord.hasStateChanges(); - } -} + TransactionSidecarRecordMatcher expectedSidecarRecordMatcher, TransactionSidecarRecord actualSidecarRecord) {} diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/HapiSuite.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/HapiSuite.java index 240a8544995c..e656ef48025a 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/HapiSuite.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/HapiSuite.java @@ -19,7 +19,6 @@ import static com.hedera.services.bdd.suites.HapiSuite.FinalOutcome.SUITE_FAILED; import static com.hedera.services.bdd.suites.HapiSuite.FinalOutcome.SUITE_PASSED; -import com.hedera.services.bdd.junit.HapiTestNode; import com.hedera.services.bdd.spec.HapiSpec; import com.hedera.services.bdd.spec.HapiSpecOperation; import com.hedera.services.bdd.spec.HapiSpecSetup; @@ -28,6 +27,7 @@ import com.hederahashgraph.api.proto.java.ContractID; import com.hederahashgraph.api.proto.java.Key; import com.hederahashgraph.api.proto.java.KeyList; +import edu.umd.cs.findbugs.annotations.NonNull; import java.math.BigInteger; import java.util.Arrays; import java.util.Collections; @@ -36,10 +36,12 @@ import java.util.Random; import java.util.concurrent.CompletableFuture; import java.util.function.Consumer; +import java.util.function.Function; import java.util.function.IntFunction; import java.util.stream.IntStream; import java.util.stream.Stream; import org.apache.logging.log4j.Logger; +import org.junit.jupiter.api.DynamicTest; public abstract class HapiSuite { // The first 0 refers to the shard of the target network. @@ -48,13 +50,16 @@ public abstract class HapiSuite { public static final String TRUE_VALUE = "true"; public static final String FALSE_VALUE = "false"; public static final String TOKEN_UNDER_TEST = "TokenUnderTest"; + public static final String EVM_VERSION_PROPERTY = "contracts.evm.version"; + public static final String EVM_VERSION_046 = "v0.46"; + public static final String EVM_VERSION_050 = "v0.50"; protected static String ALICE = "ALICE"; protected static String BOB = "BOB"; protected static String CAROL = "CAROL"; protected static String RED_PARTITION = "RED_PARTITION"; protected static String BLUE_PARTITION = "BLUE_PARTITION"; protected static String GREEN_PARTITION = "GREEN_PARTITION"; - protected static String CIVILIAN_PAYER = "CIVILIAN_PAYER"; + public static String CIVILIAN_PAYER = "CIVILIAN_PAYER"; public static long FUNGIBLE_INITIAL_SUPPLY = 1_000_000_000L; public static long NON_FUNGIBLE_INITIAL_SUPPLY = 10L; public static long FUNGIBLE_INITIAL_BALANCE = FUNGIBLE_INITIAL_SUPPLY / 100; @@ -70,16 +75,27 @@ public enum FinalOutcome { protected abstract Logger getResultsLogger(); - public abstract List getSpecsInSuite(); + public abstract List> getSpecsInSuite(); + + private List getHapiSpecsInSuite() { + return getSpecsInSuite().stream() + .flatMap(Function.identity()) + .map(HapiSuite::specFrom) + .toList(); + } public List getSpecsInSuiteWithOverrides() { - final var specs = getSpecsInSuite(); + final var specs = getHapiSpecsInSuite(); if (!overrides.isEmpty()) { specs.forEach(spec -> spec.addOverrideProperties(overrides)); } return specs; } + private static HapiSpec specFrom(@NonNull final DynamicTest test) { + return (HapiSpec) test.getExecutable(); + } + public static final Key EMPTY_KEY = Key.newBuilder().setKeyList(KeyList.newBuilder().build()).build(); @@ -208,20 +224,6 @@ public void summarizeDeferredResults() { } } - public void runSuiteConcurrentWithOverrides(final Map overrides) { - this.overrides = overrides; - runSuiteAsync(); - } - - public void runSuiteSequentialWithOverrides(final Map overrides) { - this.overrides = overrides; - runSuiteSync(); - } - - public void setOverrides(final Map overrides) { - this.overrides = overrides; - } - public FinalOutcome runSuiteAsync() { return runSuite(HapiSuite::runConcurrentSpecs); } @@ -230,24 +232,6 @@ public FinalOutcome runSuiteSync() { return runSuite(HapiSuite::runSequentialSpecs); } - public FinalOutcome runSpecSync(HapiSpec spec, List nodes) { - if (!overrides.isEmpty()) { - spec.addOverrideProperties(overrides); - } - - final var name = name(); - spec.setSuitePrefix(name); - spec.setNodes(nodes); - spec.run(); - finalSpecs = List.of(spec); - // summarizeResults(getResultsLogger()); - if (tearDownClientsAfter) { - HapiApiClients.tearDown(); - } - - return finalOutcomeFor(finalSpecs); - } - protected FinalOutcome finalOutcomeFor(final List completedSpecs) { return completedSpecs.stream().allMatch(HapiSpec::ok) ? SUITE_PASSED : SUITE_FAILED; } @@ -258,7 +242,7 @@ private FinalOutcome runSuite(Consumer> runner) { getResultsLogger().info(STARTING_SUITE, name()); } - List specs = getSpecsInSuite(); + var specs = getHapiSpecsInSuite(); boolean autoSnapshotManagementOn = false; for (final var spec : specs) { autoSnapshotManagementOn |= spec.setup().autoSnapshotManagement(); @@ -299,11 +283,11 @@ public static HapiSpecOperation[] flattened(Object... ops) { } @SafeVarargs - protected final List allOf(final List... specLists) { + protected final List> allOf(final List>... specLists) { return Arrays.stream(specLists).flatMap(List::stream).toList(); } - protected HapiSpecOperation[] asOpArray(int n, IntFunction factory) { + public static HapiSpecOperation[] asOpArray(int n, IntFunction factory) { return IntStream.range(0, n).mapToObj(factory).toArray(HapiSpecOperation[]::new); } diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/SidecarAwareHapiSuite.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/SidecarAwareHapiSuite.java index 50d5fa123b9a..4bb1683e4914 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/SidecarAwareHapiSuite.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/SidecarAwareHapiSuite.java @@ -22,6 +22,7 @@ import static com.hedera.services.bdd.spec.utilops.CustomSpecAssert.allRunFor; import static com.hedera.services.bdd.spec.utilops.UtilStateChange.stateChangesToGrpc; import static com.hedera.services.bdd.spec.utilops.UtilVerbs.assertionsHold; +import static com.hedera.services.bdd.spec.utilops.UtilVerbs.sleepFor; import static com.hedera.services.bdd.spec.utilops.UtilVerbs.withOpContext; import static com.hedera.services.bdd.spec.utilops.streams.assertions.EventualRecordStreamAssertion.recordStreamLocFor; import static com.hedera.services.bdd.suites.contract.traceability.EncodingUtils.getInitcode; @@ -66,7 +67,7 @@ public abstract class SidecarAwareHapiSuite extends HapiSuite { * Initialize the sidecar watcher for the current spec. * @return A {@link CustomSpecAssert} that will initialize the sidecar watcher. */ - protected static CustomSpecAssert initializeSidecarWatcher() { + public static CustomSpecAssert initializeSidecarWatcher() { return withOpContext((spec, opLog) -> { final Path path = Paths.get(recordStreamLocFor(spec)); if (LOG.isInfoEnabled()) { @@ -81,10 +82,10 @@ protected static CustomSpecAssert initializeSidecarWatcher() { * Waits for expected sidecars and tears down the sidecar watcher for the current spec. * @return A {@link CustomSpecAssert} that will tear down the sidecar watcher. */ - protected static CustomSpecAssert tearDownSidecarWatcher() { + public static CustomSpecAssert tearDownSidecarWatcher() { return withOpContext((spec, opLog) -> { // send a dummy transaction to trigger externalization of last sidecars - allRunFor(spec, cryptoCreate("externalizeFinalSidecars").delayBy(3000)); + allRunFor(spec, sleepFor(3000), cryptoCreate("externalizeFinalSidecars")); sidecarWatcher.waitUntilFinished(); sidecarWatcher.tearDown(); }); @@ -104,7 +105,7 @@ protected static void addExpectedSidecar(final ExpectedSidecar expectedSidecar) * @param actions The contract actions to expect in the sidecar. * @return A {@link CustomSpecAssert} that will expect the sidecar file to be generated. */ - protected static CustomSpecAssert expectContractActionSidecarFor( + public static CustomSpecAssert expectContractActionSidecarFor( final String txnName, final List actions) { return withOpContext((spec, opLog) -> { final var txnRecord = getTxnRecord(txnName); @@ -126,7 +127,7 @@ protected static CustomSpecAssert expectContractActionSidecarFor( * @param stateChanges The contract state changes to expect in the sidecar. * @return A {@link CustomSpecAssert} that will expect the sidecar file to be generated. */ - protected static CustomSpecAssert expectContractStateChangesSidecarFor( + public static CustomSpecAssert expectContractStateChangesSidecarFor( final String txnName, final List stateChanges) { return withOpContext((spec, opLog) -> { final var txnRecord = getTxnRecord(txnName); @@ -152,7 +153,7 @@ protected static CustomSpecAssert expectContractStateChangesSidecarFor( * @param constructorArgs The constructor arguments to use for the init-code. * @return {@link CustomSpecAssert} expecting the sidecar file to be generated. */ - protected static CustomSpecAssert expectContractBytecodeSidecarFor( + public static CustomSpecAssert expectContractBytecodeSidecarFor( final String txnName, final String contractName, final String binFileName, @@ -189,7 +190,7 @@ protected static CustomSpecAssert expectContractBytecodeSidecarFor( * @param constructorArgs The constructor arguments to use for the init-code. * @return {@link CustomSpecAssert} expecting the sidecar file to be generated. */ - protected static CustomSpecAssert expectFailedContractBytecodeSidecarFor( + public static CustomSpecAssert expectFailedContractBytecodeSidecarFor( final String txnName, final String binFileName, final Object... constructorArgs) { return withOpContext((spec, opLog) -> { final var txnRecord = getTxnRecord(txnName); @@ -215,7 +216,7 @@ protected static CustomSpecAssert expectFailedContractBytecodeSidecarFor( * @param contractName The name of the contract to expect the bytecode for. * @return {@link CustomSpecAssert} expecting the sidecar file to be generated. */ - protected static CustomSpecAssert expectContractBytecodeWithMinimalFieldsSidecarFor( + public static CustomSpecAssert expectContractBytecodeWithMinimalFieldsSidecarFor( final String txnName, final String contractName) { return withOpContext((spec, opLog) -> { final var txnRecord = getTxnRecord(txnName).andAllChildRecords(); @@ -247,7 +248,7 @@ protected static CustomSpecAssert expectContractBytecodeWithMinimalFieldsSidecar * @param contractName The name of the contract. * @return {@link CustomSpecAssert} expecting the sidecar file to be generated. */ - protected static CustomSpecAssert expectContractBytecode(final String txnName, final String contractName) { + public static CustomSpecAssert expectContractBytecode(final String txnName, final String contractName) { return withOpContext((spec, opLog) -> { final var txnRecord = getTxnRecord(txnName); final var contractBytecode = getContractBytecode(contractName).saveResultTo(RUNTIME_CODE); @@ -280,7 +281,7 @@ protected static CustomSpecAssert expectContractBytecode(final String txnName, f * @param initCode The init bytecode of the contract. * @param runtimeCode The runtime bytecode of the contract. */ - protected static void expectContractBytecode( + public static void expectContractBytecode( final String specName, final Timestamp consensusTimestamp, final ContractID contractID, @@ -304,7 +305,7 @@ protected static void expectContractBytecode( * Asserts that there are no mismatched sidecars and no pending sidecars. * @return {@link CustomSpecAssert} with the assertions. */ - protected static CustomSpecAssert assertNoMismatchedSidecars() { + public static CustomSpecAssert assertNoMismatchedSidecars() { return assertionsHold((spec, assertLog) -> { assertTrue(sidecarWatcher.thereAreNoMismatchedSidecars(), sidecarWatcher.getMismatchErrors()); assertTrue( diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/SuiteRunner.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/SuiteRunner.java index 17e700637db2..e19c9301a74f 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/SuiteRunner.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/SuiteRunner.java @@ -28,159 +28,19 @@ import com.hedera.services.bdd.spec.HapiSpec; import com.hedera.services.bdd.spec.HapiSpecSetup; import com.hedera.services.bdd.spec.infrastructure.HapiApiClients; -import com.hedera.services.bdd.suites.autorenew.AccountAutoRenewalSuite; -import com.hedera.services.bdd.suites.autorenew.AutoRemovalCasesSuite; -import com.hedera.services.bdd.suites.autorenew.GracePeriodRestrictionsSuite; -import com.hedera.services.bdd.suites.autorenew.MacroFeesChargedSanityCheckSuite; -import com.hedera.services.bdd.suites.autorenew.NoGprIfNoAutoRenewSuite; -import com.hedera.services.bdd.suites.consensus.ChunkingSuite; -import com.hedera.services.bdd.suites.consensus.SubmitMessageSuite; -import com.hedera.services.bdd.suites.consensus.TopicCreateSuite; -import com.hedera.services.bdd.suites.consensus.TopicDeleteSuite; -import com.hedera.services.bdd.suites.consensus.TopicGetInfoSuite; -import com.hedera.services.bdd.suites.consensus.TopicUpdateSuite; -import com.hedera.services.bdd.suites.contract.hapi.ContractCallLocalSuite; -import com.hedera.services.bdd.suites.contract.hapi.ContractCallSuite; -import com.hedera.services.bdd.suites.contract.hapi.ContractCreateSuite; -import com.hedera.services.bdd.suites.contract.hapi.ContractDeleteSuite; -import com.hedera.services.bdd.suites.contract.hapi.ContractGetBytecodeSuite; -import com.hedera.services.bdd.suites.contract.hapi.ContractGetInfoSuite; -import com.hedera.services.bdd.suites.contract.hapi.ContractUpdateSuite; -import com.hedera.services.bdd.suites.contract.opcodes.CreateOperationSuite; -import com.hedera.services.bdd.suites.contract.opcodes.GlobalPropertiesSuite; -import com.hedera.services.bdd.suites.contract.opcodes.SStoreSuite; -import com.hedera.services.bdd.suites.contract.precompile.AssociatePrecompileSuite; -import com.hedera.services.bdd.suites.contract.precompile.ContractBurnHTSSuite; -import com.hedera.services.bdd.suites.contract.precompile.ContractHTSSuite; -import com.hedera.services.bdd.suites.contract.precompile.ContractKeysHTSSuite; -import com.hedera.services.bdd.suites.contract.precompile.ContractMintHTSSuite; -import com.hedera.services.bdd.suites.contract.precompile.CryptoTransferHTSSuite; -import com.hedera.services.bdd.suites.contract.precompile.DelegatePrecompileSuite; -import com.hedera.services.bdd.suites.contract.records.LogsSuite; -import com.hedera.services.bdd.suites.contract.records.RecordsSuite; -import com.hedera.services.bdd.suites.crypto.AutoAccountCreationSuite; -import com.hedera.services.bdd.suites.crypto.AutoAccountUpdateSuite; -import com.hedera.services.bdd.suites.crypto.CryptoApproveAllowanceSuite; -import com.hedera.services.bdd.suites.crypto.CryptoCornerCasesSuite; import com.hedera.services.bdd.suites.crypto.CryptoCreateForSuiteRunner; -import com.hedera.services.bdd.suites.crypto.CryptoCreateSuite; -import com.hedera.services.bdd.suites.crypto.CryptoDeleteAllowanceSuite; -import com.hedera.services.bdd.suites.crypto.CryptoDeleteSuite; -import com.hedera.services.bdd.suites.crypto.CryptoGetInfoRegression; -import com.hedera.services.bdd.suites.crypto.CryptoTransferSuite; -import com.hedera.services.bdd.suites.crypto.CryptoUpdateSuite; -import com.hedera.services.bdd.suites.crypto.QueryPaymentSuite; -import com.hedera.services.bdd.suites.fees.CongestionPricingSuite; -import com.hedera.services.bdd.suites.fees.SpecialAccountsAreExempted; -import com.hedera.services.bdd.suites.file.ExchangeRateControlSuite; -import com.hedera.services.bdd.suites.file.FetchSystemFiles; -import com.hedera.services.bdd.suites.file.FileAppendSuite; -import com.hedera.services.bdd.suites.file.FileCreateSuite; -import com.hedera.services.bdd.suites.file.FileDeleteSuite; -import com.hedera.services.bdd.suites.file.FileUpdateSuite; -import com.hedera.services.bdd.suites.file.PermissionSemanticsSpec; -import com.hedera.services.bdd.suites.file.ProtectedFilesUpdateSuite; -import com.hedera.services.bdd.suites.file.ValidateNewAddressBook; -import com.hedera.services.bdd.suites.file.negative.UpdateFailuresSpec; -import com.hedera.services.bdd.suites.file.positive.SysDelSysUndelSpec; -import com.hedera.services.bdd.suites.freeze.CryptoTransferThenFreezeTest; import com.hedera.services.bdd.suites.freeze.FreezeAbort; -import com.hedera.services.bdd.suites.freeze.FreezeSuite; import com.hedera.services.bdd.suites.freeze.FreezeUpgrade; import com.hedera.services.bdd.suites.freeze.PrepareUpgrade; import com.hedera.services.bdd.suites.freeze.SimpleFreezeOnly; import com.hedera.services.bdd.suites.freeze.UpdateFileForUpgrade; -import com.hedera.services.bdd.suites.freeze.UpdateServerFiles; -import com.hedera.services.bdd.suites.leaky.FeatureFlagSuite; import com.hedera.services.bdd.suites.meta.VersionInfoSpec; -import com.hedera.services.bdd.suites.misc.CannotDeleteSystemEntitiesSuite; -import com.hedera.services.bdd.suites.misc.ConsensusQueriesStressTests; -import com.hedera.services.bdd.suites.misc.ContractQueriesStressTests; -import com.hedera.services.bdd.suites.misc.CryptoQueriesStressTests; -import com.hedera.services.bdd.suites.misc.FileQueriesStressTests; -import com.hedera.services.bdd.suites.misc.MemoValidation; -import com.hedera.services.bdd.suites.misc.MixedOpsTransactionsSuite; -import com.hedera.services.bdd.suites.misc.OneOfEveryTransaction; -import com.hedera.services.bdd.suites.misc.ZeroStakeNodeTest; -import com.hedera.services.bdd.suites.perf.AccountBalancesClientSaveLoadTest; import com.hedera.services.bdd.suites.perf.AdjustFeeScheduleSuite; -import com.hedera.services.bdd.suites.perf.FileContractMemoPerfSuite; -import com.hedera.services.bdd.suites.perf.QueryOnlyLoadTest; -import com.hedera.services.bdd.suites.perf.contract.ContractCallLoadTest; -import com.hedera.services.bdd.suites.perf.contract.ContractCallLocalPerfSuite; -import com.hedera.services.bdd.suites.perf.contract.ContractCallPerfSuite; -import com.hedera.services.bdd.suites.perf.contract.ContractPerformanceSuite; -import com.hedera.services.bdd.suites.perf.contract.FibonacciPlusLoadProvider; -import com.hedera.services.bdd.suites.perf.contract.MixedSmartContractOpsLoadTest; -import com.hedera.services.bdd.suites.perf.contract.opcodes.SStoreOperationLoadTest; import com.hedera.services.bdd.suites.perf.crypto.*; -import com.hedera.services.bdd.suites.perf.file.FileUpdateLoadTest; -import com.hedera.services.bdd.suites.perf.file.MixedFileOpsLoadTest; -import com.hedera.services.bdd.suites.perf.mixedops.MixedOpsLoadTest; -import com.hedera.services.bdd.suites.perf.mixedops.MixedOpsMemoPerfSuite; -import com.hedera.services.bdd.suites.perf.mixedops.MixedTransferAndSubmitLoadTest; -import com.hedera.services.bdd.suites.perf.mixedops.MixedTransferCallAndSubmitLoadTest; -import com.hedera.services.bdd.suites.perf.schedule.ReadyToRunScheduledXfersLoad; -import com.hedera.services.bdd.suites.perf.token.TokenRelStatusChanges; -import com.hedera.services.bdd.suites.perf.token.TokenTransferBasicLoadTest; -import com.hedera.services.bdd.suites.perf.token.TokenTransfersLoadProvider; -import com.hedera.services.bdd.suites.perf.token.UniqueTokenStateSetup; -import com.hedera.services.bdd.suites.perf.topic.CreateTopicPerfSuite; -import com.hedera.services.bdd.suites.perf.topic.HCSChunkingRealisticPerfSuite; import com.hedera.services.bdd.suites.perf.topic.SubmitMessageLoadTest; -import com.hedera.services.bdd.suites.reconnect.AutoAccountCreationValidationsAfterReconnect; -import com.hedera.services.bdd.suites.reconnect.AutoAccountCreationsBeforeReconnect; -import com.hedera.services.bdd.suites.reconnect.AutoRenewEntitiesForReconnect; -import com.hedera.services.bdd.suites.reconnect.CheckUnavailableNode; import com.hedera.services.bdd.suites.reconnect.CreateAccountsBeforeReconnect; import com.hedera.services.bdd.suites.reconnect.CreateFilesBeforeReconnect; -import com.hedera.services.bdd.suites.reconnect.CreateSchedulesBeforeReconnect; -import com.hedera.services.bdd.suites.reconnect.CreateTokensBeforeReconnect; import com.hedera.services.bdd.suites.reconnect.CreateTopicsBeforeReconnect; -import com.hedera.services.bdd.suites.reconnect.MixedValidationsAfterReconnect; -import com.hedera.services.bdd.suites.reconnect.SchedulesExpiryDuringReconnect; -import com.hedera.services.bdd.suites.reconnect.SubmitMessagesForReconnect; -import com.hedera.services.bdd.suites.reconnect.UpdateAllProtectedFilesDuringReconnect; -import com.hedera.services.bdd.suites.reconnect.UpdatePermissionsDuringReconnect; -import com.hedera.services.bdd.suites.reconnect.ValidateAppPropertiesStateAfterReconnect; -import com.hedera.services.bdd.suites.reconnect.ValidateCongestionPricingAfterReconnect; -import com.hedera.services.bdd.suites.reconnect.ValidateDuplicateTransactionAfterReconnect; -import com.hedera.services.bdd.suites.reconnect.ValidateExchangeRateStateAfterReconnect; -import com.hedera.services.bdd.suites.reconnect.ValidateFeeScheduleStateAfterReconnect; -import com.hedera.services.bdd.suites.reconnect.ValidatePermissionStateAfterReconnect; -import com.hedera.services.bdd.suites.reconnect.ValidateTokensDeleteAfterReconnect; -import com.hedera.services.bdd.suites.reconnect.ValidateTokensStateAfterReconnect; -import com.hedera.services.bdd.suites.records.ContractRecordsSanityCheckSuite; -import com.hedera.services.bdd.suites.records.CryptoRecordsSanityCheckSuite; -import com.hedera.services.bdd.suites.records.DuplicateManagementTest; -import com.hedera.services.bdd.suites.records.FileRecordsSanityCheckSuite; -import com.hedera.services.bdd.suites.records.RecordCreationSuite; -import com.hedera.services.bdd.suites.records.SignedTransactionBytesRecordsSuite; -import com.hedera.services.bdd.suites.regression.AddWellKnownEntities; -import com.hedera.services.bdd.suites.regression.JrsRestartTestTemplate; -import com.hedera.services.bdd.suites.regression.SteadyStateThrottlingCheck; -import com.hedera.services.bdd.suites.regression.TargetNetworkPrep; -import com.hedera.services.bdd.suites.regression.UmbrellaRedux; -import com.hedera.services.bdd.suites.schedule.ScheduleCreateSpecs; -import com.hedera.services.bdd.suites.schedule.ScheduleDeleteSpecs; -import com.hedera.services.bdd.suites.schedule.ScheduleExecutionSpecStateful; -import com.hedera.services.bdd.suites.schedule.ScheduleExecutionSpecs; -import com.hedera.services.bdd.suites.schedule.ScheduleLongTermExecutionSpecs; -import com.hedera.services.bdd.suites.schedule.ScheduleLongTermSignSpecs; -import com.hedera.services.bdd.suites.schedule.ScheduleRecordSpecs; -import com.hedera.services.bdd.suites.schedule.ScheduleSignSpecs; -import com.hedera.services.bdd.suites.streaming.RecordStreamValidation; -import com.hedera.services.bdd.suites.throttling.PrivilegedOpsSuite; -import com.hedera.services.bdd.suites.throttling.ThrottleDefValidationSuite; -import com.hedera.services.bdd.suites.token.Hip17UnhappyTokensSuite; -import com.hedera.services.bdd.suites.token.TokenAssociationSpecs; -import com.hedera.services.bdd.suites.token.TokenCreateSpecs; -import com.hedera.services.bdd.suites.token.TokenDeleteSpecs; -import com.hedera.services.bdd.suites.token.TokenManagementSpecs; -import com.hedera.services.bdd.suites.token.TokenManagementSpecsStateful; -import com.hedera.services.bdd.suites.token.TokenPauseSpecs; -import com.hedera.services.bdd.suites.token.TokenTransactSpecs; -import com.hedera.services.bdd.suites.token.TokenUpdateSpecs; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; @@ -219,262 +79,18 @@ public class SuiteRunner { @SuppressWarnings({"java:S1171", "java:S3599", "java:S125"}) private static final Map> CATEGORY_MAP = new HashMap<>() { { - /* Convenience entries, uncomment locally to run CI jobs */ - // put("CiConsensusAndCryptoJob", aof( - // SignedTransactionBytesRecordsSuite::new, - // DuplicateManagementTest::new, - // TopicCreateSuite::new, - // TopicUpdateSuite::new, - // TopicDeleteSuite::new, - // SubmitMessageSuite::new, - // ChunkingSuite::new, - // TopicGetInfoSuite::new, - // SpecialAccountsAreExempted::new, - // CryptoTransferSuite::new, - // CryptoUpdateSuite::new, - // CryptoRecordsSanityCheckSuite::new, - // PrivilegedOpsSuite::new, - // CannotDeleteSystemEntitiesSuite::new)); - // put("CiScheduleJob", aof( - // ScheduleDeleteSpecs::new, - // ScheduleExecutionSpecs::new, - // ScheduleCreateSpecs::new, - // ScheduleSignSpecs::new, - // ScheduleRecordSpecs::new)); - // put("CiTokenJob", aof( - // TokenAssociationSpecs::new, - // TokenUpdateSpecs::new, - // TokenCreateSpecs::new, - // TokenDeleteSpecs::new, - // TokenManagementSpecs::new, - // TokenTransactSpecs::new)); - // put("CiFileJob", aof( - // FileRecordsSanityCheckSuite::new, - // VersionInfoSpec::new, - // ProtectedFilesUpdateSuite::new, - // PermissionSemanticsSpec::new, - // SysDelSysUndelSpec::new)); - // put("CiSmartContractJob", aof( - // ContractQueriesStressTests::new, - // ContractCallLocalSuite::new, - // ContractCreateSuite::new, - // SStoreSuite::new, - // ContractDeleteSuite::new, - // ContractGetBytecodeSuite::new, - // ContractGetInfoSuite::new, - // ContractUpdateSuite::new, - // ContractRecordsSanityCheckSuite::new, - // ContractCallSuite::new, - // BalanceOperationSuite::new, - // CallCodeOperationSuite::new, - // CallOperationSuite::new, - // CreateOperationSuite::new, - // DelegateCallOperationSuite::new, - // ExtCodeCopyOperationSuite::new, - // ExtCodeHashOperationSuite::new, - // ExtCodeSizeOperationSuite::new, - // GlobalPropertiesSuite::new, - // StaticCallOperationSuite::new, - // SStoreOperationLoadTest::new, - // ContractCallLoadTest::new, - // ContractCallLocalPerfSuite::new, - // ContractCallPerfSuite::new, - // ContractPerformanceSuite::new, - // MixedSmartContractOpsLoadTest::new)); - /* Adjust fee schedules */ - put("AdjustFeeSchedule", aof(AdjustFeeScheduleSuite::new)); - /* Umbrella Redux */ - put("UmbrellaRedux", aof(UmbrellaRedux::new)); - /* Regression saved state management helpers */ - put("AddWellKnownEntities", aof(AddWellKnownEntities::new)); - /* JRS restart tests */ - put("RestartWithScheduledEntities", aof(JrsRestartTestTemplate::new)); - /* Load tests. */ - put("SimpleXfersAvoidingHotspot", aof(SimpleXfersAvoidingHotspot::new)); - put("NWayDistNoHotspots", aof(NWayDistNoHotspots::new)); - put("QueryOnlyLoadTest", aof(QueryOnlyLoadTest::new)); - put("TokenTransfersBasicLoadTest", aof(TokenTransferBasicLoadTest::new)); - put("AccountBalancesLoadTest", aof(AccountBalancesClientSaveLoadTest::new)); - put("TokenTransfersLoad", aof(TokenTransfersLoadProvider::new)); - put("ReadyToRunScheduledXfersLoad", aof(ReadyToRunScheduledXfersLoad::new)); - put("TokenRelChangesLoad", aof(TokenRelStatusChanges::new)); - put("FileUpdateLoadTest", aof(FileUpdateLoadTest::new)); - put("ContractCallLoadTest", aof(ContractCallLoadTest::new)); - put("SubmitMessageLoadTest", aof(SubmitMessageLoadTest::new)); - put("CryptoTransferLoadTest", aof(CryptoTransferLoadTest::new)); put("CryptoTransferLoadTestWithStakedAccounts", aof(CryptoTransferLoadTestWithStakedAccounts::new)); - put("CryptoTransferLoadTestWithAutoAccounts", aof(CryptoTransferLoadTestWithAutoAccounts::new)); - put("CryptoTransferLoadTestWithInvalidAccounts", aof(CryptoTransferLoadTestWithInvalidAccounts::new)); - put("MixedTransferAndSubmitLoadTest", aof(MixedTransferAndSubmitLoadTest::new)); - put("MixedTransferCallAndSubmitLoadTest", aof(MixedTransferCallAndSubmitLoadTest::new)); - put("HCSChunkingRealisticPerfSuite", aof(HCSChunkingRealisticPerfSuite::new)); - put("CryptoCreatePerfSuite", aof(CryptoCreatePerfSuite::new)); - put("CreateTopicPerfSuite", aof(CreateTopicPerfSuite::new)); - put("MixedOpsMemoPerfSuite", aof(MixedOpsMemoPerfSuite::new)); - put("FileContractMemoPerfSuite", aof(FileContractMemoPerfSuite::new)); - // put("MixedSmartContractOpsLoadTest", - // aof(MixedSmartContractOpsLoadTest::new)); - put("MixedFileOpsLoadTest", aof(MixedFileOpsLoadTest::new)); - put("UniqueTokenStateSetup", aof(UniqueTokenStateSetup::new)); - /* Functional tests - RECONNECT */ - put("CreateAccountsBeforeReconnect", aof(CreateAccountsBeforeReconnect::new)); - put("CreateTopicsBeforeReconnect", aof(CreateTopicsBeforeReconnect::new)); - put("SubmitMessagesForReconnect", aof(SubmitMessagesForReconnect::new)); - put("CreateFilesBeforeReconnect", aof(CreateFilesBeforeReconnect::new)); - put("CreateTokensBeforeReconnect", aof(CreateTokensBeforeReconnect::new)); - put("CreateSchedulesBeforeReconnect", aof(CreateSchedulesBeforeReconnect::new)); - put("CheckUnavailableNode", aof(CheckUnavailableNode::new)); - put("MixedValidationsAfterReconnect", aof(MixedValidationsAfterReconnect::new)); - put("UpdateApiPermissionsDuringReconnect", aof(UpdatePermissionsDuringReconnect::new)); - put("ValidateDuplicateTransactionAfterReconnect", aof(ValidateDuplicateTransactionAfterReconnect::new)); - put("ValidateApiPermissionStateAfterReconnect", aof(ValidatePermissionStateAfterReconnect::new)); - put("ValidateAppPropertiesStateAfterReconnect", aof(ValidateAppPropertiesStateAfterReconnect::new)); - put("ValidateFeeScheduleStateAfterReconnect", aof(ValidateFeeScheduleStateAfterReconnect::new)); - put("ValidateExchangeRateStateAfterReconnect", aof(ValidateExchangeRateStateAfterReconnect::new)); - put("UpdateAllProtectedFilesDuringReconnect", aof(UpdateAllProtectedFilesDuringReconnect::new)); - put("AutoRenewEntitiesForReconnect", aof(AutoRenewEntitiesForReconnect::new)); - put("SchedulesExpiryDuringReconnect", aof(SchedulesExpiryDuringReconnect::new)); - put("ValidateTokensStateAfterReconnect", aof(ValidateTokensStateAfterReconnect::new)); - put("ValidateCongestionPricingAfterReconnect", aof(ValidateCongestionPricingAfterReconnect::new)); - /* Functional tests - AutoAccountCreations */ - put("AutoAccountCreationValidationsAfterReconnect", aof(AutoAccountCreationValidationsAfterReconnect::new)); - put("AutoAccountCreationSuite", aof(AutoAccountCreationSuite::new)); - put("AutoAccountUpdateSuite", aof(AutoAccountUpdateSuite::new)); - put("AutoAccountCreationsBeforeReconnect", aof(AutoAccountCreationsBeforeReconnect::new)); - /* Functional tests - AUTORENEW */ - put("AutoRemovalCasesSuite", aof(AutoRemovalCasesSuite::new)); - // put("AccountAutoRenewalSuite", aof(AccountAutoRenewalSuite::new)); - put("GracePeriodRestrictionsSuite", aof(GracePeriodRestrictionsSuite::new)); - put("MacroFeesChargedSanityCheckSuite", aof(MacroFeesChargedSanityCheckSuite::new)); - put("NoGprIfNoAutoRenewSuite", aof(NoGprIfNoAutoRenewSuite::new)); - /* Functional tests - CONSENSUS */ - put("TopicCreateSpecs", aof(TopicCreateSuite::new)); - put("TopicDeleteSpecs", aof(TopicDeleteSuite::new)); - put("TopicUpdateSpecs", aof(TopicUpdateSuite::new)); - put("SubmitMessageSpecs", aof(SubmitMessageSuite::new)); - put("HCSTopicFragmentationSuite", aof(ChunkingSuite::new)); - put("TopicGetInfoSpecs", aof(TopicGetInfoSuite::new)); - put("ConsensusQueriesStressTests", aof(ConsensusQueriesStressTests::new)); - /* Functional tests - FILE */ - put("FileCreateSuite", aof(FileCreateSuite::new)); - put("FileAppendSuite", aof(FileAppendSuite::new)); - put("FileUpdateSuite", aof(FileUpdateSuite::new)); - put("FileDeleteSuite", aof(FileDeleteSuite::new)); - put("UpdateFailuresSpec", aof(UpdateFailuresSpec::new)); - put("ExchangeRateControlSuite", aof(ExchangeRateControlSuite::new)); - put("PermissionSemanticsSpec", aof(PermissionSemanticsSpec::new)); - put("FileQueriesStressTests", aof(FileQueriesStressTests::new)); - /* Functional tests - SCHEDULE */ - put("ScheduleCreateSpecs", aof(ScheduleCreateSpecs::new)); - put("ScheduleSignSpecs", aof(ScheduleSignSpecs::new)); - put("ScheduleLongTermExecutionSpecs", aof(ScheduleLongTermExecutionSpecs::new)); - put("ScheduleLongTermSignSpecs", aof(ScheduleLongTermSignSpecs::new)); - put("ScheduleRecordSpecs", aof(ScheduleRecordSpecs::new)); - put("ScheduleDeleteSpecs", aof(ScheduleDeleteSpecs::new)); - put("ScheduleExecutionSpecs", aof(ScheduleExecutionSpecs::new)); - put("ScheduleExecutionSpecStateful", aof(ScheduleExecutionSpecStateful::new)); - /* Functional tests - TOKEN */ - put("TokenCreateSpecs", aof(TokenCreateSpecs::new)); - put("TokenUpdateSpecs", aof(TokenUpdateSpecs::new)); - put("TokenDeleteSpecs", aof(TokenDeleteSpecs::new)); - put("TokenTransactSpecs", aof(TokenTransactSpecs::new)); - put("TokenManagementSpecs", aof(TokenManagementSpecs::new)); - put("TokenAssociationSpecs", aof(TokenAssociationSpecs::new)); - put("TokenPauseSpecs", aof(TokenPauseSpecs::new)); - put("Hip17UnhappyTokensSuite", aof(Hip17UnhappyTokensSuite::new)); - put("TokenManagementSpecsStateful", aof(TokenManagementSpecsStateful::new)); - /* Functional tests - CRYPTO */ - put("CryptoTransferSuite", aof(CryptoTransferSuite::new)); - put("CryptoDeleteSuite", aof(CryptoDeleteSuite::new)); - put("CryptoCreateSuite", aof(CryptoCreateSuite::new)); - put("CryptoUpdateSuite", aof(CryptoUpdateSuite::new)); - put("CryptoQueriesStressTests", aof(CryptoQueriesStressTests::new)); - put("CryptoCornerCasesSuite", aof(CryptoCornerCasesSuite::new)); - put("CryptoGetInfoRegression", aof(CryptoGetInfoRegression::new)); - /* Functional tests - CONTRACTS */ - put("ContractQueriesStressTests", aof(ContractQueriesStressTests::new)); - put("ContractCallLocalSuite", aof(ContractCallLocalSuite::new)); - put("ContractCreateSuite", aof(ContractCreateSuite::new)); - put("SStoreSuite", aof(SStoreSuite::new)); - put("ContractDeleteSuite", aof(ContractDeleteSuite::new)); - put("ContractGetBytecodeSuite", aof(ContractGetBytecodeSuite::new)); - put("ContractGetInfoSuite", aof(ContractGetInfoSuite::new)); - put("ContractUpdateSuite", aof(ContractUpdateSuite::new)); - put("ContractCallSuite", aof(ContractCallSuite::new)); - put("CreateOperationSuite", aof(CreateOperationSuite::new)); - put("GlobalPropertiesSuite", aof(GlobalPropertiesSuite::new)); - put("SStoreOperationLoadTest", aof(SStoreOperationLoadTest::new)); - // put("ContractCallLoadTest", aof(ContractCallLoadTest::new)); - put("ContractCallLocalPerfSuite", aof(ContractCallLocalPerfSuite::new)); - put("ContractCallPerfSuite", aof(ContractCallPerfSuite::new)); - put("ContractPerformanceSuite", aof(ContractPerformanceSuite::new)); - put("MixedSmartContractOpsLoadTest", aof(MixedSmartContractOpsLoadTest::new)); - put("FibonacciPlusLoadProvider", aof(FibonacciPlusLoadProvider::new)); - put("AssociatePrecompileSuite", aof(AssociatePrecompileSuite::new)); - put("ContractBurnHTSSuite", aof(ContractBurnHTSSuite::new)); - put("ContractHTSSuite", aof(ContractHTSSuite::new)); - put("ContractKeysHTSSuite", aof(ContractKeysHTSSuite::new)); - put("ContractMintHTSSuite", aof(ContractMintHTSSuite::new)); - put("CryptoTransferHTSSuite", aof(CryptoTransferHTSSuite::new)); - put("DelegatePrecompileSuite", aof(DelegatePrecompileSuite::new)); - /* Functional tests - AUTORENEW */ - put("AccountAutoRenewalSuite", aof(AccountAutoRenewalSuite::new)); - /* Functional tests - MIXED (record emphasis) */ - put("ThresholdRecordCreationSpecs", aof(RecordCreationSuite::new)); - put("SignedTransactionBytesRecordsSuite", aof(SignedTransactionBytesRecordsSuite::new)); - put("CryptoRecordSanityChecks", aof(CryptoRecordsSanityCheckSuite::new)); - put("FileRecordSanityChecks", aof(FileRecordsSanityCheckSuite::new)); - put("ContractRecordSanityChecks", aof(ContractRecordsSanityCheckSuite::new)); - put("LogsSuite", aof(LogsSuite::new)); - put("RecordsSuite", aof(RecordsSuite::new)); - put("ProtectedFilesUpdateSuite", aof(ProtectedFilesUpdateSuite::new)); - put("DuplicateManagementTest", aof(DuplicateManagementTest::new)); - /* Record validation. */ - put("RecordStreamValidation", aof(RecordStreamValidation::new)); - /* Fee characterization. */ - put("ControlAccountsExemptForUpdates", aof(SpecialAccountsAreExempted::new)); - /* System files. */ - put("FetchSystemFiles", aof(FetchSystemFiles::new)); - put("CannotDeleteSystemEntitiesSuite", aof(CannotDeleteSystemEntitiesSuite::new)); - /* Throttling */ - put("ThrottleDefValidationSuite", aof(ThrottleDefValidationSuite::new)); - put("CongestionPricingSuite", aof(CongestionPricingSuite::new)); - put("SteadyStateThrottlingCheck", aof(SteadyStateThrottlingCheck::new)); - /* Network metadata. */ put("VersionInfoSpec", aof(VersionInfoSpec::new)); - put("FreezeSuite", aof(FreezeSuite::new)); - /* Authorization. */ - put("PrivilegedOpsSuite", aof(PrivilegedOpsSuite::new)); - put("SysDelSysUndelSpec", aof(SysDelSysUndelSpec::new)); - /* Freeze and update */ - put("UpdateServerFiles", aof(UpdateServerFiles::new)); - put("OneOfEveryTxn", aof(OneOfEveryTransaction::new)); - /* Zero Stake behaviour */ - put("ZeroStakeTest", aof(ZeroStakeNodeTest::new)); - /* Query payment validation */ - put("QueryPaymentSuite", aof(QueryPaymentSuite::new)); - put("SimpleFreezeOnly", aof(SimpleFreezeOnly::new)); - /* Transfer then freeze */ - put("CryptoTransferThenFreezeTest", aof(CryptoTransferThenFreezeTest::new)); - put("MixedOpsTransactionsSuite", aof(MixedOpsTransactionsSuite::new)); - put("MixedOpsLoadTest", aof(MixedOpsLoadTest::new)); - /* Validate new AddressBook */ - put("ValidateNewAddressBook", aof(ValidateNewAddressBook::new)); - put("CryptoTransferPerfSuiteWOpProvider", aof(CryptoTransferPerfSuiteWOpProvider::new)); - put("ValidateTokensDeleteAfterReconnect", aof(ValidateTokensDeleteAfterReconnect::new)); - /* Freeze with upgrade */ put("UpdateFileForUpgrade", aof(UpdateFileForUpgrade::new)); put("PrepareUpgrade", aof(PrepareUpgrade::new)); put("FreezeUpgrade", aof(FreezeUpgrade::new)); + put("SimpleFreezeOnly", aof(SimpleFreezeOnly::new)); put("FreezeAbort", aof(FreezeAbort::new)); - /* Memo validation */ - put("MemoValidation", aof(MemoValidation::new)); - /* Approval and Allowance */ - put("CryptoApproveAllowanceSuite", aof(CryptoApproveAllowanceSuite::new)); - put("CryptoDeleteAllowanceSuite", aof(CryptoDeleteAllowanceSuite::new)); - /* Network Prep*/ - put("TargetNetworkPrep", aof(TargetNetworkPrep::new)); - put("FeatureFlagSuite", aof(FeatureFlagSuite::new)); + put("CreateAccountsBeforeReconnect", aof(CreateAccountsBeforeReconnect::new)); + put("CreateTopicsBeforeReconnect", aof(CreateTopicsBeforeReconnect::new)); + put("CreateFilesBeforeReconnect", aof(CreateFilesBeforeReconnect::new)); + put("SubmitMessageLoadTest", aof(SubmitMessageLoadTest::new)); + put("AdjustFeeSchedule", aof(AdjustFeeScheduleSuite::new)); } }; diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/TargetNetworkType.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/TargetNetworkType.java index 98dbd02ce8cb..d0d123c93359 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/TargetNetworkType.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/TargetNetworkType.java @@ -25,9 +25,9 @@ */ public enum TargetNetworkType { /** - * A network launched by the {@link com.hedera.services.bdd.junit.HapiTestEngine}. + * A network launched by the {@link com.hedera.services.bdd.junit.SharedNetworkLauncherSessionListener}. */ - HAPI_TEST_NETWORK, + SHARED_HAPI_TEST_NETWORK, /** * A mono-service network started via Gradle task (can be removed once mono-service is no longer in use). */ diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/autorenew/AccountAutoRenewalSuite.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/autorenew/AccountAutoRenewalSuite.java deleted file mode 100644 index cd117b55777d..000000000000 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/autorenew/AccountAutoRenewalSuite.java +++ /dev/null @@ -1,268 +0,0 @@ -/* - * Copyright (C) 2020-2024 Hedera Hashgraph, LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.hedera.services.bdd.suites.autorenew; - -import static com.hedera.services.bdd.spec.HapiSpec.defaultHapiSpec; -import static com.hedera.services.bdd.spec.assertions.AccountInfoAsserts.accountWith; -import static com.hedera.services.bdd.spec.queries.QueryVerbs.getAccountBalance; -import static com.hedera.services.bdd.spec.queries.QueryVerbs.getAccountInfo; -import static com.hedera.services.bdd.spec.queries.QueryVerbs.getTxnRecord; -import static com.hedera.services.bdd.spec.transactions.TxnVerbs.cryptoCreate; -import static com.hedera.services.bdd.spec.transactions.TxnVerbs.cryptoDelete; -import static com.hedera.services.bdd.spec.transactions.TxnVerbs.cryptoTransfer; -import static com.hedera.services.bdd.spec.transactions.TxnVerbs.fileUpdate; -import static com.hedera.services.bdd.spec.transactions.crypto.HapiCryptoTransfer.tinyBarsFromTo; -import static com.hedera.services.bdd.spec.utilops.CustomSpecAssert.allRunFor; -import static com.hedera.services.bdd.spec.utilops.UtilVerbs.assertionsHold; -import static com.hedera.services.bdd.spec.utilops.UtilVerbs.freezeOnly; -import static com.hedera.services.bdd.spec.utilops.UtilVerbs.inParallel; -import static com.hedera.services.bdd.spec.utilops.UtilVerbs.sleepFor; -import static com.hedera.services.bdd.suites.autorenew.AutoRenewConfigChoices.DEFAULT_HIGH_TOUCH_COUNT; -import static com.hedera.services.bdd.suites.autorenew.AutoRenewConfigChoices.HIGH_SCAN_CYCLE_COUNT; -import static com.hedera.services.bdd.suites.autorenew.AutoRenewConfigChoices.disablingAutoRenewWithDefaults; -import static com.hedera.services.bdd.suites.autorenew.AutoRenewConfigChoices.propsForAccountAutoRenewOnWith; -import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.INVALID_ACCOUNT_ID; -import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.OK; - -import com.hedera.services.bdd.spec.HapiSpec; -import com.hedera.services.bdd.spec.HapiSpecOperation; -import com.hedera.services.bdd.spec.queries.HapiQueryOp; -import com.hedera.services.bdd.spec.queries.crypto.HapiGetAccountBalance; -import com.hedera.services.bdd.spec.utilops.UtilVerbs; -import com.hedera.services.bdd.suites.HapiSuite; -import java.util.List; -import java.util.function.IntFunction; -import java.util.stream.IntStream; -import java.util.stream.Stream; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; -import org.junit.jupiter.api.Assertions; - -public class AccountAutoRenewalSuite extends HapiSuite { - - private static final Logger log = LogManager.getLogger(AccountAutoRenewalSuite.class); - public static final String TRIGGERING_TRANSACTION = "triggeringTransaction"; - - public static void main(String... args) { - new AccountAutoRenewalSuite().runSuiteSync(); - } - - @Override - @SuppressWarnings("java:S3878") - public List getSpecsInSuite() { - return List.of(new HapiSpec[] { - accountAutoRemoval(), - accountAutoRenewal(), - maxNumberOfEntitiesToRenewOrDeleteWorks(), - numberOfEntitiesToScanWorks(), - autoDeleteAfterGracePeriod(), - accountAutoRenewalSuiteCleanup(), - freezeAtTheEnd(), - }); - } - - final HapiSpec accountAutoRemoval() { - String autoRemovedAccount = "autoRemovedAccount"; - return defaultHapiSpec("AccountAutoRemoval") - .given( - fileUpdate(APP_PROPERTIES) - .payingWith(GENESIS) - .overridingProps(AutoRenewConfigChoices.propsForAccountAutoRenewOnWith(1, 0)), - cryptoCreate(autoRemovedAccount).autoRenewSecs(1).balance(0L), - getAccountInfo(autoRemovedAccount).logged()) - .when( - sleepFor(1_500L), - cryptoTransfer(tinyBarsFromTo(GENESIS, NODE, 1L)).via(TRIGGERING_TRANSACTION), - getTxnRecord(TRIGGERING_TRANSACTION) - .andAllChildRecords() - .logged()) - .then(getAccountBalance(autoRemovedAccount).hasAnswerOnlyPrecheck(INVALID_ACCOUNT_ID)); - } - - final HapiSpec accountAutoRenewal() { - final var briefAutoRenew = 3L; - final var autoRenewedAccount = "autoRenewedAccount"; - - long initialBalance = ONE_HUNDRED_HBARS; - return defaultHapiSpec("AccountAutoRenewal") - .given( - fileUpdate(APP_PROPERTIES) - .payingWith(GENESIS) - .overridingProps(propsForAccountAutoRenewOnWith( - briefAutoRenew, 0, HIGH_SCAN_CYCLE_COUNT, DEFAULT_HIGH_TOUCH_COUNT)), - cryptoCreate(autoRenewedAccount) - .autoRenewSecs(briefAutoRenew) - .balance(initialBalance), - getAccountInfo(autoRenewedAccount) - .savingSnapshot(autoRenewedAccount) - .logged()) - .when( - sleepFor(briefAutoRenew * 1_000L + 500L), - cryptoTransfer(tinyBarsFromTo(GENESIS, NODE, 1L)).via(TRIGGERING_TRANSACTION)) - .then( - getAccountInfo(autoRenewedAccount) - .has(accountWith() - .expiry(autoRenewedAccount, briefAutoRenew) - .balanceLessThan(initialBalance)) - .logged(), - cryptoDelete(autoRenewedAccount)); - } - - /** - * Mostly useful to run from a blank state where the only three accounts that exist are those - * created in the "given" clause of this spec. - * - *

    If run against a network that has existing funded accounts with very low auto-renew - * periods, this test is just a minimal sanity check. - */ - @SuppressWarnings("java:S5960") - final HapiSpec maxNumberOfEntitiesToRenewOrDeleteWorks() { - final var briefAutoRenew = 3L; - final var firstTouchable = "a"; - final var secondTouchable = "b"; - final var untouchable = "c"; - - return defaultHapiSpec("MaxNumberOfEntitiesToRenewOrDeleteWorks") - .given( - fileUpdate(APP_PROPERTIES) - .payingWith(GENESIS) - .overridingProps( - propsForAccountAutoRenewOnWith(briefAutoRenew, 0, HIGH_SCAN_CYCLE_COUNT, 2)), - cryptoTransfer(tinyBarsFromTo(GENESIS, NODE, 1L)), - cryptoCreate(firstTouchable) - .autoRenewSecs(briefAutoRenew) - .balance(0L), - cryptoCreate(secondTouchable) - .autoRenewSecs(briefAutoRenew) - .balance(0L), - cryptoCreate(untouchable).autoRenewSecs(briefAutoRenew).balance(0L)) - .when( - sleepFor(briefAutoRenew * 1_000L + 500L), - cryptoTransfer(tinyBarsFromTo(GENESIS, NODE, 1L)).via(TRIGGERING_TRANSACTION)) - .then(assertionsHold((spec, opLog) -> { - var subOpA = getAccountBalance(firstTouchable).hasAnswerOnlyPrecheckFrom(OK, INVALID_ACCOUNT_ID); - var subOpB = getAccountBalance(secondTouchable).hasAnswerOnlyPrecheckFrom(OK, INVALID_ACCOUNT_ID); - var subOpC = getAccountBalance(untouchable).hasAnswerOnlyPrecheckFrom(OK, INVALID_ACCOUNT_ID); - allRunFor(spec, subOpA, subOpB, subOpC); - final var aStatus = subOpA.getResponse() - .getCryptogetAccountBalance() - .getHeader() - .getNodeTransactionPrecheckCode(); - final var bStatus = subOpB.getResponse() - .getCryptogetAccountBalance() - .getHeader() - .getNodeTransactionPrecheckCode(); - final var cStatus = subOpC.getResponse() - .getCryptogetAccountBalance() - .getHeader() - .getNodeTransactionPrecheckCode(); - opLog.info("Results: {}, {}, {}", aStatus, bStatus, cStatus); - final long numRemoved = Stream.of(aStatus, bStatus, cStatus) - .filter(INVALID_ACCOUNT_ID::equals) - .count(); - Assertions.assertTrue(numRemoved <= 2L, "More than 2 entities were touched!"); - })); - } - - /** - * Mostly useful to run from a blank state where the only accounts that exist are those created - * in the "given" clause of this spec. - * - *

    If run against a network that has existing funded accounts with very low auto-renew - * periods, this test is just a minimal sanity check. - */ - final HapiSpec numberOfEntitiesToScanWorks() { - final var briefAutoRenew = 3L; - final int abbrevMaxToScan = 10; - final IntFunction accountName = i -> "fastExpiring" + i; - - return defaultHapiSpec("NumberOfEntitiesToScanWorks") - .given( - fileUpdate(APP_PROPERTIES) - .payingWith(GENESIS) - .overridingProps(propsForAccountAutoRenewOnWith( - briefAutoRenew, 0, abbrevMaxToScan, DEFAULT_HIGH_TOUCH_COUNT)), - inParallel(IntStream.range(0, abbrevMaxToScan + 1) - .mapToObj(i -> cryptoCreate(accountName.apply(i)) - .autoRenewSecs(briefAutoRenew) - .balance(0L)) - .toArray(HapiSpecOperation[]::new))) - .when(sleepFor(briefAutoRenew * 1_000L + 500L), cryptoTransfer(tinyBarsFromTo(GENESIS, NODE, 1L))) - .then(assertionsHold((spec, opLog) -> { - final HapiSpecOperation[] subOps = IntStream.range(0, abbrevMaxToScan + 1) - .mapToObj(i -> getAccountBalance(accountName.apply(i)) - .hasAnswerOnlyPrecheckFrom(INVALID_ACCOUNT_ID, OK)) - .toArray(HapiSpecOperation[]::new); - allRunFor(spec, subOps); - @SuppressWarnings("java:S3864") - final long numRemoved = Stream.of(subOps) - .map(op -> ((HapiQueryOp) op) - .getResponse() - .getCryptogetAccountBalance() - .getHeader() - .getNodeTransactionPrecheckCode()) - .peek(opLog::info) - .filter(INVALID_ACCOUNT_ID::equals) - .count(); - Assertions.assertTrue( - numRemoved <= abbrevMaxToScan + 1, - "More than " + abbrevMaxToScan + " entities were touched!"); - })); - } - - final HapiSpec autoDeleteAfterGracePeriod() { - final var briefAutoRenew = 3L; - String autoDeleteAccount = "autoDeleteAccount"; - return defaultHapiSpec("AutoDeleteAfterGracePeriod") - .given( - fileUpdate(APP_PROPERTIES) - .payingWith(GENESIS) - .overridingProps(AutoRenewConfigChoices.propsForAccountAutoRenewOnWith( - briefAutoRenew, 2 * briefAutoRenew)), - cryptoCreate(autoDeleteAccount) - .autoRenewSecs(briefAutoRenew) - .balance(0L)) - .when( - sleepFor(briefAutoRenew * 1_000L + 500L), - cryptoTransfer(tinyBarsFromTo(GENESIS, NODE, 1L)), - getAccountBalance(autoDeleteAccount), - sleepFor(2 * briefAutoRenew * 1_000L + 500L), - cryptoTransfer(tinyBarsFromTo(GENESIS, NODE, 1L))) - .then(getAccountBalance(autoDeleteAccount).hasAnswerOnlyPrecheck(INVALID_ACCOUNT_ID)); - } - - final HapiSpec accountAutoRenewalSuiteCleanup() { - return defaultHapiSpec("accountAutoRenewalSuiteCleanup") - .given() - .when() - .then(fileUpdate(APP_PROPERTIES).payingWith(GENESIS).overridingProps(disablingAutoRenewWithDefaults())); - } - - final HapiSpec freezeAtTheEnd() { - return defaultHapiSpec("freezeAtTheEnd") - .given() - .when(freezeOnly().startingIn(30).seconds().payingWith(GENESIS)) - .then( - // sleep for a while to wait for this freeze transaction be handled - UtilVerbs.sleepFor(75_000)); - } - - @Override - protected Logger getResultsLogger() { - return log; - } -} diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/autorenew/AutoRemovalCasesSuite.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/autorenew/AutoRemovalCasesSuite.java deleted file mode 100644 index fa459351cd91..000000000000 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/autorenew/AutoRemovalCasesSuite.java +++ /dev/null @@ -1,151 +0,0 @@ -/* - * Copyright (C) 2021-2024 Hedera Hashgraph, LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.hedera.services.bdd.suites.autorenew; - -import static com.hedera.services.bdd.spec.HapiSpec.defaultHapiSpec; -import static com.hedera.services.bdd.spec.queries.QueryVerbs.getAccountBalance; -import static com.hedera.services.bdd.spec.queries.QueryVerbs.getAccountInfo; -import static com.hedera.services.bdd.spec.queries.QueryVerbs.getContractInfo; -import static com.hedera.services.bdd.spec.transactions.TxnVerbs.contractCreate; -import static com.hedera.services.bdd.spec.transactions.TxnVerbs.contractDelete; -import static com.hedera.services.bdd.spec.transactions.TxnVerbs.cryptoCreate; -import static com.hedera.services.bdd.spec.transactions.TxnVerbs.cryptoDelete; -import static com.hedera.services.bdd.spec.transactions.TxnVerbs.cryptoTransfer; -import static com.hedera.services.bdd.spec.transactions.TxnVerbs.fileUpdate; -import static com.hedera.services.bdd.spec.transactions.TxnVerbs.tokenAssociate; -import static com.hedera.services.bdd.spec.transactions.TxnVerbs.tokenCreate; -import static com.hedera.services.bdd.spec.transactions.TxnVerbs.tokenDelete; -import static com.hedera.services.bdd.spec.transactions.crypto.HapiCryptoTransfer.tinyBarsFromTo; -import static com.hedera.services.bdd.spec.transactions.token.TokenMovement.moving; -import static com.hedera.services.bdd.spec.utilops.UtilVerbs.newKeyNamed; -import static com.hedera.services.bdd.spec.utilops.UtilVerbs.sleepFor; -import static com.hedera.services.bdd.suites.autorenew.AutoRenewConfigChoices.disablingAutoRenewWithDefaults; -import static com.hedera.services.bdd.suites.autorenew.AutoRenewConfigChoices.propsForAccountAutoRenewOnWith; -import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.INVALID_ACCOUNT_ID; -import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.INVALID_CONTRACT_ID; - -import com.hedera.services.bdd.spec.HapiSpec; -import com.hedera.services.bdd.suites.HapiSuite; -import java.util.List; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; - -public class AutoRemovalCasesSuite extends HapiSuite { - - private static final Logger log = LogManager.getLogger(AutoRemovalCasesSuite.class); - - public static void main(String... args) { - new AutoRemovalCasesSuite().runSuiteSync(); - } - - @Override - @SuppressWarnings("java:S3878") - public List getSpecsInSuite() { - return List.of(new HapiSpec[] { - ignoresExpiredDeletedContracts(), - displacesTokenUnitsAsExpected(), - immediatelyRemovesDeletedAccountOnExpiry(), - autoRemovalCasesSuiteCleanup(), - }); - } - - final HapiSpec ignoresExpiredDeletedContracts() { - final var adminKey = "tac"; - final var tbd = "dead"; - - return defaultHapiSpec("IgnoresExpiredDeletedContracts") - .given( - fileUpdate(APP_PROPERTIES) - .payingWith(GENESIS) - .overridingProps(propsForAccountAutoRenewOnWith(1, 7776000L)), - newKeyNamed(adminKey), - contractCreate(tbd).balance(0L).autoRenewSecs(5).adminKey(adminKey), - contractDelete(tbd)) - .when(sleepFor(5_500L), cryptoTransfer(tinyBarsFromTo(DEFAULT_PAYER, FUNDING, 1L))) - .then(getContractInfo(tbd).hasCostAnswerPrecheck(INVALID_CONTRACT_ID)); - } - - final HapiSpec immediatelyRemovesDeletedAccountOnExpiry() { - final var tbd = "dead"; - final var onlyDetached = "gone"; - - return defaultHapiSpec("ImmediatelyRemovesDeletedAccountOnExpiry") - .given( - fileUpdate(APP_PROPERTIES) - .payingWith(GENESIS) - .overridingProps(propsForAccountAutoRenewOnWith(1, 7776000L)), - cryptoCreate(tbd).balance(0L).autoRenewSecs(5), - cryptoCreate(onlyDetached).balance(0L).autoRenewSecs(5), - cryptoDelete(tbd)) - .when(sleepFor(5_500L), cryptoTransfer(tinyBarsFromTo(DEFAULT_PAYER, FUNDING, 1L))) - .then(getAccountInfo(onlyDetached), getAccountInfo(tbd).hasCostAnswerPrecheck(INVALID_ACCOUNT_ID)); - } - - final HapiSpec displacesTokenUnitsAsExpected() { - final long startSupply = 10; - final long displacedSupply = 1; - final var adminKey = "tak"; - final var civilian = "misc"; - final var removedAccount = "gone"; - final var deletedToken = "unreturnable"; - final var liveToken = "returnable"; - final var anotherLiveToken = "alsoReturnable"; - - return defaultHapiSpec("DisplacesTokenUnitsAsExpected") - .given( - fileUpdate(APP_PROPERTIES) - .payingWith(GENESIS) - .overridingProps(propsForAccountAutoRenewOnWith(1, 0L)), - newKeyNamed(adminKey), - cryptoCreate(civilian).balance(0L), - tokenCreate(deletedToken) - .initialSupply(startSupply) - .adminKey(adminKey) - .treasury(civilian), - tokenCreate(liveToken).initialSupply(startSupply).treasury(civilian), - tokenCreate(anotherLiveToken).initialSupply(startSupply).treasury(civilian), - cryptoCreate(removedAccount) - .maxAutomaticTokenAssociations(1) - .balance(0L) - .autoRenewSecs(5), - tokenAssociate(removedAccount, List.of(deletedToken, liveToken)), - cryptoTransfer( - moving(displacedSupply, deletedToken).between(civilian, removedAccount), - moving(displacedSupply, liveToken).between(civilian, removedAccount), - moving(displacedSupply, anotherLiveToken).between(civilian, removedAccount)), - tokenDelete(deletedToken)) - .when(sleepFor(5_500L), cryptoTransfer(tinyBarsFromTo(DEFAULT_PAYER, FUNDING, 1L))) - .then( - getAccountInfo(removedAccount).hasCostAnswerPrecheck(INVALID_ACCOUNT_ID), - getAccountBalance(civilian) - .hasTokenBalance(deletedToken, startSupply - displacedSupply) - .hasTokenBalance(liveToken, startSupply) - .hasTokenBalance(anotherLiveToken, startSupply)); - } - - final HapiSpec autoRemovalCasesSuiteCleanup() { - return defaultHapiSpec("AutoRemovalCasesSuiteCleanup") - .given() - .when() - .then(fileUpdate(APP_PROPERTIES).payingWith(GENESIS).overridingProps(disablingAutoRenewWithDefaults())); - } - - @Override - protected Logger getResultsLogger() { - return log; - } -} diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/autorenew/ContractAutoExpirySpecs.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/autorenew/ContractAutoExpirySpecs.java index eb752ae7c425..a63f8f3f9386 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/autorenew/ContractAutoExpirySpecs.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/autorenew/ContractAutoExpirySpecs.java @@ -57,8 +57,7 @@ import static org.junit.jupiter.api.Assertions.assertTrue; import com.google.protobuf.ByteString; -import com.hedera.services.bdd.junit.ExpiryRecordsValidator; -import com.hedera.services.bdd.spec.HapiSpec; +import com.hedera.services.bdd.junit.support.validators.ExpiryRecordsValidator; import com.hedera.services.bdd.suites.HapiSuite; import com.hederahashgraph.api.proto.java.ResponseCodeEnum; import com.hederahashgraph.api.proto.java.Timestamp; @@ -68,8 +67,10 @@ import java.util.Map; import java.util.concurrent.atomic.AtomicLong; import java.util.concurrent.atomic.AtomicReference; +import java.util.stream.Stream; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; +import org.junit.jupiter.api.DynamicTest; public class ContractAutoExpirySpecs extends HapiSuite { @@ -90,7 +91,7 @@ public static void main(String... args) { } @Override - public List getSpecsInSuite() { + public List> getSpecsInSuite() { return List.of( renewsUsingContractFundsIfNoAutoRenewAccount(), renewalFeeDistributedToStakingAccounts(), @@ -105,7 +106,7 @@ public List getSpecsInSuite() { verifyNonFungibleTokenTransferredBackToTreasuryWithoutCharging()); } - final HapiSpec renewalWithCustomFeesWorks() { + final Stream renewalWithCustomFeesWorks() { final var minimalLifetime = 4; final var aFungibleToken = "aFT"; final var bFungibleToken = "bFT"; @@ -175,7 +176,7 @@ final HapiSpec renewalWithCustomFeesWorks() { getTokenNftInfo(nonFungibleToken, 2L).hasAccountID(TOKEN_TREASURY)); } - final HapiSpec receiverSigReqBypassedForTreasuryAtEndOfGracePeriod() { + final Stream receiverSigReqBypassedForTreasuryAtEndOfGracePeriod() { final var minimalLifetime = 4; final var aFungibleToken = "aFT"; final var nonFungibleToken = "NFT"; @@ -242,14 +243,14 @@ final HapiSpec receiverSigReqBypassedForTreasuryAtEndOfGracePeriod() { getTokenNftInfo(nonFungibleToken, 2L).hasAccountID(TOKEN_TREASURY)); } - final HapiSpec validateStreams() { + final Stream validateStreams() { return defaultHapiSpec("validateStreams") .given() .when() .then(sourcing(() -> assertEventuallyPasses(new ExpiryRecordsValidator(), Duration.ofMillis(2_100)))); } - final HapiSpec storageRentChargedOnlyAfterInitialFreePeriodIsComplete() { + final Stream storageRentChargedOnlyAfterInitialFreePeriodIsComplete() { final var contract = "User"; final var gasToOffer = 1_000_000; final var minimalLifetime = 4; @@ -338,7 +339,7 @@ final HapiSpec storageRentChargedOnlyAfterInitialFreePeriodIsComplete() { overriding(INDIVIDUAL_KV_LIMIT_PROP, String.valueOf(16_384_000))); } - final HapiSpec autoRenewWorksAsExpected() { + final Stream autoRenewWorksAsExpected() { final var minimalLifetime = 3; return defaultHapiSpec("autoRenewWorksAsExpected") @@ -386,7 +387,7 @@ final HapiSpec autoRenewWorksAsExpected() { overriding(LEDGER_AUTO_RENEW_PERIOD_MIN_DURATION, DEFAULT_MIN_AUTO_RENEW_PERIOD)); } - final HapiSpec autoRenewInGracePeriodIfEnoughBalance() { + final Stream autoRenewInGracePeriodIfEnoughBalance() { final var minimalLifetime = 3; final var expectedExpiryPostRenew = new AtomicLong(); final var currentExpiry = new AtomicLong(); @@ -446,7 +447,7 @@ final HapiSpec autoRenewInGracePeriodIfEnoughBalance() { overriding(LEDGER_AUTO_RENEW_PERIOD_MIN_DURATION, DEFAULT_MIN_AUTO_RENEW_PERIOD)); } - final HapiSpec renewalFeeDistributedToStakingAccounts() { + final Stream renewalFeeDistributedToStakingAccounts() { final var initBalance = ONE_HBAR; final var minimalLifetime = 3; final var standardLifetime = 7776000L; @@ -519,7 +520,7 @@ final HapiSpec renewalFeeDistributedToStakingAccounts() { overriding(LEDGER_AUTO_RENEW_PERIOD_MIN_DURATION, DEFAULT_MIN_AUTO_RENEW_PERIOD)); } - final HapiSpec chargesContractFundsWhenAutoRenewAccountHasZeroBalance() { + final Stream chargesContractFundsWhenAutoRenewAccountHasZeroBalance() { final var initBalance = ONE_HBAR; final var minimalLifetime = 3; final var standardLifetime = 7776000L; @@ -587,7 +588,7 @@ final HapiSpec chargesContractFundsWhenAutoRenewAccountHasZeroBalance() { overriding(LEDGER_AUTO_RENEW_PERIOD_MIN_DURATION, DEFAULT_MIN_AUTO_RENEW_PERIOD)); } - final HapiSpec renewsUsingAutoRenewAccountIfSet() { + final Stream renewsUsingAutoRenewAccountIfSet() { final var initBalance = ONE_HBAR; final var minimalLifetime = 3; final var standardLifetime = 7776000L; @@ -654,7 +655,7 @@ final HapiSpec renewsUsingAutoRenewAccountIfSet() { overriding(LEDGER_AUTO_RENEW_PERIOD_MIN_DURATION, DEFAULT_MIN_AUTO_RENEW_PERIOD)); } - final HapiSpec storageExpiryWorksAtTheExpectedInterval() { + final Stream storageExpiryWorksAtTheExpectedInterval() { final var minimalLifetime = 4; final var aFungibleToken = "aFT"; final var bFungibleToken = "bFT"; @@ -748,7 +749,7 @@ final HapiSpec storageExpiryWorksAtTheExpectedInterval() { getAccountBalance(TOKEN_TREASURY).hasTinyBars(ONE_HBAR)); } - final HapiSpec verifyNonFungibleTokenTransferredBackToTreasuryWithoutCharging() { + final Stream verifyNonFungibleTokenTransferredBackToTreasuryWithoutCharging() { final var minimalLifetime = 4; final var nonFungibleToken = "NFT"; final var initBalance = ONE_HBAR; @@ -801,7 +802,7 @@ final HapiSpec verifyNonFungibleTokenTransferredBackToTreasuryWithoutCharging() getAccountBalance(TOKEN_TREASURY).hasTinyBars(ONE_HBAR)); } - final HapiSpec renewsUsingContractFundsIfNoAutoRenewAccount() { + final Stream renewsUsingContractFundsIfNoAutoRenewAccount() { final var initBalance = ONE_HBAR; final var minimalLifetime = 3; final var standardLifetime = 7776000L; diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/autorenew/GracePeriodRestrictionsSuite.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/autorenew/GracePeriodRestrictionsSuite.java deleted file mode 100644 index d179f254d813..000000000000 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/autorenew/GracePeriodRestrictionsSuite.java +++ /dev/null @@ -1,424 +0,0 @@ -/* - * Copyright (C) 2021-2024 Hedera Hashgraph, LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.hedera.services.bdd.suites.autorenew; - -import static com.hedera.services.bdd.spec.HapiSpec.defaultHapiSpec; -import static com.hedera.services.bdd.spec.queries.QueryVerbs.getAccountBalance; -import static com.hedera.services.bdd.spec.queries.QueryVerbs.getAccountInfo; -import static com.hedera.services.bdd.spec.queries.QueryVerbs.getTokenInfo; -import static com.hedera.services.bdd.spec.queries.QueryVerbs.getTopicInfo; -import static com.hedera.services.bdd.spec.transactions.TxnVerbs.burnToken; -import static com.hedera.services.bdd.spec.transactions.TxnVerbs.contractCreate; -import static com.hedera.services.bdd.spec.transactions.TxnVerbs.contractDelete; -import static com.hedera.services.bdd.spec.transactions.TxnVerbs.createDefaultContract; -import static com.hedera.services.bdd.spec.transactions.TxnVerbs.createTopic; -import static com.hedera.services.bdd.spec.transactions.TxnVerbs.cryptoCreate; -import static com.hedera.services.bdd.spec.transactions.TxnVerbs.cryptoDelete; -import static com.hedera.services.bdd.spec.transactions.TxnVerbs.cryptoTransfer; -import static com.hedera.services.bdd.spec.transactions.TxnVerbs.cryptoUpdate; -import static com.hedera.services.bdd.spec.transactions.TxnVerbs.explicitContractCall; -import static com.hedera.services.bdd.spec.transactions.TxnVerbs.fileUpdate; -import static com.hedera.services.bdd.spec.transactions.TxnVerbs.grantTokenKyc; -import static com.hedera.services.bdd.spec.transactions.TxnVerbs.mintToken; -import static com.hedera.services.bdd.spec.transactions.TxnVerbs.revokeTokenKyc; -import static com.hedera.services.bdd.spec.transactions.TxnVerbs.scheduleCreate; -import static com.hedera.services.bdd.spec.transactions.TxnVerbs.tokenAssociate; -import static com.hedera.services.bdd.spec.transactions.TxnVerbs.tokenCreate; -import static com.hedera.services.bdd.spec.transactions.TxnVerbs.tokenDissociate; -import static com.hedera.services.bdd.spec.transactions.TxnVerbs.tokenFreeze; -import static com.hedera.services.bdd.spec.transactions.TxnVerbs.tokenUnfreeze; -import static com.hedera.services.bdd.spec.transactions.TxnVerbs.tokenUpdate; -import static com.hedera.services.bdd.spec.transactions.TxnVerbs.updateTopic; -import static com.hedera.services.bdd.spec.transactions.TxnVerbs.uploadInitCode; -import static com.hedera.services.bdd.spec.transactions.crypto.HapiCryptoTransfer.tinyBarsFromTo; -import static com.hedera.services.bdd.spec.transactions.token.TokenMovement.moving; -import static com.hedera.services.bdd.spec.utilops.UtilVerbs.newKeyNamed; -import static com.hedera.services.bdd.spec.utilops.UtilVerbs.sleepFor; -import static com.hedera.services.bdd.spec.utilops.UtilVerbs.sourcing; -import static com.hedera.services.bdd.spec.utilops.UtilVerbs.withOpContext; -import static com.hedera.services.bdd.suites.autorenew.AutoRenewConfigChoices.disablingAutoRenewWith; -import static com.hedera.services.bdd.suites.autorenew.AutoRenewConfigChoices.propsForAccountAutoRenewOnWith; -import static com.hedera.services.bdd.suites.contract.Utils.FunctionType.FUNCTION; -import static com.hedera.services.bdd.suites.contract.Utils.asAddress; -import static com.hedera.services.bdd.suites.contract.Utils.getABIFor; -import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.ACCOUNT_EXPIRED_AND_PENDING_REMOVAL; -import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.EXPIRATION_REDUCTION_NOT_ALLOWED; -import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.INVALID_ALIAS_KEY; -import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.INVALID_EXPIRATION_TIME; - -import com.hedera.services.bdd.spec.HapiSpec; -import com.hedera.services.bdd.spec.transactions.contract.HapiParserUtil; -import com.hedera.services.bdd.suites.HapiSuite; -import com.hederahashgraph.api.proto.java.AccountID; -import java.time.Instant; -import java.util.List; -import java.util.concurrent.atomic.AtomicReference; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; - -public class GracePeriodRestrictionsSuite extends HapiSuite { - - private static final Logger log = LogManager.getLogger(GracePeriodRestrictionsSuite.class); - - public static void main(String... args) { - new GracePeriodRestrictionsSuite().runSuiteSync(); - } - - @Override - public List getSpecsInSuite() { - return List.of( - gracePeriodRestrictionsSuiteSetup(), - contractCallRestrictionsEnforced(), - payerRestrictionsEnforced(), - cryptoTransferRestrictionsEnforced(), - tokenMgmtRestrictionsEnforced(), - cryptoAndContractDeleteRestrictionsEnforced(), - treasuryOpsRestrictionEnforced(), - tokenAutoRenewOpsEnforced(), - topicAutoRenewOpsEnforced(), - cryptoUpdateRestrictionsEnforced(), - gracePeriodRestrictionsSuiteCleanup()); - } - - final HapiSpec contractCallRestrictionsEnforced() { - final var civilian = "misc"; - final var detachedAccount = "gone"; - final var contract = "DoubleSend"; - final AtomicReference detachedAccountID = new AtomicReference(); - final AtomicReference civilianAccountID = new AtomicReference(); - - return defaultHapiSpec("ContractCallRestrictionsEnforced") - .given( - uploadInitCode(contract), - contractCreate(contract).balance(ONE_HBAR), - cryptoCreate(civilian).balance(0L), - cryptoCreate(detachedAccount).balance(0L).autoRenewSecs(1)) - .when( - sleepFor(1_500L), - cryptoTransfer(tinyBarsFromTo(DEFAULT_PAYER, FUNDING, 1L)), - withOpContext((spec, opLog) -> { - detachedAccountID.set(spec.registry().getAccountID(detachedAccount)); - civilianAccountID.set(spec.registry().getAccountID(civilian)); - }), - sourcing(() -> explicitContractCall( - contract, getABIFor(FUNCTION, "donate", contract), new Object[] { - HapiParserUtil.asHeadlongAddress(asAddress(civilianAccountID.get())), - HapiParserUtil.asHeadlongAddress(asAddress(detachedAccountID.get())) - }) - .hasKnownStatus(INVALID_ALIAS_KEY)), - getAccountBalance(civilian).hasTinyBars(0L), - getAccountBalance(detachedAccount).hasTinyBars(0L)) - .then( - cryptoUpdate(detachedAccount) - .expiring(Instant.now().getEpochSecond() + THREE_MONTHS_IN_SECONDS), - sourcing(() -> - explicitContractCall(contract, getABIFor(FUNCTION, "donate", contract), new Object[] { - HapiParserUtil.asHeadlongAddress(asAddress(civilianAccountID.get())), - HapiParserUtil.asHeadlongAddress(asAddress(detachedAccountID.get())) - })), - getAccountBalance(civilian).hasTinyBars(1L), - getAccountBalance(detachedAccount).hasTinyBars(1L)); - } - - final HapiSpec cryptoUpdateRestrictionsEnforced() { - final var detachedAccount = "gone"; - final long certainlyPast = Instant.now().getEpochSecond() - THREE_MONTHS_IN_SECONDS; - final long certainlyDistant = Instant.now().getEpochSecond() + THREE_MONTHS_IN_SECONDS; - - return defaultHapiSpec("CryptoUpdateRestrictionsEnforced") - .given( - newKeyNamed("ntb"), - cryptoCreate(detachedAccount).balance(0L).autoRenewSecs(1), - sleepFor(1_500L), - cryptoTransfer(tinyBarsFromTo(DEFAULT_PAYER, FUNDING, 1L))) - .when( - cryptoUpdate(detachedAccount) - .memo("Can't update receiverSigRequired") - .receiverSigRequired(true) - .hasKnownStatus(ACCOUNT_EXPIRED_AND_PENDING_REMOVAL), - cryptoUpdate(detachedAccount) - .memo("Can't update key") - .key("ntb") - .hasKnownStatus(ACCOUNT_EXPIRED_AND_PENDING_REMOVAL), - cryptoUpdate(detachedAccount) - .memo("Can't update auto-renew period") - .autoRenewPeriod(THREE_MONTHS_IN_SECONDS) - .hasKnownStatus(ACCOUNT_EXPIRED_AND_PENDING_REMOVAL), - cryptoUpdate(detachedAccount) - .memo("Can't update memo") - .entityMemo("NOPE") - .hasKnownStatus(ACCOUNT_EXPIRED_AND_PENDING_REMOVAL), - cryptoUpdate(detachedAccount) - .memo("Can't update with past expiry") - .expiring(certainlyPast) - .hasKnownStatus(INVALID_EXPIRATION_TIME), - cryptoUpdate(detachedAccount).memo("CAN extend expiry").expiring(certainlyDistant)) - .then( - cryptoUpdate(detachedAccount).memo("Should work now!").receiverSigRequired(true), - cryptoUpdate(detachedAccount).key("ntb"), - cryptoUpdate(detachedAccount).autoRenewPeriod(THREE_MONTHS_IN_SECONDS), - cryptoUpdate(detachedAccount).entityMemo("NOPE"), - cryptoUpdate(detachedAccount) - .expiring(certainlyDistant - 1_234L) - .hasKnownStatus(EXPIRATION_REDUCTION_NOT_ALLOWED)); - } - - final HapiSpec payerRestrictionsEnforced() { - final var detachedAccount = "gone"; - - return defaultHapiSpec("PayerRestrictionsEnforced") - .given(cryptoCreate(detachedAccount).balance(0L).autoRenewSecs(1)) - .when(sleepFor(1_500L), cryptoTransfer(tinyBarsFromTo(DEFAULT_PAYER, FUNDING, 1L))) - .then( - cryptoTransfer(tinyBarsFromTo(GENESIS, FUNDING, 1L)) - .payingWith(detachedAccount) - .hasPrecheck(ACCOUNT_EXPIRED_AND_PENDING_REMOVAL), - getAccountInfo("0.0.2") - .payingWith(detachedAccount) - .hasAnswerOnlyPrecheck(ACCOUNT_EXPIRED_AND_PENDING_REMOVAL), - getAccountInfo("0.0.2") - .payingWith(detachedAccount) - .nodePayment(666L) - .hasAnswerOnlyPrecheck(ACCOUNT_EXPIRED_AND_PENDING_REMOVAL), - scheduleCreate("notToBe", cryptoTransfer(tinyBarsFromTo(DEFAULT_PAYER, FUNDING, 1))) - .designatingPayer(detachedAccount) - .hasKnownStatus(ACCOUNT_EXPIRED_AND_PENDING_REMOVAL)); - } - - final HapiSpec topicAutoRenewOpsEnforced() { - final var topicWithDetachedAsAutoRenew = "c"; - final var topicSansDetachedAsAutoRenew = "d"; - final var detachedAccount = "gone"; - final var adminKey = "tak"; - final var civilian = "misc"; - final var notToBe = "nope"; - - return defaultHapiSpec("TopicAutoRenewOpsEnforced") - .given(newKeyNamed(adminKey), cryptoCreate(civilian)) - .when( - cryptoCreate(detachedAccount).balance(0L).autoRenewSecs(4), - createTopic(topicWithDetachedAsAutoRenew) - .adminKeyName(adminKey) - .autoRenewAccountId(detachedAccount), - createTopic(topicSansDetachedAsAutoRenew) - .adminKeyName(adminKey) - .autoRenewAccountId(civilian), - sleepFor(3_500L), - // Add a triggering transfer to let the ENTITY_EXPIRATION task - // mark the detached account as expired-and-pending-removal - cryptoTransfer(tinyBarsFromTo(DEFAULT_PAYER, FUNDING, 1L))) - .then( - createTopic(notToBe) - .adminKeyName(adminKey) - .autoRenewAccountId(detachedAccount) - .hasKnownStatus(ACCOUNT_EXPIRED_AND_PENDING_REMOVAL), - updateTopic(topicWithDetachedAsAutoRenew) - .autoRenewAccountId(civilian) - .hasKnownStatus(ACCOUNT_EXPIRED_AND_PENDING_REMOVAL), - updateTopic(topicSansDetachedAsAutoRenew) - .autoRenewAccountId(detachedAccount) - .hasKnownStatus(ACCOUNT_EXPIRED_AND_PENDING_REMOVAL), - getTopicInfo(topicSansDetachedAsAutoRenew).hasAutoRenewAccount(civilian), - getTopicInfo(topicWithDetachedAsAutoRenew).hasAutoRenewAccount(detachedAccount)); - } - - final HapiSpec tokenAutoRenewOpsEnforced() { - final var tokenWithDetachedAsAutoRenew = "c"; - final var tokenSansDetachedAsAutoRenew = "d"; - final var detachedAccount = "gone"; - final var adminKey = "tak"; - final var civilian = "misc"; - final var notToBe = "nope"; - - return defaultHapiSpec("TokenAutoRenewOpsEnforced") - .given(newKeyNamed(adminKey), cryptoCreate(civilian)) - .when( - cryptoCreate(detachedAccount).balance(0L).autoRenewSecs(3), - tokenCreate(tokenWithDetachedAsAutoRenew) - .adminKey(adminKey) - .autoRenewAccount(detachedAccount), - tokenCreate(tokenSansDetachedAsAutoRenew) - .autoRenewAccount(civilian) - .adminKey(adminKey), - sleepFor(2_500L), - // Add a triggering transfer to let the ENTITY_EXPIRATION task - // mark the detached account as expired-and-pending-removal - cryptoTransfer(tinyBarsFromTo(DEFAULT_PAYER, FUNDING, 1L))) - .then( - tokenCreate(notToBe) - .autoRenewAccount(detachedAccount) - .hasKnownStatus(ACCOUNT_EXPIRED_AND_PENDING_REMOVAL), - tokenUpdate(tokenWithDetachedAsAutoRenew) - .autoRenewAccount(civilian) - .signedByPayerAnd(adminKey) - .hasKnownStatus(ACCOUNT_EXPIRED_AND_PENDING_REMOVAL), - tokenUpdate(tokenSansDetachedAsAutoRenew) - .autoRenewAccount(detachedAccount) - .signedByPayerAnd(adminKey) - .hasKnownStatus(ACCOUNT_EXPIRED_AND_PENDING_REMOVAL), - getTokenInfo(tokenSansDetachedAsAutoRenew).hasAutoRenewAccount(civilian), - getTokenInfo(tokenWithDetachedAsAutoRenew).hasAutoRenewAccount(detachedAccount)); - } - - final HapiSpec treasuryOpsRestrictionEnforced() { - final var aToken = "c"; - final var detachedAccount = "gone"; - final var tokenMultiKey = "tak"; - final var civilian = "misc"; - final long expectedSupply = 1_234L; - - return defaultHapiSpec("TreasuryOpsRestrictionEnforced") - .given(newKeyNamed(tokenMultiKey), cryptoCreate(civilian)) - .when( - cryptoCreate(detachedAccount).balance(0L).autoRenewSecs(5), - tokenCreate(aToken) - .adminKey(tokenMultiKey) - .supplyKey(tokenMultiKey) - .initialSupply(expectedSupply) - .treasury(detachedAccount), - tokenAssociate(civilian, aToken), - sleepFor(4_500L), - // Add a triggering transfer to let the ENTITY_EXPIRATION task - // mark the detached account as expired-and-pending-removal - cryptoTransfer(tinyBarsFromTo(DEFAULT_PAYER, FUNDING, 1L))) - .then( - tokenUpdate(aToken) - .treasury(civilian) - .signedByPayerAnd(tokenMultiKey) - .hasKnownStatus(ACCOUNT_EXPIRED_AND_PENDING_REMOVAL), - mintToken(aToken, 1L).hasKnownStatus(ACCOUNT_EXPIRED_AND_PENDING_REMOVAL), - burnToken(aToken, 1L).hasKnownStatus(ACCOUNT_EXPIRED_AND_PENDING_REMOVAL), - getTokenInfo(aToken).hasTreasury(detachedAccount), - getAccountBalance(detachedAccount).hasTokenBalance(aToken, expectedSupply)); - } - - final HapiSpec tokenMgmtRestrictionsEnforced() { - final var notToBe = "a"; - final var tokenNotYetAssociated = "b"; - final var tokenAlreadyAssociated = "c"; - final var detachedAccount = "gone"; - final var tokenMultiKey = "tak"; - final var civilian = "misc"; - - return defaultHapiSpec("TokenMgmtRestrictionsEnforced") - .given( - newKeyNamed(tokenMultiKey), - cryptoCreate(civilian), - tokenCreate(tokenNotYetAssociated).adminKey(tokenMultiKey), - tokenCreate(tokenAlreadyAssociated) - .freezeKey(tokenMultiKey) - .kycKey(tokenMultiKey)) - .when( - cryptoCreate(detachedAccount).balance(0L).autoRenewSecs(4), - tokenAssociate(detachedAccount, tokenAlreadyAssociated), - sleepFor(3_500L), - // Add a triggering transfer to let the ENTITY_EXPIRATION task - // mark the detached account as expired-and-pending-removal - cryptoTransfer(tinyBarsFromTo(DEFAULT_PAYER, FUNDING, 1L))) - .then( - tokenCreate(notToBe) - .treasury(detachedAccount) - .hasKnownStatus(ACCOUNT_EXPIRED_AND_PENDING_REMOVAL), - tokenUnfreeze(tokenAlreadyAssociated, detachedAccount) - .hasKnownStatus(ACCOUNT_EXPIRED_AND_PENDING_REMOVAL), - tokenFreeze(tokenAlreadyAssociated, detachedAccount) - .hasKnownStatus(ACCOUNT_EXPIRED_AND_PENDING_REMOVAL), - grantTokenKyc(tokenAlreadyAssociated, detachedAccount) - .hasKnownStatus(ACCOUNT_EXPIRED_AND_PENDING_REMOVAL), - revokeTokenKyc(tokenAlreadyAssociated, detachedAccount) - .hasKnownStatus(ACCOUNT_EXPIRED_AND_PENDING_REMOVAL), - tokenAssociate(detachedAccount, tokenNotYetAssociated) - .hasKnownStatus(ACCOUNT_EXPIRED_AND_PENDING_REMOVAL), - tokenUpdate(tokenNotYetAssociated) - .treasury(detachedAccount) - .signedByPayerAnd(tokenMultiKey) - .hasKnownStatus(ACCOUNT_EXPIRED_AND_PENDING_REMOVAL), - tokenDissociate(detachedAccount, tokenAlreadyAssociated) - .hasKnownStatus(ACCOUNT_EXPIRED_AND_PENDING_REMOVAL)); - } - - final HapiSpec cryptoAndContractDeleteRestrictionsEnforced() { - final var detachedAccount = "gone"; - final var civilian = "misc"; - final var tbd = "contract"; - final var adminKey = "tac"; - - return defaultHapiSpec("CryptoAndContractDeleteRestrictionsEnforced") - .given( - newKeyNamed(adminKey), - createDefaultContract(tbd).adminKey(adminKey), - cryptoCreate(civilian), - cryptoCreate(detachedAccount).balance(0L).autoRenewSecs(2)) - .when( - sleepFor(2_500L), - // Add a triggering transfer to let the ENTITY_EXPIRATION task - // mark the detached account as expired-and-pending-removal - cryptoTransfer(tinyBarsFromTo(DEFAULT_PAYER, FUNDING, 1L))) - .then( - cryptoDelete(detachedAccount).hasKnownStatus(ACCOUNT_EXPIRED_AND_PENDING_REMOVAL), - cryptoDelete(civilian) - .transfer(detachedAccount) - .hasKnownStatus(ACCOUNT_EXPIRED_AND_PENDING_REMOVAL), - contractDelete(tbd) - .transferAccount(detachedAccount) - .hasKnownStatus(ACCOUNT_EXPIRED_AND_PENDING_REMOVAL)); - } - - final HapiSpec cryptoTransferRestrictionsEnforced() { - final var aToken = "c"; - final var detachedAccount = "gone"; - final var civilian = "misc"; - - return defaultHapiSpec("CryptoTransferRestrictionsEnforced") - .given( - cryptoCreate(civilian), - cryptoCreate(detachedAccount).balance(0L).autoRenewSecs(4), - tokenCreate(aToken).treasury(detachedAccount), - tokenAssociate(civilian, aToken)) - .when( - sleepFor(3_500L), - // Add a triggering transfer to let the ENTITY_EXPIRATION task - // mark the detached account as expired-and-pending-removal - cryptoTransfer(tinyBarsFromTo(DEFAULT_PAYER, FUNDING, 1L))) - .then( - cryptoTransfer(tinyBarsFromTo(GENESIS, detachedAccount, ONE_MILLION_HBARS)) - .hasKnownStatus(ACCOUNT_EXPIRED_AND_PENDING_REMOVAL), - cryptoTransfer(moving(1, aToken).between(detachedAccount, civilian)) - .hasKnownStatus(ACCOUNT_EXPIRED_AND_PENDING_REMOVAL)); - } - - final HapiSpec gracePeriodRestrictionsSuiteSetup() { - return defaultHapiSpec("GracePeriodRestrictionsSuiteSetup") - .given() - .when() - .then(fileUpdate(APP_PROPERTIES) - .payingWith(GENESIS) - .overridingProps(propsForAccountAutoRenewOnWith(1, 3600))); - } - - final HapiSpec gracePeriodRestrictionsSuiteCleanup() { - return defaultHapiSpec("GracePeriodRestrictionsSuiteCleanup") - .given() - .when() - .then(fileUpdate(APP_PROPERTIES).payingWith(GENESIS).overridingProps(disablingAutoRenewWith(10L))); - } - - @Override - protected Logger getResultsLogger() { - return log; - } -} diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/autorenew/MacroFeesChargedSanityCheckSuite.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/autorenew/MacroFeesChargedSanityCheckSuite.java deleted file mode 100644 index 132007fad48f..000000000000 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/autorenew/MacroFeesChargedSanityCheckSuite.java +++ /dev/null @@ -1,258 +0,0 @@ -/* - * Copyright (C) 2021-2024 Hedera Hashgraph, LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.hedera.services.bdd.suites.autorenew; - -import static com.google.protobuf.ByteString.copyFromUtf8; -import static com.hedera.node.app.hapi.fees.usage.crypto.entities.CryptoEntitySizes.CRYPTO_ENTITY_SIZES; -import static com.hedera.node.app.hapi.utils.fee.FeeBuilder.FEE_DIVISOR_FACTOR; -import static com.hedera.node.app.hapi.utils.fee.FeeBuilder.getTinybarsFromTinyCents; -import static com.hedera.services.bdd.spec.HapiSpec.defaultHapiSpec; -import static com.hedera.services.bdd.spec.assertions.AccountInfoAsserts.accountWith; -import static com.hedera.services.bdd.spec.queries.QueryVerbs.getAccountBalance; -import static com.hedera.services.bdd.spec.queries.QueryVerbs.getAccountInfo; -import static com.hedera.services.bdd.spec.transactions.TxnUtils.randomUppercase; -import static com.hedera.services.bdd.spec.transactions.TxnVerbs.cryptoCreate; -import static com.hedera.services.bdd.spec.transactions.TxnVerbs.cryptoDelete; -import static com.hedera.services.bdd.spec.transactions.TxnVerbs.cryptoTransfer; -import static com.hedera.services.bdd.spec.transactions.TxnVerbs.cryptoUpdate; -import static com.hedera.services.bdd.spec.transactions.TxnVerbs.fileUpdate; -import static com.hedera.services.bdd.spec.transactions.crypto.HapiCryptoTransfer.tinyBarsFromTo; -import static com.hedera.services.bdd.spec.utilops.CustomSpecAssert.allRunFor; -import static com.hedera.services.bdd.spec.utilops.UtilVerbs.assertionsHold; -import static com.hedera.services.bdd.spec.utilops.UtilVerbs.runWithProvider; -import static com.hedera.services.bdd.spec.utilops.UtilVerbs.sleepFor; -import static com.hedera.services.bdd.spec.utilops.UtilVerbs.sourcing; -import static com.hedera.services.bdd.spec.utilops.UtilVerbs.withOpContext; -import static com.hedera.services.bdd.suites.autorenew.AutoRenewConfigChoices.disablingAutoRenewWithDefaults; -import static com.hedera.services.bdd.suites.autorenew.AutoRenewConfigChoices.propsForAccountAutoRenewOnWith; -import static com.hederahashgraph.api.proto.java.HederaFunctionality.CryptoAccountAutoRenew; -import static java.util.concurrent.TimeUnit.SECONDS; - -import com.hedera.node.app.hapi.fees.usage.crypto.ExtantCryptoContext; -import com.hedera.services.bdd.spec.HapiSpec; -import com.hedera.services.bdd.spec.HapiSpecOperation; -import com.hedera.services.bdd.spec.infrastructure.OpProvider; -import com.hedera.services.bdd.suites.HapiSuite; -import com.hederahashgraph.api.proto.java.ExchangeRate; -import com.hederahashgraph.api.proto.java.Key; -import java.util.Collections; -import java.util.List; -import java.util.Optional; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.atomic.AtomicInteger; -import java.util.concurrent.atomic.AtomicLong; -import java.util.concurrent.atomic.AtomicReference; -import java.util.function.Function; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; -import org.junit.jupiter.api.Assertions; - -public class MacroFeesChargedSanityCheckSuite extends HapiSuite { - - private static final Logger log = LogManager.getLogger(MacroFeesChargedSanityCheckSuite.class); - - public static void main(final String... args) { - new MacroFeesChargedSanityCheckSuite().runSuiteSync(); - } - - @Override - public List getSpecsInSuite() { - return List.of( - feesChargedMatchNumberOfRenewals(), - renewalCappedByAffordablePeriod(), - macroFeesChargedSanityCheckSuiteCleanup()); - } - - @SuppressWarnings("java:S125") - final HapiSpec renewalCappedByAffordablePeriod() { - final long briefAutoRenew = 10L; - final long normalAutoRenew = THREE_MONTHS_IN_SECONDS; - final long threeHoursInSeconds = 3 * 3600L; - final AtomicLong initialExpiry = new AtomicLong(); - final AtomicLong balanceForThreeHourRenew = new AtomicLong(); - - final var target = "czar"; - final String crazyMemo = "Calmer than you are!"; - - final ExtantCryptoContext cryptoCtx = ExtantCryptoContext.newBuilder() - .setCurrentExpiry(0L) - .setCurrentKey(Key.newBuilder() - .setEd25519(copyFromUtf8(randomUppercase(32))) - .build()) - .setCurrentlyHasProxy(true) - .setCurrentMemo(crazyMemo) - .setCurrentNumTokenRels(0) - .build(); - - return defaultHapiSpec("RenewalCappedByAffordablePeriod") - .given( - withOpContext((spec, opLog) -> { - final var tbFee = autoRenewFeesFor(spec, cryptoCtx); - balanceForThreeHourRenew.set(tbFee.totalTb(3)); - opLog.info("Balance {} will pay for three-hour renewal", balanceForThreeHourRenew.get()); - }), - fileUpdate(APP_PROPERTIES) - .payingWith(GENESIS) - .overridingProps(propsForAccountAutoRenewOnWith(1, 1234L)), - sourcing(() -> cryptoCreate(target) - .entityMemo(crazyMemo) - .balance(balanceForThreeHourRenew.get()) - .autoRenewSecs(briefAutoRenew)), - /* Despite asking for a three month autorenew here, the account will only - be able to afford a three hour extension. */ - cryptoUpdate(target).autoRenewPeriod(normalAutoRenew), - getAccountInfo(target).exposingExpiry(initialExpiry::set)) - .when( - sleepFor(briefAutoRenew * 1_000L + 500L), - cryptoTransfer(tinyBarsFromTo(DEFAULT_PAYER, FUNDING, 1L))) - .then( - /* The account in question will have expired (and been auto-renewed); - should only have received a three-hour renewal, and its entire balance - been used. */ - getAccountBalance(target).hasTinyBars(0L), - sourcing(() -> getAccountInfo(target) - .has(accountWith().expiry(initialExpiry.get() + threeHoursInSeconds, 0L))), - cryptoDelete(target)); - } - - @SuppressWarnings("java:S5960") - final HapiSpec feesChargedMatchNumberOfRenewals() { - final long reqAutoRenew = 2L; - final long startBalance = ONE_HUNDRED_HBARS; - final var target = "czar"; - final String crazyMemo = "Calmer than you are!"; - final AtomicLong initialExpiry = new AtomicLong(); - final AtomicLong finalExpiry = new AtomicLong(); - final AtomicLong finalBalance = new AtomicLong(); - final AtomicLong duration = new AtomicLong(30); - final AtomicReference unit = new AtomicReference<>(SECONDS); - final AtomicInteger maxOpsPerSec = new AtomicInteger(100); - - final ExtantCryptoContext cryptoCtx = ExtantCryptoContext.newBuilder() - .setCurrentExpiry(0L) - .setCurrentKey(Key.newBuilder() - .setEd25519(copyFromUtf8(randomUppercase(32))) - .build()) - .setCurrentlyHasProxy(true) - .setCurrentMemo(crazyMemo) - .setCurrentNumTokenRels(0) - .build(); - - return defaultHapiSpec("FeesChargedMatchNumberOfRenewals") - .given( - fileUpdate(APP_PROPERTIES) - .payingWith(GENESIS) - .overridingProps(propsForAccountAutoRenewOnWith(1, 1234L)), - cryptoCreate(target) - .entityMemo(crazyMemo) - .balance(startBalance) - .autoRenewSecs(reqAutoRenew), - getAccountInfo(target).exposingExpiry(initialExpiry::set)) - .when( - sleepFor(reqAutoRenew * 1_000L + 500L), - runWithProvider(getAnyOldXfers()) - .lasting(duration::get, unit::get) - .maxOpsPerSec(maxOpsPerSec::get)) - .then( - /* The account with the crazy short auto-renew will have expired (and - been auto-renewed) multiple times during the 30-second burst of - CryptoTransfers. We want to confirm its balance changed as expected - with the number of renewals. */ - assertionsHold((spec, opLog) -> { - final var tbFee = autoRenewFeesFor(spec, cryptoCtx).totalTb(1); - opLog.info("Expected fee in tinybars: {}", tbFee); - - final var infoOp = getAccountInfo(target) - .exposingBalance(finalBalance::set) - .exposingExpiry(finalExpiry::set); - allRunFor(spec, infoOp); - final long balanceChange = startBalance - finalBalance.get(); - final long expiryChange = finalExpiry.get() - initialExpiry.get(); - final int numRenewals = (int) (expiryChange / reqAutoRenew); - opLog.info( - "{} renewals happened, extending expiry by {} and" + " reducing balance by {}", - numRenewals, - expiryChange, - balanceChange); - Assertions.assertEquals(balanceChange, numRenewals * tbFee); - }), - cryptoDelete(target)); - } - - private long inTinybars(final long nominalFee, final ExchangeRate rate) { - return getTinybarsFromTinyCents(rate, nominalFee / FEE_DIVISOR_FACTOR); - } - - private Function getAnyOldXfers() { - return spec -> new OpProvider() { - @Override - public List suggestedInitializers() { - return Collections.emptyList(); - } - - @Override - public Optional get() { - return Optional.of(cryptoTransfer(tinyBarsFromTo(GENESIS, FUNDING, 1L)) - .noLogging() - .deferStatusResolution()); - } - }; - } - - private RenewalFeeComponents autoRenewFeesFor(final HapiSpec spec, final ExtantCryptoContext extantCtx) { - @SuppressWarnings("java:S1874") - final var prices = spec.ratesProvider().currentSchedule().getTransactionFeeScheduleList().stream() - .filter(tfs -> tfs.getHederaFunctionality() == CryptoAccountAutoRenew) - .findFirst() - .orElseThrow() - .getFeeData(); - final var constantPrice = prices.getNodedata().getConstant() - + prices.getNetworkdata().getConstant() - + prices.getServicedata().getConstant(); - final var rbUsage = CRYPTO_ENTITY_SIZES.fixedBytesInAccountRepr() + extantCtx.currentNonBaseRb(); - final var variablePrice = prices.getServicedata().getRbh() * rbUsage; - final var rates = spec.ratesProvider().rates(); - return new RenewalFeeComponents(inTinybars(constantPrice, rates), inTinybars(variablePrice, rates)); - } - - final HapiSpec macroFeesChargedSanityCheckSuiteCleanup() { - return defaultHapiSpec("MacroFeesChargedSanityCheckSuiteCleanup") - .given() - .when() - .then(fileUpdate(APP_PROPERTIES).payingWith(GENESIS).overridingProps(disablingAutoRenewWithDefaults())); - } - - private static class RenewalFeeComponents { - - private final long fixedTb; - private final long hourlyTb; - - public RenewalFeeComponents(final long fixedTb, final long hourlyTb) { - this.fixedTb = fixedTb; - this.hourlyTb = hourlyTb; - } - - public long totalTb(final int n) { - return fixedTb + n * hourlyTb; - } - } - - @Override - protected Logger getResultsLogger() { - return log; - } -} diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/autorenew/NoGprIfNoAutoRenewSuite.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/autorenew/NoGprIfNoAutoRenewSuite.java deleted file mode 100644 index ad4c31b903c4..000000000000 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/autorenew/NoGprIfNoAutoRenewSuite.java +++ /dev/null @@ -1,357 +0,0 @@ -/* - * Copyright (C) 2021-2024 Hedera Hashgraph, LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.hedera.services.bdd.suites.autorenew; - -import static com.hedera.services.bdd.spec.HapiSpec.defaultHapiSpec; -import static com.hedera.services.bdd.spec.assertions.TransactionRecordAsserts.recordWith; -import static com.hedera.services.bdd.spec.queries.QueryVerbs.getAccountBalance; -import static com.hedera.services.bdd.spec.queries.QueryVerbs.getAccountInfo; -import static com.hedera.services.bdd.spec.queries.QueryVerbs.getTokenInfo; -import static com.hedera.services.bdd.spec.queries.QueryVerbs.getTopicInfo; -import static com.hedera.services.bdd.spec.queries.QueryVerbs.getTxnRecord; -import static com.hedera.services.bdd.spec.transactions.TxnVerbs.burnToken; -import static com.hedera.services.bdd.spec.transactions.TxnVerbs.contractCall; -import static com.hedera.services.bdd.spec.transactions.TxnVerbs.contractCreate; -import static com.hedera.services.bdd.spec.transactions.TxnVerbs.createTopic; -import static com.hedera.services.bdd.spec.transactions.TxnVerbs.cryptoCreate; -import static com.hedera.services.bdd.spec.transactions.TxnVerbs.cryptoDelete; -import static com.hedera.services.bdd.spec.transactions.TxnVerbs.cryptoTransfer; -import static com.hedera.services.bdd.spec.transactions.TxnVerbs.cryptoUpdate; -import static com.hedera.services.bdd.spec.transactions.TxnVerbs.fileUpdate; -import static com.hedera.services.bdd.spec.transactions.TxnVerbs.grantTokenKyc; -import static com.hedera.services.bdd.spec.transactions.TxnVerbs.mintToken; -import static com.hedera.services.bdd.spec.transactions.TxnVerbs.revokeTokenKyc; -import static com.hedera.services.bdd.spec.transactions.TxnVerbs.scheduleCreate; -import static com.hedera.services.bdd.spec.transactions.TxnVerbs.tokenAssociate; -import static com.hedera.services.bdd.spec.transactions.TxnVerbs.tokenCreate; -import static com.hedera.services.bdd.spec.transactions.TxnVerbs.tokenDissociate; -import static com.hedera.services.bdd.spec.transactions.TxnVerbs.tokenFreeze; -import static com.hedera.services.bdd.spec.transactions.TxnVerbs.tokenUnfreeze; -import static com.hedera.services.bdd.spec.transactions.TxnVerbs.tokenUpdate; -import static com.hedera.services.bdd.spec.transactions.TxnVerbs.updateTopic; -import static com.hedera.services.bdd.spec.transactions.TxnVerbs.uploadInitCode; -import static com.hedera.services.bdd.spec.transactions.crypto.HapiCryptoTransfer.tinyBarsFromTo; -import static com.hedera.services.bdd.spec.transactions.token.TokenMovement.moving; -import static com.hedera.services.bdd.spec.utilops.UtilVerbs.newKeyNamed; -import static com.hedera.services.bdd.spec.utilops.UtilVerbs.sleepFor; -import static com.hedera.services.bdd.spec.utilops.UtilVerbs.sourcing; -import static com.hedera.services.bdd.spec.utilops.UtilVerbs.withOpContext; -import static com.hedera.services.bdd.suites.autorenew.AutoRenewConfigChoices.disablingAutoRenewWithDefaults; -import static com.hedera.services.bdd.suites.autorenew.AutoRenewConfigChoices.leavingAutoRenewDisabledWith; -import static com.hedera.services.bdd.suites.contract.Utils.FunctionType.FUNCTION; -import static com.hedera.services.bdd.suites.contract.Utils.getABIFor; -import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.ACCOUNT_DELETED; -import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.ACCOUNT_FROZEN_FOR_TOKEN; -import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.EXPIRATION_REDUCTION_NOT_ALLOWED; -import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.INSUFFICIENT_PAYER_BALANCE; -import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.INVALID_EXPIRATION_TIME; - -import com.hedera.services.bdd.spec.HapiSpec; -import com.hedera.services.bdd.spec.transactions.TxnUtils; -import com.hedera.services.bdd.suites.HapiSuite; -import java.time.Instant; -import java.util.List; -import java.util.concurrent.atomic.AtomicInteger; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; - -public class NoGprIfNoAutoRenewSuite extends HapiSuite { - - private static final Logger log = LogManager.getLogger(NoGprIfNoAutoRenewSuite.class); - - public static void main(String... args) { - new NoGprIfNoAutoRenewSuite().runSuiteSync(); - } - - @Override - public List getSpecsInSuite() { - return List.of(new HapiSpec[] { - noGracePeriodRestrictionsIfNoAutoRenewSuiteSetup(), - payerRestrictionsNotEnforced(), - cryptoTransferRestrictionsNotEnforced(), - tokenMgmtRestrictionsNotEnforced(), - cryptoDeleteRestrictionsNotEnforced(), - treasuryOpsRestrictionNotEnforced(), - tokenAutoRenewOpsNotEnforced(), - topicAutoRenewOpsNotEnforced(), - cryptoUpdateRestrictionsNotEnforced(), - contractCallRestrictionsNotEnforced(), - noGracePeriodRestrictionsIfNoAutoRenewSuiteCleanup(), - }); - } - - final HapiSpec contractCallRestrictionsNotEnforced() { - final var civilian = "misc"; - final var notDetachedAccount = "gone"; - final var contract = "DoubleSend"; - final AtomicInteger detachedNum = new AtomicInteger(); - final AtomicInteger civilianNum = new AtomicInteger(); - - return defaultHapiSpec("ContractCallRestrictionsNotEnforced") - .given( - uploadInitCode(contract), - contractCreate(contract).balance(ONE_HBAR), - cryptoCreate(civilian).balance(0L), - cryptoCreate(notDetachedAccount).balance(0L).autoRenewSecs(1)) - .when( - sleepFor(1_500L), - cryptoTransfer(tinyBarsFromTo(DEFAULT_PAYER, FUNDING, 1L)), - withOpContext((spec, opLog) -> { - detachedNum.set((int) spec.registry() - .getAccountID(notDetachedAccount) - .getAccountNum()); - civilianNum.set( - (int) spec.registry().getAccountID(civilian).getAccountNum()); - }), - sourcing(() -> contractCall(contract, getABIFor(FUNCTION, "donate", contract), new Object[] { - civilianNum.get(), detachedNum.get() - }))) - .then( - getAccountBalance(civilian).hasTinyBars(1L), - getAccountBalance(notDetachedAccount).hasTinyBars(1L)); - } - - final HapiSpec cryptoUpdateRestrictionsNotEnforced() { - final var notDetachedAccount = "gone"; - final long certainlyPast = Instant.now().getEpochSecond() - THREE_MONTHS_IN_SECONDS; - final long certainlyDistant = Instant.now().getEpochSecond() + THREE_MONTHS_IN_SECONDS; - - return defaultHapiSpec("CryptoUpdateRestrictionsNotEnforced") - .given( - newKeyNamed("ntb"), - cryptoCreate(notDetachedAccount).balance(0L).autoRenewSecs(1), - sleepFor(1_500L), - cryptoTransfer(tinyBarsFromTo(DEFAULT_PAYER, FUNDING, 1L))) - .when( - cryptoUpdate(notDetachedAccount) - .memo("Can update receiverSigRequired") - .receiverSigRequired(true), - cryptoUpdate(notDetachedAccount).memo("Can update key").key("ntb"), - cryptoUpdate(notDetachedAccount) - .memo("Can update auto-renew period") - .autoRenewPeriod(THREE_MONTHS_IN_SECONDS), - cryptoUpdate(notDetachedAccount).memo("Can update memo").entityMemo("NOPE"), - cryptoUpdate(notDetachedAccount) - .memo("Can't pass precheck with past expiry") - .expiring(certainlyPast) - .hasPrecheck(INVALID_EXPIRATION_TIME)) - .then( - cryptoUpdate(notDetachedAccount) - .memo("CAN extend expiry") - .expiring(certainlyDistant), - cryptoUpdate(notDetachedAccount) - .expiring(certainlyDistant - 1_234L) - .hasKnownStatus(EXPIRATION_REDUCTION_NOT_ALLOWED)); - } - - final HapiSpec payerRestrictionsNotEnforced() { - final var notDetachedAccount = "gone"; - - return defaultHapiSpec("PayerRestrictionsEnforced") - .given(cryptoCreate(notDetachedAccount).balance(0L).autoRenewSecs(1)) - .when(sleepFor(1_500L), cryptoTransfer(tinyBarsFromTo(DEFAULT_PAYER, FUNDING, 1L))) - .then( - cryptoTransfer(tinyBarsFromTo(GENESIS, FUNDING, 1L)) - .payingWith(notDetachedAccount) - .hasPrecheck(INSUFFICIENT_PAYER_BALANCE), - getAccountInfo("0.0.2") - .payingWith(notDetachedAccount) - .hasCostAnswerPrecheck(INSUFFICIENT_PAYER_BALANCE), - getAccountInfo("0.0.2") - .payingWith(notDetachedAccount) - .nodePayment(666L) - .hasAnswerOnlyPrecheck(INSUFFICIENT_PAYER_BALANCE), - scheduleCreate( - "notEnoughMoney", - cryptoTransfer(tinyBarsFromTo(DEFAULT_PAYER, FUNDING, 1)) - .memo(TxnUtils.randomUppercase(32))) - .via("creation") - .designatingPayer(notDetachedAccount) - .alsoSigningWith(notDetachedAccount), - getTxnRecord("creation") - .scheduled() - .hasPriority(recordWith().status(INSUFFICIENT_PAYER_BALANCE))); - } - - final HapiSpec topicAutoRenewOpsNotEnforced() { - final var topicWithDetachedAsAutoRenew = "c"; - final var topicSansDetachedAsAutoRenew = "d"; - final var notDetachedAccount = "gone"; - final var adminKey = "tak"; - final var civilian = "misc"; - final var onTheFly = "nope"; - - return defaultHapiSpec("TopicAutoRenewOpsNotEnforced") - .given(newKeyNamed(adminKey), cryptoCreate(civilian)) - .when( - cryptoCreate(notDetachedAccount).balance(0L).autoRenewSecs(2), - createTopic(topicWithDetachedAsAutoRenew) - .adminKeyName(adminKey) - .autoRenewAccountId(notDetachedAccount), - createTopic(topicSansDetachedAsAutoRenew) - .adminKeyName(adminKey) - .autoRenewAccountId(civilian), - sleepFor(1_500L)) - .then( - createTopic(onTheFly).adminKeyName(adminKey).autoRenewAccountId(notDetachedAccount), - updateTopic(topicWithDetachedAsAutoRenew).autoRenewAccountId(civilian), - updateTopic(topicSansDetachedAsAutoRenew).autoRenewAccountId(notDetachedAccount), - getTopicInfo(topicSansDetachedAsAutoRenew).hasAutoRenewAccount(notDetachedAccount), - getTopicInfo(topicWithDetachedAsAutoRenew).hasAutoRenewAccount(civilian)); - } - - final HapiSpec tokenAutoRenewOpsNotEnforced() { - final var tokenWithDetachedAsAutoRenew = "c"; - final var tokenSansDetachedAsAutoRenew = "d"; - final var notDetachedAccount = "gone"; - final var adminKey = "tak"; - final var civilian = "misc"; - final var notToBe = "nope"; - - return defaultHapiSpec("TokenAutoRenewOpsNotEnforced") - .given(newKeyNamed(adminKey), cryptoCreate(civilian)) - .when( - cryptoCreate(notDetachedAccount).balance(0L).autoRenewSecs(2), - tokenCreate(tokenWithDetachedAsAutoRenew) - .adminKey(adminKey) - .autoRenewAccount(notDetachedAccount), - tokenCreate(tokenSansDetachedAsAutoRenew) - .autoRenewAccount(civilian) - .adminKey(adminKey), - sleepFor(1_500L)) - .then( - tokenCreate(notToBe).autoRenewAccount(notDetachedAccount), - tokenUpdate(tokenWithDetachedAsAutoRenew) - .autoRenewAccount(civilian) - .signedByPayerAnd(adminKey), - tokenUpdate(tokenSansDetachedAsAutoRenew) - .autoRenewAccount(notDetachedAccount) - .signedByPayerAnd(adminKey), - getTokenInfo(tokenSansDetachedAsAutoRenew).hasAutoRenewAccount(notDetachedAccount), - getTokenInfo(tokenWithDetachedAsAutoRenew).hasAutoRenewAccount(civilian)); - } - - final HapiSpec treasuryOpsRestrictionNotEnforced() { - final var aToken = "c"; - final var notDetachedAccount = "gone"; - final var tokenMultiKey = "tak"; - final var civilian = "misc"; - final long expectedSupply = 1_234L; - - return defaultHapiSpec("TreasuryOpsRestrictionNotEnforced") - .given(newKeyNamed(tokenMultiKey), cryptoCreate(civilian)) - .when( - cryptoCreate(notDetachedAccount).balance(0L).autoRenewSecs(2), - tokenCreate(aToken) - .adminKey(tokenMultiKey) - .supplyKey(tokenMultiKey) - .initialSupply(expectedSupply) - .treasury(notDetachedAccount), - tokenAssociate(civilian, aToken), - sleepFor(1_500L)) - .then( - tokenUpdate(aToken).treasury(civilian).signedByPayerAnd(tokenMultiKey), - mintToken(aToken, 1L), - burnToken(aToken, 1L), - getTokenInfo(aToken).hasTreasury(civilian), - getAccountBalance(notDetachedAccount).hasTokenBalance(aToken, 0L)); - } - - final HapiSpec tokenMgmtRestrictionsNotEnforced() { - final var onTheFly = "a"; - final var tokenNotYetAssociated = "b"; - final var tokenAlreadyAssociated = "c"; - final var notDetachedAccount = "gone"; - final var tokenMultiKey = "tak"; - final var civilian = "misc"; - - return defaultHapiSpec("TokenMgmtRestrictionsNotEnforced") - .given( - newKeyNamed(tokenMultiKey), - cryptoCreate(civilian), - tokenCreate(tokenNotYetAssociated).adminKey(tokenMultiKey), - tokenCreate(tokenAlreadyAssociated) - .freezeKey(tokenMultiKey) - .kycKey(tokenMultiKey)) - .when( - cryptoCreate(notDetachedAccount).balance(0L).autoRenewSecs(2), - tokenAssociate(notDetachedAccount, tokenAlreadyAssociated), - sleepFor(1_500L)) - .then( - tokenCreate(onTheFly).treasury(notDetachedAccount), - tokenUnfreeze(tokenAlreadyAssociated, notDetachedAccount), - tokenFreeze(tokenAlreadyAssociated, notDetachedAccount), - grantTokenKyc(tokenAlreadyAssociated, notDetachedAccount), - revokeTokenKyc(tokenAlreadyAssociated, notDetachedAccount), - tokenAssociate(notDetachedAccount, tokenNotYetAssociated), - tokenUpdate(tokenNotYetAssociated) - .treasury(notDetachedAccount) - .signedByPayerAnd(tokenMultiKey), - tokenDissociate(notDetachedAccount, tokenAlreadyAssociated) - .hasKnownStatus(ACCOUNT_FROZEN_FOR_TOKEN)); - } - - final HapiSpec cryptoDeleteRestrictionsNotEnforced() { - final var notDetachedAccount = "gone"; - final var civilian = "misc"; - - return defaultHapiSpec("CryptoDeleteRestrictionsNotEnforced") - .given( - cryptoCreate(civilian), - cryptoCreate(notDetachedAccount).balance(0L).autoRenewSecs(2)) - .when(sleepFor(1_500L)) - .then( - cryptoDelete(notDetachedAccount), - cryptoDelete(civilian).transfer(notDetachedAccount).hasKnownStatus(ACCOUNT_DELETED)); - } - - final HapiSpec cryptoTransferRestrictionsNotEnforced() { - final var aToken = "c"; - final var notDetachedAccount = "gone"; - final var civilian = "misc"; - - return defaultHapiSpec("CryptoTransferRestrictionsNotEnforced") - .given( - cryptoCreate(civilian), - cryptoCreate(notDetachedAccount).balance(0L).autoRenewSecs(2), - tokenCreate(aToken).treasury(notDetachedAccount), - tokenAssociate(civilian, aToken)) - .when(sleepFor(1_500L)) - .then( - cryptoTransfer(tinyBarsFromTo(GENESIS, notDetachedAccount, ONE_HBAR)), - cryptoTransfer(moving(1, aToken).between(notDetachedAccount, civilian))); - } - - final HapiSpec noGracePeriodRestrictionsIfNoAutoRenewSuiteSetup() { - return defaultHapiSpec("NoGracePeriodRestrictionsIfNoAutoRenewSuiteSetup") - .given() - .when() - .then(fileUpdate(APP_PROPERTIES).payingWith(GENESIS).overridingProps(leavingAutoRenewDisabledWith(1))); - } - - final HapiSpec noGracePeriodRestrictionsIfNoAutoRenewSuiteCleanup() { - return defaultHapiSpec("NoGracePeriodRestrictionsIfNoAutoRenewSuiteCleanup") - .given() - .when() - .then(fileUpdate(APP_PROPERTIES).payingWith(GENESIS).overridingProps(disablingAutoRenewWithDefaults())); - } - - @Override - protected Logger getResultsLogger() { - return log; - } -} diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/autorenew/TopicAutoRenewalSuite.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/autorenew/TopicAutoRenewalSuite.java deleted file mode 100644 index 418fa41bcb70..000000000000 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/autorenew/TopicAutoRenewalSuite.java +++ /dev/null @@ -1,54 +0,0 @@ -/* - * Copyright (C) 2021-2024 Hedera Hashgraph, LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.hedera.services.bdd.suites.autorenew; - -import static com.hedera.services.bdd.spec.HapiSpec.defaultHapiSpec; - -import com.hedera.services.bdd.spec.HapiSpec; -import com.hedera.services.bdd.suites.HapiSuite; -import java.util.List; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; - -public class TopicAutoRenewalSuite extends HapiSuite { - - private static final Logger log = LogManager.getLogger(TopicAutoRenewalSuite.class); - - public static void main(String... args) { - new TopicAutoRenewalSuite().runSuiteSync(); - } - - // TODO : just added empty shells for now. - - @Override - public List getSpecsInSuite() { - return List.of(topicAutoRemoval(), topicAutoRenewal()); - } - - final HapiSpec topicAutoRemoval() { - return defaultHapiSpec("").given().when().then(); - } - - final HapiSpec topicAutoRenewal() { - return defaultHapiSpec("").given().when().then(); - } - - @Override - protected Logger getResultsLogger() { - return log; - } -} diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/block/BlockSuite.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/block/BlockSuite.java deleted file mode 100644 index 9c2dd6801b7d..000000000000 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/block/BlockSuite.java +++ /dev/null @@ -1,247 +0,0 @@ -/* - * Copyright (C) 2022-2024 Hedera Hashgraph, LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.hedera.services.bdd.suites.block; - -import static com.hedera.services.bdd.spec.HapiSpec.defaultHapiSpec; -import static com.hedera.services.bdd.spec.queries.QueryVerbs.getTxnRecord; -import static com.hedera.services.bdd.spec.transactions.TxnVerbs.contractCreate; -import static com.hedera.services.bdd.spec.transactions.TxnVerbs.cryptoCreate; -import static com.hedera.services.bdd.spec.transactions.TxnVerbs.cryptoTransfer; -import static com.hedera.services.bdd.spec.transactions.TxnVerbs.ethereumCall; -import static com.hedera.services.bdd.spec.transactions.TxnVerbs.uploadInitCode; -import static com.hedera.services.bdd.spec.transactions.crypto.HapiCryptoTransfer.tinyBarsFromAccountToAlias; -import static com.hedera.services.bdd.spec.utilops.CustomSpecAssert.allRunFor; -import static com.hedera.services.bdd.spec.utilops.UtilVerbs.newKeyNamed; -import static com.hedera.services.bdd.spec.utilops.UtilVerbs.waitUntilStartOfNextAdhocPeriod; -import static com.hedera.services.bdd.spec.utilops.UtilVerbs.withOpContext; -import static com.hedera.services.bdd.spec.utilops.records.SnapshotMatchMode.NONDETERMINISTIC_ETHEREUM_DATA; -import static com.hedera.services.bdd.spec.utilops.records.SnapshotMatchMode.NONDETERMINISTIC_LOG_DATA; -import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.RECORD_NOT_FOUND; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertNotEquals; -import static org.junit.jupiter.api.Assertions.assertTrue; - -import com.google.common.primitives.Longs; -import com.hedera.node.app.hapi.utils.ethereum.EthTxData; -import com.hedera.services.bdd.junit.HapiTest; -import com.hedera.services.bdd.junit.HapiTestSuite; -import com.hedera.services.bdd.spec.HapiSpec; -import com.hedera.services.bdd.spec.HapiSpecSetup; -import com.hedera.services.bdd.suites.HapiSuite; -import com.hederahashgraph.api.proto.java.ResponseCodeEnum; -import com.hederahashgraph.api.proto.java.Timestamp; -import edu.umd.cs.findbugs.annotations.NonNull; -import java.util.Arrays; -import java.util.List; -import java.util.Objects; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; -import org.apache.tuweni.bytes.Bytes32; - -@HapiTestSuite -public class BlockSuite extends HapiSuite { - - private static final Logger LOG = LogManager.getLogger(BlockSuite.class); - public static final String LOG_NOW = "logNow"; - public static final String AUTO_ACCOUNT = "autoAccount"; - - public static void main(String... args) { - new BlockSuite().runSuiteSync(); - } - - @Override - public List getSpecsInSuite() { - return List.of(blck001And002And003And004ReturnsCorrectBlockProperties(), blck003ReturnsTimestampOfTheBlock()); - } - - @SuppressWarnings("java:S5960") - @HapiTest - final HapiSpec blck003ReturnsTimestampOfTheBlock() { - final var contract = "EmitBlockTimestamp"; - final var firstCall = "firstCall"; - final var secondCall = "secondCall"; - - return defaultHapiSpec("returnsTimestampOfTheBlock", NONDETERMINISTIC_ETHEREUM_DATA, NONDETERMINISTIC_LOG_DATA) - .given( - newKeyNamed(SECP_256K1_SOURCE_KEY).shape(SECP_256K1_SHAPE), - cryptoCreate(RELAYER).balance(6 * ONE_MILLION_HBARS), - cryptoTransfer(tinyBarsFromAccountToAlias(GENESIS, SECP_256K1_SOURCE_KEY, ONE_HUNDRED_HBARS)) - .via(AUTO_ACCOUNT), - getTxnRecord(AUTO_ACCOUNT).andAllChildRecords(), - uploadInitCode(contract), - contractCreate(contract)) - .when( - // Ensure we submit these two transactions in the same block - waitUntilStartOfNextAdhocPeriod(2_000), - ethereumCall(contract, LOG_NOW) - .type(EthTxData.EthTransactionType.EIP1559) - .signingWith(SECP_256K1_SOURCE_KEY) - .payingWith(RELAYER) - .nonce(0) - .maxFeePerGas(50L) - .gasLimit(1_000_000L) - .via(firstCall) - .deferStatusResolution() - .hasKnownStatus(ResponseCodeEnum.SUCCESS), - ethereumCall(contract, LOG_NOW) - .type(EthTxData.EthTransactionType.EIP1559) - .signingWith(SECP_256K1_SOURCE_KEY) - .payingWith(RELAYER) - .nonce(1) - .maxFeePerGas(50L) - .gasLimit(1_000_000L) - .via(secondCall) - .deferStatusResolution() - .hasKnownStatus(ResponseCodeEnum.SUCCESS)) - .then(withOpContext((spec, opLog) -> { - final var firstBlockOp = getTxnRecord(firstCall).hasRetryAnswerOnlyPrecheck(RECORD_NOT_FOUND); - final var recordOp = getTxnRecord(secondCall).hasRetryAnswerOnlyPrecheck(RECORD_NOT_FOUND); - allRunFor(spec, firstBlockOp, recordOp); - - final var firstCallRecord = firstBlockOp.getResponseRecord(); - final var firstCallLogs = - firstCallRecord.getContractCallResult().getLogInfoList(); - final var firstCallTimeLogData = - firstCallLogs.get(0).getData().toByteArray(); - final var firstCallTimestamp = - Longs.fromByteArray(Arrays.copyOfRange(firstCallTimeLogData, 24, 32)); - - final var secondCallRecord = recordOp.getResponseRecord(); - final var secondCallLogs = - secondCallRecord.getContractCallResult().getLogInfoList(); - final var secondCallTimeLogData = - secondCallLogs.get(0).getData().toByteArray(); - final var secondCallTimestamp = - Longs.fromByteArray(Arrays.copyOfRange(secondCallTimeLogData, 24, 32)); - - final var firstBlockPeriod = canonicalBlockPeriod(firstCallRecord.getConsensusTimestamp()); - final var secondBlockPeriod = canonicalBlockPeriod(secondCallRecord.getConsensusTimestamp()); - - // In general both calls will be handled in the same block period, and should hence have the - // same Ethereum block timestamp; but timing fluctuations in CI _can_ cause them to be handled - // in different block periods, so we allow for that here as well - if (firstBlockPeriod < secondBlockPeriod) { - assertTrue( - firstCallTimestamp < secondCallTimestamp, - "Block timestamps should change from period " + firstBlockPeriod + " to " - + secondBlockPeriod); - } else { - assertEquals(firstCallTimestamp, secondCallTimestamp, "Block timestamps should be equal"); - } - })); - } - - @HapiTest - final HapiSpec blck001And002And003And004ReturnsCorrectBlockProperties() { - final var contract = "EmitBlockTimestamp"; - final var firstBlock = "firstBlock"; - final var secondBlock = "secondBlock"; - - return defaultHapiSpec( - "returnsCorrectBlockProperties", NONDETERMINISTIC_ETHEREUM_DATA, NONDETERMINISTIC_LOG_DATA) - .given( - newKeyNamed(SECP_256K1_SOURCE_KEY).shape(SECP_256K1_SHAPE), - cryptoCreate(RELAYER).balance(6 * ONE_MILLION_HBARS), - cryptoTransfer(tinyBarsFromAccountToAlias(GENESIS, SECP_256K1_SOURCE_KEY, ONE_HUNDRED_HBARS)) - .via(AUTO_ACCOUNT), - getTxnRecord(AUTO_ACCOUNT).andAllChildRecords(), - uploadInitCode(contract), - contractCreate(contract)) - .when( - waitUntilStartOfNextAdhocPeriod(2_000L), - ethereumCall(contract, LOG_NOW) - .type(EthTxData.EthTransactionType.EIP1559) - .signingWith(SECP_256K1_SOURCE_KEY) - .payingWith(RELAYER) - .nonce(0) - .maxFeePerGas(50L) - .gasLimit(1_000_000L) - .via(firstBlock) - .deferStatusResolution() - .hasKnownStatus(ResponseCodeEnum.SUCCESS), - // Make sure we submit the next transaction in the next block - waitUntilStartOfNextAdhocPeriod(2_000L), - ethereumCall(contract, LOG_NOW) - .type(EthTxData.EthTransactionType.EIP1559) - .signingWith(SECP_256K1_SOURCE_KEY) - .payingWith(RELAYER) - .nonce(1) - .maxFeePerGas(50L) - .gasLimit(1_000_000L) - .via(secondBlock) - .hasKnownStatus(ResponseCodeEnum.SUCCESS)) - .then(withOpContext((spec, opLog) -> { - final var firstBlockOp = getTxnRecord(firstBlock).hasRetryAnswerOnlyPrecheck(RECORD_NOT_FOUND); - final var recordOp = getTxnRecord(secondBlock).hasRetryAnswerOnlyPrecheck(RECORD_NOT_FOUND); - allRunFor(spec, firstBlockOp, recordOp); - - // First block info - final var firstBlockRecord = firstBlockOp.getResponseRecord(); - final var firstBlockLogs = - firstBlockRecord.getContractCallResult().getLogInfoList(); - final var firstBlockTimeLogData = - firstBlockLogs.get(0).getData().toByteArray(); - final var firstBlockTimestamp = - Longs.fromByteArray(Arrays.copyOfRange(firstBlockTimeLogData, 24, 32)); - final var firstBlockHashLogData = - firstBlockLogs.get(1).getData().toByteArray(); - final var firstBlockNumber = Longs.fromByteArray(Arrays.copyOfRange(firstBlockHashLogData, 24, 32)); - final var firstBlockHash = Bytes32.wrap(Arrays.copyOfRange(firstBlockHashLogData, 32, 64)); - - assertEquals(Bytes32.ZERO, firstBlockHash); - - // Second block info - final var secondBlockRecord = recordOp.getResponseRecord(); - final var secondBlockLogs = - secondBlockRecord.getContractCallResult().getLogInfoList(); - assertEquals(2, secondBlockLogs.size()); - final var secondBlockTimeLogData = - secondBlockLogs.get(0).getData().toByteArray(); - final var secondBlockTimestamp = - Longs.fromByteArray(Arrays.copyOfRange(secondBlockTimeLogData, 24, 32)); - - assertNotEquals(firstBlockTimestamp, secondBlockTimestamp, "Block timestamps should change"); - - final var secondBlockHashLogData = - secondBlockLogs.get(1).getData().toByteArray(); - final var secondBlockNumber = - Longs.fromByteArray(Arrays.copyOfRange(secondBlockHashLogData, 24, 32)); - - assertEquals(firstBlockNumber + 1, secondBlockNumber, "Wrong previous block number"); - - final var secondBlockHash = Bytes32.wrap(Arrays.copyOfRange(secondBlockHashLogData, 32, 64)); - - assertEquals(Bytes32.ZERO, secondBlockHash); - })); - } - - @Override - protected Logger getResultsLogger() { - return LOG; - } - - /** - * Returns the canonical block period for the given consensus timestamp. - * - * @param consensusTimestamp the consensus timestamp - * @return the canonical block period - */ - private long canonicalBlockPeriod(@NonNull final Timestamp consensusTimestamp) { - return Objects.requireNonNull(consensusTimestamp).getSeconds() - / Long.parseLong(HapiSpecSetup.getDefaultNodeProps().get("hedera.recordStream.logPeriod")); - } -} diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/compose/LocalNetworkCheck.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/compose/LocalNetworkCheck.java index cb4012b36069..87ca0954ffb5 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/compose/LocalNetworkCheck.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/compose/LocalNetworkCheck.java @@ -24,12 +24,13 @@ import static com.hedera.services.bdd.spec.transactions.crypto.HapiCryptoTransfer.tinyBarsFromTo; import static com.hedera.services.bdd.spec.utilops.UtilVerbs.balanceSnapshot; -import com.hedera.services.bdd.spec.HapiSpec; import com.hedera.services.bdd.suites.HapiSuite; import java.util.List; import java.util.Map; +import java.util.stream.Stream; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; +import org.junit.jupiter.api.DynamicTest; public class LocalNetworkCheck extends HapiSuite { @@ -42,13 +43,11 @@ public static void main(String... args) { } @Override - public List getSpecsInSuite() { - return List.of(new HapiSpec[] { - balancesChangeOnTransfer(), - }); + public List> getSpecsInSuite() { + return List.of(balancesChangeOnTransfer()); } - final HapiSpec balancesChangeOnTransfer() { + final Stream balancesChangeOnTransfer() { return customHapiSpec("BalancesChangeOnTransfer") .withProperties(Map.of("nodes", "127.0.0.1:50213:0.0.3,127.0.0.1:50214:0.0.4,127.0.0.1:50215:0.0.5")) .given( diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/compose/PerpetualLocalCalls.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/compose/PerpetualLocalCalls.java index 7425992b3086..7d969d2b8647 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/compose/PerpetualLocalCalls.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/compose/PerpetualLocalCalls.java @@ -39,8 +39,10 @@ import java.util.concurrent.atomic.AtomicLong; import java.util.concurrent.atomic.AtomicReference; import java.util.function.Function; +import java.util.stream.Stream; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; +import org.junit.jupiter.api.DynamicTest; public class PerpetualLocalCalls extends HapiSuite { @@ -57,13 +59,11 @@ public static void main(String... args) { } @Override - public List getSpecsInSuite() { - return List.of(new HapiSpec[] { - localCallsForever(), - }); + public List> getSpecsInSuite() { + return List.of(localCallsForever()); } - final HapiSpec localCallsForever() { + final Stream localCallsForever() { return defaultHapiSpec("LocalCallsForever") .given() .when() diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/consensus/AssortedHcsOps.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/consensus/AssortedHcsOps.java index ec358f59652a..8b592cc27b63 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/consensus/AssortedHcsOps.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/consensus/AssortedHcsOps.java @@ -33,7 +33,6 @@ import static com.hedera.services.bdd.spec.utilops.UtilVerbs.withOpContext; import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.INVALID_TOPIC_ID; -import com.hedera.services.bdd.spec.HapiSpec; import com.hedera.services.bdd.spec.HapiSpecOperation; import com.hedera.services.bdd.spec.keys.KeyShape; import com.hedera.services.bdd.spec.queries.QueryVerbs; @@ -44,8 +43,10 @@ import java.util.concurrent.atomic.AtomicReference; import java.util.function.Function; import java.util.stream.IntStream; +import java.util.stream.Stream; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; +import org.junit.jupiter.api.DynamicTest; public class AssortedHcsOps extends HapiSuite { @@ -59,17 +60,13 @@ public static void main(String... args) { } @Override - public List getSpecsInSuite() { - return List.of(new HapiSpec[] { - // runMisc(), - testRechargingPayer(), - // infoLookup(), - }); + public List> getSpecsInSuite() { + return List.of(testRechargingPayer()); } final String TARGET_DIR = "./dev-system-files"; - final HapiSpec testRechargingPayer() { + final Stream testRechargingPayer() { long startingBalance = 1_000_000L; return defaultHapiSpec("testRechargingPayer") @@ -83,14 +80,14 @@ final HapiSpec testRechargingPayer() { .toArray(HapiSpecOperation[]::new)); } - final HapiSpec infoLookup() { + final Stream infoLookup() { return defaultHapiSpec("infoLookup") .given() .when() .then(QueryVerbs.getTopicInfo("0.0.1161").logged()); } - final HapiSpec runMisc() { + final Stream runMisc() { final int SUBMIT_BURST_SIZE = 10; AtomicReference vanillaTopic = new AtomicReference<>(); diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/consensus/ChunkingSuite.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/consensus/ChunkingSuite.java deleted file mode 100644 index e09d5591c956..000000000000 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/consensus/ChunkingSuite.java +++ /dev/null @@ -1,134 +0,0 @@ -/* - * Copyright (C) 2020-2024 Hedera Hashgraph, LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.hedera.services.bdd.suites.consensus; - -import static com.hedera.services.bdd.spec.HapiSpec.defaultHapiSpec; -import static com.hedera.services.bdd.spec.transactions.TxnVerbs.createTopic; -import static com.hedera.services.bdd.spec.transactions.TxnVerbs.cryptoCreate; -import static com.hedera.services.bdd.spec.transactions.TxnVerbs.submitMessageTo; -import static com.hedera.services.bdd.spec.utilops.UtilVerbs.chunkAFile; -import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.BUSY; -import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.INVALID_CHUNK_NUMBER; -import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.INVALID_CHUNK_TRANSACTION_ID; -import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.SUCCESS; - -import com.hedera.services.bdd.junit.HapiTest; -import com.hedera.services.bdd.junit.HapiTestSuite; -import com.hedera.services.bdd.spec.HapiSpec; -import com.hedera.services.bdd.suites.HapiSuite; -import java.util.List; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; - -@HapiTestSuite -public class ChunkingSuite extends HapiSuite { - - private static final Logger log = LogManager.getLogger(ChunkingSuite.class); - private static final int CHUNK_SIZE = 1024; - - public static void main(String... args) { - new ChunkingSuite().runSuiteAsync(); - } - - @Override - public boolean canRunConcurrent() { - return true; - } - - @Override - public List getSpecsInSuite() { - return List.of(chunkNumberIsValidated(), chunkTransactionIDIsValidated(), longMessageIsFragmentedIntoChunks()); - } - - @HapiTest - final HapiSpec chunkNumberIsValidated() { - return defaultHapiSpec("chunkNumberIsValidated") - .given(createTopic("testTopic")) - .when() - .then( - submitMessageTo("testTopic") - .message("failsForChunkNumberGreaterThanTotalChunks") - .chunkInfo(2, 3) - .hasRetryPrecheckFrom(BUSY) - .hasKnownStatus(INVALID_CHUNK_NUMBER), - submitMessageTo("testTopic") - .message("acceptsChunkNumberLessThanTotalChunks") - .chunkInfo(3, 2) - .hasRetryPrecheckFrom(BUSY) - .hasKnownStatus(SUCCESS), - submitMessageTo("testTopic") - .message("acceptsChunkNumberEqualTotalChunks") - .chunkInfo(5, 5) - .hasRetryPrecheckFrom(BUSY) - .hasKnownStatus(SUCCESS)); - } - - @HapiTest - final HapiSpec chunkTransactionIDIsValidated() { - return defaultHapiSpec("chunkTransactionIDIsValidated") - .given(cryptoCreate("initialTransactionPayer"), createTopic("testTopic")) - .when() - .then( - submitMessageTo("testTopic") - .message("failsForDifferentPayers") - .chunkInfo(3, 2, "initialTransactionPayer") - .hasRetryPrecheckFrom(BUSY) - .hasKnownStatus(INVALID_CHUNK_TRANSACTION_ID), - /* AcceptsChunkNumberDifferentThan1HavingTheSamePayerEvenWhenNotMatchingValidStart */ - submitMessageTo("testTopic") - .message("A") - .chunkInfo(3, 3, "initialTransactionPayer") - .payingWith("initialTransactionPayer") - // Add delay to make sure the valid start of the transaction will - // not match - // that of the initialTransactionID - .delayBy(1000) - .hasRetryPrecheckFrom(BUSY) - .hasKnownStatus(SUCCESS), - /* FailsForTransactionIDOfChunkNumber1NotMatchingTheEntireInitialTransactionID */ - submitMessageTo("testTopic") - .message("B") - .chunkInfo(2, 1) - // Also add delay here - .delayBy(1000) - .hasRetryPrecheckFrom(BUSY) - .hasKnownStatus(INVALID_CHUNK_TRANSACTION_ID), - /* AcceptsChunkNumber1WhenItsTransactionIDMatchesTheEntireInitialTransactionID */ - submitMessageTo("testTopic") - .message("C") - .chunkInfo(4, 1) - .via("firstChunk") - .payingWith("initialTransactionPayer") - .usePresetTimestamp() - .hasRetryPrecheckFrom(BUSY) - .hasKnownStatus(SUCCESS)); - } - - @HapiTest - final HapiSpec longMessageIsFragmentedIntoChunks() { - String fileForLongMessage = "src/main/resource/RandomLargeBinary.bin"; - return defaultHapiSpec("longMessageIsFragmentedIntoChunks") - .given(cryptoCreate("payer"), createTopic("testTopic")) - .when() - .then(chunkAFile(fileForLongMessage, CHUNK_SIZE, "payer", "testTopic")); - } - - @Override - protected Logger getResultsLogger() { - return log; - } -} diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/consensus/SubmitMessageSuite.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/consensus/SubmitMessageSuite.java index ad35e2ed9395..816eefe5a376 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/consensus/SubmitMessageSuite.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/consensus/SubmitMessageSuite.java @@ -30,14 +30,17 @@ import static com.hedera.services.bdd.spec.transactions.TxnVerbs.createTopic; import static com.hedera.services.bdd.spec.transactions.TxnVerbs.cryptoCreate; import static com.hedera.services.bdd.spec.transactions.TxnVerbs.submitMessageTo; +import static com.hedera.services.bdd.spec.utilops.UtilVerbs.chunkAFile; import static com.hedera.services.bdd.spec.utilops.UtilVerbs.inParallel; import static com.hedera.services.bdd.spec.utilops.UtilVerbs.newKeyNamed; import static com.hedera.services.bdd.spec.utilops.UtilVerbs.sleepFor; import static com.hedera.services.bdd.spec.utilops.UtilVerbs.submitModified; import static com.hedera.services.bdd.spec.utilops.UtilVerbs.validateChargedUsd; import static com.hedera.services.bdd.spec.utilops.mod.ModificationUtils.withSuccessivelyVariedBodyIds; +import static com.hedera.services.bdd.suites.HapiSuite.asOpArray; import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.BUSY; import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.INVALID_CHUNK_NUMBER; +import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.INVALID_CHUNK_TRANSACTION_ID; import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.INVALID_SIGNATURE; import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.INVALID_TOPIC_ID; import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.INVALID_TOPIC_MESSAGE; @@ -47,47 +50,17 @@ import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.TRANSACTION_OVERSIZE; import com.hedera.services.bdd.junit.HapiTest; -import com.hedera.services.bdd.junit.HapiTestSuite; -import com.hedera.services.bdd.spec.HapiSpec; import com.hedera.services.bdd.spec.keys.KeyShape; import com.hedera.services.bdd.spec.keys.SigControl; -import com.hedera.services.bdd.suites.HapiSuite; import java.util.Arrays; -import java.util.List; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; +import java.util.stream.Stream; +import org.junit.jupiter.api.DynamicTest; -@HapiTestSuite -public class SubmitMessageSuite extends HapiSuite { - - private static final Logger log = LogManager.getLogger(SubmitMessageSuite.class); - - public static void main(String... args) { - new SubmitMessageSuite().runSuiteAsync(); - } - - @Override - public boolean canRunConcurrent() { - return true; - } - - @Override - public List getSpecsInSuite() { - return List.of( - pureCheckFails(), - topicIdIsValidated(), - messageIsValidated(), - messageSubmissionSimple(), - messageSubmissionIncreasesSeqNo(), - messageSubmissionWithSubmitKey(), - messageSubmissionMultiple(), - messageSubmissionOverSize(), - messageSubmissionCorrectlyUpdatesRunningHash(), - feeAsExpected()); - } +public class SubmitMessageSuite { + private static final int CHUNK_SIZE = 1024; @HapiTest - final HapiSpec pureCheckFails() { + final Stream pureCheckFails() { return defaultHapiSpec("testTopic") .given(cryptoCreate("nonTopicId")) .when() @@ -98,7 +71,7 @@ final HapiSpec pureCheckFails() { } @HapiTest - public HapiSpec idVariantsTreatedAsExpected() { + final Stream idVariantsTreatedAsExpected() { return defaultHapiSpec("idVariantsTreatedAsExpected") .given(createTopic("testTopic")) .when() @@ -107,7 +80,7 @@ public HapiSpec idVariantsTreatedAsExpected() { } @HapiTest - final HapiSpec topicIdIsValidated() { + final Stream topicIdIsValidated() { return defaultHapiSpec("topicIdIsValidated") .given(cryptoCreate("nonTopicId")) .when() @@ -122,7 +95,7 @@ final HapiSpec topicIdIsValidated() { } @HapiTest - final HapiSpec messageIsValidated() { + final Stream messageIsValidated() { return defaultHapiSpec("messageIsValidated") .given(createTopic("testTopic")) .when() @@ -138,7 +111,7 @@ final HapiSpec messageIsValidated() { } @HapiTest - final HapiSpec messageSubmissionSimple() { + final Stream messageSubmissionSimple() { return defaultHapiSpec("messageSubmissionSimple") .given( newKeyNamed("submitKey"), @@ -152,7 +125,7 @@ final HapiSpec messageSubmissionSimple() { } @HapiTest - final HapiSpec messageSubmissionIncreasesSeqNo() { + final Stream messageSubmissionIncreasesSeqNo() { KeyShape submitKeyShape = threshOf(2, SIMPLE, SIMPLE, listOf(2)); return defaultHapiSpec("messageSubmissionIncreasesSeqNo") @@ -164,7 +137,7 @@ final HapiSpec messageSubmissionIncreasesSeqNo() { } @HapiTest - final HapiSpec messageSubmissionWithSubmitKey() { + final Stream messageSubmissionWithSubmitKey() { KeyShape submitKeyShape = threshOf(2, SIMPLE, SIMPLE, listOf(2)); SigControl validSig = submitKeyShape.signedWith(sigs(ON, OFF, sigs(ON, ON))); @@ -187,7 +160,7 @@ final HapiSpec messageSubmissionWithSubmitKey() { } @HapiTest - final HapiSpec messageSubmissionMultiple() { + final Stream messageSubmissionMultiple() { final int numMessages = 10; return defaultHapiSpec("messageSubmissionMultiple") @@ -199,7 +172,7 @@ final HapiSpec messageSubmissionMultiple() { } @HapiTest - final HapiSpec messageSubmissionOverSize() { + final Stream messageSubmissionOverSize() { final byte[] messageBytes = new byte[4096]; // 4k Arrays.fill(messageBytes, (byte) 0b1); @@ -216,7 +189,7 @@ final HapiSpec messageSubmissionOverSize() { } @HapiTest - final HapiSpec feeAsExpected() { + final Stream feeAsExpected() { final byte[] messageBytes = new byte[100]; // 4k Arrays.fill(messageBytes, (byte) 0b1); return defaultHapiSpec("feeAsExpected") @@ -233,7 +206,7 @@ final HapiSpec feeAsExpected() { } @HapiTest - final HapiSpec messageSubmissionCorrectlyUpdatesRunningHash() { + final Stream messageSubmissionCorrectlyUpdatesRunningHash() { String topic = "testTopic"; String message1 = "Hello world!"; String message2 = "Hello world again!"; @@ -274,8 +247,76 @@ final HapiSpec messageSubmissionCorrectlyUpdatesRunningHash() { getTxnRecord("submitMessage3").hasCorrectRunningHash(topic, message3)); } - @Override - protected Logger getResultsLogger() { - return log; + @HapiTest + final Stream chunkNumberIsValidated() { + return defaultHapiSpec("chunkNumberIsValidated") + .given(createTopic("testTopic")) + .when() + .then( + submitMessageTo("testTopic") + .message("failsForChunkNumberGreaterThanTotalChunks") + .chunkInfo(2, 3) + .hasRetryPrecheckFrom(BUSY) + .hasKnownStatus(INVALID_CHUNK_NUMBER), + submitMessageTo("testTopic") + .message("acceptsChunkNumberLessThanTotalChunks") + .chunkInfo(3, 2) + .hasRetryPrecheckFrom(BUSY) + .hasKnownStatus(SUCCESS), + submitMessageTo("testTopic") + .message("acceptsChunkNumberEqualTotalChunks") + .chunkInfo(5, 5) + .hasRetryPrecheckFrom(BUSY) + .hasKnownStatus(SUCCESS)); + } + + @HapiTest + final Stream chunkTransactionIDIsValidated() { + return defaultHapiSpec("chunkTransactionIDIsValidated") + .given(cryptoCreate("initialTransactionPayer"), createTopic("testTopic")) + .when() + .then( + submitMessageTo("testTopic") + .message("failsForDifferentPayers") + .chunkInfo(3, 2, "initialTransactionPayer") + .hasRetryPrecheckFrom(BUSY) + .hasKnownStatus(INVALID_CHUNK_TRANSACTION_ID), + // Add delay to make sure the valid start of the transaction will + // not match + // that of the initialTransactionID + sleepFor(1000), + /* AcceptsChunkNumberDifferentThan1HavingTheSamePayerEvenWhenNotMatchingValidStart */ + submitMessageTo("testTopic") + .message("A") + .chunkInfo(3, 3, "initialTransactionPayer") + .payingWith("initialTransactionPayer") + .hasRetryPrecheckFrom(BUSY) + .hasKnownStatus(SUCCESS), + /* FailsForTransactionIDOfChunkNumber1NotMatchingTheEntireInitialTransactionID */ + sleepFor(1000), + submitMessageTo("testTopic") + .message("B") + .chunkInfo(2, 1) + // Also add delay here + .hasRetryPrecheckFrom(BUSY) + .hasKnownStatus(INVALID_CHUNK_TRANSACTION_ID), + /* AcceptsChunkNumber1WhenItsTransactionIDMatchesTheEntireInitialTransactionID */ + submitMessageTo("testTopic") + .message("C") + .chunkInfo(4, 1) + .via("firstChunk") + .payingWith("initialTransactionPayer") + .usePresetTimestamp() + .hasRetryPrecheckFrom(BUSY) + .hasKnownStatus(SUCCESS)); + } + + @HapiTest + final Stream longMessageIsFragmentedIntoChunks() { + String fileForLongMessage = "src/main/resource/RandomLargeBinary.bin"; + return defaultHapiSpec("longMessageIsFragmentedIntoChunks") + .given(cryptoCreate("payer"), createTopic("testTopic")) + .when() + .then(chunkAFile(fileForLongMessage, CHUNK_SIZE, "payer", "testTopic")); } } diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/consensus/TopicCreateSuite.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/consensus/TopicCreateSuite.java index 212a71642268..5cc46544955a 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/consensus/TopicCreateSuite.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/consensus/TopicCreateSuite.java @@ -18,16 +18,27 @@ import static com.hedera.services.bdd.spec.HapiSpec.defaultHapiSpec; import static com.hedera.services.bdd.spec.queries.QueryVerbs.getTopicInfo; +import static com.hedera.services.bdd.spec.queries.QueryVerbs.getTxnRecord; import static com.hedera.services.bdd.spec.transactions.TxnVerbs.contractCreate; import static com.hedera.services.bdd.spec.transactions.TxnVerbs.createDefaultContract; import static com.hedera.services.bdd.spec.transactions.TxnVerbs.createTopic; import static com.hedera.services.bdd.spec.transactions.TxnVerbs.cryptoCreate; +import static com.hedera.services.bdd.spec.transactions.TxnVerbs.deleteTopic; +import static com.hedera.services.bdd.spec.transactions.TxnVerbs.submitMessageTo; +import static com.hedera.services.bdd.spec.transactions.TxnVerbs.updateTopic; import static com.hedera.services.bdd.spec.transactions.TxnVerbs.uploadInitCode; +import static com.hedera.services.bdd.spec.utilops.UtilVerbs.exposeTargetLedgerIdTo; import static com.hedera.services.bdd.spec.utilops.UtilVerbs.newKeyNamed; +import static com.hedera.services.bdd.spec.utilops.UtilVerbs.sendModified; +import static com.hedera.services.bdd.spec.utilops.UtilVerbs.sourcing; import static com.hedera.services.bdd.spec.utilops.UtilVerbs.submitModified; import static com.hedera.services.bdd.spec.utilops.UtilVerbs.validateChargedUsd; import static com.hedera.services.bdd.spec.utilops.mod.ModificationUtils.withSuccessivelyVariedBodyIds; +import static com.hedera.services.bdd.spec.utilops.mod.ModificationUtils.withSuccessivelyVariedQueryIds; import static com.hedera.services.bdd.spec.utilops.records.SnapshotMatchMode.NONDETERMINISTIC_TRANSACTION_FEES; +import static com.hedera.services.bdd.suites.HapiSuite.GENESIS; +import static com.hedera.services.bdd.suites.HapiSuite.NONSENSE_KEY; +import static com.hedera.services.bdd.suites.HapiSuite.ONE_HUNDRED_HBARS; import static com.hedera.services.bdd.suites.contract.hapi.ContractCallSuite.PAY_RECEIVABLE_CONTRACT; import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.AUTORENEW_ACCOUNT_NOT_ALLOWED; import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.AUTORENEW_DURATION_NOT_IN_RANGE; @@ -35,45 +46,21 @@ import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.INVALID_AUTORENEW_ACCOUNT; import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.INVALID_RENEWAL_PERIOD; import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.INVALID_SIGNATURE; +import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.INVALID_TOPIC_ID; import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.OK; +import com.google.protobuf.ByteString; import com.hedera.services.bdd.junit.HapiTest; -import com.hedera.services.bdd.junit.HapiTestSuite; -import com.hedera.services.bdd.spec.HapiSpec; -import com.hedera.services.bdd.suites.HapiSuite; -import java.util.List; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; +import java.util.concurrent.atomic.AtomicReference; +import java.util.stream.Stream; +import org.junit.jupiter.api.DynamicTest; -@HapiTestSuite -public class TopicCreateSuite extends HapiSuite { - private static final Logger log = LogManager.getLogger(TopicCreateSuite.class); - - public static void main(String... args) { - new TopicCreateSuite().runSuiteAsync(); - } - - @Override - public List getSpecsInSuite() { - return List.of( - signingRequirementsEnforced(), - autoRenewPeriodIsValidated(), - autoRenewAccountIdNeedsAdminKeyToo(), - submitKeyIsValidated(), - adminKeyIsValidated(), - autoRenewAccountIsValidated(), - noAutoRenewPeriod(), - allFieldsSetHappyCase(), - feeAsExpected()); - } - - @Override - public boolean canRunConcurrent() { - return true; - } +public class TopicCreateSuite { + public static final String TEST_TOPIC = "testTopic"; + public static final String TESTMEMO = "testmemo"; @HapiTest - final HapiSpec adminKeyIsValidated() { + final Stream adminKeyIsValidated() { return defaultHapiSpec("AdminKeyIsValidated") .given() .when() @@ -85,7 +72,7 @@ final HapiSpec adminKeyIsValidated() { } @HapiTest - final HapiSpec submitKeyIsValidated() { + final Stream submitKeyIsValidated() { return defaultHapiSpec("SubmitKeyIsValidated") .given() .when() @@ -96,7 +83,7 @@ final HapiSpec submitKeyIsValidated() { } @HapiTest - final HapiSpec autoRenewAccountIsValidated() { + final Stream autoRenewAccountIsValidated() { return defaultHapiSpec("AutoRenewAccountIsValidated") .given() .when() @@ -107,7 +94,7 @@ final HapiSpec autoRenewAccountIsValidated() { } @HapiTest - final HapiSpec autoRenewAccountIdNeedsAdminKeyToo() { + final Stream autoRenewAccountIdNeedsAdminKeyToo() { return defaultHapiSpec("autoRenewAccountIdNeedsAdminKeyToo") .given(cryptoCreate("payer"), cryptoCreate("autoRenewAccount")) .when() @@ -120,7 +107,7 @@ final HapiSpec autoRenewAccountIdNeedsAdminKeyToo() { } @HapiTest - public HapiSpec idVariantsTreatedAsExpected() { + final Stream idVariantsTreatedAsExpected() { final var autoRenewAccount = "autoRenewAccount"; return defaultHapiSpec("idVariantsTreatedAsExpected") .given(cryptoCreate(autoRenewAccount), newKeyNamed("adminKey")) @@ -131,7 +118,7 @@ public HapiSpec idVariantsTreatedAsExpected() { } @HapiTest - final HapiSpec autoRenewPeriodIsValidated() { + final Stream autoRenewPeriodIsValidated() { final var tooShortAutoRenewPeriod = "tooShortAutoRenewPeriod"; final var tooLongAutoRenewPeriod = "tooLongAutoRenewPeriod"; return defaultHapiSpec("autoRenewPeriodIsValidated") @@ -147,7 +134,7 @@ final HapiSpec autoRenewPeriodIsValidated() { } @HapiTest - final HapiSpec noAutoRenewPeriod() { + final Stream noAutoRenewPeriod() { return defaultHapiSpec("noAutoRenewPeriod") .given() .when() @@ -158,7 +145,7 @@ final HapiSpec noAutoRenewPeriod() { } @HapiTest - final HapiSpec signingRequirementsEnforced() { + final Stream signingRequirementsEnforced() { long PAYER_BALANCE = 1_999_999_999L; final var contractWithAdminKey = "nonCryptoAccount"; @@ -237,7 +224,7 @@ final HapiSpec signingRequirementsEnforced() { } @HapiTest - final HapiSpec allFieldsSetHappyCase() { + final Stream allFieldsSetHappyCase() { return defaultHapiSpec("AllFieldsSetHappyCase", NONDETERMINISTIC_TRANSACTION_FEES) .given(newKeyNamed("adminKey"), newKeyNamed("submitKey"), cryptoCreate("autoRenewAccount")) .when() @@ -249,7 +236,7 @@ final HapiSpec allFieldsSetHappyCase() { } @HapiTest - final HapiSpec feeAsExpected() { + final Stream feeAsExpected() { return defaultHapiSpec("feeAsExpected") .given( newKeyNamed("adminKey"), @@ -266,8 +253,72 @@ final HapiSpec feeAsExpected() { .then(validateChargedUsd("topicCreate", 0.0226)); } - @Override - protected Logger getResultsLogger() { - return log; + @HapiTest + final Stream getInfoIdVariantsTreatedAsExpected() { + return defaultHapiSpec("idVariantsTreatedAsExpected") + .given(createTopic("topic")) + .when() + .then(sendModified(withSuccessivelyVariedQueryIds(), () -> getTopicInfo("topic"))); + } + + @HapiTest + final Stream getInfoAllFieldsSetHappyCase() { + // sequenceNumber should be 0 and runningHash should be 48 bytes all 0s. + final AtomicReference targetLedgerId = new AtomicReference<>(); + return defaultHapiSpec("AllFieldsSetHappyCase") + .given( + newKeyNamed("adminKey"), + newKeyNamed("submitKey"), + cryptoCreate("autoRenewAccount"), + cryptoCreate("payer"), + createTopic(TEST_TOPIC) + .topicMemo(TESTMEMO) + .adminKeyName("adminKey") + .submitKeyName("submitKey") + .autoRenewAccountId("autoRenewAccount") + .via("createTopic")) + .when() + .then( + exposeTargetLedgerIdTo(targetLedgerId::set), + sourcing(() -> getTopicInfo(TEST_TOPIC) + .hasEncodedLedgerId(targetLedgerId.get()) + .hasMemo(TESTMEMO) + .hasAdminKey("adminKey") + .hasSubmitKey("submitKey") + .hasAutoRenewAccount("autoRenewAccount") + .hasSeqNo(0) + .hasRunningHash(new byte[48])), + getTxnRecord("createTopic").logged(), + submitMessageTo(TEST_TOPIC) + .blankMemo() + .payingWith("payer") + .message(new String("test".getBytes())) + .via("submitMessage"), + getTxnRecord("submitMessage").logged(), + sourcing(() -> getTopicInfo(TEST_TOPIC) + .hasEncodedLedgerId(targetLedgerId.get()) + .hasMemo(TESTMEMO) + .hasAdminKey("adminKey") + .hasSubmitKey("submitKey") + .hasAutoRenewAccount("autoRenewAccount") + .hasSeqNo(1) + .logged()), + updateTopic(TEST_TOPIC) + .topicMemo("Don't worry about the vase") + .via("updateTopic"), + getTxnRecord("updateTopic").logged(), + sourcing(() -> getTopicInfo(TEST_TOPIC) + .hasEncodedLedgerId(targetLedgerId.get()) + .hasMemo("Don't worry about the vase") + .hasAdminKey("adminKey") + .hasSubmitKey("submitKey") + .hasAutoRenewAccount("autoRenewAccount") + .hasSeqNo(1) + .logged()), + deleteTopic(TEST_TOPIC).via("deleteTopic"), + getTxnRecord("deleteTopic").logged(), + getTopicInfo(TEST_TOPIC) + .hasCostAnswerPrecheck(INVALID_TOPIC_ID) + .logged()); } } diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/consensus/TopicDeleteSuite.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/consensus/TopicDeleteSuite.java index 0e99b45c80da..239b78b5fd46 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/consensus/TopicDeleteSuite.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/consensus/TopicDeleteSuite.java @@ -31,42 +31,13 @@ import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.UNAUTHORIZED; import com.hedera.services.bdd.junit.HapiTest; -import com.hedera.services.bdd.junit.HapiTestSuite; -import com.hedera.services.bdd.spec.HapiSpec; -import com.hedera.services.bdd.suites.HapiSuite; import com.hederahashgraph.api.proto.java.ResponseCodeEnum; -import java.util.List; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; - -@HapiTestSuite -public class TopicDeleteSuite extends HapiSuite { - - private static final Logger log = LogManager.getLogger(TopicDeleteSuite.class); - - public static void main(String... args) { - new TopicDeleteSuite().runSuiteAsync(); - } - - @Override - public boolean canRunConcurrent() { - return true; - } - - @Override - public List getSpecsInSuite() { - return List.of( - pureCheckFails(), - cannotDeleteAccountAsTopic(), - topicIdIsValidated(), - noAdminKeyCannotDelete(), - deleteWithAdminKey(), - deleteFailedWithWrongKey(), - feeAsExpected()); - } +import java.util.stream.Stream; +import org.junit.jupiter.api.DynamicTest; +public class TopicDeleteSuite { @HapiTest - public HapiSpec idVariantsTreatedAsExpected() { + final Stream idVariantsTreatedAsExpected() { return defaultHapiSpec("idVariantsTreatedAsExpected") .given(newKeyNamed("adminKey")) .when(createTopic("topic").adminKeyName("adminKey")) @@ -74,7 +45,7 @@ public HapiSpec idVariantsTreatedAsExpected() { } @HapiTest - final HapiSpec pureCheckFails() { + final Stream pureCheckFails() { return defaultHapiSpec("CannotDeleteAccountAsTopic") .given(cryptoCreate("nonTopicId")) .when() @@ -85,7 +56,7 @@ final HapiSpec pureCheckFails() { } @HapiTest - final HapiSpec cannotDeleteAccountAsTopic() { + final Stream cannotDeleteAccountAsTopic() { return defaultHapiSpec("CannotDeleteAccountAsTopic") .given(cryptoCreate("nonTopicId")) .when() @@ -94,7 +65,7 @@ final HapiSpec cannotDeleteAccountAsTopic() { } @HapiTest - final HapiSpec topicIdIsValidated() { + final Stream topicIdIsValidated() { // Fully non-deterministic for fuzzy matching because the test uses an absolute entity number (i.e. // 100.232.4534) // but fuzzy matching compares relative entity numbers @@ -108,7 +79,7 @@ final HapiSpec topicIdIsValidated() { } @HapiTest - final HapiSpec noAdminKeyCannotDelete() { + final Stream noAdminKeyCannotDelete() { return defaultHapiSpec("noAdminKeyCannotDelete") .given(createTopic("testTopic")) .when(deleteTopic("testTopic").hasKnownStatus(UNAUTHORIZED)) @@ -116,7 +87,7 @@ final HapiSpec noAdminKeyCannotDelete() { } @HapiTest - final HapiSpec deleteWithAdminKey() { + final Stream deleteWithAdminKey() { return defaultHapiSpec("deleteWithAdminKey") .given(newKeyNamed("adminKey"), createTopic("testTopic").adminKeyName("adminKey")) .when(deleteTopic("testTopic").hasPrecheck(ResponseCodeEnum.OK)) @@ -124,7 +95,7 @@ final HapiSpec deleteWithAdminKey() { } @HapiTest - final HapiSpec deleteFailedWithWrongKey() { + final Stream deleteFailedWithWrongKey() { long PAYER_BALANCE = 1_999_999_999L; return defaultHapiSpec("deleteFailedWithWrongKey") .given( @@ -140,15 +111,10 @@ final HapiSpec deleteFailedWithWrongKey() { } @HapiTest - final HapiSpec feeAsExpected() { + final Stream feeAsExpected() { return defaultHapiSpec("feeAsExpected") .given(cryptoCreate("payer"), createTopic("testTopic").adminKeyName("payer")) .when(deleteTopic("testTopic").blankMemo().payingWith("payer").via("topicDelete")) .then(validateChargedUsd("topicDelete", 0.005)); } - - @Override - protected Logger getResultsLogger() { - return log; - } } diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/consensus/TopicGetInfoSuite.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/consensus/TopicGetInfoSuite.java deleted file mode 100644 index 5b6b4621b68d..000000000000 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/consensus/TopicGetInfoSuite.java +++ /dev/null @@ -1,138 +0,0 @@ -/* - * Copyright (C) 2020-2024 Hedera Hashgraph, LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.hedera.services.bdd.suites.consensus; - -import static com.hedera.services.bdd.spec.HapiSpec.defaultHapiSpec; -import static com.hedera.services.bdd.spec.queries.QueryVerbs.getTopicInfo; -import static com.hedera.services.bdd.spec.queries.QueryVerbs.getTxnRecord; -import static com.hedera.services.bdd.spec.transactions.TxnVerbs.createTopic; -import static com.hedera.services.bdd.spec.transactions.TxnVerbs.cryptoCreate; -import static com.hedera.services.bdd.spec.transactions.TxnVerbs.deleteTopic; -import static com.hedera.services.bdd.spec.transactions.TxnVerbs.submitMessageTo; -import static com.hedera.services.bdd.spec.transactions.TxnVerbs.updateTopic; -import static com.hedera.services.bdd.spec.utilops.UtilVerbs.exposeTargetLedgerIdTo; -import static com.hedera.services.bdd.spec.utilops.UtilVerbs.newKeyNamed; -import static com.hedera.services.bdd.spec.utilops.UtilVerbs.sendModified; -import static com.hedera.services.bdd.spec.utilops.UtilVerbs.sourcing; -import static com.hedera.services.bdd.spec.utilops.mod.ModificationUtils.withSuccessivelyVariedQueryIds; -import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.INVALID_TOPIC_ID; - -import com.google.protobuf.ByteString; -import com.hedera.services.bdd.junit.HapiTest; -import com.hedera.services.bdd.junit.HapiTestSuite; -import com.hedera.services.bdd.spec.HapiSpec; -import com.hedera.services.bdd.suites.HapiSuite; -import java.util.List; -import java.util.concurrent.atomic.AtomicReference; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; - -@HapiTestSuite -public class TopicGetInfoSuite extends HapiSuite { - - private static final Logger log = LogManager.getLogger(TopicGetInfoSuite.class); - public static final String TEST_TOPIC = "testTopic"; - public static final String TESTMEMO = "testmemo"; - - public static void main(String... args) { - new TopicGetInfoSuite().runSuiteAsync(); - } - - @Override - public List getSpecsInSuite() { - return List.of(allFieldsSetHappyCase()); - } - - @Override - public boolean canRunConcurrent() { - return true; - } - - @HapiTest - public HapiSpec idVariantsTreatedAsExpected() { - return defaultHapiSpec("idVariantsTreatedAsExpected") - .given(createTopic("topic")) - .when() - .then(sendModified(withSuccessivelyVariedQueryIds(), () -> getTopicInfo("topic"))); - } - - @HapiTest - final HapiSpec allFieldsSetHappyCase() { - // sequenceNumber should be 0 and runningHash should be 48 bytes all 0s. - final AtomicReference targetLedgerId = new AtomicReference<>(); - return defaultHapiSpec("AllFieldsSetHappyCase") - .given( - newKeyNamed("adminKey"), - newKeyNamed("submitKey"), - cryptoCreate("autoRenewAccount"), - cryptoCreate("payer"), - createTopic(TEST_TOPIC) - .topicMemo(TESTMEMO) - .adminKeyName("adminKey") - .submitKeyName("submitKey") - .autoRenewAccountId("autoRenewAccount") - .via("createTopic")) - .when() - .then( - exposeTargetLedgerIdTo(targetLedgerId::set), - sourcing(() -> getTopicInfo(TEST_TOPIC) - .hasEncodedLedgerId(targetLedgerId.get()) - .hasMemo(TESTMEMO) - .hasAdminKey("adminKey") - .hasSubmitKey("submitKey") - .hasAutoRenewAccount("autoRenewAccount") - .hasSeqNo(0) - .hasRunningHash(new byte[48])), - getTxnRecord("createTopic").logged(), - submitMessageTo(TEST_TOPIC) - .blankMemo() - .payingWith("payer") - .message(new String("test".getBytes())) - .via("submitMessage"), - getTxnRecord("submitMessage").logged(), - sourcing(() -> getTopicInfo(TEST_TOPIC) - .hasEncodedLedgerId(targetLedgerId.get()) - .hasMemo(TESTMEMO) - .hasAdminKey("adminKey") - .hasSubmitKey("submitKey") - .hasAutoRenewAccount("autoRenewAccount") - .hasSeqNo(1) - .logged()), - updateTopic(TEST_TOPIC) - .topicMemo("Don't worry about the vase") - .via("updateTopic"), - getTxnRecord("updateTopic").logged(), - sourcing(() -> getTopicInfo(TEST_TOPIC) - .hasEncodedLedgerId(targetLedgerId.get()) - .hasMemo("Don't worry about the vase") - .hasAdminKey("adminKey") - .hasSubmitKey("submitKey") - .hasAutoRenewAccount("autoRenewAccount") - .hasSeqNo(1) - .logged()), - deleteTopic(TEST_TOPIC).via("deleteTopic"), - getTxnRecord("deleteTopic").logged(), - getTopicInfo(TEST_TOPIC) - .hasCostAnswerPrecheck(INVALID_TOPIC_ID) - .logged()); - } - - @Override - protected Logger getResultsLogger() { - return log; - } -} diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/consensus/TopicUpdateSuite.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/consensus/TopicUpdateSuite.java index 9dcb0f119060..7da4c5c0d471 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/consensus/TopicUpdateSuite.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/consensus/TopicUpdateSuite.java @@ -26,6 +26,10 @@ import static com.hedera.services.bdd.spec.utilops.UtilVerbs.submitModified; import static com.hedera.services.bdd.spec.utilops.UtilVerbs.validateChargedUsdWithin; import static com.hedera.services.bdd.spec.utilops.mod.ModificationUtils.withSuccessivelyVariedBodyIds; +import static com.hedera.services.bdd.suites.HapiSuite.EMPTY_KEY; +import static com.hedera.services.bdd.suites.HapiSuite.NONSENSE_KEY; +import static com.hedera.services.bdd.suites.HapiSuite.THREE_MONTHS_IN_SECONDS; +import static com.hedera.services.bdd.suites.HapiSuite.ZERO_BYTE_MEMO; import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.AUTORENEW_ACCOUNT_NOT_ALLOWED; import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.AUTORENEW_DURATION_NOT_IN_RANGE; import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.BAD_ENCODING; @@ -40,63 +44,22 @@ import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.UNAUTHORIZED; import com.hedera.services.bdd.junit.HapiTest; -import com.hedera.services.bdd.junit.HapiTestSuite; -import com.hedera.services.bdd.spec.HapiSpec; import com.hedera.services.bdd.spec.HapiSpecSetup; import com.hedera.services.bdd.spec.transactions.consensus.HapiTopicUpdate; -import com.hedera.services.bdd.suites.HapiSuite; -import com.hederahashgraph.api.proto.java.Key; -import com.hederahashgraph.api.proto.java.KeyList; import java.nio.charset.StandardCharsets; import java.time.Instant; import java.util.Arrays; -import java.util.List; import java.util.function.Function; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; - -@HapiTestSuite -public class TopicUpdateSuite extends HapiSuite { - - private static final Logger log = LogManager.getLogger(TopicUpdateSuite.class); +import java.util.stream.Stream; +import org.junit.jupiter.api.DynamicTest; +public class TopicUpdateSuite { private static final long validAutoRenewPeriod = 7_000_000L; private static final long defaultMaxLifetime = Long.parseLong(HapiSpecSetup.getDefaultNodeProps().get("entities.maxLifetime")); - public static final Key IMMUTABILITY_SENTINEL_KEY = - Key.newBuilder().setKeyList(KeyList.getDefaultInstance()).build(); - - public static void main(String... args) { - new TopicUpdateSuite().runSuiteAsync(); - } - - @Override - public List getSpecsInSuite() { - return List.of( - pureCheckFails(), - validateMultipleFields(), - topicUpdateSigReqsEnforcedAtConsensus(), - updateSubmitKeyToDiffKey(), - updateAdminKeyToDiffKey(), - updateAdminKeyToEmpty(), - updateMultipleFields(), - expirationTimestampIsValidated(), - updateSubmitKeyOnTopicWithNoAdminKeyFails(), - clearingAdminKeyWhenAutoRenewAccountPresent(), - feeAsExpected(), - updateExpiryOnTopicWithNoAdminKey(), - updateToMissingTopicFails(), - canRemoveSubmitKeyDuringUpdate()); - } - - @Override - public boolean canRunConcurrent() { - return true; - } - @HapiTest - final HapiSpec pureCheckFails() { + final Stream pureCheckFails() { return defaultHapiSpec("testTopic") .given() .when() @@ -104,7 +67,7 @@ final HapiSpec pureCheckFails() { } @HapiTest - final HapiSpec updateToMissingTopicFails() { + final Stream updateToMissingTopicFails() { return defaultHapiSpec("updateToMissingTopicFails") .given() .when() @@ -112,7 +75,7 @@ final HapiSpec updateToMissingTopicFails() { } @HapiTest - public HapiSpec idVariantsTreatedAsExpected() { + final Stream idVariantsTreatedAsExpected() { final var autoRenewAccount = "autoRenewAccount"; return defaultHapiSpec("idVariantsTreatedAsExpected") .given(cryptoCreate(autoRenewAccount), cryptoCreate("replacementAccount"), newKeyNamed("adminKey")) @@ -122,7 +85,7 @@ public HapiSpec idVariantsTreatedAsExpected() { } @HapiTest - final HapiSpec validateMultipleFields() { + final Stream validateMultipleFields() { byte[] longBytes = new byte[1000]; Arrays.fill(longBytes, (byte) 33); String longMemo = new String(longBytes, StandardCharsets.UTF_8); @@ -144,7 +107,7 @@ final HapiSpec validateMultipleFields() { } @HapiTest - final HapiSpec topicUpdateSigReqsEnforcedAtConsensus() { + final Stream topicUpdateSigReqsEnforcedAtConsensus() { long PAYER_BALANCE = 199_999_999_999L; Function updateTopicSignedBy = (signers) -> updateTopic("testTopic") .payingWith("payer") @@ -183,7 +146,7 @@ final HapiSpec topicUpdateSigReqsEnforcedAtConsensus() { } @HapiTest - final HapiSpec updateSubmitKeyToDiffKey() { + final Stream updateSubmitKeyToDiffKey() { return defaultHapiSpec("updateSubmitKeyToDiffKey") .given( newKeyNamed("adminKey"), @@ -197,7 +160,7 @@ final HapiSpec updateSubmitKeyToDiffKey() { } @HapiTest - final HapiSpec canRemoveSubmitKeyDuringUpdate() { + final Stream canRemoveSubmitKeyDuringUpdate() { return defaultHapiSpec("updateSubmitKeyToDiffKey") .given( newKeyNamed("adminKey"), @@ -212,7 +175,7 @@ final HapiSpec canRemoveSubmitKeyDuringUpdate() { } @HapiTest - final HapiSpec updateAdminKeyToDiffKey() { + final Stream updateAdminKeyToDiffKey() { return defaultHapiSpec("updateAdminKeyToDiffKey") .given( newKeyNamed("adminKey"), @@ -223,7 +186,7 @@ final HapiSpec updateAdminKeyToDiffKey() { } @HapiTest - final HapiSpec updateAdminKeyToEmpty() { + final Stream updateAdminKeyToEmpty() { return defaultHapiSpec("updateAdminKeyToEmpty") .given(newKeyNamed("adminKey"), createTopic("testTopic").adminKeyName("adminKey")) /* if adminKey is empty list should clear adminKey */ @@ -232,7 +195,7 @@ final HapiSpec updateAdminKeyToEmpty() { } @HapiTest - final HapiSpec updateMultipleFields() { + final Stream updateMultipleFields() { long expirationTimestamp = Instant.now().getEpochSecond() + 10000000; // more than default.autorenew // .secs=7000000 return defaultHapiSpec("updateMultipleFields") @@ -266,7 +229,7 @@ final HapiSpec updateMultipleFields() { } @HapiTest - final HapiSpec expirationTimestampIsValidated() { + final Stream expirationTimestampIsValidated() { long now = Instant.now().getEpochSecond(); return defaultHapiSpec("expirationTimestampIsValidated") .given(createTopic("testTopic").autoRenewPeriod(validAutoRenewPeriod)) @@ -282,7 +245,7 @@ final HapiSpec expirationTimestampIsValidated() { /* If admin key is not set, only expiration timestamp updates are allowed */ @HapiTest - final HapiSpec updateExpiryOnTopicWithNoAdminKey() { + final Stream updateExpiryOnTopicWithNoAdminKey() { long overlyDistantNewExpiry = Instant.now().getEpochSecond() + defaultMaxLifetime + 12_345L; long reasonableNewExpiry = Instant.now().getEpochSecond() + defaultMaxLifetime - 12_345L; return defaultHapiSpec("updateExpiryOnTopicWithNoAdminKey") @@ -294,7 +257,7 @@ final HapiSpec updateExpiryOnTopicWithNoAdminKey() { } @HapiTest - final HapiSpec clearingAdminKeyWhenAutoRenewAccountPresent() { + final Stream clearingAdminKeyWhenAutoRenewAccountPresent() { return defaultHapiSpec("clearingAdminKeyWhenAutoRenewAccountPresent") .given( newKeyNamed("adminKey"), @@ -307,7 +270,7 @@ final HapiSpec clearingAdminKeyWhenAutoRenewAccountPresent() { } @HapiTest - final HapiSpec updateSubmitKeyOnTopicWithNoAdminKeyFails() { + final Stream updateSubmitKeyOnTopicWithNoAdminKeyFails() { return defaultHapiSpec("updateSubmitKeyOnTopicWithNoAdminKeyFails") .given(newKeyNamed("submitKey"), createTopic("testTopic")) .when(updateTopic("testTopic").submitKey("submitKey").hasKnownStatus(UNAUTHORIZED)) @@ -315,7 +278,7 @@ final HapiSpec updateSubmitKeyOnTopicWithNoAdminKeyFails() { } @HapiTest - final HapiSpec feeAsExpected() { + final Stream feeAsExpected() { return defaultHapiSpec("feeAsExpected") .given( cryptoCreate("autoRenewAccount"), @@ -330,9 +293,4 @@ final HapiSpec feeAsExpected() { .via("updateTopic")) .then(validateChargedUsdWithin("updateTopic", 0.00022, 3.0)); } - - @Override - protected Logger getResultsLogger() { - return log; - } } diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/Utils.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/Utils.java index 9cf0e686c461..4a6ee7251b4b 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/Utils.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/Utils.java @@ -353,6 +353,10 @@ public static HapiSpecOperation captureOneChildCreate2MetaFor( return captureChildCreate2MetaFor(1, 0, desc, creation2, mirrorAddr, create2Addr); } + /** + * This method captures the meta information of a CREATE2 operation. It extracts the mirror and the create2 addresses. + * Additionally, it verifies the number of children + */ public static HapiSpecOperation captureChildCreate2MetaFor( final int givenNumExpectedChildren, final int givenChildOfInterest, @@ -367,6 +371,15 @@ public static HapiSpecOperation captureChildCreate2MetaFor( final var numRecords = response.getChildTransactionRecordsCount(); int numExpectedChildren = givenNumExpectedChildren; int childOfInterest = givenChildOfInterest; + + // if we use ethereum transaction for contract creation, we have one additional child record + var creation2ContractId = + lookup.getResponseRecord().getContractCreateResult().getContractID(); + if (spec.registry().hasEVMAddress(String.valueOf(creation2ContractId.getContractNum()))) { + numExpectedChildren++; + childOfInterest++; + } + if (numRecords == numExpectedChildren + 1 && TxnUtils.isEndOfStakingPeriodRecord(response.getChildTransactionRecords(0))) { // This transaction may have had a preceding record for the end-of-day diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/classiccalls/FailureCharacterizationSuite.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/classiccalls/FailureCharacterizationSuite.java index 8b687ac30c20..ea2168d7d05f 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/classiccalls/FailureCharacterizationSuite.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/classiccalls/FailureCharacterizationSuite.java @@ -107,8 +107,10 @@ import edu.umd.cs.findbugs.annotations.NonNull; import java.util.Arrays; import java.util.List; +import java.util.stream.Stream; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; +import org.junit.jupiter.api.DynamicTest; public class FailureCharacterizationSuite extends HapiSuite { private static final Logger log = LogManager.getLogger(FailureCharacterizationSuite.class); @@ -118,7 +120,7 @@ public static void main(String... args) { } @Override - public List getSpecsInSuite() { + public List> getSpecsInSuite() { return List.of(characterizeClassicFailureModes( List.of( new IsKycFailableCall(), @@ -178,7 +180,7 @@ enum CharacterizationMode { // assertions in production code, repeated string literals @SuppressWarnings({"java:S5960", "java:S1192"}) - final HapiSpec characterizeClassicFailureModes( + final Stream characterizeClassicFailureModes( @NonNull final List calls, @NonNull final CharacterizationMode characterizationMode) { if (characterizationMode == CharacterizationMode.RECORD_SNAPSHOT) { CALL_RESULTS_SNAPSHOT.begin(); diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/evm/Evm38ValidationSuite.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/evm/Evm38ValidationSuite.java index 633904f22825..6fe94b2f922f 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/evm/Evm38ValidationSuite.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/evm/Evm38ValidationSuite.java @@ -19,7 +19,7 @@ import static com.hedera.services.bdd.junit.TestTags.SMART_CONTRACT; import static com.hedera.services.bdd.spec.HapiPropertySource.asContract; import static com.hedera.services.bdd.spec.HapiPropertySource.asHexedSolidityAddress; -import static com.hedera.services.bdd.spec.HapiSpec.propertyPreservingHapiSpec; +import static com.hedera.services.bdd.spec.HapiSpec.hapiTest; import static com.hedera.services.bdd.spec.assertions.ContractFnResultAsserts.isLiteralResult; import static com.hedera.services.bdd.spec.assertions.ContractFnResultAsserts.resultWith; import static com.hedera.services.bdd.spec.assertions.TransactionRecordAsserts.recordWith; @@ -35,12 +35,16 @@ import static com.hedera.services.bdd.spec.transactions.TxnVerbs.uploadInitCode; import static com.hedera.services.bdd.spec.transactions.contract.HapiParserUtil.asHeadlongAddress; import static com.hedera.services.bdd.spec.utilops.CustomSpecAssert.allRunFor; -import static com.hedera.services.bdd.spec.utilops.UtilVerbs.ifHapiTest; -import static com.hedera.services.bdd.spec.utilops.UtilVerbs.ifNotHapiTest; import static com.hedera.services.bdd.spec.utilops.UtilVerbs.newKeyNamed; import static com.hedera.services.bdd.spec.utilops.UtilVerbs.overriding; import static com.hedera.services.bdd.spec.utilops.UtilVerbs.sourcing; import static com.hedera.services.bdd.spec.utilops.UtilVerbs.withOpContext; +import static com.hedera.services.bdd.suites.HapiSuite.DEFAULT_PAYER; +import static com.hedera.services.bdd.suites.HapiSuite.EVM_VERSION_050; +import static com.hedera.services.bdd.suites.HapiSuite.GENESIS; +import static com.hedera.services.bdd.suites.HapiSuite.ONE_HBAR; +import static com.hedera.services.bdd.suites.HapiSuite.ONE_HUNDRED_HBARS; +import static com.hedera.services.bdd.suites.HapiSuite.TOKEN_TREASURY; import static com.hedera.services.bdd.suites.contract.Utils.FunctionType.FUNCTION; import static com.hedera.services.bdd.suites.contract.Utils.asAddress; import static com.hedera.services.bdd.suites.contract.Utils.captureOneChildCreate2MetaFor; @@ -56,34 +60,33 @@ import com.google.protobuf.ByteString; import com.hedera.services.bdd.junit.HapiTest; -import com.hedera.services.bdd.junit.HapiTestSuite; +import com.hedera.services.bdd.junit.HapiTestLifecycle; +import com.hedera.services.bdd.junit.support.SpecManager; import com.hedera.services.bdd.spec.HapiPropertySource; -import com.hedera.services.bdd.spec.HapiSpec; import com.hedera.services.bdd.spec.assertions.ContractFnResultAsserts; import com.hedera.services.bdd.spec.transactions.contract.HapiParserUtil; -import com.hedera.services.bdd.suites.HapiSuite; import com.hederahashgraph.api.proto.java.AccountID; import com.hederahashgraph.api.proto.java.TokenSupplyType; +import edu.umd.cs.findbugs.annotations.NonNull; import java.math.BigInteger; -import java.util.List; import java.util.Random; import java.util.concurrent.atomic.AtomicReference; +import java.util.stream.Stream; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.apache.tuweni.bytes.Bytes; import org.hyperledger.besu.crypto.Hash; +import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.DynamicTest; import org.junit.jupiter.api.Tag; -@HapiTestSuite @Tag(SMART_CONTRACT) -public class Evm38ValidationSuite extends HapiSuite { - +@HapiTestLifecycle +public class Evm38ValidationSuite { private static final Logger LOG = LogManager.getLogger(Evm38ValidationSuite.class); private static final String EVM_VERSION_PROPERTY = "contracts.evm.version"; - private static final String EVM_ALLOW_CALLS_TO_NON_CONTRACT_ACCOUNTS = - "contracts.evm.allowCallsToNonContractAccounts"; - private static final String DYNAMIC_EVM_PROPERTY = "contracts.evm.version.dynamic"; private static final String EVM_VERSION_038 = "v0.38"; private static final String CREATE_TRIVIAL = "CreateTrivial"; private static final String BALANCE_OF = "balanceOf"; @@ -95,50 +98,27 @@ public class Evm38ValidationSuite extends HapiSuite { private static final String BENEFICIARY = "beneficiary"; private static final String SIMPLE_UPDATE_CONTRACT = "SimpleUpdate"; - public static void main(String... args) { - new Evm38ValidationSuite().runSuiteSync(); + @BeforeAll + static void beforeAll(@NonNull final SpecManager specManager) throws Throwable { + specManager.setup(overriding(EVM_VERSION_PROPERTY, EVM_VERSION_038)); } - @Override - public List getSpecsInSuite() { - return List.of( - invalidContractCall(), - cannotSendValueToTokenAccount(), - verifiesExistenceOfAccountsAndContracts(), - verifiesExistenceForCallCodeOperation(), - verifiesExistenceForCallOperation(), - verifiesExistenceForCallOperationInternal(), - verifiesExistenceForDelegateCallOperation(), - verifiesExistenceForExtCodeOperation(), - verifiesExistenceForExtCodeSize(), - verifiesExistenceForExtCodeHash(), - verifiesExistenceForStaticCall(), - canInternallyCallAliasedAddressesOnlyViaCreate2Address(), - callingDestructedContractReturnsStatusDeleted(), - factoryAndSelfDestructInConstructorContract()); + @AfterAll + static void afterAll(@NonNull final SpecManager specManager) throws Throwable { + specManager.teardown(overriding(EVM_VERSION_PROPERTY, EVM_VERSION_050)); } @HapiTest - HapiSpec invalidContractCall() { + final Stream invalidContractCall() { final var function = getABIFor(FUNCTION, "getIndirect", CREATE_TRIVIAL); - return propertyPreservingHapiSpec("InvalidContract") - .preserving(EVM_VERSION_PROPERTY, DYNAMIC_EVM_PROPERTY) - .given( - overriding(DYNAMIC_EVM_PROPERTY, "true"), - overriding(EVM_VERSION_PROPERTY, EVM_VERSION_038), - withOpContext( - (spec, ctxLog) -> spec.registry().saveContractId("invalid", asContract("0.0.5555")))) - .when() - .then( - ifHapiTest( - contractCallWithFunctionAbi("invalid", function).hasKnownStatus(INVALID_CONTRACT_ID)), - ifNotHapiTest( - contractCallWithFunctionAbi("invalid", function).hasPrecheck(INVALID_CONTRACT_ID))); + return hapiTest( + withOpContext((spec, ctxLog) -> spec.registry().saveContractId("invalid", asContract("0.0.5555"))), + contractCallWithFunctionAbi("invalid", function).hasKnownStatus(INVALID_CONTRACT_ID)); } @HapiTest - private HapiSpec cannotSendValueToTokenAccount() { + final Stream cannotSendValueToTokenAccount() { final var multiKey = "multiKey"; final var nonFungibleToken = "NFT"; final var contract = "ManyChildren"; @@ -146,339 +126,272 @@ private HapiSpec cannotSendValueToTokenAccount() { final var externalViolation = "external"; final AtomicReference tokenMirrorAddr = new AtomicReference<>(); - return propertyPreservingHapiSpec("cannotSendValueToTokenAccount") - .preserving(EVM_VERSION_PROPERTY, DYNAMIC_EVM_PROPERTY) - .given( - overriding(DYNAMIC_EVM_PROPERTY, "true"), - overriding(EVM_VERSION_PROPERTY, EVM_VERSION_038), - newKeyNamed(multiKey), - cryptoCreate(TOKEN_TREASURY).balance(ONE_HUNDRED_HBARS), - tokenCreate(nonFungibleToken) - .supplyType(TokenSupplyType.INFINITE) - .tokenType(NON_FUNGIBLE_UNIQUE) - .treasury(TOKEN_TREASURY) - .initialSupply(0) - .supplyKey(multiKey) - .exposingCreatedIdTo(idLit -> - tokenMirrorAddr.set(asHexedSolidityAddress(HapiPropertySource.asToken(idLit))))) - .when( - uploadInitCode(contract), - contractCreate(contract), - sourcing(() -> contractCall( - contract, "sendSomeValueTo", asHeadlongAddress(tokenMirrorAddr.get())) - .sending(ONE_HBAR) - .payingWith(TOKEN_TREASURY) - .via(internalViolation) - .hasKnownStatus(CONTRACT_REVERT_EXECUTED)), - sourcing((() -> contractCall(tokenMirrorAddr.get()) - .sending(1L) - .payingWith(TOKEN_TREASURY) - .refusingEthConversion() - .via(externalViolation) - .hasKnownStatus(LOCAL_CALL_MODIFICATION_EXCEPTION)))) - .then( - getTxnRecord(internalViolation).hasPriority(recordWith().feeGreaterThan(0L)), - getTxnRecord(externalViolation).hasPriority(recordWith().feeGreaterThan(0L))); + return hapiTest( + newKeyNamed(multiKey), + cryptoCreate(TOKEN_TREASURY).balance(ONE_HUNDRED_HBARS), + tokenCreate(nonFungibleToken) + .supplyType(TokenSupplyType.INFINITE) + .tokenType(NON_FUNGIBLE_UNIQUE) + .treasury(TOKEN_TREASURY) + .initialSupply(0) + .supplyKey(multiKey) + .exposingCreatedIdTo(idLit -> + tokenMirrorAddr.set(asHexedSolidityAddress(HapiPropertySource.asToken(idLit)))), + uploadInitCode(contract), + contractCreate(contract), + sourcing(() -> contractCall(contract, "sendSomeValueTo", asHeadlongAddress(tokenMirrorAddr.get())) + .sending(ONE_HBAR) + .payingWith(TOKEN_TREASURY) + .via(internalViolation) + .hasKnownStatus(CONTRACT_REVERT_EXECUTED)), + sourcing((() -> contractCall(tokenMirrorAddr.get()) + .sending(1L) + .payingWith(TOKEN_TREASURY) + .refusingEthConversion() + .via(externalViolation) + .hasKnownStatus(LOCAL_CALL_MODIFICATION_EXCEPTION))), + getTxnRecord(internalViolation).hasPriority(recordWith().feeGreaterThan(0L)), + getTxnRecord(externalViolation).hasPriority(recordWith().feeGreaterThan(0L))); } @HapiTest - HapiSpec verifiesExistenceOfAccountsAndContracts() { + final Stream verifiesExistenceOfAccountsAndContracts() { final var contract = "BalanceChecker"; final var BALANCE = 10L; final var ACCOUNT = "test"; final var INVALID_ADDRESS = "0x0000000000000000000000000000000000123456"; - return propertyPreservingHapiSpec("verifiesExistenceOfAccountsAndContracts") - .preserving(EVM_VERSION_PROPERTY, DYNAMIC_EVM_PROPERTY) - .given( - overriding(DYNAMIC_EVM_PROPERTY, "true"), - overriding(EVM_VERSION_PROPERTY, EVM_VERSION_038), - cryptoCreate("test").balance(BALANCE), - uploadInitCode(contract), - contractCreate(contract)) - .when() - .then( - contractCall(contract, BALANCE_OF, asHeadlongAddress(INVALID_ADDRESS)) - .hasKnownStatus(INVALID_SOLIDITY_ADDRESS), - contractCallLocal(contract, BALANCE_OF, asHeadlongAddress(INVALID_ADDRESS)) - .hasAnswerOnlyPrecheck(INVALID_SOLIDITY_ADDRESS), - withOpContext((spec, opLog) -> { - final var id = spec.registry().getAccountID(ACCOUNT); - final var contractID = spec.registry().getContractId(contract); - - final var solidityAddress = HapiParserUtil.asHeadlongAddress(asAddress(id)); - final var contractAddress = asHeadlongAddress(asHexedSolidityAddress(contractID)); - - final var call = contractCall(contract, BALANCE_OF, solidityAddress) - .via("callRecord"); - - final var callRecord = getTxnRecord("callRecord") - .hasPriority(recordWith() - .contractCallResult(resultWith() - .resultThruAbi( - getABIFor(FUNCTION, BALANCE_OF, contract), - isLiteralResult( - new Object[] {BigInteger.valueOf(BALANCE)})))); - - final var callLocal = contractCallLocal(contract, BALANCE_OF, solidityAddress) - .has(ContractFnResultAsserts.resultWith() - .resultThruAbi( - getABIFor(FUNCTION, BALANCE_OF, contract), - ContractFnResultAsserts.isLiteralResult( - new Object[] {BigInteger.valueOf(BALANCE)}))); - - final var contractCallLocal = contractCallLocal(contract, BALANCE_OF, contractAddress) - .has(ContractFnResultAsserts.resultWith() + return hapiTest( + cryptoCreate("test").balance(BALANCE), + uploadInitCode(contract), + contractCreate(contract), + contractCall(contract, BALANCE_OF, asHeadlongAddress(INVALID_ADDRESS)) + .hasKnownStatus(INVALID_SOLIDITY_ADDRESS), + contractCallLocal(contract, BALANCE_OF, asHeadlongAddress(INVALID_ADDRESS)) + .hasAnswerOnlyPrecheck(INVALID_SOLIDITY_ADDRESS), + withOpContext((spec, opLog) -> { + final var id = spec.registry().getAccountID(ACCOUNT); + final var contractID = spec.registry().getContractId(contract); + + final var solidityAddress = HapiParserUtil.asHeadlongAddress(asAddress(id)); + final var contractAddress = asHeadlongAddress(asHexedSolidityAddress(contractID)); + + final var call = + contractCall(contract, BALANCE_OF, solidityAddress).via("callRecord"); + + final var callRecord = getTxnRecord("callRecord") + .hasPriority(recordWith() + .contractCallResult(resultWith() .resultThruAbi( getABIFor(FUNCTION, BALANCE_OF, contract), - ContractFnResultAsserts.isLiteralResult( - new Object[] {BigInteger.valueOf(0)}))); - - allRunFor(spec, call, callLocal, callRecord, contractCallLocal); - })); + isLiteralResult(new Object[] {BigInteger.valueOf(BALANCE)})))); + + final var callLocal = contractCallLocal(contract, BALANCE_OF, solidityAddress) + .has(ContractFnResultAsserts.resultWith() + .resultThruAbi( + getABIFor(FUNCTION, BALANCE_OF, contract), + ContractFnResultAsserts.isLiteralResult( + new Object[] {BigInteger.valueOf(BALANCE)}))); + + final var contractCallLocal = contractCallLocal(contract, BALANCE_OF, contractAddress) + .has(ContractFnResultAsserts.resultWith() + .resultThruAbi( + getABIFor(FUNCTION, BALANCE_OF, contract), + ContractFnResultAsserts.isLiteralResult( + new Object[] {BigInteger.valueOf(0)}))); + + allRunFor(spec, call, callLocal, callRecord, contractCallLocal); + })); } @HapiTest - HapiSpec verifiesExistenceForCallCodeOperation() { + final Stream verifiesExistenceForCallCodeOperation() { final var contract = "CallOperationsChecker"; final var INVALID_ADDRESS = "0x0000000000000000000000000000000000123456"; - return propertyPreservingHapiSpec("verifiesExistenceForCallCodeOperation") - .preserving(EVM_VERSION_PROPERTY, DYNAMIC_EVM_PROPERTY) - .given( - overriding(DYNAMIC_EVM_PROPERTY, "true"), - overriding(EVM_VERSION_PROPERTY, EVM_VERSION_038), - uploadInitCode(contract), - contractCreate(contract)) - .when() - .then( - contractCall(contract, "callCode", asHeadlongAddress(INVALID_ADDRESS)) - .hasKnownStatus(INVALID_SOLIDITY_ADDRESS), - withOpContext((spec, opLog) -> { - final var id = spec.registry().getAccountID(DEFAULT_PAYER); - final var solidityAddress = HapiPropertySource.asHexedSolidityAddress(id); - - final var contractCall = contractCall( - contract, "callCode", asHeadlongAddress(solidityAddress)) - .hasKnownStatus(SUCCESS); - - allRunFor(spec, contractCall); - })); + return hapiTest( + uploadInitCode(contract), + contractCreate(contract), + contractCall(contract, "callCode", asHeadlongAddress(INVALID_ADDRESS)) + .hasKnownStatus(INVALID_SOLIDITY_ADDRESS), + withOpContext((spec, opLog) -> { + final var id = spec.registry().getAccountID(DEFAULT_PAYER); + final var solidityAddress = HapiPropertySource.asHexedSolidityAddress(id); + + final var contractCall = contractCall(contract, "callCode", asHeadlongAddress(solidityAddress)) + .hasKnownStatus(SUCCESS); + + allRunFor(spec, contractCall); + })); } @HapiTest - HapiSpec verifiesExistenceForCallOperation() { + final Stream verifiesExistenceForCallOperation() { final var contract = "CallOperationsChecker"; final var INVALID_ADDRESS = "0x0000000000000000000000000000000000123456"; final var ACCOUNT = "account"; final var EXPECTED_BALANCE = 10; - return propertyPreservingHapiSpec("verifiesExistenceForCallOperation") - .preserving(EVM_VERSION_PROPERTY, DYNAMIC_EVM_PROPERTY) - .given( - overriding(DYNAMIC_EVM_PROPERTY, "true"), - overriding(EVM_VERSION_PROPERTY, EVM_VERSION_038), - cryptoCreate(ACCOUNT).balance(0L), - uploadInitCode(contract), - contractCreate(contract)) - .when() - .then( - contractCall(contract, "call", asHeadlongAddress(INVALID_ADDRESS)) - .hasKnownStatus(INVALID_SOLIDITY_ADDRESS), - withOpContext((spec, opLog) -> { - final var id = spec.registry().getAccountID(ACCOUNT); - - final var contractCall = contractCall( - contract, "call", HapiParserUtil.asHeadlongAddress(asAddress(id))) - .sending(EXPECTED_BALANCE); - - final var balance = getAccountBalance(ACCOUNT).hasTinyBars(EXPECTED_BALANCE); - - allRunFor(spec, contractCall, balance); - })); + return hapiTest( + cryptoCreate(ACCOUNT).balance(0L), + uploadInitCode(contract), + contractCreate(contract), + contractCall(contract, "call", asHeadlongAddress(INVALID_ADDRESS)) + .hasKnownStatus(INVALID_SOLIDITY_ADDRESS), + withOpContext((spec, opLog) -> { + final var id = spec.registry().getAccountID(ACCOUNT); + + final var contractCall = contractCall( + contract, "call", HapiParserUtil.asHeadlongAddress(asAddress(id))) + .sending(EXPECTED_BALANCE); + + final var balance = getAccountBalance(ACCOUNT).hasTinyBars(EXPECTED_BALANCE); + + allRunFor(spec, contractCall, balance); + })); } @HapiTest - HapiSpec verifiesExistenceForCallOperationInternal() { + final Stream verifiesExistenceForCallOperationInternal() { final var contract = "CallingContract"; final var INVALID_ADDRESS = "0x0000000000000000000000000000000000123456"; - return propertyPreservingHapiSpec("verifiesExistenceForCallOperationInternal") - .preserving(EVM_VERSION_PROPERTY, DYNAMIC_EVM_PROPERTY) - .given( - overriding(DYNAMIC_EVM_PROPERTY, "true"), - overriding(EVM_VERSION_PROPERTY, EVM_VERSION_038), - uploadInitCode(contract), - contractCreate(contract)) - .when( - contractCall(contract, "setVar1", BigInteger.valueOf(35)), - contractCallLocal(contract, "getVar1").logged(), - contractCall( - contract, - "callContract", - asHeadlongAddress(INVALID_ADDRESS), - BigInteger.valueOf(222)) - .hasKnownStatus(INVALID_SOLIDITY_ADDRESS)) - .then(contractCallLocal(contract, "getVar1").logged()); + return hapiTest( + uploadInitCode(contract), + contractCreate(contract), + contractCall(contract, "setVar1", BigInteger.valueOf(35)), + contractCallLocal(contract, "getVar1").logged(), + contractCall(contract, "callContract", asHeadlongAddress(INVALID_ADDRESS), BigInteger.valueOf(222)) + .hasKnownStatus(INVALID_SOLIDITY_ADDRESS), + contractCallLocal(contract, "getVar1").logged()); } @HapiTest - HapiSpec verifiesExistenceForDelegateCallOperation() { + final Stream verifiesExistenceForDelegateCallOperation() { final var contract = "CallOperationsChecker"; final var INVALID_ADDRESS = "0x0000000000000000000000000000000000123456"; - return propertyPreservingHapiSpec("verifiesExistenceForDelegateCallOperation") - .preserving(EVM_VERSION_PROPERTY, DYNAMIC_EVM_PROPERTY) - .given( - overriding(DYNAMIC_EVM_PROPERTY, "true"), - overriding(EVM_VERSION_PROPERTY, EVM_VERSION_038), - uploadInitCode(contract), - contractCreate(contract)) - .when() - .then( - contractCall(contract, "delegateCall", asHeadlongAddress(INVALID_ADDRESS)) - .hasKnownStatus(INVALID_SOLIDITY_ADDRESS), - withOpContext((spec, opLog) -> { - final var id = spec.registry().getAccountID(DEFAULT_PAYER); - final var solidityAddress = HapiPropertySource.asHexedSolidityAddress(id); - - final var contractCall = contractCall( - contract, "delegateCall", asHeadlongAddress(solidityAddress)) - .hasKnownStatus(SUCCESS); - - allRunFor(spec, contractCall); - })); + return hapiTest( + uploadInitCode(contract), + contractCreate(contract), + contractCall(contract, "delegateCall", asHeadlongAddress(INVALID_ADDRESS)) + .hasKnownStatus(INVALID_SOLIDITY_ADDRESS), + withOpContext((spec, opLog) -> { + final var id = spec.registry().getAccountID(DEFAULT_PAYER); + final var solidityAddress = HapiPropertySource.asHexedSolidityAddress(id); + + final var contractCall = contractCall(contract, "delegateCall", asHeadlongAddress(solidityAddress)) + .hasKnownStatus(SUCCESS); + + allRunFor(spec, contractCall); + })); } @SuppressWarnings("java:S5960") @HapiTest - HapiSpec verifiesExistenceForExtCodeOperation() { + final Stream verifiesExistenceForExtCodeOperation() { final var contract = "ExtCodeOperationsChecker"; final var invalidAddress = "0x0000000000000000000000000000000000123456"; final var emptyBytecode = ByteString.EMPTY; final var codeCopyOf = "codeCopyOf"; final var account = "account"; - return propertyPreservingHapiSpec("verifiesExistenceForExtCodeOperation") - .preserving(EVM_VERSION_PROPERTY, DYNAMIC_EVM_PROPERTY) - .given( - overriding(DYNAMIC_EVM_PROPERTY, "true"), - overriding(EVM_VERSION_PROPERTY, EVM_VERSION_038), - cryptoCreate(account), - uploadInitCode(contract), - contractCreate(contract)) - .when() - .then( - contractCall(contract, codeCopyOf, asHeadlongAddress(invalidAddress)) - .hasKnownStatus(INVALID_SOLIDITY_ADDRESS), - contractCallLocal(contract, codeCopyOf, asHeadlongAddress(invalidAddress)) - .hasAnswerOnlyPrecheck(INVALID_SOLIDITY_ADDRESS), - withOpContext((spec, opLog) -> { - final var accountID = spec.registry().getAccountID(account); - final var contractID = spec.registry().getContractId(contract); - final var accountSolidityAddress = asHexedSolidityAddress(accountID); - final var contractAddress = asHexedSolidityAddress(contractID); - - final var call = contractCall( - contract, codeCopyOf, asHeadlongAddress(accountSolidityAddress)) - .via("callRecord"); - final var callRecord = getTxnRecord("callRecord"); - - final var accountCodeCallLocal = contractCallLocal( - contract, codeCopyOf, asHeadlongAddress(accountSolidityAddress)) - .saveResultTo("accountCode"); - - final var contractCodeCallLocal = contractCallLocal( - contract, codeCopyOf, asHeadlongAddress(contractAddress)) - .saveResultTo("contractCode"); - - final var getBytecodeCall = - getContractBytecode(contract).saveResultTo("contractGetBytecode"); - - allRunFor( - spec, - call, - callRecord, - accountCodeCallLocal, - contractCodeCallLocal, - getBytecodeCall); - - final var recordResult = - callRecord.getResponseRecord().getContractCallResult(); - final var accountCode = spec.registry().getBytes("accountCode"); - final var contractCode = spec.registry().getBytes("contractCode"); - final var getBytecode = spec.registry().getBytes("contractGetBytecode"); - - Assertions.assertEquals(emptyBytecode, recordResult.getContractCallResult()); - Assertions.assertArrayEquals(emptyBytecode.toByteArray(), accountCode); - Assertions.assertArrayEquals(getBytecode, contractCode); - })); + return hapiTest( + cryptoCreate(account), + uploadInitCode(contract), + contractCreate(contract), + contractCall(contract, codeCopyOf, asHeadlongAddress(invalidAddress)) + .hasKnownStatus(INVALID_SOLIDITY_ADDRESS), + contractCallLocal(contract, codeCopyOf, asHeadlongAddress(invalidAddress)) + .hasAnswerOnlyPrecheck(INVALID_SOLIDITY_ADDRESS), + withOpContext((spec, opLog) -> { + final var accountID = spec.registry().getAccountID(account); + final var contractID = spec.registry().getContractId(contract); + final var accountSolidityAddress = asHexedSolidityAddress(accountID); + final var contractAddress = asHexedSolidityAddress(contractID); + + final var call = contractCall(contract, codeCopyOf, asHeadlongAddress(accountSolidityAddress)) + .via("callRecord"); + final var callRecord = getTxnRecord("callRecord"); + + final var accountCodeCallLocal = contractCallLocal( + contract, codeCopyOf, asHeadlongAddress(accountSolidityAddress)) + .saveResultTo("accountCode"); + + final var contractCodeCallLocal = contractCallLocal( + contract, codeCopyOf, asHeadlongAddress(contractAddress)) + .saveResultTo("contractCode"); + + final var getBytecodeCall = getContractBytecode(contract).saveResultTo("contractGetBytecode"); + + allRunFor(spec, call, callRecord, accountCodeCallLocal, contractCodeCallLocal, getBytecodeCall); + + final var recordResult = callRecord.getResponseRecord().getContractCallResult(); + final var accountCode = spec.registry().getBytes("accountCode"); + final var contractCode = spec.registry().getBytes("contractCode"); + final var getBytecode = spec.registry().getBytes("contractGetBytecode"); + + Assertions.assertEquals(emptyBytecode, recordResult.getContractCallResult()); + Assertions.assertArrayEquals(emptyBytecode.toByteArray(), accountCode); + Assertions.assertArrayEquals(getBytecode, contractCode); + })); } @SuppressWarnings("java:S5960") @HapiTest - HapiSpec verifiesExistenceForExtCodeSize() { + final Stream verifiesExistenceForExtCodeSize() { final var contract = "ExtCodeOperationsChecker"; final var invalidAddress = "0x0000000000000000000000000000000000123456"; final var sizeOf = "sizeOf"; final var account = "account"; - return propertyPreservingHapiSpec("verifiesExistenceForExtCodeSize") - .preserving(EVM_VERSION_PROPERTY, DYNAMIC_EVM_PROPERTY) - .given( - overriding(DYNAMIC_EVM_PROPERTY, "true"), - overriding(EVM_VERSION_PROPERTY, EVM_VERSION_038), - uploadInitCode(contract), - contractCreate(contract), - cryptoCreate(account)) - .when() - .then( - contractCall(contract, sizeOf, asHeadlongAddress(invalidAddress)) - .hasKnownStatus(INVALID_SOLIDITY_ADDRESS), - contractCallLocal(contract, sizeOf, asHeadlongAddress(invalidAddress)) - .hasAnswerOnlyPrecheck(INVALID_SOLIDITY_ADDRESS), - withOpContext((spec, opLog) -> { - final var accountID = spec.registry().getAccountID(account); - final var contractID = spec.registry().getContractId(contract); - final var accountSolidityAddress = asHexedSolidityAddress(accountID); - final var contractAddress = asHexedSolidityAddress(contractID); - - final var call = contractCall(contract, sizeOf, asHeadlongAddress(accountSolidityAddress)) - .via("callRecord"); - - final var callRecord = getTxnRecord("callRecord") - .hasPriority(recordWith() - .contractCallResult(resultWith() - .resultThruAbi( - getABIFor(FUNCTION, sizeOf, contract), - isLiteralResult(new Object[] {BigInteger.valueOf(0)})))); - - final var accountCodeSizeCallLocal = contractCallLocal( - contract, sizeOf, asHeadlongAddress(accountSolidityAddress)) - .has(ContractFnResultAsserts.resultWith() + return hapiTest( + uploadInitCode(contract), + contractCreate(contract), + cryptoCreate(account), + contractCall(contract, sizeOf, asHeadlongAddress(invalidAddress)) + .hasKnownStatus(INVALID_SOLIDITY_ADDRESS), + contractCallLocal(contract, sizeOf, asHeadlongAddress(invalidAddress)) + .hasAnswerOnlyPrecheck(INVALID_SOLIDITY_ADDRESS), + withOpContext((spec, opLog) -> { + final var accountID = spec.registry().getAccountID(account); + final var contractID = spec.registry().getContractId(contract); + final var accountSolidityAddress = asHexedSolidityAddress(accountID); + final var contractAddress = asHexedSolidityAddress(contractID); + + final var call = contractCall(contract, sizeOf, asHeadlongAddress(accountSolidityAddress)) + .via("callRecord"); + + final var callRecord = getTxnRecord("callRecord") + .hasPriority(recordWith() + .contractCallResult(resultWith() .resultThruAbi( getABIFor(FUNCTION, sizeOf, contract), - ContractFnResultAsserts.isLiteralResult( - new Object[] {BigInteger.valueOf(0)}))); + isLiteralResult(new Object[] {BigInteger.valueOf(0)})))); + + final var accountCodeSizeCallLocal = contractCallLocal( + contract, sizeOf, asHeadlongAddress(accountSolidityAddress)) + .has(ContractFnResultAsserts.resultWith() + .resultThruAbi( + getABIFor(FUNCTION, sizeOf, contract), + ContractFnResultAsserts.isLiteralResult( + new Object[] {BigInteger.valueOf(0)}))); - final var getBytecode = - getContractBytecode(contract).saveResultTo("contractBytecode"); + final var getBytecode = getContractBytecode(contract).saveResultTo("contractBytecode"); - final var contractCodeSize = contractCallLocal( - contract, sizeOf, asHeadlongAddress(contractAddress)) - .saveResultTo("contractCodeSize"); + final var contractCodeSize = contractCallLocal(contract, sizeOf, asHeadlongAddress(contractAddress)) + .saveResultTo("contractCodeSize"); - allRunFor(spec, call, callRecord, accountCodeSizeCallLocal, getBytecode, contractCodeSize); + allRunFor(spec, call, callRecord, accountCodeSizeCallLocal, getBytecode, contractCodeSize); - final var contractCodeSizeResult = spec.registry().getBytes("contractCodeSize"); - final var contractBytecode = spec.registry().getBytes("contractBytecode"); + final var contractCodeSizeResult = spec.registry().getBytes("contractCodeSize"); + final var contractBytecode = spec.registry().getBytes("contractBytecode"); - Assertions.assertEquals( - BigInteger.valueOf(contractBytecode.length), - new BigInteger(contractCodeSizeResult)); - })); + Assertions.assertEquals( + BigInteger.valueOf(contractBytecode.length), new BigInteger(contractCodeSizeResult)); + })); } @SuppressWarnings("java:S5960") @HapiTest - HapiSpec verifiesExistenceForExtCodeHash() { + final Stream verifiesExistenceForExtCodeHash() { final var contract = "ExtCodeOperationsChecker"; final var invalidAddress = "0x0000000000000000000000000000000000123456"; final var expectedAccountHash = @@ -486,94 +399,77 @@ HapiSpec verifiesExistenceForExtCodeHash() { final var hashOf = "hashOf"; final String account = "account"; - return propertyPreservingHapiSpec("verifiesExistenceForExtCodeHash") - .preserving(EVM_VERSION_PROPERTY, DYNAMIC_EVM_PROPERTY) - .given( - overriding(DYNAMIC_EVM_PROPERTY, "true"), - overriding(EVM_VERSION_PROPERTY, EVM_VERSION_038), - uploadInitCode(contract), - contractCreate(contract), - cryptoCreate(account)) - .when() - .then( - contractCall(contract, hashOf, asHeadlongAddress(invalidAddress)) - .hasKnownStatus(INVALID_SOLIDITY_ADDRESS), - contractCallLocal(contract, hashOf, asHeadlongAddress(invalidAddress)) - .hasAnswerOnlyPrecheck(INVALID_SOLIDITY_ADDRESS), - withOpContext((spec, opLog) -> { - final var accountID = spec.registry().getAccountID(account); - final var contractID = spec.registry().getContractId(contract); - final var accountSolidityAddress = asHexedSolidityAddress(accountID); - final var contractAddress = asHexedSolidityAddress(contractID); - - final var call = contractCall(contract, hashOf, asHeadlongAddress(accountSolidityAddress)) - .via("callRecord"); - final var callRecord = getTxnRecord("callRecord"); - - final var accountCodeHashCallLocal = contractCallLocal( - contract, hashOf, asHeadlongAddress(accountSolidityAddress)) - .saveResultTo("accountCodeHash"); - - final var contractCodeHash = contractCallLocal( - contract, hashOf, asHeadlongAddress(contractAddress)) - .saveResultTo("contractCodeHash"); - - final var getBytecode = - getContractBytecode(contract).saveResultTo("contractBytecode"); - - allRunFor(spec, call, callRecord, accountCodeHashCallLocal, contractCodeHash, getBytecode); - - final var recordResult = - callRecord.getResponseRecord().getContractCallResult(); - final var accountCodeHash = spec.registry().getBytes("accountCodeHash"); - - final var contractCodeResult = spec.registry().getBytes("contractCodeHash"); - final var contractBytecode = spec.registry().getBytes("contractBytecode"); - final var expectedContractCodeHash = ByteString.copyFrom( - Hash.keccak256(Bytes.of(contractBytecode)) - .toArray()) - .toByteArray(); - - Assertions.assertEquals(expectedAccountHash, recordResult.getContractCallResult()); - Assertions.assertArrayEquals(expectedAccountHash.toByteArray(), accountCodeHash); - Assertions.assertArrayEquals(expectedContractCodeHash, contractCodeResult); - })); + return hapiTest( + uploadInitCode(contract), + contractCreate(contract), + cryptoCreate(account), + contractCall(contract, hashOf, asHeadlongAddress(invalidAddress)) + .hasKnownStatus(INVALID_SOLIDITY_ADDRESS), + contractCallLocal(contract, hashOf, asHeadlongAddress(invalidAddress)) + .hasAnswerOnlyPrecheck(INVALID_SOLIDITY_ADDRESS), + withOpContext((spec, opLog) -> { + final var accountID = spec.registry().getAccountID(account); + final var contractID = spec.registry().getContractId(contract); + final var accountSolidityAddress = asHexedSolidityAddress(accountID); + final var contractAddress = asHexedSolidityAddress(contractID); + + final var call = contractCall(contract, hashOf, asHeadlongAddress(accountSolidityAddress)) + .via("callRecord"); + final var callRecord = getTxnRecord("callRecord"); + + final var accountCodeHashCallLocal = contractCallLocal( + contract, hashOf, asHeadlongAddress(accountSolidityAddress)) + .saveResultTo("accountCodeHash"); + + final var contractCodeHash = contractCallLocal(contract, hashOf, asHeadlongAddress(contractAddress)) + .saveResultTo("contractCodeHash"); + + final var getBytecode = getContractBytecode(contract).saveResultTo("contractBytecode"); + + allRunFor(spec, call, callRecord, accountCodeHashCallLocal, contractCodeHash, getBytecode); + + final var recordResult = callRecord.getResponseRecord().getContractCallResult(); + final var accountCodeHash = spec.registry().getBytes("accountCodeHash"); + + final var contractCodeResult = spec.registry().getBytes("contractCodeHash"); + final var contractBytecode = spec.registry().getBytes("contractBytecode"); + final var expectedContractCodeHash = ByteString.copyFrom( + Hash.keccak256(Bytes.of(contractBytecode)).toArray()) + .toByteArray(); + + Assertions.assertEquals(expectedAccountHash, recordResult.getContractCallResult()); + Assertions.assertArrayEquals(expectedAccountHash.toByteArray(), accountCodeHash); + Assertions.assertArrayEquals(expectedContractCodeHash, contractCodeResult); + })); } @HapiTest - HapiSpec verifiesExistenceForStaticCall() { + final Stream verifiesExistenceForStaticCall() { final var contract = "CallOperationsChecker"; final var INVALID_ADDRESS = "0x0000000000000000000000000000000000123456"; - return propertyPreservingHapiSpec("verifiesExistenceForStaticCall") - .preserving(EVM_VERSION_PROPERTY, DYNAMIC_EVM_PROPERTY) - .given( - overriding(DYNAMIC_EVM_PROPERTY, "true"), - overriding(EVM_VERSION_PROPERTY, EVM_VERSION_038), - uploadInitCode(contract), - contractCreate(contract)) - .when() - .then( - contractCall(contract, STATIC_CALL, asHeadlongAddress(INVALID_ADDRESS)) - .hasKnownStatus(INVALID_SOLIDITY_ADDRESS), - withOpContext((spec, opLog) -> { - final var id = spec.registry().getAccountID(DEFAULT_PAYER); - final var solidityAddress = HapiPropertySource.asHexedSolidityAddress(id); - - final var contractCall = contractCall( - contract, STATIC_CALL, asHeadlongAddress(solidityAddress)) - .hasKnownStatus(SUCCESS); - - final var contractCallLocal = - contractCallLocal(contract, STATIC_CALL, asHeadlongAddress(solidityAddress)); - - allRunFor(spec, contractCall, contractCallLocal); - })); + return hapiTest( + uploadInitCode(contract), + contractCreate(contract), + contractCall(contract, STATIC_CALL, asHeadlongAddress(INVALID_ADDRESS)) + .hasKnownStatus(INVALID_SOLIDITY_ADDRESS), + withOpContext((spec, opLog) -> { + final var id = spec.registry().getAccountID(DEFAULT_PAYER); + final var solidityAddress = HapiPropertySource.asHexedSolidityAddress(id); + + final var contractCall = contractCall(contract, STATIC_CALL, asHeadlongAddress(solidityAddress)) + .hasKnownStatus(SUCCESS); + + final var contractCallLocal = + contractCallLocal(contract, STATIC_CALL, asHeadlongAddress(solidityAddress)); + + allRunFor(spec, contractCall, contractCallLocal); + })); } @SuppressWarnings("java:S5669") @HapiTest - private HapiSpec canInternallyCallAliasedAddressesOnlyViaCreate2Address() { + final Stream canInternallyCallAliasedAddressesOnlyViaCreate2Address() { final var contract = "AddressValueRet"; final var aliasCall = "aliasCall"; final var mirrorCall = "mirrorCall"; @@ -586,40 +482,35 @@ private HapiSpec canInternallyCallAliasedAddressesOnlyViaCreate2Address() { final var salt = new byte[32]; new Random().nextBytes(salt); - return propertyPreservingHapiSpec("canInternallyCallAliasedAddressesOnlyViaCreate2Address") - .preserving(EVM_VERSION_PROPERTY, DYNAMIC_EVM_PROPERTY) - .given( - overriding(DYNAMIC_EVM_PROPERTY, "true"), - overriding(EVM_VERSION_PROPERTY, EVM_VERSION_038), - uploadInitCode(contract), - contractCreate(contract).payingWith(GENESIS), - contractCall(contract, "createReturner", salt) - .payingWith(GENESIS) - .gas(4_000_000L) - .via(CREATE_2_TXN), - captureOneChildCreate2MetaFor(RETURNER, CREATE_2_TXN, mirrorAddr, aliasAddr)) - .when( - sourcing(() -> contractCallLocal(contract, CALL_RETURNER, asHeadlongAddress(mirrorAddr.get())) - .hasAnswerOnlyPrecheck(INVALID_SOLIDITY_ADDRESS) - .payingWith(GENESIS) - .exposingTypedResultsTo(results -> { - LOG.info(RETURNER_REPORTED_LOG_MESSAGE, results); - staticCallMirrorAns.set((BigInteger) results[0]); - })), - sourcing(() -> contractCallLocal(contract, CALL_RETURNER, asHeadlongAddress(aliasAddr.get())) - .payingWith(GENESIS) - .exposingTypedResultsTo(results -> { - LOG.info("Returner reported {} when" + " called with alias" + " address", results); - staticCallAliasAns.set((BigInteger) results[0]); - })), - sourcing(() -> contractCall(contract, CALL_RETURNER, asHeadlongAddress(aliasAddr.get())) - .payingWith(GENESIS) - .via(aliasCall)), - sourcing(() -> contractCall(contract, CALL_RETURNER, asHeadlongAddress(mirrorAddr.get())) - .hasKnownStatus(INVALID_SOLIDITY_ADDRESS) - .payingWith(GENESIS) - .via(mirrorCall))) - .then(withOpContext((spec, opLog) -> { + return hapiTest( + uploadInitCode(contract), + contractCreate(contract).payingWith(GENESIS), + contractCall(contract, "createReturner", salt) + .payingWith(GENESIS) + .gas(4_000_000L) + .via(CREATE_2_TXN), + captureOneChildCreate2MetaFor(RETURNER, CREATE_2_TXN, mirrorAddr, aliasAddr), + sourcing(() -> contractCallLocal(contract, CALL_RETURNER, asHeadlongAddress(mirrorAddr.get())) + .hasAnswerOnlyPrecheck(INVALID_SOLIDITY_ADDRESS) + .payingWith(GENESIS) + .exposingTypedResultsTo(results -> { + LOG.info(RETURNER_REPORTED_LOG_MESSAGE, results); + staticCallMirrorAns.set((BigInteger) results[0]); + })), + sourcing(() -> contractCallLocal(contract, CALL_RETURNER, asHeadlongAddress(aliasAddr.get())) + .payingWith(GENESIS) + .exposingTypedResultsTo(results -> { + LOG.info("Returner reported {} when" + " called with alias" + " address", results); + staticCallAliasAns.set((BigInteger) results[0]); + })), + sourcing(() -> contractCall(contract, CALL_RETURNER, asHeadlongAddress(aliasAddr.get())) + .payingWith(GENESIS) + .via(aliasCall)), + sourcing(() -> contractCall(contract, CALL_RETURNER, asHeadlongAddress(mirrorAddr.get())) + .hasKnownStatus(INVALID_SOLIDITY_ADDRESS) + .payingWith(GENESIS) + .via(mirrorCall)), + withOpContext((spec, opLog) -> { final var mirrorLookup = getTxnRecord(mirrorCall); allRunFor(spec, mirrorLookup); final var mirrorResult = mirrorLookup @@ -634,53 +525,37 @@ private HapiSpec canInternallyCallAliasedAddressesOnlyViaCreate2Address() { } @HapiTest - HapiSpec callingDestructedContractReturnsStatusDeleted() { + final Stream callingDestructedContractReturnsStatusDeleted() { final AtomicReference accountIDAtomicReference = new AtomicReference<>(); - return propertyPreservingHapiSpec("callingDestructedContractReturnsStatusDeleted") - .preserving(EVM_VERSION_PROPERTY, DYNAMIC_EVM_PROPERTY, EVM_ALLOW_CALLS_TO_NON_CONTRACT_ACCOUNTS) - .given( - overriding(EVM_VERSION_PROPERTY, EVM_VERSION_038), - overriding(DYNAMIC_EVM_PROPERTY, "true"), - overriding(EVM_ALLOW_CALLS_TO_NON_CONTRACT_ACCOUNTS, "false"), - cryptoCreate(BENEFICIARY).exposingCreatedIdTo(accountIDAtomicReference::set), - uploadInitCode(SIMPLE_UPDATE_CONTRACT)) - .when( - contractCreate(SIMPLE_UPDATE_CONTRACT).gas(300_000L), - contractCall(SIMPLE_UPDATE_CONTRACT, "set", BigInteger.valueOf(5), BigInteger.valueOf(42)) - .gas(300_000L), - sourcing(() -> contractCall( - SIMPLE_UPDATE_CONTRACT, - "del", - asHeadlongAddress(asAddress(accountIDAtomicReference.get()))) - .gas(1_000_000L))) - .then(contractCall(SIMPLE_UPDATE_CONTRACT, "set", BigInteger.valueOf(15), BigInteger.valueOf(434)) + return hapiTest( + cryptoCreate(BENEFICIARY).exposingCreatedIdTo(accountIDAtomicReference::set), + uploadInitCode(SIMPLE_UPDATE_CONTRACT), + contractCreate(SIMPLE_UPDATE_CONTRACT).gas(300_000L), + contractCall(SIMPLE_UPDATE_CONTRACT, "set", BigInteger.valueOf(5), BigInteger.valueOf(42)) + .gas(300_000L), + sourcing(() -> contractCall( + SIMPLE_UPDATE_CONTRACT, + "del", + asHeadlongAddress(asAddress(accountIDAtomicReference.get()))) + .gas(1_000_000L)), + contractCall(SIMPLE_UPDATE_CONTRACT, "set", BigInteger.valueOf(15), BigInteger.valueOf(434)) .gas(350_000L) .hasPrecheck(CONTRACT_DELETED)); } @HapiTest - private HapiSpec factoryAndSelfDestructInConstructorContract() { + final Stream factoryAndSelfDestructInConstructorContract() { final var contract = "FactorySelfDestructConstructor"; final var sender = "sender"; - return propertyPreservingHapiSpec("factoryAndSelfDestructInConstructorContract") - .preserving(EVM_VERSION_PROPERTY, DYNAMIC_EVM_PROPERTY, EVM_ALLOW_CALLS_TO_NON_CONTRACT_ACCOUNTS) - .given( - overriding(EVM_VERSION_PROPERTY, EVM_VERSION_038), - overriding(DYNAMIC_EVM_PROPERTY, "true"), - overriding(EVM_ALLOW_CALLS_TO_NON_CONTRACT_ACCOUNTS, "false"), - uploadInitCode(contract), - cryptoCreate(sender).balance(ONE_HUNDRED_HBARS), - contractCreate(contract).balance(10).payingWith(sender)) - .when(contractCall(contract) + return hapiTest( + uploadInitCode(contract), + cryptoCreate(sender).balance(ONE_HUNDRED_HBARS), + contractCreate(contract).balance(10).payingWith(sender), + contractCall(contract) .hasPrecheck(CONTRACT_DELETED) .payingWith(sender) - .hasKnownStatus(SUCCESS)) - .then(getContractBytecode(contract).hasCostAnswerPrecheck(CONTRACT_DELETED)); - } - - @Override - protected Logger getResultsLogger() { - return LOG; + .hasKnownStatus(SUCCESS), + getContractBytecode(contract).hasCostAnswerPrecheck(CONTRACT_DELETED)); } } diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/evm/Evm46ValidationSuite.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/evm/Evm46ValidationSuite.java index ee27e54fbaf4..4b36b01be185 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/evm/Evm46ValidationSuite.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/evm/Evm46ValidationSuite.java @@ -21,6 +21,7 @@ import static com.hedera.services.bdd.spec.HapiPropertySource.asAccountString; import static com.hedera.services.bdd.spec.HapiPropertySource.asContract; import static com.hedera.services.bdd.spec.HapiPropertySource.asContractIdWithEvmAddress; +import static com.hedera.services.bdd.spec.HapiPropertySource.idAsHeadlongAddress; import static com.hedera.services.bdd.spec.HapiSpec.defaultHapiSpec; import static com.hedera.services.bdd.spec.assertions.AccountInfoAsserts.changeFromSnapshot; import static com.hedera.services.bdd.spec.assertions.ContractFnResultAsserts.resultWith; @@ -47,6 +48,12 @@ import static com.hedera.services.bdd.spec.utilops.UtilVerbs.newKeyNamed; import static com.hedera.services.bdd.spec.utilops.UtilVerbs.sourcing; import static com.hedera.services.bdd.spec.utilops.UtilVerbs.withOpContext; +import static com.hedera.services.bdd.spec.utilops.records.SnapshotMatchMode.NONDETERMINISTIC_CONTRACT_CALL_RESULTS; +import static com.hedera.services.bdd.spec.utilops.records.SnapshotMatchMode.NONDETERMINISTIC_TRANSACTION_FEES; +import static com.hedera.services.bdd.suites.HapiSuite.GENESIS; +import static com.hedera.services.bdd.suites.HapiSuite.ONE_HBAR; +import static com.hedera.services.bdd.suites.HapiSuite.ONE_HUNDRED_HBARS; +import static com.hedera.services.bdd.suites.HapiSuite.SECP_256K1_SHAPE; import static com.hedera.services.bdd.suites.contract.Utils.FunctionType.FUNCTION; import static com.hedera.services.bdd.suites.contract.Utils.asAddress; import static com.hedera.services.bdd.suites.contract.Utils.getABIFor; @@ -58,6 +65,7 @@ import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.CONTRACT_REVERT_EXECUTED; import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.INVALID_ACCOUNT_ID; import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.INVALID_CONTRACT_ID; +import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.INVALID_FEE_SUBMITTED; import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.INVALID_SIGNATURE; import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.INVALID_SOLIDITY_ADDRESS; import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.SUCCESS; @@ -67,42 +75,36 @@ import com.google.protobuf.ByteString; import com.hedera.services.bdd.junit.HapiTest; -import com.hedera.services.bdd.junit.HapiTestSuite; -import com.hedera.services.bdd.spec.HapiSpec; import com.hedera.services.bdd.spec.HapiSpecOperation; import com.hedera.services.bdd.spec.assertions.ContractFnResultAsserts; -import com.hedera.services.bdd.suites.HapiSuite; import com.hederahashgraph.api.proto.java.AccountID; import com.hederahashgraph.api.proto.java.ContractID; import java.math.BigInteger; import java.util.List; import java.util.concurrent.atomic.AtomicLong; import java.util.concurrent.atomic.AtomicReference; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; +import java.util.stream.Stream; import org.apache.tuweni.bytes.Bytes; +import org.junit.jupiter.api.DynamicTest; import org.junit.jupiter.api.Tag; -@HapiTestSuite @Tag(SMART_CONTRACT) -public class Evm46ValidationSuite extends HapiSuite { +public class Evm46ValidationSuite { - private static final Logger LOG = LogManager.getLogger(Evm46ValidationSuite.class); private static final long FIRST_NONEXISTENT_CONTRACT_NUM = 4303224382569680425L; private static final String NAME = "name"; private static final String ERC_721_ABI = "ERC721ABI"; private static final String NON_EXISTING_MIRROR_ADDRESS = "0000000000000000000000000000000000123456"; private static final String NON_EXISTING_NON_MIRROR_ADDRESS = "1234561234561234561234561234568888123456"; + private static final String MAKE_CALLS_CONTRACT = "MakeCalls"; private static final String INTERNAL_CALLER_CONTRACT = "InternalCaller"; private static final String INTERNAL_CALLEE_CONTRACT = "InternalCallee"; private static final String REVERT_WITH_REVERT_REASON_FUNCTION = "revertWithRevertReason"; - private static final String REVERT_WITHOUT_REVERT_REASON_FUNCTION = "revertWithoutRevertReason"; private static final String CALL_NON_EXISTING_FUNCTION = "callNonExisting"; private static final String CALL_EXTERNAL_FUNCTION = "callExternalFunction"; private static final String DELEGATE_CALL_EXTERNAL_FUNCTION = "delegateCallExternalFunction"; private static final String STATIC_CALL_EXTERNAL_FUNCTION = "staticCallExternalFunction"; private static final String CALL_REVERT_WITH_REVERT_REASON_FUNCTION = "callRevertWithRevertReason"; - private static final String CALL_REVERT_WITHOUT_REVERT_REASON_FUNCTION = "callRevertWithoutRevertReason"; private static final String TRANSFER_TO_FUNCTION = "transferTo"; private static final String SEND_TO_FUNCTION = "sendTo"; private static final String CALL_WITH_VALUE_TO_FUNCTION = "callWithValueTo"; @@ -118,11 +120,6 @@ public class Evm46ValidationSuite extends HapiSuite { private static final String CUSTOM_PAYER = "customPayer"; private static final String BENEFICIARY = "beneficiary"; private static final String SIMPLE_UPDATE_CONTRACT = "SimpleUpdate"; - private static final String EVM_VERSION_PROPERTY = "contracts.evm.version"; - private static final String EVM_ALLOW_CALLS_TO_NON_CONTRACT_ACCOUNTS = - "contracts.evm.allowCallsToNonContractAccounts"; - private static final String DYNAMIC_EVM_PROPERTY = "contracts.evm.version.dynamic"; - private static final String EVM_VERSION_046 = "v0.46"; private static final String BALANCE_OF = "balanceOf"; public static final List nonExistingSystemAccounts = List.of(0L, 1L, 9L, 10L, 358L, 359L, 360L, 361L, 750L, 751L); @@ -130,148 +127,20 @@ public class Evm46ValidationSuite extends HapiSuite { public static final List systemAccounts = List.of(0L, 1L, 9L, 10L, 358L, 359L, 360L, 361L, 750L, 751L, 999L, 1000L); - public static void main(String... args) { - new Evm46ValidationSuite().runSuiteAsync(); - } - - @Override - public boolean canRunConcurrent() { - return true; - } - - @Override - public List getSpecsInSuite() { - return List.of( - // Top-level calls: - // EOA -calls-> NonExistingMirror, expect noop success - directCallToNonExistingMirrorAddressResultsInSuccessfulNoOp(), - // EOA -calls-> NonExistingNonMirror, expect noop success - directCallToNonExistingNonMirrorAddressResultsInSuccessfulNoOp(), - // EOA -calls-> ExistingCryptoAccount, expect noop success - directCallToExistingCryptoAccountResultsInSuccess(), - // EOA -callsWValue-> ExistingCryptoAccount, expect successful transfer - directCallWithValueToExistingCryptoAccountResultsInSuccess(), - // EOA -calls-> Reverting, expect revert - directCallToRevertingContractRevertsWithCorrectRevertReason(), - - // Internal calls: - // EOA -calls-> InternalCaller -calls-> NonExistingMirror, expect noop success - internalCallToNonExistingMirrorAddressResultsInNoopSuccess(), - // EOA -calls-> InternalCaller -calls-> ExistingMirror, expect successful call - internalCallToExistingMirrorAddressResultsInSuccessfulCall(), - // EOA -calls-> InternalCaller -calls-> NonExistingNonMirror, expect noop success - internalCallToNonExistingNonMirrorAddressResultsInNoopSuccess(), - // EOA -calls-> InternalCaller -calls-> Existing reverting without revert message - internalCallToExistingRevertingResultsInSuccessfulTopLevelTxn(), - - // Internal transfers: - // EOA -calls-> InternalCaller -transfer-> NonExistingMirror, expect revert - internalTransferToNonExistingMirrorAddressResultsInInvalidAliasKey(), - // EOA -calls-> InternalCaller -transfer-> ExistingMirror, expect success - internalTransferToExistingMirrorAddressResultsInSuccess(), - // EOA -calls-> InternalCaller -transfer-> NonExistingNonMirror, expect revert - internalTransferToNonExistingNonMirrorAddressResultsInRevert(), - // EOA -calls-> InternalCaller -transfer-> ExistingNonMirror, expect success - internalTransferToExistingNonMirrorAddressResultsInSuccess(), - - // Internal sends: - // EOA -calls-> InternalCaller -send-> NonExistingMirror, expect revert - internalSendToNonExistingMirrorAddressDoesNotLazyCreateIt(), - // EOA -calls-> InternalCaller -send-> ExistingMirror, expect success - internalSendToExistingMirrorAddressResultsInSuccess(), - // EOA -calls-> InternalCaller -send-> NonExistingNonMirror, expect revert - internalSendToNonExistingNonMirrorAddressResultsInSuccess(), - // EOA -calls-> InternalCaller -send-> ExistingNonMirror, expect success - internalSendToExistingNonMirrorAddressResultsInSuccess(), - - // Internal calls with value: - // EOA -calls-> InternalCaller -callWValue-> NonExistingMirror, expect revert - internalCallWithValueToNonExistingMirrorAddressResultsInInvalidAliasKey(), - // EOA -calls-> InternalCaller -callWValue-> ExistingMirror, expect success - internalCallWithValueToExistingMirrorAddressResultsInSuccess(), - // EOA -calls-> InternalCaller -callWValue-> NonExistingNonMirror and not enough gas for lazy creation, - // expect success with no account created - internalCallWithValueToNonExistingNonMirrorAddressWithoutEnoughGasForLazyCreationResultsInSuccessNoAccountCreated(), - // EOA -calls-> InternalCaller -callWValue-> NonExistingNonMirror and enough gas for lazy creation, - // expect success with account created - internalCallWithValueToNonExistingNonMirrorAddressWithEnoughGasForLazyCreationResultsInSuccessAccountCreated(), - // EOA -calls-> InternalCaller -callWValue-> ExistingNonMirror, expect ? - internalCallWithValueToExistingNonMirrorAddressResultsInSuccess(), - - // Internal calls to selfdestruct: - // EOA -calls-> InternalCaller -selfdestruct-> NonExistingNonMirror, expect INVALID_SOLIDITY_ADDRESS - selfdestructToNonExistingNonMirrorAddressResultsInInvalidSolidityAddress(), - // EOA -calls-> InternalCaller -selfdestruct-> NonExistingMirror, expect INVALID_SOLIDITY_ADDRESS - selfdestructToNonExistingMirrorAddressResultsInInvalidSolidityAddress(), - // EOA -calls-> InternalCaller -selfdestruct-> ExistingNonMirror, expect success - selfdestructToExistingNonMirrorAddressResultsInSuccess(), - // EOA -calls-> InternalCaller -selfdestruct-> ExistingMirror, expect success - selfdestructToExistingMirrorAddressResultsInSuccess(), - - // Calls to deleted contract - // EOA -calls-> InternalCaller -call-> deleted contract, expect success noop - internalCallToDeletedContractReturnsSuccessfulNoop(), - // EOA -calls-> deleted contract, expect success noop - directCallToDeletedContractResultsInSuccessfulNoop(), - // prerequisite: several successful calls then delete - // the contract (bytecode is already cached in AbstractCodeCache) - // then: EOA -calls-> deleted contract, expect success noop - callingDestructedContractReturnsStatusSuccess(), - - // Internal static calls: - // EOA -calls-> InternalCaller -staticcall-> NonExistingMirror, expect success noop - internalStaticCallNonExistingMirrorAddressResultsInSuccess(), - // EOA -calls-> InternalCaller -staticcall-> ExistingMirror, expect success noop - internalStaticCallExistingMirrorAddressResultsInSuccess(), - // EOA -calls-> InternalCaller -staticcall-> NonExistingNonMirror, expect success noop - internalStaticCallNonExistingNonMirrorAddressResultsInSuccess(), - // EOA -calls-> InternalCaller -staticcall-> ExistingNonMirror, expect success noop - internalStaticCallExistingNonMirrorAddressResultsInSuccess(), - - // Internal delegate calls: - // EOA -calls-> InternalCaller -delegatecall-> NonExistingMirror, expect success noop - internalDelegateCallNonExistingMirrorAddressResultsInSuccess(), - // EOA -calls-> InternalCaller -delegatecall-> ExistingMirror, expect success noop - internalDelegateCallExistingMirrorAddressResultsInSuccess(), - // EOA -calls-> InternalCaller -delegatecall-> NonExistingNonMirror, expect success noop - internalDelegateCallNonExistingNonMirrorAddressResultsInSuccess(), - // EOA -calls-> InternalCaller -delegatecall-> ExistingNonMirror, expect success noop - internalDelegateCallExistingNonMirrorAddressResultsInSuccess(), - - // EOA -calls-> InternalCaller -callWithValue-> ExistingMirror - // with receiverSigRequired=true, expect INVALID_SIGNATURE - internalCallWithValueToAccountWithReceiverSigRequiredTrue(), - - // Internal call to system account - // EOA -calls-> InternalCaller -call-> 0.0.2 (ethereum precompile) - internalCallToEthereumPrecompile0x2ResultsInSuccess(), - // EOA -calls-> InternalCaller -callWithValue-> 0.0.2 (ethereum precompile) - internalCallWithValueToEthereumPrecompile0x2ResultsInRevert(), - // EOA -calls-> InternalCaller -call-> 0.0.564 (system account < 0.0.750) - internalCallToSystemAccount564ResultsInSuccessNoop(), - // EOA -calls-> InternalCaller -call-> 0.0.800 (existing system account > 0.0.750) - internalCallToExistingSystemAccount800ResultsInSuccessNoop(), - // EOA -calls-> InternalCaller -call-> 0.0.852 (non-existing system account > 0.0.750) - internalCallToNonExistingSystemAccount852ResultsInSuccessNoop(), - - // EOA -calls-> InternalCaller -callWithValue-> 0.0.564 (system account < 0.0.750) - internalCallWithValueToSystemAccount564ResultsInSuccessNoopNoTransfer(), - // EOA -calls-> InternalCaller -callWithValue-> 0.0.800 (existing system account > 0.0.750) - internalCallWithValueToExistingSystemAccount800ResultsInSuccessfulTransfer(), - // EOA -calls-> InternalCaller -callWithValue-> 0.0.852 (non-existing system account > 0.0.750) - internalCallWithValueToNonExistingSystemAccount852ResultsInInvalidAliasKey(), - testBalanceOfForSystemAccounts()); - } - @HapiTest - private HapiSpec directCallToDeletedContractResultsInSuccessfulNoop() { + final Stream directCallToDeletedContractResultsInSuccessfulNoop() { AtomicReference receiverId = new AtomicReference<>(); return defaultHapiSpec("directCallToDeletedContractResultsInSuccessfulNoop") .given( cryptoCreate(RECEIVER).exposingCreatedIdTo(receiverId::set), uploadInitCode(INTERNAL_CALLER_CONTRACT), - contractCreate(INTERNAL_CALLER_CONTRACT).balance(ONE_HBAR), + contractCreate(INTERNAL_CALLER_CONTRACT) + // Refusing ethereum create conversion, because we get INVALID_SIGNATURE upon + // tokenAssociate, + // since we have CONTRACT_ID key + .refusingEthConversion() + .balance(ONE_HBAR), contractDelete(INTERNAL_CALLER_CONTRACT)) .when(withOpContext((spec, op) -> allRunFor( spec, @@ -288,7 +157,7 @@ private HapiSpec directCallToDeletedContractResultsInSuccessfulNoop() { } @HapiTest - private HapiSpec selfdestructToExistingMirrorAddressResultsInSuccess() { + final Stream selfdestructToExistingMirrorAddressResultsInSuccess() { AtomicReference receiverId = new AtomicReference<>(); return defaultHapiSpec("selfdestructToExistingMirrorAddressResultsInSuccess") .given( @@ -311,7 +180,7 @@ private HapiSpec selfdestructToExistingMirrorAddressResultsInSuccess() { } @HapiTest - private HapiSpec selfdestructToExistingNonMirrorAddressResultsInSuccess() { + final Stream selfdestructToExistingNonMirrorAddressResultsInSuccess() { return defaultHapiSpec("selfdestructToExistingNonMirrorAddressResultsInSuccess") .given( newKeyNamed(ECDSA_KEY).shape(SECP_256K1_SHAPE), @@ -336,7 +205,7 @@ private HapiSpec selfdestructToExistingNonMirrorAddressResultsInSuccess() { } @HapiTest - private HapiSpec selfdestructToNonExistingNonMirrorAddressResultsInInvalidSolidityAddress() { + final Stream selfdestructToNonExistingNonMirrorAddressResultsInInvalidSolidityAddress() { AtomicReference nonExistingNonMirrorAddress = new AtomicReference<>(); return defaultHapiSpec("selfdestructToNonExistingNonMirrorAddressResultsInInvalidSolidityAddress") @@ -368,7 +237,7 @@ private HapiSpec selfdestructToNonExistingNonMirrorAddressResultsInInvalidSolidi } @HapiTest - private HapiSpec selfdestructToNonExistingMirrorAddressResultsInInvalidSolidityAddress() { + final Stream selfdestructToNonExistingMirrorAddressResultsInInvalidSolidityAddress() { return defaultHapiSpec("selfdestructToNonExistingMirrorAddressResultsInInvalidSolidityAddress") .given( uploadInitCode(INTERNAL_CALLER_CONTRACT), @@ -389,7 +258,7 @@ private HapiSpec selfdestructToNonExistingMirrorAddressResultsInInvalidSolidityA } @HapiTest - private HapiSpec directCallToNonExistingMirrorAddressResultsInSuccessfulNoOp() { + final Stream directCallToNonExistingMirrorAddressResultsInSuccessfulNoOp() { return defaultHapiSpec("directCallToNonExistingMirrorAddressResultsInSuccessfulNoOp") .given(withOpContext((spec, ctxLog) -> spec.registry() @@ -420,7 +289,7 @@ private HapiSpec directCallToNonExistingMirrorAddressResultsInSuccessfulNoOp() { } @HapiTest - HapiSpec directCallToNonExistingNonMirrorAddressResultsInSuccessfulNoOp() { + final Stream directCallToNonExistingNonMirrorAddressResultsInSuccessfulNoOp() { return defaultHapiSpec("directCallToNonExistingNonMirrorAddressResultsInSuccessfulNoOp") .given(withOpContext((spec, ctxLog) -> spec.registry() @@ -454,7 +323,7 @@ HapiSpec directCallToNonExistingNonMirrorAddressResultsInSuccessfulNoOp() { } @HapiTest - private HapiSpec directCallToRevertingContractRevertsWithCorrectRevertReason() { + final Stream directCallToRevertingContractRevertsWithCorrectRevertReason() { return defaultHapiSpec("directCallToRevertingContractRevertsWithCorrectRevertReason") .given(uploadInitCode(INTERNAL_CALLEE_CONTRACT), contractCreate(INTERNAL_CALLEE_CONTRACT)) @@ -475,7 +344,7 @@ private HapiSpec directCallToRevertingContractRevertsWithCorrectRevertReason() { } @HapiTest - private HapiSpec directCallToExistingCryptoAccountResultsInSuccess() { + final Stream directCallToExistingCryptoAccountResultsInSuccess() { AtomicReference mirrorAccountID = new AtomicReference<>(); @@ -523,7 +392,7 @@ private HapiSpec directCallToExistingCryptoAccountResultsInSuccess() { } @HapiTest - private HapiSpec directCallWithValueToExistingCryptoAccountResultsInSuccess() { + final Stream directCallWithValueToExistingCryptoAccountResultsInSuccess() { AtomicReference mirrorAccountID = new AtomicReference<>(); @@ -590,7 +459,7 @@ private HapiSpec directCallWithValueToExistingCryptoAccountResultsInSuccess() { } @HapiTest - private HapiSpec internalCallToNonExistingMirrorAddressResultsInNoopSuccess() { + final Stream internalCallToNonExistingMirrorAddressResultsInNoopSuccess() { return defaultHapiSpec("internalCallToNonExistingMirrorAddressResultsInNoopSuccess") .given( @@ -610,15 +479,21 @@ private HapiSpec internalCallToNonExistingMirrorAddressResultsInNoopSuccess() { } @HapiTest - private HapiSpec internalCallToExistingMirrorAddressResultsInSuccessfulCall() { + final Stream internalCallToExistingMirrorAddressResultsInSuccessfulCall() { final AtomicLong calleeNum = new AtomicLong(); return defaultHapiSpec("internalCallToExistingMirrorAddressResultsInSuccessfulCall") .given( uploadInitCode(INTERNAL_CALLER_CONTRACT, INTERNAL_CALLEE_CONTRACT), - contractCreate(INTERNAL_CALLER_CONTRACT).balance(ONE_HBAR), - contractCreate(INTERNAL_CALLEE_CONTRACT).exposingNumTo(calleeNum::set)) + contractCreate(INTERNAL_CALLER_CONTRACT) + .balance(ONE_HBAR) + // Adding refusingEthConversion() due to fee differences and not supported address type + .refusingEthConversion(), + contractCreate(INTERNAL_CALLEE_CONTRACT) + .exposingNumTo(calleeNum::set) + // Adding refusingEthConversion() due to fee differences and not supported address type + .refusingEthConversion()) .when(withOpContext((spec, ignored) -> allRunFor( spec, contractCall(INTERNAL_CALLER_CONTRACT, CALL_EXTERNAL_FUNCTION, mirrorAddrWith(calleeNum.get())) @@ -634,7 +509,7 @@ private HapiSpec internalCallToExistingMirrorAddressResultsInSuccessfulCall() { } @HapiTest - private HapiSpec internalCallToNonExistingNonMirrorAddressResultsInNoopSuccess() { + final Stream internalCallToNonExistingNonMirrorAddressResultsInNoopSuccess() { return defaultHapiSpec("internalCallToNonExistingNonMirrorAddressResultsInNoopSuccess") .given( @@ -654,7 +529,7 @@ private HapiSpec internalCallToNonExistingNonMirrorAddressResultsInNoopSuccess() } @HapiTest - private HapiSpec internalCallToExistingRevertingResultsInSuccessfulTopLevelTxn() { + final Stream internalCallToExistingRevertingResultsInSuccessfulTopLevelTxn() { final AtomicLong calleeNum = new AtomicLong(); @@ -676,7 +551,7 @@ private HapiSpec internalCallToExistingRevertingResultsInSuccessfulTopLevelTxn() } @HapiTest - HapiSpec internalTransferToNonExistingMirrorAddressResultsInInvalidAliasKey() { + final Stream internalTransferToNonExistingMirrorAddressResultsInInvalidAliasKey() { return defaultHapiSpec("internalTransferToNonExistingMirrorAddressResultsInInvalidAliasKey") .given( uploadInitCode(INTERNAL_CALLER_CONTRACT), @@ -693,7 +568,7 @@ HapiSpec internalTransferToNonExistingMirrorAddressResultsInInvalidAliasKey() { } @HapiTest - private HapiSpec internalTransferToExistingMirrorAddressResultsInSuccess() { + final Stream internalTransferToExistingMirrorAddressResultsInSuccess() { AtomicReference receiverId = new AtomicReference<>(); @@ -719,7 +594,7 @@ private HapiSpec internalTransferToExistingMirrorAddressResultsInSuccess() { } @HapiTest - private HapiSpec internalTransferToNonExistingNonMirrorAddressResultsInRevert() { + final Stream internalTransferToNonExistingNonMirrorAddressResultsInRevert() { return defaultHapiSpec("internalTransferToNonExistingNonMirrorAddressResultsInRevert") .given( cryptoCreate(CUSTOM_PAYER).balance(ONE_HUNDRED_HBARS), @@ -737,7 +612,7 @@ private HapiSpec internalTransferToNonExistingNonMirrorAddressResultsInRevert() } @HapiTest - private HapiSpec internalTransferToExistingNonMirrorAddressResultsInSuccess() { + final Stream internalTransferToExistingNonMirrorAddressResultsInSuccess() { return defaultHapiSpec("internalTransferToExistingNonMirrorAddressResultsInSuccess") .given( @@ -769,7 +644,7 @@ private HapiSpec internalTransferToExistingNonMirrorAddressResultsInSuccess() { } @HapiTest - HapiSpec internalSendToNonExistingMirrorAddressDoesNotLazyCreateIt() { + final Stream internalSendToNonExistingMirrorAddressDoesNotLazyCreateIt() { return defaultHapiSpec("internalSendToNonExistingMirrorAddressDoesNotLazyCreateIt") .given( uploadInitCode(INTERNAL_CALLER_CONTRACT), @@ -786,7 +661,7 @@ HapiSpec internalSendToNonExistingMirrorAddressDoesNotLazyCreateIt() { } @HapiTest - private HapiSpec internalSendToExistingMirrorAddressResultsInSuccess() { + final Stream internalSendToExistingMirrorAddressResultsInSuccess() { AtomicReference receiverId = new AtomicReference<>(); @@ -812,7 +687,7 @@ private HapiSpec internalSendToExistingMirrorAddressResultsInSuccess() { } @HapiTest - private HapiSpec internalSendToNonExistingNonMirrorAddressResultsInSuccess() { + final Stream internalSendToNonExistingNonMirrorAddressResultsInSuccess() { AtomicReference nonExistingNonMirrorAddress = new AtomicReference<>(); @@ -848,7 +723,7 @@ private HapiSpec internalSendToNonExistingNonMirrorAddressResultsInSuccess() { } @HapiTest - private HapiSpec internalSendToExistingNonMirrorAddressResultsInSuccess() { + final Stream internalSendToExistingNonMirrorAddressResultsInSuccess() { return defaultHapiSpec("internalSendToExistingNonMirrorAddressResultsInSuccess") .given( @@ -877,7 +752,7 @@ private HapiSpec internalSendToExistingNonMirrorAddressResultsInSuccess() { } @HapiTest - private HapiSpec internalCallWithValueToNonExistingMirrorAddressResultsInInvalidAliasKey() { + final Stream internalCallWithValueToNonExistingMirrorAddressResultsInInvalidAliasKey() { return defaultHapiSpec("internalCallWithValueToNonExistingMirrorAddressResultsInInvalidAliasKey") .given( uploadInitCode(INTERNAL_CALLER_CONTRACT), @@ -893,7 +768,7 @@ private HapiSpec internalCallWithValueToNonExistingMirrorAddressResultsInInvalid } @HapiTest - private HapiSpec internalCallWithValueToExistingMirrorAddressResultsInSuccess() { + final Stream internalCallWithValueToExistingMirrorAddressResultsInSuccess() { AtomicReference receiverId = new AtomicReference<>(); @@ -919,7 +794,7 @@ private HapiSpec internalCallWithValueToExistingMirrorAddressResultsInSuccess() } @HapiTest - private HapiSpec + final Stream internalCallWithValueToNonExistingNonMirrorAddressWithoutEnoughGasForLazyCreationResultsInSuccessNoAccountCreated() { return defaultHapiSpec( "internalCallWithValueToNonExistingNonMirrorAddressWithoutEnoughGasForLazyCreationResultsInSuccessNoAccountCreated") @@ -944,7 +819,7 @@ private HapiSpec internalCallWithValueToExistingMirrorAddressResultsInSuccess() } @HapiTest - private HapiSpec + final Stream internalCallWithValueToNonExistingNonMirrorAddressWithEnoughGasForLazyCreationResultsInSuccessAccountCreated() { return defaultHapiSpec( "internalCallWithValueToNonExistingNonMirrorAddressWithEnoughGasForLazyCreationResultsInSuccessAccountCreated") @@ -969,7 +844,7 @@ private HapiSpec internalCallWithValueToExistingMirrorAddressResultsInSuccess() } @HapiTest - private HapiSpec internalCallWithValueToExistingNonMirrorAddressResultsInSuccess() { + final Stream internalCallWithValueToExistingNonMirrorAddressResultsInSuccess() { return defaultHapiSpec("internalCallWithValueToExistingNonMirrorAddressResultsInSuccess") .given( @@ -1001,13 +876,23 @@ private HapiSpec internalCallWithValueToExistingNonMirrorAddressResultsInSuccess } @HapiTest - private HapiSpec internalCallToDeletedContractReturnsSuccessfulNoop() { + final Stream internalCallToDeletedContractReturnsSuccessfulNoop() { final AtomicLong calleeNum = new AtomicLong(); return defaultHapiSpec("internalCallToDeletedContractReturnsSuccessfulNoop") .given( uploadInitCode(INTERNAL_CALLER_CONTRACT, INTERNAL_CALLEE_CONTRACT), - contractCreate(INTERNAL_CALLER_CONTRACT).balance(ONE_HBAR), - contractCreate(INTERNAL_CALLEE_CONTRACT).exposingNumTo(calleeNum::set), + contractCreate(INTERNAL_CALLER_CONTRACT) + // Refusing ethereum create conversion, because we get INVALID_SIGNATURE upon + // tokenAssociate, + // since we have CONTRACT_ID key + .refusingEthConversion() + .balance(ONE_HBAR), + contractCreate(INTERNAL_CALLEE_CONTRACT) + // Refusing ethereum create conversion, because we get INVALID_SIGNATURE upon + // tokenAssociate, + // since we have CONTRACT_ID key + .refusingEthConversion() + .exposingNumTo(calleeNum::set), contractDelete(INTERNAL_CALLEE_CONTRACT)) .when(withOpContext((spec, ignored) -> allRunFor( spec, @@ -1024,7 +909,7 @@ private HapiSpec internalCallToDeletedContractReturnsSuccessfulNoop() { } @HapiTest - private HapiSpec callingDestructedContractReturnsStatusSuccess() { + final Stream callingDestructedContractReturnsStatusSuccess() { final AtomicReference accountIDAtomicReference = new AtomicReference<>(); return defaultHapiSpec("callingDestructedContractReturnsStatusSuccess") .given( @@ -1045,7 +930,7 @@ private HapiSpec callingDestructedContractReturnsStatusSuccess() { } @HapiTest - private HapiSpec internalStaticCallNonExistingMirrorAddressResultsInSuccess() { + final Stream internalStaticCallNonExistingMirrorAddressResultsInSuccess() { return defaultHapiSpec("internalStaticCallNonExistingMirrorAddressResultsInSuccess") .given( uploadInitCode(INTERNAL_CALLER_CONTRACT), @@ -1064,7 +949,7 @@ private HapiSpec internalStaticCallNonExistingMirrorAddressResultsInSuccess() { } @HapiTest - private HapiSpec internalStaticCallExistingMirrorAddressResultsInSuccess() { + final Stream internalStaticCallExistingMirrorAddressResultsInSuccess() { AtomicReference receiverId = new AtomicReference<>(); return defaultHapiSpec("internalStaticCallExistingMirrorAddressResultsInSuccess") .given( @@ -1088,7 +973,7 @@ private HapiSpec internalStaticCallExistingMirrorAddressResultsInSuccess() { } @HapiTest - private HapiSpec internalStaticCallNonExistingNonMirrorAddressResultsInSuccess() { + final Stream internalStaticCallNonExistingNonMirrorAddressResultsInSuccess() { AtomicReference nonExistingNonMirrorAddress = new AtomicReference<>(); return defaultHapiSpec("internalStaticCallNonExistingNonMirrorAddressResultsInSuccess") .given( @@ -1123,7 +1008,7 @@ private HapiSpec internalStaticCallNonExistingNonMirrorAddressResultsInSuccess() } @HapiTest - private HapiSpec internalStaticCallExistingNonMirrorAddressResultsInSuccess() { + final Stream internalStaticCallExistingNonMirrorAddressResultsInSuccess() { return defaultHapiSpec("internalStaticCallExistingNonMirrorAddressResultsInSuccess") .given( newKeyNamed(ECDSA_KEY).shape(SECP_256K1_SHAPE), @@ -1153,7 +1038,7 @@ private HapiSpec internalStaticCallExistingNonMirrorAddressResultsInSuccess() { } @HapiTest - private HapiSpec internalDelegateCallNonExistingMirrorAddressResultsInSuccess() { + final Stream internalDelegateCallNonExistingMirrorAddressResultsInSuccess() { return defaultHapiSpec("internalDelegateCallNonExistingMirrorAddressResultsInSuccess") .given( uploadInitCode(INTERNAL_CALLER_CONTRACT), @@ -1172,7 +1057,7 @@ private HapiSpec internalDelegateCallNonExistingMirrorAddressResultsInSuccess() } @HapiTest - private HapiSpec internalDelegateCallExistingMirrorAddressResultsInSuccess() { + final Stream internalDelegateCallExistingMirrorAddressResultsInSuccess() { AtomicReference receiverId = new AtomicReference<>(); return defaultHapiSpec("internalDelegateCallExistingMirrorAddressResultsInSuccess") .given( @@ -1196,7 +1081,7 @@ private HapiSpec internalDelegateCallExistingMirrorAddressResultsInSuccess() { } @HapiTest - private HapiSpec internalDelegateCallNonExistingNonMirrorAddressResultsInSuccess() { + final Stream internalDelegateCallNonExistingNonMirrorAddressResultsInSuccess() { AtomicReference nonExistingNonMirrorAddress = new AtomicReference<>(); return defaultHapiSpec("internalDelegateCallNonExistingNonMirrorAddressResultsInSuccess") .given( @@ -1231,7 +1116,7 @@ private HapiSpec internalDelegateCallNonExistingNonMirrorAddressResultsInSuccess } @HapiTest - private HapiSpec internalDelegateCallExistingNonMirrorAddressResultsInSuccess() { + final Stream internalDelegateCallExistingNonMirrorAddressResultsInSuccess() { return defaultHapiSpec("internalDelegateCallExistingNonMirrorAddressResultsInSuccess") .given( newKeyNamed(ECDSA_KEY).shape(SECP_256K1_SHAPE), @@ -1261,7 +1146,7 @@ private HapiSpec internalDelegateCallExistingNonMirrorAddressResultsInSuccess() } @HapiTest - private HapiSpec internalCallWithValueToAccountWithReceiverSigRequiredTrue() { + final Stream internalCallWithValueToAccountWithReceiverSigRequiredTrue() { AtomicReference receiverId = new AtomicReference<>(); return defaultHapiSpec("internalCallWithValueToAccountWithReceiverSigRequiredTrue") @@ -1286,7 +1171,7 @@ private HapiSpec internalCallWithValueToAccountWithReceiverSigRequiredTrue() { } @HapiTest - private HapiSpec internalCallToSystemAccount564ResultsInSuccessNoop() { + final Stream internalCallToSystemAccount564ResultsInSuccessNoop() { AtomicReference targetId = new AtomicReference<>(); targetId.set(AccountID.newBuilder().setAccountNum(564L).build()); @@ -1311,7 +1196,60 @@ private HapiSpec internalCallToSystemAccount564ResultsInSuccessNoop() { } @HapiTest - private HapiSpec internalCallToEthereumPrecompile0x2ResultsInSuccess() { + final Stream internalCallsAgainstSystemAccountsWithValue() { + final var withAmount = "makeCallWithAmount"; + return defaultHapiSpec( + "internalCallsAgainstSystemAccountsWithValue", + NONDETERMINISTIC_TRANSACTION_FEES, + NONDETERMINISTIC_CONTRACT_CALL_RESULTS) + .given( + uploadInitCode(MAKE_CALLS_CONTRACT), + contractCreate(MAKE_CALLS_CONTRACT).gas(GAS_LIMIT_FOR_CALL * 4)) + .when( + balanceSnapshot("initialBalance", MAKE_CALLS_CONTRACT), + contractCall( + MAKE_CALLS_CONTRACT, + withAmount, + idAsHeadlongAddress(AccountID.newBuilder() + .setAccountNum(357) + .build()), + new byte[] {"system account".getBytes()[0]}) + .gas(GAS_LIMIT_FOR_CALL * 4) + .sending(2L) + .via(INNER_TXN) + .hasKnownStatus(INVALID_FEE_SUBMITTED)) + .then(getAccountBalance(MAKE_CALLS_CONTRACT).hasTinyBars(changeFromSnapshot("initialBalance", 0))); + } + + @HapiTest + final Stream internalCallsAgainstSystemAccountsWithoutValue() { + final var withoutAmount = "makeCallWithoutAmount"; + return defaultHapiSpec( + "internalCallsAgainstSystemAccountsWithoutValue", + NONDETERMINISTIC_TRANSACTION_FEES, + NONDETERMINISTIC_CONTRACT_CALL_RESULTS) + .given( + uploadInitCode(MAKE_CALLS_CONTRACT), + contractCreate(MAKE_CALLS_CONTRACT).gas(GAS_LIMIT_FOR_CALL * 4)) + .when( + balanceSnapshot("initialBalance", MAKE_CALLS_CONTRACT), + contractCall( + MAKE_CALLS_CONTRACT, + withoutAmount, + idAsHeadlongAddress(AccountID.newBuilder() + .setAccountNum(357) + .build()), + new byte[] {"system account".getBytes()[0]}) + .gas(GAS_LIMIT_FOR_CALL * 4) + .via(INNER_TXN) + .hasKnownStatus(SUCCESS)) + .then( + getTxnRecord(INNER_TXN).hasPriority(recordWith().status(SUCCESS)), + getAccountBalance(MAKE_CALLS_CONTRACT).hasTinyBars(changeFromSnapshot("initialBalance", 0))); + } + + @HapiTest + final Stream internalCallToEthereumPrecompile0x2ResultsInSuccess() { AtomicReference targetId = new AtomicReference<>(); targetId.set(AccountID.newBuilder().setAccountNum(2L).build()); @@ -1343,7 +1281,7 @@ private HapiSpec internalCallToEthereumPrecompile0x2ResultsInSuccess() { } @HapiTest - private HapiSpec internalCallWithValueToEthereumPrecompile0x2ResultsInRevert() { + final Stream internalCallWithValueToEthereumPrecompile0x2ResultsInRevert() { AtomicReference targetId = new AtomicReference<>(); targetId.set(AccountID.newBuilder().setAccountNum(2L).build()); @@ -1360,22 +1298,13 @@ private HapiSpec internalCallWithValueToEthereumPrecompile0x2ResultsInRevert() { CALL_WITH_VALUE_TO_FUNCTION, mirrorAddrWith(targetId.get().getAccountNum())) .gas(GAS_LIMIT_FOR_CALL * 4) - .via(INNER_TXN)))) - .then( - withOpContext((spec, opLog) -> { - final var lookup = getTxnRecord(INNER_TXN); - allRunFor(spec, lookup); - final var result = lookup.getResponseRecord() - .getContractCallResult() - .getContractCallResult(); - assertEquals(ByteString.copyFrom(new byte[0]), result); - }), - getAccountBalance(INTERNAL_CALLER_CONTRACT) - .hasTinyBars(changeFromSnapshot("initialBalance", 0))); + .via(INNER_TXN) + .hasKnownStatus(INVALID_FEE_SUBMITTED)))) + .then(getAccountBalance(INTERNAL_CALLER_CONTRACT).hasTinyBars(changeFromSnapshot("initialBalance", 0))); } @HapiTest - private HapiSpec internalCallToNonExistingSystemAccount852ResultsInSuccessNoop() { + final Stream internalCallToNonExistingSystemAccount852ResultsInSuccessNoop() { AtomicReference targetId = new AtomicReference<>(); targetId.set(AccountID.newBuilder().setAccountNum(852L).build()); @@ -1400,7 +1329,7 @@ private HapiSpec internalCallToNonExistingSystemAccount852ResultsInSuccessNoop() } @HapiTest - final HapiSpec internalCallWithValueToNonExistingSystemAccount852ResultsInInvalidAliasKey() { + final Stream internalCallWithValueToNonExistingSystemAccount852ResultsInInvalidAliasKey() { AtomicReference targetId = new AtomicReference<>(); final var systemAccountNum = 852L; targetId.set(AccountID.newBuilder().setAccountNum(systemAccountNum).build()); @@ -1425,7 +1354,7 @@ final HapiSpec internalCallWithValueToNonExistingSystemAccount852ResultsInInvali } @HapiTest - private HapiSpec internalCallWithValueToSystemAccount564ResultsInSuccessNoopNoTransfer() { + final Stream internalCallWithValueToSystemAccount564ResultsInSuccessNoopNoTransfer() { AtomicReference targetId = new AtomicReference<>(); targetId.set(AccountID.newBuilder().setAccountNum(564L).build()); @@ -1442,15 +1371,13 @@ private HapiSpec internalCallWithValueToSystemAccount564ResultsInSuccessNoopNoTr CALL_WITH_VALUE_TO_FUNCTION, mirrorAddrWith(targetId.get().getAccountNum())) .gas(GAS_LIMIT_FOR_CALL * 4) - .via(INNER_TXN)))) - .then( - getTxnRecord(INNER_TXN).hasPriority(recordWith().status(SUCCESS)), - getAccountBalance(INTERNAL_CALLER_CONTRACT) - .hasTinyBars(changeFromSnapshot("initialBalance", 0))); + .via(INNER_TXN) + .hasKnownStatus(INVALID_FEE_SUBMITTED)))) + .then(getAccountBalance(INTERNAL_CALLER_CONTRACT).hasTinyBars(changeFromSnapshot("initialBalance", 0))); } @HapiTest - private HapiSpec internalCallWithValueToExistingSystemAccount800ResultsInSuccessfulTransfer() { + final Stream internalCallWithValueToExistingSystemAccount800ResultsInSuccessfulTransfer() { AtomicReference targetId = new AtomicReference<>(); targetId.set(AccountID.newBuilder().setAccountNum(800L).build()); @@ -1475,7 +1402,7 @@ private HapiSpec internalCallWithValueToExistingSystemAccount800ResultsInSuccess } @HapiTest - private HapiSpec internalCallToExistingSystemAccount800ResultsInSuccessNoop() { + final Stream internalCallToExistingSystemAccount800ResultsInSuccessNoop() { AtomicReference targetId = new AtomicReference<>(); targetId.set(AccountID.newBuilder().setAccountNum(800L).build()); @@ -1500,11 +1427,11 @@ private HapiSpec internalCallToExistingSystemAccount800ResultsInSuccessNoop() { } @HapiTest - final HapiSpec testBalanceOfForSystemAccounts() { + final Stream testBalanceOfForSystemAccounts() { final var contract = "BalanceChecker46Version"; final var balance = 10L; final var systemAccountBalance = 0; - final HapiSpecOperation[] opsArray = new HapiSpecOperation[systemAccounts.size() * 2]; + final var opsArray = new HapiSpecOperation[systemAccounts.size() * 2]; for (int i = 0; i < systemAccounts.size(); i++) { // add contract call for all accounts in the list @@ -1526,8 +1453,25 @@ contract, BALANCE_OF, mirrorAddrWith(systemAccounts.get(i))) .then(opsArray); } - @Override - protected Logger getResultsLogger() { - return LOG; + @HapiTest + final Stream directCallToSystemAccountResultsInSuccessfulNoOp() { + return defaultHapiSpec("directCallToSystemAccountResultsInSuccessfulNoOp") + .given( + cryptoCreate("account").balance(ONE_HUNDRED_HBARS), + withOpContext((spec, opLog) -> spec.registry() + .saveContractId( + "contract", + asContractIdWithEvmAddress(ByteString.copyFrom( + unhex("0000000000000000000000000000000000000275")))))) + .when(withOpContext((spec, ctxLog) -> allRunFor( + spec, + contractCallWithFunctionAbi("contract", getABIFor(FUNCTION, NAME, ERC_721_ABI)) + .gas(GAS_LIMIT_FOR_CALL) + .via("callToSystemAddress") + .signingWith("account")))) + .then(getTxnRecord("callToSystemAddress") + .hasPriority(recordWith() + .status(SUCCESS) + .contractCallResult(resultWith().gasUsed(GAS_LIMIT_FOR_CALL)))); } } diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/evm/Evm50ValidationSuite.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/evm/Evm50ValidationSuite.java index d6f17c0cfa84..b794759d97c9 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/evm/Evm50ValidationSuite.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/evm/Evm50ValidationSuite.java @@ -16,214 +16,61 @@ package com.hedera.services.bdd.suites.contract.evm; +import static com.hedera.services.bdd.junit.ContextRequirement.PROPERTY_OVERRIDES; import static com.hedera.services.bdd.junit.TestTags.SMART_CONTRACT; +import static com.hedera.services.bdd.spec.HapiSpec.hapiTest; import static com.hedera.services.bdd.spec.HapiSpec.propertyPreservingHapiSpec; import static com.hedera.services.bdd.spec.transactions.TxnVerbs.contractCall; import static com.hedera.services.bdd.spec.transactions.TxnVerbs.contractCreate; -import static com.hedera.services.bdd.spec.transactions.TxnVerbs.cryptoCreate; import static com.hedera.services.bdd.spec.transactions.TxnVerbs.uploadInitCode; import static com.hedera.services.bdd.spec.utilops.UtilVerbs.overriding; -import static com.hedera.services.bdd.spec.utilops.UtilVerbs.sourcing; import static com.hedera.services.bdd.spec.utilops.records.SnapshotMatchMode.NONDETERMINISTIC_TRANSACTION_FEES; import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.CONTRACT_EXECUTION_EXCEPTION; -import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.SUCCESS; import com.hedera.services.bdd.junit.HapiTest; -import com.hedera.services.bdd.junit.HapiTestSuite; -import com.hedera.services.bdd.spec.HapiSpec; -import com.hedera.services.bdd.suites.HapiSuite; -import java.util.List; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; +import com.hedera.services.bdd.junit.LeakyHapiTest; +import java.util.stream.Stream; +import org.junit.jupiter.api.DynamicTest; import org.junit.jupiter.api.Tag; -@HapiTestSuite @Tag(SMART_CONTRACT) -public class Evm50ValidationSuite extends HapiSuite { - - private static final Logger LOG = LogManager.getLogger(Evm50ValidationSuite.class); +public class Evm50ValidationSuite { private static final String EVM_VERSION_PROPERTY = "contracts.evm.version"; private static final String DYNAMIC_EVM_PROPERTY = "contracts.evm.version.dynamic"; private static final String EVM_VERSION_046 = "v0.46"; - private static final String EVM_VERSION_050 = "v0.50"; - private static final String ACCOUNT = "account"; private static final String Module05OpcodesExist_CONTRACT = "Module050OpcodesExist"; private static final long A_BUNCH_OF_GAS = 500_000L; - public static void main(String... args) { - new Evm50ValidationSuite().runSuiteSync(); - } - - @Override - public boolean canRunConcurrent() { - return false; - } - - public List getSpecsInSuite() { - return List.of( - verifiesNonExistenceForTransientStorageOpcodesV46(), - verifiesNonExistenceForMCOPYOpcodeV46(), - verifiesNonExistenceForKZGPrecompileV46(), - verifiesExistenceForTransientStorageOpcodesV050(), - verifiesExistenceForMCOPYOpcodeV050(), - verifiesExistenceForKZGPrecompileV050(), - successTestForKZGPrecompileV050()); - } - - @HapiTest - HapiSpec verifiesNonExistenceForTransientStorageOpcodesV46() { - final var contract = Module05OpcodesExist_CONTRACT; - - return propertyPreservingHapiSpec( - "verifiesNonExistenceForTransientStorageOpcodesV46", NONDETERMINISTIC_TRANSACTION_FEES) - .preserving(EVM_VERSION_PROPERTY, DYNAMIC_EVM_PROPERTY) - .given( - overriding(DYNAMIC_EVM_PROPERTY, "true"), - overriding(EVM_VERSION_PROPERTY, EVM_VERSION_046), - cryptoCreate(ACCOUNT), - uploadInitCode(contract), - contractCreate(contract)) - .when() - .then(sourcing(() -> contractCall(contract, "try_transient_storage") - .gas(A_BUNCH_OF_GAS) - .payingWith(ACCOUNT) - .via(txnName("non_transient")) - .hasKnownStatus(CONTRACT_EXECUTION_EXCEPTION) - .logged())); - } - - @HapiTest - HapiSpec verifiesNonExistenceForMCOPYOpcodeV46() { + @LeakyHapiTest(PROPERTY_OVERRIDES) + final Stream verifiesNonExistenceForV50OpcodesInV46() { final var contract = Module05OpcodesExist_CONTRACT; - return propertyPreservingHapiSpec("verifiesNonExistenceForMCOPYOpcodeV46", NONDETERMINISTIC_TRANSACTION_FEES) + return propertyPreservingHapiSpec("verifiesNonExistenceForV50OpcodesInV46", NONDETERMINISTIC_TRANSACTION_FEES) .preserving(EVM_VERSION_PROPERTY, DYNAMIC_EVM_PROPERTY) .given( overriding(DYNAMIC_EVM_PROPERTY, "true"), overriding(EVM_VERSION_PROPERTY, EVM_VERSION_046), - cryptoCreate(ACCOUNT), - uploadInitCode(contract), - contractCreate(contract)) - .when() - .then(sourcing(() -> contractCall(contract, "try_mcopy") - .gas(A_BUNCH_OF_GAS) - .payingWith(ACCOUNT) - .via(txnName("non_mcopy")) - .hasKnownStatus(CONTRACT_EXECUTION_EXCEPTION) - .logged())); - } - - @HapiTest - HapiSpec verifiesNonExistenceForKZGPrecompileV46() { - final var contract = Module05OpcodesExist_CONTRACT; - - return propertyPreservingHapiSpec("verifiesNonExistenceForKZGPrecompileV46") - .preserving(EVM_VERSION_PROPERTY, DYNAMIC_EVM_PROPERTY) - .given( - overriding(DYNAMIC_EVM_PROPERTY, "true"), - overriding(EVM_VERSION_PROPERTY, EVM_VERSION_046), - cryptoCreate(ACCOUNT), - uploadInitCode(contract), - contractCreate(contract)) - .when() - .then(sourcing(() -> contractCall(contract, "try_kzg_precompile") - .gas(A_BUNCH_OF_GAS) - .payingWith(ACCOUNT) - .via(txnName("non-kzg")) - .hasKnownStatus(CONTRACT_EXECUTION_EXCEPTION) - .logged())); - } - - @HapiTest - HapiSpec verifiesExistenceForTransientStorageOpcodesV050() { - final var contract = Module05OpcodesExist_CONTRACT; - - return propertyPreservingHapiSpec("verifiesExistenceForTransientStorageOpcodesV050") - .preserving(EVM_VERSION_PROPERTY, DYNAMIC_EVM_PROPERTY) - .given( - overriding(DYNAMIC_EVM_PROPERTY, "true"), - overriding(EVM_VERSION_PROPERTY, EVM_VERSION_050), - cryptoCreate(ACCOUNT), - uploadInitCode(contract), - contractCreate(contract)) - .when() - .then(sourcing(() -> contractCall(contract, "try_transient_storage") - .gas(A_BUNCH_OF_GAS) - .payingWith(ACCOUNT) - .via(txnName("transient")) - .hasKnownStatus(SUCCESS) - .logged())); - } - - @HapiTest - HapiSpec verifiesExistenceForMCOPYOpcodeV050() { - final var contract = Module05OpcodesExist_CONTRACT; - - return propertyPreservingHapiSpec("verifiesExistenceForMCOPYOpcodeV050") - .preserving(EVM_VERSION_PROPERTY, DYNAMIC_EVM_PROPERTY) - .given( - overriding(DYNAMIC_EVM_PROPERTY, "true"), - overriding(EVM_VERSION_PROPERTY, EVM_VERSION_050), - cryptoCreate(ACCOUNT), - uploadInitCode(contract), - contractCreate(contract)) - .when() - .then(sourcing(() -> contractCall(contract, "try_mcopy") - .gas(A_BUNCH_OF_GAS) - .payingWith(ACCOUNT) - .via("mcopy") - .hasKnownStatus(SUCCESS) - .logged())); - } - - @HapiTest - HapiSpec verifiesExistenceForKZGPrecompileV050() { - final var contract = Module05OpcodesExist_CONTRACT; - - return propertyPreservingHapiSpec("verifiesExistenceForKZGPrecompileV050") - .preserving(EVM_VERSION_PROPERTY, DYNAMIC_EVM_PROPERTY) - .given( - overriding(DYNAMIC_EVM_PROPERTY, "true"), - overriding(EVM_VERSION_PROPERTY, EVM_VERSION_050), - cryptoCreate(ACCOUNT), uploadInitCode(contract), contractCreate(contract)) .when() - .then(sourcing(() -> contractCall(contract, "try_kzg_precompile") - .gas(A_BUNCH_OF_GAS) - .payingWith(ACCOUNT) - .via(txnName("kzg")) - .hasKnownStatus(SUCCESS) - .logged())); + .then( + contractCall(contract, "try_transient_storage") + .gas(A_BUNCH_OF_GAS) + .hasKnownStatus(CONTRACT_EXECUTION_EXCEPTION), + contractCall(contract, "try_mcopy") + .gas(A_BUNCH_OF_GAS) + .hasKnownStatus(CONTRACT_EXECUTION_EXCEPTION), + contractCall(contract, "try_kzg_precompile").hasKnownStatus(CONTRACT_EXECUTION_EXCEPTION)); } @HapiTest - HapiSpec successTestForKZGPrecompileV050() { + final Stream verifiesExistenceOfV050Opcodes() { final var contract = Module05OpcodesExist_CONTRACT; - - return propertyPreservingHapiSpec("successTestForKZGPrecompileV050") - .preserving(EVM_VERSION_PROPERTY, DYNAMIC_EVM_PROPERTY) - .given( - overriding(DYNAMIC_EVM_PROPERTY, "true"), - overriding(EVM_VERSION_PROPERTY, EVM_VERSION_050), - cryptoCreate(ACCOUNT), - uploadInitCode(contract), - contractCreate(contract)) - .when() - .then(sourcing(() -> contractCall(contract, "kzg_precompile_success_case") - .gas(A_BUNCH_OF_GAS) - .payingWith(ACCOUNT) - .via(txnName("kzg_success")) - .hasKnownStatus(SUCCESS) - .logged())); - } - - @Override - protected Logger getResultsLogger() { - return LOG; - } - - private String txnName(final String suffix) { - return "Module0050_" + suffix; + return hapiTest( + uploadInitCode(contract), + contractCreate(contract), + contractCall(contract, "try_transient_storage").gas(A_BUNCH_OF_GAS), + contractCall(contract, "try_mcopy").gas(A_BUNCH_OF_GAS), + contractCall(contract, "try_kzg_precompile").gas(A_BUNCH_OF_GAS)); } } diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/hapi/ContractCallLocalSuite.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/hapi/ContractCallLocalSuite.java index 6ea1b61e1217..26876cd84c74 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/hapi/ContractCallLocalSuite.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/hapi/ContractCallLocalSuite.java @@ -42,6 +42,12 @@ import static com.hedera.services.bdd.spec.utilops.UtilVerbs.withOpContext; import static com.hedera.services.bdd.spec.utilops.mod.ModificationUtils.withSuccessivelyVariedQueryIds; import static com.hedera.services.bdd.spec.utilops.records.SnapshotMatchMode.NONDETERMINISTIC_TRANSACTION_FEES; +import static com.hedera.services.bdd.suites.HapiSuite.GENESIS; +import static com.hedera.services.bdd.suites.HapiSuite.ONE_HBAR; +import static com.hedera.services.bdd.suites.HapiSuite.ONE_HUNDRED_HBARS; +import static com.hedera.services.bdd.suites.HapiSuite.SECP_256K1_SHAPE; +import static com.hedera.services.bdd.suites.HapiSuite.SECP_256K1_SOURCE_KEY; +import static com.hedera.services.bdd.suites.HapiSuite.TOKEN_TREASURY; import static com.hedera.services.bdd.suites.contract.Utils.FunctionType.FUNCTION; import static com.hedera.services.bdd.suites.contract.Utils.asAddress; import static com.hedera.services.bdd.suites.contract.Utils.getABIFor; @@ -57,12 +63,9 @@ import com.google.protobuf.ByteString; import com.hedera.services.bdd.junit.HapiTest; -import com.hedera.services.bdd.junit.HapiTestSuite; -import com.hedera.services.bdd.spec.HapiSpec; import com.hedera.services.bdd.spec.HapiSpecSetup; import com.hedera.services.bdd.spec.transactions.contract.HapiParserUtil; import com.hedera.services.bdd.spec.transactions.token.TokenMovement; -import com.hedera.services.bdd.suites.HapiSuite; import com.hederahashgraph.api.proto.java.AccountID; import com.hederahashgraph.api.proto.java.ResponseCodeEnum; import com.hederahashgraph.api.proto.java.TokenType; @@ -70,17 +73,15 @@ import java.util.List; import java.util.concurrent.atomic.AtomicReference; import java.util.stream.IntStream; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; +import java.util.stream.Stream; import org.apache.tuweni.bytes.Bytes; import org.hyperledger.besu.datatypes.Address; +import org.junit.jupiter.api.DynamicTest; import org.junit.jupiter.api.Tag; -@HapiTestSuite(fuzzyMatch = true) @Tag(SMART_CONTRACT) -public class ContractCallLocalSuite extends HapiSuite { +public class ContractCallLocalSuite { - private static final Logger log = LogManager.getLogger(ContractCallLocalSuite.class); private static final String CONTRACT = "CreateTrivial"; private static final String OWNERSHIP_CHECK_CONTRACT = "OwnershipCheck"; private static final String OWNERSHIP_CHECK_CONTRACT_IS_OWNER_FUNCTION = "isOwner"; @@ -92,33 +93,8 @@ public class ContractCallLocalSuite extends HapiSuite { private static final String SYMBOL = "ħT"; private static final int DECIMALS = 13; - public static void main(String... args) { - new ContractCallLocalSuite().runSuiteSync(); - } - - @Override - public boolean canRunConcurrent() { - return true; - } - - @Override - public List getSpecsInSuite() { - return List.of( - successOnDeletedContract(), - gasBelowIntrinsicGasFails(), - insufficientGasFails(), - invalidContractID(), - impureCallFails(), - insufficientFeeFails(), - lowBalanceFails(), - erc20Query(), - vanillaSuccess(), - callLocalDoesNotCheckSignaturesNorPayer(), - htsOwnershipCheckWorksWithAliasAddress()); - } - @HapiTest - final HapiSpec htsOwnershipCheckWorksWithAliasAddress() { + final Stream htsOwnershipCheckWorksWithAliasAddress() { final AtomicReference ecdsaAccountId = new AtomicReference<>(); final AtomicReference ecdsaAccountIdLongZeroAddress = new AtomicReference<>(); final AtomicReference ecdsaAccountIdAlias = new AtomicReference<>(); @@ -192,7 +168,7 @@ final HapiSpec htsOwnershipCheckWorksWithAliasAddress() { } @HapiTest - public HapiSpec idVariantsTreatedAsExpected() { + final Stream idVariantsTreatedAsExpected() { return defaultHapiSpec("idVariantsTreatedAsExpected") .given(uploadInitCode(CONTRACT), contractCreate(CONTRACT).adminKey(THRESHOLD)) .when(contractCall(CONTRACT, "create").gas(785_000)) @@ -200,7 +176,7 @@ public HapiSpec idVariantsTreatedAsExpected() { } @HapiTest - final HapiSpec vanillaSuccess() { + final Stream vanillaSuccess() { return defaultHapiSpec("vanillaSuccess", NONDETERMINISTIC_TRANSACTION_FEES) .given(uploadInitCode(CONTRACT), contractCreate(CONTRACT).adminKey(THRESHOLD)) .when(contractCall(CONTRACT, "create").gas(785_000)) @@ -214,7 +190,7 @@ final HapiSpec vanillaSuccess() { } @HapiTest - final HapiSpec gasBelowIntrinsicGasFails() { + final Stream gasBelowIntrinsicGasFails() { return defaultHapiSpec("gasBelowIntrinsicGasFails", NONDETERMINISTIC_TRANSACTION_FEES) .given(cryptoCreate("payer"), uploadInitCode(CONTRACT), contractCreate(CONTRACT)) .when(contractCall(CONTRACT, "create").gas(785_000)) @@ -224,7 +200,7 @@ final HapiSpec gasBelowIntrinsicGasFails() { } @HapiTest - final HapiSpec insufficientGasFails() { + final Stream insufficientGasFails() { return defaultHapiSpec("insufficientGasFails", NONDETERMINISTIC_TRANSACTION_FEES) .given(cryptoCreate("payer"), uploadInitCode(CONTRACT), contractCreate(CONTRACT)) .when(contractCall(CONTRACT, "create").gas(785_000)) @@ -236,7 +212,7 @@ final HapiSpec insufficientGasFails() { } @HapiTest - final HapiSpec impureCallFails() { + final Stream impureCallFails() { return defaultHapiSpec("impureCallFails", NONDETERMINISTIC_TRANSACTION_FEES) .given(uploadInitCode(CONTRACT), contractCreate(CONTRACT).adminKey(THRESHOLD)) .when() @@ -248,9 +224,11 @@ final HapiSpec impureCallFails() { } @HapiTest - final HapiSpec successOnDeletedContract() { + final Stream successOnDeletedContract() { return defaultHapiSpec("SuccessOnDeletedContract", NONDETERMINISTIC_TRANSACTION_FEES) - .given(uploadInitCode(CONTRACT), contractCreate(CONTRACT)) + // Refusing ethereum create conversion, because we get INVALID_SIGNATURE upon tokenAssociate, + // since we have CONTRACT_ID key + .given(uploadInitCode(CONTRACT), contractCreate(CONTRACT).refusingEthConversion()) .when(contractDelete(CONTRACT)) .then(contractCallLocal(CONTRACT, "create") .nodePayment(1_234_567) @@ -258,7 +236,7 @@ final HapiSpec successOnDeletedContract() { } @HapiTest - final HapiSpec invalidContractID() { + final Stream invalidContractID() { final var invalidContract = HapiSpecSetup.getDefaultInstance().invalidContractName(); final var functionAbi = getABIFor(FUNCTION, "getIndirect", "CreateTrivial"); return defaultHapiSpec("InvalidContractID", NONDETERMINISTIC_TRANSACTION_FEES) @@ -274,7 +252,7 @@ final HapiSpec invalidContractID() { } @HapiTest - final HapiSpec insufficientFeeFails() { + final Stream insufficientFeeFails() { final long adequateQueryPayment = 500_000L; return defaultHapiSpec("insufficientFeeFails", NONDETERMINISTIC_TRANSACTION_FEES) @@ -290,7 +268,7 @@ final HapiSpec insufficientFeeFails() { } @HapiTest - final HapiSpec lowBalanceFails() { + final Stream lowBalanceFails() { final long adequateQueryPayment = 500_000_000L; return defaultHapiSpec("lowBalanceFails", NONDETERMINISTIC_TRANSACTION_FEES) @@ -313,7 +291,7 @@ final HapiSpec lowBalanceFails() { } @HapiTest - final HapiSpec erc20Query() { + final Stream erc20Query() { final var decimalsABI = "{\"constant\": true,\"inputs\": [],\"name\": \"decimals\"," + "\"outputs\": [{\"name\": \"\",\"type\": \"uint8\"}],\"payable\": false," + "\"type\": \"function\"}"; @@ -327,7 +305,7 @@ final HapiSpec erc20Query() { // https://github.com/hashgraph/hedera-services/pull/5485 @HapiTest - final HapiSpec callLocalDoesNotCheckSignaturesNorPayer() { + final Stream callLocalDoesNotCheckSignaturesNorPayer() { return defaultHapiSpec("callLocalDoesNotCheckSignaturesNorPayer", NONDETERMINISTIC_TRANSACTION_FEES) .given(uploadInitCode(CONTRACT), contractCreate(CONTRACT).adminKey(THRESHOLD)) .when(contractCall(CONTRACT, "create").gas(785_000)) @@ -339,9 +317,4 @@ final HapiSpec callLocalDoesNotCheckSignaturesNorPayer() { allRunFor(spec, create, callLocal); }))); } - - @Override - protected Logger getResultsLogger() { - return log; - } } diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/hapi/ContractCallSuite.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/hapi/ContractCallSuite.java index 630c4cffd473..1c27baa15e83 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/hapi/ContractCallSuite.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/hapi/ContractCallSuite.java @@ -33,11 +33,13 @@ import static com.hedera.services.bdd.spec.queries.QueryVerbs.contractCallLocal; import static com.hedera.services.bdd.spec.queries.QueryVerbs.contractCallLocalWithFunctionAbi; import static com.hedera.services.bdd.spec.queries.QueryVerbs.getAccountBalance; +import static com.hedera.services.bdd.spec.queries.QueryVerbs.getAccountDetails; import static com.hedera.services.bdd.spec.queries.QueryVerbs.getAccountInfo; import static com.hedera.services.bdd.spec.queries.QueryVerbs.getAliasedAccountInfo; import static com.hedera.services.bdd.spec.queries.QueryVerbs.getContractInfo; import static com.hedera.services.bdd.spec.queries.QueryVerbs.getContractRecords; import static com.hedera.services.bdd.spec.queries.QueryVerbs.getTxnRecord; +import static com.hedera.services.bdd.spec.queries.crypto.ExpectedTokenRel.relationshipWith; import static com.hedera.services.bdd.spec.transactions.TxnUtils.asId; import static com.hedera.services.bdd.spec.transactions.TxnUtils.literalInitcodeFor; import static com.hedera.services.bdd.spec.transactions.TxnVerbs.contractCall; @@ -53,6 +55,7 @@ import static com.hedera.services.bdd.spec.transactions.TxnVerbs.tokenAssociate; import static com.hedera.services.bdd.spec.transactions.TxnVerbs.tokenCreate; import static com.hedera.services.bdd.spec.transactions.TxnVerbs.uploadInitCode; +import static com.hedera.services.bdd.spec.transactions.contract.HapiContractCall.DEFAULT_ID_SENTINEL; import static com.hedera.services.bdd.spec.transactions.contract.HapiParserUtil.asHeadlongAddress; import static com.hedera.services.bdd.spec.transactions.crypto.HapiCryptoTransfer.tinyBarsFromAccountToAlias; import static com.hedera.services.bdd.spec.transactions.token.TokenMovement.moving; @@ -78,6 +81,19 @@ import static com.hedera.services.bdd.spec.utilops.records.SnapshotMatchMode.NONDETERMINISTIC_CONTRACT_CALL_RESULTS; import static com.hedera.services.bdd.spec.utilops.records.SnapshotMatchMode.NONDETERMINISTIC_FUNCTION_PARAMETERS; import static com.hedera.services.bdd.spec.utilops.records.SnapshotMatchMode.NONDETERMINISTIC_TRANSACTION_FEES; +import static com.hedera.services.bdd.suites.HapiSuite.CIVILIAN_PAYER; +import static com.hedera.services.bdd.suites.HapiSuite.DEFAULT_CONTRACT_RECEIVER; +import static com.hedera.services.bdd.suites.HapiSuite.DEFAULT_CONTRACT_SENDER; +import static com.hedera.services.bdd.suites.HapiSuite.DEFAULT_PAYER; +import static com.hedera.services.bdd.suites.HapiSuite.GENESIS; +import static com.hedera.services.bdd.suites.HapiSuite.ONE_HBAR; +import static com.hedera.services.bdd.suites.HapiSuite.ONE_HUNDRED_HBARS; +import static com.hedera.services.bdd.suites.HapiSuite.ONE_MILLION_HBARS; +import static com.hedera.services.bdd.suites.HapiSuite.SECP_256K1_RECEIVER_SOURCE_KEY; +import static com.hedera.services.bdd.suites.HapiSuite.SECP_256K1_SHAPE; +import static com.hedera.services.bdd.suites.HapiSuite.SECP_256K1_SOURCE_KEY; +import static com.hedera.services.bdd.suites.HapiSuite.TINY_PARTS_PER_WHOLE; +import static com.hedera.services.bdd.suites.HapiSuite.TOKEN_TREASURY; import static com.hedera.services.bdd.suites.contract.Utils.FunctionType.FUNCTION; import static com.hedera.services.bdd.suites.contract.Utils.asAddress; import static com.hedera.services.bdd.suites.contract.Utils.asToken; @@ -94,6 +110,7 @@ import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.INSUFFICIENT_TX_FEE; import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.INVALID_ACCOUNT_ID; import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.INVALID_CONTRACT_ID; +import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.INVALID_FEE_SUBMITTED; import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.INVALID_SIGNATURE; import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.INVALID_SOLIDITY_ADDRESS; import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.NOT_SUPPORTED; @@ -113,14 +130,13 @@ import com.esaulpaugh.headlong.abi.TypeFactory; import com.google.protobuf.ByteString; import com.hedera.services.bdd.junit.HapiTest; -import com.hedera.services.bdd.junit.HapiTestSuite; import com.hedera.services.bdd.spec.HapiPropertySource; import com.hedera.services.bdd.spec.HapiSpec; import com.hedera.services.bdd.spec.HapiSpecSetup; import com.hedera.services.bdd.spec.keys.SigControl; +import com.hedera.services.bdd.spec.transactions.TxnUtils; import com.hedera.services.bdd.spec.transactions.token.TokenMovement; import com.hedera.services.bdd.spec.utilops.CustomSpecAssert; -import com.hedera.services.bdd.suites.HapiSuite; import com.hedera.services.bdd.suites.utils.contracts.ContractCallResult; import com.hederahashgraph.api.proto.java.AccountID; import com.hederahashgraph.api.proto.java.ResponseCodeEnum; @@ -133,17 +149,19 @@ import java.util.concurrent.atomic.AtomicReference; import java.util.stream.IntStream; import java.util.stream.LongStream; +import java.util.stream.Stream; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.apache.tuweni.bytes.Bytes; import org.apache.tuweni.bytes.Bytes32; import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.DynamicTest; import org.junit.jupiter.api.Tag; -@HapiTestSuite(fuzzyMatch = true) @Tag(SMART_CONTRACT) -public class ContractCallSuite extends HapiSuite { +public class ContractCallSuite { + public static final String TOKEN = "yahcliToken"; private static final Logger LOG = LogManager.getLogger(ContractCallSuite.class); private static final String ALICE = "Alice"; @@ -208,65 +226,8 @@ public class ContractCallSuite extends HapiSuite { private static final String RECEIVER_3_INFO = "receiver3Info"; public static final String DELEGATE_CALL_SPECIFIC = "delegateCallSpecific"; - public static void main(String... args) { - new ContractCallSuite().runSuiteAsync(); - } - - @Override - public boolean canRunConcurrent() { - return true; - } - - @Override - public List getSpecsInSuite() { - return List.of( - consTimeManagementWorksWithRevertedInternalCreations(), - payableSuccess(), - depositSuccess(), - depositDeleteSuccess(), - associationAcknowledgedInApprovePrecompile(), - multipleDepositSuccess(), - payTestSelfDestructCall(), - multipleSelfDestructsAreSafe(), - smartContractInlineAssemblyCheck(), - ocToken(), - erc721TokenUriAndHtsNftInfoTreatNonUtf8BytesDifferently(), - minChargeIsTXGasUsedByContractCall(), - hscsEvm005TransferOfHBarsWorksBetweenContracts(), - hscsEvm006ContractHBarTransferToAccount(), - hscsEvm005TransfersWithSubLevelCallsBetweenContracts(), - hscsEvm010MultiSignatureAccounts(), - hscsEvm010ReceiverMustSignContractTx(), - insufficientGas(), - insufficientFee(), - nonPayable(), - smartContractFailFirst(), - contractTransferToSigReqAccountWithoutKeyFails(), - imapUserExercise(), - specialQueriesXTest(), - sendHbarsToAddressesMultipleTimes(), - sendHbarsToDifferentAddresses(), - sendHbarsFromDifferentAddressessToAddress(), - sendHbarsFromAndToDifferentAddressess(), - transferNegativeAmountOfHbarsFails(), - transferZeroHbars(), - sendHbarsToOuterContractFromDifferentAddresses(), - sendHbarsToCallerFromDifferentAddresses(), - bitcarbonTestStillPasses(), - whitelistingAliasedContract(), - cannotUseMirrorAddressOfAliasedContractInPrecompileMethod(), - exchangeRatePrecompileWorks(), - nestedContractCannotOverSendValue(), - depositMoreThanBalanceFailsGracefully(), - lowLevelEcrecCallBehavior(), - callsToSystemEntityNumsAreTreatedAsPrecompileCalls(), - hollowCreationFailsCleanly(), - repeatedCreate2FailsWithInterpretableActionSidecars(), - callStaticCallToLargeAddress()); - } - @HapiTest - public HapiSpec canHandleInvalidContractCallTransactions() { + final Stream canHandleInvalidContractCallTransactions() { return defaultHapiSpec("canHandleInvalidContractCallTransactions") .given() .when() @@ -274,7 +235,7 @@ public HapiSpec canHandleInvalidContractCallTransactions() { } @HapiTest - final HapiSpec repeatedCreate2FailsWithInterpretableActionSidecars() { + final Stream repeatedCreate2FailsWithInterpretableActionSidecars() { final var contract = "Create2PrecompileUser"; final var salt = unhex(SALT); final var firstCreation = "firstCreation"; @@ -299,7 +260,7 @@ final HapiSpec repeatedCreate2FailsWithInterpretableActionSidecars() { } @HapiTest - final HapiSpec insufficientGasToPrecompileFailsWithInterpretableActionSidecars() { + final Stream insufficientGasToPrecompileFailsWithInterpretableActionSidecars() { final var contract = "LowLevelCall"; // A real-world payload for a call to the altbn128 pairing precompile, for // completeness; c.f. https://hashscan.io/testnet/transaction/1711397022.025030483 @@ -339,7 +300,7 @@ final HapiSpec insufficientGasToPrecompileFailsWithInterpretableActionSidecars() } @HapiTest - final HapiSpec hollowCreationFailsCleanly() { + final Stream hollowCreationFailsCleanly() { final var contract = "HollowAccountCreator"; return defaultHapiSpec("HollowCreationFailsCleanly", FULLY_NONDETERMINISTIC) .given( @@ -355,11 +316,11 @@ final HapiSpec hollowCreationFailsCleanly() { } @HapiTest - final HapiSpec lowLevelEcrecCallBehavior() { + final Stream lowLevelEcrecCallBehavior() { final var TEST_CONTRACT = "TestContract"; final var somebody = "somebody"; final var account = "0.0.1"; - return defaultHapiSpec("LowLevelEcrecCallBehavior", NONDETERMINISTIC_TRANSACTION_FEES) + return defaultHapiSpec("LowLevelEcrecCallBehavior", FULLY_NONDETERMINISTIC) .given( uploadInitCode(TEST_CONTRACT), contractCreate( @@ -379,19 +340,19 @@ final HapiSpec lowLevelEcrecCallBehavior() { .hasKnownStatus(SUCCESS), contractCall(TEST_CONTRACT, "lowLevelECRECWithValue") .payingWith(somebody) - .hasKnownStatus(CONTRACT_REVERT_EXECUTED), + .hasKnownStatus(INVALID_FEE_SUBMITTED), cryptoUpdate(account).receiverSigRequired(false).signedBy(GENESIS), contractCall(TEST_CONTRACT, "lowLevelECREC") .payingWith(somebody) .hasKnownStatus(SUCCESS), contractCall(TEST_CONTRACT, "lowLevelECRECWithValue") .payingWith(somebody) - .hasKnownStatus(CONTRACT_REVERT_EXECUTED), + .hasKnownStatus(INVALID_FEE_SUBMITTED), getAccountBalance(account).hasTinyBars(changeFromSnapshot("start", +0))); } @HapiTest - final HapiSpec callsToSystemEntityNumsAreTreatedAsPrecompileCalls() { + final Stream callsToSystemEntityNumsAreTreatedAsPrecompileCalls() { final var TEST_CONTRACT = "TestContract"; final var ZERO_ADDRESS = 0L; final var zeroAddressTxn = "zeroAddressTxn"; @@ -415,7 +376,7 @@ final HapiSpec callsToSystemEntityNumsAreTreatedAsPrecompileCalls() { final var failedResult = Bytes32.repeat((byte) 0); final ContractCallResult unsuccessfulResult = () -> failedResult; final ContractCallResult successfulResult = () -> failedResult.or(Bytes.of(1)); - return defaultHapiSpec("callsToSystemEntityNumsAreTreatedAsPrecompileCalls", NONDETERMINISTIC_TRANSACTION_FEES) + return defaultHapiSpec("callsToSystemEntityNumsAreTreatedAsPrecompileCalls", FULLY_NONDETERMINISTIC) .given( uploadInitCode(TEST_CONTRACT), contractCreate( @@ -426,6 +387,7 @@ final HapiSpec callsToSystemEntityNumsAreTreatedAsPrecompileCalls() { BigInteger.ONE) .balance(ONE_HBAR)) .when( + balanceSnapshot("initialBalance", TEST_CONTRACT), // call to 0x0 contractCall( TEST_CONTRACT, @@ -444,7 +406,7 @@ final HapiSpec callsToSystemEntityNumsAreTreatedAsPrecompileCalls() { .build())) .sending(500L) .via(zeroAddressWithValueTxn) - .hasKnownStatus(SUCCESS), + .hasKnownStatus(INVALID_FEE_SUBMITTED), // call to existing account in the 0-750 range, without precompile collision on the same address contractCall( TEST_CONTRACT, @@ -464,7 +426,7 @@ final HapiSpec callsToSystemEntityNumsAreTreatedAsPrecompileCalls() { .build())) .via(existingSystemEntityWithValueTxn) .sending(500L) - .hasKnownStatus(SUCCESS), + .hasKnownStatus(INVALID_FEE_SUBMITTED), // call to existing account in the 0-750 range, WITH precompile collision contractCall( TEST_CONTRACT, @@ -483,7 +445,7 @@ final HapiSpec callsToSystemEntityNumsAreTreatedAsPrecompileCalls() { .build())) .via(existingNumAndPrecompileWithValueTxn) .sending(500L) - .hasKnownStatus(SUCCESS), + .hasKnownStatus(INVALID_FEE_SUBMITTED), // call to non-existing account in the 0-750 range contractCall( TEST_CONTRACT, @@ -502,7 +464,7 @@ final HapiSpec callsToSystemEntityNumsAreTreatedAsPrecompileCalls() { .build())) .via(nonExistingSystemEntityWithValueTxn) .sending(500L) - .hasKnownStatus(SUCCESS), + .hasKnownStatus(INVALID_FEE_SUBMITTED), // delegate call to collision address (0.0.1) contractCall( TEST_CONTRACT, @@ -530,7 +492,7 @@ final HapiSpec callsToSystemEntityNumsAreTreatedAsPrecompileCalls() { .build())) .via(delegateCallNonExistingPrecompile) .hasKnownStatus(SUCCESS), - // self destruct with beneficiary in the 0-750 range + // self-destruct with beneficiary in the 0-750 range contractCall( TEST_CONTRACT, "selfDestructWithBeneficiary", @@ -550,36 +512,31 @@ final HapiSpec callsToSystemEntityNumsAreTreatedAsPrecompileCalls() { .hasKnownStatus(SUCCESS)) .then( getTxnRecord(zeroAddressTxn) - .hasPriority(recordWith() - .contractCallResult(resultWith().contractCallResult(unsuccessfulResult))) + .hasPriority(recordWith().status(SUCCESS)) .logged(), getTxnRecord(zeroAddressWithValueTxn) - .hasPriority(recordWith() - .contractCallResult(resultWith().contractCallResult(unsuccessfulResult))) + .hasPriority(recordWith().status(INVALID_FEE_SUBMITTED)) .logged(), getTxnRecord(existingSystemEntityTxn) .hasPriority(recordWith() - .contractCallResult(resultWith().contractCallResult(unsuccessfulResult))) + .contractCallResult(resultWith().contractCallResult(successfulResult))) .logged(), getTxnRecord(existingSystemEntityWithValueTxn) - .hasPriority(recordWith() - .contractCallResult(resultWith().contractCallResult(unsuccessfulResult))) + .hasPriority(recordWith().status(INVALID_FEE_SUBMITTED)) .logged(), getTxnRecord(existingNumAndPrecompileTxn) .hasPriority(recordWith() .contractCallResult(resultWith().contractCallResult(successfulResult))) .logged(), getTxnRecord(existingNumAndPrecompileWithValueTxn) - .hasPriority(recordWith() - .contractCallResult(resultWith().contractCallResult(unsuccessfulResult))) + .hasPriority(recordWith().status(INVALID_FEE_SUBMITTED)) .logged(), getTxnRecord(nonExistingSystemEntityTxn) .hasPriority(recordWith() - .contractCallResult(resultWith().contractCallResult(unsuccessfulResult))) + .contractCallResult(resultWith().contractCallResult(successfulResult))) .logged(), getTxnRecord(nonExistingSystemEntityWithValueTxn) - .hasPriority(recordWith() - .contractCallResult(resultWith().contractCallResult(unsuccessfulResult))) + .hasPriority(recordWith().status(INVALID_FEE_SUBMITTED)) .logged(), getTxnRecord(existingNumAndPrecompileDelegateCallTxn) .hasPriority(recordWith() @@ -587,11 +544,11 @@ final HapiSpec callsToSystemEntityNumsAreTreatedAsPrecompileCalls() { .logged(), getTxnRecord(delegateCallNonExistingPrecompile) .hasPriority(recordWith() - .contractCallResult(resultWith().contractCallResult(unsuccessfulResult))) + .contractCallResult(resultWith().contractCallResult(successfulResult))) .logged(), getTxnRecord(delegateCallExistingAccountNonExistingPrecompile) .hasPriority(recordWith() - .contractCallResult(resultWith().contractCallResult(unsuccessfulResult))) + .contractCallResult(resultWith().contractCallResult(successfulResult))) .logged(), getTxnRecord(selfDestructToSystemAccountTxn) .hasPriority(recordWith() @@ -599,13 +556,13 @@ final HapiSpec callsToSystemEntityNumsAreTreatedAsPrecompileCalls() { .logged(), getTxnRecord(balanceOfSystemAccountTxn) .hasPriority(recordWith() - .contractCallResult( - resultWith().contractCallResult(() -> Bytes32.repeat((byte) 0)))) - .logged()); + .contractCallResult(resultWith().contractCallResult(unsuccessfulResult))) + .logged(), + getAccountBalance(TEST_CONTRACT).hasTinyBars(changeFromSnapshot("initialBalance", 0))); } @HapiTest - final HapiSpec depositMoreThanBalanceFailsGracefully() { + final Stream depositMoreThanBalanceFailsGracefully() { return defaultHapiSpec("depositMoreThanBalanceFailsGracefully", NONDETERMINISTIC_TRANSACTION_FEES) .given( uploadInitCode(PAY_RECEIVABLE_CONTRACT), @@ -619,7 +576,7 @@ final HapiSpec depositMoreThanBalanceFailsGracefully() { } @HapiTest - final HapiSpec nestedContractCannotOverSendValue() { + final Stream nestedContractCannotOverSendValue() { return defaultHapiSpec( "NestedContractCannotOverSendValue", NONDETERMINISTIC_CONSTRUCTOR_PARAMETERS, @@ -668,7 +625,7 @@ final HapiSpec nestedContractCannotOverSendValue() { } @HapiTest - final HapiSpec whitelistingAliasedContract() { + final Stream whitelistingAliasedContract() { final var creationTxn = "creationTxn"; final var mirrorWhitelistCheckTxn = "mirrorWhitelistCheckTxn"; final var evmWhitelistCheckTxn = "evmWhitelistCheckTxn"; @@ -725,7 +682,7 @@ final HapiSpec whitelistingAliasedContract() { } @HapiTest - final HapiSpec cannotUseMirrorAddressOfAliasedContractInPrecompileMethod() { + final Stream cannotUseMirrorAddressOfAliasedContractInPrecompileMethod() { final var creationTxn = "creationTxn"; final var ASSOCIATOR = "Associator"; @@ -784,7 +741,7 @@ final HapiSpec cannotUseMirrorAddressOfAliasedContractInPrecompileMethod() { @SuppressWarnings("java:S5669") @HapiTest - final HapiSpec bitcarbonTestStillPasses() { + final Stream bitcarbonTestStillPasses() { final var addressBook = "AddressBook"; final var jurisdictions = "Jurisdictions"; final var minters = "Minters"; @@ -802,15 +759,20 @@ final HapiSpec bitcarbonTestStillPasses() { .getAccountInfo(DEFAULT_CONTRACT_SENDER) .getContractAccountID())))), uploadInitCode(addressBook, jurisdictions), + // refusingEthConversion because the minters contract has placeholders that the + // HapiEthereumContractCreate doesn't + // support contractCreate(addressBook) .gas(1_000_000L) .exposingNumTo(num -> addressBookMirror.set(asHexedSolidityAddress(0, 0, num))) - .payingWith(DEFAULT_CONTRACT_SENDER), + .payingWith(DEFAULT_CONTRACT_SENDER) + .refusingEthConversion(), contractCreate(jurisdictions) .gas(1_000_000L) .exposingNumTo(num -> jurisdictionMirror.set(asHexedSolidityAddress(0, 0, num))) .withExplicitParams(() -> EXPLICIT_JURISDICTION_CONS_PARAMS) - .payingWith(DEFAULT_CONTRACT_SENDER), + .payingWith(DEFAULT_CONTRACT_SENDER) + .refusingEthConversion(), sourcing(() -> createLargeFile( DEFAULT_CONTRACT_SENDER, minters, @@ -819,7 +781,8 @@ final HapiSpec bitcarbonTestStillPasses() { .gas(2_000_000L) .withExplicitParams( () -> String.format(EXPLICIT_MINTER_CONS_PARAMS_TPL, jurisdictionMirror.get())) - .payingWith(DEFAULT_CONTRACT_SENDER)) + .payingWith(DEFAULT_CONTRACT_SENDER) + .refusingEthConversion()) .when( contractCall(minters) .withExplicitParams(() -> @@ -867,7 +830,7 @@ final HapiSpec bitcarbonTestStillPasses() { } @HapiTest - final HapiSpec exchangeRatePrecompileWorks() { + final Stream exchangeRatePrecompileWorks() { final var valueToTinycentCall = "recoverUsd"; final var rateAware = "ExchangeRatePrecompile"; // Must send $6.66 USD to access the gated method @@ -909,7 +872,13 @@ final HapiSpec exchangeRatePrecompileWorks() { .logged(), sourcing(() -> contractCall(rateAware, "invalidCall") .sending(minValueToAccessGatedMethodAtCurrentRate.get()) - .hasKnownStatus(CONTRACT_REVERT_EXECUTED))); + .hasKnownStatus(CONTRACT_REVERT_EXECUTED)), + sourcing(() -> contractCall( + rateAware, + "callWithValue", + BigInteger.valueOf(minValueToAccessGatedMethodAtCurrentRate.get())) + .sending(minValueToAccessGatedMethodAtCurrentRate.get()) + .hasKnownStatus(INVALID_FEE_SUBMITTED))); } /** @@ -921,7 +890,7 @@ final HapiSpec exchangeRatePrecompileWorks() { */ @SuppressWarnings("java:S5960") @HapiTest - final HapiSpec erc721TokenUriAndHtsNftInfoTreatNonUtf8BytesDifferently() { + final Stream erc721TokenUriAndHtsNftInfoTreatNonUtf8BytesDifferently() { final var contractAlternatives = "ErcAndHtsAlternatives"; final AtomicReference

    nftAddr = new AtomicReference<>(); final var viaErc721TokenURI = "erc721TokenURI"; @@ -985,7 +954,7 @@ final HapiSpec erc721TokenUriAndHtsNftInfoTreatNonUtf8BytesDifferently() { } @HapiTest - final HapiSpec imapUserExercise() { + final Stream imapUserExercise() { final var contract = "User"; final var insert1To4 = "insert1To10"; final var insert2To8 = "insert2To8"; @@ -1012,7 +981,7 @@ final HapiSpec imapUserExercise() { } @HapiTest - HapiSpec specialQueriesXTest() { + final Stream specialQueriesXTest() { final var secret = BigInteger.valueOf(123456789L); final var tinybars = BigInteger.valueOf(666_666_666L); final var erc20Symbol = "SYM20"; @@ -1180,7 +1149,7 @@ HapiSpec specialQueriesXTest() { // since we should modify the expected balances and change the test itself in order to pass with // Eth Calls @HapiTest - HapiSpec ocToken() { + final Stream ocToken() { final var contract = "OcToken"; return defaultHapiSpec( @@ -1205,6 +1174,7 @@ HapiSpec ocToken() { .gas(250_000L) .payingWith(TOKEN_ISSUER) .via("tokenCreateTxn") + .refusingEthConversion() .logged()) .when(assertionsHold((spec, ctxLog) -> { final var issuerEthAddress = spec.registry() @@ -1406,7 +1376,7 @@ private T getValueFromRegistry(HapiSpec spec, String from, Function function } @HapiTest - HapiSpec smartContractInlineAssemblyCheck() { + final Stream smartContractInlineAssemblyCheck() { final var inlineTestContract = "InlineTest"; return defaultHapiSpec("smartContractInlineAssemblyCheck", HIGHLY_NON_DETERMINISTIC_FEES) @@ -1477,7 +1447,7 @@ inlineTestContract, GET_CODE_SIZE, asHeadlongAddress(acctAddress)) } @HapiTest - final HapiSpec multipleSelfDestructsAreSafe() { + final Stream multipleSelfDestructsAreSafe() { final var contract = "Fuse"; return defaultHapiSpec("MultipleSelfDestructsAreSafe", NONDETERMINISTIC_TRANSACTION_FEES) .given(uploadInitCode(contract), contractCreate(contract).gas(600_000)) @@ -1486,7 +1456,7 @@ final HapiSpec multipleSelfDestructsAreSafe() { } @HapiTest - HapiSpec depositSuccess() { + final Stream depositSuccess() { return defaultHapiSpec("DepositSuccess", NONDETERMINISTIC_TRANSACTION_FEES) .given( uploadInitCode(PAY_RECEIVABLE_CONTRACT), @@ -1500,7 +1470,7 @@ HapiSpec depositSuccess() { } @HapiTest - HapiSpec multipleDepositSuccess() { + final Stream multipleDepositSuccess() { return defaultHapiSpec("MultipleDepositSuccess", NONDETERMINISTIC_TRANSACTION_FEES) .given( uploadInitCode(PAY_RECEIVABLE_CONTRACT), @@ -1521,13 +1491,18 @@ HapiSpec multipleDepositSuccess() { } @HapiTest - HapiSpec depositDeleteSuccess() { + final Stream depositDeleteSuccess() { final var initBalance = 7890L; return defaultHapiSpec("DepositDeleteSuccess", HIGHLY_NON_DETERMINISTIC_FEES) .given( cryptoCreate(BENEFICIARY).balance(initBalance), uploadInitCode(PAY_RECEIVABLE_CONTRACT), - contractCreate(PAY_RECEIVABLE_CONTRACT).adminKey(THRESHOLD)) + contractCreate(PAY_RECEIVABLE_CONTRACT) + .adminKey(THRESHOLD) + // Refusing ethereum create conversion, because we get INVALID_SIGNATURE upon + // tokenAssociate, + // since we have CONTRACT_ID key + .refusingEthConversion()) .when(contractCall(PAY_RECEIVABLE_CONTRACT, DEPOSIT, BigInteger.valueOf(DEPOSIT_AMOUNT)) .via(PAY_TXN) .sending(DEPOSIT_AMOUNT)) @@ -1537,7 +1512,7 @@ HapiSpec depositDeleteSuccess() { } @HapiTest - HapiSpec associationAcknowledgedInApprovePrecompile() { + final Stream associationAcknowledgedInApprovePrecompile() { final var token = "TOKEN"; final var spender = "SPENDER"; final AtomicReference
    tokenAddress = new AtomicReference<>(); @@ -1558,7 +1533,7 @@ HapiSpec associationAcknowledgedInApprovePrecompile() { } @HapiTest - HapiSpec payableSuccess() { + final Stream payableSuccess() { return defaultHapiSpec("PayableSuccess", NONDETERMINISTIC_TRANSACTION_FEES) .given( uploadInitCode(PAY_RECEIVABLE_CONTRACT), @@ -1573,22 +1548,27 @@ HapiSpec payableSuccess() { } @HapiTest - final HapiSpec idVariantsTreatedAsExpected() { + final Stream idVariantsTreatedAsExpected() { return defaultHapiSpec("idVariantsTreatedAsExpected") .given(uploadInitCode(PAY_RECEIVABLE_CONTRACT), contractCreate(PAY_RECEIVABLE_CONTRACT)) .when() - .then(submitModified(withSuccessivelyVariedBodyIds(), () -> contractCall(PAY_RECEIVABLE_CONTRACT))); + .then( + submitModified(withSuccessivelyVariedBodyIds(), () -> contractCall(PAY_RECEIVABLE_CONTRACT)), + // It's also ok to use a default PBJ ContractID (i.e. an id with + // UNSET contract oneof) to make a no-op call to address 0x00...00 + contractCall(DEFAULT_ID_SENTINEL)); } @HapiTest - HapiSpec callFailsWhenAmountIsNegativeButStillChargedFee() { + final Stream callFailsWhenAmountIsNegativeButStillChargedFee() { final var payer = "payer"; return defaultHapiSpec("callFailsWhenAmountIsNegativeButStillChargedFee") .given( uploadInitCode(PAY_RECEIVABLE_CONTRACT), contractCreate(PAY_RECEIVABLE_CONTRACT) .adminKey(THRESHOLD) - .gas(1_000_000), + .gas(1_000_000) + .refusingEthConversion(), cryptoCreate(payer).balance(ONE_MILLION_HBARS).payingWith(GENESIS)) .when(ifHapiTest(withOpContext((spec, ignore) -> { final var subop1 = balanceSnapshot("balanceBefore0", payer); @@ -1596,7 +1576,8 @@ HapiSpec callFailsWhenAmountIsNegativeButStillChargedFee() { .via(PAY_TXN) .payingWith(payer) .sending(-DEPOSIT_AMOUNT) - .hasKnownStatus(CONTRACT_NEGATIVE_VALUE); + .hasKnownStatus(CONTRACT_NEGATIVE_VALUE) + .refusingEthConversion(); final var subop3 = getTxnRecord(PAY_TXN).logged(); allRunFor(spec, subop1, subop2, subop3); final var delta = subop3.getResponseRecord() @@ -1611,7 +1592,7 @@ HapiSpec callFailsWhenAmountIsNegativeButStillChargedFee() { } @HapiTest - HapiSpec insufficientGas() { + final Stream insufficientGas() { return defaultHapiSpec("InsufficientGas", NONDETERMINISTIC_CONTRACT_CALL_RESULTS, HIGHLY_NON_DETERMINISTIC_FEES) .given( uploadInitCode(SIMPLE_STORAGE_CONTRACT), @@ -1625,20 +1606,22 @@ HapiSpec insufficientGas() { } @HapiTest - HapiSpec insufficientFee() { + final Stream insufficientFee() { final var contract = CREATE_TRIVIAL; + // FUTURE: Once we add the check again that compares the estimated gas with the maximum transaction fee, + // this test will need to be updated and expect INSUFFICIENT_TX_FEE. return defaultHapiSpec("InsufficientFee", NONDETERMINISTIC_TRANSACTION_FEES) .given(cryptoCreate("accountToPay"), uploadInitCode(contract), contractCreate(contract)) .when() .then(contractCall(contract, "create") .fee(0L) .payingWith("accountToPay") - .hasPrecheck(INSUFFICIENT_TX_FEE)); + .hasPrecheck(OK)); } @HapiTest - HapiSpec nonPayable() { + final Stream nonPayable() { final var contract = CREATE_TRIVIAL; return defaultHapiSpec("NonPayable", NONDETERMINISTIC_TRANSACTION_FEES) @@ -1653,8 +1636,9 @@ HapiSpec nonPayable() { } // This test disabled for modularization service + // Adding refusingEthConversion() due to fee differences @HapiTest - HapiSpec smartContractFailFirst() { + final Stream smartContractFailFirst() { final var civilian = "civilian"; return defaultHapiSpec("smartContractFailFirst", FULLY_NONDETERMINISTIC) .given( @@ -1668,7 +1652,8 @@ HapiSpec smartContractFailFirst() { .payingWith(civilian) .gas(1) .hasKnownStatus(INSUFFICIENT_GAS) - .via(FAIL_INSUFFICIENT_GAS); + .via(FAIL_INSUFFICIENT_GAS) + .refusingEthConversion(); final var subop3 = getTxnRecord(FAIL_INSUFFICIENT_GAS); allRunFor(spec, subop1, subop2, subop3); final var delta = subop3.getResponseRecord().getTransactionFee(); @@ -1683,7 +1668,8 @@ HapiSpec smartContractFailFirst() { .payingWith(civilian) .gas(250_000L) .via(FAIL_INVALID_INITIAL_BALANCE) - .hasKnownStatus(CONTRACT_REVERT_EXECUTED); + .hasKnownStatus(CONTRACT_REVERT_EXECUTED) + .refusingEthConversion(); final var subop3 = getTxnRecord(FAIL_INVALID_INITIAL_BALANCE); allRunFor(spec, subop1, subop2, subop3); final var delta = subop3.getResponseRecord().getTransactionFee(); @@ -1698,7 +1684,8 @@ HapiSpec smartContractFailFirst() { .payingWith(civilian) .gas(250_000L) .hasKnownStatus(SUCCESS) - .via(SUCCESS_WITH_ZERO_INITIAL_BALANCE); + .via(SUCCESS_WITH_ZERO_INITIAL_BALANCE) + .refusingEthConversion(); final var subop3 = getTxnRecord(SUCCESS_WITH_ZERO_INITIAL_BALANCE); allRunFor(spec, subop1, subop2, subop3); final var delta = subop3.getResponseRecord().getTransactionFee(); @@ -1763,8 +1750,9 @@ HapiSpec smartContractFailFirst() { } @HapiTest - HapiSpec payTestSelfDestructCall() { + final Stream payTestSelfDestructCall() { final var contract = "PayTestSelfDestruct"; + final AtomicReference
    tokenCreateContractAddress = new AtomicReference<>(); return defaultHapiSpec( "payTestSelfDestructCall", @@ -1774,7 +1762,9 @@ HapiSpec payTestSelfDestructCall() { cryptoCreate(PAYER).balance(1_000_000_000_000L).logged(), cryptoCreate(RECEIVER).balance(1_000L), uploadInitCode(contract), - contractCreate(contract)) + contractCreate(contract), + getContractInfo(contract) + .exposingEvmAddress(cb -> tokenCreateContractAddress.set(asHeadlongAddress(cb)))) .when(withOpContext((spec, opLog) -> { final var subop1 = contractCall(contract, DEPOSIT, BigInteger.valueOf(1_000L)) .payingWith(PAYER) @@ -1787,8 +1777,7 @@ HapiSpec payTestSelfDestructCall() { .gas(300_000L) .via(GET_BALANCE); - final var contractAccountId = asId(contract, spec); - final var subop3 = contractCall(contract, KILL_ME, asHeadlongAddress(asAddress(contractAccountId))) + final var subop3 = contractCall(contract, KILL_ME, tokenCreateContractAddress.get()) .payingWith(PAYER) .gas(300_000L) .hasKnownStatus(OBTAINER_SAME_CONTRACT_ID); @@ -1820,7 +1809,7 @@ HapiSpec payTestSelfDestructCall() { } @HapiTest - final HapiSpec contractTransferToSigReqAccountWithoutKeyFails() { + final Stream contractTransferToSigReqAccountWithoutKeyFails() { return defaultHapiSpec( "ContractTransferToSigReqAccountWithoutKeyFails", NONDETERMINISTIC_CONTRACT_CALL_RESULTS, @@ -1848,7 +1837,7 @@ final HapiSpec contractTransferToSigReqAccountWithoutKeyFails() { } @HapiTest - final HapiSpec minChargeIsTXGasUsedByContractCall() { + final Stream minChargeIsTXGasUsedByContractCall() { return defaultHapiSpec("MinChargeIsTXGasUsedByContractCall", NONDETERMINISTIC_TRANSACTION_FEES) .given(uploadInitCode(SIMPLE_UPDATE_CONTRACT)) .when( @@ -1869,7 +1858,7 @@ final HapiSpec minChargeIsTXGasUsedByContractCall() { } @HapiTest - final HapiSpec hscsEvm006ContractHBarTransferToAccount() { + final Stream hscsEvm006ContractHBarTransferToAccount() { return defaultHapiSpec( "hscsEvm006ContractHBarTransferToAccount", NONDETERMINISTIC_FUNCTION_PARAMETERS, @@ -1898,7 +1887,7 @@ final HapiSpec hscsEvm006ContractHBarTransferToAccount() { } @HapiTest - final HapiSpec hscsEvm005TransfersWithSubLevelCallsBetweenContracts() { + final Stream hscsEvm005TransfersWithSubLevelCallsBetweenContracts() { final var topLevelContract = "TopLevelTransferring"; final var subLevelContract = "SubLevelTransferring"; final var INITIAL_CONTRACT_BALANCE = 100; @@ -1912,8 +1901,16 @@ final HapiSpec hscsEvm005TransfersWithSubLevelCallsBetweenContracts() { cryptoCreate(ACCOUNT).balance(ONE_HUNDRED_HBARS), uploadInitCode(topLevelContract, subLevelContract)) .when( - contractCreate(topLevelContract).payingWith(ACCOUNT).balance(INITIAL_CONTRACT_BALANCE), - contractCreate(subLevelContract).payingWith(ACCOUNT).balance(INITIAL_CONTRACT_BALANCE)) + contractCreate(topLevelContract) + .payingWith(ACCOUNT) + .balance(INITIAL_CONTRACT_BALANCE) + // Adding refusingEthConversion() due to fee differences + .refusingEthConversion(), + contractCreate(subLevelContract) + .payingWith(ACCOUNT) + .balance(INITIAL_CONTRACT_BALANCE) + // Adding refusingEthConversion() due to fee differences + .refusingEthConversion()) .then( contractCall(topLevelContract).sending(10).payingWith(ACCOUNT), getAccountBalance(topLevelContract).hasTinyBars(INITIAL_CONTRACT_BALANCE + 10L), @@ -1960,7 +1957,7 @@ final HapiSpec hscsEvm005TransfersWithSubLevelCallsBetweenContracts() { } @HapiTest - final HapiSpec hscsEvm005TransferOfHBarsWorksBetweenContracts() { + final Stream hscsEvm005TransferOfHBarsWorksBetweenContracts() { final var to = "To"; return defaultHapiSpec( @@ -1971,9 +1968,12 @@ final HapiSpec hscsEvm005TransferOfHBarsWorksBetweenContracts() { cryptoCreate(ACCOUNT).balance(ONE_HUNDRED_HBARS), uploadInitCode(TRANSFERRING_CONTRACT), contractCreate(TRANSFERRING_CONTRACT).balance(10_000L).payingWith(ACCOUNT), + // refuse eth conversion because we can't call contract by contract num + // when it has EVM address alias (isNotPriority check fails) contractCustomCreate(TRANSFERRING_CONTRACT, to) .balance(10_000L) - .payingWith(ACCOUNT), + .payingWith(ACCOUNT) + .refusingEthConversion(), getContractInfo(TRANSFERRING_CONTRACT).saveToRegistry(CONTRACT_FROM), getContractInfo(TRANSFERRING_CONTRACT + to).saveToRegistry("contract_to"), getAccountInfo(ACCOUNT).savingSnapshot(ACCOUNT_INFO)) @@ -1996,7 +1996,7 @@ final HapiSpec hscsEvm005TransferOfHBarsWorksBetweenContracts() { } @HapiTest - final HapiSpec hscsEvm010ReceiverMustSignContractTx() { + final Stream hscsEvm010ReceiverMustSignContractTx() { final var ACC = "acc"; final var RECEIVER_KEY = "receiverKey"; return defaultHapiSpec( @@ -2004,6 +2004,7 @@ final HapiSpec hscsEvm010ReceiverMustSignContractTx() { NONDETERMINISTIC_FUNCTION_PARAMETERS, NONDETERMINISTIC_TRANSACTION_FEES) .given( + streamMustIncludeNoFailuresFrom(sidecarIdValidator()), newKeyNamed(RECEIVER_KEY), cryptoCreate(ACC) .balance(5 * ONE_HUNDRED_HBARS) @@ -2035,8 +2036,9 @@ final HapiSpec hscsEvm010ReceiverMustSignContractTx() { })); } + // Adding refusingEthConversion() due to fee differences @HapiTest - final HapiSpec hscsEvm010MultiSignatureAccounts() { + final Stream hscsEvm010MultiSignatureAccounts() { final var ACC = "acc"; final var PAYER_KEY = "pkey"; final var OTHER_KEY = "okey"; @@ -2060,12 +2062,14 @@ final HapiSpec hscsEvm010MultiSignatureAccounts() { .payingWith(ACC) .signedBy(PAYER_KEY) .adminKey(KEY_LIST) - .hasPrecheck(INVALID_SIGNATURE), + .hasPrecheck(INVALID_SIGNATURE) + .refusingEthConversion(), contractCreate(TRANSFERRING_CONTRACT) .payingWith(ACC) .signedBy(PAYER_KEY, OTHER_KEY) .balance(10) - .adminKey(KEY_LIST)) + .adminKey(KEY_LIST) + .refusingEthConversion()) .then(withOpContext((spec, log) -> { final var acc = spec.registry().getAccountInfo(ACC_INFO).getContractAccountID(); final var assertionWithOnlyOneKey = contractCall( @@ -2093,7 +2097,7 @@ final HapiSpec hscsEvm010MultiSignatureAccounts() { } @HapiTest - final HapiSpec sendHbarsToAddressesMultipleTimes() { + final Stream sendHbarsToAddressesMultipleTimes() { return defaultHapiSpec( "sendHbarsToAddressesMultipleTimes", NONDETERMINISTIC_FUNCTION_PARAMETERS, @@ -2124,7 +2128,7 @@ final HapiSpec sendHbarsToAddressesMultipleTimes() { } @HapiTest - final HapiSpec sendHbarsToDifferentAddresses() { + final Stream sendHbarsToDifferentAddresses() { return defaultHapiSpec( "sendHbarsToDifferentAddresses", NONDETERMINISTIC_FUNCTION_PARAMETERS, @@ -2167,7 +2171,7 @@ final HapiSpec sendHbarsToDifferentAddresses() { } @HapiTest - final HapiSpec sendHbarsFromDifferentAddressessToAddress() { + final Stream sendHbarsFromDifferentAddressessToAddress() { return defaultHapiSpec( "sendHbarsFromDifferentAddressessToAddress", NONDETERMINISTIC_FUNCTION_PARAMETERS, @@ -2215,7 +2219,7 @@ final HapiSpec sendHbarsFromDifferentAddressessToAddress() { } @HapiTest - final HapiSpec sendHbarsToOuterContractFromDifferentAddresses() { + final Stream sendHbarsToOuterContractFromDifferentAddresses() { return defaultHapiSpec( "sendHbarsToOuterContractFromDifferentAddresses", NONDETERMINISTIC_FUNCTION_PARAMETERS, @@ -2256,7 +2260,7 @@ final HapiSpec sendHbarsToOuterContractFromDifferentAddresses() { } @HapiTest - final HapiSpec sendHbarsToCallerFromDifferentAddresses() { + final Stream sendHbarsToCallerFromDifferentAddresses() { return defaultHapiSpec( "sendHbarsToCallerFromDifferentAddresses", NONDETERMINISTIC_CONSTRUCTOR_PARAMETERS, @@ -2282,10 +2286,16 @@ final HapiSpec sendHbarsToCallerFromDifferentAddresses() { final var nestedTransferringUpload = uploadInitCode(NESTED_TRANSFERRING_CONTRACT, NESTED_TRANSFER_CONTRACT); - final var createFirstNestedContract = - contractCustomCreate(NESTED_TRANSFER_CONTRACT, "1").balance(10_000L); - final var createSecondNestedContract = - contractCustomCreate(NESTED_TRANSFER_CONTRACT, "2").balance(10_000L); + final var createFirstNestedContract = contractCustomCreate(NESTED_TRANSFER_CONTRACT, "1") + .balance(10_000L) + + // Adding refusingEthConversion() due to fee differences + .refusingEthConversion(); + final var createSecondNestedContract = contractCustomCreate(NESTED_TRANSFER_CONTRACT, "2") + .balance(10_000L) + + // Adding refusingEthConversion() due to fee differences + .refusingEthConversion(); final var transfer2 = cryptoTransfer( TokenMovement.movingHbar(10_000_000L).between(GENESIS, DEFAULT_CONTRACT_RECEIVER)); final var saveSnapshot = getAccountInfo(DEFAULT_CONTRACT_RECEIVER) @@ -2310,7 +2320,10 @@ final HapiSpec sendHbarsToCallerFromDifferentAddresses() { asHeadlongAddress( getNestedContractAddress(NESTED_TRANSFER_CONTRACT + "2", spec))) .balance(10_000L) - .payingWith(GENESIS), + .payingWith(GENESIS) + + // Adding refusingEthConversion() due to fee differences + .refusingEthConversion(), contractCall( NESTED_TRANSFERRING_CONTRACT, "transferToCallerFromDifferentAddresses", @@ -2346,7 +2359,7 @@ final HapiSpec sendHbarsToCallerFromDifferentAddresses() { } @HapiTest - final HapiSpec sendHbarsFromAndToDifferentAddressess() { + final Stream sendHbarsFromAndToDifferentAddressess() { return defaultHapiSpec( "sendHbarsFromAndToDifferentAddressess", NONDETERMINISTIC_CONSTRUCTOR_PARAMETERS, @@ -2406,8 +2419,9 @@ final HapiSpec sendHbarsFromAndToDifferentAddressess() { .has(contractWith().balance(10_000 - 60L)))); } + // Adding refusingEthConversion() due to fee differences @HapiTest - final HapiSpec transferNegativeAmountOfHbarsFails() { + final Stream transferNegativeAmountOfHbarsFails() { return defaultHapiSpec( "transferNegativeAmountOfHbarsFails", NONDETERMINISTIC_FUNCTION_PARAMETERS, @@ -2416,7 +2430,10 @@ final HapiSpec transferNegativeAmountOfHbarsFails() { cryptoCreate(ACCOUNT).balance(ONE_HUNDRED_HBARS), cryptoCreate(RECEIVER).balance(10_000L), uploadInitCode(TRANSFERRING_CONTRACT), - contractCreate(TRANSFERRING_CONTRACT).balance(10_000L).payingWith(ACCOUNT), + contractCreate(TRANSFERRING_CONTRACT) + .balance(10_000L) + .payingWith(ACCOUNT) + .refusingEthConversion(), getAccountInfo(RECEIVER).savingSnapshot(RECEIVER_INFO)) .when(withOpContext((spec, log) -> { var receiverAddr = @@ -2444,14 +2461,15 @@ final HapiSpec transferNegativeAmountOfHbarsFails() { } @HapiTest - final HapiSpec transferZeroHbars() { + final Stream transferZeroHbars() { return defaultHapiSpec( "transferZeroHbars", NONDETERMINISTIC_FUNCTION_PARAMETERS, NONDETERMINISTIC_TRANSACTION_FEES) .given( cryptoCreate(ACCOUNT).balance(ONE_HUNDRED_HBARS), cryptoCreate(RECEIVER).balance(10_000L), uploadInitCode(TRANSFERRING_CONTRACT), - contractCreate(TRANSFERRING_CONTRACT).balance(10_000L), + // Adding refusingEthConversion() due to fee differences + contractCreate(TRANSFERRING_CONTRACT).balance(10_000L).refusingEthConversion(), getAccountInfo(RECEIVER).savingSnapshot(RECEIVER_INFO)) .when(withOpContext((spec, log) -> { var receiverAddr = @@ -2483,7 +2501,7 @@ final HapiSpec transferZeroHbars() { } @HapiTest - final HapiSpec consTimeManagementWorksWithRevertedInternalCreations() { + final Stream consTimeManagementWorksWithRevertedInternalCreations() { final var contract = "ConsTimeRepro"; final var failingCall = "FailingCall"; final AtomicReference parentConsTime = new AtomicReference<>(); @@ -2511,7 +2529,7 @@ final HapiSpec consTimeManagementWorksWithRevertedInternalCreations() { } @HapiTest - final HapiSpec callStaticCallToLargeAddress() { + final Stream callStaticCallToLargeAddress() { final var txn = "txn"; final var contract = "CallInConstructor"; return defaultHapiSpec("callStaticAddress") @@ -2531,13 +2549,83 @@ final var record = op.getResponseRecord(); })); } - private String getNestedContractAddress(final String contract, final HapiSpec spec) { - return HapiPropertySource.asHexedSolidityAddress(spec.registry().getContractId(contract)); + @HapiTest + final Stream htsCallWithInsufficientGasHasNoStateChanges() { + final var contract = "LowLevelCall"; + final var htsSystemContractAddress = asHeadlongAddress("0x0167"); + final var transferToken = new Function("transferToken(address,address,address,int64)", "(int64)"); + final AtomicReference
    treasuryAddress = new AtomicReference<>(); + final AtomicReference
    receiverAddress = new AtomicReference<>(); + final AtomicReference
    tokenAddress = new AtomicReference<>(); + final var initialSupply = 100L; + return defaultHapiSpec("htsCallWithInsufficientGasHasNoStateChanges") + .given( + cryptoCreate(TOKEN_TREASURY).exposingEvmAddressTo(treasuryAddress::set), + cryptoCreate(CIVILIAN_PAYER) + .exposingEvmAddressTo(receiverAddress::set) + .maxAutomaticTokenAssociations(1), + tokenCreate(TOKEN) + .treasury(TOKEN_TREASURY) + .initialSupply(initialSupply) + .exposingAddressTo(tokenAddress::set), + uploadInitCode(contract), + contractCreate(contract), + cryptoApproveAllowance() + .addTokenAllowance(TOKEN_TREASURY, TOKEN, contract, 100) + .signedBy(DEFAULT_PAYER, TOKEN_TREASURY)) + .when() + .then( + // Call transferToken() with insufficent gas + sourcing(() -> contractCall( + contract, + "callRequestedAndIgnoreFailure", + htsSystemContractAddress, + transferToken + .encodeCallWithArgs( + tokenAddress.get(), + treasuryAddress.get(), + receiverAddress.get(), + 13L) + .array(), + BigInteger.valueOf(13_000L)) + .via("callTxn")), + childRecordsCheck("callTxn", SUCCESS, recordWith().status(INSUFFICIENT_GAS)), + // Verify no token balances changed + getAccountDetails(TOKEN_TREASURY) + .hasToken(relationshipWith(TOKEN).balance(initialSupply)), + getAccountDetails(CIVILIAN_PAYER).hasNoTokenRelationship(TOKEN)); + } + + @HapiTest + final Stream callToNonExtantLongZeroAddressUsesTargetedAddress() { + final var contract = "LowLevelCall"; + final var nonExtantMirrorAddress = asHeadlongAddress("0xE8D4A50FFF"); + return defaultHapiSpec("callToNonExtantLongZeroAddressUsesTargetedAddress") + .given( + streamMustIncludeNoFailuresFrom(sidecarIdValidator()), + uploadInitCode(contract), + contractCreate(contract)) + .when() + .then(contractCall( + contract, "callRequested", nonExtantMirrorAddress, new byte[0], BigInteger.valueOf(88_888L))); + } + + @HapiTest + final Stream callToNonExtantEvmAddressUsesTargetedAddress() { + final var contract = "LowLevelCall"; + final var nonExtantEvmAddress = asHeadlongAddress(TxnUtils.randomUtf8Bytes(20)); + return defaultHapiSpec("callToNonExtantEvmAddressUsesTargetedAddress") + .given( + streamMustIncludeNoFailuresFrom(sidecarIdValidator()), + uploadInitCode(contract), + contractCreate(contract)) + .when() + .then(contractCall( + contract, "callRequested", nonExtantEvmAddress, new byte[0], BigInteger.valueOf(88_888L))); } - @Override - protected Logger getResultsLogger() { - return LOG; + private String getNestedContractAddress(final String contract, final HapiSpec spec) { + return HapiPropertySource.asHexedSolidityAddress(spec.registry().getContractId(contract)); } private ByteString bookInterpolated(final byte[] jurisdictionInitcode, final String addressBookMirror) { diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/hapi/ContractCallV1SecurityModelSuite.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/hapi/ContractCallV1SecurityModelSuite.java index e77918701537..fefc32e2fe32 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/hapi/ContractCallV1SecurityModelSuite.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/hapi/ContractCallV1SecurityModelSuite.java @@ -54,7 +54,6 @@ import com.google.protobuf.ByteString; import com.hedera.services.bdd.spec.HapiPropertySource; -import com.hedera.services.bdd.spec.HapiSpec; import com.hedera.services.bdd.spec.keys.KeyShape; import com.hedera.services.bdd.spec.transactions.contract.HapiContractCreate; import com.hedera.services.bdd.suites.HapiSuite; @@ -64,8 +63,10 @@ import java.util.List; import java.util.concurrent.atomic.AtomicLong; import java.util.concurrent.atomic.AtomicReference; +import java.util.stream.Stream; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; +import org.junit.jupiter.api.DynamicTest; public class ContractCallV1SecurityModelSuite extends HapiSuite { @@ -91,7 +92,7 @@ public boolean canRunConcurrent() { } @Override - public List getSpecsInSuite() { + public List> getSpecsInSuite() { return List.of( contractTransferToSigReqAccountWithKeySucceeds(), canMintAndTransferInSameContractOperation(), @@ -99,7 +100,7 @@ public List getSpecsInSuite() { lpFarmSimulation()); } - final HapiSpec workingHoursDemo() { + final Stream workingHoursDemo() { final var gasToOffer = 4_000_000; final var contract = "WorkingHours"; final var ticketToken = "ticketToken"; @@ -184,7 +185,7 @@ final HapiSpec workingHoursDemo() { getTxnRecord(ticketWorking).andAllChildRecords().logged()); } - final HapiSpec canMintAndTransferInSameContractOperation() { + final Stream canMintAndTransferInSameContractOperation() { final AtomicReference tokenMirrorAddr = new AtomicReference<>(); final AtomicReference aCivilianMirrorAddr = new AtomicReference<>(); final var nfToken = "nfToken"; @@ -251,7 +252,7 @@ final HapiSpec canMintAndTransferInSameContractOperation() { .alsoSigningWithFullPrefix(multiKey, aCivilian))); } - final HapiSpec contractTransferToSigReqAccountWithKeySucceeds() { + final Stream contractTransferToSigReqAccountWithKeySucceeds() { return propertyPreservingHapiSpec("ContractTransferToSigReqAccountWithKeySucceeds") .preserving(CONTRACTS_ALLOW_SYSTEM_USE_OF_HAPI_SIGS, CONTRACTS_MAX_NUM_WITH_HAPI_SIGS_ACCESS) .given( @@ -301,7 +302,7 @@ final HapiSpec contractTransferToSigReqAccountWithKeySucceeds() { })); } - final HapiSpec lpFarmSimulation() { + final Stream lpFarmSimulation() { final var adminKey = "adminKey"; final var gasToOffer = 4_000_000; final var farmInitcodeLoc = "src/main/resource/contract/bytecodes/farmInitcode.bin"; diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/hapi/ContractCreateSuite.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/hapi/ContractCreateSuite.java index 124c0daf77e8..8e7329fa1545 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/hapi/ContractCreateSuite.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/hapi/ContractCreateSuite.java @@ -17,8 +17,10 @@ package com.hedera.services.bdd.suites.contract.hapi; import static com.hedera.node.app.hapi.utils.ethereum.EthTxSigs.signMessage; +import static com.hedera.services.bdd.junit.ContextRequirement.PROPERTY_OVERRIDES; import static com.hedera.services.bdd.junit.TestTags.SMART_CONTRACT; import static com.hedera.services.bdd.spec.HapiSpec.defaultHapiSpec; +import static com.hedera.services.bdd.spec.HapiSpec.propertyPreservingHapiSpec; import static com.hedera.services.bdd.spec.assertions.AccountInfoAsserts.accountWith; import static com.hedera.services.bdd.spec.assertions.AssertUtils.inOrder; import static com.hedera.services.bdd.spec.assertions.ContractFnResultAsserts.isContractWith; @@ -57,6 +59,7 @@ import static com.hedera.services.bdd.spec.utilops.UtilVerbs.getPrivateKeyFromSpec; import static com.hedera.services.bdd.spec.utilops.UtilVerbs.newKeyListNamed; import static com.hedera.services.bdd.spec.utilops.UtilVerbs.newKeyNamed; +import static com.hedera.services.bdd.spec.utilops.UtilVerbs.overriding; import static com.hedera.services.bdd.spec.utilops.UtilVerbs.sleepFor; import static com.hedera.services.bdd.spec.utilops.UtilVerbs.sourcing; import static com.hedera.services.bdd.spec.utilops.UtilVerbs.submitModified; @@ -67,6 +70,16 @@ import static com.hedera.services.bdd.spec.utilops.records.SnapshotMatchMode.NONDETERMINISTIC_FUNCTION_PARAMETERS; import static com.hedera.services.bdd.spec.utilops.records.SnapshotMatchMode.NONDETERMINISTIC_NONCE; import static com.hedera.services.bdd.spec.utilops.records.SnapshotMatchMode.NONDETERMINISTIC_TRANSACTION_FEES; +import static com.hedera.services.bdd.suites.HapiSuite.CHAIN_ID; +import static com.hedera.services.bdd.suites.HapiSuite.DEFAULT_PAYER; +import static com.hedera.services.bdd.suites.HapiSuite.FUNDING; +import static com.hedera.services.bdd.suites.HapiSuite.GENESIS; +import static com.hedera.services.bdd.suites.HapiSuite.ONE_HUNDRED_HBARS; +import static com.hedera.services.bdd.suites.HapiSuite.ONE_MILLION_HBARS; +import static com.hedera.services.bdd.suites.HapiSuite.RELAYER; +import static com.hedera.services.bdd.suites.HapiSuite.SECP_256K1_SHAPE; +import static com.hedera.services.bdd.suites.HapiSuite.SECP_256K1_SOURCE_KEY; +import static com.hedera.services.bdd.suites.HapiSuite.ZERO_BYTE_MEMO; import static com.hedera.services.bdd.suites.contract.Utils.FunctionType.FUNCTION; import static com.hedera.services.bdd.suites.contract.Utils.getABIFor; import static com.hedera.services.bdd.suites.contract.hapi.ContractUpdateSuite.ADMIN_KEY; @@ -93,14 +106,12 @@ import com.google.protobuf.ByteString; import com.hedera.node.app.hapi.utils.ethereum.EthTxData; import com.hedera.services.bdd.junit.HapiTest; -import com.hedera.services.bdd.junit.HapiTestSuite; -import com.hedera.services.bdd.spec.HapiSpec; +import com.hedera.services.bdd.junit.LeakyHapiTest; import com.hedera.services.bdd.spec.assertions.ContractInfoAsserts; import com.hedera.services.bdd.spec.keys.KeyShape; import com.hedera.services.bdd.spec.transactions.TxnUtils; import com.hedera.services.bdd.spec.transactions.crypto.HapiCryptoTransfer; import com.hedera.services.bdd.spec.utilops.UtilVerbs; -import com.hedera.services.bdd.suites.HapiSuite; import com.hederahashgraph.api.proto.java.FileID; import com.hederahashgraph.api.proto.java.Key; import com.swirlds.common.utility.CommonUtils; @@ -112,19 +123,19 @@ import java.util.Arrays; import java.util.List; import java.util.concurrent.atomic.AtomicLong; +import java.util.stream.Stream; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.apache.tuweni.bytes.Bytes32; +import org.junit.jupiter.api.DynamicTest; import org.junit.jupiter.api.Tag; -@HapiTestSuite(fuzzyMatch = true) @Tag(SMART_CONTRACT) -public class ContractCreateSuite extends HapiSuite { +public class ContractCreateSuite { public static final String EMPTY_CONSTRUCTOR_CONTRACT = "EmptyConstructor"; public static final String PARENT_INFO = "parentInfo"; private static final String PAYER = "payer"; - private static final String PATTERN = "0.0.%d"; private static final Logger log = LogManager.getLogger(ContractCreateSuite.class); @@ -136,46 +147,8 @@ public class ContractCreateSuite extends HapiSuite { private static final String EXPECTED_DEPLOYER_ADDRESS = "4e59b44847b379578588920ca78fbf26c0b4956c"; private static final String DEPLOYER = "DeployerContract"; - public static void main(String... args) { - new ContractCreateSuite().runSuiteAsync(); - } - - @Override - public List getSpecsInSuite() { - return List.of( - createDeterministicDeployer(), - createEmptyConstructor(), - insufficientPayerBalanceUponCreation(), - rejectsInvalidMemo(), - rejectsInsufficientFee(), - rejectsInvalidBytecode(), - revertsNonzeroBalance(), - createFailsIfMissingSigs(), - rejectsInsufficientGas(), - createsVanillaContractAsExpectedWithOmittedAdminKey(), - childCreationsHaveExpectedKeysWithOmittedAdminKey(), - cannotCreateTooLargeContract(), - revertedTryExtCallHasNoSideEffects(), - cannotSendToNonExistentAccount(), - invalidSystemInitcodeFileFailsWithInvalidFileId(), - delegateContractIdRequiredForTransferInDelegateCall(), - vanillaSuccess(), - blockTimestampChangesWithinFewSeconds(), - contractWithAutoRenewNeedSignatures(), - newAccountsCanUsePureContractIdKey(), - createContractWithStakingFields(), - disallowCreationsOfEmptyInitCode(), - createCallInConstructor(), - cannotSetMaxAutomaticAssociations()); - } - - @Override - public boolean canRunConcurrent() { - return true; - } - @HapiTest - final HapiSpec createDeterministicDeployer() { + final Stream createDeterministicDeployer() { final var creatorAddress = ByteString.copyFrom(CommonUtils.unhex(DEPLOYMENT_SIGNER)); final var transaction = ByteString.copyFrom(CommonUtils.unhex(DEPLOYMENT_TRANSACTION)); final var systemFileId = FileID.newBuilder().setFileNum(159).build(); @@ -195,15 +168,17 @@ final HapiSpec createDeterministicDeployer() { } @HapiTest - HapiSpec createContractWithStakingFields() { + final Stream createContractWithStakingFields() { final var contract = "CreateTrivial"; return defaultHapiSpec("createContractWithStakingFields", HIGHLY_NON_DETERMINISTIC_FEES) .given( uploadInitCode(contract), + // refuse eth conversion because ethereum transaction is missing staking fields to map contractCreate(contract) .adminKey(THRESHOLD) .declinedReward(true) - .stakedNodeId(0), + .stakedNodeId(0) + .refusingEthConversion(), getContractInfo(contract) .has(contractWith() .isDeclinedReward(true) @@ -214,7 +189,8 @@ HapiSpec createContractWithStakingFields() { contractCreate(contract) .adminKey(THRESHOLD) .declinedReward(true) - .stakedAccountId("0.0.10"), + .stakedAccountId("0.0.10") + .refusingEthConversion(), getContractInfo(contract) .has(contractWith() .isDeclinedReward(true) @@ -225,7 +201,8 @@ HapiSpec createContractWithStakingFields() { contractCreate(contract) .adminKey(THRESHOLD) .declinedReward(false) - .stakedNodeId(0), + .stakedNodeId(0) + .refusingEthConversion(), getContractInfo(contract) .has(contractWith() .isDeclinedReward(false) @@ -235,7 +212,8 @@ HapiSpec createContractWithStakingFields() { contractCreate(contract) .adminKey(THRESHOLD) .declinedReward(false) - .stakedAccountId("0.0.10"), + .stakedAccountId("0.0.10") + .refusingEthConversion(), getContractInfo(contract) .has(contractWith() .isDeclinedReward(false) @@ -247,16 +225,18 @@ HapiSpec createContractWithStakingFields() { .adminKey(THRESHOLD) .declinedReward(false) .stakedAccountId("0.0.0") - .hasPrecheck(INVALID_STAKING_ID), + .hasPrecheck(INVALID_STAKING_ID) + .refusingEthConversion(), contractCreate(contract) .adminKey(THRESHOLD) .declinedReward(false) .stakedNodeId(-1L) - .hasPrecheck(INVALID_STAKING_ID)); + .hasPrecheck(INVALID_STAKING_ID) + .refusingEthConversion()); } @HapiTest - final HapiSpec insufficientPayerBalanceUponCreation() { + final Stream insufficientPayerBalanceUponCreation() { return defaultHapiSpec("InsufficientPayerBalanceUponCreation", NONDETERMINISTIC_TRANSACTION_FEES) .given(cryptoCreate("bankrupt").balance(0L), uploadInitCode(EMPTY_CONSTRUCTOR_CONTRACT)) .when() @@ -266,22 +246,25 @@ final HapiSpec insufficientPayerBalanceUponCreation() { } @HapiTest - private HapiSpec disallowCreationsOfEmptyInitCode() { + final Stream disallowCreationsOfEmptyInitCode() { final var contract = "EmptyContract"; return defaultHapiSpec("allowCreationsOfEmptyContract") .given( newKeyNamed(ADMIN_KEY), + // refuse eth conversion because we can't set invalid bytecode to callData in ethereum + // transaction contractCreate(contract) .adminKey(ADMIN_KEY) .entityMemo("Empty Contract") .inlineInitCode(ByteString.EMPTY) - .hasKnownStatus(CONTRACT_BYTECODE_EMPTY)) + .hasKnownStatus(CONTRACT_BYTECODE_EMPTY) + .refusingEthConversion()) .when() .then(); } @HapiTest - HapiSpec cannotSendToNonExistentAccount() { + final Stream cannotSendToNonExistentAccount() { final var contract = "Multipurpose"; Object[] donationArgs = new Object[] {666666L, "Hey, Ma!"}; @@ -292,7 +275,7 @@ HapiSpec cannotSendToNonExistentAccount() { } @HapiTest - HapiSpec invalidSystemInitcodeFileFailsWithInvalidFileId() { + final Stream invalidSystemInitcodeFileFailsWithInvalidFileId() { final var neverToBe = "NeverToBe"; final var systemFileId = FileID.newBuilder().setFileNum(159).build(); return defaultHapiSpec("InvalidSystemInitcodeFileFailsWithInvalidFileId") @@ -302,7 +285,10 @@ HapiSpec invalidSystemInitcodeFileFailsWithInvalidFileId() { .when() .then( explicitContractCreate(neverToBe, (spec, b) -> b.setFileID(systemFileId)) - .hasKnownStatus(INVALID_FILE_ID), + // refuse eth conversion because we can't set invalid bytecode to callData in ethereum + // transaction + .hasKnownStatus(INVALID_FILE_ID) + .refusingEthConversion(), explicitEthereumTransaction(neverToBe, (spec, b) -> { final var signedEthTx = signMessage( placeholderEthTx(), getPrivateKeyFromSpec(spec, SECP_256K1_SOURCE_KEY)); @@ -313,7 +299,7 @@ HapiSpec invalidSystemInitcodeFileFailsWithInvalidFileId() { } @HapiTest - final HapiSpec createsVanillaContractAsExpectedWithOmittedAdminKey() { + final Stream createsVanillaContractAsExpectedWithOmittedAdminKey() { return defaultHapiSpec("createsVanillaContractAsExpectedWithOmittedAdminKey") .given(uploadInitCode(EMPTY_CONSTRUCTOR_CONTRACT)) .when() @@ -324,16 +310,19 @@ final HapiSpec createsVanillaContractAsExpectedWithOmittedAdminKey() { .logged()); } - @HapiTest - final HapiSpec childCreationsHaveExpectedKeysWithOmittedAdminKey() { + @LeakyHapiTest(PROPERTY_OVERRIDES) + final Stream childCreationsHaveExpectedKeysWithOmittedAdminKey() { final AtomicLong firstStickId = new AtomicLong(); final AtomicLong secondStickId = new AtomicLong(); final AtomicLong thirdStickId = new AtomicLong(); final var txn = "creation"; final var contract = "Fuse"; - return defaultHapiSpec("ChildCreationsHaveExpectedKeysWithOmittedAdminKey", NONDETERMINISTIC_TRANSACTION_FEES) + return propertyPreservingHapiSpec( + "ChildCreationsHaveExpectedKeysWithOmittedAdminKey", NONDETERMINISTIC_TRANSACTION_FEES) + .preserving("contracts.evm.version") .given( + overriding("contracts.evm.version", "v0.46"), uploadInitCode(contract), contractCreate(contract).omitAdminKey().gas(600_000).via(txn), withOpContext((spec, opLog) -> { @@ -367,7 +356,7 @@ final var record = op.getResponseRecord(); } @HapiTest - final HapiSpec createEmptyConstructor() { + final Stream createEmptyConstructor() { return defaultHapiSpec("createEmptyConstructor", NONDETERMINISTIC_TRANSACTION_FEES) .given(uploadInitCode(EMPTY_CONSTRUCTOR_CONTRACT)) .when() @@ -375,7 +364,7 @@ final HapiSpec createEmptyConstructor() { } @HapiTest - final HapiSpec createCallInConstructor() { + final Stream createCallInConstructor() { final var txn = "txn"; return defaultHapiSpec("callInConstructor") .given(uploadInitCode("CallInConstructor")) @@ -397,7 +386,7 @@ final var record = op.getResponseRecord(); } @HapiTest - final HapiSpec revertedTryExtCallHasNoSideEffects() { + final Stream revertedTryExtCallHasNoSideEffects() { final var balance = 3_000; final int sendAmount = balance / 3; final var contract = "RevertingSendTry"; @@ -430,7 +419,7 @@ final HapiSpec revertedTryExtCallHasNoSideEffects() { } @HapiTest - final HapiSpec createFailsIfMissingSigs() { + final Stream createFailsIfMissingSigs() { final var shape = listOf(SIMPLE, threshOf(2, 3), threshOf(1, 3)); final var validSig = shape.signedWith(sigs(ON, sigs(ON, ON, OFF), sigs(OFF, OFF, ON))); final var invalidSig = shape.signedWith(sigs(OFF, sigs(ON, ON, OFF), sigs(OFF, OFF, ON))); @@ -442,23 +431,36 @@ final HapiSpec createFailsIfMissingSigs() { contractCreate(EMPTY_CONSTRUCTOR_CONTRACT) .adminKeyShape(shape) .sigControl(forKey(EMPTY_CONSTRUCTOR_CONTRACT, invalidSig)) - .hasKnownStatus(INVALID_SIGNATURE), + .hasKnownStatus(INVALID_SIGNATURE) + // Refusing ethereum create conversion, because we get INVALID_SIGNATURE upon + // tokenAssociate, + // since we have CONTRACT_ID key + .refusingEthConversion(), contractCreate(EMPTY_CONSTRUCTOR_CONTRACT) .adminKeyShape(shape) .sigControl(forKey(EMPTY_CONSTRUCTOR_CONTRACT, validSig)) - .hasKnownStatus(SUCCESS)); + .hasKnownStatus(SUCCESS) + // Refusing ethereum create conversion, because we get INVALID_SIGNATURE upon + // tokenAssociate, + // since we have CONTRACT_ID key + .refusingEthConversion()); } @HapiTest - final HapiSpec rejectsInsufficientGas() { + final Stream rejectsInsufficientGas() { return defaultHapiSpec("RejectsInsufficientGas", NONDETERMINISTIC_TRANSACTION_FEES) .given(uploadInitCode(EMPTY_CONSTRUCTOR_CONTRACT)) .when() - .then(contractCreate(EMPTY_CONSTRUCTOR_CONTRACT).gas(0L).hasKnownStatus(INSUFFICIENT_GAS)); + // refuse eth conversion because ethereum transaction fails in IngestChecker with precheck status + // INSUFFICIENT_GAS + .then(contractCreate(EMPTY_CONSTRUCTOR_CONTRACT) + .gas(0L) + .hasKnownStatus(INSUFFICIENT_GAS) + .refusingEthConversion()); } @HapiTest - final HapiSpec rejectsInvalidMemo() { + final Stream rejectsInvalidMemo() { return defaultHapiSpec("RejectsInvalidMemo") .given() .when() @@ -473,7 +475,7 @@ final HapiSpec rejectsInvalidMemo() { } @HapiTest - final HapiSpec rejectsInsufficientFee() { + final Stream rejectsInsufficientFee() { return defaultHapiSpec("RejectsInsufficientFee", NONDETERMINISTIC_TRANSACTION_FEES) .given(cryptoCreate(PAYER), uploadInitCode(EMPTY_CONSTRUCTOR_CONTRACT)) .when() @@ -484,16 +486,19 @@ final HapiSpec rejectsInsufficientFee() { } @HapiTest - final HapiSpec rejectsInvalidBytecode() { + final Stream rejectsInvalidBytecode() { final var contract = "InvalidBytecode"; return defaultHapiSpec("RejectsInvalidBytecode") .given(uploadInitCode(contract)) .when() - .then(contractCreate(contract).hasKnownStatus(ERROR_DECODING_BYTESTRING)); + // refuse eth conversion because we can't set invalid bytecode to callData in ethereum transaction + .then(contractCreate(contract) + .hasKnownStatus(ERROR_DECODING_BYTESTRING) + .refusingEthConversion()); } @HapiTest - final HapiSpec revertsNonzeroBalance() { + final Stream revertsNonzeroBalance() { return defaultHapiSpec("RevertsNonzeroBalance", HIGHLY_NON_DETERMINISTIC_FEES) .given(uploadInitCode(EMPTY_CONSTRUCTOR_CONTRACT)) .when() @@ -501,7 +506,7 @@ final HapiSpec revertsNonzeroBalance() { } @HapiTest - final HapiSpec delegateContractIdRequiredForTransferInDelegateCall() { + final Stream delegateContractIdRequiredForTransferInDelegateCall() { final var justSendContract = "JustSend"; final var sendInternalAndDelegateContract = "SendInternalAndDelegate"; @@ -521,7 +526,12 @@ final HapiSpec delegateContractIdRequiredForTransferInDelegateCall() { NONDETERMINISTIC_FUNCTION_PARAMETERS) .given( uploadInitCode(justSendContract, sendInternalAndDelegateContract), - contractCreate(justSendContract).gas(300_000L).exposingNumTo(justSendContractNum::set), + // refuse eth conversion because we can't delegate call contract by contract num + // when it has EVM address alias (isNotPriority check fails) + contractCreate(justSendContract) + .gas(300_000L) + .exposingNumTo(justSendContractNum::set) + .refusingEthConversion(), contractCreate(sendInternalAndDelegateContract) .gas(300_000L) .balance(2 * totalToSend)) @@ -555,7 +565,7 @@ final HapiSpec delegateContractIdRequiredForTransferInDelegateCall() { } @HapiTest - final HapiSpec cannotCreateTooLargeContract() { + final Stream cannotCreateTooLargeContract() { ByteString contents; try { contents = ByteString.copyFrom(Files.readAllBytes(Path.of(bytecodePath("CryptoKitties")))); @@ -581,11 +591,14 @@ final HapiSpec cannotCreateTooLargeContract() { .then(contractCreate("contract") .bytecode("bytecode") .payingWith(ACCOUNT) - .hasKnownStatus(INSUFFICIENT_GAS)); + .hasKnownStatus(INSUFFICIENT_GAS) + // refuse eth conversion because we can't set invalid bytecode to callData in ethereum + // transaction + .refusingEthConversion()); } @HapiTest - HapiSpec blockTimestampChangesWithinFewSeconds() { + final Stream blockTimestampChangesWithinFewSeconds() { final var contract = "EmitBlockTimestamp"; final var firstBlock = "firstBlock"; final var timeLoggingTxn = "timeLoggingTxn"; @@ -649,7 +662,7 @@ HapiSpec blockTimestampChangesWithinFewSeconds() { } @HapiTest - HapiSpec vanillaSuccess() { + final Stream vanillaSuccess() { final var contract = "CreateTrivial"; return defaultHapiSpec( "VanillaSuccess", @@ -658,7 +671,8 @@ HapiSpec vanillaSuccess() { NONDETERMINISTIC_NONCE) .given( uploadInitCode(contract), - contractCreate(contract).adminKey(THRESHOLD), + // refuse eth conversion because ethereum transaction is missing admin key + contractCreate(contract).adminKey(THRESHOLD).refusingEthConversion(), getContractInfo(contract).saveToRegistry(PARENT_INFO)) .when( contractCall(contract, "create").gas(1_000_000L).via("createChildTxn"), @@ -687,7 +701,7 @@ HapiSpec vanillaSuccess() { } @HapiTest - HapiSpec newAccountsCanUsePureContractIdKey() { + final Stream newAccountsCanUsePureContractIdKey() { final var contract = "CreateTrivial"; final var contractControlled = "contractControlled"; return defaultHapiSpec("NewAccountsCanUsePureContractIdKey", NONDETERMINISTIC_TRANSACTION_FEES) @@ -708,7 +722,7 @@ HapiSpec newAccountsCanUsePureContractIdKey() { } @HapiTest - public HapiSpec idVariantsTreatedAsExpected() { + final Stream idVariantsTreatedAsExpected() { final var autoRenewAccount = "autoRenewAccount"; final var creationNumber = new AtomicLong(); final var contract = "CreateTrivial"; @@ -722,7 +736,7 @@ public HapiSpec idVariantsTreatedAsExpected() { } @HapiTest - HapiSpec contractWithAutoRenewNeedSignatures() { + final Stream contractWithAutoRenewNeedSignatures() { final var contract = "CreateTrivial"; final var autoRenewAccount = "autoRenewAccount"; return defaultHapiSpec("contractWithAutoRenewNeedSignatures", HIGHLY_NON_DETERMINISTIC_FEES) @@ -730,15 +744,18 @@ HapiSpec contractWithAutoRenewNeedSignatures() { newKeyNamed(ADMIN_KEY), uploadInitCode(contract), cryptoCreate(autoRenewAccount).balance(ONE_HUNDRED_HBARS), + // refuse eth conversion because ethereum transaction is missing autoRenewAccountId field to map contractCreate(contract) .adminKey(ADMIN_KEY) .autoRenewAccountId(autoRenewAccount) .signedBy(DEFAULT_PAYER, ADMIN_KEY) - .hasKnownStatus(INVALID_SIGNATURE), + .hasKnownStatus(INVALID_SIGNATURE) + .refusingEthConversion(), contractCreate(contract) .adminKey(ADMIN_KEY) .autoRenewAccountId(autoRenewAccount) .signedBy(DEFAULT_PAYER, ADMIN_KEY, autoRenewAccount) + .refusingEthConversion() .logged(), getContractInfo(contract) .has(ContractInfoAsserts.contractWith().autoRenewAccountId(autoRenewAccount)) @@ -748,7 +765,7 @@ HapiSpec contractWithAutoRenewNeedSignatures() { } @HapiTest - private HapiSpec cannotSetMaxAutomaticAssociations() { + final Stream cannotSetMaxAutomaticAssociations() { return defaultHapiSpec("cannotSetMaxAutomaticAssociations") .given(uploadInitCode(EMPTY_CONSTRUCTOR_CONTRACT)) .when() @@ -757,11 +774,6 @@ private HapiSpec cannotSetMaxAutomaticAssociations() { .hasKnownStatus(NOT_SUPPORTED)); } - @Override - protected Logger getResultsLogger() { - return log; - } - private EthTxData placeholderEthTx() { return new EthTxData( null, diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/hapi/ContractCreateV1SecurityModelSuite.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/hapi/ContractCreateV1SecurityModelSuite.java index 43ed89de35e8..19279b49b210 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/hapi/ContractCreateV1SecurityModelSuite.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/hapi/ContractCreateV1SecurityModelSuite.java @@ -29,13 +29,14 @@ import static com.hedera.services.bdd.suites.contract.precompile.V1SecurityModelOverrides.CONTRACTS_V1_SECURITY_MODEL_BLOCK_CUTOFF; import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.INVALID_SIGNATURE; -import com.hedera.services.bdd.spec.HapiSpec; import com.hedera.services.bdd.suites.HapiSuite; import java.math.BigInteger; import java.util.List; import java.util.concurrent.atomic.AtomicLong; +import java.util.stream.Stream; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; +import org.junit.jupiter.api.DynamicTest; @SuppressWarnings("java:S1192") // "string literal should not be duplicated" - this rule makes test suites worse public class ContractCreateV1SecurityModelSuite extends HapiSuite { @@ -49,7 +50,7 @@ public static void main(String... args) { } @Override - public List getSpecsInSuite() { + public List> getSpecsInSuite() { return List.of(receiverSigReqTransferRecipientMustSignWithFullPubKeyPrefix()); } @@ -58,7 +59,7 @@ public boolean canRunConcurrent() { return false; } - final HapiSpec receiverSigReqTransferRecipientMustSignWithFullPubKeyPrefix() { + final Stream receiverSigReqTransferRecipientMustSignWithFullPubKeyPrefix() { final var sendInternalAndDelegateContract = "SendInternalAndDelegate"; final var justSendContract = "JustSend"; final var beneficiary = "civilian"; diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/hapi/ContractDeleteSuite.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/hapi/ContractDeleteSuite.java index d4988b08f79e..549206111e95 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/hapi/ContractDeleteSuite.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/hapi/ContractDeleteSuite.java @@ -49,6 +49,12 @@ import static com.hedera.services.bdd.spec.utilops.mod.ModificationUtils.withSuccessivelyVariedBodyIds; import static com.hedera.services.bdd.spec.utilops.records.SnapshotMatchMode.HIGHLY_NON_DETERMINISTIC_FEES; import static com.hedera.services.bdd.spec.utilops.records.SnapshotMatchMode.NONDETERMINISTIC_FUNCTION_PARAMETERS; +import static com.hedera.services.bdd.suites.HapiSuite.GENESIS; +import static com.hedera.services.bdd.suites.HapiSuite.ONE_HBAR; +import static com.hedera.services.bdd.suites.HapiSuite.ONE_HUNDRED_HBARS; +import static com.hedera.services.bdd.suites.HapiSuite.SYSTEM_DELETE_ADMIN; +import static com.hedera.services.bdd.suites.HapiSuite.SYSTEM_UNDELETE_ADMIN; +import static com.hedera.services.bdd.suites.HapiSuite.TOKEN_TREASURY; import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.ACCOUNT_IS_TREASURY; import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.CONTRACT_EXECUTION_EXCEPTION; import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.CONTRACT_REVERT_EXECUTED; @@ -67,61 +73,31 @@ import com.google.protobuf.ByteString; import com.hedera.services.bdd.junit.HapiTest; -import com.hedera.services.bdd.junit.HapiTestSuite; import com.hedera.services.bdd.spec.HapiPropertySource; -import com.hedera.services.bdd.spec.HapiSpec; import com.hedera.services.bdd.spec.HapiSpecSetup; import com.hedera.services.bdd.spec.assertions.TransactionRecordAsserts; import com.hedera.services.bdd.spec.transactions.token.TokenMovement; -import com.hedera.services.bdd.suites.HapiSuite; import com.hederahashgraph.api.proto.java.TokenSupplyType; import java.math.BigInteger; import java.util.List; import java.util.concurrent.atomic.AtomicReference; import java.util.stream.IntStream; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; +import java.util.stream.Stream; import org.apache.tuweni.bytes.Bytes; import org.apache.tuweni.bytes.Bytes32; +import org.junit.jupiter.api.DynamicTest; import org.junit.jupiter.api.Tag; -@HapiTestSuite(fuzzyMatch = true) @Tag(SMART_CONTRACT) -public class ContractDeleteSuite extends HapiSuite { - - private static final Logger log = LogManager.getLogger(ContractDeleteSuite.class); +public class ContractDeleteSuite { private static final String CONTRACT = "Multipurpose"; private static final String PAYABLE_CONSTRUCTOR = "PayableConstructor"; private static final String CONTRACT_DESTROY = "destroy"; private static final String RECEIVER_CONTRACT_NAME = "receiver"; private static final String SIMPLE_STORAGE_CONTRACT = "SimpleStorage"; - public static void main(final String... args) { - new ContractDeleteSuite().runSuiteSync(); - } - - @Override - public boolean canRunConcurrent() { - return true; - } - - @Override - public List getSpecsInSuite() { - return List.of( - rejectsWithoutProperSig(), - systemCannotDeleteOrUndeleteContracts(), - deleteWorksWithMutableContract(), - deleteFailsWithImmutableContract(), - deleteTransfersToAccount(), - deleteTransfersToContract(), - cannotDeleteOrSelfDestructTokenTreasury(), - cannotDeleteOrSelfDestructContractWithNonZeroBalance(), - cannotSendValueToTokenAccount(), - cannotUseMoreThanChildContractLimit()); - } - @HapiTest - public HapiSpec idVariantsTreatedAsExpected() { + final Stream idVariantsTreatedAsExpected() { return defaultHapiSpec("idVariantsTreatedAsExpected") .given( newKeyNamed("adminKey"), @@ -139,7 +115,7 @@ public HapiSpec idVariantsTreatedAsExpected() { } @HapiTest - final HapiSpec cannotUseMoreThanChildContractLimit() { + final Stream cannotUseMoreThanChildContractLimit() { final var illegalNumChildren = HapiSpecSetup.getDefaultNodeProps().getInteger("consensus.handle.maxFollowingRecords") + 1; final var fungible = "fungible"; @@ -188,7 +164,7 @@ final HapiSpec cannotUseMoreThanChildContractLimit() { } @HapiTest - final HapiSpec cannotSendValueToTokenAccount() { + final Stream cannotSendValueToTokenAccount() { final var multiKey = "multiKey"; final var nonFungibleToken = "NFT"; final var contract = "ManyChildren"; @@ -228,7 +204,7 @@ final HapiSpec cannotSendValueToTokenAccount() { } @HapiTest - HapiSpec cannotDeleteOrSelfDestructTokenTreasury() { + final Stream cannotDeleteOrSelfDestructTokenTreasury() { final var someToken = "someToken"; final var selfDestructCallable = "SelfDestructCallable"; final var multiKey = "multi"; @@ -242,10 +218,18 @@ HapiSpec cannotDeleteOrSelfDestructTokenTreasury() { uploadInitCode(selfDestructCallable), contractCustomCreate(selfDestructCallable, "1") .adminKey(multiKey) - .balance(123), + .balance(123) + // Refusing ethereum create conversion, because we get INVALID_SIGNATURE upon + // tokenAssociate, + // since we have CONTRACT_ID key + .refusingEthConversion(), contractCustomCreate(selfDestructCallable, "2") .adminKey(multiKey) - .balance(321), + .balance(321) + // Refusing ethereum create conversion, because we get INVALID_SIGNATURE upon + // tokenAssociate, + // since we have CONTRACT_ID key + .refusingEthConversion(), tokenCreate(someToken).adminKey(multiKey).treasury(selfDestructCallable + "1")) .when( contractDelete(selfDestructCallable + "1").hasKnownStatus(ACCOUNT_IS_TREASURY), @@ -263,7 +247,7 @@ HapiSpec cannotDeleteOrSelfDestructTokenTreasury() { } @HapiTest - HapiSpec cannotDeleteOrSelfDestructContractWithNonZeroBalance() { + final Stream cannotDeleteOrSelfDestructContractWithNonZeroBalance() { final var someToken = "someToken"; final var multiKey = "multi"; final var selfDestructableContract = "SelfDestructCallable"; @@ -277,9 +261,15 @@ HapiSpec cannotDeleteOrSelfDestructContractWithNonZeroBalance() { uploadInitCode(selfDestructableContract), contractCreate(selfDestructableContract) .adminKey(multiKey) - .balance(123), + .balance(123) + // Refusing ethereum create conversion, because we get INVALID_SIGNATURE upon + // tokenAssociate, + // since we have CONTRACT_ID key + .refusingEthConversion(), uploadInitCode(otherMiscContract), - contractCreate(otherMiscContract), + // Refusing ethereum create conversion, because we get INVALID_SIGNATURE upon tokenAssociate, + // since we have CONTRACT_ID key + contractCreate(otherMiscContract).refusingEthConversion(), tokenCreate(someToken) .initialSupply(0L) .adminKey(multiKey) @@ -300,15 +290,17 @@ HapiSpec cannotDeleteOrSelfDestructContractWithNonZeroBalance() { } @HapiTest - HapiSpec rejectsWithoutProperSig() { + final Stream rejectsWithoutProperSig() { return defaultHapiSpec("rejectsWithoutProperSig") - .given(uploadInitCode(CONTRACT), contractCreate(CONTRACT)) + // Refusing ethereum create conversion, because we get INVALID_SIGNATURE upon tokenAssociate, + // since we have CONTRACT_ID key + .given(uploadInitCode(CONTRACT), contractCreate(CONTRACT).refusingEthConversion()) .when() .then(contractDelete(CONTRACT).signedBy(GENESIS).hasKnownStatus(INVALID_SIGNATURE)); } @HapiTest - final HapiSpec systemCannotDeleteOrUndeleteContracts() { + final Stream systemCannotDeleteOrUndeleteContracts() { return defaultHapiSpec("SystemCannotDeleteOrUndeleteContracts") .given(uploadInitCode(CONTRACT), contractCreate(CONTRACT)) .when() @@ -323,15 +315,20 @@ final HapiSpec systemCannotDeleteOrUndeleteContracts() { } @HapiTest - final HapiSpec deleteWorksWithMutableContract() { + final Stream deleteWorksWithMutableContract() { final var tbdFile = "FTBD"; final var tbdContract = "CTBD"; return defaultHapiSpec("DeleteWorksWithMutableContract") .given( fileCreate(tbdFile), fileDelete(tbdFile), - createDefaultContract(tbdContract).bytecode(tbdFile).hasKnownStatus(FILE_DELETED)) - .when(uploadInitCode(CONTRACT), contractCreate(CONTRACT)) + // refuse eth conversion because we can't set invalid bytecode to callData in ethereum + // transaction + trying to delete immutable contract + createDefaultContract(tbdContract) + .bytecode(tbdFile) + .hasKnownStatus(FILE_DELETED) + .refusingEthConversion()) + .when(uploadInitCode(CONTRACT), contractCreate(CONTRACT).refusingEthConversion()) .then( contractDelete(CONTRACT) .claimingPermanentRemoval() @@ -341,7 +338,7 @@ final HapiSpec deleteWorksWithMutableContract() { } @HapiTest - final HapiSpec deleteFailsWithImmutableContract() { + final Stream deleteFailsWithImmutableContract() { return defaultHapiSpec("DeleteFailsWithImmutableContract") .given(uploadInitCode(CONTRACT), contractCreate(CONTRACT).omitAdminKey()) .when() @@ -349,33 +346,46 @@ final HapiSpec deleteFailsWithImmutableContract() { } @HapiTest - final HapiSpec deleteTransfersToAccount() { + final Stream deleteTransfersToAccount() { return defaultHapiSpec("DeleteTransfersToAccount") .given( cryptoCreate(RECEIVER_CONTRACT_NAME).balance(0L), uploadInitCode(PAYABLE_CONSTRUCTOR), - contractCreate(PAYABLE_CONSTRUCTOR).balance(1L)) + contractCreate(PAYABLE_CONSTRUCTOR) + // Refusing ethereum create conversion, because we get INVALID_SIGNATURE upon + // tokenAssociate, + // since we have CONTRACT_ID key + .refusingEthConversion() + .balance(1L)) .when(contractDelete(PAYABLE_CONSTRUCTOR).transferAccount(RECEIVER_CONTRACT_NAME)) .then(getAccountBalance(RECEIVER_CONTRACT_NAME).hasTinyBars(1L)); } @HapiTest - final HapiSpec deleteTransfersToContract() { + final Stream deleteTransfersToContract() { final var suffix = "Receiver"; return defaultHapiSpec("DeleteTransfersToContract") .given( uploadInitCode(PAYABLE_CONSTRUCTOR), - contractCreate(PAYABLE_CONSTRUCTOR).balance(0L), + contractCreate(PAYABLE_CONSTRUCTOR) + // Refusing ethereum create conversion, because we get INVALID_SIGNATURE upon + // tokenAssociate, + // since we have CONTRACT_ID key + .refusingEthConversion() + .balance(0L), contractCustomCreate(PAYABLE_CONSTRUCTOR, suffix).balance(1L)) .when(contractDelete(PAYABLE_CONSTRUCTOR).transferContract(PAYABLE_CONSTRUCTOR + suffix)) .then(getAccountBalance(PAYABLE_CONSTRUCTOR + suffix).hasTinyBars(1L)); } @HapiTest - HapiSpec localCallToDeletedContract() { + final Stream localCallToDeletedContract() { return defaultHapiSpec("LocalCallToDeletedContract") - .given(uploadInitCode(SIMPLE_STORAGE_CONTRACT), contractCreate(SIMPLE_STORAGE_CONTRACT)) + // refuse eth conversion because MODIFYING_IMMUTABLE_CONTRACT + .given( + uploadInitCode(SIMPLE_STORAGE_CONTRACT), + contractCreate(SIMPLE_STORAGE_CONTRACT).refusingEthConversion()) .when( contractCallLocal(SIMPLE_STORAGE_CONTRACT, "get") .hasCostAnswerPrecheck(OK) @@ -385,9 +395,4 @@ HapiSpec localCallToDeletedContract() { .hasCostAnswerPrecheck(OK) .has(resultWith().contractCallResult(() -> Bytes.EMPTY))); } - - @Override - protected Logger getResultsLogger() { - return log; - } } diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/hapi/ContractGetBytecodeSuite.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/hapi/ContractGetBytecodeSuite.java index c3c05aca87c9..18a48a062910 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/hapi/ContractGetBytecodeSuite.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/hapi/ContractGetBytecodeSuite.java @@ -31,55 +31,37 @@ import static com.hedera.services.bdd.spec.utilops.UtilVerbs.sourcing; import static com.hedera.services.bdd.spec.utilops.UtilVerbs.withOpContext; import static com.hedera.services.bdd.spec.utilops.mod.ModificationUtils.withSuccessivelyVariedQueryIds; +import static com.hedera.services.bdd.suites.HapiSuite.CIVILIAN_PAYER; +import static com.hedera.services.bdd.suites.HapiSuite.ONE_HUNDRED_HBARS; +import static com.hedera.services.bdd.suites.HapiSuite.TINY_PARTS_PER_WHOLE; import static com.hedera.services.bdd.suites.contract.Utils.getResourcePath; import static com.hedera.services.bdd.suites.contract.precompile.CreatePrecompileSuite.MEMO; import com.google.common.io.Files; import com.hedera.services.bdd.junit.HapiTest; -import com.hedera.services.bdd.junit.HapiTestSuite; import com.hedera.services.bdd.spec.HapiSpec; import com.hedera.services.bdd.spec.HapiSpecSetup; -import com.hedera.services.bdd.suites.HapiSuite; import com.hederahashgraph.api.proto.java.ResponseCodeEnum; import java.io.File; import java.util.Arrays; -import java.util.List; import java.util.concurrent.atomic.AtomicLong; +import java.util.stream.Stream; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.bouncycastle.util.encoders.Hex; import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.DynamicTest; import org.junit.jupiter.api.Tag; -@HapiTestSuite(fuzzyMatch = true) @Tag(SMART_CONTRACT) -public class ContractGetBytecodeSuite extends HapiSuite { +public class ContractGetBytecodeSuite { private static final Logger log = LogManager.getLogger(ContractGetBytecodeSuite.class); private static final String NON_EXISTING_CONTRACT = HapiSpecSetup.getDefaultInstance().invalidContractName(); - public static void main(String... args) { - new ContractGetBytecodeSuite().runSuiteSync(); - } - - @Override - protected Logger getResultsLogger() { - return log; - } - - @Override - public List getSpecsInSuite() { - return List.of(getByteCodeWorks(), invalidContractFromCostAnswer(), invalidContractFromAnswerOnly()); - } - - @Override - public boolean canRunConcurrent() { - return true; - } - @HapiTest - public HapiSpec idVariantsTreatedAsExpected() { + final Stream idVariantsTreatedAsExpected() { final var contract = "Multipurpose"; return defaultHapiSpec("idVariantsTreatedAsExpected") .given( @@ -90,7 +72,7 @@ public HapiSpec idVariantsTreatedAsExpected() { } @HapiTest - final HapiSpec getByteCodeWorks() { + final Stream getByteCodeWorks() { final var contract = "EmptyConstructor"; final var canonicalUsdFee = 0.05; final var canonicalQueryFeeAtActiveRate = new AtomicLong(); @@ -136,7 +118,7 @@ final HapiSpec getByteCodeWorks() { } @HapiTest - final HapiSpec invalidContractFromCostAnswer() { + final Stream invalidContractFromCostAnswer() { return defaultHapiSpec("InvalidContractFromCostAnswer") .given() .when() @@ -145,7 +127,7 @@ final HapiSpec invalidContractFromCostAnswer() { } @HapiTest - final HapiSpec invalidContractFromAnswerOnly() { + final Stream invalidContractFromAnswerOnly() { return defaultHapiSpec("InvalidContractFromAnswerOnly") .given() .when() diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/hapi/ContractGetInfoSuite.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/hapi/ContractGetInfoSuite.java index 2396c9ef174c..e0021f6c2e7b 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/hapi/ContractGetInfoSuite.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/hapi/ContractGetInfoSuite.java @@ -34,45 +34,26 @@ import static com.hedera.services.bdd.spec.utilops.UtilVerbs.withTargetLedgerId; import static com.hedera.services.bdd.spec.utilops.mod.ModificationUtils.withSuccessivelyVariedQueryIds; import static com.hedera.services.bdd.spec.utilops.records.SnapshotMatchMode.NONDETERMINISTIC_TRANSACTION_FEES; +import static com.hedera.services.bdd.suites.HapiSuite.CIVILIAN_PAYER; +import static com.hedera.services.bdd.suites.HapiSuite.ONE_HUNDRED_HBARS; +import static com.hedera.services.bdd.suites.HapiSuite.TINY_PARTS_PER_WHOLE; import static com.hedera.services.bdd.suites.contract.precompile.CreatePrecompileSuite.MEMO; import com.hedera.services.bdd.junit.HapiTest; -import com.hedera.services.bdd.junit.HapiTestSuite; -import com.hedera.services.bdd.spec.HapiSpec; import com.hedera.services.bdd.spec.HapiSpecSetup; -import com.hedera.services.bdd.suites.HapiSuite; import com.hederahashgraph.api.proto.java.ResponseCodeEnum; -import java.util.List; import java.util.concurrent.atomic.AtomicLong; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; +import java.util.stream.Stream; +import org.junit.jupiter.api.DynamicTest; import org.junit.jupiter.api.Tag; -@HapiTestSuite(fuzzyMatch = true) @Tag(SMART_CONTRACT) -public class ContractGetInfoSuite extends HapiSuite { - - private static final Logger log = LogManager.getLogger(ContractGetInfoSuite.class); - +public class ContractGetInfoSuite { private static final String NON_EXISTING_CONTRACT = HapiSpecSetup.getDefaultInstance().invalidContractName(); - public static void main(String... args) { - new ContractGetInfoSuite().runSuiteSync(); - } - - @Override - public List getSpecsInSuite() { - return List.of(getInfoWorks(), invalidContractFromCostAnswer(), invalidContractFromAnswerOnly()); - } - - @Override - public boolean canRunConcurrent() { - return true; - } - @HapiTest - public HapiSpec idVariantsTreatedAsExpected() { + final Stream idVariantsTreatedAsExpected() { final var contract = "Multipurpose"; return defaultHapiSpec("idVariantsTreatedAsExpected") .given( @@ -83,7 +64,7 @@ public HapiSpec idVariantsTreatedAsExpected() { } @HapiTest - final HapiSpec getInfoWorks() { + final Stream getInfoWorks() { final var contract = "Multipurpose"; final var MEMO = "This is a test."; final var canonicalUsdPrice = 0.0001; @@ -94,10 +75,13 @@ final HapiSpec getInfoWorks() { cryptoCreate(CIVILIAN_PAYER).balance(ONE_HUNDRED_HBARS), balanceSnapshot("beforeQuery", CIVILIAN_PAYER), uploadInitCode(contract), + // refuse eth conversion because ethereum transaction is missing admin key and memo is same as + // parent contractCreate(contract) .adminKey("adminKey") .entityMemo(MEMO) - .autoRenewSecs(6999999L), + .autoRenewSecs(6999999L) + .refusingEthConversion(), withOpContext((spec, opLog) -> canonicalQueryFeeAtActiveRate.set(spec.ratesProvider() .toTbWithActiveRates((long) (canonicalUsdPrice * 100 * TINY_PARTS_PER_WHOLE))))) .when(withTargetLedgerId(ledgerId -> getContractInfo(contract) @@ -117,7 +101,7 @@ final HapiSpec getInfoWorks() { } @HapiTest - final HapiSpec invalidContractFromCostAnswer() { + final Stream invalidContractFromCostAnswer() { return defaultHapiSpec("InvalidContractFromCostAnswer") .given() .when() @@ -126,7 +110,7 @@ final HapiSpec invalidContractFromCostAnswer() { } @HapiTest - final HapiSpec invalidContractFromAnswerOnly() { + final Stream invalidContractFromAnswerOnly() { return defaultHapiSpec("InvalidContractFromAnswerOnly") .given() .when() @@ -134,9 +118,4 @@ final HapiSpec invalidContractFromAnswerOnly() { .nodePayment(27_159_182L) .hasAnswerOnlyPrecheck(ResponseCodeEnum.INVALID_CONTRACT_ID)); } - - @Override - protected Logger getResultsLogger() { - return log; - } } diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/hapi/ContractMusicalChairsSuite.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/hapi/ContractMusicalChairsSuite.java deleted file mode 100644 index 53bfecf5c2e6..000000000000 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/hapi/ContractMusicalChairsSuite.java +++ /dev/null @@ -1,132 +0,0 @@ -/* - * Copyright (C) 2021-2024 Hedera Hashgraph, LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.hedera.services.bdd.suites.contract.hapi; - -import static com.hedera.services.bdd.spec.HapiSpec.defaultHapiSpec; -import static com.hedera.services.bdd.spec.assertions.ContractFnResultAsserts.isLiteralResult; -import static com.hedera.services.bdd.spec.assertions.ContractFnResultAsserts.resultWith; -import static com.hedera.services.bdd.spec.queries.QueryVerbs.contractCallLocal; -import static com.hedera.services.bdd.spec.queries.QueryVerbs.getAccountInfo; -import static com.hedera.services.bdd.spec.transactions.TxnVerbs.contractCall; -import static com.hedera.services.bdd.spec.transactions.TxnVerbs.contractCreate; -import static com.hedera.services.bdd.spec.transactions.TxnVerbs.cryptoCreate; -import static com.hedera.services.bdd.spec.transactions.TxnVerbs.uploadInitCode; -import static com.hedera.services.bdd.spec.transactions.contract.HapiParserUtil.asHeadlongAddress; -import static com.hedera.services.bdd.spec.utilops.CustomSpecAssert.allRunFor; -import static com.hedera.services.bdd.spec.utilops.UtilVerbs.withOpContext; -import static com.hedera.services.bdd.suites.contract.Utils.FunctionType.FUNCTION; -import static com.hedera.services.bdd.suites.contract.Utils.asAddress; -import static com.hedera.services.bdd.suites.contract.Utils.getABIFor; - -import com.hedera.services.bdd.junit.HapiTest; -import com.hedera.services.bdd.junit.HapiTestSuite; -import com.hedera.services.bdd.spec.HapiSpec; -import com.hedera.services.bdd.spec.HapiSpecOperation; -import com.hedera.services.bdd.spec.transactions.TxnVerbs; -import com.hedera.services.bdd.spec.transactions.contract.HapiParserUtil; -import com.hedera.services.bdd.suites.HapiSuite; -import java.util.ArrayList; -import java.util.List; -import java.util.Random; -import java.util.stream.IntStream; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; - -@HapiTestSuite -public class ContractMusicalChairsSuite extends HapiSuite { - - private static final Logger log = LogManager.getLogger(ContractMusicalChairsSuite.class); - - public static void main(String... args) { - new ContractMusicalChairsSuite().runSuiteAsync(); - } - - @Override - public boolean canRunConcurrent() { - return true; - } - - @Override - public List getSpecsInSuite() { - return List.of(playGame()); - } - - @HapiTest - final HapiSpec playGame() { - final var dj = "dj"; - final var players = IntStream.range(1, 30).mapToObj(i -> "Player" + i).toList(); - final var contract = "MusicalChairs"; - - List given = new ArrayList<>(); - List when = new ArrayList<>(); - List then = new ArrayList<>(); - - ////// Create contract ////// - given.add(cryptoCreate(dj).balance(10 * ONE_HUNDRED_HBARS)); - given.add(getAccountInfo(DEFAULT_CONTRACT_SENDER).savingSnapshot(DEFAULT_CONTRACT_SENDER)); - given.add(uploadInitCode(contract)); - given.add(withOpContext((spec, opLog) -> allRunFor( - spec, - contractCreate( - contract, - asHeadlongAddress(spec.registry() - .getAccountInfo(DEFAULT_CONTRACT_SENDER) - .getContractAccountID())) - .payingWith(dj)))); - - ////// Add the players ////// - players.stream().map(TxnVerbs::cryptoCreate).forEach(given::add); - - ////// Start the music! ////// - when.add(contractCall(contract, "startMusic").payingWith(DEFAULT_CONTRACT_SENDER)); - - ////// 100 "random" seats taken ////// - new Random(0x1337) - .ints(100, 0, 29) - .forEach(i -> when.add(contractCall(contract, "sitDown") - .payingWith(players.get(i)) - .refusingEthConversion() - .hasAnyStatusAtAll())); // sometimes a player sits - // too soon, so don't fail - // on reverts - - ////// Stop the music! ////// - then.add(contractCall(contract, "stopMusic").payingWith(DEFAULT_CONTRACT_SENDER)); - - ////// And the winner is..... ////// - then.add(withOpContext((spec, opLog) -> allRunFor( - spec, - contractCallLocal(contract, "whoIsOnTheBubble") - .has(resultWith() - .resultThruAbi( - getABIFor(FUNCTION, "whoIsOnTheBubble", contract), - isLiteralResult(new Object[] { - HapiParserUtil.asHeadlongAddress( - asAddress(spec.registry().getAccountID("Player13"))) - })))))); - - return defaultHapiSpec("playGame") - .given(given.toArray(HapiSpecOperation[]::new)) - .when(when.toArray(HapiSpecOperation[]::new)) - .then(then.toArray(HapiSpecOperation[]::new)); - } - - @Override - protected Logger getResultsLogger() { - return log; - } -} diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/hapi/ContractStateSuite.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/hapi/ContractStateSuite.java new file mode 100644 index 000000000000..93eae8ce4065 --- /dev/null +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/hapi/ContractStateSuite.java @@ -0,0 +1,155 @@ +/* + * Copyright (C) 2024 Hedera Hashgraph, LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.hedera.services.bdd.suites.contract.hapi; + +import static com.hedera.services.bdd.junit.TestTags.SMART_CONTRACT; +import static com.hedera.services.bdd.spec.HapiSpec.defaultHapiSpec; +import static com.hedera.services.bdd.spec.transactions.TxnVerbs.contractCall; +import static com.hedera.services.bdd.spec.transactions.TxnVerbs.contractCreate; +import static com.hedera.services.bdd.spec.transactions.TxnVerbs.uploadInitCode; +import static com.hedera.services.bdd.spec.transactions.contract.HapiParserUtil.asHeadlongAddress; +import static java.lang.Integer.MAX_VALUE; + +import com.esaulpaugh.headlong.abi.Address; +import com.esaulpaugh.headlong.abi.Tuple; +import com.hedera.services.bdd.junit.HapiTest; +import com.hedera.services.bdd.spec.HapiSpecOperation; +import java.math.BigInteger; +import java.util.Arrays; +import java.util.Map; +import java.util.SplittableRandom; +import java.util.stream.IntStream; +import java.util.stream.Stream; +import org.junit.jupiter.api.DynamicTest; +import org.junit.jupiter.api.Tag; + +@Tag(SMART_CONTRACT) +public class ContractStateSuite { + private static final String CONTRACT = "StateContract"; + private static final SplittableRandom RANDOM = new SplittableRandom(1_234_567L); + + @HapiTest + final Stream stateChangesSpec() { + final var iterations = 2; + final var integralTypes = Map.ofEntries( + Map.entry("Uint8", 0x01), + Map.entry("Uint16", 0x02), + Map.entry("Uint32", (long) 0x03), + Map.entry("Uint64", BigInteger.valueOf(4)), + Map.entry("Uint128", BigInteger.valueOf(5)), + Map.entry("Uint256", BigInteger.valueOf(6)), + Map.entry("Int8", 0x01), + Map.entry("Int16", 0x02), + Map.entry("Int32", 0x03), + Map.entry("Int64", 4L), + Map.entry("Int128", BigInteger.valueOf(5)), + Map.entry("Int256", BigInteger.valueOf(6))); + + return defaultHapiSpec("stateChangesSpec") + .given(uploadInitCode(CONTRACT), contractCreate(CONTRACT)) + .when(IntStream.range(0, iterations) + .boxed() + .flatMap(i -> Stream.of( + Stream.of(contractCall(CONTRACT, "setVarBool", RANDOM.nextBoolean())), + Arrays.stream(integralTypes.keySet().toArray(new String[0])) + .map(type -> contractCall( + CONTRACT, "setVar" + type, integralTypes.get(type))), + Stream.of(contractCall(CONTRACT, "setVarAddress", randomHeadlongAddress())), + Stream.of(contractCall(CONTRACT, "setVarContractType")), + Stream.of(contractCall(CONTRACT, "setVarBytes32", randomBytes32())), + Stream.of(contractCall(CONTRACT, "setVarString", randomString())), + Stream.of(contractCall(CONTRACT, "setVarEnum", randomEnum())), + randomSetAndDeleteVarInt(), + randomSetAndDeleteString(), + randomSetAndDeleteStruct()) + .flatMap(s -> s)) + .toArray(HapiSpecOperation[]::new)) + .then(); + } + + private Stream randomSetAndDeleteVarInt() { + final var numSetsAndDeletes = 2; + return IntStream.range(0, numSetsAndDeletes) + .boxed() + .flatMap(i -> Stream.of( + contractCall(CONTRACT, "setVarIntArrDataAlloc", new Object[] {randomInts()}) + .gas(5_000_000), + contractCall(CONTRACT, "deleteVarIntArrDataAlloc"))); + } + + private Stream randomSetAndDeleteString() { + final var numCycles = 2; + return IntStream.range(0, numCycles) + .boxed() + .flatMap(i -> Stream.of( + contractCall(CONTRACT, "setVarStringConcat", randomString()) + .gas(5_000_000), + contractCall(CONTRACT, "setVarStringConcat", randomString()) + .gas(5_000_000), + contractCall(CONTRACT, "deleteVarStringConcat").gas(5_000_000))); + } + + private Stream randomSetAndDeleteStruct() { + final var numCycles = 4; + return IntStream.range(0, numCycles) + .boxed() + .flatMap(i -> Stream.of( + contractCall(CONTRACT, "setVarContractStruct", randomContractStruct()) + .gas(5_000_000), + contractCall(CONTRACT, "deleteVarContractStruct").gas(5_000_000))); + } + + private Tuple randomContractStruct() { + return Tuple.of( + BigInteger.valueOf(RANDOM.nextInt(MAX_VALUE)), + randomHeadlongAddress(), + randomBytes32(), + randomString(), + RANDOM.nextInt(3), + randomInts(), + randomString()); + } + + private Address randomHeadlongAddress() { + final var bytes = new byte[20]; + RANDOM.nextBytes(bytes); + return asHeadlongAddress(bytes); + } + + private byte[] randomBytes32() { + final var bytes = new byte[32]; + RANDOM.nextBytes(bytes); + return bytes; + } + + private String randomString() { + return new String(randomBytes32()); + } + + private int randomEnum() { + return RANDOM.nextInt(3); + } + + private BigInteger[] randomInts() { + return new BigInteger[] { + BigInteger.valueOf(RANDOM.nextInt(MAX_VALUE)), + BigInteger.valueOf(RANDOM.nextInt(MAX_VALUE)), + BigInteger.valueOf(RANDOM.nextInt(MAX_VALUE)), + BigInteger.valueOf(RANDOM.nextInt(MAX_VALUE)), + }; + } +} diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/hapi/ContractUpdateSuite.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/hapi/ContractUpdateSuite.java index 87b496ec35d6..6533ce600cc0 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/hapi/ContractUpdateSuite.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/hapi/ContractUpdateSuite.java @@ -23,6 +23,8 @@ import static com.hedera.services.bdd.spec.assertions.ContractInfoAsserts.contractWith; import static com.hedera.services.bdd.spec.assertions.TransactionRecordAsserts.recordWith; import static com.hedera.services.bdd.spec.keys.KeyShape.listOf; +import static com.hedera.services.bdd.spec.queries.QueryVerbs.contractCallLocal; +import static com.hedera.services.bdd.spec.queries.QueryVerbs.getAccountInfo; import static com.hedera.services.bdd.spec.queries.QueryVerbs.getContractBytecode; import static com.hedera.services.bdd.spec.queries.QueryVerbs.getContractInfo; import static com.hedera.services.bdd.spec.queries.QueryVerbs.getTxnRecord; @@ -43,7 +45,15 @@ import static com.hedera.services.bdd.spec.utilops.records.SnapshotMatchMode.FULLY_NONDETERMINISTIC; import static com.hedera.services.bdd.spec.utilops.records.SnapshotMatchMode.NONDETERMINISTIC_CONTRACT_CALL_RESULTS; import static com.hedera.services.bdd.spec.utilops.records.SnapshotMatchMode.NONDETERMINISTIC_TRANSACTION_FEES; +import static com.hedera.services.bdd.suites.HapiSuite.DEFAULT_CONTRACT_SENDER; +import static com.hedera.services.bdd.suites.HapiSuite.DEFAULT_PAYER; +import static com.hedera.services.bdd.suites.HapiSuite.DEFAULT_PROPS; +import static com.hedera.services.bdd.suites.HapiSuite.GENESIS; +import static com.hedera.services.bdd.suites.HapiSuite.ONE_HUNDRED_HBARS; +import static com.hedera.services.bdd.suites.HapiSuite.THREE_MONTHS_IN_SECONDS; +import static com.hedera.services.bdd.suites.HapiSuite.ZERO_BYTE_MEMO; import static com.hedera.services.bdd.suites.contract.Utils.FunctionType.FUNCTION; +import static com.hedera.services.bdd.suites.contract.Utils.asAddress; import static com.hedera.services.bdd.suites.contract.Utils.captureChildCreate2MetaFor; import static com.hedera.services.bdd.suites.contract.Utils.getABIFor; import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.EXPIRATION_REDUCTION_NOT_ALLOWED; @@ -56,25 +66,24 @@ import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.SUCCESS; import com.hedera.services.bdd.junit.HapiTest; -import com.hedera.services.bdd.junit.HapiTestSuite; -import com.hedera.services.bdd.spec.HapiSpec; +import com.hedera.services.bdd.spec.HapiSpecOperation; import com.hedera.services.bdd.spec.HapiSpecSetup; import com.hedera.services.bdd.spec.assertions.ContractInfoAsserts; import com.hedera.services.bdd.spec.keys.KeyShape; -import com.hedera.services.bdd.suites.HapiSuite; +import com.hedera.services.bdd.spec.transactions.TxnVerbs; +import com.hedera.services.bdd.spec.transactions.contract.HapiParserUtil; import java.time.Instant; +import java.util.ArrayList; import java.util.List; +import java.util.Random; import java.util.concurrent.atomic.AtomicReference; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; +import java.util.stream.IntStream; +import java.util.stream.Stream; +import org.junit.jupiter.api.DynamicTest; import org.junit.jupiter.api.Tag; -@HapiTestSuite(fuzzyMatch = true) @Tag(SMART_CONTRACT) -public class ContractUpdateSuite extends HapiSuite { - - private static final Logger log = LogManager.getLogger(ContractUpdateSuite.class); - +public class ContractUpdateSuite { private static final long DEFAULT_MAX_LIFETIME = Long.parseLong(HapiSpecSetup.getDefaultNodeProps().get("entities.maxLifetime")); private static final long ONE_DAY = 60L * 60L * 24L; @@ -84,36 +93,8 @@ public class ContractUpdateSuite extends HapiSuite { private static final String CONTRACT = "Multipurpose"; public static final String INITIAL_ADMIN_KEY = "initialAdminKey"; - public static void main(String... args) { - new ContractUpdateSuite().runSuiteAsync(); - } - - @Override - public boolean canRunConcurrent() { - return true; - } - - @Override - public List getSpecsInSuite() { - return List.of( - updateWithBothMemoSettersWorks(), - updatingExpiryWorks(), - rejectsExpiryTooFarInTheFuture(), - updateAutoRenewWorks(), - updateAdminKeyWorks(), - canMakeContractImmutableWithEmptyKeyList(), - givenAdminKeyMustBeValid(), - fridayThe13thSpec(), - updateDoesNotChangeBytecode(), - eip1014AddressAlwaysHasPriority(), - immutableContractKeyFormIsStandard(), - updateAutoRenewAccountWorks(), - updateStakingFieldsWorks(), - cannotUpdateMaxAutomaticAssociations()); - } - @HapiTest - public HapiSpec idVariantsTreatedAsExpected() { + final Stream idVariantsTreatedAsExpected() { return defaultHapiSpec("idVariantsTreatedAsExpected") .given( newKeyNamed("adminKey"), @@ -128,11 +109,16 @@ public HapiSpec idVariantsTreatedAsExpected() { } @HapiTest - final HapiSpec updateStakingFieldsWorks() { + final Stream updateStakingFieldsWorks() { return defaultHapiSpec("updateStakingFieldsWorks", FULLY_NONDETERMINISTIC) .given( uploadInitCode(CONTRACT), - contractCreate(CONTRACT).declinedReward(true).stakedNodeId(0), + // refuse eth conversion because ethereum transaction is missing staking fields to map + // (isDeclinedReward) + contractCreate(CONTRACT) + .declinedReward(true) + .stakedNodeId(0) + .refusingEthConversion(), getContractInfo(CONTRACT) .has(contractWith() .isDeclinedReward(true) @@ -156,7 +142,12 @@ final HapiSpec updateStakingFieldsWorks() { .noStakingNodeId() .noStakedAccountId()) .logged(), - contractCreate(CONTRACT).declinedReward(true).stakedNodeId(0), + // refuse eth conversion because ethereum transaction is missing staking fields to map + // (isDeclinedReward) + contractCreate(CONTRACT) + .declinedReward(true) + .stakedNodeId(0) + .refusingEthConversion(), getContractInfo(CONTRACT) .has(contractWith() .isDeclinedReward(true) @@ -177,7 +168,7 @@ final HapiSpec updateStakingFieldsWorks() { // https://github.com/hashgraph/hedera-services/issues/2877 @HapiTest - final HapiSpec eip1014AddressAlwaysHasPriority() { + final Stream eip1014AddressAlwaysHasPriority() { final var contract = "VariousCreate2Calls"; final var creationTxn = "creationTxn"; final var callTxn = "callTxn"; @@ -238,7 +229,7 @@ final HapiSpec eip1014AddressAlwaysHasPriority() { } @HapiTest - final HapiSpec updateWithBothMemoSettersWorks() { + final Stream updateWithBothMemoSettersWorks() { final var firstMemo = "First"; final var secondMemo = "Second"; final var thirdMemo = "Third"; @@ -247,7 +238,13 @@ final HapiSpec updateWithBothMemoSettersWorks() { .given( newKeyNamed(ADMIN_KEY), uploadInitCode(CONTRACT), - contractCreate(CONTRACT).adminKey(ADMIN_KEY).entityMemo(firstMemo)) + contractCreate(CONTRACT) + // Refusing ethereum create conversion, because we get INVALID_SIGNATURE upon + // tokenAssociate, + // since we have CONTRACT_ID key + .refusingEthConversion() + .adminKey(ADMIN_KEY) + .entityMemo(firstMemo)) .when( contractUpdate(CONTRACT).newMemo(secondMemo), contractUpdate(CONTRACT).newMemo(ZERO_BYTE_MEMO).hasPrecheck(INVALID_ZERO_BYTE_IN_STRING), @@ -258,7 +255,7 @@ final HapiSpec updateWithBothMemoSettersWorks() { } @HapiTest - final HapiSpec updatingExpiryWorks() { + final Stream updatingExpiryWorks() { final var newExpiry = Instant.now().getEpochSecond() + 5 * ONE_MONTH; return defaultHapiSpec("UpdatingExpiryWorks", NONDETERMINISTIC_TRANSACTION_FEES) .given(uploadInitCode(CONTRACT), contractCreate(CONTRACT)) @@ -267,7 +264,7 @@ final HapiSpec updatingExpiryWorks() { } @HapiTest - final HapiSpec rejectsExpiryTooFarInTheFuture() { + final Stream rejectsExpiryTooFarInTheFuture() { final var smallBuffer = 12_345L; final var excessiveExpiry = DEFAULT_MAX_LIFETIME + Instant.now().getEpochSecond() + smallBuffer; @@ -278,18 +275,24 @@ final HapiSpec rejectsExpiryTooFarInTheFuture() { } @HapiTest - final HapiSpec updateAutoRenewWorks() { + final Stream updateAutoRenewWorks() { return defaultHapiSpec("UpdateAutoRenewWorks", NONDETERMINISTIC_TRANSACTION_FEES) .given( newKeyNamed(ADMIN_KEY), uploadInitCode(CONTRACT), - contractCreate(CONTRACT).adminKey(ADMIN_KEY).autoRenewSecs(THREE_MONTHS_IN_SECONDS)) + contractCreate(CONTRACT) + // Refusing ethereum create conversion, because we get INVALID_SIGNATURE upon + // tokenAssociate, + // since we have CONTRACT_ID key + .refusingEthConversion() + .adminKey(ADMIN_KEY) + .autoRenewSecs(THREE_MONTHS_IN_SECONDS)) .when(contractUpdate(CONTRACT).newAutoRenew(THREE_MONTHS_IN_SECONDS + ONE_DAY)) .then(getContractInfo(CONTRACT).has(contractWith().autoRenew(THREE_MONTHS_IN_SECONDS + ONE_DAY))); } @HapiTest - final HapiSpec updateAutoRenewAccountWorks() { + final Stream updateAutoRenewAccountWorks() { final var autoRenewAccount = "autoRenewAccount"; final var newAutoRenewAccount = "newAutoRenewAccount"; return defaultHapiSpec("UpdateAutoRenewAccountWorks", NONDETERMINISTIC_TRANSACTION_FEES) @@ -298,7 +301,11 @@ final HapiSpec updateAutoRenewAccountWorks() { cryptoCreate(autoRenewAccount), cryptoCreate(newAutoRenewAccount), uploadInitCode(CONTRACT), - contractCreate(CONTRACT).adminKey(ADMIN_KEY).autoRenewAccountId(autoRenewAccount), + // refuse eth conversion because ethereum transaction is missing admin key and autoRenewAccount + contractCreate(CONTRACT) + .adminKey(ADMIN_KEY) + .autoRenewAccountId(autoRenewAccount) + .refusingEthConversion(), getContractInfo(CONTRACT) .has(ContractInfoAsserts.contractWith().autoRenewAccountId(autoRenewAccount)) .logged()) @@ -316,13 +323,15 @@ final HapiSpec updateAutoRenewAccountWorks() { } @HapiTest - final HapiSpec updateAdminKeyWorks() { + final Stream updateAdminKeyWorks() { return defaultHapiSpec("UpdateAdminKeyWorks", NONDETERMINISTIC_TRANSACTION_FEES) .given( newKeyNamed(ADMIN_KEY), newKeyNamed(NEW_ADMIN_KEY), uploadInitCode(CONTRACT), - contractCreate(CONTRACT).adminKey(ADMIN_KEY)) + // Refusing ethereum create conversion, because we get INVALID_SIGNATURE upon tokenAssociate, + // since we have CONTRACT_ID key + contractCreate(CONTRACT).refusingEthConversion().adminKey(ADMIN_KEY)) .when(contractUpdate(CONTRACT).newKey(NEW_ADMIN_KEY)) .then( contractUpdate(CONTRACT).newMemo("some new memo"), @@ -332,7 +341,7 @@ final HapiSpec updateAdminKeyWorks() { // https://github.com/hashgraph/hedera-services/issues/3037 @HapiTest - final HapiSpec immutableContractKeyFormIsStandard() { + final Stream immutableContractKeyFormIsStandard() { return defaultHapiSpec("ImmutableContractKeyFormIsStandard", NONDETERMINISTIC_TRANSACTION_FEES) .given(uploadInitCode(CONTRACT), contractCreate(CONTRACT).immutable()) .when() @@ -340,13 +349,15 @@ final HapiSpec immutableContractKeyFormIsStandard() { } @HapiTest - final HapiSpec canMakeContractImmutableWithEmptyKeyList() { + final Stream canMakeContractImmutableWithEmptyKeyList() { return defaultHapiSpec("CanMakeContractImmutableWithEmptyKeyList", NONDETERMINISTIC_TRANSACTION_FEES) .given( newKeyNamed(ADMIN_KEY), newKeyNamed(NEW_ADMIN_KEY), uploadInitCode(CONTRACT), - contractCreate(CONTRACT).adminKey(ADMIN_KEY)) + // Refusing ethereum create conversion, because we get INVALID_SIGNATURE upon tokenAssociate, + // since we have CONTRACT_ID key + contractCreate(CONTRACT).refusingEthConversion().adminKey(ADMIN_KEY)) .when( contractUpdate(CONTRACT).improperlyEmptyingAdminKey().hasPrecheck(INVALID_ADMIN_KEY), contractUpdate(CONTRACT).properlyEmptyingAdminKey()) @@ -354,10 +365,12 @@ final HapiSpec canMakeContractImmutableWithEmptyKeyList() { } @HapiTest - final HapiSpec givenAdminKeyMustBeValid() { + final Stream givenAdminKeyMustBeValid() { final var contract = "BalanceLookup"; return defaultHapiSpec("GivenAdminKeyMustBeValid", NONDETERMINISTIC_TRANSACTION_FEES) - .given(uploadInitCode(contract), contractCreate(contract)) + // Refusing ethereum create conversion, because we get INVALID_SIGNATURE upon tokenAssociate, + // since we have CONTRACT_ID key + .given(uploadInitCode(contract), contractCreate(contract).refusingEthConversion()) .when(getContractInfo(contract).logged()) .then(contractUpdate(contract) .useDeprecatedAdminKey() @@ -366,7 +379,7 @@ final HapiSpec givenAdminKeyMustBeValid() { } @HapiTest - HapiSpec fridayThe13thSpec() { + final Stream fridayThe13thSpec() { final var contract = "SimpleStorage"; final var suffix = "Clone"; final var newExpiry = Instant.now().getEpochSecond() + DEFAULT_PROPS.defaultExpirationSecs() * 2; @@ -386,10 +399,13 @@ HapiSpec fridayThe13thSpec() { uploadInitCode(contract)) .when( contractCreate(contract).payingWith(payer).omitAdminKey(), + // refuse eth conversion because ethereum transaction is missing admin key and memo is same as + // parent contractCustomCreate(contract, suffix) .payingWith(payer) .adminKey(INITIAL_ADMIN_KEY) - .entityMemo(INITIAL_MEMO), + .entityMemo(INITIAL_MEMO) + .refusingEthConversion(), getContractInfo(contract + suffix) .payingWith(payer) .logged() @@ -459,14 +475,16 @@ HapiSpec fridayThe13thSpec() { } @HapiTest - final HapiSpec updateDoesNotChangeBytecode() { + final Stream updateDoesNotChangeBytecode() { // HSCS-DCPR-001 final var simpleStorageContract = "SimpleStorage"; final var emptyConstructorContract = "EmptyConstructor"; return defaultHapiSpec("updateDoesNotChangeBytecode", NONDETERMINISTIC_TRANSACTION_FEES) .given( uploadInitCode(simpleStorageContract, emptyConstructorContract), - contractCreate(simpleStorageContract), + // Refusing ethereum create conversion, because we get INVALID_SIGNATURE upon tokenAssociate, + // since we have CONTRACT_ID key + contractCreate(simpleStorageContract).refusingEthConversion(), getContractBytecode(simpleStorageContract).saveResultTo("initialBytecode")) .when(contractUpdate(simpleStorageContract).bytecode(emptyConstructorContract)) .then(withOpContext((spec, log) -> { @@ -477,7 +495,7 @@ final HapiSpec updateDoesNotChangeBytecode() { } @HapiTest - private HapiSpec cannotUpdateMaxAutomaticAssociations() { + final Stream cannotUpdateMaxAutomaticAssociations() { return defaultHapiSpec("cannotUpdateMaxAutomaticAssociations") .given( newKeyNamed(ADMIN_KEY), @@ -487,8 +505,63 @@ private HapiSpec cannotUpdateMaxAutomaticAssociations() { .then(contractUpdate(CONTRACT).newMaxAutomaticAssociations(20).hasKnownStatus(NOT_SUPPORTED)); } - @Override - protected Logger getResultsLogger() { - return log; + @HapiTest + final Stream playGame() { + final var dj = "dj"; + final var players = IntStream.range(1, 30).mapToObj(i -> "Player" + i).toList(); + final var contract = "MusicalChairs"; + + List given = new ArrayList<>(); + List when = new ArrayList<>(); + List then = new ArrayList<>(); + + ////// Create contract ////// + given.add(cryptoCreate(dj).balance(10 * ONE_HUNDRED_HBARS)); + given.add(getAccountInfo(DEFAULT_CONTRACT_SENDER).savingSnapshot(DEFAULT_CONTRACT_SENDER)); + given.add(uploadInitCode(contract)); + given.add(withOpContext((spec, opLog) -> allRunFor( + spec, + contractCreate( + contract, + asHeadlongAddress(spec.registry() + .getAccountInfo(DEFAULT_CONTRACT_SENDER) + .getContractAccountID())) + .payingWith(dj)))); + + ////// Add the players ////// + players.stream().map(TxnVerbs::cryptoCreate).forEach(given::add); + + ////// Start the music! ////// + when.add(contractCall(contract, "startMusic").payingWith(DEFAULT_CONTRACT_SENDER)); + + ////// 100 "random" seats taken ////// + new Random(0x1337) + .ints(100, 0, 29) + .forEach(i -> when.add(contractCall(contract, "sitDown") + .payingWith(players.get(i)) + .refusingEthConversion() + .hasAnyStatusAtAll())); // sometimes a player sits + // too soon, so don't fail + // on reverts + + ////// Stop the music! ////// + then.add(contractCall(contract, "stopMusic").payingWith(DEFAULT_CONTRACT_SENDER)); + + ////// And the winner is..... ////// + then.add(withOpContext((spec, opLog) -> allRunFor( + spec, + contractCallLocal(contract, "whoIsOnTheBubble") + .has(resultWith() + .resultThruAbi( + getABIFor(FUNCTION, "whoIsOnTheBubble", contract), + isLiteralResult(new Object[] { + HapiParserUtil.asHeadlongAddress( + asAddress(spec.registry().getAccountID("Player13"))) + })))))); + + return defaultHapiSpec("playGame") + .given(given.toArray(HapiSpecOperation[]::new)) + .when(when.toArray(HapiSpecOperation[]::new)) + .then(then.toArray(HapiSpecOperation[]::new)); } } diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/opcodes/Create2OperationSuite.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/opcodes/Create2OperationSuite.java index 7e5f85db06ef..f65300fb5951 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/opcodes/Create2OperationSuite.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/opcodes/Create2OperationSuite.java @@ -16,6 +16,8 @@ package com.hedera.services.bdd.suites.contract.opcodes; +import static com.hedera.services.bdd.junit.ContextRequirement.NO_CONCURRENT_CREATIONS; +import static com.hedera.services.bdd.junit.ContextRequirement.PROPERTY_OVERRIDES; import static com.hedera.services.bdd.junit.TestTags.SMART_CONTRACT; import static com.hedera.services.bdd.spec.HapiPropertySource.accountIdFromHexedMirrorAddress; import static com.hedera.services.bdd.spec.HapiPropertySource.asContractString; @@ -25,6 +27,7 @@ import static com.hedera.services.bdd.spec.HapiPropertySource.explicitBytesOf; import static com.hedera.services.bdd.spec.HapiPropertySource.literalIdFromHexedMirrorAddress; import static com.hedera.services.bdd.spec.HapiSpec.defaultHapiSpec; +import static com.hedera.services.bdd.spec.HapiSpec.propertyPreservingHapiSpec; import static com.hedera.services.bdd.spec.assertions.AccountInfoAsserts.accountWith; import static com.hedera.services.bdd.spec.assertions.AssertUtils.inOrder; import static com.hedera.services.bdd.spec.assertions.ContractFnResultAsserts.isLiteralResult; @@ -66,6 +69,7 @@ import static com.hedera.services.bdd.spec.utilops.UtilVerbs.inParallel; import static com.hedera.services.bdd.spec.utilops.UtilVerbs.logIt; import static com.hedera.services.bdd.spec.utilops.UtilVerbs.newKeyNamed; +import static com.hedera.services.bdd.spec.utilops.UtilVerbs.overriding; import static com.hedera.services.bdd.spec.utilops.UtilVerbs.sourcing; import static com.hedera.services.bdd.spec.utilops.UtilVerbs.withOpContext; import static com.hedera.services.bdd.spec.utilops.records.SnapshotMatchMode.ACCEPTED_MONO_GAS_CALCULATION_DIFFERENCE; @@ -77,6 +81,15 @@ import static com.hedera.services.bdd.spec.utilops.records.SnapshotMatchMode.NONDETERMINISTIC_FUNCTION_PARAMETERS; import static com.hedera.services.bdd.spec.utilops.records.SnapshotMatchMode.NONDETERMINISTIC_LOG_DATA; import static com.hedera.services.bdd.spec.utilops.records.SnapshotMatchMode.NONDETERMINISTIC_TRANSACTION_FEES; +import static com.hedera.services.bdd.suites.HapiSuite.DEFAULT_PAYER; +import static com.hedera.services.bdd.suites.HapiSuite.FUNDING; +import static com.hedera.services.bdd.suites.HapiSuite.GENESIS; +import static com.hedera.services.bdd.suites.HapiSuite.ONE_HBAR; +import static com.hedera.services.bdd.suites.HapiSuite.ONE_HUNDRED_HBARS; +import static com.hedera.services.bdd.suites.HapiSuite.RELAYER; +import static com.hedera.services.bdd.suites.HapiSuite.SECP_256K1_SHAPE; +import static com.hedera.services.bdd.suites.HapiSuite.SECP_256K1_SOURCE_KEY; +import static com.hedera.services.bdd.suites.HapiSuite.TOKEN_TREASURY; import static com.hedera.services.bdd.suites.contract.Utils.FunctionType.FUNCTION; import static com.hedera.services.bdd.suites.contract.Utils.aaWith; import static com.hedera.services.bdd.suites.contract.Utils.accountId; @@ -119,15 +132,13 @@ import com.google.protobuf.ByteString; import com.hedera.node.app.hapi.utils.contracts.ParsingConstants.FunctionType; import com.hedera.services.bdd.junit.HapiTest; -import com.hedera.services.bdd.junit.HapiTestSuite; +import com.hedera.services.bdd.junit.LeakyHapiTest; import com.hedera.services.bdd.spec.HapiPropertySource; -import com.hedera.services.bdd.spec.HapiSpec; import com.hedera.services.bdd.spec.HapiSpecOperation; import com.hedera.services.bdd.spec.queries.contract.HapiContractCallLocal; import com.hedera.services.bdd.spec.transactions.contract.HapiParserUtil; import com.hedera.services.bdd.spec.transactions.crypto.HapiCryptoTransfer; import com.hedera.services.bdd.spec.utilops.CustomSpecAssert; -import com.hedera.services.bdd.suites.HapiSuite; import com.hederahashgraph.api.proto.java.AccountID; import com.hederahashgraph.api.proto.java.ContractID; import com.hederahashgraph.api.proto.java.NftTransfer; @@ -142,14 +153,15 @@ import java.util.concurrent.atomic.AtomicLong; import java.util.concurrent.atomic.AtomicReference; import java.util.stream.IntStream; +import java.util.stream.Stream; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.apache.tuweni.bytes.Bytes; +import org.junit.jupiter.api.DynamicTest; import org.junit.jupiter.api.Tag; -@HapiTestSuite @Tag(SMART_CONTRACT) -public class Create2OperationSuite extends HapiSuite { +public class Create2OperationSuite { public static final String GET_BYTECODE = "getBytecode"; public static final String DEPLOY = "deploy"; @@ -169,44 +181,9 @@ public class Create2OperationSuite extends HapiSuite { private static final String ENTITY_MEMO = "JUST DO IT"; private static final String DELETED_CREATE_2_LOG = "Deleted the deployed CREATE2 contract using HAPI"; - public static void main(String... args) { - new Create2OperationSuite().runSuiteSync(); - } - - @Override - protected Logger getResultsLogger() { - return LOG; - } - - @Override - public boolean canRunConcurrent() { - return false; - } - - @Override - public List getSpecsInSuite() { - return List.of( - create2FactoryWorksAsExpected(), - payableCreate2WorksAsExpected(), - canDeleteViaAlias(), - cannotSelfDestructToMirrorAddress(), - priorityAddressIsCreate2ForStaticHapiCalls(), - create2InputAddressIsStableWithTopLevelCallWhetherMirrorOrAliasIsUsed(), - canUseAliasesInPrecompilesAndContractKeys(), - inlineCreateCanFailSafely(), - inlineCreate2CanFailSafely(), - allLogOpcodesResolveExpectedContractId(), - eip1014AliasIsPriorityInErcOwnerPrecompile(), - canAssociateInConstructor(), - /* --- HIP 583 --- */ - canMergeCreate2ChildWithHollowAccount(), - canMergeCreate2MultipleCreatesWithHollowAccount(), - canCallFinalizedContractViaHapi()); - } - @SuppressWarnings("java:S5669") - @HapiTest - final HapiSpec allLogOpcodesResolveExpectedContractId() { + @LeakyHapiTest(NO_CONCURRENT_CREATIONS) + final Stream allLogOpcodesResolveExpectedContractId() { final var contract = "OuterCreator"; final AtomicLong outerCreatorNum = new AtomicLong(); @@ -239,8 +216,8 @@ final HapiSpec allLogOpcodesResolveExpectedContractId() { } // https://github.com/hashgraph/hedera-services/issues/2868 - @HapiTest - final HapiSpec inlineCreate2CanFailSafely() { + @LeakyHapiTest(NO_CONCURRENT_CREATIONS) + final Stream inlineCreate2CanFailSafely() { final var tcValue = 1_234L; final var contract = "RevertingCreateFactory"; final var foo = BigInteger.valueOf(22); @@ -287,8 +264,8 @@ final HapiSpec inlineCreate2CanFailSafely() { } @SuppressWarnings("java:S5669") - @HapiTest - final HapiSpec inlineCreateCanFailSafely() { + @LeakyHapiTest(NO_CONCURRENT_CREATIONS) + final Stream inlineCreateCanFailSafely() { final var tcValue = 1_234L; final var creation = CREATION; final var contract = "RevertingCreateFactory"; @@ -336,7 +313,7 @@ final HapiSpec inlineCreateCanFailSafely() { } @HapiTest - final HapiSpec canAssociateInConstructor() { + final Stream canAssociateInConstructor() { final var token = "token"; final var contract = "SelfAssociating"; final AtomicReference tokenMirrorAddr = new AtomicReference<>(); @@ -361,7 +338,7 @@ final HapiSpec canAssociateInConstructor() { } @HapiTest - final HapiSpec payableCreate2WorksAsExpected() { + final Stream payableCreate2WorksAsExpected() { final var contract = "PayableCreate2Deploy"; AtomicReference tcMirrorAddr2 = new AtomicReference<>(); AtomicReference tcAliasAddr2 = new AtomicReference<>(); @@ -383,8 +360,8 @@ final HapiSpec payableCreate2WorksAsExpected() { // https://github.com/hashgraph/hedera-services/issues/2867 // https://github.com/hashgraph/hedera-services/issues/2868 @SuppressWarnings("java:S5960") - @HapiTest - final HapiSpec create2FactoryWorksAsExpected() { + @LeakyHapiTest(PROPERTY_OVERRIDES) + final Stream create2FactoryWorksAsExpected() { final var tcValue = 1_234L; final var contract = "Create2Factory"; final var testContract = "TestContract"; @@ -401,13 +378,15 @@ final HapiSpec create2FactoryWorksAsExpected() { final AtomicReference bytecodeFromAlias = new AtomicReference<>(); final AtomicReference mirrorLiteralId = new AtomicReference<>(); - return defaultHapiSpec( + return propertyPreservingHapiSpec( "Create2FactoryWorksAsExpected", NONDETERMINISTIC_FUNCTION_PARAMETERS, NONDETERMINISTIC_CONTRACT_CALL_RESULTS, NONDETERMINISTIC_TRANSACTION_FEES, NONDETERMINISTIC_LOG_DATA) + .preserving("contracts.evm.version") .given( + overriding("contracts.evm.version", "v0.46"), newKeyNamed(adminKey), newKeyNamed(replAdminKey), uploadInitCode(contract), @@ -521,7 +500,7 @@ contract, GET_BYTECODE, asHeadlongAddress(factoryEvmAddress.get()), salt) @SuppressWarnings("java:S5960") @HapiTest - final HapiSpec canMergeCreate2ChildWithHollowAccount() { + final Stream canMergeCreate2ChildWithHollowAccount() { final var tcValue = 1_234L; final var contract = "Create2Factory"; final var creation = CREATION; @@ -640,7 +619,7 @@ contract, GET_BYTECODE, asHeadlongAddress(factoryEvmAddress.get()), salt) @SuppressWarnings("java:S5960") @HapiTest - final HapiSpec canMergeCreate2MultipleCreatesWithHollowAccount() { + final Stream canMergeCreate2MultipleCreatesWithHollowAccount() { final var tcValue = 1_234L; final var contract = "Create2MultipleCreates"; final var creation = CREATION; @@ -761,8 +740,8 @@ contract, GET_BYTECODE, asHeadlongAddress(factoryEvmAddress.get()), salt) cryptoCreate("confirmingNoEntityIdCollision")); } - @HapiTest - final HapiSpec canCallFinalizedContractViaHapi() { + @LeakyHapiTest(PROPERTY_OVERRIDES) + final Stream canCallFinalizedContractViaHapi() { final var contract = "FinalizedDestructible"; final var salt = BigInteger.valueOf(1_234_567_890L); final AtomicReference
    childAddress = new AtomicReference<>(); @@ -770,11 +749,13 @@ final HapiSpec canCallFinalizedContractViaHapi() { final var vacateAddressAbi = "{\"inputs\":[],\"name\":\"vacateAddress\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"}"; - return defaultHapiSpec( + return propertyPreservingHapiSpec( "CanCallFinalizedContractViaHapi", NONDETERMINISTIC_ETHEREUM_DATA, NONDETERMINISTIC_TRANSACTION_FEES) + .preserving("contracts.evm.version") .given( + overriding("contracts.evm.version", "v0.46"), cryptoCreate(RELAYER).balance(ONE_HUNDRED_HBARS), newKeyNamed(SECP_256K1_SOURCE_KEY).shape(SECP_256K1_SHAPE), cryptoTransfer(tinyBarsFromAccountToAlias(GENESIS, SECP_256K1_SOURCE_KEY, ONE_HUNDRED_HBARS)), @@ -801,7 +782,7 @@ final HapiSpec canCallFinalizedContractViaHapi() { @SuppressWarnings("java:S5669") @HapiTest - final HapiSpec eip1014AliasIsPriorityInErcOwnerPrecompile() { + final Stream eip1014AliasIsPriorityInErcOwnerPrecompile() { final var ercContract = "ERC721Contract"; final var pc2User = "Create2PrecompileUser"; final var nft = "nonFungibleToken"; @@ -868,7 +849,7 @@ final HapiSpec eip1014AliasIsPriorityInErcOwnerPrecompile() { @SuppressWarnings("java:S5669") @HapiTest - final HapiSpec canUseAliasesInPrecompilesAndContractKeys() { + final Stream canUseAliasesInPrecompilesAndContractKeys() { final var creation2 = CREATE_2_TXN; final var contract = "Create2PrecompileUser"; final var userContract = "Create2User"; @@ -1058,7 +1039,7 @@ final HapiSpec canUseAliasesInPrecompilesAndContractKeys() { // https://github.com/hashgraph/hedera-services/issues/2925 @SuppressWarnings("java:S5669") @HapiTest - final HapiSpec cannotSelfDestructToMirrorAddress() { + final Stream cannotSelfDestructToMirrorAddress() { final var creation2 = CREATE_2_TXN; final var messyCreation2 = "messyCreate2Txn"; final var contract = "CreateDonor"; @@ -1112,7 +1093,7 @@ final HapiSpec cannotSelfDestructToMirrorAddress() { // https://github.com/hashgraph/hedera-services/issues/2874 @SuppressWarnings("java:S5669") @HapiTest - final HapiSpec canDeleteViaAlias() { + final Stream canDeleteViaAlias() { final var adminKey = ADMIN_KEY; final var creation2 = CREATE_2_TXN; final var deletion = "deletion"; @@ -1185,8 +1166,8 @@ final HapiSpec canDeleteViaAlias() { } @SuppressWarnings("java:S5669") - @HapiTest - final HapiSpec create2InputAddressIsStableWithTopLevelCallWhetherMirrorOrAliasIsUsed() { + @LeakyHapiTest(PROPERTY_OVERRIDES) + final Stream create2InputAddressIsStableWithTopLevelCallWhetherMirrorOrAliasIsUsed() { final var creation2 = CREATE_2_TXN; final var innerCreation2 = "innerCreate2Txn"; final var delegateCreation2 = "delegateCreate2Txn"; @@ -1202,11 +1183,13 @@ final HapiSpec create2InputAddressIsStableWithTopLevelCallWhetherMirrorOrAliasIs final var salt = unhex(SALT); - return defaultHapiSpec( + return propertyPreservingHapiSpec( "Create2InputAddressIsStableWithTopLevelCallWhetherMirrorOrAliasIsUsed", NONDETERMINISTIC_TRANSACTION_FEES, NONDETERMINISTIC_FUNCTION_PARAMETERS) + .preserving("contracts.evm.version") .given( + overriding("contracts.evm.version", "v0.46"), uploadInitCode(contract), contractCreate(contract).payingWith(GENESIS), contractCall(contract, "buildCreator", salt) @@ -1263,7 +1246,7 @@ final HapiSpec create2InputAddressIsStableWithTopLevelCallWhetherMirrorOrAliasIs @SuppressWarnings("java:S5669") @HapiTest - final HapiSpec priorityAddressIsCreate2ForStaticHapiCalls() { + final Stream priorityAddressIsCreate2ForStaticHapiCalls() { final var contract = "AddressValueRet"; final AtomicReference aliasAddr = new AtomicReference<>(); diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/opcodes/Create2OperationV1SecurityModelSuite.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/opcodes/Create2OperationV1SecurityModelSuite.java index 17594cbb4f89..bc540120892d 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/opcodes/Create2OperationV1SecurityModelSuite.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/opcodes/Create2OperationV1SecurityModelSuite.java @@ -32,12 +32,13 @@ import static com.swirlds.common.utility.CommonUtils.hex; import com.hedera.services.bdd.spec.HapiPropertySource; -import com.hedera.services.bdd.spec.HapiSpec; import com.hedera.services.bdd.suites.HapiSuite; import java.util.List; import java.util.concurrent.atomic.AtomicReference; +import java.util.stream.Stream; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; +import org.junit.jupiter.api.DynamicTest; public class Create2OperationV1SecurityModelSuite extends HapiSuite { @@ -60,11 +61,11 @@ public boolean canRunConcurrent() { } @Override - public List getSpecsInSuite() { + public List> getSpecsInSuite() { return List.of(childInheritanceOfAdminKeyAuthorizesParentAssociationInConstructor()); } - final HapiSpec childInheritanceOfAdminKeyAuthorizesParentAssociationInConstructor() { + final Stream childInheritanceOfAdminKeyAuthorizesParentAssociationInConstructor() { final var ft = "fungibleToken"; final var multiKey = SWISS; final var creationAndAssociation = "creationAndAssociation"; diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/opcodes/CreateOperationSuite.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/opcodes/CreateOperationSuite.java index 8735026e0481..35e83b5c3a43 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/opcodes/CreateOperationSuite.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/opcodes/CreateOperationSuite.java @@ -39,33 +39,28 @@ import static com.hedera.services.bdd.spec.utilops.records.SnapshotMatchMode.NONDETERMINISTIC_FUNCTION_PARAMETERS; import static com.hedera.services.bdd.spec.utilops.records.SnapshotMatchMode.NONDETERMINISTIC_LOG_DATA; import static com.hedera.services.bdd.spec.utilops.records.SnapshotMatchMode.NONDETERMINISTIC_TRANSACTION_FEES; +import static com.hedera.services.bdd.suites.HapiSuite.ONE_HUNDRED_HBARS; import static com.hedera.services.bdd.suites.contract.Utils.FunctionType.FUNCTION; import static com.hedera.services.bdd.suites.contract.Utils.eventSignatureOf; import static com.hedera.services.bdd.suites.contract.Utils.getABIFor; import com.esaulpaugh.headlong.abi.Address; import com.hedera.services.bdd.junit.HapiTest; -import com.hedera.services.bdd.junit.HapiTestSuite; -import com.hedera.services.bdd.spec.HapiSpec; import com.hedera.services.bdd.spec.transactions.TxnUtils; import com.hedera.services.bdd.spec.utilops.CustomSpecAssert; import com.hedera.services.bdd.spec.utilops.UtilVerbs; -import com.hedera.services.bdd.suites.HapiSuite; import com.hederahashgraph.api.proto.java.ContractGetInfoResponse; import com.hederahashgraph.api.proto.java.ContractID; import com.hederahashgraph.api.proto.java.ResponseCodeEnum; import java.math.BigInteger; import java.util.List; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; +import java.util.stream.Stream; import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.DynamicTest; import org.junit.jupiter.api.Tag; -@HapiTestSuite @Tag(SMART_CONTRACT) -public class CreateOperationSuite extends HapiSuite { - - private static final Logger log = LogManager.getLogger(CreateOperationSuite.class); +public class CreateOperationSuite { private static final String CONTRACT = "FactoryContract"; private static final String CALL_RECORD_TRANSACTION_NAME = "callRecord"; private static final String DEPLOYMENT_SUCCESS_FUNCTION = "deploymentSuccess"; @@ -73,31 +68,8 @@ public class CreateOperationSuite extends HapiSuite { private static final String CONTRACT_INFO = "contractInfo"; private static final String PARENT_INFO = "parentInfo"; - public static void main(final String... args) { - new CreateOperationSuite().runSuiteAsync(); - } - - @Override - public List getSpecsInSuite() { - return List.of( - simpleFactoryWorks(), - stackedFactoryWorks(), - resetOnFactoryFailureWorks(), - resetOnFactoryFailureAfterDeploymentWorks(), - resetOnStackedFactoryFailureWorks(), - inheritanceOfNestedCreatedContracts(), - factoryQuickSelfDestructContract(), - contractCreateWithNewOpInConstructorAbandoningParent(), - childContractStorageWorks()); - } - - @Override - public boolean canRunConcurrent() { - return true; - } - @HapiTest - final HapiSpec factoryQuickSelfDestructContract() { + final Stream factoryQuickSelfDestructContract() { final var contract = "FactoryQuickSelfDestruct"; final var sender = "sender"; return defaultHapiSpec( @@ -125,12 +97,14 @@ final HapiSpec factoryQuickSelfDestructContract() { } @HapiTest - final HapiSpec inheritanceOfNestedCreatedContracts() { + final Stream inheritanceOfNestedCreatedContracts() { final var contract = "NestedChildren"; return defaultHapiSpec("InheritanceOfNestedCreatedContracts", FULLY_NONDETERMINISTIC) .given( uploadInitCode(contract), - contractCreate(contract).logged().via("createRecord"), + // refuse eth conversion because ethereum transaction is missing admin key and memo is same as + // parent + contractCreate(contract).logged().via("createRecord").refusingEthConversion(), getContractInfo(contract).logged().saveToRegistry(PARENT_INFO)) .when(contractCall(contract, "callCreate").gas(780_000).via(CALL_RECORD_TRANSACTION_NAME)) .then( @@ -141,7 +115,7 @@ final HapiSpec inheritanceOfNestedCreatedContracts() { } @HapiTest - HapiSpec simpleFactoryWorks() { + final Stream simpleFactoryWorks() { return defaultHapiSpec("simpleFactoryWorks", NONDETERMINISTIC_TRANSACTION_FEES) .given(uploadInitCode(CONTRACT), contractCreate(CONTRACT)) .when(contractCall(CONTRACT, DEPLOYMENT_SUCCESS_FUNCTION) @@ -162,7 +136,7 @@ HapiSpec simpleFactoryWorks() { } @HapiTest - HapiSpec stackedFactoryWorks() { + final Stream stackedFactoryWorks() { return defaultHapiSpec("StackedFactoryWorks", FULLY_NONDETERMINISTIC) .given(uploadInitCode(CONTRACT), contractCreate(CONTRACT)) .when(contractCall(CONTRACT, "stackedDeploymentSuccess") @@ -183,7 +157,7 @@ HapiSpec stackedFactoryWorks() { } @HapiTest - HapiSpec resetOnFactoryFailureWorks() { + final Stream resetOnFactoryFailureWorks() { return defaultHapiSpec("ResetOnFactoryFailureWorks") .given(uploadInitCode(CONTRACT), contractCreate(CONTRACT)) .when( @@ -215,7 +189,7 @@ HapiSpec resetOnFactoryFailureWorks() { } @HapiTest - HapiSpec resetOnFactoryFailureAfterDeploymentWorks() { + final Stream resetOnFactoryFailureAfterDeploymentWorks() { return defaultHapiSpec("ResetOnFactoryFailureAfterDeploymentWorks") .given(uploadInitCode(CONTRACT), contractCreate(CONTRACT)) .when( @@ -247,7 +221,7 @@ HapiSpec resetOnFactoryFailureAfterDeploymentWorks() { } @HapiTest - HapiSpec resetOnStackedFactoryFailureWorks() { + final Stream resetOnStackedFactoryFailureWorks() { return defaultHapiSpec("ResetOnStackedFactoryFailureWorks") .given(uploadInitCode(CONTRACT), contractCreate(CONTRACT)) .when( @@ -279,14 +253,18 @@ HapiSpec resetOnStackedFactoryFailureWorks() { } @HapiTest - final HapiSpec contractCreateWithNewOpInConstructorAbandoningParent() { + final Stream contractCreateWithNewOpInConstructorAbandoningParent() { final var contract = "AbandoningParent"; return defaultHapiSpec( "contractCreateWithNewOpInConstructorAbandoningParent", NONDETERMINISTIC_FUNCTION_PARAMETERS, HIGHLY_NON_DETERMINISTIC_FEES, ACCEPTED_MONO_GAS_CALCULATION_DIFFERENCE) - .given(uploadInitCode(contract), contractCreate(contract).via("AbandoningParentTxn")) + // refuse eth conversion because ethereum transaction is missing admin key (the new contract has own key + // - isSelfAdmin(parent)) + .given( + uploadInitCode(contract), + contractCreate(contract).via("AbandoningParentTxn").refusingEthConversion()) .when() .then( getContractInfo(contract) @@ -300,7 +278,7 @@ final HapiSpec contractCreateWithNewOpInConstructorAbandoningParent() { } @HapiTest - HapiSpec childContractStorageWorks() { + final Stream childContractStorageWorks() { final var contract = "CreateTrivial"; final var CREATED_TRIVIAL_CONTRACT_RETURNS = 7; @@ -370,9 +348,4 @@ HapiSpec childContractStorageWorks() { Assertions.assertTrue(createdContractInfo.hasExpirationTime()); })); } - - @Override - protected Logger getResultsLogger() { - return log; - } } diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/opcodes/DelegateCallOperationSuite.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/opcodes/DelegateCallOperationSuite.java index c7ac2a00bbe8..e0f5574d04ab 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/opcodes/DelegateCallOperationSuite.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/opcodes/DelegateCallOperationSuite.java @@ -24,40 +24,19 @@ import static com.hedera.services.bdd.spec.transactions.contract.HapiParserUtil.asHeadlongAddress; import static com.hedera.services.bdd.spec.utilops.CustomSpecAssert.allRunFor; import static com.hedera.services.bdd.spec.utilops.UtilVerbs.withOpContext; +import static com.hedera.services.bdd.suites.HapiSuite.DEFAULT_PAYER; import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.SUCCESS; import com.hedera.services.bdd.junit.HapiTest; -import com.hedera.services.bdd.junit.HapiTestSuite; import com.hedera.services.bdd.spec.HapiPropertySource; -import com.hedera.services.bdd.spec.HapiSpec; -import com.hedera.services.bdd.suites.HapiSuite; -import java.util.List; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; +import java.util.stream.Stream; +import org.junit.jupiter.api.DynamicTest; import org.junit.jupiter.api.Tag; -@HapiTestSuite @Tag(SMART_CONTRACT) -public class DelegateCallOperationSuite extends HapiSuite { - - private static final Logger log = LogManager.getLogger(DelegateCallOperationSuite.class); - - public static void main(String[] args) { - new DelegateCallOperationSuite().runSuiteAsync(); - } - - @Override - public List getSpecsInSuite() { - return List.of(verifiesExistence()); - } - - @Override - public boolean canRunConcurrent() { - return true; - } - +public class DelegateCallOperationSuite { @HapiTest - HapiSpec verifiesExistence() { + final Stream verifiesExistence() { final var contract = "CallOperationsChecker"; final var INVALID_ADDRESS = "0x0000000000000000000000000000000000123456"; return defaultHapiSpec("VerifiesExistence") @@ -77,9 +56,4 @@ HapiSpec verifiesExistence() { allRunFor(spec, contractCall); })); } - - @Override - protected Logger getResultsLogger() { - return log; - } } diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/opcodes/ExtCodeCopyOperationSuite.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/opcodes/ExtCodeCopyOperationSuite.java index 86c3d8a07780..f17ab0b16d20 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/opcodes/ExtCodeCopyOperationSuite.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/opcodes/ExtCodeCopyOperationSuite.java @@ -38,39 +38,17 @@ import com.google.protobuf.ByteString; import com.hedera.services.bdd.junit.HapiTest; -import com.hedera.services.bdd.junit.HapiTestSuite; -import com.hedera.services.bdd.spec.HapiSpec; import com.hedera.services.bdd.spec.HapiSpecOperation; -import com.hedera.services.bdd.suites.HapiSuite; -import java.util.List; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; +import java.util.stream.Stream; import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.DynamicTest; import org.junit.jupiter.api.Tag; -@HapiTestSuite(fuzzyMatch = true) @Tag(SMART_CONTRACT) -public class ExtCodeCopyOperationSuite extends HapiSuite { - - private static final Logger LOG = LogManager.getLogger(ExtCodeCopyOperationSuite.class); - - public static void main(String[] args) { - new ExtCodeCopyOperationSuite().runSuiteAsync(); - } - - @Override - public List getSpecsInSuite() { - return List.of(verifiesExistence(), testExtCodeCopyWithSystemAccounts()); - } - - @Override - public boolean canRunConcurrent() { - return true; - } - +public class ExtCodeCopyOperationSuite { @SuppressWarnings("java:S5960") @HapiTest - HapiSpec verifiesExistence() { + final Stream verifiesExistence() { final var contract = "ExtCodeOperationsChecker"; final var invalidAddress = "0x0000000000000000000000000000000000123456"; final var emptyBytecode = ByteString.EMPTY; @@ -128,7 +106,7 @@ contract, codeCopyOf, asHeadlongAddress(contractAddress)) } @HapiTest - HapiSpec testExtCodeCopyWithSystemAccounts() { + final Stream testExtCodeCopyWithSystemAccounts() { final var contract = "ExtCodeOperationsChecker"; final var emptyBytecode = ByteString.EMPTY; final var codeCopyOf = "codeCopyOf"; @@ -160,9 +138,4 @@ HapiSpec testExtCodeCopyWithSystemAccounts() { .when() .then(opsArray); } - - @Override - protected Logger getResultsLogger() { - return LOG; - } } diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/opcodes/ExtCodeHashOperationSuite.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/opcodes/ExtCodeHashOperationSuite.java index d00bfaffbe5e..39a53b24e0af 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/opcodes/ExtCodeHashOperationSuite.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/opcodes/ExtCodeHashOperationSuite.java @@ -39,42 +39,20 @@ import com.google.protobuf.ByteString; import com.hedera.services.bdd.junit.HapiTest; -import com.hedera.services.bdd.junit.HapiTestSuite; -import com.hedera.services.bdd.spec.HapiSpec; import com.hedera.services.bdd.spec.HapiSpecOperation; import com.hedera.services.bdd.spec.assertions.ContractFnResultAsserts; -import com.hedera.services.bdd.suites.HapiSuite; -import java.util.List; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; +import java.util.stream.Stream; import org.apache.tuweni.bytes.Bytes; import org.hyperledger.besu.crypto.Hash; import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.DynamicTest; import org.junit.jupiter.api.Tag; -@HapiTestSuite @Tag(SMART_CONTRACT) -public class ExtCodeHashOperationSuite extends HapiSuite { - - private static final Logger LOG = LogManager.getLogger(ExtCodeHashOperationSuite.class); - - public static void main(String[] args) { - new ExtCodeHashOperationSuite().runSuiteAsync(); - } - - @Override - public List getSpecsInSuite() { - return List.of(verifiesExistence(), testExtCodeHashWithSystemAccounts()); - } - - @Override - public boolean canRunConcurrent() { - return true; - } - +public class ExtCodeHashOperationSuite { @SuppressWarnings("java:S5960") @HapiTest - HapiSpec verifiesExistence() { + final Stream verifiesExistence() { final var contract = "ExtCodeOperationsChecker"; final var invalidAddress = "0x0000000000000000000000000000000000123456"; final var expectedAccountHash = @@ -131,7 +109,7 @@ contract, hashOf, asHeadlongAddress(contractAddress)) } @HapiTest - HapiSpec testExtCodeHashWithSystemAccounts() { + final Stream testExtCodeHashWithSystemAccounts() { final var contract = "ExtCodeOperationsChecker"; final var hashOf = "hashOf"; final String account = "account"; @@ -156,9 +134,4 @@ contract, hashOf, mirrorAddrWith(systemAccounts.get(i))) .when() .then(opsArray); } - - @Override - protected Logger getResultsLogger() { - return LOG; - } } diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/opcodes/ExtCodeSizeOperationSuite.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/opcodes/ExtCodeSizeOperationSuite.java index 1c1d0c100f18..4c0dde371a5b 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/opcodes/ExtCodeSizeOperationSuite.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/opcodes/ExtCodeSizeOperationSuite.java @@ -41,41 +41,19 @@ import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.SUCCESS; import com.hedera.services.bdd.junit.HapiTest; -import com.hedera.services.bdd.junit.HapiTestSuite; -import com.hedera.services.bdd.spec.HapiSpec; import com.hedera.services.bdd.spec.HapiSpecOperation; import com.hedera.services.bdd.spec.assertions.ContractFnResultAsserts; -import com.hedera.services.bdd.suites.HapiSuite; import java.math.BigInteger; -import java.util.List; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; +import java.util.stream.Stream; import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.DynamicTest; import org.junit.jupiter.api.Tag; -@HapiTestSuite @Tag(SMART_CONTRACT) -public class ExtCodeSizeOperationSuite extends HapiSuite { - - private static final Logger LOG = LogManager.getLogger(ExtCodeSizeOperationSuite.class); - - public static void main(String[] args) { - new ExtCodeSizeOperationSuite().runSuiteAsync(); - } - - @Override - public List getSpecsInSuite() { - return List.of(verifiesExistence(), testExtCodeSizeWithSystemAccounts()); - } - - @Override - public boolean canRunConcurrent() { - return true; - } - +public class ExtCodeSizeOperationSuite { @SuppressWarnings("java:S5960") @HapiTest - HapiSpec verifiesExistence() { + final Stream verifiesExistence() { final var contract = "ExtCodeOperationsChecker"; final var invalidAddress = "0x0000000000000000000000000000000000123456"; final var sizeOf = "sizeOf"; @@ -132,11 +110,11 @@ contract, sizeOf, asHeadlongAddress(contractAddress)) } @HapiTest - HapiSpec testExtCodeSizeWithSystemAccounts() { + final Stream testExtCodeSizeWithSystemAccounts() { final var contract = "ExtCodeOperationsChecker"; final var sizeOf = "sizeOf"; final var account = "account"; - final HapiSpecOperation[] opsArray = new HapiSpecOperation[systemAccounts.size() * 2]; + final var opsArray = new HapiSpecOperation[systemAccounts.size() * 2]; for (int i = 0; i < systemAccounts.size(); i++) { // add contract call for all accounts in the list @@ -157,9 +135,4 @@ contract, sizeOf, mirrorAddrWith(systemAccounts.get(i))) .when() .then(opsArray); } - - @Override - protected Logger getResultsLogger() { - return LOG; - } } diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/opcodes/GlobalPropertiesSuite.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/opcodes/GlobalPropertiesSuite.java index c103fe772ff8..1257c3459d24 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/opcodes/GlobalPropertiesSuite.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/opcodes/GlobalPropertiesSuite.java @@ -27,27 +27,25 @@ import static com.hedera.services.bdd.spec.transactions.TxnVerbs.uploadInitCode; import static com.hedera.services.bdd.spec.utilops.CustomSpecAssert.allRunFor; import static com.hedera.services.bdd.spec.utilops.UtilVerbs.withOpContext; +import static com.hedera.services.bdd.suites.HapiSuite.DEFAULT_PROPS; import static com.hedera.services.bdd.suites.contract.Utils.FunctionType.FUNCTION; import static com.hedera.services.bdd.suites.contract.Utils.getABIFor; import static com.hedera.services.bdd.suites.contract.Utils.parsedToByteString; import com.hedera.services.bdd.junit.HapiTest; -import com.hedera.services.bdd.junit.HapiTestSuite; -import com.hedera.services.bdd.spec.HapiSpec; import com.hedera.services.bdd.spec.HapiSpecSetup; import com.hedera.services.bdd.spec.assertions.ContractFnResultAsserts; -import com.hedera.services.bdd.suites.HapiSuite; import java.math.BigInteger; -import java.util.List; import java.util.Set; +import java.util.stream.Stream; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.DynamicTest; import org.junit.jupiter.api.Tag; -@HapiTestSuite @Tag(SMART_CONTRACT) -public class GlobalPropertiesSuite extends HapiSuite { +public class GlobalPropertiesSuite { private static final Logger LOG = LogManager.getLogger(GlobalPropertiesSuite.class); private static final String CONTRACT = "GlobalProperties"; @@ -55,27 +53,8 @@ public class GlobalPropertiesSuite extends HapiSuite { private static final String GET_BASE_FEE = "getBaseFee"; private static final String GET_GAS_LIMIT = "getGasLimit"; - public static void main(String... args) { - new GlobalPropertiesSuite().runSuiteAsync(); - } - - @Override - protected Logger getResultsLogger() { - return LOG; - } - - @Override - public List getSpecsInSuite() { - return List.of(chainIdWorks(), baseFeeWorks(), coinbaseWorks(), gasLimitWorks()); - } - - @Override - public boolean canRunConcurrent() { - return true; - } - @HapiTest - final HapiSpec chainIdWorks() { + final Stream chainIdWorks() { final var defaultChainId = BigInteger.valueOf(295L); final var devChainId = BigInteger.valueOf(298L); final Set acceptableChainIds = Set.of(devChainId, defaultChainId); @@ -99,7 +78,7 @@ final HapiSpec chainIdWorks() { } @HapiTest - final HapiSpec baseFeeWorks() { + final Stream baseFeeWorks() { final var expectedBaseFee = BigInteger.valueOf(0); return defaultHapiSpec("baseFeeWorks") .given(uploadInitCode(CONTRACT), contractCreate(CONTRACT)) @@ -123,7 +102,7 @@ final HapiSpec baseFeeWorks() { @SuppressWarnings("java:S5960") @HapiTest - final HapiSpec coinbaseWorks() { + final Stream coinbaseWorks() { return defaultHapiSpec("coinbaseWorks") .given(uploadInitCode(CONTRACT), contractCreate(CONTRACT)) .when(contractCall(CONTRACT, "getCoinbase").via("coinbase")) @@ -146,7 +125,7 @@ final HapiSpec coinbaseWorks() { } @HapiTest - final HapiSpec gasLimitWorks() { + final Stream gasLimitWorks() { final var gasLimit = Long.parseLong(HapiSpecSetup.getDefaultNodeProps().get("contracts.maxGasPerSec")); return defaultHapiSpec("gasLimitWorks") .given(uploadInitCode(CONTRACT), contractCreate(CONTRACT)) diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/opcodes/PrngSeedOperationSuite.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/opcodes/PrngSeedOperationSuite.java index 672fa4afd6b6..754cbbd7788f 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/opcodes/PrngSeedOperationSuite.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/opcodes/PrngSeedOperationSuite.java @@ -17,8 +17,7 @@ package com.hedera.services.bdd.suites.contract.opcodes; import static com.hedera.services.bdd.junit.TestTags.SMART_CONTRACT; -import static com.hedera.services.bdd.spec.HapiSpec.propertyPreservingHapiSpec; -import static com.hedera.services.bdd.spec.assertions.ContractFnResultAsserts.isLiteralResult; +import static com.hedera.services.bdd.spec.HapiSpec.hapiTest; import static com.hedera.services.bdd.spec.assertions.ContractFnResultAsserts.isRandomResult; import static com.hedera.services.bdd.spec.assertions.ContractFnResultAsserts.resultWith; import static com.hedera.services.bdd.spec.assertions.TransactionRecordAsserts.recordWith; @@ -29,83 +28,37 @@ import static com.hedera.services.bdd.spec.transactions.TxnVerbs.cryptoCreate; import static com.hedera.services.bdd.spec.transactions.TxnVerbs.uploadInitCode; import static com.hedera.services.bdd.spec.utilops.CustomSpecAssert.allRunFor; -import static com.hedera.services.bdd.spec.utilops.UtilVerbs.overriding; -import static com.hedera.services.bdd.spec.utilops.UtilVerbs.sourcing; import static com.hedera.services.bdd.spec.utilops.UtilVerbs.withOpContext; -import static com.hedera.services.bdd.spec.utilops.records.SnapshotMatchMode.NONDETERMINISTIC_CONTRACT_CALL_RESULTS; -import static com.hedera.services.bdd.spec.utilops.records.SnapshotMatchMode.NONDETERMINISTIC_TRANSACTION_FEES; import static org.junit.jupiter.api.Assertions.assertArrayEquals; import static org.junit.jupiter.api.Assertions.assertEquals; import com.hedera.services.bdd.junit.HapiTest; -import com.hedera.services.bdd.junit.HapiTestSuite; -import com.hedera.services.bdd.spec.HapiSpec; -import com.hedera.services.bdd.suites.HapiSuite; import com.swirlds.common.utility.CommonUtils; import java.util.ArrayList; import java.util.HashSet; import java.util.List; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; +import java.util.stream.Stream; +import org.junit.jupiter.api.DynamicTest; import org.junit.jupiter.api.Tag; -@HapiTestSuite(fuzzyMatch = true) @Tag(SMART_CONTRACT) -public class PrngSeedOperationSuite extends HapiSuite { - - private static final Logger log = LogManager.getLogger(PrngSeedOperationSuite.class); +public class PrngSeedOperationSuite { private static final long GAS_TO_OFFER = 400_000L; private static final String THE_PRNG_CONTRACT = "PrngSeedOperationContract"; private static final String BOB = "bob"; private static final String GET_SEED = "getPseudorandomSeed"; - public static final String CONTRACTS_DYNAMIC_EVM_VERSION = "contracts.evm.version.dynamic"; - public static final String CONTRACTS_EVM_VERSION = "contracts.evm.version"; - - public static final String EVM_VERSION_0_34 = "v0.34"; - public static final String EVM_VERSION_0_30 = "v0.30"; - - public static void main(String... args) { - new PrngSeedOperationSuite().runSuiteSync(); - } - - @Override - public boolean canRunConcurrent() { - return false; - } - - @Override - public List getSpecsInSuite() { - return allOf(positiveSpecs(), negativeSpecs()); - } - - List negativeSpecs() { - return List.of(); - } - - List positiveSpecs() { - return List.of( - prngPrecompileHappyPathWorks(), multipleCallsHaveIndependentResults(), prngPrecompileDisabledInV030()); - } - @HapiTest - final HapiSpec multipleCallsHaveIndependentResults() { + final Stream multipleCallsHaveIndependentResults() { final var prng = THE_PRNG_CONTRACT; final var gasToOffer = 400_000; final var numCalls = 5; final List prngSeeds = new ArrayList<>(); - return propertyPreservingHapiSpec( - "MultipleCallsHaveIndependentResults", - NONDETERMINISTIC_CONTRACT_CALL_RESULTS, - NONDETERMINISTIC_TRANSACTION_FEES) - .preserving(CONTRACTS_DYNAMIC_EVM_VERSION, CONTRACTS_EVM_VERSION) - .given( - uploadInitCode(prng), - contractCreate(prng), - overriding(CONTRACTS_DYNAMIC_EVM_VERSION, TRUE_VALUE), - overriding(CONTRACTS_EVM_VERSION, EVM_VERSION_0_34)) - .when(withOpContext((spec, opLog) -> { + return hapiTest( + uploadInitCode(prng), + contractCreate(prng), + withOpContext((spec, opLog) -> { for (int i = 0; i < numCalls; i++) { final var txn = "call" + i; final var call = @@ -136,67 +89,28 @@ final HapiSpec multipleCallsHaveIndependentResults() { prngSeeds.size(), new HashSet<>(prngSeeds).size(), "An N-3 running hash was repeated, which is" + " inconceivable"); - })) - .then( - // It's possible to call these contracts in a static context with no issues - contractCallLocal(prng, GET_SEED).gas(gasToOffer)); + }), + // It's possible to call these contracts in a static context with no issues + contractCallLocal(prng, GET_SEED).gas(gasToOffer)); } @HapiTest - final HapiSpec prngPrecompileHappyPathWorks() { + final Stream prngPrecompileHappyPathWorks() { final var prng = THE_PRNG_CONTRACT; final var randomBits = "randomBits"; - return propertyPreservingHapiSpec( - "prngPrecompileHappyPathWorks", - NONDETERMINISTIC_CONTRACT_CALL_RESULTS, - NONDETERMINISTIC_TRANSACTION_FEES) - .preserving(CONTRACTS_DYNAMIC_EVM_VERSION, CONTRACTS_EVM_VERSION) - .given( - overriding(CONTRACTS_DYNAMIC_EVM_VERSION, TRUE_VALUE), - overriding(CONTRACTS_EVM_VERSION, EVM_VERSION_0_34), - cryptoCreate(BOB), - uploadInitCode(prng), - contractCreate(prng)) - .when(sourcing(() -> contractCall(prng, GET_SEED) + return hapiTest( + cryptoCreate(BOB), + uploadInitCode(prng), + contractCreate(prng), + contractCall(prng, GET_SEED) .gas(GAS_TO_OFFER) .payingWith(BOB) .via(randomBits) - .logged())) - .then(getTxnRecord(randomBits) + .logged(), + getTxnRecord(randomBits) .hasPriority(recordWith() .contractCallResult(resultWith() .resultViaFunctionName( - GET_SEED, prng, isRandomResult((new Object[] {new byte[32]}))))) - .logged()); - } - - @HapiTest - final HapiSpec prngPrecompileDisabledInV030() { - final var prng = THE_PRNG_CONTRACT; - final var randomBits = "randomBits"; - return propertyPreservingHapiSpec("prngPrecompileDisabledInV_0_30") - .preserving(CONTRACTS_DYNAMIC_EVM_VERSION, CONTRACTS_EVM_VERSION) - .given( - overriding(CONTRACTS_DYNAMIC_EVM_VERSION, TRUE_VALUE), - overriding(CONTRACTS_EVM_VERSION, EVM_VERSION_0_30), - cryptoCreate(BOB), - uploadInitCode(prng), - contractCreate(prng)) - .when(sourcing(() -> contractCall(prng, GET_SEED) - .gas(GAS_TO_OFFER) - .payingWith(BOB) - .via(randomBits) - .logged())) - .then(getTxnRecord(randomBits) - .hasPriority(recordWith() - .contractCallResult(resultWith() - .resultViaFunctionName( - GET_SEED, prng, isLiteralResult((new Object[] {new byte[32]}))))) - .logged()); - } - - @Override - protected Logger getResultsLogger() { - return log; + GET_SEED, prng, isRandomResult((new Object[] {new byte[32]})))))); } } diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/opcodes/PushZeroOperationSuite.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/opcodes/PushZeroOperationSuite.java index f1c132f7d603..9dcd28bf80b4 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/opcodes/PushZeroOperationSuite.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/opcodes/PushZeroOperationSuite.java @@ -16,8 +16,9 @@ package com.hedera.services.bdd.suites.contract.opcodes; +import static com.hedera.services.bdd.junit.ContextRequirement.PROPERTY_OVERRIDES; import static com.hedera.services.bdd.junit.TestTags.SMART_CONTRACT; -import static com.hedera.services.bdd.spec.HapiSpec.defaultHapiSpec; +import static com.hedera.services.bdd.spec.HapiSpec.hapiTest; import static com.hedera.services.bdd.spec.HapiSpec.propertyPreservingHapiSpec; import static com.hedera.services.bdd.spec.assertions.ContractFnResultAsserts.isLiteralResult; import static com.hedera.services.bdd.spec.assertions.ContractFnResultAsserts.resultWith; @@ -30,86 +31,55 @@ import static com.hedera.services.bdd.spec.utilops.UtilVerbs.overriding; import static com.hedera.services.bdd.spec.utilops.UtilVerbs.sourcing; import static com.hedera.services.bdd.spec.utilops.records.SnapshotMatchMode.NONDETERMINISTIC_TRANSACTION_FEES; +import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.CONTRACT_EXECUTION_EXCEPTION; import com.hedera.services.bdd.junit.HapiTest; -import com.hedera.services.bdd.junit.HapiTestSuite; -import com.hedera.services.bdd.spec.HapiSpec; -import com.hedera.services.bdd.suites.HapiSuite; -import com.hederahashgraph.api.proto.java.ResponseCodeEnum; +import com.hedera.services.bdd.junit.LeakyHapiTest; import java.math.BigInteger; -import java.util.List; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; +import java.util.stream.Stream; +import org.junit.jupiter.api.DynamicTest; import org.junit.jupiter.api.Tag; -@HapiTestSuite(fuzzyMatch = true) @Tag(SMART_CONTRACT) -public class PushZeroOperationSuite extends HapiSuite { - - private static final Logger log = LogManager.getLogger(PushZeroOperationSuite.class); +public class PushZeroOperationSuite { private static final long GAS_TO_OFFER = 400_000L; private static final String CONTRACT = "OpcodesContract"; private static final String BOB = "bob"; private static final String OP_PUSH_ZERO = "opPush0"; - public static final String CONTRACTS_DYNAMIC_EVM_VERSION = "contracts.evm.version.dynamic"; public static final String CONTRACTS_EVM_VERSION = "contracts.evm.version"; public static final String EVM_VERSION_0_34 = "v0.34"; - public static final String EVM_VERSION_0_38 = "v0.38"; - - public static void main(String... args) { - new PushZeroOperationSuite().runSuiteSync(); - } - - @Override - public List getSpecsInSuite() { - return allOf(positiveSpecs(), negativeSpecs()); - } - - List negativeSpecs() { - return List.of(); - } - - List positiveSpecs() { - return List.of(pushZeroHappyPathWorks(), pushZeroDisabledInV034()); - } @HapiTest - final HapiSpec pushZeroHappyPathWorks() { + final Stream pushZeroHappyPathWorks() { final var pushZeroContract = CONTRACT; final var pushResult = "pushResult"; - return defaultHapiSpec("pushZeroHappyPathWorks", NONDETERMINISTIC_TRANSACTION_FEES) - .given( - overriding(CONTRACTS_DYNAMIC_EVM_VERSION, TRUE_VALUE), - overriding(CONTRACTS_EVM_VERSION, EVM_VERSION_0_38), - cryptoCreate(BOB), - uploadInitCode(pushZeroContract), - contractCreate(pushZeroContract)) - .when(sourcing(() -> contractCall(pushZeroContract, OP_PUSH_ZERO) + return hapiTest( + cryptoCreate(BOB), + uploadInitCode(pushZeroContract), + contractCreate(pushZeroContract), + contractCall(pushZeroContract, OP_PUSH_ZERO) .gas(GAS_TO_OFFER) .payingWith(BOB) - .via(pushResult) - .logged())) - .then(getTxnRecord(pushResult) + .via(pushResult), + getTxnRecord(pushResult) .hasPriority(recordWith() .contractCallResult(resultWith() .resultViaFunctionName( OP_PUSH_ZERO, pushZeroContract, - isLiteralResult((new Object[] {BigInteger.valueOf(0x5f)}))))) - .logged()); + isLiteralResult((new Object[] {BigInteger.valueOf(0x5f)})))))); } - @HapiTest - final HapiSpec pushZeroDisabledInV034() { + @LeakyHapiTest(PROPERTY_OVERRIDES) + final Stream pushZeroDisabledInV034() { final var pushZeroContract = CONTRACT; final var pushResult = "pushResult"; return propertyPreservingHapiSpec("pushZeroDisabledInV034", NONDETERMINISTIC_TRANSACTION_FEES) - .preserving(CONTRACTS_DYNAMIC_EVM_VERSION, CONTRACTS_EVM_VERSION) + .preserving(CONTRACTS_EVM_VERSION) .given( - overriding(CONTRACTS_DYNAMIC_EVM_VERSION, TRUE_VALUE), overriding(CONTRACTS_EVM_VERSION, EVM_VERSION_0_34), cryptoCreate(BOB), uploadInitCode(pushZeroContract), @@ -119,12 +89,7 @@ final HapiSpec pushZeroDisabledInV034() { .gas(GAS_TO_OFFER) .payingWith(BOB) .via(pushResult) - .hasKnownStatus(ResponseCodeEnum.CONTRACT_EXECUTION_EXCEPTION) + .hasKnownStatus(CONTRACT_EXECUTION_EXCEPTION) .logged())); } - - @Override - protected Logger getResultsLogger() { - return log; - } } diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/opcodes/SStoreSuite.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/opcodes/SStoreSuite.java index 169c692539b5..698ff9c1eda9 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/opcodes/SStoreSuite.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/opcodes/SStoreSuite.java @@ -29,56 +29,42 @@ import static com.hedera.services.bdd.spec.utilops.UtilVerbs.withOpContext; import static com.hedera.services.bdd.spec.utilops.records.SnapshotMatchMode.HIGHLY_NON_DETERMINISTIC_FEES; import static com.hedera.services.bdd.spec.utilops.records.SnapshotMatchMode.NONDETERMINISTIC_FUNCTION_PARAMETERS; +import static com.hedera.services.bdd.suites.HapiSuite.ONE_HUNDRED_HBARS; +import static com.hedera.services.bdd.suites.HapiSuite.flattened; import static com.hedera.services.bdd.suites.contract.Utils.FunctionType.FUNCTION; import static com.hedera.services.bdd.suites.contract.Utils.getABIFor; import com.hedera.services.bdd.junit.HapiTest; -import com.hedera.services.bdd.junit.HapiTestSuite; -import com.hedera.services.bdd.spec.HapiSpec; import com.hedera.services.bdd.spec.HapiSpecOperation; import com.hedera.services.bdd.spec.assertions.ContractFnResultAsserts; import com.hedera.services.bdd.spec.utilops.CustomSpecAssert; -import com.hedera.services.bdd.suites.HapiSuite; import com.hedera.services.bdd.suites.contract.Utils; import java.math.BigInteger; import java.util.ArrayList; import java.util.List; import java.util.concurrent.ThreadLocalRandom; +import java.util.stream.Stream; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.apache.tuweni.bytes.Bytes; +import org.junit.jupiter.api.DynamicTest; import org.junit.jupiter.api.Tag; -@HapiTestSuite(fuzzyMatch = true) @Tag(SMART_CONTRACT) /** - CONCURRENCY STATUS - . Can run concurrent without temporarySStoreRefundTest() */ -public class SStoreSuite extends HapiSuite { +public class SStoreSuite { private static final Logger log = LogManager.getLogger(SStoreSuite.class); public static final int MAX_CONTRACT_STORAGE_KB = 1024; public static final int MAX_CONTRACT_GAS = 15_000_000; private static final String GET_CHILD_VALUE = "getChildValue"; - public static void main(String... args) { - new SStoreSuite().runSuiteAsync(); - } - - @Override - public boolean canRunConcurrent() { - return true; - } - - @Override - public List getSpecsInSuite() { - return List.of(multipleSStoreOpsSucceed(), benchmarkSingleSetter(), childStorage()); - } - // This test is failing with CONSENSUS_GAS_EXHAUSTED prior the refactor. @HapiTest - HapiSpec multipleSStoreOpsSucceed() { + final Stream multipleSStoreOpsSucceed() { final var contract = "GrowArray"; final var GAS_TO_OFFER = 6_000_000L; - return HapiSpec.defaultHapiSpec( + return defaultHapiSpec( "multipleSStoreOpsSucceed", NONDETERMINISTIC_FUNCTION_PARAMETERS, HIGHLY_NON_DETERMINISTIC_FEES) .given(uploadInitCode(contract), contractCreate(contract)) .when(withOpContext((spec, opLog) -> { @@ -111,7 +97,7 @@ HapiSpec multipleSStoreOpsSucceed() { } @HapiTest - HapiSpec childStorage() { + final Stream childStorage() { // Successfully exceeds deprecated max contract storage of 1 KB final var contract = "ChildStorage"; return defaultHapiSpec("ChildStorage", HIGHLY_NON_DETERMINISTIC_FEES) @@ -175,7 +161,7 @@ private HapiSpecOperation[] valuesMatch( @SuppressWarnings("java:S5669") @HapiTest - final HapiSpec benchmarkSingleSetter() { + final Stream benchmarkSingleSetter() { final var contract = "Benchmark"; final var GAS_LIMIT = 1_000_000; var value = Bytes.fromHexString("0x0000000000000000000000000000000000000000000000000000000000000005") @@ -200,9 +186,4 @@ final HapiSpec benchmarkSingleSetter() { ContractFnResultAsserts.isLiteralResult( new Object[] {BigInteger.valueOf(1L)})))); } - - @Override - protected Logger getResultsLogger() { - return log; - } } diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/opcodes/SelfDestructSuite.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/opcodes/SelfDestructSuite.java index 82e7b54b5355..2d673d74605c 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/opcodes/SelfDestructSuite.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/opcodes/SelfDestructSuite.java @@ -17,8 +17,7 @@ package com.hedera.services.bdd.suites.contract.opcodes; import static com.hedera.services.bdd.junit.TestTags.SMART_CONTRACT; -import static com.hedera.services.bdd.spec.HapiSpec.defaultHapiSpec; -import static com.hedera.services.bdd.spec.HapiSpec.propertyPreservingHapiSpec; +import static com.hedera.services.bdd.spec.HapiSpec.hapiTest; import static com.hedera.services.bdd.spec.assertions.AccountInfoAsserts.accountWith; import static com.hedera.services.bdd.spec.assertions.ContractInfoAsserts.contractWith; import static com.hedera.services.bdd.spec.queries.QueryVerbs.contractCallLocal; @@ -33,10 +32,9 @@ import static com.hedera.services.bdd.spec.utilops.UtilVerbs.overriding; import static com.hedera.services.bdd.spec.utilops.UtilVerbs.sourcing; import static com.hedera.services.bdd.spec.utilops.UtilVerbs.withOpContext; -import static com.hedera.services.bdd.spec.utilops.records.SnapshotMatchMode.EXPECT_STREAMLINED_INGEST_RECORDS; -import static com.hedera.services.bdd.spec.utilops.records.SnapshotMatchMode.NONDETERMINISTIC_LOG_DATA; -import static com.hedera.services.bdd.spec.utilops.records.SnapshotMatchMode.NONDETERMINISTIC_NONCE; -import static com.hedera.services.bdd.spec.utilops.records.SnapshotMatchMode.NONDETERMINISTIC_TRANSACTION_FEES; +import static com.hedera.services.bdd.suites.HapiSuite.ONE_HBAR; +import static com.hedera.services.bdd.suites.HapiSuite.ONE_HUNDRED_HBARS; +import static com.hedera.services.bdd.suites.HapiSuite.flattened; import static com.hedera.services.bdd.suites.contract.Utils.mirrorAddrWith; import static com.hedera.services.bdd.suites.contract.evm.Evm46ValidationSuite.existingSystemAccounts; import static com.hedera.services.bdd.suites.contract.evm.Evm46ValidationSuite.nonExistingSystemAccounts; @@ -46,180 +44,174 @@ import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.INVALID_SOLIDITY_ADDRESS; import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.LOCAL_CALL_MODIFICATION_EXCEPTION; import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.SUCCESS; +import static org.junit.jupiter.api.parallel.ResourceAccessMode.READ; +import static org.junit.jupiter.api.parallel.ResourceAccessMode.READ_WRITE; import com.hedera.services.bdd.junit.HapiTest; -import com.hedera.services.bdd.junit.HapiTestSuite; -import com.hedera.services.bdd.spec.HapiSpec; +import com.hedera.services.bdd.junit.HapiTestLifecycle; +import com.hedera.services.bdd.junit.support.SpecManager; import com.hedera.services.bdd.spec.HapiSpecOperation; import com.hedera.services.bdd.suites.HapiSuite; import com.hederahashgraph.api.proto.java.ResponseCodeEnum; import edu.umd.cs.findbugs.annotations.NonNull; import java.util.List; import java.util.concurrent.atomic.AtomicLong; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; +import java.util.stream.Stream; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.DynamicTest; +import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.parallel.ResourceLock; -@HapiTestSuite(fuzzyMatch = true) @Tag(SMART_CONTRACT) -public class SelfDestructSuite extends HapiSuite { - - private final Logger LOGGER = LogManager.getLogger(SelfDestructSuite.class); - - private static final String EVM_VERSION_PROPERTY = "contracts.evm.version"; - private static final String DYNAMIC_EVM_PROPERTY = "contracts.evm.version.dynamic"; - private static final String EVM_VERSION_046 = "v0.46"; - private static final String EVM_VERSION_050 = "v0.50"; - +@HapiTestLifecycle +@DisplayName("SELFDESTRUCT") +public class SelfDestructSuite { private static final String SELF_DESTRUCT_CALLABLE_CONTRACT = "SelfDestructCallable"; private static final String DESTROY_EXPLICIT_BENEFICIARY = "destroyExplicitBeneficiary"; private static final String BENEFICIARY = "beneficiary"; - public static void main(String... args) { - new SelfDestructSuite().runSuiteAsync(); + @BeforeAll + static void beforeAll(@NonNull final SpecManager specManager) throws Throwable { + // Multiple tests use the same contract, so we upload it once here + specManager.setup(uploadInitCode(SELF_DESTRUCT_CALLABLE_CONTRACT)); } - @Override - protected Logger getResultsLogger() { - return LOGGER; - } + @Nested + @DisplayName("with 0.46 EVM") + @ResourceLock(value = "EVM_VERSION", mode = READ_WRITE) + class WithV046EVM { + @BeforeAll + static void beforeAll(@NonNull final SpecManager specManager) throws Throwable { + specManager.setup(overriding(HapiSuite.EVM_VERSION_PROPERTY, HapiSuite.EVM_VERSION_046)); + } - @Override - public List getSpecsInSuite() { - return List.of( - hscsEvm008SelfDestructInConstructorWorks(), - hscsEvm008SelfDestructWhenCalling(), - selfDestructFailsWhenBeneficiaryHasReceiverSigRequiredAndHasNotSignedTheTxn(EVM_VERSION_046), - selfDestructFailsWhenBeneficiaryHasReceiverSigRequiredAndHasNotSignedTheTxn(EVM_VERSION_050), - selfDestructViaCallLocalWithAccount999ResultsInLocalCallModificationPrecheckFailed(EVM_VERSION_046), - selfDestructViaCallLocalWithAccount999ResultsInLocalCallModificationPrecheckFailed(EVM_VERSION_050), - testSelfDestructForSystemAccounts(EVM_VERSION_046), - testSelfDestructForSystemAccounts(EVM_VERSION_050), - deletedContractsCannotBeUpdated(EVM_VERSION_046), - deletedContractsCannotBeUpdated(EVM_VERSION_050)); - } + @AfterAll + static void afterAll(@NonNull final SpecManager specManager) throws Throwable { + specManager.teardown(overriding(HapiSuite.EVM_VERSION_PROPERTY, HapiSuite.EVM_VERSION_050)); + } - @Override - public boolean canRunConcurrent() { - return false; - } + @HapiTest + @DisplayName("can SELFDESTRUCT in constructor without destroying created child") + final Stream hscsEvm008SelfDestructInConstructorWorks() { + final var contract = "FactorySelfDestructConstructor"; + final var nextAccount = "civilian"; + return hapiTest( + cryptoCreate(BENEFICIARY).balance(ONE_HUNDRED_HBARS), + uploadInitCode(contract), + contractCreate(contract) + .balance(3 * ONE_HBAR) + .via("contractCreate") + .payingWith(BENEFICIARY), + cryptoCreate(nextAccount), + getAccountInfo(contract).hasCostAnswerPrecheck(ACCOUNT_DELETED), + getContractInfo(contract).has(contractWith().isDeleted()), + withOpContext((spec, opLog) -> { + final var registry = spec.registry(); + final var destroyedNum = + registry.getContractId(contract).getContractNum(); + final var childInfoQuery = getContractInfo("0.0." + (destroyedNum + 1)) + .has(contractWith().isNotDeleted()); + allRunFor(spec, childInfoQuery); + })); + } - @HapiTest - final HapiSpec hscsEvm008SelfDestructInConstructorWorks() { - final var contract = "FactorySelfDestructConstructor"; - final var nextAccount = "civilian"; - return defaultHapiSpec("hscsEvm008SelfDestructInConstructorWorks", NONDETERMINISTIC_LOG_DATA) - .given(cryptoCreate(BENEFICIARY).balance(ONE_HUNDRED_HBARS), uploadInitCode(contract)) - .when( - contractCreate(contract) - .balance(3 * ONE_HBAR) - .via("contractCreate") - .payingWith(BENEFICIARY), - cryptoCreate(nextAccount)) - .then( - getAccountInfo(contract).hasCostAnswerPrecheck(ACCOUNT_DELETED), - getContractInfo(contract).has(contractWith().isDeleted()), - withOpContext((spec, opLog) -> { - final var registry = spec.registry(); - final var destroyedNum = - registry.getContractId(contract).getContractNum(); - final var childInfoQuery = getContractInfo("0.0." + (destroyedNum + 1)) - .has(contractWith().isNotDeleted()); - allRunFor(spec, childInfoQuery); - })); - } + @HapiTest + @DisplayName("can SELFDESTRUCT after being constructed") + final Stream hscsEvm008SelfDestructWhenCalling() { + return hapiTest( + cryptoCreate("acc").balance(5 * ONE_HUNDRED_HBARS), + contractCreate(SELF_DESTRUCT_CALLABLE_CONTRACT).via("cc").payingWith("acc"), + contractCall(SELF_DESTRUCT_CALLABLE_CONTRACT, "destroy").payingWith("acc"), + getAccountInfo(SELF_DESTRUCT_CALLABLE_CONTRACT).hasCostAnswerPrecheck(ACCOUNT_DELETED), + getContractInfo(SELF_DESTRUCT_CALLABLE_CONTRACT) + .has(contractWith().isDeleted())); + } - @HapiTest - final HapiSpec hscsEvm008SelfDestructWhenCalling() { - return defaultHapiSpec("hscsEvm008SelfDestructWhenCalling", NONDETERMINISTIC_TRANSACTION_FEES) - .given( - cryptoCreate("acc").balance(5 * ONE_HUNDRED_HBARS), - uploadInitCode(SELF_DESTRUCT_CALLABLE_CONTRACT)) - .when(contractCreate(SELF_DESTRUCT_CALLABLE_CONTRACT) - .via("cc") - .payingWith("acc") - .hasKnownStatus(SUCCESS)) - .then( - contractCall(SELF_DESTRUCT_CALLABLE_CONTRACT, "destroy").payingWith("acc"), - getAccountInfo(SELF_DESTRUCT_CALLABLE_CONTRACT).hasCostAnswerPrecheck(ACCOUNT_DELETED), - getContractInfo(SELF_DESTRUCT_CALLABLE_CONTRACT) - .has(contractWith().isDeleted())); - } + @HapiTest + @DisplayName("cannot SELFDESTRUCT to a beneficiary with receiverSigRequired that has not signed") + final Stream selfDestructFailsWhenBeneficiaryHasReceiverSigRequiredAndHasNotSignedTheTxn46() { + return selfDestructFailsWhenBeneficiaryHasReceiverSigRequiredAndHasNotSignedTheTxn(); + } - @HapiTest - final HapiSpec selfDestructFailsWhenBeneficiaryHasReceiverSigRequiredAndHasNotSignedTheTxn46() { - return selfDestructFailsWhenBeneficiaryHasReceiverSigRequiredAndHasNotSignedTheTxn(EVM_VERSION_046); - } + @HapiTest + @DisplayName("cannot SELFDESTRUCT within a static call") + final Stream + selfDestructViaCallLocalWithAccount999ResultsInLocalCallModificationPrecheckFailed46() { + return selfDestructViaCallLocalWithAccount999ResultsInLocalCallModificationPrecheckFailed(); + } - @HapiTest - final HapiSpec selfDestructFailsWhenBeneficiaryHasReceiverSigRequiredAndHasNotSignedTheTxn50() { - return selfDestructFailsWhenBeneficiaryHasReceiverSigRequiredAndHasNotSignedTheTxn(EVM_VERSION_050); - } + @HapiTest + @DisplayName("cannot SELFDESTRUCT with system account beneficiary") + final Stream testSelfDestructForSystemAccounts46() { + return testSelfDestructForSystemAccounts(); + } - final HapiSpec selfDestructFailsWhenBeneficiaryHasReceiverSigRequiredAndHasNotSignedTheTxn( - @NonNull final String evmVersion) { - final AtomicLong beneficiaryId = new AtomicLong(); - return propertyPreservingHapiSpec( - "selfDestructFailsWhenBeneficiaryHasReceiverSigRequiredAndHasNotSignedTheTxn" + evmVersion) - .preserving(EVM_VERSION_PROPERTY, DYNAMIC_EVM_PROPERTY) - .given( - overriding(DYNAMIC_EVM_PROPERTY, "true"), - overriding(EVM_VERSION_PROPERTY, evmVersion), - cryptoCreate(BENEFICIARY) - .balance(ONE_HUNDRED_HBARS) - .receiverSigRequired(true) - .exposingCreatedIdTo(id -> beneficiaryId.set(id.getAccountNum())), - uploadInitCode(SELF_DESTRUCT_CALLABLE_CONTRACT), - contractCreate(SELF_DESTRUCT_CALLABLE_CONTRACT).balance(ONE_HBAR)) - .when(sourcing(() -> contractCall( - SELF_DESTRUCT_CALLABLE_CONTRACT, - "destroyExplicitBeneficiary", - mirrorAddrWith(beneficiaryId.get())) - .hasKnownStatus(INVALID_SIGNATURE))) - .then( - getAccountInfo(BENEFICIARY).has(accountWith().balance(ONE_HUNDRED_HBARS)), - getContractInfo(SELF_DESTRUCT_CALLABLE_CONTRACT) - .has(contractWith().balance(ONE_HBAR))); + @HapiTest + @DisplayName("cannot update a contract after SELFDESTRUCT") + final Stream deletedContractsCannotBeUpdated46() { + return deletedContractsCannotBeUpdated(HapiSuite.EVM_VERSION_046); + } } - @HapiTest - final HapiSpec selfDestructViaCallLocalWithAccount999ResultsInLocalCallModificationPrecheckFailed46() { - return selfDestructViaCallLocalWithAccount999ResultsInLocalCallModificationPrecheckFailed(EVM_VERSION_046); - } + @Nested + @DisplayName("with 0.50 EVM") + @ResourceLock(value = "EVM_VERSION", mode = READ) + class WithV050EVM { + @HapiTest + @DisplayName("cannot SELFDESTRUCT to a beneficiary with receiverSigRequired that has not signed") + final Stream selfDestructFailsWhenBeneficiaryHasReceiverSigRequiredAndHasNotSignedTheTxn50() { + return selfDestructFailsWhenBeneficiaryHasReceiverSigRequiredAndHasNotSignedTheTxn(); + } - @HapiTest - final HapiSpec selfDestructViaCallLocalWithAccount999ResultsInLocalCallModificationPrecheckFailed50() { - return selfDestructViaCallLocalWithAccount999ResultsInLocalCallModificationPrecheckFailed(EVM_VERSION_050); - } + @HapiTest + @DisplayName("cannot SELFDESTRUCT within a static call") + final Stream + selfDestructViaCallLocalWithAccount999ResultsInLocalCallModificationPrecheckFailed50() { + return selfDestructViaCallLocalWithAccount999ResultsInLocalCallModificationPrecheckFailed(); + } + + @HapiTest + @DisplayName("cannot SELFDESTRUCT with system account beneficiary") + final Stream testSelfDestructForSystemAccounts50() { + return testSelfDestructForSystemAccounts(); + } - final HapiSpec selfDestructViaCallLocalWithAccount999ResultsInLocalCallModificationPrecheckFailed( - @NonNull final String evmVersion) { - return propertyPreservingHapiSpec( - "selfDestructViaCallLocalWithAccount999ResultsInLocalCallModificationPrecheckFailed" - + evmVersion) - .preserving(EVM_VERSION_PROPERTY, DYNAMIC_EVM_PROPERTY) - .given( - overriding(DYNAMIC_EVM_PROPERTY, "true"), - overriding(EVM_VERSION_PROPERTY, evmVersion), - uploadInitCode(SELF_DESTRUCT_CALLABLE_CONTRACT), - contractCreate(SELF_DESTRUCT_CALLABLE_CONTRACT).balance(ONE_HBAR)) - .when(contractCallLocal( - SELF_DESTRUCT_CALLABLE_CONTRACT, "destroyExplicitBeneficiary", mirrorAddrWith(999L)) - .hasAnswerOnlyPrecheck(LOCAL_CALL_MODIFICATION_EXCEPTION)) - .then(); + @HapiTest + @DisplayName("can update a contract after SELFDESTRUCT") + final Stream deletedContractsCannotBeUpdated50() { + return deletedContractsCannotBeUpdated(HapiSuite.EVM_VERSION_050); + } } - @HapiTest - final HapiSpec testSelfDestructForSystemAccounts46() { - return testSelfDestructForSystemAccounts(EVM_VERSION_046); + final Stream selfDestructFailsWhenBeneficiaryHasReceiverSigRequiredAndHasNotSignedTheTxn() { + final AtomicLong beneficiaryId = new AtomicLong(); + return hapiTest( + cryptoCreate(BENEFICIARY) + .balance(ONE_HUNDRED_HBARS) + .receiverSigRequired(true) + .exposingCreatedIdTo(id -> beneficiaryId.set(id.getAccountNum())), + contractCreate(SELF_DESTRUCT_CALLABLE_CONTRACT).balance(ONE_HBAR), + sourcing(() -> contractCall( + SELF_DESTRUCT_CALLABLE_CONTRACT, + "destroyExplicitBeneficiary", + mirrorAddrWith(beneficiaryId.get())) + .hasKnownStatus(INVALID_SIGNATURE)), + getAccountInfo(BENEFICIARY).has(accountWith().balance(ONE_HUNDRED_HBARS)), + getContractInfo(SELF_DESTRUCT_CALLABLE_CONTRACT) + .has(contractWith().balance(ONE_HBAR))); } - @HapiTest - final HapiSpec testSelfDestructForSystemAccounts50() { - return testSelfDestructForSystemAccounts(EVM_VERSION_050); + final Stream selfDestructViaCallLocalWithAccount999ResultsInLocalCallModificationPrecheckFailed() { + return hapiTest( + contractCreate(SELF_DESTRUCT_CALLABLE_CONTRACT).balance(ONE_HBAR), + contractCallLocal(SELF_DESTRUCT_CALLABLE_CONTRACT, "destroyExplicitBeneficiary", mirrorAddrWith(999L)) + .hasAnswerOnlyPrecheck(LOCAL_CALL_MODIFICATION_EXCEPTION)); } - final HapiSpec testSelfDestructForSystemAccounts(@NonNull final String evmVersion) { + final Stream testSelfDestructForSystemAccounts() { final AtomicLong deployer = new AtomicLong(); final var nonExistingAccountsOps = createOpsArray( nonExistingSystemAccounts, @@ -232,56 +224,32 @@ final HapiSpec testSelfDestructForSystemAccounts(@NonNull final String evmVersio System.arraycopy(nonExistingAccountsOps, 0, opsArray, 0, nonExistingAccountsOps.length); System.arraycopy(existingAccountsOps, 0, opsArray, nonExistingAccountsOps.length, existingAccountsOps.length); - return propertyPreservingHapiSpec("testSelfDestructForSystemAccounts" + evmVersion) - .preserving(EVM_VERSION_PROPERTY, DYNAMIC_EVM_PROPERTY) - .given( - overriding(DYNAMIC_EVM_PROPERTY, "true"), - overriding(EVM_VERSION_PROPERTY, evmVersion), - cryptoCreate(BENEFICIARY) - .balance(ONE_HUNDRED_HBARS) - .receiverSigRequired(false) - .exposingCreatedIdTo(id -> deployer.set(id.getAccountNum())), - uploadInitCode(SELF_DESTRUCT_CALLABLE_CONTRACT), - contractCreate(SELF_DESTRUCT_CALLABLE_CONTRACT).balance(ONE_HBAR)) - .when() - .then(nonExistingAccountsOps); - } - - @HapiTest - final HapiSpec deletedContractsCannotBeUpdated46() { - return deletedContractsCannotBeUpdated(EVM_VERSION_046); - } - - @HapiTest - final HapiSpec deletedContractsCannotBeUpdated50() { - return deletedContractsCannotBeUpdated(EVM_VERSION_050); + return hapiTest(flattened( + cryptoCreate(BENEFICIARY) + .balance(ONE_HUNDRED_HBARS) + .receiverSigRequired(false) + .exposingCreatedIdTo(id -> deployer.set(id.getAccountNum())), + contractCreate(SELF_DESTRUCT_CALLABLE_CONTRACT).balance(ONE_HBAR), + nonExistingAccountsOps)); } - final HapiSpec deletedContractsCannotBeUpdated(@NonNull final String evmVersion) { + final Stream deletedContractsCannotBeUpdated(@NonNull final String evmVersion) { final var contract = SELF_DESTRUCT_CALLABLE_CONTRACT; final var beneficiary = BENEFICIARY; final var expectedStatus = switch (evmVersion) { - case EVM_VERSION_046 -> INVALID_CONTRACT_ID; - case EVM_VERSION_050 -> SUCCESS; + case HapiSuite.EVM_VERSION_046 -> INVALID_CONTRACT_ID; + case HapiSuite.EVM_VERSION_050 -> SUCCESS; default -> throw new IllegalArgumentException("unexpected evm version: " + evmVersion); }; - return propertyPreservingHapiSpec( - "deletedContractsCannotBeUpdated" + evmVersion, - EXPECT_STREAMLINED_INGEST_RECORDS, - NONDETERMINISTIC_TRANSACTION_FEES, - NONDETERMINISTIC_NONCE) - .preserving(EVM_VERSION_PROPERTY, DYNAMIC_EVM_PROPERTY) - .given( - overriding(DYNAMIC_EVM_PROPERTY, "true"), - overriding(EVM_VERSION_PROPERTY, evmVersion), - uploadInitCode(contract), - contractCreate(contract).gas(300_000), - cryptoCreate(beneficiary).balance(ONE_HUNDRED_HBARS)) - .when(contractCall(contract, "destroy").deferStatusResolution().payingWith(beneficiary)) - .then(contractUpdate(contract).newMemo("Hi there!").hasKnownStatus(expectedStatus)); + return hapiTest( + uploadInitCode(contract), + contractCreate(contract).gas(300_000), + cryptoCreate(beneficiary).balance(ONE_HUNDRED_HBARS), + contractCall(contract, "destroy").deferStatusResolution().payingWith(beneficiary), + contractUpdate(contract).newMemo("Hi there!").hasKnownStatus(expectedStatus)); } private HapiSpecOperation[] createOpsArray( diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/openzeppelin/ERC1155ContractInteractions.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/openzeppelin/ERC1155ContractInteractions.java index 25cf0ce76692..506dc47cf9f1 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/openzeppelin/ERC1155ContractInteractions.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/openzeppelin/ERC1155ContractInteractions.java @@ -31,47 +31,28 @@ import static com.hedera.services.bdd.spec.utilops.UtilVerbs.withOpContext; import static com.hedera.services.bdd.spec.utilops.records.SnapshotMatchMode.NONDETERMINISTIC_CONTRACT_CALL_RESULTS; import static com.hedera.services.bdd.spec.utilops.records.SnapshotMatchMode.NONDETERMINISTIC_FUNCTION_PARAMETERS; +import static com.hedera.services.bdd.suites.HapiSuite.ONE_HUNDRED_HBARS; +import static com.hedera.services.bdd.suites.HapiSuite.SECP_256K1_SHAPE; import com.google.protobuf.ByteString; import com.hedera.node.app.service.evm.utils.EthSigsUtils; import com.hedera.services.bdd.junit.HapiTest; -import com.hedera.services.bdd.junit.HapiTestSuite; -import com.hedera.services.bdd.spec.HapiSpec; import com.hedera.services.bdd.spec.HapiSpecOperation; -import com.hedera.services.bdd.suites.HapiSuite; import com.hederahashgraph.api.proto.java.ResponseCodeEnum; import java.math.BigInteger; import java.util.ArrayList; -import java.util.List; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; +import java.util.stream.Stream; +import org.junit.jupiter.api.DynamicTest; import org.junit.jupiter.api.Tag; -@HapiTestSuite(fuzzyMatch = true) @Tag(SMART_CONTRACT) -public class ERC1155ContractInteractions extends HapiSuite { - - private static final Logger log = LogManager.getLogger(ERC1155ContractInteractions.class); +public class ERC1155ContractInteractions { private static final String ACCOUNT1 = "acc1"; private static final String ACCOUNT2 = "acc2"; private static final String CONTRACT = "GameItems"; - public static void main(String... args) { - new ERC1155ContractInteractions().runSuiteSync(); - } - - @Override - public List getSpecsInSuite() { - return List.of(erc1155()); - } - - @Override - public boolean canRunConcurrent() { - return true; - } - @HapiTest - final HapiSpec erc1155() { + final Stream erc1155() { // Adding NONDETERMINISTIC_CONTRACT_CALL_RESULTS because one of the // contractCallResult->logInfo->topics is always different(both in mono and mod) return defaultHapiSpec("erc1155", NONDETERMINISTIC_FUNCTION_PARAMETERS, NONDETERMINISTIC_CONTRACT_CALL_RESULTS) @@ -152,9 +133,4 @@ final HapiSpec erc1155() { getTxnRecord("acc1ApproveCall").logged(), getTxnRecord("contractTransferFromCall").logged()); } - - @Override - protected Logger getResultsLogger() { - return log; - } } diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/openzeppelin/ERC20ContractInteractions.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/openzeppelin/ERC20ContractInteractions.java index 0a97b462e4e1..9a0f36b999e4 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/openzeppelin/ERC20ContractInteractions.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/openzeppelin/ERC20ContractInteractions.java @@ -34,6 +34,10 @@ import static com.hedera.services.bdd.spec.utilops.UtilVerbs.newKeyNamed; import static com.hedera.services.bdd.spec.utilops.UtilVerbs.withOpContext; import static com.hedera.services.bdd.spec.utilops.records.SnapshotMatchMode.NONDETERMINISTIC_TRANSACTION_FEES; +import static com.hedera.services.bdd.suites.HapiSuite.DEFAULT_CONTRACT_RECEIVER; +import static com.hedera.services.bdd.suites.HapiSuite.DEFAULT_CONTRACT_SENDER; +import static com.hedera.services.bdd.suites.HapiSuite.SECP_256K1_RECEIVER_SOURCE_KEY; +import static com.hedera.services.bdd.suites.HapiSuite.SECP_256K1_SHAPE; import static com.hedera.services.bdd.suites.contract.Utils.asAddressInTopic; import static com.hedera.services.bdd.suites.contract.Utils.eventSignatureOf; import static com.hedera.services.bdd.suites.contract.Utils.parsedToByteString; @@ -43,48 +47,23 @@ import com.google.protobuf.ByteString; import com.hedera.services.bdd.junit.HapiTest; -import com.hedera.services.bdd.junit.HapiTestSuite; -import com.hedera.services.bdd.spec.HapiSpec; -import com.hedera.services.bdd.suites.HapiSuite; import java.math.BigInteger; import java.util.List; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; +import java.util.stream.Stream; import org.apache.tuweni.bytes.Bytes; +import org.junit.jupiter.api.DynamicTest; import org.junit.jupiter.api.Tag; -@HapiTestSuite(fuzzyMatch = true) @Tag(SMART_CONTRACT) -public class ERC20ContractInteractions extends HapiSuite { - - private static final Logger log = LogManager.getLogger(ERC20ContractInteractions.class); +public class ERC20ContractInteractions { private static final String TRANSFER = "transfer"; private static final String VALID_ALIAS = "validAlias"; private static final String TRANSFER_FROM = "transferFrom"; private static final String TX_STR_PREFIX = " tx - "; private static final String TRANSFER_ADDRESS_ADDRESS_UINT_256 = "Transfer(address,address,uint256)"; - @Override - public boolean canRunConcurrent() { - return true; - } - - public static void main(String[] args) { - new ERC20ContractInteractions().runSuiteSync(); - } - - @Override - protected Logger getResultsLogger() { - return log; - } - - @Override - public List getSpecsInSuite() { - return List.of(callsERC20ContractInteractions()); - } - @HapiTest - final HapiSpec callsERC20ContractInteractions() { + final Stream callsERC20ContractInteractions() { final var CONTRACT = "GLDToken"; final var CREATE_TX = "create"; final var APPROVE_TX = "approve"; diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/openzeppelin/ERC721ContractInteractions.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/openzeppelin/ERC721ContractInteractions.java index ba5f6eb93ccf..89c643d213d6 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/openzeppelin/ERC721ContractInteractions.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/openzeppelin/ERC721ContractInteractions.java @@ -27,46 +27,23 @@ import static com.hedera.services.bdd.spec.utilops.CustomSpecAssert.allRunFor; import static com.hedera.services.bdd.spec.utilops.UtilVerbs.withOpContext; import static com.hedera.services.bdd.spec.utilops.records.SnapshotMatchMode.NONDETERMINISTIC_TRANSACTION_FEES; +import static com.hedera.services.bdd.suites.HapiSuite.DEFAULT_CONTRACT_RECEIVER; +import static com.hedera.services.bdd.suites.HapiSuite.DEFAULT_CONTRACT_SENDER; +import static com.hedera.services.bdd.suites.HapiSuite.ONE_HBAR; +import static com.hedera.services.bdd.suites.HapiSuite.SECP_256K1_RECEIVER_SOURCE_KEY; import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.SUCCESS; import com.hedera.services.bdd.junit.HapiTest; -import com.hedera.services.bdd.junit.HapiTestSuite; -import com.hedera.services.bdd.spec.HapiSpec; import com.hedera.services.bdd.spec.queries.QueryVerbs; -import com.hedera.services.bdd.suites.HapiSuite; import java.math.BigInteger; -import java.util.List; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; +import java.util.stream.Stream; +import org.junit.jupiter.api.DynamicTest; import org.junit.jupiter.api.Tag; -@HapiTestSuite(fuzzyMatch = true) @Tag(SMART_CONTRACT) -public class ERC721ContractInteractions extends HapiSuite { - - private static final Logger log = LogManager.getLogger(ERC721ContractInteractions.class); - - public static void main(String... args) { - new ERC721ContractInteractions().runSuiteSync(); - } - - @Override - protected Logger getResultsLogger() { - return log; - } - - @Override - public List getSpecsInSuite() { - return List.of(callsERC721ContractInteractions()); - } - - @Override - public boolean canRunConcurrent() { - return true; - } - +public class ERC721ContractInteractions { @HapiTest - final HapiSpec callsERC721ContractInteractions() { + final Stream callsERC721ContractInteractions() { final var CONTRACT = "GameItem"; final var nftId = BigInteger.ONE; final var CREATE_TX = "create"; diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/precompile/ApproveAllowanceSuite.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/precompile/ApproveAllowanceSuite.java index f2723d8bf9eb..4cce822649dd 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/precompile/ApproveAllowanceSuite.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/precompile/ApproveAllowanceSuite.java @@ -52,6 +52,11 @@ import static com.hedera.services.bdd.spec.utilops.records.SnapshotMatchMode.NONDETERMINISTIC_CONTRACT_CALL_RESULTS; import static com.hedera.services.bdd.spec.utilops.records.SnapshotMatchMode.NONDETERMINISTIC_FUNCTION_PARAMETERS; import static com.hedera.services.bdd.spec.utilops.records.SnapshotMatchMode.NONDETERMINISTIC_TRANSACTION_FEES; +import static com.hedera.services.bdd.suites.HapiSuite.DEFAULT_PAYER; +import static com.hedera.services.bdd.suites.HapiSuite.GENESIS; +import static com.hedera.services.bdd.suites.HapiSuite.ONE_HBAR; +import static com.hedera.services.bdd.suites.HapiSuite.ONE_HUNDRED_HBARS; +import static com.hedera.services.bdd.suites.HapiSuite.TOKEN_TREASURY; import static com.hedera.services.bdd.suites.contract.Utils.asAddress; import static com.hedera.services.bdd.suites.contract.Utils.asToken; import static com.hedera.services.bdd.suites.contract.Utils.eventSignatureOf; @@ -65,10 +70,6 @@ import com.google.protobuf.ByteString; import com.hedera.node.app.hapi.utils.contracts.ParsingConstants.FunctionType; import com.hedera.services.bdd.junit.HapiTest; -import com.hedera.services.bdd.junit.HapiTestSuite; -import com.hedera.services.bdd.spec.HapiSpec; -import com.hedera.services.bdd.suites.BddMethodIsNotATest; -import com.hedera.services.bdd.suites.HapiSuite; import com.hederahashgraph.api.proto.java.NftTransfer; import com.hederahashgraph.api.proto.java.TokenID; import com.hederahashgraph.api.proto.java.TokenSupplyType; @@ -78,13 +79,14 @@ import java.math.BigInteger; import java.util.List; import java.util.concurrent.atomic.AtomicReference; +import java.util.stream.Stream; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; +import org.junit.jupiter.api.DynamicTest; import org.junit.jupiter.api.Tag; -@HapiTestSuite(fuzzyMatch = true) @Tag(SMART_CONTRACT) -public class ApproveAllowanceSuite extends HapiSuite { +public class ApproveAllowanceSuite { public static final String CONTRACTS_PERMITTED_DELEGATE_CALLERS = "contracts.permittedDelegateCallers"; private static final Logger log = LogManager.getLogger(ApproveAllowanceSuite.class); @@ -105,33 +107,8 @@ public class ApproveAllowanceSuite extends HapiSuite { private static final String APPROVE_FOR_ALL_SIGNATURE = "ApprovalForAll(address,address,bool)"; public static final String CALL_TO = "callTo"; - public static void main(String... args) { - new ApproveAllowanceSuite().runSuiteAsync(); - } - - @Override - protected Logger getResultsLogger() { - return log; - } - - @Override - public List getSpecsInSuite() { - return List.of( - hapiNftGetApproved(), - hapiNftIsApprovedForAll(), - hapiNftSetApprovalForAll(), - htsTokenAllowance(), - htsTokenApprove(), - htsTokenApproveToInnerContract(), - nftAutoCreationIncludeAllowanceCheck(), - testIndirectApprovalWithDirectPrecompileCallee(), - testIndirectApprovalWithDelegateErc20Callee(), - testIndirectApprovalWithDelegatePrecompileCallee(), - testIndirectApprovalWithDirectErc20Callee()); - } - @HapiTest - final HapiSpec nftAutoCreationIncludeAllowanceCheck() { + final Stream nftAutoCreationIncludeAllowanceCheck() { final var ownerAccount = "owningAlias"; final var receivingAlias = "receivingAlias"; return defaultHapiSpec("NftAutoCreationIncludeAllowanceCheck", NONDETERMINISTIC_TRANSACTION_FEES) @@ -179,13 +156,8 @@ final HapiSpec nftAutoCreationIncludeAllowanceCheck() { public static final String DELEGATE_ERC_CALLEE = "ERC20DelegateCallee"; private static final String DIRECT_ERC_CALLEE = "NonDelegateCallee"; - @Override - public boolean canRunConcurrent() { - return true; - } - @HapiTest - final HapiSpec htsTokenApproveToInnerContract() { + final Stream htsTokenApproveToInnerContract() { final var approveTxn = "NestedChildren"; final var nestedContract = DIRECT_ERC_CALLEE; final var theSpender = SPENDER; @@ -209,9 +181,11 @@ final HapiSpec htsTokenApproveToInnerContract() { .adminKey(MULTI_KEY) .supplyKey(MULTI_KEY), uploadInitCode(HTS_APPROVE_ALLOWANCE_CONTRACT), - contractCreate(HTS_APPROVE_ALLOWANCE_CONTRACT), + // Refusing ethereum create conversion, because we get INVALID_SIGNATURE upon tokenAssociate, + // since we have CONTRACT_ID key + contractCreate(HTS_APPROVE_ALLOWANCE_CONTRACT).refusingEthConversion(), uploadInitCode(nestedContract), - contractCreate(nestedContract).adminKey(MULTI_KEY), + contractCreate(nestedContract).adminKey(MULTI_KEY).refusingEthConversion(), tokenAssociate(OWNER, FUNGIBLE_TOKEN), tokenAssociate(HTS_APPROVE_ALLOWANCE_CONTRACT, FUNGIBLE_TOKEN), tokenAssociate(nestedContract, FUNGIBLE_TOKEN)) @@ -256,7 +230,7 @@ final HapiSpec htsTokenApproveToInnerContract() { } @HapiTest - public HapiSpec idVariantsTreatedAsExpected() { + final Stream idVariantsTreatedAsExpected() { return defaultHapiSpec("idVariantsTreatedAsExpected") .given( newKeyNamed("supplyKey"), @@ -294,7 +268,7 @@ public HapiSpec idVariantsTreatedAsExpected() { } @HapiTest - final HapiSpec htsTokenAllowance() { + final Stream htsTokenAllowance() { final var theSpender = SPENDER; final var allowanceTxn = ALLOWANCE_TX; @@ -377,7 +351,7 @@ final HapiSpec htsTokenAllowance() { } @HapiTest - final HapiSpec htsTokenApprove() { + final Stream htsTokenApprove() { final var approveTxn = "approveTxn"; final var theSpender = SPENDER; @@ -400,7 +374,9 @@ final HapiSpec htsTokenApprove() { .adminKey(MULTI_KEY) .supplyKey(MULTI_KEY), uploadInitCode(HTS_APPROVE_ALLOWANCE_CONTRACT), - contractCreate(HTS_APPROVE_ALLOWANCE_CONTRACT), + // Refusing ethereum create conversion, because we get INVALID_SIGNATURE upon tokenAssociate, + // since we have CONTRACT_ID key + contractCreate(HTS_APPROVE_ALLOWANCE_CONTRACT).refusingEthConversion(), tokenAssociate(OWNER, FUNGIBLE_TOKEN), tokenAssociate(HTS_APPROVE_ALLOWANCE_CONTRACT, FUNGIBLE_TOKEN)) .when(withOpContext((spec, opLog) -> allRunFor( @@ -442,7 +418,7 @@ final HapiSpec htsTokenApprove() { } @HapiTest - final HapiSpec hapiNftIsApprovedForAll() { + final Stream hapiNftIsApprovedForAll() { final var notApprovedTxn = "notApprovedTxn"; final var approvedForAllTxn = "approvedForAllTxn"; final var notAnAddress = new byte[20]; @@ -560,7 +536,7 @@ final HapiSpec hapiNftIsApprovedForAll() { } @HapiTest - final HapiSpec hapiNftGetApproved() { + final Stream hapiNftGetApproved() { final var theSpender = SPENDER; final var theSpender2 = "spender2"; final var allowanceTxn = ALLOWANCE_TX; @@ -649,7 +625,7 @@ final HapiSpec hapiNftGetApproved() { } @HapiTest - final HapiSpec hapiNftSetApprovalForAll() { + final Stream hapiNftSetApprovalForAll() { final var theSpender = SPENDER; final var theSpender2 = "spender2"; final var allowanceTxn = ALLOWANCE_TX; @@ -672,7 +648,9 @@ final HapiSpec hapiNftSetApprovalForAll() { .adminKey(MULTI_KEY) .treasury(TOKEN_TREASURY), uploadInitCode(HTS_APPROVE_ALLOWANCE_CONTRACT), - contractCreate(HTS_APPROVE_ALLOWANCE_CONTRACT), + // Refusing ethereum create conversion, because we get INVALID_SIGNATURE upon tokenAssociate, + // since we have CONTRACT_ID key + contractCreate(HTS_APPROVE_ALLOWANCE_CONTRACT).refusingEthConversion(), tokenAssociate(OWNER, NON_FUNGIBLE_TOKEN), tokenAssociate(HTS_APPROVE_ALLOWANCE_CONTRACT, NON_FUNGIBLE_TOKEN), mintToken(NON_FUNGIBLE_TOKEN, List.of(ByteString.copyFromUtf8("a"))) @@ -720,27 +698,26 @@ final HapiSpec hapiNftSetApprovalForAll() { } @HapiTest - final HapiSpec testIndirectApprovalWithDelegatePrecompileCallee() { + final Stream testIndirectApprovalWithDelegatePrecompileCallee() { return testIndirectApprovalWith("DelegatePrecompileCallee", DELEGATE_PRECOMPILE_CALLEE, false); } @HapiTest - final HapiSpec testIndirectApprovalWithDirectPrecompileCallee() { + final Stream testIndirectApprovalWithDirectPrecompileCallee() { return testIndirectApprovalWith("DirectPrecompileCallee", DIRECT_PRECOMPILE_CALLEE, true); } @HapiTest - final HapiSpec testIndirectApprovalWithDelegateErc20Callee() { + final Stream testIndirectApprovalWithDelegateErc20Callee() { return testIndirectApprovalWith("DelegateErc20Callee", DELEGATE_ERC_CALLEE, false); } @HapiTest - final HapiSpec testIndirectApprovalWithDirectErc20Callee() { + final Stream testIndirectApprovalWithDirectErc20Callee() { return testIndirectApprovalWith("DirectErc20Callee", DIRECT_ERC_CALLEE, true); } - @BddMethodIsNotATest - final HapiSpec testIndirectApprovalWith( + final Stream testIndirectApprovalWith( @NonNull final String testName, @NonNull final String callee, final boolean expectGrantedApproval) { final AtomicReference tokenID = new AtomicReference<>(); @@ -762,9 +739,12 @@ final HapiSpec testIndirectApprovalWith( .treasury(TOKEN_TREASURY) .exposingCreatedIdTo(id -> tokenID.set(asToken(id))), uploadInitCode(PRETEND_PAIR), - contractCreate(PRETEND_PAIR).adminKey(DEFAULT_PAYER), + // Refusing ethereum create conversion, because we get INVALID_SIGNATURE upon tokenAssociate, + // since we have CONTRACT_ID key + contractCreate(PRETEND_PAIR).adminKey(DEFAULT_PAYER).refusingEthConversion(), uploadInitCode(callee), contractCreate(callee) + .refusingEthConversion() .adminKey(DEFAULT_PAYER) .exposingNumTo(num -> calleeMirrorAddr.set(asHexedSolidityAddress(0, 0, num))), tokenAssociate(PRETEND_PAIR, FUNGIBLE_TOKEN), diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/precompile/AssociatePrecompileSuite.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/precompile/AssociatePrecompileSuite.java index de5b0fbcb0a2..9b9a02b243a2 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/precompile/AssociatePrecompileSuite.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/precompile/AssociatePrecompileSuite.java @@ -46,6 +46,7 @@ import static com.hedera.services.bdd.spec.utilops.records.SnapshotMatchMode.NONDETERMINISTIC_FUNCTION_PARAMETERS; import static com.hedera.services.bdd.spec.utilops.records.SnapshotMatchMode.NONDETERMINISTIC_NONCE; import static com.hedera.services.bdd.spec.utilops.records.SnapshotMatchMode.NONDETERMINISTIC_TRANSACTION_FEES; +import static com.hedera.services.bdd.suites.HapiSuite.GENESIS; import static com.hedera.services.bdd.suites.contract.Utils.asAddress; import static com.hedera.services.bdd.suites.contract.Utils.asToken; import static com.hedera.services.bdd.suites.token.TokenAssociationSpecs.VANILLA_TOKEN; @@ -59,27 +60,19 @@ import com.esaulpaugh.headlong.abi.Address; import com.hedera.services.bdd.junit.HapiTest; -import com.hedera.services.bdd.junit.HapiTestSuite; import com.hedera.services.bdd.spec.HapiPropertySource; -import com.hedera.services.bdd.spec.HapiSpec; import com.hedera.services.bdd.spec.keys.KeyShape; import com.hedera.services.bdd.spec.transactions.contract.HapiParserUtil; -import com.hedera.services.bdd.suites.HapiSuite; import com.hederahashgraph.api.proto.java.AccountID; import com.hederahashgraph.api.proto.java.TokenID; import com.hederahashgraph.api.proto.java.TokenType; -import java.util.List; import java.util.concurrent.atomic.AtomicReference; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; +import java.util.stream.Stream; +import org.junit.jupiter.api.DynamicTest; import org.junit.jupiter.api.Tag; -@HapiTestSuite(fuzzyMatch = true) @Tag(SMART_CONTRACT) -public class AssociatePrecompileSuite extends HapiSuite { - - private static final Logger log = LogManager.getLogger(AssociatePrecompileSuite.class); - +public class AssociatePrecompileSuite { private static final long GAS_TO_OFFER = 4_000_000L; private static final KeyShape DELEGATE_CONTRACT_KEY_SHAPE = KeyShape.threshOf(1, SIMPLE, DELEGATE_CONTRACT); private static final String TOKEN_TREASURY = "treasury"; @@ -100,39 +93,9 @@ public class AssociatePrecompileSuite extends HapiSuite { private static final String CONTRACT_KEY = "ContractKey"; private static final KeyShape KEY_SHAPE = KeyShape.threshOf(1, ED25519, CONTRACT); - public static void main(String... args) { - new AssociatePrecompileSuite().runSuiteAsync(); - } - - @Override - public boolean canRunConcurrent() { - return true; - } - - @Override - public List getSpecsInSuite() { - return allOf(positiveSpecs(), negativeSpecs()); - } - - List negativeSpecs() { - return List.of( - functionCallWithLessThanFourBytesFailsWithinSingleContractCall(), - nonSupportedAbiCallGracefullyFailsWithMultipleContractCalls(), - invalidlyFormattedAbiCallGracefullyFailsWithMultipleContractCalls(), - nonSupportedAbiCallGracefullyFailsWithinSingleContractCall(), - invalidAbiCallGracefullyFailsWithinSingleContractCall(), - invalidSingleAbiCallConsumesAllProvidedGas(), - associateTokensNegativeScenarios(), - associateTokenNegativeScenarios()); - } - - List positiveSpecs() { - return List.of(associateWithMissingEvmAddressHasSaneTxnAndRecord()); - } - /* -- HSCS-PREC-27 from HTS Precompile Test Plan -- */ @HapiTest - final HapiSpec functionCallWithLessThanFourBytesFailsWithinSingleContractCall() { + final Stream functionCallWithLessThanFourBytesFailsWithinSingleContractCall() { return defaultHapiSpec("functionCallWithLessThanFourBytesFailsWithinSingleContractCall") .given(uploadInitCode(THE_GRACEFULLY_FAILING_CONTRACT), contractCreate(THE_GRACEFULLY_FAILING_CONTRACT)) .when(contractCall( @@ -148,7 +111,7 @@ final HapiSpec functionCallWithLessThanFourBytesFailsWithinSingleContractCall() /* -- HSCS-PREC-27 from HTS Precompile Test Plan -- */ @HapiTest - final HapiSpec invalidAbiCallGracefullyFailsWithinSingleContractCall() { + final Stream invalidAbiCallGracefullyFailsWithinSingleContractCall() { return defaultHapiSpec("invalidAbiCallGracefullyFailsWithinSingleContractCall") .given(uploadInitCode(THE_GRACEFULLY_FAILING_CONTRACT), contractCreate(THE_GRACEFULLY_FAILING_CONTRACT)) .when(contractCall( @@ -166,7 +129,7 @@ final HapiSpec invalidAbiCallGracefullyFailsWithinSingleContractCall() { /* -- HSCS-PREC-26 from HTS Precompile Test Plan -- */ @HapiTest - final HapiSpec nonSupportedAbiCallGracefullyFailsWithinSingleContractCall() { + final Stream nonSupportedAbiCallGracefullyFailsWithinSingleContractCall() { return defaultHapiSpec( "nonSupportedAbiCallGracefullyFailsWithinSingleContractCall", NONDETERMINISTIC_TRANSACTION_FEES) .given(uploadInitCode(THE_GRACEFULLY_FAILING_CONTRACT), contractCreate(THE_GRACEFULLY_FAILING_CONTRACT)) @@ -182,7 +145,7 @@ final HapiSpec nonSupportedAbiCallGracefullyFailsWithinSingleContractCall() { /* -- HSCS-PREC-26 from HTS Precompile Test Plan -- */ @HapiTest - final HapiSpec nonSupportedAbiCallGracefullyFailsWithMultipleContractCalls() { + final Stream nonSupportedAbiCallGracefullyFailsWithMultipleContractCalls() { final AtomicReference accountID = new AtomicReference<>(); final AtomicReference vanillaTokenID = new AtomicReference<>(); @@ -235,7 +198,7 @@ final HapiSpec nonSupportedAbiCallGracefullyFailsWithMultipleContractCalls() { /* -- HSCS-PREC-27 from HTS Precompile Test Plan -- */ @HapiTest - final HapiSpec invalidlyFormattedAbiCallGracefullyFailsWithMultipleContractCalls() { + final Stream invalidlyFormattedAbiCallGracefullyFailsWithMultipleContractCalls() { final AtomicReference accountID = new AtomicReference<>(); final AtomicReference vanillaTokenID = new AtomicReference<>(); final var invalidAbiArgument = new byte[20]; @@ -297,7 +260,7 @@ final HapiSpec invalidlyFormattedAbiCallGracefullyFailsWithMultipleContractCalls } @HapiTest - final HapiSpec associateWithMissingEvmAddressHasSaneTxnAndRecord() { + final Stream associateWithMissingEvmAddressHasSaneTxnAndRecord() { final AtomicReference
    tokenAddress = new AtomicReference<>(); final var missingAddress = Address.wrap(Address.toChecksumAddress("0xabababababababababababababababababababab")); @@ -327,7 +290,7 @@ final HapiSpec associateWithMissingEvmAddressHasSaneTxnAndRecord() { /* -- HSCS-PREC-27 from HTS Precompile Test Plan -- */ @HapiTest - final HapiSpec invalidSingleAbiCallConsumesAllProvidedGas() { + final Stream invalidSingleAbiCallConsumesAllProvidedGas() { return defaultHapiSpec("invalidSingleAbiCallConsumesAllProvidedGas", NONDETERMINISTIC_TRANSACTION_FEES) .given(uploadInitCode(THE_GRACEFULLY_FAILING_CONTRACT), contractCreate(THE_GRACEFULLY_FAILING_CONTRACT)) .when( @@ -349,7 +312,7 @@ final HapiSpec invalidSingleAbiCallConsumesAllProvidedGas() { } @HapiTest - final HapiSpec associateTokensNegativeScenarios() { + final Stream associateTokensNegativeScenarios() { final AtomicReference
    tokenAddress1 = new AtomicReference<>(); final AtomicReference
    tokenAddress2 = new AtomicReference<>(); final AtomicReference
    accountAddress = new AtomicReference<>(); @@ -466,7 +429,7 @@ someNonExistingTokenArray, SUCCESS, recordWith().status(SUCCESS)), } @HapiTest - final HapiSpec associateTokenNegativeScenarios() { + final Stream associateTokenNegativeScenarios() { final AtomicReference
    tokenAddress = new AtomicReference<>(); final AtomicReference
    accountAddress = new AtomicReference<>(); final var nonExistingAccount = "nonExistingAccount"; @@ -544,9 +507,4 @@ final HapiSpec associateTokenNegativeScenarios() { CONTRACT_REVERT_EXECUTED, recordWith().status(INVALID_TOKEN_ID))); } - - @Override - protected Logger getResultsLogger() { - return log; - } } diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/precompile/AssociatePrecompileV1SecurityModelSuite.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/precompile/AssociatePrecompileV1SecurityModelSuite.java index 96f464e569a9..fc8eb0704c50 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/precompile/AssociatePrecompileV1SecurityModelSuite.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/precompile/AssociatePrecompileV1SecurityModelSuite.java @@ -49,7 +49,6 @@ import static com.hederahashgraph.api.proto.java.TokenType.FUNGIBLE_COMMON; import com.esaulpaugh.headlong.abi.Address; -import com.hedera.services.bdd.spec.HapiSpec; import com.hedera.services.bdd.spec.transactions.contract.HapiParserUtil; import com.hedera.services.bdd.suites.HapiSuite; import com.hedera.services.bdd.suites.token.TokenAssociationSpecs; @@ -58,8 +57,10 @@ import com.hederahashgraph.api.proto.java.TokenID; import java.util.List; import java.util.concurrent.atomic.AtomicReference; +import java.util.stream.Stream; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; +import org.junit.jupiter.api.DynamicTest; public class AssociatePrecompileV1SecurityModelSuite extends HapiSuite { @@ -88,20 +89,20 @@ public boolean canRunConcurrent() { } @Override - public List getSpecsInSuite() { + public List> getSpecsInSuite() { return allOf(positiveSpecs(), negativeSpecs()); } - List negativeSpecs() { + List> negativeSpecs() { return List.of(); } - List positiveSpecs() { + List> positiveSpecs() { return List.of(nestedAssociateWorksAsExpected(), multipleAssociatePrecompileWithSignatureWorksForFungible()); } /* -- HSCS-PREC-006 from HTS Precompile Test Plan -- */ - final HapiSpec multipleAssociatePrecompileWithSignatureWorksForFungible() { + final Stream multipleAssociatePrecompileWithSignatureWorksForFungible() { final AtomicReference accountID = new AtomicReference<>(); final AtomicReference frozenTokenID = new AtomicReference<>(); final AtomicReference unfrozenTokenID = new AtomicReference<>(); @@ -184,7 +185,7 @@ final HapiSpec multipleAssociatePrecompileWithSignatureWorksForFungible() { } /* -- HSCS-PREC-010 from HTS Precompile Test Plan -- */ - final HapiSpec nestedAssociateWorksAsExpected() { + final Stream nestedAssociateWorksAsExpected() { final AtomicReference accountID = new AtomicReference<>(); final AtomicReference vanillaTokenID = new AtomicReference<>(); diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/precompile/AssociatePrecompileV2SecurityModelSuite.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/precompile/AssociatePrecompileV2SecurityModelSuite.java index b25454d1d586..c1544502f315 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/precompile/AssociatePrecompileV2SecurityModelSuite.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/precompile/AssociatePrecompileV2SecurityModelSuite.java @@ -41,6 +41,9 @@ import static com.hedera.services.bdd.spec.utilops.UtilVerbs.emptyChildRecordsCheck; import static com.hedera.services.bdd.spec.utilops.UtilVerbs.newKeyNamed; import static com.hedera.services.bdd.spec.utilops.UtilVerbs.withOpContext; +import static com.hedera.services.bdd.suites.HapiSuite.ONE_HUNDRED_HBARS; +import static com.hedera.services.bdd.suites.HapiSuite.ONE_MILLION_HBARS; +import static com.hedera.services.bdd.suites.HapiSuite.THOUSAND_HBAR; import static com.hedera.services.bdd.suites.contract.Utils.asAddress; import static com.hedera.services.bdd.suites.contract.Utils.getNestedContractAddress; import static com.hedera.services.bdd.suites.utils.contracts.precompile.HTSPrecompileResult.htsPrecompileResult; @@ -59,24 +62,16 @@ import com.esaulpaugh.headlong.abi.Address; import com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.associations.AssociationsTranslator; import com.hedera.services.bdd.junit.HapiTest; -import com.hedera.services.bdd.junit.HapiTestSuite; -import com.hedera.services.bdd.spec.HapiSpec; import com.hedera.services.bdd.spec.keys.KeyShape; import com.hedera.services.bdd.spec.transactions.contract.HapiParserUtil; -import com.hedera.services.bdd.suites.HapiSuite; import com.hederahashgraph.api.proto.java.TokenType; -import java.util.List; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; +import java.util.stream.Stream; import org.apache.tuweni.bytes.Bytes; +import org.junit.jupiter.api.DynamicTest; import org.junit.jupiter.api.Tag; -@HapiTestSuite @Tag(SMART_CONTRACT) -public class AssociatePrecompileV2SecurityModelSuite extends HapiSuite { - - private static final Logger log = LogManager.getLogger(AssociatePrecompileV1SecurityModelSuite.class); - +public class AssociatePrecompileV2SecurityModelSuite { private static final long GAS_TO_OFFER = 4_000_000L; private static final long TOTAL_SUPPLY = 1_000; private static final String SIGNER = "anybody"; @@ -98,34 +93,8 @@ public class AssociatePrecompileV2SecurityModelSuite extends HapiSuite { private static final String MINT_TOKEN_CONTRACT = "MixedMintToken"; private static final String CALLCODE_CONTRACT = "MixedMintToken"; - public static void main(String... args) { - new AssociatePrecompileV2SecurityModelSuite().runSuiteAsync(); - } - - @Override - public boolean canRunConcurrent() { - return true; - } - - @Override - public List getSpecsInSuite() { - return allOf(positiveSpecs(), negativeSpecs()); - } - - List negativeSpecs() { - return List.of( - v2Security006TokenAssociateNegativeTests(), V2Security041TokenAssociateFromStaticcallAndCallcode()); - } - - List positiveSpecs() { - return List.of( - v2Security031AssociateSingleTokenWithDelegateContractKey(), - v2Security010NestedAssociateNftAndNonFungibleTokens(), - V2Security036TokenAssociateFromDelegateCallWithDelegateContractId()); - } - @HapiTest - final HapiSpec v2Security031AssociateSingleTokenWithDelegateContractKey() { + final Stream v2Security031AssociateSingleTokenWithDelegateContractKey() { return defaultHapiSpec("v2Security031AssociateSingleTokenWithDelegateContractKey") .given( @@ -251,7 +220,7 @@ final HapiSpec v2Security031AssociateSingleTokenWithDelegateContractKey() { } @HapiTest - final HapiSpec v2Security006TokenAssociateNegativeTests() { + final Stream v2Security006TokenAssociateNegativeTests() { return defaultHapiSpec("v2Security006TokenAssociateNegativeTests") .given( newKeyNamed(FREEZE_KEY), @@ -448,7 +417,7 @@ final HapiSpec v2Security006TokenAssociateNegativeTests() { } @HapiTest - final HapiSpec v2Security010NestedAssociateNftAndNonFungibleTokens() { + final Stream v2Security010NestedAssociateNftAndNonFungibleTokens() { return defaultHapiSpec("v2Security010NestedAssociateNftAndNonFungibleTokens") .given( @@ -536,7 +505,7 @@ final HapiSpec v2Security010NestedAssociateNftAndNonFungibleTokens() { } @HapiTest - final HapiSpec V2Security036TokenAssociateFromDelegateCallWithDelegateContractId() { + final Stream V2Security036TokenAssociateFromDelegateCallWithDelegateContractId() { return defaultHapiSpec("v2Security010NestedAssociateNftAndNonFungibleTokens") .given( @@ -624,7 +593,7 @@ final HapiSpec V2Security036TokenAssociateFromDelegateCallWithDelegateContractId } @HapiTest - final HapiSpec V2Security041TokenAssociateFromStaticcallAndCallcode() { + final Stream V2Security041TokenAssociateFromStaticcallAndCallcode() { return defaultHapiSpec("V2Security041TokenAssociateFromStaticcallAndCallcode") .given( @@ -706,9 +675,4 @@ final HapiSpec V2Security041TokenAssociateFromStaticcallAndCallcode() { emptyChildRecordsCheck("associateCallcodeFungibleTxn", CONTRACT_REVERT_EXECUTED), getAccountInfo(ACCOUNT).hasNoTokenRelationship(FUNGIBLE_TOKEN)); } - - @Override - protected Logger getResultsLogger() { - return log; - } } diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/precompile/AtomicCryptoTransferHTSSuite.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/precompile/AtomicCryptoTransferHTSSuite.java index f79645bcb68b..1cf20f47f5fc 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/precompile/AtomicCryptoTransferHTSSuite.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/precompile/AtomicCryptoTransferHTSSuite.java @@ -16,6 +16,7 @@ package com.hedera.services.bdd.suites.contract.precompile; +import static com.hedera.services.bdd.junit.ContextRequirement.PROPERTY_OVERRIDES; import static com.hedera.services.bdd.junit.TestTags.SMART_CONTRACT; import static com.hedera.services.bdd.spec.HapiPropertySource.asHexedSolidityAddress; import static com.hedera.services.bdd.spec.HapiSpec.propertyPreservingHapiSpec; @@ -65,6 +66,10 @@ import static com.hedera.services.bdd.spec.utilops.records.SnapshotMatchMode.NONDETERMINISTIC_FUNCTION_PARAMETERS; import static com.hedera.services.bdd.spec.utilops.records.SnapshotMatchMode.NONDETERMINISTIC_NONCE; import static com.hedera.services.bdd.spec.utilops.records.SnapshotMatchMode.NONDETERMINISTIC_TRANSACTION_FEES; +import static com.hedera.services.bdd.suites.HapiSuite.DEFAULT_PAYER; +import static com.hedera.services.bdd.suites.HapiSuite.GENESIS; +import static com.hedera.services.bdd.suites.HapiSuite.ONE_HBAR; +import static com.hedera.services.bdd.suites.HapiSuite.ONE_HUNDRED_HBARS; import static com.hedera.services.bdd.suites.contract.precompile.ApproveAllowanceSuite.CONTRACTS_PERMITTED_DELEGATE_CALLERS; import static com.hedera.services.bdd.suites.utils.MiscEETUtils.metadata; import static com.hedera.services.bdd.suites.utils.contracts.precompile.HTSPrecompileResult.htsPrecompileResult; @@ -79,30 +84,24 @@ import com.esaulpaugh.headlong.abi.Tuple; import com.hedera.node.app.hapi.utils.ByteStringUtils; import com.hedera.services.bdd.junit.HapiTest; -import com.hedera.services.bdd.junit.HapiTestSuite; -import com.hedera.services.bdd.spec.HapiSpec; +import com.hedera.services.bdd.junit.LeakyHapiTest; import com.hedera.services.bdd.spec.assertions.ContractInfoAsserts; import com.hedera.services.bdd.spec.assertions.NonFungibleTransfers; import com.hedera.services.bdd.spec.assertions.SomeFungibleTransfers; import com.hedera.services.bdd.spec.keys.KeyShape; import com.hedera.services.bdd.spec.transactions.token.TokenMovement; -import com.hedera.services.bdd.suites.HapiSuite; import com.hederahashgraph.api.proto.java.TokenSupplyType; import com.hederahashgraph.api.proto.java.TokenType; import java.util.List; import java.util.OptionalLong; import java.util.concurrent.atomic.AtomicLong; import java.util.concurrent.atomic.AtomicReference; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; +import java.util.stream.Stream; +import org.junit.jupiter.api.DynamicTest; import org.junit.jupiter.api.Tag; -@HapiTestSuite(fuzzyMatch = true) @Tag(SMART_CONTRACT) -public class AtomicCryptoTransferHTSSuite extends HapiSuite { - - private static final Logger log = LogManager.getLogger(AtomicCryptoTransferHTSSuite.class); - +public class AtomicCryptoTransferHTSSuite { private static final Tuple[] EMPTY_TUPLE_ARRAY = new Tuple[] {}; private static final long GAS_TO_OFFER = 5_000_000L; private static final long TOTAL_SUPPLY = 1_000; @@ -124,37 +123,8 @@ public class AtomicCryptoTransferHTSSuite extends HapiSuite { public static final String SECP_256K1_SOURCE_KEY = "secp256k1Alias"; - public static void main(final String... args) { - new AtomicCryptoTransferHTSSuite().runSuiteSync(); - } - - @Override - public boolean canRunConcurrent() { - return false; - } - - @Override - public List getSpecsInSuite() { - return List.of(new HapiSpec[] { - cryptoTransferForHbarOnly(), - cryptoTransferForFungibleTokenOnly(), - cryptoTransferForFungibleTokenWithFees(), - cryptoTransferForNFTWithFees(), - cryptoTransferForNonFungibleTokenOnly(), - cryptoTransferHBarFungibleNft(), - cryptoTransferSpecialAccounts(), - blockCryptoTransferForPermittedDelegates(), - cryptoTransferAllowanceToContractHbar(), - cryptoTransferAllowanceToContractFT(), - cryptoTransferAllowanceToContractNFT(), - cryptoTransferAllowanceToContractFromContract(), - receiverSigRequiredButNotProvided(), - cryptoTransferSpecialAccounts() - }); - } - @HapiTest - final HapiSpec cryptoTransferForHbarOnly() { + final Stream cryptoTransferForHbarOnly() { final var cryptoTransferTxn = "cryptoTransferTxn"; final var cryptoTransferMultiTxn = "cryptoTransferMultiTxn"; final var cryptoTransferRevertTxn = "cryptoTransferRevertTxn"; @@ -344,7 +314,7 @@ final HapiSpec cryptoTransferForHbarOnly() { } @HapiTest - final HapiSpec cryptoTransferForFungibleTokenOnly() { + final Stream cryptoTransferForFungibleTokenOnly() { final var cryptoTransferTxnForFungible = "cryptoTransferTxnForFungible"; final var cryptoTransferRevertNoKeyTxn = "cryptoTransferRevertNoKeyTxn"; @@ -452,7 +422,7 @@ final HapiSpec cryptoTransferForFungibleTokenOnly() { } @HapiTest - final HapiSpec cryptoTransferForFungibleTokenWithFees() { + final Stream cryptoTransferForFungibleTokenWithFees() { final var cryptoTransferTxnForFungible = "cryptoTransferTxnForFungible"; final var FEE_TOKEN = "FeeToken"; @@ -544,7 +514,7 @@ final HapiSpec cryptoTransferForFungibleTokenWithFees() { } @HapiTest - final HapiSpec cryptoTransferForNFTWithFees() { + final Stream cryptoTransferForNFTWithFees() { final var cryptoTransferTxnForNonFungible = "cryptoTransferTxnForNonFungible"; final var FEE_TOKEN = "FeeToken"; @@ -640,7 +610,7 @@ final HapiSpec cryptoTransferForNFTWithFees() { } @HapiTest - final HapiSpec cryptoTransferForNonFungibleTokenOnly() { + final Stream cryptoTransferForNonFungibleTokenOnly() { final var cryptoTransferTxnForNft = "cryptoTransferTxnForNft"; final var cryptoTransferRevertNoKeyTxn = "cryptoTransferRevertNoKeyTxn"; @@ -745,7 +715,7 @@ final HapiSpec cryptoTransferForNonFungibleTokenOnly() { } @HapiTest - final HapiSpec cryptoTransferHBarFungibleNft() { + final Stream cryptoTransferHBarFungibleNft() { final var cryptoTransferTxnForAll = "cryptoTransferTxnForAll"; return propertyPreservingHapiSpec( @@ -877,7 +847,7 @@ final HapiSpec cryptoTransferHBarFungibleNft() { } @HapiTest - final HapiSpec cryptoTransferAllowanceToContractHbar() { + final Stream cryptoTransferAllowanceToContractHbar() { final var allowance = 11L; final var successfulTransferFromTxn = "txn"; final var successfulTransferFromTxn2 = "txn2"; @@ -1041,7 +1011,7 @@ final HapiSpec cryptoTransferAllowanceToContractHbar() { } @HapiTest - final HapiSpec cryptoTransferAllowanceToContractFT() { + final Stream cryptoTransferAllowanceToContractFT() { final var allowance = 11L; final var successfulTransferFromTxn = "txn"; final var successfulTransferFromTxn2 = "txn2"; @@ -1232,7 +1202,7 @@ final HapiSpec cryptoTransferAllowanceToContractFT() { } @HapiTest - final HapiSpec cryptoTransferAllowanceToContractNFT() { + final Stream cryptoTransferAllowanceToContractNFT() { final var successfulTransferFromTxn = "txn"; final var revertingTransferFromTxnNft = "revertWhenMoreThanAllowanceNft"; return propertyPreservingHapiSpec( @@ -1320,7 +1290,7 @@ final HapiSpec cryptoTransferAllowanceToContractNFT() { } @HapiTest - final HapiSpec cryptoTransferAllowanceToContractFromContract() { + final Stream cryptoTransferAllowanceToContractFromContract() { final var successfulTransferFromTxn = "txn"; final var revertingTransferFromTxnNft = "revertWhenMoreThanAllowanceNft"; final var simpleStorageContract = "SimpleStorage"; @@ -1383,7 +1353,7 @@ final HapiSpec cryptoTransferAllowanceToContractFromContract() { } @HapiTest - final HapiSpec receiverSigRequiredButNotProvided() { + final Stream receiverSigRequiredButNotProvided() { final var failedTransferFromTxn = "failed_txn"; final long allowance = 20L; @@ -1437,8 +1407,8 @@ final HapiSpec receiverSigRequiredButNotProvided() { .withStatus(INVALID_FULL_PREFIX_SIGNATURE_FOR_PRECOMPILE))))); } - @HapiTest - final HapiSpec cryptoTransferSpecialAccounts() { + @LeakyHapiTest(PROPERTY_OVERRIDES) + final Stream cryptoTransferSpecialAccounts() { final var cryptoTransferTxn = "cryptoTransferTxn"; return propertyPreservingHapiSpec( @@ -1514,7 +1484,7 @@ final HapiSpec cryptoTransferSpecialAccounts() { } @HapiTest - final HapiSpec blockCryptoTransferForPermittedDelegates() { + final Stream blockCryptoTransferForPermittedDelegates() { final var blockCryptoTransferForPermittedDelegates = "blockCryptoTransferForPermittedDelegates"; final AtomicLong whitelistedCalleeMirrorNum = new AtomicLong(); final AtomicReference whitelistedCalleeMirrorAddr = new AtomicReference<>(); @@ -1589,9 +1559,4 @@ final HapiSpec blockCryptoTransferForPermittedDelegates() { .contractCallResult( htsPrecompileResult().withStatus(SPENDER_DOES_NOT_HAVE_ALLOWANCE))))); } - - @Override - protected Logger getResultsLogger() { - return log; - } } diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/precompile/ContractBurnHTSSuite.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/precompile/ContractBurnHTSSuite.java index e5a5b42574e3..12edfbdbc743 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/precompile/ContractBurnHTSSuite.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/precompile/ContractBurnHTSSuite.java @@ -34,6 +34,7 @@ import static com.hedera.services.bdd.spec.utilops.UtilVerbs.sourcing; import static com.hedera.services.bdd.spec.utilops.records.SnapshotMatchMode.NONDETERMINISTIC_FUNCTION_PARAMETERS; import static com.hedera.services.bdd.spec.utilops.records.SnapshotMatchMode.NONDETERMINISTIC_TRANSACTION_FEES; +import static com.hedera.services.bdd.suites.HapiSuite.ONE_HUNDRED_HBARS; import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.CONTRACT_REVERT_EXECUTED; import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.INVALID_TOKEN_BURN_AMOUNT; import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.INVALID_TOKEN_ID; @@ -43,36 +44,24 @@ import com.esaulpaugh.headlong.abi.Address; import com.hedera.services.bdd.junit.HapiTest; -import com.hedera.services.bdd.junit.HapiTestSuite; -import com.hedera.services.bdd.spec.HapiPropertySource; -import com.hedera.services.bdd.spec.HapiSpec; import com.hedera.services.bdd.spec.keys.KeyShape; -import com.hedera.services.bdd.suites.HapiSuite; import com.hederahashgraph.api.proto.java.TokenType; -import edu.umd.cs.findbugs.annotations.NonNull; import java.math.BigInteger; import java.util.List; import java.util.concurrent.atomic.AtomicReference; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; +import java.util.stream.Stream; +import org.junit.jupiter.api.DynamicTest; import org.junit.jupiter.api.Tag; -@HapiTestSuite(fuzzyMatch = true) @Tag(SMART_CONTRACT) -public class ContractBurnHTSSuite extends HapiSuite { - - private static final Logger log = LogManager.getLogger(ContractBurnHTSSuite.class); - +public class ContractBurnHTSSuite { private static final long GAS_TO_OFFER = 4_000_000L; - public static final String THE_BURN_CONTRACT = "BurnToken"; public static final String MULTIVERSION_BURN_CONTRACT = "MultiversionBurn"; public static final String ALICE = "Alice"; private static final String TOKEN = "Token"; private static final String TOKEN_TREASURY = "TokenTreasury"; private static final String MULTI_KEY = "purpose"; - public static final String CREATION_TX = "creationTx"; - public static final String BURN_TOKEN_WITH_EVENT = "burnTokenWithEvent"; private static final String FIRST = "First!"; private static final String SECOND = "Second!"; private static final String BURN_TOKEN_V_1 = "burnTokenV1"; @@ -81,37 +70,8 @@ public class ContractBurnHTSSuite extends HapiSuite { private static final String CONTRACT_KEY = "ContractKey"; private static final String NFT = "NFT"; - public static void main(String... args) { - new ContractBurnHTSSuite().runSuiteAsync(); - } - - @Override - public boolean canRunConcurrent() { - return true; - } - - @Override - public List getSpecsInSuite() { - return allOf(positiveSpecs(), negativeSpecs()); - } - - List negativeSpecs() { - return List.of( - burnFungibleV1andV2WithZeroAndNegativeValues(), - burnNonFungibleV1andV2WithNegativeValues(), - burnWithNegativeAmount(), - burnWithExtremeAmount(), - burnWithInvalidAddress(), - burnWithZeroAddress(), - burnWithInvalidSerials()); - } - - List positiveSpecs() { - return List.of(); - } - @HapiTest - final HapiSpec burnFungibleV1andV2WithZeroAndNegativeValues() { + final Stream burnFungibleV1andV2WithZeroAndNegativeValues() { final AtomicReference
    tokenAddress = new AtomicReference<>(); return defaultHapiSpec("burnFungibleV1andV2WithZeroAndNegativeValues", NONDETERMINISTIC_FUNCTION_PARAMETERS) .given( @@ -175,7 +135,7 @@ final HapiSpec burnFungibleV1andV2WithZeroAndNegativeValues() { } @HapiTest - final HapiSpec burnNonFungibleV1andV2WithNegativeValues() { + final Stream burnNonFungibleV1andV2WithNegativeValues() { final AtomicReference
    tokenAddress = new AtomicReference<>(); return defaultHapiSpec( "burnNonFungibleV1andV2WithNegativeValues", @@ -226,7 +186,7 @@ final HapiSpec burnNonFungibleV1andV2WithNegativeValues() { } @HapiTest - final HapiSpec burnWithNegativeAmount() { + final Stream burnWithNegativeAmount() { final var negativeBurnFungible = "negativeBurnFungible"; final var negativeBurnNFT = "negativeBurnNFT"; final AtomicReference
    tokenAddress = new AtomicReference<>(); @@ -280,7 +240,7 @@ final HapiSpec burnWithNegativeAmount() { } @HapiTest - final HapiSpec burnWithExtremeAmount() { + final Stream burnWithExtremeAmount() { final AtomicReference
    tokenAddress = new AtomicReference<>(); final AtomicReference
    nftAddress = new AtomicReference<>(); final var fungibleExtremeAmount = "fungibleExtremeAmounts"; @@ -331,7 +291,7 @@ final HapiSpec burnWithExtremeAmount() { } @HapiTest - final HapiSpec burnWithZeroAddress() { + final Stream burnWithZeroAddress() { return defaultHapiSpec("burnWithZeroAddress") .given(uploadInitCode(NEGATIVE_BURN_CONTRACT), contractCreate(NEGATIVE_BURN_CONTRACT)) .when( @@ -357,7 +317,7 @@ final HapiSpec burnWithZeroAddress() { } @HapiTest - final HapiSpec burnWithInvalidAddress() { + final Stream burnWithInvalidAddress() { return defaultHapiSpec("burnWithInvalidAddress") .given(uploadInitCode(NEGATIVE_BURN_CONTRACT), contractCreate(NEGATIVE_BURN_CONTRACT)) .when( @@ -384,7 +344,7 @@ final HapiSpec burnWithInvalidAddress() { } @HapiTest - final HapiSpec burnWithInvalidSerials() { + final Stream burnWithInvalidSerials() { final AtomicReference
    tokenAddress = new AtomicReference<>(); final AtomicReference
    nftAddress = new AtomicReference<>(); final var negativeBurnFungible = "negativeBurnFungible"; @@ -428,14 +388,4 @@ final HapiSpec burnWithInvalidSerials() { getAccountBalance(TOKEN_TREASURY).hasTokenBalance(TOKEN, 1_000), getAccountBalance(TOKEN_TREASURY).hasTokenBalance(NFT, 2)); } - - @NonNull - private String getNestedContractAddress(String outerContract, HapiSpec spec) { - return HapiPropertySource.asHexedSolidityAddress(spec.registry().getContractId(outerContract)); - } - - @Override - protected Logger getResultsLogger() { - return log; - } } diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/precompile/ContractBurnHTSV1SecurityModelSuite.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/precompile/ContractBurnHTSV1SecurityModelSuite.java index dfa584ce592b..735f9ebcf6d5 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/precompile/ContractBurnHTSV1SecurityModelSuite.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/precompile/ContractBurnHTSV1SecurityModelSuite.java @@ -70,8 +70,10 @@ import edu.umd.cs.findbugs.annotations.NonNull; import java.math.BigInteger; import java.util.List; +import java.util.stream.Stream; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; +import org.junit.jupiter.api.DynamicTest; @SuppressWarnings("java:S1192") // "string literal should not be duplicated" - this rule makes test suites worse public class ContractBurnHTSV1SecurityModelSuite extends HapiSuite { @@ -102,22 +104,22 @@ public boolean canRunConcurrent() { } @Override - public List getSpecsInSuite() { + public List> getSpecsInSuite() { return allOf(positiveSpecs(), negativeSpecs()); } - List negativeSpecs() { + List> negativeSpecs() { return List.of(hscsPreC020RollbackBurnThatFailsAfterAPrecompileTransfer()); } - List positiveSpecs() { + List> positiveSpecs() { return List.of( hscsPrec004TokenBurnOfFungibleTokenUnits(), hscsPrec005TokenBurnOfNft(), hscsPrec011BurnAfterNestedMint()); } - final HapiSpec hscsPreC020RollbackBurnThatFailsAfterAPrecompileTransfer() { + final Stream hscsPreC020RollbackBurnThatFailsAfterAPrecompileTransfer() { final var bob = "bob"; final var feeCollector = "feeCollector"; final var tokenWithHbarFee = "tokenWithHbarFee"; @@ -202,7 +204,7 @@ final HapiSpec hscsPreC020RollbackBurnThatFailsAfterAPrecompileTransfer() { getAccountBalance(ALICE).hasTokenBalance(tokenWithHbarFee, 1)); } - final HapiSpec hscsPrec004TokenBurnOfFungibleTokenUnits() { + final Stream hscsPrec004TokenBurnOfFungibleTokenUnits() { final var gasUsed = 14085L; return propertyPreservingHapiSpec("hscsPrec004TokenBurnOfFungibleTokenUnits") .preserving(CONTRACTS_ALLOW_SYSTEM_USE_OF_HAPI_SIGS, CONTRACTS_MAX_NUM_WITH_HAPI_SIGS_ACCESS) @@ -287,7 +289,7 @@ final HapiSpec hscsPrec004TokenBurnOfFungibleTokenUnits() { .then(getAccountBalance(TOKEN_TREASURY).hasTokenBalance(TOKEN, 48)); } - final HapiSpec hscsPrec005TokenBurnOfNft() { + final Stream hscsPrec005TokenBurnOfNft() { final var gasUsed = 14085; return propertyPreservingHapiSpec("hscsPrec005TokenBurnOfNft") .preserving(CONTRACTS_ALLOW_SYSTEM_USE_OF_HAPI_SIGS, CONTRACTS_MAX_NUM_WITH_HAPI_SIGS_ACCESS) @@ -344,7 +346,7 @@ final HapiSpec hscsPrec005TokenBurnOfNft() { .then(getAccountBalance(TOKEN_TREASURY).hasTokenBalance(TOKEN, 1)); } - final HapiSpec hscsPrec011BurnAfterNestedMint() { + final Stream hscsPrec011BurnAfterNestedMint() { final var innerContract = "MintToken"; final var outerContract = "NestedBurn"; final var revisedKey = KeyShape.threshOf(1, SIMPLE, DELEGATE_CONTRACT, DELEGATE_CONTRACT); diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/precompile/ContractBurnHTSV2SecurityModelSuite.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/precompile/ContractBurnHTSV2SecurityModelSuite.java index b5b65a2b70b7..8466c2425021 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/precompile/ContractBurnHTSV2SecurityModelSuite.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/precompile/ContractBurnHTSV2SecurityModelSuite.java @@ -17,41 +17,51 @@ package com.hedera.services.bdd.suites.contract.precompile; import static com.google.protobuf.ByteString.copyFromUtf8; +import static com.hedera.services.bdd.spec.HapiPropertySource.asDotDelimitedLongArray; import static com.hedera.services.bdd.spec.HapiPropertySource.asToken; import static com.hedera.services.bdd.spec.HapiSpec.defaultHapiSpec; +import static com.hedera.services.bdd.spec.assertions.AssertUtils.inOrder; +import static com.hedera.services.bdd.spec.assertions.ContractFnResultAsserts.resultWith; +import static com.hedera.services.bdd.spec.assertions.ContractLogAsserts.logWith; +import static com.hedera.services.bdd.spec.assertions.SomeFungibleTransfers.changingFungibleBalances; import static com.hedera.services.bdd.spec.assertions.TransactionRecordAsserts.recordWith; import static com.hedera.services.bdd.spec.keys.KeyShape.*; import static com.hedera.services.bdd.spec.keys.SigControl.ON; import static com.hedera.services.bdd.spec.queries.QueryVerbs.*; import static com.hedera.services.bdd.spec.transactions.TxnVerbs.*; +import static com.hedera.services.bdd.spec.transactions.contract.HapiParserUtil.asHeadlongAddress; +import static com.hedera.services.bdd.spec.transactions.token.CustomFeeSpecs.fixedHbarFee; +import static com.hedera.services.bdd.spec.transactions.token.TokenMovement.movingUnique; import static com.hedera.services.bdd.spec.utilops.CustomSpecAssert.allRunFor; import static com.hedera.services.bdd.spec.utilops.UtilVerbs.*; -import static com.hedera.services.bdd.suites.contract.Utils.asAddress; +import static com.hedera.services.bdd.suites.HapiSuite.GENESIS; +import static com.hedera.services.bdd.suites.HapiSuite.ONE_HBAR; +import static com.hedera.services.bdd.suites.HapiSuite.ONE_HUNDRED_HBARS; +import static com.hedera.services.bdd.suites.HapiSuite.ONE_MILLION_HBARS; +import static com.hedera.services.bdd.suites.contract.Utils.*; +import static com.hedera.services.bdd.suites.contract.precompile.ContractBurnHTSSuite.ALICE; +import static com.hedera.services.bdd.suites.utils.contracts.precompile.HTSPrecompileResult.htsPrecompileResult; import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.*; +import com.hedera.node.app.hapi.utils.contracts.ParsingConstants; import com.hedera.services.bdd.junit.HapiTest; -import com.hedera.services.bdd.junit.HapiTestSuite; -import com.hedera.services.bdd.spec.HapiSpec; +import com.hedera.services.bdd.spec.assertions.AccountInfoAsserts; import com.hedera.services.bdd.spec.keys.KeyShape; import com.hedera.services.bdd.spec.transactions.contract.HapiParserUtil; -import com.hedera.services.bdd.suites.HapiSuite; import com.hederahashgraph.api.proto.java.TokenID; import com.hederahashgraph.api.proto.java.TokenType; import java.math.BigInteger; import java.util.List; +import java.util.concurrent.atomic.AtomicLong; import java.util.concurrent.atomic.AtomicReference; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; +import java.util.stream.Stream; +import org.junit.jupiter.api.DynamicTest; -@HapiTestSuite @SuppressWarnings("java:S1192") -public class ContractBurnHTSV2SecurityModelSuite extends HapiSuite { - - private static final Logger LOG = LogManager.getLogger(ContractMintHTSV1SecurityModelSuite.class); - +public class ContractBurnHTSV2SecurityModelSuite { private static final long GAS_TO_OFFER = 4_000_000L; private static final String TOKEN_TREASURY = "treasury"; - private static final KeyShape TRESHOLD_KEY_SHAPE = KeyShape.threshOf(1, ED25519, CONTRACT); + private static final KeyShape THRESHOLD_KEY_SHAPE = KeyShape.threshOf(1, ED25519, CONTRACT); private static final String CONTRACT_KEY = "ContractKey"; public static final String MINT_CONTRACT = "MintContract"; private static final String DELEGATE_CONTRACT_KEY_NAME = "contractKey"; @@ -67,6 +77,7 @@ public class ContractBurnHTSV2SecurityModelSuite extends HapiSuite { private static final String SIGNER = "anybody"; private static final String SIGNER2 = "anybody"; private static final String FUNGIBLE_TOKEN = "fungibleToken"; + private static final String FUNGIBLE_TOKEN_2 = "fungibleToken2"; private static final String SIGNER_AND_TOKEN_HAVE_NO_UPDATED_KEYS = "signerAndTokenHaveNoUpdatedKeys"; private static final String SIGNER_BURNS_WITH_CONTRACT_ID = "signerBurnsAndTokenSupplyKeyHasTheIntermediaryContractId"; @@ -78,6 +89,7 @@ public class ContractBurnHTSV2SecurityModelSuite extends HapiSuite { private static final String NON_FUNGIBLE_TOKEN = "nonFungibleToken"; private static final String BURN_TOKEN = "BurnToken"; private static final String MIXED_BURN_TOKEN = "MixedBurnToken"; + private static final String BURN_TOKEN_WITH_EVENT = "burnTokenWithEvent"; private static final String FIRST = "First!"; private static final String SECOND = "Second!"; private static final String THIRD = "Third!"; @@ -90,34 +102,13 @@ public class ContractBurnHTSV2SecurityModelSuite extends HapiSuite { "NonFungibleTokenHasTheContractIdOnDelegateCall"; private static final String DELEGATE_CALL_WHEN_FUNGIBLE_TOKEN_HAS_CONTRACT_ID_SIGNER_SIGNS = "FungibleTokenHasTheContractIdOnDelegateCall"; - - public static void main(final String... args) { - new ContractBurnHTSV2SecurityModelSuite().runSuiteAsync(); - } - - @Override - public boolean canRunConcurrent() { - return true; - } - - public List getSpecsInSuite() { - return allOf(positiveSpecs(), negativeSpecs()); - } - - List positiveSpecs() { - return List.of(V2Security004FungibleTokenBurnPositive(), V2Security005NonFungibleTokenBurnPositive()); - } - - List negativeSpecs() { - return List.of( - V2Security004FungibleTokenBurnNegative(), - V2Security004NonFungibleTokenBurnNegative(), - V2Security039FungibleTokenWithDelegateContractKeyCanNotBurnFromDelegatecall(), - V2Security039NonFungibleTokenWithDelegateContractKeyCanNotBurnFromDelegatecall()); - } + private static final String ACCOUNT_NAME = "anybody"; + private static final String ORDINARY_CALLS_CONTRACT = "HTSCalls"; + private static final String ADMIN_KEY = "ADMIN_KEY"; + private static final String SUPPLY_KEY = "SUPPLY_KEY"; @HapiTest - final HapiSpec V2Security004FungibleTokenBurnPositive() { + final Stream V2Security004FungibleTokenBurnPositive() { final var initialAmount = 20L; final var amountToBurn = 5L; final AtomicReference fungible = new AtomicReference<>(); @@ -183,7 +174,7 @@ final HapiSpec V2Security004FungibleTokenBurnPositive() { getTokenInfo(FUNGIBLE_TOKEN).hasTotalSupply(initialAmount - 3 * amountToBurn), // Create a key with thresh 1/2 with sigs: new ed25519 key, contractId of burnToken contract newKeyNamed(TRESHOLD_KEY_CORRECT_CONTRACT_ID) - .shape(TRESHOLD_KEY_SHAPE.signedWith(sigs(ON, MIXED_BURN_TOKEN))), + .shape(THRESHOLD_KEY_SHAPE.signedWith(sigs(ON, MIXED_BURN_TOKEN))), // Update the token supply key to with the created key tokenUpdate(FUNGIBLE_TOKEN) .supplyKey(TRESHOLD_KEY_CORRECT_CONTRACT_ID) @@ -217,7 +208,7 @@ final HapiSpec V2Security004FungibleTokenBurnPositive() { } @HapiTest - final HapiSpec V2Security005NonFungibleTokenBurnPositive() { + final Stream V2Security005NonFungibleTokenBurnPositive() { final var amountToBurn = 1L; final AtomicReference nonFungible = new AtomicReference<>(); final var serialNumber1 = new long[] {1L}; @@ -296,7 +287,7 @@ final HapiSpec V2Security005NonFungibleTokenBurnPositive() { } @HapiTest - final HapiSpec V2Security004FungibleTokenBurnNegative() { + final Stream V2Security004FungibleTokenBurnNegative() { final var initialAmount = 20L; final var amountToBurn = 5L; final AtomicReference fungible = new AtomicReference<>(); @@ -337,7 +328,7 @@ final HapiSpec V2Security004FungibleTokenBurnNegative() { // Create a key with thresh 1/2 with sigs: new ed25519 key, contractId of MINT_CONTRACT // contract. MINT_CONTRACT is used only as a "wrong" contract id newKeyNamed(TRESHOLD_KEY_WITH_SIGNER_KEY) - .shape(TRESHOLD_KEY_SHAPE.signedWith(sigs(ON, MINT_CONTRACT))), + .shape(THRESHOLD_KEY_SHAPE.signedWith(sigs(ON, MINT_CONTRACT))), // Update the signer of the transaction to have the threshold key with the wrong contract id cryptoUpdate(SIGNER).key(TRESHOLD_KEY_WITH_SIGNER_KEY), // Update the token's supply to have the threshold key with the wrong contract id @@ -363,7 +354,7 @@ final HapiSpec V2Security004FungibleTokenBurnNegative() { // Create a key with thresh 1/2 with sigs: new ed25519 key, contractId of MIXED_BURN_TOKEN // contract // Here the key has the contract`id of the correct contract - newKeyNamed(THRESHOLD_KEY).shape(TRESHOLD_KEY_SHAPE.signedWith(sigs(ON, MIXED_BURN_TOKEN))), + newKeyNamed(THRESHOLD_KEY).shape(THRESHOLD_KEY_SHAPE.signedWith(sigs(ON, MIXED_BURN_TOKEN))), // Set the token's supply key to the initial one tokenUpdate(FUNGIBLE_TOKEN).supplyKey(TOKEN_TREASURY).signedByPayerAnd(TOKEN_TREASURY), // Update the Signer with the correct threshold key @@ -397,7 +388,7 @@ final HapiSpec V2Security004FungibleTokenBurnNegative() { } @HapiTest - final HapiSpec V2Security004NonFungibleTokenBurnNegative() { + final Stream V2Security004NonFungibleTokenBurnNegative() { final AtomicReference nonFungible = new AtomicReference<>(); final var serialNumber1 = new long[] {1L}; @@ -441,7 +432,7 @@ final HapiSpec V2Security004NonFungibleTokenBurnNegative() { // contract. MINT_CONTRACT is only used as a "wrong" contractId // Here the key has the contract`id of the wrong contract newKeyNamed(TRESHOLD_KEY_WITH_SIGNER_KEY) - .shape(TRESHOLD_KEY_SHAPE.signedWith(sigs(ON, MINT_CONTRACT))), + .shape(THRESHOLD_KEY_SHAPE.signedWith(sigs(ON, MINT_CONTRACT))), // Update the signer of the transaction to have the threshold key with the wrong contract id cryptoUpdate(SIGNER).key(TRESHOLD_KEY_WITH_SIGNER_KEY), // Update the token's supply to have the threshold key with the wrong contract id @@ -467,7 +458,7 @@ final HapiSpec V2Security004NonFungibleTokenBurnNegative() { // Create a key with thresh 1/2 with sigs: new ed25519 key, contractId of MIXED_BURN_TOKEN // contract // Here the key has the contract`id of the correct contract - newKeyNamed(THRESHOLD_KEY).shape(TRESHOLD_KEY_SHAPE.signedWith(sigs(ON, MIXED_BURN_TOKEN))), + newKeyNamed(THRESHOLD_KEY).shape(THRESHOLD_KEY_SHAPE.signedWith(sigs(ON, MIXED_BURN_TOKEN))), // Set the token's supply key to the initial one tokenUpdate(NON_FUNGIBLE_TOKEN) .supplyKey(TOKEN_TREASURY) @@ -503,7 +494,7 @@ final HapiSpec V2Security004NonFungibleTokenBurnNegative() { } @HapiTest - final HapiSpec V2Security039NonFungibleTokenWithDelegateContractKeyCanNotBurnFromDelegatecall() { + final Stream V2Security039NonFungibleTokenWithDelegateContractKeyCanNotBurnFromDelegatecall() { final var serialNumber1 = new long[] {1L}; return defaultHapiSpec("V2Security035NonFungibleTokenWithDelegateContractKeyCanNotBurnFromDelegatecall") .given( @@ -549,7 +540,7 @@ final HapiSpec V2Security039NonFungibleTokenWithDelegateContractKeyCanNotBurnFro // Create a key with thresh 1/2 with sigs: new ed25519 key, contractId of // BURN_TOKEN_VIA_DELEGATE_CALL contract newKeyNamed(TRESHOLD_KEY_CORRECT_CONTRACT_ID) - .shape(TRESHOLD_KEY_SHAPE.signedWith(sigs(ON, MIXED_BURN_TOKEN))), + .shape(THRESHOLD_KEY_SHAPE.signedWith(sigs(ON, MIXED_BURN_TOKEN))), // Update the token's supply to have the threshold key wit the wrong contract id tokenUpdate(NON_FUNGIBLE_TOKEN) .supplyKey(TRESHOLD_KEY_CORRECT_CONTRACT_ID) @@ -592,7 +583,7 @@ final HapiSpec V2Security039NonFungibleTokenWithDelegateContractKeyCanNotBurnFro } @HapiTest - final HapiSpec V2Security039FungibleTokenWithDelegateContractKeyCanNotBurnFromDelegatecall() { + final Stream V2Security039FungibleTokenWithDelegateContractKeyCanNotBurnFromDelegatecall() { final var initialAmount = 20L; return defaultHapiSpec("V2Security035FungibleTokenWithDelegateContractKeyCanNotBurnFromDelegatecall") .given( @@ -669,8 +660,388 @@ final HapiSpec V2Security039FungibleTokenWithDelegateContractKeyCanNotBurnFromDe })); } - @Override - protected Logger getResultsLogger() { - return LOG; + @HapiTest + final Stream V2SecurityBurnTokenWithFullPrefixAndPartialPrefixKeys() { + final var firstBurnTxn = "firstBurnTxn"; + final var secondBurnTxn = "secondBurnTxn"; + final var amount = 99L; + final AtomicLong fungibleNum = new AtomicLong(); + + return defaultHapiSpec("burnTokenWithFullPrefixAndPartialPrefixKeys") + .given( + newKeyNamed(SIGNER), + uploadInitCode(ORDINARY_CALLS_CONTRACT), + contractCreate(ORDINARY_CALLS_CONTRACT), + newKeyNamed(THRESHOLD_KEY) + .shape(THRESHOLD_KEY_SHAPE.signedWith(sigs(ON, ORDINARY_CALLS_CONTRACT))), + cryptoCreate(ACCOUNT_NAME).balance(10 * ONE_HUNDRED_HBARS), + cryptoCreate(TOKEN_TREASURY), + tokenCreate(FUNGIBLE_TOKEN) + .tokenType(TokenType.FUNGIBLE_COMMON) + .initialSupply(100) + .treasury(TOKEN_TREASURY) + .adminKey(SIGNER) + .supplyKey(THRESHOLD_KEY) + .exposingCreatedIdTo(idLit -> fungibleNum.set(asDotDelimitedLongArray(idLit)[2])), + tokenCreate(FUNGIBLE_TOKEN_2) + .tokenType(TokenType.FUNGIBLE_COMMON) + .initialSupply(100) + .treasury(TOKEN_TREASURY) + .adminKey(SIGNER) + .supplyKey(SIGNER) + .exposingCreatedIdTo(idLit -> fungibleNum.set(asDotDelimitedLongArray(idLit)[2]))) + .when(withOpContext((spec, opLog) -> allRunFor( + spec, + contractCall( + ORDINARY_CALLS_CONTRACT, + "burnTokenCall", + HapiParserUtil.asHeadlongAddress( + asAddress(spec.registry().getTokenID(FUNGIBLE_TOKEN_2))), + BigInteger.ONE, + new long[0]) + .via(firstBurnTxn) + .payingWith(ACCOUNT_NAME) + .signedBy(SIGNER) + .hasKnownStatus(SUCCESS), + contractCall( + ORDINARY_CALLS_CONTRACT, + "burnTokenCall", + HapiParserUtil.asHeadlongAddress( + asAddress(spec.registry().getTokenID(FUNGIBLE_TOKEN))), + BigInteger.ONE, + new long[0]) + .via(secondBurnTxn) + .payingWith(ACCOUNT_NAME) + .alsoSigningWithFullPrefix(SIGNER, THRESHOLD_KEY, ACCOUNT_NAME) + .hasKnownStatus(SUCCESS)))) + .then( + childRecordsCheck( + firstBurnTxn, + SUCCESS, + recordWith() + .status(INVALID_FULL_PREFIX_SIGNATURE_FOR_PRECOMPILE) + .contractCallResult(resultWith() + .contractCallResult(htsPrecompileResult() + .forFunction(ParsingConstants.FunctionType.HAPI_BURN) + .withStatus(INVALID_FULL_PREFIX_SIGNATURE_FOR_PRECOMPILE)))), + childRecordsCheck( + secondBurnTxn, + SUCCESS, + recordWith() + .status(SUCCESS) + .contractCallResult(resultWith() + .contractCallResult(htsPrecompileResult() + .forFunction(ParsingConstants.FunctionType.HAPI_BURN) + .withStatus(SUCCESS) + .withTotalSupply(99))) + .newTotalSupply(99)), + getTokenInfo(FUNGIBLE_TOKEN).hasTotalSupply(amount), + getAccountBalance(TOKEN_TREASURY).hasTokenBalance(FUNGIBLE_TOKEN, amount)); + } + + @HapiTest + final Stream V2SecurityHscsPreC020RollbackBurnThatFailsAfterAPrecompileTransfer() { + final var bob = "bob"; + final var feeCollector = "feeCollector"; + final var tokenWithHbarFee = "tokenWithHbarFee"; + final var theContract = "TransferAndBurn"; + final var SUPPLY_KEY = "SUPPLY_KEY"; + final var ADMIN_KEY = "ADMIN_KEY"; + + return defaultHapiSpec("hscsPreC020RollbackBurnThatFailsAfterAPrecompileTransfer") + .given( + newKeyNamed(SUPPLY_KEY), + cryptoCreate(ADMIN_KEY).balance(ONE_HUNDRED_HBARS), + cryptoCreate(ALICE).balance(ONE_HUNDRED_HBARS), + cryptoCreate(bob).balance(ONE_HUNDRED_HBARS), + cryptoCreate(TOKEN_TREASURY).balance(ONE_HUNDRED_HBARS), + cryptoCreate(feeCollector).balance(0L), + tokenCreate(tokenWithHbarFee) + .tokenType(TokenType.NON_FUNGIBLE_UNIQUE) + .supplyKey(SUPPLY_KEY) + .adminKey(ADMIN_KEY) + .initialSupply(0L) + .treasury(TOKEN_TREASURY) + .withCustom(fixedHbarFee(300 * ONE_HBAR, feeCollector)), + mintToken(tokenWithHbarFee, List.of(copyFromUtf8(FIRST))), + mintToken(tokenWithHbarFee, List.of(copyFromUtf8(SECOND))), + uploadInitCode(theContract), + withOpContext((spec, opLog) -> allRunFor( + spec, + contractCreate( + theContract, + asHeadlongAddress(asHexedAddress( + spec.registry().getTokenID(tokenWithHbarFee)))) + .payingWith(bob) + .gas(GAS_TO_OFFER))), + newKeyNamed(THRESHOLD_KEY).shape(THRESHOLD_KEY_SHAPE.signedWith(sigs(ON, theContract))), + tokenUpdate(tokenWithHbarFee).supplyKey(THRESHOLD_KEY).signedByPayerAnd(ADMIN_KEY), + tokenAssociate(ALICE, tokenWithHbarFee), + tokenAssociate(bob, tokenWithHbarFee), + tokenAssociate(theContract, tokenWithHbarFee), + cryptoTransfer(movingUnique(tokenWithHbarFee, 2L).between(TOKEN_TREASURY, ALICE)) + .payingWith(GENESIS), + getAccountInfo(feeCollector) + .has(AccountInfoAsserts.accountWith().balance(0L))) + .when( + withOpContext((spec, opLog) -> { + final var serialNumbers = new long[] {1L}; + allRunFor( + spec, + contractCall( + theContract, + "transferBurn", + HapiParserUtil.asHeadlongAddress(asAddress( + spec.registry().getAccountID(ALICE))), + HapiParserUtil.asHeadlongAddress(asAddress( + spec.registry().getAccountID(bob))), + BigInteger.ZERO, + 2L, + serialNumbers) + .alsoSigningWithFullPrefix(ALICE, THRESHOLD_KEY) + .gas(GAS_TO_OFFER) + .via("contractCallTxn") + .hasKnownStatus(CONTRACT_REVERT_EXECUTED)); + }), + childRecordsCheck( + "contractCallTxn", + CONTRACT_REVERT_EXECUTED, + recordWith() + .status(REVERTED_SUCCESS) + .contractCallResult(resultWith() + .contractCallResult(htsPrecompileResult() + .forFunction(ParsingConstants.FunctionType.HAPI_BURN) + .withStatus(SUCCESS) + .withTotalSupply(1))), + recordWith() + .status(SPENDER_DOES_NOT_HAVE_ALLOWANCE) + .contractCallResult(resultWith() + .contractCallResult(htsPrecompileResult() + .withStatus(SPENDER_DOES_NOT_HAVE_ALLOWANCE))))) + .then( + getAccountBalance(bob).hasTokenBalance(tokenWithHbarFee, 0), + getAccountBalance(TOKEN_TREASURY).hasTokenBalance(tokenWithHbarFee, 1), + getAccountBalance(ALICE).hasTokenBalance(tokenWithHbarFee, 1)); + } + + @HapiTest + final Stream V2SecurityHscsPrec004TokenBurnOfFungibleTokenUnits() { + final var gasUsed = 14085L; + final var CREATION_TX = "CREATION_TX"; + final var MULTI_KEY = "MULTI_KEY"; + + return defaultHapiSpec("V2SecurityHscsPrec004TokenBurnOfFungibleTokenUnits") + .given( + newKeyNamed(MULTI_KEY), + newKeyNamed(ADMIN_KEY), + cryptoCreate(ALICE).balance(10 * ONE_HUNDRED_HBARS), + cryptoCreate(TOKEN_TREASURY), + tokenCreate(FUNGIBLE_TOKEN) + .tokenType(TokenType.FUNGIBLE_COMMON) + .initialSupply(50L) + .supplyKey(MULTI_KEY) + .adminKey(ADMIN_KEY) + .treasury(TOKEN_TREASURY), + uploadInitCode(MIXED_BURN_TOKEN), + withOpContext((spec, opLog) -> allRunFor( + spec, + contractCreate( + MIXED_BURN_TOKEN, + asHeadlongAddress(asHexedAddress( + spec.registry().getTokenID(FUNGIBLE_TOKEN)))) + .payingWith(ALICE) + .via(CREATION_TX) + .gas(GAS_TO_OFFER))), + newKeyNamed(THRESHOLD_KEY).shape(THRESHOLD_KEY_SHAPE.signedWith(sigs(ON, MIXED_BURN_TOKEN))), + tokenUpdate(FUNGIBLE_TOKEN).supplyKey(THRESHOLD_KEY).signedByPayerAnd(ADMIN_KEY), + getTxnRecord(CREATION_TX).logged()) + .when( + contractCall(MIXED_BURN_TOKEN, BURN_TOKEN_WITH_EVENT, BigInteger.ZERO, new long[0]) + .payingWith(ALICE) + .alsoSigningWithFullPrefix(THRESHOLD_KEY) + .gas(GAS_TO_OFFER) + .via("burnZero"), + getAccountBalance(TOKEN_TREASURY).hasTokenBalance(FUNGIBLE_TOKEN, 50), + contractCall(MIXED_BURN_TOKEN, BURN_TOKEN_WITH_EVENT, BigInteger.ONE, new long[0]) + .payingWith(ALICE) + .alsoSigningWithFullPrefix(THRESHOLD_KEY) + .gas(GAS_TO_OFFER) + .via("burn"), + getTxnRecord("burn") + .hasPriority(recordWith() + .contractCallResult(resultWith() + .logs(inOrder(logWith() + .noData() + .withTopicsInOrder(List.of(parsedToByteString(49))))))), + getAccountBalance(TOKEN_TREASURY).hasTokenBalance(FUNGIBLE_TOKEN, 49), + childRecordsCheck( + "burn", + SUCCESS, + recordWith() + .status(SUCCESS) + .contractCallResult(resultWith() + .contractCallResult(htsPrecompileResult() + .forFunction(ParsingConstants.FunctionType.HAPI_BURN) + .withStatus(SUCCESS) + .withTotalSupply(49)) + .gasUsed(gasUsed)) + .newTotalSupply(49) + .tokenTransfers(changingFungibleBalances() + .including(FUNGIBLE_TOKEN, TOKEN_TREASURY, -1)) + .newTotalSupply(49)), + contractCall(MIXED_BURN_TOKEN, "burnToken", BigInteger.ONE, new long[0]) + .via("burn with contract key") + .gas(GAS_TO_OFFER), + childRecordsCheck( + "burn with contract key", + SUCCESS, + recordWith() + .status(SUCCESS) + .contractCallResult(resultWith() + .contractCallResult(htsPrecompileResult() + .forFunction(ParsingConstants.FunctionType.HAPI_BURN) + .withStatus(SUCCESS) + .withTotalSupply(48))) + .newTotalSupply(48) + .tokenTransfers(changingFungibleBalances() + .including(FUNGIBLE_TOKEN, TOKEN_TREASURY, -1)))) + .then(getAccountBalance(TOKEN_TREASURY).hasTokenBalance(FUNGIBLE_TOKEN, 48)); + } + + @HapiTest + final Stream V2SecurityHscsPrec011BurnAfterNestedMint() { + final var innerContract = "MintToken"; + final var outerContract = "NestedBurn"; + final var revisedKey = KeyShape.threshOf(1, SIMPLE, DELEGATE_CONTRACT, DELEGATE_CONTRACT); + final var SUPPLY_KEY = "SUPPLY_KEY"; + final var CREATION_TX = "CREATION_TX"; + final var BURN_AFTER_NESTED_MINT_TX = "burnAfterNestedMint"; + + return defaultHapiSpec("V2SecurityHscsPrec011BurnAfterNestedMint") + .given( + newKeyNamed(SUPPLY_KEY), + newKeyNamed(ADMIN_KEY), + cryptoCreate(ALICE).balance(10 * ONE_HUNDRED_HBARS), + cryptoCreate(TOKEN_TREASURY), + tokenCreate(FUNGIBLE_TOKEN) + .tokenType(TokenType.FUNGIBLE_COMMON) + .initialSupply(50L) + .supplyKey(SUPPLY_KEY) + .adminKey(ADMIN_KEY) + .treasury(TOKEN_TREASURY), + uploadInitCode(innerContract, outerContract), + contractCreate(innerContract).gas(GAS_TO_OFFER), + withOpContext((spec, opLog) -> allRunFor( + spec, + contractCreate( + outerContract, + asHeadlongAddress(getNestedContractAddress(innerContract, spec))) + .payingWith(ALICE) + .via(CREATION_TX) + .gas(GAS_TO_OFFER))), + newKeyNamed(THRESHOLD_KEY).shape(THRESHOLD_KEY_SHAPE.signedWith(sigs(ON, outerContract))), + tokenUpdate(FUNGIBLE_TOKEN).supplyKey(THRESHOLD_KEY).signedByPayerAnd(ADMIN_KEY), + getTxnRecord(CREATION_TX).logged()) + .when( + withOpContext((spec, opLog) -> allRunFor( + spec, + newKeyNamed(CONTRACT_KEY) + .shape(revisedKey.signedWith(sigs(ON, innerContract, outerContract))), + tokenUpdate(FUNGIBLE_TOKEN) + .supplyKey(CONTRACT_KEY) + .signedByPayerAnd(ADMIN_KEY), + contractCall( + outerContract, + BURN_AFTER_NESTED_MINT_TX, + BigInteger.ONE, + HapiParserUtil.asHeadlongAddress(asAddress( + spec.registry().getTokenID(FUNGIBLE_TOKEN))), + new long[0]) + .payingWith(ALICE) + .alsoSigningWithFullPrefix(CONTRACT_KEY) + .hasKnownStatus(SUCCESS) + .via(BURN_AFTER_NESTED_MINT_TX))), + childRecordsCheck( + BURN_AFTER_NESTED_MINT_TX, + SUCCESS, + recordWith() + .status(SUCCESS) + .contractCallResult(resultWith() + .contractCallResult(htsPrecompileResult() + .forFunction(ParsingConstants.FunctionType.HAPI_MINT) + .withStatus(SUCCESS) + .withTotalSupply(51) + .withSerialNumbers())) + .tokenTransfers( + changingFungibleBalances().including(FUNGIBLE_TOKEN, TOKEN_TREASURY, 1)) + .newTotalSupply(51), + recordWith() + .status(SUCCESS) + .contractCallResult(resultWith() + .contractCallResult(htsPrecompileResult() + .forFunction(ParsingConstants.FunctionType.HAPI_BURN) + .withStatus(SUCCESS) + .withTotalSupply(50))) + .tokenTransfers(changingFungibleBalances() + .including(FUNGIBLE_TOKEN, TOKEN_TREASURY, -1)) + .newTotalSupply(50))) + .then(getAccountBalance(TOKEN_TREASURY).hasTokenBalance(FUNGIBLE_TOKEN, 50)); + } + + @HapiTest + final Stream V2SecurityHscsPrec005TokenBurnOfNft() { + final var gasUsed = 14085; + final var CREATION_TX = "CREATION_TX"; + return defaultHapiSpec("V2SecurityHscsPrec005TokenBurnOfNft") + .given( + newKeyNamed(ADMIN_KEY), + newKeyNamed(SUPPLY_KEY), + cryptoCreate(ALICE).balance(10 * ONE_HUNDRED_HBARS), + cryptoCreate(TOKEN_TREASURY), + tokenCreate(NON_FUNGIBLE_TOKEN) + .tokenType(TokenType.NON_FUNGIBLE_UNIQUE) + .initialSupply(0L) + .supplyKey(SUPPLY_KEY) + .adminKey(ADMIN_KEY) + .treasury(TOKEN_TREASURY), + mintToken(NON_FUNGIBLE_TOKEN, List.of(copyFromUtf8(FIRST))), + mintToken(NON_FUNGIBLE_TOKEN, List.of(copyFromUtf8(SECOND))), + uploadInitCode(BURN_TOKEN), + withOpContext((spec, opLog) -> allRunFor( + spec, + contractCreate( + BURN_TOKEN, + asHeadlongAddress(asHexedAddress( + spec.registry().getTokenID(NON_FUNGIBLE_TOKEN)))) + .payingWith(ALICE) + .via(CREATION_TX) + .gas(GAS_TO_OFFER))), + newKeyNamed(THRESHOLD_KEY).shape(THRESHOLD_KEY_SHAPE.signedWith(sigs(ON, BURN_TOKEN))), + tokenUpdate(NON_FUNGIBLE_TOKEN).supplyKey(THRESHOLD_KEY).signedByPayerAnd(ADMIN_KEY), + getTxnRecord(CREATION_TX).logged()) + .when( + withOpContext((spec, opLog) -> { + final var serialNumbers = new long[] {1L}; + allRunFor( + spec, + contractCall(BURN_TOKEN, "burnToken", BigInteger.ZERO, serialNumbers) + .payingWith(ALICE) + .alsoSigningWithFullPrefix(THRESHOLD_KEY) + .gas(GAS_TO_OFFER) + .via("burn")); + }), + childRecordsCheck( + "burn", + SUCCESS, + recordWith() + .status(SUCCESS) + .contractCallResult(resultWith() + .contractCallResult(htsPrecompileResult() + .forFunction(ParsingConstants.FunctionType.HAPI_BURN) + .withStatus(SUCCESS) + .withTotalSupply(1)) + .gasUsed(gasUsed)) + .newTotalSupply(1))) + .then(getAccountBalance(TOKEN_TREASURY).hasTokenBalance(NON_FUNGIBLE_TOKEN, 1)); } } diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/precompile/ContractHTSSuite.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/precompile/ContractHTSSuite.java index 24fe9c76d7d3..c7b5e5a13242 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/precompile/ContractHTSSuite.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/precompile/ContractHTSSuite.java @@ -41,6 +41,10 @@ import static com.hedera.services.bdd.spec.utilops.records.SnapshotMatchMode.NONDETERMINISTIC_CONSTRUCTOR_PARAMETERS; import static com.hedera.services.bdd.spec.utilops.records.SnapshotMatchMode.NONDETERMINISTIC_FUNCTION_PARAMETERS; import static com.hedera.services.bdd.spec.utilops.records.SnapshotMatchMode.NONDETERMINISTIC_TRANSACTION_FEES; +import static com.hedera.services.bdd.suites.HapiSuite.DEFAULT_PAYER; +import static com.hedera.services.bdd.suites.HapiSuite.GENESIS; +import static com.hedera.services.bdd.suites.HapiSuite.ONE_HBAR; +import static com.hedera.services.bdd.suites.HapiSuite.ONE_HUNDRED_HBARS; import static com.hedera.services.bdd.suites.contract.Utils.asAddress; import static com.hedera.services.bdd.suites.contract.Utils.getNestedContractAddress; import static com.hedera.services.bdd.suites.utils.contracts.precompile.HTSPrecompileResult.htsPrecompileResult; @@ -56,22 +60,15 @@ import com.esaulpaugh.headlong.abi.Address; import com.hedera.services.bdd.junit.HapiTest; -import com.hedera.services.bdd.junit.HapiTestSuite; -import com.hedera.services.bdd.spec.HapiSpec; import com.hedera.services.bdd.spec.transactions.contract.HapiParserUtil; -import com.hedera.services.bdd.suites.HapiSuite; import com.hederahashgraph.api.proto.java.TokenType; import java.util.List; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; +import java.util.stream.Stream; +import org.junit.jupiter.api.DynamicTest; import org.junit.jupiter.api.Tag; -@HapiTestSuite(fuzzyMatch = true) @Tag(SMART_CONTRACT) -public class ContractHTSSuite extends HapiSuite { - - private static final Logger log = LogManager.getLogger(ContractHTSSuite.class); - +public class ContractHTSSuite { public static final String VERSATILE_TRANSFERS_CONTRACT = "VersatileTransfers"; public static final String TOKEN_TRANSFERS_CONTRACT = "TokenTransferContract"; public static final String FEE_DISTRIBUTOR_CONTRACT = "FeeDistributor"; @@ -95,35 +92,8 @@ public class ContractHTSSuite extends HapiSuite { private static final String UNIVERSAL_KEY = "multipurpose"; - public static void main(String... args) { - new ContractHTSSuite().runSuiteAsync(); - } - - @Override - public boolean canRunConcurrent() { - return true; - } - - @Override - public List getSpecsInSuite() { - return allOf(positiveSpecs(), negativeSpecs()); - } - - List negativeSpecs() { - return List.of( - nonZeroTransfersFail(), - shouldFailWhenTransferringTokensWithInvalidParametersAndConditions(), - shouldFailOnInvalidTokenTransferParametersAndConditions(), - shouldFailWhenTransferringMultipleNFTsWithInvalidParametersAndConditions(), - shouldFailOnInvalidTokenTransferParametersAndConditions()); - } - - List positiveSpecs() { - return List.of(); - } - @HapiTest - final HapiSpec nonZeroTransfersFail() { + final Stream nonZeroTransfersFail() { final var theSecondReceiver = "somebody2"; return defaultHapiSpec( "NonZeroTransfersFail", @@ -141,12 +111,19 @@ final HapiSpec nonZeroTransfersFail() { .initialSupply(TOTAL_SUPPLY) .treasury(TOKEN_TREASURY), uploadInitCode(VERSATILE_TRANSFERS_CONTRACT, FEE_DISTRIBUTOR_CONTRACT), - contractCreate(FEE_DISTRIBUTOR_CONTRACT), + // Refusing ethereum create conversion, because we get INVALID_SIGNATURE upon tokenAssociate, + // since we have CONTRACT_ID key + contractCreate(FEE_DISTRIBUTOR_CONTRACT).refusingEthConversion(), withOpContext((spec, opLog) -> allRunFor( spec, contractCreate( - VERSATILE_TRANSFERS_CONTRACT, - asHeadlongAddress(getNestedContractAddress(FEE_DISTRIBUTOR_CONTRACT, spec))))), + VERSATILE_TRANSFERS_CONTRACT, + asHeadlongAddress( + getNestedContractAddress(FEE_DISTRIBUTOR_CONTRACT, spec))) + // Refusing ethereum create conversion, because we get INVALID_SIGNATURE upon + // tokenAssociate, + // since we have CONTRACT_ID key + .refusingEthConversion())), tokenAssociate(ACCOUNT, List.of(A_TOKEN)), tokenAssociate(VERSATILE_TRANSFERS_CONTRACT, List.of(A_TOKEN)), tokenAssociate(RECEIVER, List.of(A_TOKEN)), @@ -186,7 +163,7 @@ final HapiSpec nonZeroTransfersFail() { } @HapiTest - final HapiSpec shouldFailWhenTransferringTokensWithInvalidParametersAndConditions() { + final Stream shouldFailWhenTransferringTokensWithInvalidParametersAndConditions() { final var TXN_WITH_EMPTY_AMOUNTS_ARRAY = "TXN_WITH_EMPTY_AMOUNTS_ARRAY"; final var TXN_WITH_EMPTY_ACCOUNTS_ARRAY = "TXN_WITH_EMPTY_ACCOUNTS_ARRAY"; final var TXN_WITH_NOT_LENGTH_MATCHING_ACCOUNTS_AND_AMOUNTS = @@ -322,7 +299,7 @@ final HapiSpec shouldFailWhenTransferringTokensWithInvalidParametersAndCondition } @HapiTest - final HapiSpec shouldFailOnInvalidTokenTransferParametersAndConditions() { + final Stream shouldFailOnInvalidTokenTransferParametersAndConditions() { final var TXN_WITH_INVALID_TOKEN_ADDRESS = "TXN_WITH_INVALID_TOKEN_ADDRESS"; final var TXN_WITH_INVALID_RECEIVER_ADDRESS = "TXN_WITH_INVALID_RECEIVER_ADDRESS"; final var TXN_WITH_INVALID_SENDER_ADDRESS = "TXN_WITH_INVALID_SENDER_ADDRESS"; @@ -440,7 +417,7 @@ final HapiSpec shouldFailOnInvalidTokenTransferParametersAndConditions() { } @HapiTest - final HapiSpec shouldFailWhenTransferringMultipleNFTsWithInvalidParametersAndConditions() { + final Stream shouldFailWhenTransferringMultipleNFTsWithInvalidParametersAndConditions() { final var TXN_WITH_INVALID_TOKEN_ADDRESS = "TXN_WITH_INVALID_TOKEN_ADDRESS"; final var TXN_WITH_EMPTY_SENDER_ARRAY = "TXN_WITH_EMPTY_SENDER_ARRAY"; final var TXN_WITH_EMPTY_RECEIVER_ARRAY = "TXN_WITH_EMPTY_RECEIVER_ARRAY"; @@ -598,7 +575,7 @@ final HapiSpec shouldFailWhenTransferringMultipleNFTsWithInvalidParametersAndCon } @HapiTest - final HapiSpec shouldFailOnInvalidNFTTransferParametersAndConditions() { + final Stream shouldFailOnInvalidNFTTransferParametersAndConditions() { final var TXN_WITH_INVALID_TOKEN_ADDRESS = "TXN_WITH_INVALID_TOKEN_ADDRESS"; final var TXN_WITH_INVALID_RECEIVER_ADDRESS = "TXN_WITH_INVALID_RECEIVER_ADDRESS"; final var TXN_WITH_INVALID_SENDER_ADDRESS = "TXN_WITH_INVALID_SENDER_ADDRESS"; @@ -725,9 +702,4 @@ final HapiSpec shouldFailOnInvalidNFTTransferParametersAndConditions() { CONTRACT_REVERT_EXECUTED, recordWith().status(SPENDER_DOES_NOT_HAVE_ALLOWANCE))); } - - @Override - protected Logger getResultsLogger() { - return log; - } } diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/precompile/ContractHTSV1SecurityModelSuite.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/precompile/ContractHTSV1SecurityModelSuite.java index 6c6af1583ce1..d216b7785a97 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/precompile/ContractHTSV1SecurityModelSuite.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/precompile/ContractHTSV1SecurityModelSuite.java @@ -64,7 +64,6 @@ import static com.hederahashgraph.api.proto.java.TokenType.NON_FUNGIBLE_UNIQUE; import com.esaulpaugh.headlong.abi.Address; -import com.hedera.services.bdd.spec.HapiSpec; import com.hedera.services.bdd.spec.assertions.AccountInfoAsserts; import com.hedera.services.bdd.spec.assertions.ContractInfoAsserts; import com.hedera.services.bdd.spec.assertions.NonFungibleTransfers; @@ -78,8 +77,10 @@ import java.math.BigInteger; import java.util.List; import java.util.concurrent.atomic.AtomicReference; +import java.util.stream.Stream; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; +import org.junit.jupiter.api.DynamicTest; @SuppressWarnings("java:S1192") // "string literal should not be duplicated" - this rule makes test suites worse public class ContractHTSV1SecurityModelSuite extends HapiSuite { @@ -117,15 +118,15 @@ public boolean canRunConcurrent() { } @Override - public List getSpecsInSuite() { + public List> getSpecsInSuite() { return allOf(positiveSpecs(), negativeSpecs()); } - List negativeSpecs() { + List> negativeSpecs() { return List.of(hscsPrec017RollbackAfterInsufficientBalance()); } - List positiveSpecs() { + List> positiveSpecs() { return List.of( distributeMultipleTokens(), depositAndWithdrawFungibleTokens(), @@ -136,7 +137,7 @@ List positiveSpecs() { hbarTransferFromFeeCollector()); } - final HapiSpec hscsPrec017RollbackAfterInsufficientBalance() { + final Stream hscsPrec017RollbackAfterInsufficientBalance() { final var alice = "alice"; final var bob = "bob"; final var treasuryForToken = "treasuryForToken"; @@ -217,7 +218,7 @@ final HapiSpec hscsPrec017RollbackAfterInsufficientBalance() { .has(AccountInfoAsserts.accountWith().balance(0L))); } - final HapiSpec depositAndWithdrawFungibleTokens() { + final Stream depositAndWithdrawFungibleTokens() { final var theContract = "ZenosBank"; return propertyPreservingHapiSpec("depositAndWithdrawFungibleTokens") @@ -296,7 +297,7 @@ final HapiSpec depositAndWithdrawFungibleTokens() { .including(A_TOKEN, RECEIVER, 25L)))); } - final HapiSpec distributeMultipleTokens() { + final Stream distributeMultipleTokens() { final var theSecondReceiver = "somebody2"; return propertyPreservingHapiSpec("distributeMultipleTokens") @@ -366,7 +367,7 @@ final HapiSpec distributeMultipleTokens() { .including(A_TOKEN, theSecondReceiver, 5L)))); } - final HapiSpec tokenTransferFromFeeCollector() { + final Stream tokenTransferFromFeeCollector() { return propertyPreservingHapiSpec("tokenTransferFromFeeCollector") .preserving(CONTRACTS_ALLOW_SYSTEM_USE_OF_HAPI_SIGS, CONTRACTS_MAX_NUM_WITH_HAPI_SIGS_ACCESS) .given( @@ -521,7 +522,7 @@ final HapiSpec tokenTransferFromFeeCollector() { getAccountBalance(FEE_COLLECTOR).hasTokenBalance(FEE_TOKEN, 0)); } - final HapiSpec tokenTransferFromFeeCollectorStaticNestedCall() { + final Stream tokenTransferFromFeeCollectorStaticNestedCall() { return propertyPreservingHapiSpec("tokenTransferFromFeeCollectorStaticNestedCall") .preserving(CONTRACTS_ALLOW_SYSTEM_USE_OF_HAPI_SIGS, CONTRACTS_MAX_NUM_WITH_HAPI_SIGS_ACCESS) .given( @@ -681,7 +682,7 @@ final HapiSpec tokenTransferFromFeeCollectorStaticNestedCall() { * Contract that otherwise wouldn't have enough balance for a transfer of hbars can perform the transfer after * collecting the custom hbar fees from a nested token transfer through the HTS precompile * */ - final HapiSpec hbarTransferFromFeeCollector() { + final Stream hbarTransferFromFeeCollector() { final var outerContract = "HbarFeeCollector"; final var innerContract = "NestedHTSTransferrer"; @@ -761,7 +762,7 @@ final HapiSpec hbarTransferFromFeeCollector() { getAccountBalance(SECOND_RECEIVER).hasTinyBars(CUSTOM_HBAR_FEE_AMOUNT)); } - final HapiSpec transferNft() { + final Stream transferNft() { return propertyPreservingHapiSpec("transferNft") .preserving(CONTRACTS_ALLOW_SYSTEM_USE_OF_HAPI_SIGS, CONTRACTS_MAX_NUM_WITH_HAPI_SIGS_ACCESS) .given( @@ -832,7 +833,7 @@ final HapiSpec transferNft() { .including(NFT, ACCOUNT, RECEIVER, 1L)))); } - final HapiSpec transferMultipleNfts() { + final Stream transferMultipleNfts() { return propertyPreservingHapiSpec("transferMultipleNfts") .preserving(CONTRACTS_ALLOW_SYSTEM_USE_OF_HAPI_SIGS, CONTRACTS_MAX_NUM_WITH_HAPI_SIGS_ACCESS) .given( diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/precompile/ContractKeysHTSSuite.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/precompile/ContractKeysHTSSuite.java index 2562d63d0bfc..36e41bc82a0a 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/precompile/ContractKeysHTSSuite.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/precompile/ContractKeysHTSSuite.java @@ -57,6 +57,10 @@ import static com.hedera.services.bdd.spec.utilops.records.SnapshotMatchMode.NONDETERMINISTIC_FUNCTION_PARAMETERS; import static com.hedera.services.bdd.spec.utilops.records.SnapshotMatchMode.NONDETERMINISTIC_NONCE; import static com.hedera.services.bdd.spec.utilops.records.SnapshotMatchMode.NONDETERMINISTIC_TRANSACTION_FEES; +import static com.hedera.services.bdd.suites.HapiSuite.GENESIS; +import static com.hedera.services.bdd.suites.HapiSuite.ONE_HBAR; +import static com.hedera.services.bdd.suites.HapiSuite.ONE_HUNDRED_HBARS; +import static com.hedera.services.bdd.suites.HapiSuite.ONE_MILLION_HBARS; import static com.hedera.services.bdd.suites.contract.Utils.asAddress; import static com.hedera.services.bdd.suites.contract.Utils.getNestedContractAddress; import static com.hedera.services.bdd.suites.token.TokenAssociationSpecs.VANILLA_TOKEN; @@ -78,12 +82,9 @@ import com.hedera.node.app.hapi.utils.contracts.ParsingConstants.FunctionType; import com.hedera.services.bdd.junit.HapiTest; -import com.hedera.services.bdd.junit.HapiTestSuite; -import com.hedera.services.bdd.spec.HapiSpec; import com.hedera.services.bdd.spec.assertions.NonFungibleTransfers; import com.hedera.services.bdd.spec.keys.KeyShape; import com.hedera.services.bdd.spec.transactions.contract.HapiParserUtil; -import com.hedera.services.bdd.suites.HapiSuite; import com.hederahashgraph.api.proto.java.AccountID; import com.hederahashgraph.api.proto.java.ResponseCodeEnum; import com.hederahashgraph.api.proto.java.TokenID; @@ -93,13 +94,14 @@ import java.util.List; import java.util.concurrent.atomic.AtomicLong; import java.util.concurrent.atomic.AtomicReference; +import java.util.stream.Stream; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; +import org.junit.jupiter.api.DynamicTest; import org.junit.jupiter.api.Tag; -@HapiTestSuite @Tag(SMART_CONTRACT) -public class ContractKeysHTSSuite extends HapiSuite { +public class ContractKeysHTSSuite { private static final long GAS_TO_OFFER = 1_500_000L; @@ -152,82 +154,8 @@ public class ContractKeysHTSSuite extends HapiSuite { private static final String ACCOUNT_NAME = "anybody"; private static final String TYPE_OF_TOKEN = "fungibleToken"; - public static void main(String... args) { - new ContractKeysHTSSuite().runSuiteAsync(); - } - - @Override - public boolean canRunConcurrent() { - return true; - } - - @Override - public List getSpecsInSuite() { - return allOf(hscsKey1(), hscsKey2(), hscsKey3(), hscsKey4(), hscsKey5(), hscsKey6()); - } - - List hscsKey1() { - return List.of( - callForMintWithContractKey(), - callForTransferWithContractKey(), - callForAssociateWithContractKey(), - callForDissociateWithContractKey(), - callForBurnWithContractKey(), - delegateCallForAssociatePrecompileSignedWithContractKeyFails(), - delegateCallForDissociatePrecompileSignedWithContractKeyFails()); - } - - List hscsKey2() { - return List.of( - staticCallForTransferWithContractKey(), - staticCallForBurnWithContractKey(), - staticCallForMintWithContractKey(), - delegateCallForBurnWithContractKey(), - delegateCallForMintWithContractKey(), - staticCallForDissociatePrecompileFails()); - } - - List hscsKey3() { - return List.of( - callForMintWithDelegateContractKey(), - callForTransferWithDelegateContractKey(), - callForAssociateWithDelegateContractKey(), - callForDissociateWithDelegateContractKey(), - callForBurnWithDelegateContractKey(), - delegateCallForAssociatePrecompileSignedWithDelegateContractKeyWorks(), - delegateCallForDissociatePrecompileSignedWithDelegateContractKeyWorks()); - } - - List hscsKey4() { - return List.of( - associatePrecompileWithDelegateContractKeyForFungibleVanilla(), - associatePrecompileWithDelegateContractKeyForFungibleFrozen(), - associatePrecompileWithDelegateContractKeyForFungibleWithKYC(), - associatePrecompileWithDelegateContractKeyForNonFungibleVanilla(), - associatePrecompileWithDelegateContractKeyForNonFungibleFrozen(), - associatePrecompileWithDelegateContractKeyForNonFungibleWithKYC(), - dissociatePrecompileWithDelegateContractKeyForFungibleVanilla(), - dissociatePrecompileWithDelegateContractKeyForFungibleFrozen(), - dissociatePrecompileWithDelegateContractKeyForFungibleWithKYC(), - dissociatePrecompileWithDelegateContractKeyForNonFungibleVanilla(), - dissociatePrecompileWithDelegateContractKeyForNonFungibleFrozen(), - dissociatePrecompileWithDelegateContractKeyForNonFungibleWithKYC()); - } - - List hscsKey5() { - return List.of( - staticCallForTransferWithDelegateContractKey(), - staticCallForBurnWithDelegateContractKey(), - staticCallForMintWithDelegateContractKey(), - staticCallForAssociatePrecompileFails()); - } - - List hscsKey6() { - return List.of(burnWithKeyAsPartOf1OfXThreshold()); - } - @HapiTest - final HapiSpec burnWithKeyAsPartOf1OfXThreshold() { + final Stream burnWithKeyAsPartOf1OfXThreshold() { final var delegateContractKeyShape = KeyShape.threshOf(1, SIMPLE, DELEGATE_CONTRACT); final var contractKeyShape = KeyShape.threshOf(1, SIMPLE, KeyShape.CONTRACT); @@ -293,7 +221,7 @@ final HapiSpec burnWithKeyAsPartOf1OfXThreshold() { } @HapiTest - final HapiSpec delegateCallForBurnWithContractKey() { + final Stream delegateCallForBurnWithContractKey() { final AtomicReference vanillaTokenTokenID = new AtomicReference<>(); return defaultHapiSpec( @@ -345,7 +273,7 @@ OUTER_CONTRACT, asHeadlongAddress(getNestedContractAddress(NESTED_CONTRACT, spec } @HapiTest - final HapiSpec delegateCallForMintWithContractKey() { + final Stream delegateCallForMintWithContractKey() { final AtomicReference vanillaTokenTokenID = new AtomicReference<>(); return defaultHapiSpec( @@ -395,7 +323,7 @@ OUTER_CONTRACT, asHeadlongAddress(getNestedContractAddress(NESTED_CONTRACT, spec } @HapiTest - final HapiSpec staticCallForDissociatePrecompileFails() { + final Stream staticCallForDissociatePrecompileFails() { final var outerContract = NESTED_ASSOCIATE_DISSOCIATE; final var nestedContract = ASSOCIATE_DISSOCIATE_CONTRACT; final AtomicReference accountID = new AtomicReference<>(); @@ -435,7 +363,7 @@ outerContract, asHeadlongAddress(getNestedContractAddress(nestedContract, spec)) } @HapiTest - final HapiSpec staticCallForTransferWithContractKey() { + final Stream staticCallForTransferWithContractKey() { final var outerContract = STATIC_CONTRACT; final AtomicReference accountID = new AtomicReference<>(); final AtomicReference vanillaTokenTokenID = new AtomicReference<>(); @@ -459,7 +387,9 @@ final HapiSpec staticCallForTransferWithContractKey() { cryptoCreate(ACCOUNT).exposingCreatedIdTo(accountID::set), cryptoCreate(RECEIVER).exposingCreatedIdTo(receiverID::set), uploadInitCode(outerContract, NESTED_CONTRACT), - contractCreate(NESTED_CONTRACT), + // Refusing ethereum create conversion, because we get INVALID_SIGNATURE upon tokenAssociate, + // since we have CONTRACT_ID key + contractCreate(NESTED_CONTRACT).refusingEthConversion(), tokenAssociate(NESTED_CONTRACT, VANILLA_TOKEN), tokenAssociate(ACCOUNT, VANILLA_TOKEN), tokenAssociate(RECEIVER, VANILLA_TOKEN), @@ -468,7 +398,12 @@ final HapiSpec staticCallForTransferWithContractKey() { .when(withOpContext((spec, opLog) -> allRunFor( spec, contractCreate( - outerContract, asHeadlongAddress(getNestedContractAddress(NESTED_CONTRACT, spec))), + outerContract, + asHeadlongAddress(getNestedContractAddress(NESTED_CONTRACT, spec))) + // Refusing ethereum create conversion, because we get INVALID_SIGNATURE upon + // tokenAssociate, + // since we have CONTRACT_ID key + .refusingEthConversion(), tokenAssociate(outerContract, VANILLA_TOKEN), newKeyNamed(CONTRACT_KEY).shape(CONTRACT_KEY_SHAPE.signedWith(sigs(ON, outerContract))), cryptoUpdate(ACCOUNT).key(CONTRACT_KEY), @@ -487,7 +422,7 @@ outerContract, asHeadlongAddress(getNestedContractAddress(NESTED_CONTRACT, spec) } @HapiTest - final HapiSpec staticCallForBurnWithContractKey() { + final Stream staticCallForBurnWithContractKey() { final var outerContract = STATIC_CONTRACT; final AtomicReference vanillaTokenTokenID = new AtomicReference<>(); @@ -530,7 +465,7 @@ outerContract, asHeadlongAddress(getNestedContractAddress(NESTED_CONTRACT, spec) } @HapiTest - final HapiSpec staticCallForMintWithContractKey() { + final Stream staticCallForMintWithContractKey() { final var outerContract = STATIC_CONTRACT; final AtomicReference vanillaTokenTokenID = new AtomicReference<>(); @@ -570,7 +505,7 @@ outerContract, asHeadlongAddress(getNestedContractAddress(NESTED_CONTRACT, spec) } @HapiTest - final HapiSpec staticCallForTransferWithDelegateContractKey() { + final Stream staticCallForTransferWithDelegateContractKey() { final var outerContract = STATIC_CONTRACT; final AtomicReference accountID = new AtomicReference<>(); final AtomicReference vanillaTokenTokenID = new AtomicReference<>(); @@ -594,7 +529,9 @@ final HapiSpec staticCallForTransferWithDelegateContractKey() { cryptoCreate(ACCOUNT).exposingCreatedIdTo(accountID::set), cryptoCreate(RECEIVER).exposingCreatedIdTo(receiverID::set), uploadInitCode(outerContract, NESTED_CONTRACT), - contractCreate(NESTED_CONTRACT), + // Refusing ethereum create conversion, because we get INVALID_SIGNATURE upon tokenAssociate, + // since we have CONTRACT_ID key + contractCreate(NESTED_CONTRACT).refusingEthConversion(), tokenAssociate(NESTED_CONTRACT, VANILLA_TOKEN), tokenAssociate(ACCOUNT, VANILLA_TOKEN), tokenAssociate(RECEIVER, VANILLA_TOKEN), @@ -603,7 +540,12 @@ final HapiSpec staticCallForTransferWithDelegateContractKey() { .when(withOpContext((spec, opLog) -> allRunFor( spec, contractCreate( - outerContract, asHeadlongAddress(getNestedContractAddress(NESTED_CONTRACT, spec))), + outerContract, + asHeadlongAddress(getNestedContractAddress(NESTED_CONTRACT, spec))) + // Refusing ethereum create conversion, because we get INVALID_SIGNATURE upon + // tokenAssociate, + // since we have CONTRACT_ID key + .refusingEthConversion(), tokenAssociate(outerContract, VANILLA_TOKEN), newKeyNamed(DELEGATE_KEY) .shape(DELEGATE_CONTRACT_KEY_SHAPE.signedWith(sigs(ON, outerContract))), @@ -623,7 +565,7 @@ outerContract, asHeadlongAddress(getNestedContractAddress(NESTED_CONTRACT, spec) } @HapiTest - final HapiSpec staticCallForBurnWithDelegateContractKey() { + final Stream staticCallForBurnWithDelegateContractKey() { final var outerContract = STATIC_CONTRACT; final AtomicReference vanillaTokenTokenID = new AtomicReference<>(); @@ -669,7 +611,7 @@ outerContract, asHeadlongAddress(getNestedContractAddress(NESTED_CONTRACT, spec) } @HapiTest - final HapiSpec staticCallForMintWithDelegateContractKey() { + final Stream staticCallForMintWithDelegateContractKey() { final var outerContract = STATIC_CONTRACT; final AtomicReference vanillaTokenTokenID = new AtomicReference<>(); @@ -711,7 +653,7 @@ outerContract, asHeadlongAddress(getNestedContractAddress(NESTED_CONTRACT, spec) } @HapiTest - final HapiSpec staticCallForAssociatePrecompileFails() { + final Stream staticCallForAssociatePrecompileFails() { final var outerContract = NESTED_ASSOCIATE_DISSOCIATE; final var nestedContract = ASSOCIATE_DISSOCIATE_CONTRACT; final AtomicReference accountID = new AtomicReference<>(); @@ -753,7 +695,7 @@ outerContract, asHeadlongAddress(getNestedContractAddress(nestedContract, spec)) } @HapiTest - final HapiSpec callForMintWithContractKey() { + final Stream callForMintWithContractKey() { final var firstMintTxn = "firstMintTxn"; final var amount = 10L; @@ -807,7 +749,7 @@ final HapiSpec callForMintWithContractKey() { } @HapiTest - final HapiSpec callForMintWithDelegateContractKey() { + final Stream callForMintWithDelegateContractKey() { final var firstMintTxn = "firstMintTxn"; final var amount = 10L; @@ -864,7 +806,7 @@ final HapiSpec callForMintWithDelegateContractKey() { } @HapiTest - final HapiSpec callForTransferWithContractKey() { + final Stream callForTransferWithContractKey() { return defaultHapiSpec( "callForTransferWithContractKey", NONDETERMINISTIC_FUNCTION_PARAMETERS, @@ -883,7 +825,12 @@ final HapiSpec callForTransferWithContractKey() { tokenAssociate(ACCOUNT, NFT), mintToken(NFT, List.of(metadata("firstMemo"), metadata("secondMemo"))), uploadInitCode(ORDINARY_CALLS_CONTRACT), - contractCreate(ORDINARY_CALLS_CONTRACT).via(CREATION_TX)) + contractCreate(ORDINARY_CALLS_CONTRACT) + // Refusing ethereum create conversion, because we get INVALID_SIGNATURE upon + // tokenAssociate, + // since we have CONTRACT_ID key + .refusingEthConversion() + .via(CREATION_TX)) .when(withOpContext((spec, opLog) -> allRunFor( spec, newKeyNamed(CONTRACT_KEY) @@ -926,7 +873,7 @@ final HapiSpec callForTransferWithContractKey() { } @HapiTest - final HapiSpec callForTransferWithDelegateContractKey() { + final Stream callForTransferWithDelegateContractKey() { return defaultHapiSpec( "callForTransferWithDelegateContractKey", NONDETERMINISTIC_FUNCTION_PARAMETERS, @@ -945,7 +892,9 @@ final HapiSpec callForTransferWithDelegateContractKey() { tokenAssociate(ACCOUNT, NFT), mintToken(NFT, List.of(metadata("firstMemo"), metadata("secondMemo"))), uploadInitCode(ORDINARY_CALLS_CONTRACT), - contractCreate(ORDINARY_CALLS_CONTRACT)) + // Refusing ethereum create conversion, because we get INVALID_SIGNATURE upon tokenAssociate, + // since we have CONTRACT_ID key + contractCreate(ORDINARY_CALLS_CONTRACT).refusingEthConversion()) .when(withOpContext((spec, opLog) -> allRunFor( spec, newKeyNamed(DELEGATE_KEY) @@ -988,7 +937,7 @@ final HapiSpec callForTransferWithDelegateContractKey() { } @HapiTest - final HapiSpec callForAssociateWithDelegateContractKey() { + final Stream callForAssociateWithDelegateContractKey() { final AtomicReference accountID = new AtomicReference<>(); final AtomicReference vanillaTokenID = new AtomicReference<>(); @@ -1032,7 +981,7 @@ final HapiSpec callForAssociateWithDelegateContractKey() { } @HapiTest - final HapiSpec callForAssociateWithContractKey() { + final Stream callForAssociateWithContractKey() { final AtomicReference accountID = new AtomicReference<>(); final AtomicReference vanillaTokenID = new AtomicReference<>(); @@ -1077,7 +1026,7 @@ final HapiSpec callForAssociateWithContractKey() { } @HapiTest - public HapiSpec callForDissociateWithDelegateContractKey() { + final Stream callForDissociateWithDelegateContractKey() { final AtomicReference accountID = new AtomicReference<>(); final AtomicReference treasuryID = new AtomicReference<>(); final AtomicReference vanillaTokenID = new AtomicReference<>(); @@ -1146,7 +1095,7 @@ public HapiSpec callForDissociateWithDelegateContractKey() { } @HapiTest - public HapiSpec callForDissociateWithContractKey() { + final Stream callForDissociateWithContractKey() { final AtomicReference accountID = new AtomicReference<>(); final AtomicReference treasuryID = new AtomicReference<>(); final AtomicReference vanillaTokenID = new AtomicReference<>(); @@ -1215,7 +1164,7 @@ public HapiSpec callForDissociateWithContractKey() { } @HapiTest - final HapiSpec callForBurnWithDelegateContractKey() { + final Stream callForBurnWithDelegateContractKey() { return defaultHapiSpec( "callForBurnWithDelegateContractKey", NONDETERMINISTIC_CONSTRUCTOR_PARAMETERS, @@ -1261,7 +1210,7 @@ final HapiSpec callForBurnWithDelegateContractKey() { } @HapiTest - final HapiSpec delegateCallForAssociatePrecompileSignedWithDelegateContractKeyWorks() { + final Stream delegateCallForAssociatePrecompileSignedWithDelegateContractKeyWorks() { final var outerContract = NESTED_ASSOCIATE_DISSOCIATE; final var nestedContract = ASSOCIATE_DISSOCIATE_CONTRACT; final AtomicReference accountID = new AtomicReference<>(); @@ -1310,7 +1259,7 @@ outerContract, asHeadlongAddress(getNestedContractAddress(nestedContract, spec)) } @HapiTest - final HapiSpec delegateCallForDissociatePrecompileSignedWithDelegateContractKeyWorks() { + final Stream delegateCallForDissociatePrecompileSignedWithDelegateContractKeyWorks() { final var outerContract = NESTED_ASSOCIATE_DISSOCIATE; final var nestedContract = ASSOCIATE_DISSOCIATE_CONTRACT; final AtomicReference accountID = new AtomicReference<>(); @@ -1360,7 +1309,7 @@ outerContract, asHeadlongAddress(getNestedContractAddress(nestedContract, spec)) } @HapiTest - final HapiSpec associatePrecompileWithDelegateContractKeyForNonFungibleWithKYC() { + final Stream associatePrecompileWithDelegateContractKeyForNonFungibleWithKYC() { final AtomicReference accountID = new AtomicReference<>(); final AtomicReference kycTokenID = new AtomicReference<>(); @@ -1443,7 +1392,7 @@ final HapiSpec associatePrecompileWithDelegateContractKeyForNonFungibleWithKYC() } @HapiTest - public HapiSpec dissociatePrecompileWithDelegateContractKeyForFungibleVanilla() { + final Stream dissociatePrecompileWithDelegateContractKeyForFungibleVanilla() { final AtomicReference accountID = new AtomicReference<>(); final AtomicReference treasuryID = new AtomicReference<>(); final AtomicReference vanillaTokenID = new AtomicReference<>(); @@ -1544,7 +1493,7 @@ public HapiSpec dissociatePrecompileWithDelegateContractKeyForFungibleVanilla() } @HapiTest - public HapiSpec dissociatePrecompileWithDelegateContractKeyForFungibleFrozen() { + final Stream dissociatePrecompileWithDelegateContractKeyForFungibleFrozen() { final AtomicReference accountID = new AtomicReference<>(); final AtomicReference treasuryID = new AtomicReference<>(); final AtomicReference frozenTokenID = new AtomicReference<>(); @@ -1612,7 +1561,7 @@ public HapiSpec dissociatePrecompileWithDelegateContractKeyForFungibleFrozen() { } @HapiTest - public HapiSpec dissociatePrecompileWithDelegateContractKeyForFungibleWithKYC() { + final Stream dissociatePrecompileWithDelegateContractKeyForFungibleWithKYC() { final AtomicReference accountID = new AtomicReference<>(); final AtomicReference treasuryID = new AtomicReference<>(); final AtomicReference kycTokenID = new AtomicReference<>(); @@ -1677,7 +1626,7 @@ public HapiSpec dissociatePrecompileWithDelegateContractKeyForFungibleWithKYC() } @HapiTest - public HapiSpec dissociatePrecompileWithDelegateContractKeyForNonFungibleVanilla() { + final Stream dissociatePrecompileWithDelegateContractKeyForNonFungibleVanilla() { final AtomicReference accountID = new AtomicReference<>(); final AtomicReference treasuryID = new AtomicReference<>(); final AtomicReference vanillaTokenID = new AtomicReference<>(); @@ -1782,7 +1731,7 @@ public HapiSpec dissociatePrecompileWithDelegateContractKeyForNonFungibleVanilla } @HapiTest - public HapiSpec dissociatePrecompileWithDelegateContractKeyForNonFungibleFrozen() { + final Stream dissociatePrecompileWithDelegateContractKeyForNonFungibleFrozen() { final AtomicReference accountID = new AtomicReference<>(); final AtomicReference treasuryID = new AtomicReference<>(); final AtomicReference frozenTokenID = new AtomicReference<>(); @@ -1852,7 +1801,7 @@ public HapiSpec dissociatePrecompileWithDelegateContractKeyForNonFungibleFrozen( } @HapiTest - public HapiSpec dissociatePrecompileWithDelegateContractKeyForNonFungibleWithKYC() { + final Stream dissociatePrecompileWithDelegateContractKeyForNonFungibleWithKYC() { final AtomicReference accountID = new AtomicReference<>(); final AtomicReference treasuryID = new AtomicReference<>(); final AtomicReference kycTokenID = new AtomicReference<>(); @@ -1919,7 +1868,7 @@ public HapiSpec dissociatePrecompileWithDelegateContractKeyForNonFungibleWithKYC } @HapiTest - final HapiSpec associatePrecompileWithDelegateContractKeyForNonFungibleFrozen() { + final Stream associatePrecompileWithDelegateContractKeyForNonFungibleFrozen() { final AtomicReference accountID = new AtomicReference<>(); final AtomicReference frozenTokenID = new AtomicReference<>(); @@ -2004,7 +1953,7 @@ final HapiSpec associatePrecompileWithDelegateContractKeyForNonFungibleFrozen() } @HapiTest - final HapiSpec associatePrecompileWithDelegateContractKeyForNonFungibleVanilla() { + final Stream associatePrecompileWithDelegateContractKeyForNonFungibleVanilla() { final AtomicReference accountID = new AtomicReference<>(); final AtomicReference vanillaTokenID = new AtomicReference<>(); @@ -2085,7 +2034,7 @@ final HapiSpec associatePrecompileWithDelegateContractKeyForNonFungibleVanilla() } @HapiTest - final HapiSpec associatePrecompileWithDelegateContractKeyForFungibleWithKYC() { + final Stream associatePrecompileWithDelegateContractKeyForFungibleWithKYC() { final AtomicReference accountID = new AtomicReference<>(); final AtomicReference kycTokenID = new AtomicReference<>(); @@ -2167,7 +2116,7 @@ final HapiSpec associatePrecompileWithDelegateContractKeyForFungibleWithKYC() { } @HapiTest - final HapiSpec associatePrecompileWithDelegateContractKeyForFungibleFrozen() { + final Stream associatePrecompileWithDelegateContractKeyForFungibleFrozen() { final AtomicReference accountID = new AtomicReference<>(); final AtomicReference frozenTokenID = new AtomicReference<>(); @@ -2250,7 +2199,7 @@ final HapiSpec associatePrecompileWithDelegateContractKeyForFungibleFrozen() { } @HapiTest - final HapiSpec associatePrecompileWithDelegateContractKeyForFungibleVanilla() { + final Stream associatePrecompileWithDelegateContractKeyForFungibleVanilla() { final AtomicReference accountID = new AtomicReference<>(); final AtomicReference vanillaTokenID = new AtomicReference<>(); @@ -2328,7 +2277,7 @@ final HapiSpec associatePrecompileWithDelegateContractKeyForFungibleVanilla() { } @HapiTest - final HapiSpec delegateCallForAssociatePrecompileSignedWithContractKeyFails() { + final Stream delegateCallForAssociatePrecompileSignedWithContractKeyFails() { final var outerContract = NESTED_ASSOCIATE_DISSOCIATE; final var nestedContract = ASSOCIATE_DISSOCIATE_CONTRACT; final AtomicReference accountID = new AtomicReference<>(); @@ -2376,7 +2325,7 @@ outerContract, asHeadlongAddress(getNestedContractAddress(nestedContract, spec)) } @HapiTest - final HapiSpec delegateCallForDissociatePrecompileSignedWithContractKeyFails() { + final Stream delegateCallForDissociatePrecompileSignedWithContractKeyFails() { final var outerContract = NESTED_ASSOCIATE_DISSOCIATE; final var nestedContract = ASSOCIATE_DISSOCIATE_CONTRACT; final AtomicReference accountID = new AtomicReference<>(); @@ -2425,7 +2374,7 @@ outerContract, asHeadlongAddress(getNestedContractAddress(nestedContract, spec)) } @HapiTest - final HapiSpec callForBurnWithContractKey() { + final Stream callForBurnWithContractKey() { return defaultHapiSpec( "callForBurnWithContractKey", NONDETERMINISTIC_CONSTRUCTOR_PARAMETERS, @@ -2469,9 +2418,4 @@ final HapiSpec callForBurnWithContractKey() { .newTotalSupply(49))) .then(getAccountBalance(TOKEN_TREASURY).hasTokenBalance(TOKEN_USAGE, 49)); } - - @Override - protected Logger getResultsLogger() { - return log; - } } diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/precompile/ContractKeysHTSV1SecurityModelSuite.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/precompile/ContractKeysHTSV1SecurityModelSuite.java index 4b8101c2d90e..a574367ead00 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/precompile/ContractKeysHTSV1SecurityModelSuite.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/precompile/ContractKeysHTSV1SecurityModelSuite.java @@ -56,7 +56,6 @@ import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.SUCCESS; import com.hedera.node.app.hapi.utils.contracts.ParsingConstants.FunctionType; -import com.hedera.services.bdd.spec.HapiSpec; import com.hedera.services.bdd.spec.keys.KeyShape; import com.hedera.services.bdd.spec.transactions.contract.HapiParserUtil; import com.hedera.services.bdd.suites.HapiSuite; @@ -68,8 +67,10 @@ import java.util.List; import java.util.concurrent.atomic.AtomicLong; import java.util.concurrent.atomic.AtomicReference; +import java.util.stream.Stream; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; +import org.junit.jupiter.api.DynamicTest; public class ContractKeysHTSV1SecurityModelSuite extends HapiSuite { @@ -106,14 +107,14 @@ public boolean canRunConcurrent() { } @Override - public List getSpecsInSuite() { + public List> getSpecsInSuite() { return List.of( delegateCallForTransferWithContractKey(), transferWithKeyAsPartOf2OfXThreshold(), burnTokenWithFullPrefixAndPartialPrefixKeys()); } - final HapiSpec transferWithKeyAsPartOf2OfXThreshold() { + final Stream transferWithKeyAsPartOf2OfXThreshold() { final AtomicReference accountID = new AtomicReference<>(); final AtomicReference vanillaTokenTokenID = new AtomicReference<>(); final AtomicReference receiverID = new AtomicReference<>(); @@ -178,7 +179,7 @@ OUTER_CONTRACT, asHeadlongAddress(getNestedContractAddress(NESTED_CONTRACT, spec getAccountBalance(RECEIVER).hasTokenBalance(VANILLA_TOKEN, 1)); } - final HapiSpec delegateCallForTransferWithContractKey() { + final Stream delegateCallForTransferWithContractKey() { final AtomicReference accountID = new AtomicReference<>(); final AtomicReference vanillaTokenTokenID = new AtomicReference<>(); final AtomicReference receiverID = new AtomicReference<>(); @@ -240,7 +241,7 @@ OUTER_CONTRACT, asHeadlongAddress(getNestedContractAddress(NESTED_CONTRACT, spec getAccountBalance(RECEIVER).hasTokenBalance(VANILLA_TOKEN, 0)); } - final HapiSpec burnTokenWithFullPrefixAndPartialPrefixKeys() { + final Stream burnTokenWithFullPrefixAndPartialPrefixKeys() { final var firstBurnTxn = "firstBurnTxn"; final var secondBurnTxn = "secondBurnTxn"; final var amount = 99L; diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/precompile/ContractKeysStillWorkAsExpectedSuite.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/precompile/ContractKeysStillWorkAsExpectedSuite.java index 93ade57825ba..f6cf2db8ead0 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/precompile/ContractKeysStillWorkAsExpectedSuite.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/precompile/ContractKeysStillWorkAsExpectedSuite.java @@ -53,6 +53,7 @@ import static com.hedera.services.bdd.spec.utilops.records.SnapshotMatchMode.NONDETERMINISTIC_FUNCTION_PARAMETERS; import static com.hedera.services.bdd.spec.utilops.records.SnapshotMatchMode.NONDETERMINISTIC_NONCE; import static com.hedera.services.bdd.spec.utilops.records.SnapshotMatchMode.NONDETERMINISTIC_TRANSACTION_FEES; +import static com.hedera.services.bdd.suites.HapiSuite.ONE_HBAR; import static com.hedera.services.bdd.suites.contract.hapi.ContractCallSuite.PAY_RECEIVABLE_CONTRACT; import static com.hedera.services.bdd.suites.contract.precompile.V1SecurityModelOverrides.CONTRACTS_MAX_NUM_WITH_HAPI_SIGS_ACCESS; import static com.hedera.services.bdd.suites.token.TokenAssociationSpecs.MULTI_KEY; @@ -66,50 +67,21 @@ import com.esaulpaugh.headlong.abi.Address; import com.google.protobuf.ByteString; import com.hedera.services.bdd.junit.HapiTest; -import com.hedera.services.bdd.junit.HapiTestSuite; -import com.hedera.services.bdd.spec.HapiSpec; import com.hedera.services.bdd.spec.HapiSpecOperation; import com.hedera.services.bdd.spec.keys.KeyShape; import com.hedera.services.bdd.spec.transactions.token.TokenMovement; -import com.hedera.services.bdd.suites.HapiSuite; import com.hederahashgraph.api.proto.java.ResponseCodeEnum; import com.hederahashgraph.api.proto.java.TokenType; import java.util.List; import java.util.concurrent.atomic.AtomicReference; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; +import java.util.stream.Stream; +import org.junit.jupiter.api.DynamicTest; import org.junit.jupiter.api.Tag; -@HapiTestSuite(fuzzyMatch = true) @Tag(SMART_CONTRACT) -public class ContractKeysStillWorkAsExpectedSuite extends HapiSuite { - - private static final Logger log = LogManager.getLogger(ContractKeysStillWorkAsExpectedSuite.class); - - public static void main(String... args) { - new ContractKeysStillWorkAsExpectedSuite().runSuiteSync(); - } - - @Override - public boolean canRunConcurrent() { - return false; - } - - @Override - protected Logger getResultsLogger() { - return log; - } - - @Override - public List getSpecsInSuite() { - return List.of( - contractKeysStillHaveSpecificityNoMatterTopLevelSignatures(), - canStillTransferByVirtueOfContractIdInEOAThreshold(), - approvalFallbacksRequiredWithoutTopLevelSigAccess()); - } - +public class ContractKeysStillWorkAsExpectedSuite { @HapiTest - final HapiSpec approvalFallbacksRequiredWithoutTopLevelSigAccess() { + final Stream approvalFallbacksRequiredWithoutTopLevelSigAccess() { final AtomicReference
    fungibleTokenMirrorAddr = new AtomicReference<>(); final AtomicReference
    nonFungibleTokenMirrorAddr = new AtomicReference<>(); final AtomicReference
    aSenderAddr = new AtomicReference<>(); @@ -341,7 +313,7 @@ private HapiSpecOperation someWellKnownOperationsWithAllNeededSigsInSigMap( } @HapiTest - final HapiSpec canStillTransferByVirtueOfContractIdInEOAThreshold() { + final Stream canStillTransferByVirtueOfContractIdInEOAThreshold() { final var fungibleToken = "token"; final var managementContract = "DoTokenManagement"; final AtomicReference
    tokenMirrorAddr = new AtomicReference<>(); @@ -405,7 +377,7 @@ final HapiSpec canStillTransferByVirtueOfContractIdInEOAThreshold() { } @HapiTest - final HapiSpec contractKeysStillHaveSpecificityNoMatterTopLevelSignatures() { + final Stream contractKeysStillHaveSpecificityNoMatterTopLevelSignatures() { final var fungibleToken = "token"; final var managementContract = "DoTokenManagement"; final var otherContractAsKey = "otherContractAsKey"; diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/precompile/ContractKeysStillWorkAsExpectedV1SecurityModelSuite.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/precompile/ContractKeysStillWorkAsExpectedV1SecurityModelSuite.java index dd4290f06d08..cc94d0494a5d 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/precompile/ContractKeysStillWorkAsExpectedV1SecurityModelSuite.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/precompile/ContractKeysStillWorkAsExpectedV1SecurityModelSuite.java @@ -61,7 +61,6 @@ import com.esaulpaugh.headlong.abi.Address; import com.google.protobuf.ByteString; -import com.hedera.services.bdd.spec.HapiSpec; import com.hedera.services.bdd.spec.HapiSpecOperation; import com.hedera.services.bdd.spec.transactions.token.TokenMovement; import com.hedera.services.bdd.suites.HapiSuite; @@ -70,8 +69,10 @@ import java.util.List; import java.util.Map; import java.util.concurrent.atomic.AtomicReference; +import java.util.stream.Stream; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; +import org.junit.jupiter.api.DynamicTest; public class ContractKeysStillWorkAsExpectedV1SecurityModelSuite extends HapiSuite { @@ -93,7 +94,7 @@ protected Logger getResultsLogger() { } @Override - public List getSpecsInSuite() { + public List> getSpecsInSuite() { return List.of( contractKeysWorkAsExpectedForFungibleTokenMgmt(), topLevelSigsStillWorkWithDefaultGrandfatherNum(), @@ -103,7 +104,7 @@ public List getSpecsInSuite() { fixedFeeFailsWhenDisabledButWorksWhenEnabled()); } - final HapiSpec fixedFeeFailsWhenDisabledButWorksWhenEnabled() { + final Stream fixedFeeFailsWhenDisabledButWorksWhenEnabled() { final AtomicReference
    senderAddr = new AtomicReference<>(); final AtomicReference
    receiverAddr = new AtomicReference<>(); final AtomicReference
    nonFungibleTokenMirrorAddr = new AtomicReference<>(); @@ -176,7 +177,7 @@ final HapiSpec fixedFeeFailsWhenDisabledButWorksWhenEnabled() { .alsoSigningWithFullPrefix(sender))); } - final HapiSpec fallbackFeePayerMustSign() { + final Stream fallbackFeePayerMustSign() { final AtomicReference
    senderAddr = new AtomicReference<>(); final AtomicReference
    receiverAddr = new AtomicReference<>(); final AtomicReference
    nonFungibleTokenMirrorAddr = new AtomicReference<>(); @@ -269,7 +270,7 @@ final HapiSpec fallbackFeePayerMustSign() { getAccountBalance(receiver).hasTinyBars(0L)); } - final HapiSpec fallbackFeeForHtsPayerMustSign() { + final Stream fallbackFeeForHtsPayerMustSign() { final AtomicReference
    senderAddr = new AtomicReference<>(); final AtomicReference
    receiverAddr = new AtomicReference<>(); final AtomicReference
    nonFungibleTokenMirrorAddr = new AtomicReference<>(); @@ -349,7 +350,7 @@ final HapiSpec fallbackFeeForHtsPayerMustSign() { getAccountBalance(receiver).hasTokenBalance(fungible, 9)); } - final HapiSpec contractCanStillTransferItsOwnAssets() { + final Stream contractCanStillTransferItsOwnAssets() { final AtomicReference
    fungibleTokenMirrorAddr = new AtomicReference<>(); final AtomicReference
    nonFungibleTokenMirrorAddr = new AtomicReference<>(); final AtomicReference
    aSenderAddr = new AtomicReference<>(); @@ -409,7 +410,7 @@ final HapiSpec contractCanStillTransferItsOwnAssets() { someWellKnownAssertions()); } - final HapiSpec topLevelSigsStillWorkWithDefaultGrandfatherNum() { + final Stream topLevelSigsStillWorkWithDefaultGrandfatherNum() { final AtomicReference
    fungibleTokenMirrorAddr = new AtomicReference<>(); final AtomicReference
    nonFungibleTokenMirrorAddr = new AtomicReference<>(); final AtomicReference
    aSenderAddr = new AtomicReference<>(); @@ -584,7 +585,7 @@ private HapiSpecOperation someWellKnownOperationsWithAllNeededSigsInSigMap( .hasKnownStatus(expectedStatus))); } - final HapiSpec contractKeysWorkAsExpectedForFungibleTokenMgmt() { + final Stream contractKeysWorkAsExpectedForFungibleTokenMgmt() { final var fungibleToken = "token"; final var managementContract = "DoTokenManagement"; final var mgmtContractAsKey = "mgmtContractAsKey"; diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/precompile/ContractMintHTSSuite.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/precompile/ContractMintHTSSuite.java index 28c85f75fdbb..ec78b5341c66 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/precompile/ContractMintHTSSuite.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/precompile/ContractMintHTSSuite.java @@ -34,6 +34,7 @@ import static com.hedera.services.bdd.spec.transactions.TxnVerbs.cryptoCreate; import static com.hedera.services.bdd.spec.transactions.TxnVerbs.cryptoTransfer; import static com.hedera.services.bdd.spec.transactions.TxnVerbs.cryptoUpdate; +import static com.hedera.services.bdd.spec.transactions.TxnVerbs.mintToken; import static com.hedera.services.bdd.spec.transactions.TxnVerbs.tokenAssociate; import static com.hedera.services.bdd.spec.transactions.TxnVerbs.tokenCreate; import static com.hedera.services.bdd.spec.transactions.TxnVerbs.tokenUpdate; @@ -50,6 +51,9 @@ import static com.hedera.services.bdd.spec.utilops.records.SnapshotMatchMode.NONDETERMINISTIC_CONSTRUCTOR_PARAMETERS; import static com.hedera.services.bdd.spec.utilops.records.SnapshotMatchMode.NONDETERMINISTIC_FUNCTION_PARAMETERS; import static com.hedera.services.bdd.spec.utilops.records.SnapshotMatchMode.NONDETERMINISTIC_TRANSACTION_FEES; +import static com.hedera.services.bdd.suites.HapiSuite.GENESIS; +import static com.hedera.services.bdd.suites.HapiSuite.ONE_HUNDRED_HBARS; +import static com.hedera.services.bdd.suites.HapiSuite.ONE_MILLION_HBARS; import static com.hedera.services.bdd.suites.contract.Utils.asAddress; import static com.hedera.services.bdd.suites.contract.Utils.assertTxnRecordHasNoTraceabilityEnrichedContractFnResult; import static com.hedera.services.bdd.suites.contract.Utils.expectedPrecompileGasFor; @@ -70,27 +74,20 @@ import com.hedera.node.app.hapi.utils.contracts.ParsingConstants.FunctionType; import com.hedera.services.bdd.junit.HapiTest; -import com.hedera.services.bdd.junit.HapiTestSuite; -import com.hedera.services.bdd.spec.HapiSpec; import com.hedera.services.bdd.spec.assertions.NonFungibleTransfers; import com.hedera.services.bdd.spec.keys.KeyShape; import com.hedera.services.bdd.spec.transactions.contract.HapiParserUtil; -import com.hedera.services.bdd.suites.HapiSuite; import com.hedera.services.bdd.suites.utils.contracts.FunctionParameters; import com.hederahashgraph.api.proto.java.AccountID; import com.hederahashgraph.api.proto.java.TokenSupplyType; import com.hederahashgraph.api.proto.java.TokenType; import java.util.List; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; +import java.util.stream.Stream; +import org.junit.jupiter.api.DynamicTest; import org.junit.jupiter.api.Tag; -@HapiTestSuite @Tag(SMART_CONTRACT) -public class ContractMintHTSSuite extends HapiSuite { - - private static final Logger LOG = LogManager.getLogger(ContractMintHTSSuite.class); - +public class ContractMintHTSSuite { private static final long GAS_TO_OFFER = 4_000_000L; private static final long TOTAL_SUPPLY = 1_000; private static final String CONTRACT_KEY = "ContractKey"; @@ -111,35 +108,9 @@ public class ContractMintHTSSuite extends HapiSuite { private static final String TEST_METADATA_1 = "Test metadata 1"; private static final String TEST_METADATA_2 = "Test metadata 2"; private static final String RECIPIENT = "recipient"; - public static final String MINT_FUNGIBLE_TOKEN_WITH_EVENT = "mintFungibleTokenWithEvent"; - - public static void main(final String... args) { - new ContractMintHTSSuite().runSuiteAsync(); - } - - @Override - public boolean canRunConcurrent() { - return true; - } - - @Override - public List getSpecsInSuite() { - return allOf(positiveSpecs(), negativeSpecs()); - } - - List negativeSpecs() { - return List.of( - rollbackOnFailedMintAfterFungibleTransfer(), - mintTokensWithExtremeValues(), - mintTokensWithInvalidValues()); - } - - List positiveSpecs() { - return List.of(transferNftAfterNestedMint()); - } @HapiTest - final HapiSpec mintTokensWithExtremeValues() { + final Stream mintTokensWithExtremeValues() { var mintExtremeValue = "mintExtremeValue"; var mintInvalidAddressType = "mintInvalidAddressType"; @@ -249,7 +220,7 @@ invalidTokenTest, SUCCESS, recordWith().status(INVALID_TOKEN_ID)), } @HapiTest - final HapiSpec mintTokensWithInvalidValues() { + final Stream mintTokensWithInvalidValues() { var mintToken = "mintToken"; var fungibleMintWithMetadataTest = "fungibleMintWithMetadataTest"; @@ -359,7 +330,7 @@ final HapiSpec mintTokensWithInvalidValues() { } @HapiTest - final HapiSpec transferNftAfterNestedMint() { + final Stream transferNftAfterNestedMint() { final var nestedTransferTxn = "nestedTransferTxn"; final var v2SecuritySendNftAfterNestedMint = "v2SecuritySendNftAfterNestedMint"; @@ -489,7 +460,7 @@ final HapiSpec transferNftAfterNestedMint() { @SuppressWarnings("java:S5669") @HapiTest - final HapiSpec rollbackOnFailedMintAfterFungibleTransfer() { + final Stream rollbackOnFailedMintAfterFungibleTransfer() { final var failedMintTxn = "failedMintTxn"; return defaultHapiSpec( @@ -550,9 +521,4 @@ final HapiSpec rollbackOnFailedMintAfterFungibleTransfer() { .withTotalSupply(0L) .withSerialNumbers())))); } - - @Override - protected Logger getResultsLogger() { - return LOG; - } } diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/precompile/ContractMintHTSV1SecurityModelSuite.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/precompile/ContractMintHTSV1SecurityModelSuite.java index 89a4663f0b54..f43ab59fd989 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/precompile/ContractMintHTSV1SecurityModelSuite.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/precompile/ContractMintHTSV1SecurityModelSuite.java @@ -60,7 +60,6 @@ import static com.hederahashgraph.api.proto.java.SubType.TOKEN_FUNGIBLE_COMMON; import com.hedera.node.app.hapi.utils.contracts.ParsingConstants.FunctionType; -import com.hedera.services.bdd.spec.HapiSpec; import com.hedera.services.bdd.spec.keys.KeyShape; import com.hedera.services.bdd.spec.transactions.contract.HapiParserUtil; import com.hedera.services.bdd.suites.HapiSuite; @@ -72,8 +71,10 @@ import java.util.List; import java.util.concurrent.atomic.AtomicLong; import java.util.concurrent.atomic.AtomicReference; +import java.util.stream.Stream; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; +import org.junit.jupiter.api.DynamicTest; @SuppressWarnings("java:S1192") // "string literal should not be duplicated" - this rule makes test suites worse public class ContractMintHTSV1SecurityModelSuite extends HapiSuite { @@ -107,16 +108,16 @@ public static void main(final String... args) { } @Override - public List getSpecsInSuite() { + public List> getSpecsInSuite() { return allOf(positiveSpecs(), negativeSpecs()); } - List negativeSpecs() { + List> negativeSpecs() { return List.of( rollbackOnFailedAssociateAfterNonFungibleMint(), gasCostNotMetSetsInsufficientGasStatusInChildRecord()); } - List positiveSpecs() { + List> positiveSpecs() { return List.of( helloWorldFungibleMint(), helloWorldNftMint(), @@ -125,7 +126,7 @@ List positiveSpecs() { happyPathZeroUnitFungibleTokenMint()); } - final HapiSpec happyPathZeroUnitFungibleTokenMint() { + final Stream happyPathZeroUnitFungibleTokenMint() { final var amount = 0L; final var gasUsed = 14085L; final AtomicReference fungible = new AtomicReference<>(); @@ -169,7 +170,7 @@ final HapiSpec happyPathZeroUnitFungibleTokenMint() { .newTotalSupply(0))); } - final HapiSpec helloWorldFungibleMint() { + final Stream helloWorldFungibleMint() { final var amount = 1_234_567L; final AtomicReference fungible = new AtomicReference<>(); @@ -217,7 +218,7 @@ final HapiSpec helloWorldFungibleMint() { changingFungibleBalances().including(FUNGIBLE_TOKEN, DEFAULT_PAYER, amount)))); } - final HapiSpec helloWorldNftMint() { + final Stream helloWorldNftMint() { final AtomicReference nonFungible = new AtomicReference<>(); return propertyPreservingHapiSpec("helloWorldNftMint") @@ -280,7 +281,7 @@ final HapiSpec helloWorldNftMint() { .serialNos(List.of(2L)))); } - final HapiSpec happyPathFungibleTokenMint() { + final Stream happyPathFungibleTokenMint() { final var amount = 10L; final var gasUsed = 14085L; final AtomicReference fungible = new AtomicReference<>(); @@ -334,7 +335,7 @@ final HapiSpec happyPathFungibleTokenMint() { .newTotalSupply(10))); } - final HapiSpec happyPathNonFungibleTokenMint() { + final Stream happyPathNonFungibleTokenMint() { final var totalSupply = 2; final AtomicReference nonFungible = new AtomicReference<>(); @@ -391,7 +392,7 @@ final HapiSpec happyPathNonFungibleTokenMint() { .serialNos(Arrays.asList(1L, 2L)))); } - final HapiSpec rollbackOnFailedAssociateAfterNonFungibleMint() { + final Stream rollbackOnFailedAssociateAfterNonFungibleMint() { final var nestedMintTxn = "nestedMintTxn"; return propertyPreservingHapiSpec("rollbackOnFailedAssociateAfterNonFungibleMint") @@ -449,7 +450,7 @@ final HapiSpec rollbackOnFailedAssociateAfterNonFungibleMint() { htsPrecompileResult().withStatus(INVALID_TOKEN_ID))))); } - final HapiSpec gasCostNotMetSetsInsufficientGasStatusInChildRecord() { + final Stream gasCostNotMetSetsInsufficientGasStatusInChildRecord() { final var amount = 10L; final var baselineMintWithEnoughGas = "baselineMintWithEnoughGas"; diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/precompile/ContractMintHTSV2SecurityModelSuite.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/precompile/ContractMintHTSV2SecurityModelSuite.java index 7c291ae6beeb..628192ced7c9 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/precompile/ContractMintHTSV2SecurityModelSuite.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/precompile/ContractMintHTSV2SecurityModelSuite.java @@ -42,6 +42,9 @@ import static com.hedera.services.bdd.spec.utilops.UtilVerbs.overriding; import static com.hedera.services.bdd.spec.utilops.UtilVerbs.sourcing; import static com.hedera.services.bdd.spec.utilops.UtilVerbs.withOpContext; +import static com.hedera.services.bdd.suites.HapiSuite.ONE_HUNDRED_HBARS; +import static com.hedera.services.bdd.suites.HapiSuite.ONE_MILLION_HBARS; +import static com.hedera.services.bdd.suites.HapiSuite.THOUSAND_HBAR; import static com.hedera.services.bdd.suites.contract.Utils.asAddress; import static com.hedera.services.bdd.suites.contract.Utils.getNestedContractAddress; import static com.hedera.services.bdd.suites.contract.precompile.V1SecurityModelOverrides.CONTRACTS_MAX_NUM_WITH_HAPI_SIGS_ACCESS; @@ -53,27 +56,20 @@ import com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.mint.MintTranslator; import com.hedera.services.bdd.junit.HapiTest; -import com.hedera.services.bdd.junit.HapiTestSuite; -import com.hedera.services.bdd.spec.HapiSpec; import com.hedera.services.bdd.spec.keys.KeyShape; import com.hedera.services.bdd.spec.transactions.contract.HapiParserUtil; -import com.hedera.services.bdd.suites.HapiSuite; import com.hederahashgraph.api.proto.java.TokenID; import com.hederahashgraph.api.proto.java.TokenType; import java.math.BigInteger; -import java.util.List; import java.util.concurrent.atomic.AtomicReference; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; +import java.util.stream.Stream; import org.apache.tuweni.bytes.Bytes; +import org.junit.jupiter.api.DynamicTest; import org.junit.jupiter.api.Tag; -@HapiTestSuite @Tag(SMART_CONTRACT) @SuppressWarnings("java:S1192") // "string literal should not be duplicated" - this rule makes test suites worse -public class ContractMintHTSV2SecurityModelSuite extends HapiSuite { - - private static final Logger LOG = LogManager.getLogger(ContractMintHTSV1SecurityModelSuite.class); +public class ContractMintHTSV2SecurityModelSuite { private static final long GAS_TO_OFFER = 4_000_000L; private static final String TOKEN_TREASURY = "treasury"; private static final KeyShape TRESHOLD_KEY_SHAPE = KeyShape.threshOf(1, ED25519, CONTRACT); @@ -124,36 +120,8 @@ public class ContractMintHTSV2SecurityModelSuite extends HapiSuite { static final byte[][] EMPTY_METADATA = new byte[][] {}; static final byte[][] TEST_METADATA_2 = new byte[][] {TEST_METADATA_1.getBytes()}; - public static void main(final String... args) { - new ContractMintHTSV2SecurityModelSuite().runSuiteAsync(); - } - - @Override - public boolean canRunConcurrent() { - return true; - } - - public List getSpecsInSuite() { - return allOf(positiveSpecs(), negativeSpecs()); - } - - List negativeSpecs() { - return List.of( - V2Security002FungibleTokenMintInTreasuryNegative(), - V2Security003NonFungibleTokenMintInTreasuryNegative(), - V2Security035TokenWithDelegateContractKeyCanNotMintFromDelegatecall(), - V2Security040TokenWithDelegateContractKeyCanNotMintFromStaticcall(), - V2Security040TokenWithDelegateContractKeyCanNotMintFromCallcode()); - } - - List positiveSpecs() { - return List.of( - V2Security002FungibleTokenMintInTreasuryPositive(), - V2Security003NonFungibleTokenMintInTreasuryPositive()); - } - @HapiTest - final HapiSpec V2Security002FungibleTokenMintInTreasuryPositive() { + final Stream V2Security002FungibleTokenMintInTreasuryPositive() { final var amount = 10L; final AtomicReference fungible = new AtomicReference<>(); @@ -285,7 +253,7 @@ final HapiSpec V2Security002FungibleTokenMintInTreasuryPositive() { } @HapiTest - final HapiSpec V2Security003NonFungibleTokenMintInTreasuryPositive() { + final Stream V2Security003NonFungibleTokenMintInTreasuryPositive() { final var amount = 1; final AtomicReference nonFungible = new AtomicReference<>(); @@ -422,7 +390,7 @@ final HapiSpec V2Security003NonFungibleTokenMintInTreasuryPositive() { } @HapiTest - final HapiSpec V2Security002FungibleTokenMintInTreasuryNegative() { + final Stream V2Security002FungibleTokenMintInTreasuryNegative() { final var amount = 10L; final AtomicReference fungible = new AtomicReference<>(); @@ -538,7 +506,7 @@ final HapiSpec V2Security002FungibleTokenMintInTreasuryNegative() { } @HapiTest - final HapiSpec V2Security003NonFungibleTokenMintInTreasuryNegative() { + final Stream V2Security003NonFungibleTokenMintInTreasuryNegative() { final AtomicReference nonFungible = new AtomicReference<>(); return defaultHapiSpec("V2Security003NonFungibleTokenMintNegative") @@ -653,7 +621,7 @@ final HapiSpec V2Security003NonFungibleTokenMintInTreasuryNegative() { } @HapiTest - final HapiSpec V2Security035TokenWithDelegateContractKeyCanNotMintFromDelegatecall() { + final Stream V2Security035TokenWithDelegateContractKeyCanNotMintFromDelegatecall() { return defaultHapiSpec("V2Security035TokenWithDelegateContractKeyCanNotMintFromDelegatecal") .given( overriding(CONTRACTS_MAX_NUM_WITH_HAPI_SIGS_ACCESS, CONTRACTS_V2_SECURITY_MODEL_BLOCK_CUTOFF), @@ -802,7 +770,7 @@ final HapiSpec V2Security035TokenWithDelegateContractKeyCanNotMintFromDelegateca } @HapiTest - final HapiSpec V2Security040TokenWithDelegateContractKeyCanNotMintFromStaticcall() { + final Stream V2Security040TokenWithDelegateContractKeyCanNotMintFromStaticcall() { final AtomicReference fungible = new AtomicReference<>(); final AtomicReference nonFungible = new AtomicReference<>(); @@ -886,7 +854,7 @@ final HapiSpec V2Security040TokenWithDelegateContractKeyCanNotMintFromStaticcall } @HapiTest - final HapiSpec V2Security040TokenWithDelegateContractKeyCanNotMintFromCallcode() { + final Stream V2Security040TokenWithDelegateContractKeyCanNotMintFromCallcode() { final AtomicReference fungible = new AtomicReference<>(); final AtomicReference nonFungible = new AtomicReference<>(); final String precompileAddress = "0000000000000000000000000000000000000167"; @@ -981,9 +949,4 @@ final HapiSpec V2Security040TokenWithDelegateContractKeyCanNotMintFromCallcode() childRecordsCheck(CALLCODE_WHEN_FUNGIBLE_TOKEN_HAS_CONTRACT_ID, CONTRACT_REVERT_EXECUTED), childRecordsCheck(CALLCODE_WHEN_NON_FUNGIBLE_TOKEN_HAS_CONTRACT_ID, CONTRACT_REVERT_EXECUTED)); } - - @Override - protected Logger getResultsLogger() { - return LOG; - } } diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/precompile/CreatePrecompileSuite.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/precompile/CreatePrecompileSuite.java index 12789b7f89b6..fd6cb4d3da41 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/precompile/CreatePrecompileSuite.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/precompile/CreatePrecompileSuite.java @@ -17,56 +17,82 @@ package com.hedera.services.bdd.suites.contract.precompile; import static com.hedera.services.bdd.junit.TestTags.SMART_CONTRACT; +import static com.hedera.services.bdd.spec.HapiPropertySource.asTokenString; import static com.hedera.services.bdd.spec.HapiSpec.defaultHapiSpec; import static com.hedera.services.bdd.spec.assertions.AccountInfoAsserts.changeFromSnapshot; +import static com.hedera.services.bdd.spec.keys.KeyShape.CONTRACT; +import static com.hedera.services.bdd.spec.keys.KeyShape.DELEGATE_CONTRACT; import static com.hedera.services.bdd.spec.keys.KeyShape.ED25519; import static com.hedera.services.bdd.spec.keys.KeyShape.SECP256K1; +import static com.hedera.services.bdd.spec.keys.KeyShape.sigs; +import static com.hedera.services.bdd.spec.keys.SigControl.ED25519_ON; +import static com.hedera.services.bdd.spec.keys.SigControl.ON; import static com.hedera.services.bdd.spec.queries.QueryVerbs.getAccountBalance; import static com.hedera.services.bdd.spec.queries.QueryVerbs.getAccountInfo; import static com.hedera.services.bdd.spec.queries.QueryVerbs.getContractInfo; +import static com.hedera.services.bdd.spec.queries.QueryVerbs.getTokenInfo; import static com.hedera.services.bdd.spec.queries.QueryVerbs.getTxnRecord; import static com.hedera.services.bdd.spec.transactions.TxnVerbs.contractCall; import static com.hedera.services.bdd.spec.transactions.TxnVerbs.contractCreate; import static com.hedera.services.bdd.spec.transactions.TxnVerbs.cryptoCreate; +import static com.hedera.services.bdd.spec.transactions.TxnVerbs.cryptoDelete; +import static com.hedera.services.bdd.spec.transactions.TxnVerbs.cryptoTransfer; +import static com.hedera.services.bdd.spec.transactions.TxnVerbs.cryptoUpdate; import static com.hedera.services.bdd.spec.transactions.TxnVerbs.uploadInitCode; import static com.hedera.services.bdd.spec.utilops.CustomSpecAssert.allRunFor; import static com.hedera.services.bdd.spec.utilops.UtilVerbs.balanceSnapshot; import static com.hedera.services.bdd.spec.utilops.UtilVerbs.childRecordsCheck; import static com.hedera.services.bdd.spec.utilops.UtilVerbs.emptyChildRecordsCheck; import static com.hedera.services.bdd.spec.utilops.UtilVerbs.newKeyNamed; +import static com.hedera.services.bdd.spec.utilops.UtilVerbs.sourcing; import static com.hedera.services.bdd.spec.utilops.UtilVerbs.withOpContext; import static com.hedera.services.bdd.spec.utilops.records.SnapshotMatchMode.NONDETERMINISTIC_FUNCTION_PARAMETERS; import static com.hedera.services.bdd.spec.utilops.records.SnapshotMatchMode.NONDETERMINISTIC_TRANSACTION_FEES; +import static com.hedera.services.bdd.suites.HapiSuite.DEFAULT_CONTRACT_SENDER; +import static com.hedera.services.bdd.suites.HapiSuite.GENESIS; +import static com.hedera.services.bdd.suites.HapiSuite.ONE_HBAR; +import static com.hedera.services.bdd.suites.HapiSuite.ONE_HUNDRED_HBARS; +import static com.hedera.services.bdd.suites.HapiSuite.ONE_MILLION_HBARS; import static com.hedera.services.bdd.suites.contract.Utils.asAddress; +import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.ACCOUNT_IS_TREASURY; import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.CONTRACT_REVERT_EXECUTED; import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.INSUFFICIENT_TX_FEE; import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.INVALID_ACCOUNT_ID; -import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.INVALID_EXPIRATION_TIME; +import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.INVALID_RENEWAL_PERIOD; import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.MISSING_TOKEN_SYMBOL; +import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.SUCCESS; +import com.esaulpaugh.headlong.abi.Address; import com.hedera.services.bdd.junit.HapiTest; -import com.hedera.services.bdd.junit.HapiTestSuite; -import com.hedera.services.bdd.spec.HapiSpec; import com.hedera.services.bdd.spec.assertions.ContractFnResultAsserts; +import com.hedera.services.bdd.spec.assertions.ContractInfoAsserts; import com.hedera.services.bdd.spec.assertions.TransactionRecordAsserts; +import com.hedera.services.bdd.spec.keys.KeyShape; import com.hedera.services.bdd.spec.transactions.contract.HapiEthereumCall; import com.hedera.services.bdd.spec.transactions.contract.HapiParserUtil; -import com.hedera.services.bdd.suites.HapiSuite; +import com.hedera.services.bdd.spec.transactions.token.TokenMovement; import com.hedera.services.bdd.suites.contract.Utils; import com.hederahashgraph.api.proto.java.ResponseCodeEnum; -import java.util.List; +import com.hederahashgraph.api.proto.java.TokenFreezeStatus; +import com.hederahashgraph.api.proto.java.TokenID; +import com.hederahashgraph.api.proto.java.TokenPauseStatus; +import com.hederahashgraph.api.proto.java.TokenSupplyType; +import com.hederahashgraph.api.proto.java.TokenType; +import java.util.concurrent.atomic.AtomicLong; +import java.util.concurrent.atomic.AtomicReference; +import java.util.stream.Stream; import org.apache.commons.lang3.ArrayUtils; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; +import org.junit.jupiter.api.DynamicTest; import org.junit.jupiter.api.Tag; // Some of the test cases cannot be converted to use eth calls, // since they use admin keys, which are held by the txn payer. // In the case of an eth txn, we revoke the payers keys and the txn would fail. // The only way an eth account to create a token is the admin key to be of a contractId type. -@HapiTestSuite(fuzzyMatch = true) @Tag(SMART_CONTRACT) -public class CreatePrecompileSuite extends HapiSuite { +public class CreatePrecompileSuite { public static final String ACCOUNT_2 = "account2"; public static final String CONTRACT_ADMIN_KEY = "contractAdminKey"; public static final String ACCOUNT_TO_ASSOCIATE = "account3"; @@ -76,6 +102,7 @@ public class CreatePrecompileSuite extends HapiSuite { public static final String CREATE_TOKEN_WITH_ALL_CUSTOM_FEES_AVAILABLE = "createTokenWithAllCustomFeesAvailable"; private static final Logger log = LogManager.getLogger(CreatePrecompileSuite.class); private static final long GAS_TO_OFFER = 1_000_000L; + private static final long GAS_TO_OFFER_2 = 4_000_000L; public static final long AUTO_RENEW_PERIOD = 8_000_000L; public static final String TOKEN_SYMBOL = "tokenSymbol"; public static final String TOKEN_NAME = "tokenName"; @@ -91,44 +118,584 @@ public class CreatePrecompileSuite extends HapiSuite { public static final String EXISTING_TOKEN = "EXISTING_TOKEN"; public static final String EXPLICIT_CREATE_RESULT = "Explicit create result is {}"; private static final String CREATE_NFT_WITH_KEYS_AND_EXPIRY_FUNCTION = "createNFTTokenWithKeysAndExpiry"; + private static final KeyShape THRESHOLD_KEY_SHAPE = KeyShape.threshOf(1, ED25519, CONTRACT); + private static final String THRESHOLD_KEY = "ThreshKey"; + private static final String ADMIN_KEY = "adminKey"; + private static final String TOKEN_MISC_OPERATIONS_CONTRACT = "TokenMiscOperations"; + private static final String CREATE_FUNGIBLE_TOKEN_WITH_KEYS_AND_EXPIRY_FUNCTION = "createTokenWithKeysAndExpiry"; - public static void main(String... args) { - new CreatePrecompileSuite().runSuiteAsync(); + // TEST-001 + @HapiTest + final Stream fungibleTokenCreateHappyPath() { + final var tokenCreateContractAsKeyDelegate = "tokenCreateContractAsKeyDelegate"; + final var createTokenNum = new AtomicLong(); + final AtomicReference ed2551Key = new AtomicReference<>(); + return defaultHapiSpec("fungibleTokenCreateHappyPath") + .given( + newKeyNamed(ECDSA_KEY).shape(SECP256K1), + newKeyNamed(CONTRACT_ADMIN_KEY), + cryptoCreate(ACCOUNT_TO_ASSOCIATE), + uploadInitCode(TOKEN_CREATE_CONTRACT), + cryptoCreate(ACCOUNT).balance(ONE_MILLION_HBARS), + contractCreate(TOKEN_CREATE_CONTRACT) + .autoRenewAccountId(ACCOUNT) + .adminKey(CONTRACT_ADMIN_KEY) + .gas(GAS_TO_OFFER), + newKeyNamed(THRESHOLD_KEY) + .shape(THRESHOLD_KEY_SHAPE.signedWith(sigs(ED25519_ON, TOKEN_CREATE_CONTRACT))) + .exposingKeyTo(k -> ed2551Key.set(k.getThresholdKey() + .getKeys() + .getKeys(0) + .getEd25519() + .toByteArray())), + cryptoUpdate(ACCOUNT).key(THRESHOLD_KEY), + cryptoUpdate(ACCOUNT_TO_ASSOCIATE).key(THRESHOLD_KEY)) + .when(withOpContext((spec, opLog) -> { + spec.registry() + .saveKey( + ED25519KEY, + spec.registry() + .getKey(THRESHOLD_KEY) + .getThresholdKey() + .getKeys() + .getKeys(0)); + allRunFor( + spec, + contractCall( + TOKEN_CREATE_CONTRACT, + CREATE_FUNGIBLE_TOKEN_WITH_KEYS_AND_EXPIRY_FUNCTION, + HapiParserUtil.asHeadlongAddress( + asAddress(spec.registry().getAccountID(ACCOUNT))), + ed2551Key.get(), + spec.registry() + .getKey(ECDSA_KEY) + .getECDSASecp256K1() + .toByteArray(), + HapiParserUtil.asHeadlongAddress( + asAddress(spec.registry().getContractId(TOKEN_CREATE_CONTRACT))), + HapiParserUtil.asHeadlongAddress( + asAddress(spec.registry().getContractId(TOKEN_CREATE_CONTRACT))), + HapiParserUtil.asHeadlongAddress( + asAddress(spec.registry().getAccountID(ACCOUNT))), + AUTO_RENEW_PERIOD, + HapiParserUtil.asHeadlongAddress( + asAddress(spec.registry().getAccountID(ACCOUNT_TO_ASSOCIATE)))) + .via(FIRST_CREATE_TXN) + .gas(GAS_TO_OFFER) + .sending(DEFAULT_AMOUNT_TO_SEND) + .payingWith(ACCOUNT) + .signedBy(THRESHOLD_KEY) + .refusingEthConversion() + .exposingResultTo(result -> { + log.info(EXPLICIT_CREATE_RESULT, result[0]); + final var res = (Address) result[0]; + createTokenNum.set(res.value().longValueExact()); + }) + .hasKnownStatus(SUCCESS), + newKeyNamed(TOKEN_CREATE_CONTRACT_AS_KEY).shape(CONTRACT.signedWith(TOKEN_CREATE_CONTRACT)), + newKeyNamed(tokenCreateContractAsKeyDelegate) + .shape(DELEGATE_CONTRACT.signedWith(TOKEN_CREATE_CONTRACT))); + })) + .then(withOpContext((spec, opLog) -> allRunFor( + spec, + getContractInfo(TOKEN_CREATE_CONTRACT) + .has(ContractInfoAsserts.contractWith().autoRenewAccountId(ACCOUNT)) + .logged(), + getTxnRecord(FIRST_CREATE_TXN).andAllChildRecords().logged(), + getAccountBalance(ACCOUNT).logged(), + getAccountBalance(TOKEN_CREATE_CONTRACT).logged(), + getContractInfo(TOKEN_CREATE_CONTRACT).logged(), + childRecordsCheck( + FIRST_CREATE_TXN, + ResponseCodeEnum.SUCCESS, + TransactionRecordAsserts.recordWith().status(ResponseCodeEnum.SUCCESS), + TransactionRecordAsserts.recordWith().status(ResponseCodeEnum.SUCCESS), + TransactionRecordAsserts.recordWith().status(ResponseCodeEnum.SUCCESS)), + sourcing(() -> + getAccountInfo(ACCOUNT_TO_ASSOCIATE).logged().hasTokenRelationShipCount(1)), + sourcing(() -> getTokenInfo(asTokenString(TokenID.newBuilder() + .setTokenNum(createTokenNum.get()) + .build())) + .logged() + .hasTokenType(TokenType.FUNGIBLE_COMMON) + .hasSymbol(TOKEN_SYMBOL) + .hasName(TOKEN_NAME) + .hasDecimals(8) + .hasTotalSupply(100) + .hasEntityMemo(MEMO) + .hasTreasury(ACCOUNT) + // Token doesn't inherit contract's auto-renew + // account if set in tokenCreate + .hasAutoRenewAccount(ACCOUNT) + .hasAutoRenewPeriod(AUTO_RENEW_PERIOD) + .hasSupplyType(TokenSupplyType.INFINITE) + .searchKeysGlobally() + .hasAdminKey(ED25519KEY) + .hasKycKey(ED25519KEY) + .hasFreezeKey(ECDSA_KEY) + .hasWipeKey(ECDSA_KEY) + .hasSupplyKey(TOKEN_CREATE_CONTRACT_AS_KEY) + .hasFeeScheduleKey(tokenCreateContractAsKeyDelegate) + .hasPauseKey(CONTRACT_ADMIN_KEY) + .hasPauseStatus(TokenPauseStatus.Unpaused)), + cryptoDelete(ACCOUNT).hasKnownStatus(ACCOUNT_IS_TREASURY)))); } - @Override - public boolean canRunConcurrent() { - return true; + // TEST-002 + @HapiTest + final Stream inheritsSenderAutoRenewAccountIfAnyForNftCreate() { + final var createdNftTokenNum = new AtomicLong(); + final AtomicReference ed2551Key = new AtomicReference<>(); + return defaultHapiSpec("inheritsSenderAutoRenewAccountIfAnyForNftCreate") + .given( + newKeyNamed(ED25519KEY).shape(ED25519), + cryptoCreate(ACCOUNT).balance(ONE_MILLION_HBARS), + uploadInitCode(TOKEN_CREATE_CONTRACT), + contractCreate(TOKEN_CREATE_CONTRACT) + .autoRenewAccountId(ACCOUNT) + .gas(GAS_TO_OFFER), + newKeyNamed(THRESHOLD_KEY) + .shape(THRESHOLD_KEY_SHAPE.signedWith(sigs(ED25519_ON, TOKEN_CREATE_CONTRACT))) + .exposingKeyTo(k -> ed2551Key.set(k.getThresholdKey() + .getKeys() + .getKeys(0) + .getEd25519() + .toByteArray())), + cryptoUpdate(ACCOUNT).key(THRESHOLD_KEY)) + .when(withOpContext((spec, opLog) -> allRunFor( + spec, + getContractInfo(TOKEN_CREATE_CONTRACT) + .has(ContractInfoAsserts.contractWith().autoRenewAccountId(ACCOUNT)) + .logged()))) + .then(withOpContext((spec, ignore) -> { + final var subop1 = balanceSnapshot(ACCOUNT_BALANCE, ACCOUNT); + final var subop2 = contractCall( + TOKEN_CREATE_CONTRACT, + CREATE_NFT_WITH_KEYS_AND_EXPIRY_FUNCTION, + HapiParserUtil.asHeadlongAddress( + asAddress(spec.registry().getAccountID(ACCOUNT))), + ed2551Key.get(), + HapiParserUtil.asHeadlongAddress( + asAddress(spec.registry().getAccountID(ACCOUNT))), + AUTO_RENEW_PERIOD) + .via(FIRST_CREATE_TXN) + .gas(GAS_TO_OFFER) + .payingWith(ACCOUNT) + .sending(DEFAULT_AMOUNT_TO_SEND) + .refusingEthConversion() + .exposingResultTo(result -> { + log.info("Explicit create result is" + " {}", result[0]); + final var res = (Address) result[0]; + createdNftTokenNum.set(res.value().longValueExact()); + }) + .hasKnownStatus(SUCCESS); + + allRunFor( + spec, + subop1, + subop2, + childRecordsCheck( + FIRST_CREATE_TXN, + SUCCESS, + TransactionRecordAsserts.recordWith().status(SUCCESS))); + + final var nftInfo = getTokenInfo(asTokenString(TokenID.newBuilder() + .setTokenNum(createdNftTokenNum.get()) + .build())) + .hasAutoRenewAccount(ACCOUNT) + .logged(); + + allRunFor(spec, nftInfo); + })); } - // TODO: Fix contract name in TokenCreateContract.sol - @Override - public List getSpecsInSuite() { - return allOf(positiveSpecs(), negativeSpecs()); + // TEST-001 + @HapiTest + final Stream inheritsSenderAutoRenewAccountForTokenCreate() { + final var createTokenNum = new AtomicLong(); + final AtomicReference ed2551Key = new AtomicReference<>(); + return defaultHapiSpec("inheritsSenderAutoRenewAccountForTokenCreate") + .given( + newKeyNamed(ECDSA_KEY).shape(SECP256K1), + newKeyNamed(ACCOUNT_TO_ASSOCIATE_KEY), + newKeyNamed(CONTRACT_ADMIN_KEY), + cryptoCreate(ACCOUNT).balance(ONE_MILLION_HBARS), + cryptoCreate(ACCOUNT_TO_ASSOCIATE).key(ACCOUNT_TO_ASSOCIATE_KEY), + uploadInitCode(TOKEN_CREATE_CONTRACT), + contractCreate(TOKEN_CREATE_CONTRACT) + .gas(GAS_TO_OFFER) + .adminKey(CONTRACT_ADMIN_KEY) + .autoRenewAccountId(ACCOUNT), + newKeyNamed(THRESHOLD_KEY) + .shape(THRESHOLD_KEY_SHAPE.signedWith(sigs(ED25519_ON, TOKEN_CREATE_CONTRACT))) + .exposingKeyTo(k -> ed2551Key.set(k.getThresholdKey() + .getKeys() + .getKeys(0) + .getEd25519() + .toByteArray())), + cryptoUpdate(ACCOUNT).key(THRESHOLD_KEY), + cryptoUpdate(ACCOUNT_TO_ASSOCIATE).key(THRESHOLD_KEY), + getContractInfo(TOKEN_CREATE_CONTRACT) + .has(ContractInfoAsserts.contractWith().autoRenewAccountId(ACCOUNT)) + .logged()) + .when(withOpContext((spec, opLog) -> allRunFor( + spec, + contractCall( + TOKEN_CREATE_CONTRACT, + CREATE_FUNGIBLE_TOKEN_WITH_KEYS_AND_EXPIRY_FUNCTION, + HapiParserUtil.asHeadlongAddress( + asAddress(spec.registry().getAccountID(ACCOUNT))), + ed2551Key.get(), + spec.registry() + .getKey(ECDSA_KEY) + .getECDSASecp256K1() + .toByteArray(), + HapiParserUtil.asHeadlongAddress( + asAddress(spec.registry().getContractId(TOKEN_CREATE_CONTRACT))), + HapiParserUtil.asHeadlongAddress( + asAddress(spec.registry().getContractId(TOKEN_CREATE_CONTRACT))), + HapiParserUtil.asHeadlongAddress( + asAddress(spec.registry().getAccountID(ACCOUNT))), + AUTO_RENEW_PERIOD, + HapiParserUtil.asHeadlongAddress( + asAddress(spec.registry().getAccountID(ACCOUNT_TO_ASSOCIATE)))) + .via(FIRST_CREATE_TXN) + .gas(GAS_TO_OFFER) + .sending(DEFAULT_AMOUNT_TO_SEND) + .payingWith(ACCOUNT) + .alsoSigningWithFullPrefix(THRESHOLD_KEY) + .refusingEthConversion() + .exposingResultTo(result -> { + log.info(EXPLICIT_CREATE_RESULT, result[0]); + final var res = (Address) result[0]; + createTokenNum.set(res.value().longValueExact()); + }) + .hasKnownStatus(SUCCESS)))) + .then(sourcing(() -> getTokenInfo(asTokenString(TokenID.newBuilder() + .setTokenNum(createTokenNum.get()) + .build())) + .logged() + .hasAutoRenewAccount(ACCOUNT) + .hasPauseStatus(TokenPauseStatus.Unpaused))); } - List positiveSpecs() { - return List.of( - // TODO: where are the security model V2 _positive_ tests? - ); + // TEST-003 + @HapiTest + final Stream nonFungibleTokenCreateHappyPath() { + final var createdTokenNum = new AtomicLong(); + return defaultHapiSpec("nonFungibleTokenCreateHappyPath") + .given( + newKeyNamed(ED25519KEY).shape(ED25519), + cryptoCreate(ACCOUNT).balance(ONE_MILLION_HBARS).key(ED25519KEY), + uploadInitCode(TOKEN_CREATE_CONTRACT), + getAccountInfo(DEFAULT_CONTRACT_SENDER).savingSnapshot(DEFAULT_CONTRACT_SENDER)) + .when(withOpContext((spec, opLog) -> + allRunFor(spec, contractCreate(TOKEN_CREATE_CONTRACT).gas(GAS_TO_OFFER)))) + .then(withOpContext((spec, ignore) -> { + final var subop1 = balanceSnapshot(ACCOUNT_BALANCE, ACCOUNT); + final var subop2 = contractCall( + TOKEN_CREATE_CONTRACT, + CREATE_NFT_WITH_KEYS_AND_EXPIRY_FUNCTION, + HapiParserUtil.asHeadlongAddress( + asAddress(spec.registry().getAccountID(ACCOUNT))), + spec.registry() + .getKey(ED25519KEY) + .getEd25519() + .toByteArray(), + HapiParserUtil.asHeadlongAddress( + asAddress(spec.registry().getAccountID(ACCOUNT))), + AUTO_RENEW_PERIOD) + .via(FIRST_CREATE_TXN) + .gas(GAS_TO_OFFER) + .payingWith(ACCOUNT) + .sending(DEFAULT_AMOUNT_TO_SEND) + .exposingResultTo(result -> { + log.info("Explicit create result is" + " {}", result[0]); + final var res = (Address) result[0]; + createdTokenNum.set(res.value().longValueExact()); + }) + .refusingEthConversion() + .hasKnownStatus(SUCCESS); + final var subop3 = getTxnRecord(FIRST_CREATE_TXN); + allRunFor( + spec, + subop1, + subop2, + subop3, + childRecordsCheck( + FIRST_CREATE_TXN, + SUCCESS, + TransactionRecordAsserts.recordWith().status(SUCCESS))); + + final var delta = subop3.getResponseRecord().getTransactionFee(); + final var effectivePayer = ACCOUNT; + final var subop4 = getAccountBalance(effectivePayer) + .hasTinyBars(changeFromSnapshot(ACCOUNT_BALANCE, -(delta + DEFAULT_AMOUNT_TO_SEND))); + final var contractBalanceCheck = getContractInfo(TOKEN_CREATE_CONTRACT) + .has(ContractInfoAsserts.contractWith() + .balanceGreaterThan(0L) + .balanceLessThan(DEFAULT_AMOUNT_TO_SEND)); + final var getAccountTokenBalance = getAccountBalance(ACCOUNT) + .hasTokenBalance( + asTokenString(TokenID.newBuilder() + .setTokenNum(createdTokenNum.get()) + .build()), + 0); + final var tokenInfo = getTokenInfo(asTokenString(TokenID.newBuilder() + .setTokenNum(createdTokenNum.get()) + .build())) + .hasTokenType(TokenType.NON_FUNGIBLE_UNIQUE) + .hasSymbol(TOKEN_SYMBOL) + .hasName(TOKEN_NAME) + .hasDecimals(0) + .hasTotalSupply(0) + .hasEntityMemo(MEMO) + .hasTreasury(ACCOUNT) + .hasAutoRenewAccount(ACCOUNT) + .hasAutoRenewPeriod(AUTO_RENEW_PERIOD) + .hasSupplyType(TokenSupplyType.FINITE) + .hasFreezeDefault(TokenFreezeStatus.Frozen) + .hasMaxSupply(10) + .searchKeysGlobally() + .hasAdminKey(ED25519KEY) + .hasSupplyKey(ED25519KEY) + .hasPauseKey(ED25519KEY) + .hasFreezeKey(ED25519KEY) + .hasKycKey(ED25519KEY) + .hasFeeScheduleKey(ED25519KEY) + .hasWipeKey(ED25519KEY) + .hasPauseStatus(TokenPauseStatus.Unpaused) + .logged(); + allRunFor(spec, subop4, getAccountTokenBalance, tokenInfo, contractBalanceCheck); + })); } - List negativeSpecs() { - return List.of( - tokenCreateWithEmptyKeysReverts(), - tokenCreateWithKeyWithMultipleKeyValuesReverts(), - tokenCreateWithFixedFeeWithMultiplePaymentsReverts(), - createTokenWithEmptyTokenStruct(), - createTokenWithInvalidExpiry(), - createTokenWithInvalidTreasury(), - createTokenWithInsufficientValueSent(), - delegateCallTokenCreateFails()); + // TEST-005 + @HapiTest + final Stream fungibleTokenCreateThenQueryAndTransfer() { + final var createdTokenNum = new AtomicLong(); + final AtomicReference ed2551Key = new AtomicReference<>(); + return defaultHapiSpec("fungibleTokenCreateThenQueryAndTransfer") + .given( + uploadInitCode(TOKEN_CREATE_CONTRACT), + contractCreate(TOKEN_CREATE_CONTRACT).gas(GAS_TO_OFFER), + newKeyNamed(TOKEN_CREATE_CONTRACT_AS_KEY).shape(CONTRACT.signedWith(TOKEN_CREATE_CONTRACT)), + newKeyNamed(THRESHOLD_KEY) + .shape(THRESHOLD_KEY_SHAPE.signedWith(sigs(ED25519_ON, TOKEN_CREATE_CONTRACT))) + .exposingKeyTo(k -> ed2551Key.set(k.getThresholdKey() + .getKeys() + .getKeys(0) + .getEd25519() + .toByteArray())), + cryptoCreate(ACCOUNT) + .balance(ONE_MILLION_HBARS) + .key(THRESHOLD_KEY) + .maxAutomaticTokenAssociations(1)) + .when(withOpContext((spec, opLog) -> { + spec.registry() + .saveKey( + ADMIN_KEY, + spec.registry() + .getKey(THRESHOLD_KEY) + .getThresholdKey() + .getKeys() + .getKeys(0)); + allRunFor( + spec, + contractCall( + TOKEN_CREATE_CONTRACT, + "createTokenThenQueryAndTransfer", + ed2551Key.get(), + HapiParserUtil.asHeadlongAddress( + asAddress(spec.registry().getAccountID(ACCOUNT))), + AUTO_RENEW_PERIOD) + .via(FIRST_CREATE_TXN) + .gas(GAS_TO_OFFER) + .sending(DEFAULT_AMOUNT_TO_SEND) + .payingWith(ACCOUNT) + .signedBy(ACCOUNT) + .exposingResultTo(result -> { + log.info(EXPLICIT_CREATE_RESULT, result[0]); + final var res = (Address) result[0]; + createdTokenNum.set(res.value().longValueExact()); + }) + .hasKnownStatus(SUCCESS)); + })) + .then(withOpContext((spec, opLog) -> allRunFor( + spec, + getTxnRecord(FIRST_CREATE_TXN).andAllChildRecords().logged(), + getAccountBalance(ACCOUNT).logged(), + getAccountBalance(TOKEN_CREATE_CONTRACT).logged(), + getContractInfo(TOKEN_CREATE_CONTRACT).logged(), + childRecordsCheck( + FIRST_CREATE_TXN, + ResponseCodeEnum.SUCCESS, + TransactionRecordAsserts.recordWith().status(SUCCESS), + TransactionRecordAsserts.recordWith().status(SUCCESS), + TransactionRecordAsserts.recordWith().status(SUCCESS), + TransactionRecordAsserts.recordWith().status(SUCCESS)), + sourcing(() -> getAccountBalance(ACCOUNT) + .hasTokenBalance( + asTokenString(TokenID.newBuilder() + .setTokenNum(createdTokenNum.get()) + .build()), + 20)), + sourcing(() -> getAccountBalance(TOKEN_CREATE_CONTRACT) + .hasTokenBalance( + asTokenString(TokenID.newBuilder() + .setTokenNum(createdTokenNum.get()) + .build()), + 10)), + sourcing(() -> getTokenInfo(asTokenString(TokenID.newBuilder() + .setTokenNum(createdTokenNum.get()) + .build())) + .hasTokenType(TokenType.FUNGIBLE_COMMON) + .hasSymbol(TOKEN_SYMBOL) + .hasName(TOKEN_NAME) + .hasDecimals(8) + .hasTotalSupply(30) + .hasEntityMemo(MEMO) + .hasTreasury(TOKEN_CREATE_CONTRACT) + .hasAutoRenewAccount(ACCOUNT) + .hasAutoRenewPeriod(AUTO_RENEW_PERIOD) + .hasSupplyType(TokenSupplyType.INFINITE) + .searchKeysGlobally() + .hasAdminKey("adminKey") + .hasSupplyKey(TOKEN_CREATE_CONTRACT_AS_KEY) + .hasPauseKey(TOKEN_CREATE_CONTRACT_AS_KEY) + .hasPauseStatus(TokenPauseStatus.Unpaused) + .logged())))); + } + + // TEST-006 + @HapiTest + final Stream nonFungibleTokenCreateThenQuery() { + final var createdTokenNum = new AtomicLong(); + return defaultHapiSpec("nonFungibleTokenCreateThenQuery") + .given( + cryptoCreate(ACCOUNT).balance(ONE_MILLION_HBARS), + uploadInitCode(TOKEN_CREATE_CONTRACT), + contractCreate(TOKEN_CREATE_CONTRACT) + .autoRenewAccountId(ACCOUNT) + .gas(GAS_TO_OFFER), + newKeyNamed(THRESHOLD_KEY) + .shape(THRESHOLD_KEY_SHAPE.signedWith(sigs(ON, TOKEN_CREATE_CONTRACT))), + cryptoUpdate(ACCOUNT).key(THRESHOLD_KEY)) + .when(withOpContext((spec, opLog) -> allRunFor( + spec, + contractCall( + TOKEN_CREATE_CONTRACT, + "createNonFungibleTokenThenQuery", + HapiParserUtil.asHeadlongAddress( + asAddress(spec.registry().getContractId(TOKEN_CREATE_CONTRACT))), + HapiParserUtil.asHeadlongAddress( + asAddress(spec.registry().getAccountID(ACCOUNT))), + AUTO_RENEW_PERIOD) + .via(FIRST_CREATE_TXN) + .gas(GAS_TO_OFFER) + .sending(DEFAULT_AMOUNT_TO_SEND) + .payingWith(ACCOUNT) + .signedByPayerAnd(THRESHOLD_KEY) + .refusingEthConversion() + .exposingResultTo(result -> { + log.info(EXPLICIT_CREATE_RESULT, result[0]); + final var res = (Address) result[0]; + createdTokenNum.set(res.value().longValueExact()); + }), + newKeyNamed(TOKEN_CREATE_CONTRACT_AS_KEY).shape(CONTRACT.signedWith(TOKEN_CREATE_CONTRACT))))) + .then( + getTxnRecord(FIRST_CREATE_TXN).andAllChildRecords().logged(), + getAccountBalance(ACCOUNT).logged(), + getAccountBalance(TOKEN_CREATE_CONTRACT).logged(), + getContractInfo(TOKEN_CREATE_CONTRACT).logged(), + childRecordsCheck( + FIRST_CREATE_TXN, + ResponseCodeEnum.SUCCESS, + TransactionRecordAsserts.recordWith().status(SUCCESS), + TransactionRecordAsserts.recordWith().status(SUCCESS), + TransactionRecordAsserts.recordWith().status(SUCCESS)), + sourcing(() -> getAccountBalance(TOKEN_CREATE_CONTRACT) + .hasTokenBalance( + asTokenString(TokenID.newBuilder() + .setTokenNum(createdTokenNum.get()) + .build()), + 0)), + sourcing(() -> getTokenInfo(asTokenString(TokenID.newBuilder() + .setTokenNum(createdTokenNum.get()) + .build())) + .hasTokenType(TokenType.NON_FUNGIBLE_UNIQUE) + .hasSymbol(TOKEN_SYMBOL) + .hasName(TOKEN_NAME) + .hasDecimals(0) + .hasTotalSupply(0) + .hasEntityMemo(MEMO) + .hasTreasury(TOKEN_CREATE_CONTRACT) + .hasAutoRenewAccount(ACCOUNT) + .hasAutoRenewPeriod(AUTO_RENEW_PERIOD) + .hasSupplyType(TokenSupplyType.INFINITE) + .searchKeysGlobally() + .hasAdminKey(TOKEN_CREATE_CONTRACT_AS_KEY) + .hasSupplyKey(TOKEN_CREATE_CONTRACT_AS_KEY) + .hasPauseStatus(TokenPauseStatus.PauseNotApplicable) + .logged())); + } + + @HapiTest + final Stream createTokenWithDefaultExpiryAndEmptyKeys() { + final var tokenCreateContractAsKeyDelegate = "createTokenWithDefaultExpiryAndEmptyKeys"; + final var createdTokenNum = new AtomicLong(); + return defaultHapiSpec("createTokenWithDefaultExpiryAndEmptyKeys") + .given( + uploadInitCode(TOKEN_CREATE_CONTRACT), + contractCreate(TOKEN_CREATE_CONTRACT).gas(GAS_TO_OFFER), + newKeyNamed(THRESHOLD_KEY) + .shape(THRESHOLD_KEY_SHAPE.signedWith(sigs(ED25519_ON, TOKEN_CREATE_CONTRACT))), + cryptoCreate(ACCOUNT).balance(ONE_MILLION_HBARS).key(THRESHOLD_KEY)) + .when(withOpContext((spec, opLog) -> allRunFor( + spec, + contractCall(TOKEN_CREATE_CONTRACT, tokenCreateContractAsKeyDelegate) + .via(FIRST_CREATE_TXN) + .gas(GAS_TO_OFFER) + .sending(DEFAULT_AMOUNT_TO_SEND) + .payingWith(ACCOUNT) + .exposingResultTo(result -> { + log.info(EXPLICIT_CREATE_RESULT, result[0]); + final var res = (Address) result[0]; + createdTokenNum.set(res.value().longValueExact()); + }) + .hasKnownStatus(SUCCESS)))) + .then( + getTxnRecord(FIRST_CREATE_TXN).andAllChildRecords().logged(), + getAccountBalance(ACCOUNT).logged(), + getAccountBalance(TOKEN_CREATE_CONTRACT).logged(), + getContractInfo(TOKEN_CREATE_CONTRACT).logged(), + childRecordsCheck( + FIRST_CREATE_TXN, + ResponseCodeEnum.SUCCESS, + TransactionRecordAsserts.recordWith().status(SUCCESS)), + sourcing(() -> getAccountBalance(TOKEN_CREATE_CONTRACT) + .hasTokenBalance( + asTokenString(TokenID.newBuilder() + .setTokenNum(createdTokenNum.get()) + .build()), + 200)), + sourcing(() -> getTokenInfo(asTokenString(TokenID.newBuilder() + .setTokenNum(createdTokenNum.get()) + .build())) + .hasTokenType(TokenType.FUNGIBLE_COMMON) + .hasDecimals(8) + .hasTotalSupply(200) + .hasTreasury(TOKEN_CREATE_CONTRACT) + .hasAutoRenewPeriod(0L) + .searchKeysGlobally() + .hasPauseStatus(TokenPauseStatus.PauseNotApplicable) + .logged())); } // TEST-007 & TEST-016 // Should fail on insufficient value sent @HapiTest - final HapiSpec tokenCreateWithEmptyKeysReverts() { + final Stream tokenCreateWithEmptyKeysReverts() { return defaultHapiSpec( "tokenCreateWithEmptyKeysReverts", NONDETERMINISTIC_TRANSACTION_FEES, @@ -177,7 +744,7 @@ final HapiSpec tokenCreateWithEmptyKeysReverts() { // TEST-008 @HapiTest - final HapiSpec tokenCreateWithKeyWithMultipleKeyValuesReverts() { + final Stream tokenCreateWithKeyWithMultipleKeyValuesReverts() { return defaultHapiSpec( "tokenCreateWithKeyWithMultipleKeyValuesReverts", NONDETERMINISTIC_TRANSACTION_FEES, @@ -209,7 +776,7 @@ final HapiSpec tokenCreateWithKeyWithMultipleKeyValuesReverts() { // TEST-009 @HapiTest - final HapiSpec tokenCreateWithFixedFeeWithMultiplePaymentsReverts() { + final Stream tokenCreateWithFixedFeeWithMultiplePaymentsReverts() { return defaultHapiSpec( "tokenCreateWithFixedFeeWithMultiplePaymentsReverts", NONDETERMINISTIC_TRANSACTION_FEES, @@ -249,7 +816,7 @@ final HapiSpec tokenCreateWithFixedFeeWithMultiplePaymentsReverts() { // TEST-010 & TEST-017 // Should fail on insufficient value sent @HapiTest - final HapiSpec createTokenWithEmptyTokenStruct() { + final Stream createTokenWithEmptyTokenStruct() { return defaultHapiSpec("createTokenWithEmptyTokenStruct", NONDETERMINISTIC_TRANSACTION_FEES) .given(cryptoCreate(ACCOUNT).balance(ONE_MILLION_HBARS), uploadInitCode(TOKEN_CREATE_CONTRACT)) .when(withOpContext((spec, opLog) -> @@ -297,13 +864,12 @@ final HapiSpec createTokenWithEmptyTokenStruct() { // TEST-011 @HapiTest - final HapiSpec createTokenWithInvalidExpiry() { + final Stream createTokenWithInvalidExpiry() { return defaultHapiSpec( "createTokenWithInvalidExpiry", NONDETERMINISTIC_TRANSACTION_FEES, NONDETERMINISTIC_FUNCTION_PARAMETERS) .given( - newKeyNamed(ECDSA_KEY).shape(SECP256K1), cryptoCreate(ACCOUNT).balance(ONE_MILLION_HBARS), uploadInitCode(TOKEN_CREATE_CONTRACT), contractCreate(TOKEN_CREATE_CONTRACT).gas(GAS_TO_OFFER)) @@ -329,14 +895,14 @@ final HapiSpec createTokenWithInvalidExpiry() { FIRST_CREATE_TXN, ResponseCodeEnum.CONTRACT_REVERT_EXECUTED, TransactionRecordAsserts.recordWith() - .status(INVALID_EXPIRATION_TIME) + .status(INVALID_RENEWAL_PERIOD) .contractCallResult(ContractFnResultAsserts.resultWith() - .error(INVALID_EXPIRATION_TIME.name())))); + .error(INVALID_RENEWAL_PERIOD.name())))); } // TEST-013 @HapiTest - final HapiSpec createTokenWithInvalidTreasury() { + final Stream createTokenWithInvalidTreasury() { return defaultHapiSpec( "createTokenWithInvalidTreasury", NONDETERMINISTIC_TRANSACTION_FEES, @@ -382,7 +948,7 @@ final HapiSpec createTokenWithInvalidTreasury() { // TEST-018 // Should fail on insufficient value sent @HapiTest - final HapiSpec createTokenWithInsufficientValueSent() { + final Stream createTokenWithInsufficientValueSent() { return defaultHapiSpec( "createTokenWithInsufficientValueSent", NONDETERMINISTIC_TRANSACTION_FEES, @@ -444,7 +1010,7 @@ final HapiSpec createTokenWithInsufficientValueSent() { // TEST-020 @HapiTest - final HapiSpec delegateCallTokenCreateFails() { + final Stream delegateCallTokenCreateFails() { return defaultHapiSpec( "delegateCallTokenCreateFails", NONDETERMINISTIC_TRANSACTION_FEES, @@ -475,8 +1041,78 @@ final HapiSpec delegateCallTokenCreateFails() { getContractInfo(TOKEN_CREATE_CONTRACT)); } - @Override - protected Logger getResultsLogger() { - return log; + @HapiTest + final Stream createTokenWithFixedFeeThenTransferAndAssessFee() { + final var createTokenNum = new AtomicLong(); + final var FEE_COLLECTOR = "feeCollector"; + final var RECIPIENT = "recipient"; + final var SECOND_RECIPIENT = "secondRecipient"; + return defaultHapiSpec("createTokenWithFixedFeeThenTransferAndAssessFee") + .given( + cryptoCreate(ACCOUNT).balance(ONE_MILLION_HBARS), + cryptoCreate(RECIPIENT).balance(ONE_HUNDRED_HBARS), + cryptoCreate(SECOND_RECIPIENT), + cryptoCreate(FEE_COLLECTOR).balance(0L), + uploadInitCode(TOKEN_MISC_OPERATIONS_CONTRACT), + contractCreate(TOKEN_MISC_OPERATIONS_CONTRACT) + .gas(GAS_TO_OFFER_2) + .autoRenewAccountId(ACCOUNT), + newKeyNamed(THRESHOLD_KEY) + .shape(THRESHOLD_KEY_SHAPE.signedWith(sigs(ON, TOKEN_MISC_OPERATIONS_CONTRACT))), + cryptoUpdate(ACCOUNT).key(THRESHOLD_KEY), + cryptoUpdate(FEE_COLLECTOR).key(THRESHOLD_KEY), + cryptoUpdate(RECIPIENT).key(THRESHOLD_KEY), + cryptoUpdate(SECOND_RECIPIENT).key(THRESHOLD_KEY), + cryptoTransfer(TokenMovement.movingHbar(ONE_HUNDRED_HBARS) + .between(GENESIS, TOKEN_MISC_OPERATIONS_CONTRACT)), + getContractInfo(TOKEN_MISC_OPERATIONS_CONTRACT) + .has(ContractInfoAsserts.contractWith().autoRenewAccountId(ACCOUNT)) + .logged()) + .when(withOpContext((spec, opLog) -> allRunFor( + spec, + contractCall( + TOKEN_MISC_OPERATIONS_CONTRACT, + "createTokenWithHbarsFixedFeeAndTransferIt", + 10L, + HapiParserUtil.asHeadlongAddress( + asAddress(spec.registry().getAccountID(FEE_COLLECTOR))), + HapiParserUtil.asHeadlongAddress( + asAddress(spec.registry().getAccountID(RECIPIENT))), + HapiParserUtil.asHeadlongAddress( + asAddress(spec.registry().getAccountID(SECOND_RECIPIENT))), + HapiParserUtil.asHeadlongAddress( + asAddress(spec.registry().getAccountID(ACCOUNT)))) + .via(FIRST_CREATE_TXN) + .gas(GAS_TO_OFFER_2) + .sending(DEFAULT_AMOUNT_TO_SEND) + .payingWith(ACCOUNT) + .exposingResultTo(result -> { + log.info(EXPLICIT_CREATE_RESULT, result[0]); + final var res = (Address) result[0]; + createTokenNum.set(res.value().longValueExact()); + }) + .hasKnownStatus(SUCCESS)))) + .then(withOpContext((spec, opLog) -> allRunFor( + spec, + getTxnRecord(FIRST_CREATE_TXN).andAllChildRecords().logged(), + getAccountBalance(RECIPIENT) + .hasTokenBalance( + asTokenString(TokenID.newBuilder() + .setTokenNum(createTokenNum.get()) + .build()), + 0L), + getAccountBalance(SECOND_RECIPIENT) + .hasTokenBalance( + asTokenString(TokenID.newBuilder() + .setTokenNum(createTokenNum.get()) + .build()), + 1L), + getAccountBalance(ACCOUNT) + .hasTokenBalance( + asTokenString(TokenID.newBuilder() + .setTokenNum(createTokenNum.get()) + .build()), + 199L), + getAccountBalance(FEE_COLLECTOR).hasTinyBars(10L)))); } } diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/precompile/CreatePrecompileV1SecurityModelSuite.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/precompile/CreatePrecompileV1SecurityModelSuite.java index f62a6cdb9713..387b5a0eeac6 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/precompile/CreatePrecompileV1SecurityModelSuite.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/precompile/CreatePrecompileV1SecurityModelSuite.java @@ -47,7 +47,6 @@ import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.SUCCESS; import com.esaulpaugh.headlong.abi.Address; -import com.hedera.services.bdd.spec.HapiSpec; import com.hedera.services.bdd.spec.assertions.ContractInfoAsserts; import com.hedera.services.bdd.spec.assertions.TransactionRecordAsserts; import com.hedera.services.bdd.spec.transactions.contract.HapiParserUtil; @@ -60,8 +59,10 @@ import com.hederahashgraph.api.proto.java.TokenType; import java.util.List; import java.util.concurrent.atomic.AtomicLong; +import java.util.stream.Stream; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; +import org.junit.jupiter.api.DynamicTest; // Some of the test cases cannot be converted to use eth calls, // since they use admin keys, which are held by the txn payer. @@ -104,11 +105,11 @@ public boolean canRunConcurrent() { // TODO: Fix contract name in TokenCreateContract.sol @Override - public List getSpecsInSuite() { + public List> getSpecsInSuite() { return allOf(positiveSpecs(), negativeSpecs()); } - List positiveSpecs() { + List> positiveSpecs() { return List.of( fungibleTokenCreateHappyPath(), nonFungibleTokenCreateHappyPath(), @@ -119,13 +120,13 @@ List positiveSpecs() { createTokenWithDefaultExpiryAndEmptyKeys()); } - List negativeSpecs() { + List> negativeSpecs() { // TODO: Where are the security model v1 _negative_ tests? return List.of(); } // TEST-001 - final HapiSpec fungibleTokenCreateHappyPath() { + final Stream fungibleTokenCreateHappyPath() { final var tokenCreateContractAsKeyDelegate = "tokenCreateContractAsKeyDelegate"; final var createTokenNum = new AtomicLong(); return propertyPreservingHapiSpec("fungibleTokenCreateHappyPath") @@ -232,7 +233,7 @@ final HapiSpec fungibleTokenCreateHappyPath() { // TEST-002 - final HapiSpec inheritsSenderAutoRenewAccountIfAnyForNftCreate() { + final Stream inheritsSenderAutoRenewAccountIfAnyForNftCreate() { final var createdNftTokenNum = new AtomicLong(); return propertyPreservingHapiSpec("inheritsSenderAutoRenewAccountIfAnyForNftCreate") .preserving(CONTRACTS_MAX_NUM_WITH_HAPI_SIGS_ACCESS) @@ -298,7 +299,7 @@ final HapiSpec inheritsSenderAutoRenewAccountIfAnyForNftCreate() { })); } - final HapiSpec inheritsSenderAutoRenewAccountForTokenCreate() { + final Stream inheritsSenderAutoRenewAccountForTokenCreate() { final var createTokenNum = new AtomicLong(); return propertyPreservingHapiSpec("inheritsSenderAutoRenewAccountForTokenCreate") .preserving(CONTRACTS_MAX_NUM_WITH_HAPI_SIGS_ACCESS) @@ -368,7 +369,7 @@ final HapiSpec inheritsSenderAutoRenewAccountForTokenCreate() { } // TEST-003 & TEST-019 - final HapiSpec nonFungibleTokenCreateHappyPath() { + final Stream nonFungibleTokenCreateHappyPath() { final var createdTokenNum = new AtomicLong(); return propertyPreservingHapiSpec("nonFungibleTokenCreateHappyPath") .preserving(CONTRACTS_MAX_NUM_WITH_HAPI_SIGS_ACCESS) @@ -460,7 +461,7 @@ final HapiSpec nonFungibleTokenCreateHappyPath() { } // TEST-005 - final HapiSpec fungibleTokenCreateThenQueryAndTransfer() { + final Stream fungibleTokenCreateThenQueryAndTransfer() { final var createdTokenNum = new AtomicLong(); return propertyPreservingHapiSpec("fungibleTokenCreateThenQueryAndTransfer") .preserving(CONTRACTS_MAX_NUM_WITH_HAPI_SIGS_ACCESS) @@ -542,7 +543,7 @@ final HapiSpec fungibleTokenCreateThenQueryAndTransfer() { } // TEST-006 - final HapiSpec nonFungibleTokenCreateThenQuery() { + final Stream nonFungibleTokenCreateThenQuery() { final var createdTokenNum = new AtomicLong(); return propertyPreservingHapiSpec("nonFungibleTokenCreateThenQuery") .preserving(CONTRACTS_MAX_NUM_WITH_HAPI_SIGS_ACCESS) @@ -609,7 +610,7 @@ final HapiSpec nonFungibleTokenCreateThenQuery() { .logged())); } - final HapiSpec createTokenWithDefaultExpiryAndEmptyKeys() { + final Stream createTokenWithDefaultExpiryAndEmptyKeys() { final var tokenCreateContractAsKeyDelegate = "createTokenWithDefaultExpiryAndEmptyKeys"; final var createTokenNum = new AtomicLong(); return propertyPreservingHapiSpec("createTokenWithDefaultExpiryAndEmptyKeys") diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/precompile/CryptoTransferHTSSuite.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/precompile/CryptoTransferHTSSuite.java index cb12c69c1a69..28aa8b794f3b 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/precompile/CryptoTransferHTSSuite.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/precompile/CryptoTransferHTSSuite.java @@ -62,6 +62,11 @@ import static com.hedera.services.bdd.spec.utilops.records.SnapshotMatchMode.NONDETERMINISTIC_CONTRACT_CALL_RESULTS; import static com.hedera.services.bdd.spec.utilops.records.SnapshotMatchMode.NONDETERMINISTIC_FUNCTION_PARAMETERS; import static com.hedera.services.bdd.spec.utilops.records.SnapshotMatchMode.NONDETERMINISTIC_TRANSACTION_FEES; +import static com.hedera.services.bdd.suites.HapiSuite.DEFAULT_PAYER; +import static com.hedera.services.bdd.suites.HapiSuite.GENESIS; +import static com.hedera.services.bdd.suites.HapiSuite.ONE_HBAR; +import static com.hedera.services.bdd.suites.HapiSuite.ONE_HUNDRED_HBARS; +import static com.hedera.services.bdd.suites.HapiSuite.TOKEN_TREASURY; import static com.hedera.services.bdd.suites.contract.Utils.asAddress; import static com.hedera.services.bdd.suites.contract.Utils.eventSignatureOf; import static com.hedera.services.bdd.suites.contract.Utils.parsedToByteString; @@ -85,27 +90,25 @@ import com.hedera.node.app.hapi.utils.ByteStringUtils; import com.hedera.node.app.hapi.utils.contracts.ParsingConstants.FunctionType; import com.hedera.services.bdd.junit.HapiTest; -import com.hedera.services.bdd.junit.HapiTestSuite; -import com.hedera.services.bdd.spec.HapiSpec; import com.hedera.services.bdd.spec.assertions.NonFungibleTransfers; import com.hedera.services.bdd.spec.assertions.SomeFungibleTransfers; import com.hedera.services.bdd.spec.keys.KeyShape; import com.hedera.services.bdd.spec.transactions.contract.HapiParserUtil; import com.hedera.services.bdd.spec.transactions.token.TokenMovement; -import com.hedera.services.bdd.suites.HapiSuite; import com.hederahashgraph.api.proto.java.AccountID; import com.hederahashgraph.api.proto.java.TokenSupplyType; import com.hederahashgraph.api.proto.java.TokenType; import java.math.BigInteger; import java.util.List; import java.util.OptionalLong; +import java.util.stream.Stream; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; +import org.junit.jupiter.api.DynamicTest; import org.junit.jupiter.api.Tag; -@HapiTestSuite @Tag(SMART_CONTRACT) -public class CryptoTransferHTSSuite extends HapiSuite { +public class CryptoTransferHTSSuite { private static final Logger log = LogManager.getLogger(CryptoTransferHTSSuite.class); @@ -150,37 +153,8 @@ public class CryptoTransferHTSSuite extends HapiSuite { private static final String CRYPTO_TRANSFER_TXN = "cryptoTransferTxn"; private static final String SPENDER = "spender"; - public static void main(final String... args) { - new CryptoTransferHTSSuite().runSuiteAsync(); - } - - @Override - public boolean canRunConcurrent() { - return true; - } - - @Override - public List getSpecsInSuite() { - return List.of( - nonNestedCryptoTransferForFungibleTokenWithMultipleReceivers(), - nonNestedCryptoTransferForNonFungibleToken(), - nonNestedCryptoTransferForMultipleNonFungibleTokens(), - nonNestedCryptoTransferForFungibleAndNonFungibleToken(), - nonNestedCryptoTransferForFungibleTokenWithMultipleSendersAndReceiversAndNonFungibleTokens(), - repeatedTokenIdsAreAutomaticallyConsolidated(), - hapiTransferFromForFungibleToken(), - hapiTransferFromForFungibleTokenToSystemAccountsFails(), - hapiTransferFromForNFT(), - hapiTransferFromForNFTWithCustomFeesWithoutApproveFails(), - hapiTransferFromForFungibleTokenWithCustomFeesWithoutApproveFails(), - hapiTransferFromForFungibleTokenWithCustomFeesWithBothApproveForAllAndAssignedSpender(), - hapiTransferFromForNFTWithInvalidAddressesFails(), - hapiTransferFromForFungibleTokenWithInvalidAddressesFails(), - hapiTransferFromForFungibleTokenWithInvalidAmountsFails()); - } - @HapiTest - final HapiSpec hapiTransferFromForFungibleToken() { + final Stream hapiTransferFromForFungibleToken() { final var allowance = 10L; final var successfulTransferFromTxn = "txn"; final var successfulTransferFromTxn2 = "txn2"; @@ -363,7 +337,7 @@ final HapiSpec hapiTransferFromForFungibleToken() { } @HapiTest - final HapiSpec hapiTransferFromForFungibleTokenToSystemAccountsFails() { + final Stream hapiTransferFromForFungibleTokenToSystemAccountsFails() { final var UPPER_BOUND_SYSTEM_ADDRESS = 750L; final var ADDRESS_ONE = 1L; final var NON_EXISTING_SYSTEM_ADDRESS = 345L; @@ -485,7 +459,7 @@ final HapiSpec hapiTransferFromForFungibleTokenToSystemAccountsFails() { } @HapiTest - final HapiSpec hapiTransferFromForNFTWithInvalidAddressesFails() { + final Stream hapiTransferFromForNFTWithInvalidAddressesFails() { final var NON_EXISTING_ADDRESS = "0x0000000000000000000000000000000000123456"; final var TXN_TO_NON_EXISTING_ADDRESS = "TXN_TO_NON_EXISTING_ADDRESS"; final var TXN_FROM_NON_EXISTING_ADDRESS = "TXN_FROM_NON_EXISTING_ADDRESS"; @@ -572,7 +546,7 @@ final HapiSpec hapiTransferFromForNFTWithInvalidAddressesFails() { } @HapiTest - final HapiSpec hapiTransferFromForFungibleTokenWithInvalidAddressesFails() { + final Stream hapiTransferFromForFungibleTokenWithInvalidAddressesFails() { final var NON_EXISTING_ADDRESS = "0x0000000000000000000000000000000000123456"; final var TXN_TO_NON_EXISTING_ADDRESS = "TXN_TO_NON_EXISTING_ADDRESS"; final var TXN_FROM_NON_EXISTING_ADDRESS = "TXN_FROM_NON_EXISTING_ADDRESS"; @@ -652,7 +626,7 @@ final HapiSpec hapiTransferFromForFungibleTokenWithInvalidAddressesFails() { } @HapiTest - final HapiSpec hapiTransferFromForFungibleTokenWithInvalidAmountsFails() { + final Stream hapiTransferFromForFungibleTokenWithInvalidAmountsFails() { final var TXN_WITH_AMOUNT_BIGGER_THAN_BALANCE = "TXN_WITH_AMOUNT_BIGGER_THAN_BALANCE"; final var TXN_WITH_AMOUNT_BIGGER_THAN_ALLOWANCE = "TXN_WITH_AMOUNT_BIGGER_THAN_ALLOWANCE"; final var TXN_WITH_AMOUNT_UNDERFLOW_UINT = "TXN_WITH_AMOUNT_UNDERFLOW_UINT"; @@ -762,7 +736,7 @@ final HapiSpec hapiTransferFromForFungibleTokenWithInvalidAmountsFails() { } @HapiTest - final HapiSpec hapiTransferFromForNFT() { + final Stream hapiTransferFromForNFT() { final var successfulTransferFromTxn = "txn"; final var revertingTransferFromTxn = "revertWhenMoreThanAllowance"; return defaultHapiSpec( @@ -862,7 +836,7 @@ final HapiSpec hapiTransferFromForNFT() { } @HapiTest - final HapiSpec repeatedTokenIdsAreAutomaticallyConsolidated() { + final Stream repeatedTokenIdsAreAutomaticallyConsolidated() { final var repeatedIdsPrecompileXferTxn = "repeatedIdsPrecompileXfer"; final var senderStartBalance = 200L; final var receiverStartBalance = 0L; @@ -942,7 +916,7 @@ final HapiSpec repeatedTokenIdsAreAutomaticallyConsolidated() { } @HapiTest - final HapiSpec nonNestedCryptoTransferForFungibleTokenWithMultipleReceivers() { + final Stream nonNestedCryptoTransferForFungibleTokenWithMultipleReceivers() { final var cryptoTransferTxn = CRYPTO_TRANSFER_TXN; return defaultHapiSpec( @@ -1015,7 +989,7 @@ final HapiSpec nonNestedCryptoTransferForFungibleTokenWithMultipleReceivers() { } @HapiTest - final HapiSpec nonNestedCryptoTransferForNonFungibleToken() { + final Stream nonNestedCryptoTransferForNonFungibleToken() { final var cryptoTransferTxn = CRYPTO_TRANSFER_TXN; return defaultHapiSpec( @@ -1085,7 +1059,7 @@ final HapiSpec nonNestedCryptoTransferForNonFungibleToken() { } @HapiTest - final HapiSpec nonNestedCryptoTransferForMultipleNonFungibleTokens() { + final Stream nonNestedCryptoTransferForMultipleNonFungibleTokens() { final var cryptoTransferTxn = CRYPTO_TRANSFER_TXN; return defaultHapiSpec( @@ -1172,7 +1146,7 @@ final HapiSpec nonNestedCryptoTransferForMultipleNonFungibleTokens() { } @HapiTest - final HapiSpec nonNestedCryptoTransferForFungibleAndNonFungibleToken() { + final Stream nonNestedCryptoTransferForFungibleAndNonFungibleToken() { final var cryptoTransferTxn = CRYPTO_TRANSFER_TXN; return defaultHapiSpec( @@ -1279,7 +1253,8 @@ final HapiSpec nonNestedCryptoTransferForFungibleAndNonFungibleToken() { } @HapiTest - final HapiSpec nonNestedCryptoTransferForFungibleTokenWithMultipleSendersAndReceiversAndNonFungibleTokens() { + final Stream + nonNestedCryptoTransferForFungibleTokenWithMultipleSendersAndReceiversAndNonFungibleTokens() { final var cryptoTransferTxn = CRYPTO_TRANSFER_TXN; return defaultHapiSpec( @@ -1403,7 +1378,7 @@ final HapiSpec nonNestedCryptoTransferForFungibleTokenWithMultipleSendersAndRece } @HapiTest - final HapiSpec hapiTransferFromForNFTWithCustomFeesWithoutApproveFails() { + final Stream hapiTransferFromForNFTWithCustomFeesWithoutApproveFails() { return defaultHapiSpec( "HapiTransferFromForNFTWithCustomFeesWithoutApproveFails", NONDETERMINISTIC_FUNCTION_PARAMETERS, @@ -1550,7 +1525,7 @@ final HapiSpec hapiTransferFromForNFTWithCustomFeesWithoutApproveFails() { } @HapiTest - final HapiSpec hapiTransferFromForFungibleTokenWithCustomFeesWithoutApproveFails() { + final Stream hapiTransferFromForFungibleTokenWithCustomFeesWithoutApproveFails() { final var FUNGIBLE_TOKEN_WITH_FIXED_HBAR_FEE = "fungibleTokenWithFixedHbarFee"; final var FUNGIBLE_TOKEN_WITH_FIXED_TOKEN_FEE = "fungibleTokenWithFixedTokenFee"; final var FUNGIBLE_TOKEN_WITH_FRACTIONAL_FEE = "fungibleTokenWithFractionalTokenFee"; @@ -1650,7 +1625,7 @@ final HapiSpec hapiTransferFromForFungibleTokenWithCustomFeesWithoutApproveFails } @HapiTest - final HapiSpec hapiTransferFromForFungibleTokenWithCustomFeesWithBothApproveForAllAndAssignedSpender() { + final Stream hapiTransferFromForFungibleTokenWithCustomFeesWithBothApproveForAllAndAssignedSpender() { final var FUNGIBLE_TOKEN_WITH_FIXED_HBAR_FEE = "fungibleTokenWithFixedHbarFee"; final var FUNGIBLE_TOKEN_WITH_FIXED_TOKEN_FEE = "fungibleTokenWithFixedTokenFee"; final var FUNGIBLE_TOKEN_WITH_FRACTIONAL_FEE = "fungibleTokenWithFractionalTokenFee"; @@ -1756,9 +1731,4 @@ final HapiSpec hapiTransferFromForFungibleTokenWithCustomFeesWithBothApproveForA .alsoSigningWithFullPrefix(RECEIVER_SIGNATURE)))) .then(); } - - @Override - protected Logger getResultsLogger() { - return log; - } } diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/precompile/CryptoTransferHTSV1SecurityModelSuite.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/precompile/CryptoTransferHTSV1SecurityModelSuite.java index 6c2c2f008317..59f8afb988d8 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/precompile/CryptoTransferHTSV1SecurityModelSuite.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/precompile/CryptoTransferHTSV1SecurityModelSuite.java @@ -65,7 +65,6 @@ import com.esaulpaugh.headlong.abi.Tuple; import com.google.protobuf.ByteString; import com.hedera.node.app.hapi.utils.ByteStringUtils; -import com.hedera.services.bdd.spec.HapiSpec; import com.hedera.services.bdd.spec.assertions.NonFungibleTransfers; import com.hedera.services.bdd.spec.assertions.SomeFungibleTransfers; import com.hedera.services.bdd.spec.keys.KeyShape; @@ -76,8 +75,10 @@ import com.hederahashgraph.api.proto.java.TokenType; import java.math.BigInteger; import java.util.List; +import java.util.stream.Stream; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; +import org.junit.jupiter.api.DynamicTest; @SuppressWarnings("java:S1192") // "string literal should not be duplicated" - this rule makes test suites worse public class CryptoTransferHTSV1SecurityModelSuite extends HapiSuite { @@ -132,7 +133,7 @@ public boolean canRunConcurrent() { } @Override - public List getSpecsInSuite() { + public List> getSpecsInSuite() { return List.of( nonNestedCryptoTransferForFungibleToken(), activeContractInFrameIsVerifiedWithoutNeedForSignature(), @@ -141,7 +142,7 @@ public List getSpecsInSuite() { hapiTransferFromForNFTWithCustomFeesWithBothApproveForAllAndAssignedSpender()); } - final HapiSpec nonNestedCryptoTransferForFungibleToken() { + final Stream nonNestedCryptoTransferForFungibleToken() { final var cryptoTransferTxn = CRYPTO_TRANSFER_TXN; return propertyPreservingHapiSpec("nonNestedCryptoTransferForFungibleToken") @@ -216,7 +217,7 @@ final HapiSpec nonNestedCryptoTransferForFungibleToken() { .including(FUNGIBLE_TOKEN, RECEIVER, 50)))); } - final HapiSpec activeContractInFrameIsVerifiedWithoutNeedForSignature() { + final Stream activeContractInFrameIsVerifiedWithoutNeedForSignature() { final var revertedFungibleTransferTxn = "revertedFungibleTransferTxn"; final var successfulFungibleTransferTxn = "successfulFungibleTransferTxn"; final var revertedNftTransferTxn = "revertedNftTransferTxn"; @@ -390,7 +391,7 @@ final HapiSpec activeContractInFrameIsVerifiedWithoutNeedForSignature() { .including(NFT_TOKEN, CONTRACT, RECEIVER, 2L)))); } - final HapiSpec cryptoTransferNFTsWithCustomFeesMixedScenario() { + final Stream cryptoTransferNFTsWithCustomFeesMixedScenario() { final var SPENDER_SIGNATURE = "spenderSignature"; return propertyPreservingHapiSpec("cryptoTransferNFTsWithCustomFeesMixedScenario") .preserving(CONTRACTS_MAX_NUM_WITH_HAPI_SIGS_ACCESS) @@ -532,7 +533,7 @@ final HapiSpec cryptoTransferNFTsWithCustomFeesMixedScenario() { .then(); } - final HapiSpec hapiTransferFromForNFTWithCustomFeesWithApproveForAll() { + final Stream hapiTransferFromForNFTWithCustomFeesWithApproveForAll() { return propertyPreservingHapiSpec("hapiTransferFromForNFTWithCustomFeesWithApproveForAll") .preserving(CONTRACTS_MAX_NUM_WITH_HAPI_SIGS_ACCESS) .given( @@ -692,7 +693,7 @@ final HapiSpec hapiTransferFromForNFTWithCustomFeesWithApproveForAll() { .then(); } - final HapiSpec hapiTransferFromForNFTWithCustomFeesWithBothApproveForAllAndAssignedSpender() { + final Stream hapiTransferFromForNFTWithCustomFeesWithBothApproveForAllAndAssignedSpender() { return propertyPreservingHapiSpec("hapiTransferFromForNFTWithCustomFeesWithBothApproveForAllAndAssignedSpender") .preserving(CONTRACTS_MAX_NUM_WITH_HAPI_SIGS_ACCESS) .given( diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/precompile/DefaultTokenStatusSuite.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/precompile/DefaultTokenStatusSuite.java index c1f67bdc1a3d..afd88a6f1abd 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/precompile/DefaultTokenStatusSuite.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/precompile/DefaultTokenStatusSuite.java @@ -33,6 +33,8 @@ import static com.hedera.services.bdd.spec.utilops.records.SnapshotMatchMode.NONDETERMINISTIC_CONTRACT_CALL_RESULTS; import static com.hedera.services.bdd.spec.utilops.records.SnapshotMatchMode.NONDETERMINISTIC_FUNCTION_PARAMETERS; import static com.hedera.services.bdd.spec.utilops.records.SnapshotMatchMode.NONDETERMINISTIC_TRANSACTION_FEES; +import static com.hedera.services.bdd.suites.HapiSuite.ONE_HUNDRED_HBARS; +import static com.hedera.services.bdd.suites.HapiSuite.TOKEN_TREASURY; import static com.hedera.services.bdd.suites.contract.Utils.asAddress; import static com.hedera.services.bdd.suites.contract.Utils.asToken; import static com.hedera.services.bdd.suites.token.TokenAssociationSpecs.VANILLA_TOKEN; @@ -42,22 +44,15 @@ import com.hedera.node.app.hapi.utils.contracts.ParsingConstants.FunctionType; import com.hedera.services.bdd.junit.HapiTest; -import com.hedera.services.bdd.junit.HapiTestSuite; -import com.hedera.services.bdd.spec.HapiSpec; import com.hedera.services.bdd.spec.transactions.contract.HapiParserUtil; -import com.hedera.services.bdd.suites.HapiSuite; import com.hederahashgraph.api.proto.java.TokenID; -import java.util.List; import java.util.concurrent.atomic.AtomicReference; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; +import java.util.stream.Stream; +import org.junit.jupiter.api.DynamicTest; import org.junit.jupiter.api.Tag; -@HapiTestSuite(fuzzyMatch = true) @Tag(SMART_CONTRACT) -public class DefaultTokenStatusSuite extends HapiSuite { - - private static final Logger log = LogManager.getLogger(DefaultTokenStatusSuite.class); +public class DefaultTokenStatusSuite { private static final String TOKEN_DEFAULT_KYC_FREEZE_STATUS_CONTRACT = "TokenDefaultKycAndFreezeStatus"; private static final String ACCOUNT = "anybody"; private static final String KYC_KEY = "kycKey"; @@ -66,29 +61,9 @@ public class DefaultTokenStatusSuite extends HapiSuite { private static final String GET_TOKEN_DEFAULT_FREEZE = "getTokenDefaultFreeze"; private static final String GET_TOKEN_DEFAULT_KYC = "getTokenDefaultKyc"; - public static void main(String... args) { - new DefaultTokenStatusSuite().runSuiteAsync(); - } - - @Override - protected Logger getResultsLogger() { - return log; - } - - @Override - public List getSpecsInSuite() { - return List.of(getTokenDefaultFreezeStatus(), getTokenDefaultKycStatus()); - } - - @Override - public boolean canRunConcurrent() { - return true; - } - @HapiTest - final HapiSpec getTokenDefaultFreezeStatus() { + final Stream getTokenDefaultFreezeStatus() { final AtomicReference vanillaTokenID = new AtomicReference<>(); - final var notAnAddress = new byte[20]; return defaultHapiSpec( "GetTokenDefaultFreezeStatus", @@ -154,7 +129,7 @@ final HapiSpec getTokenDefaultFreezeStatus() { } @HapiTest - final HapiSpec getTokenDefaultKycStatus() { + final Stream getTokenDefaultKycStatus() { final AtomicReference vanillaTokenID = new AtomicReference<>(); final var notAnAddress = new byte[20]; diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/precompile/DelegatePrecompileSuite.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/precompile/DelegatePrecompileSuite.java index ff54d8520363..133f74215222 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/precompile/DelegatePrecompileSuite.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/precompile/DelegatePrecompileSuite.java @@ -46,6 +46,7 @@ import static com.hedera.services.bdd.spec.utilops.records.SnapshotMatchMode.NONDETERMINISTIC_CONSTRUCTOR_PARAMETERS; import static com.hedera.services.bdd.spec.utilops.records.SnapshotMatchMode.NONDETERMINISTIC_FUNCTION_PARAMETERS; import static com.hedera.services.bdd.spec.utilops.records.SnapshotMatchMode.NONDETERMINISTIC_TRANSACTION_FEES; +import static com.hedera.services.bdd.suites.HapiSuite.GENESIS; import static com.hedera.services.bdd.suites.contract.Utils.asAddress; import static com.hedera.services.bdd.suites.contract.Utils.getNestedContractAddress; import static com.hedera.services.bdd.suites.token.TokenAssociationSpecs.VANILLA_TOKEN; @@ -54,27 +55,20 @@ import com.hedera.node.app.hapi.utils.contracts.ParsingConstants.FunctionType; import com.hedera.services.bdd.junit.HapiTest; -import com.hedera.services.bdd.junit.HapiTestSuite; -import com.hedera.services.bdd.spec.HapiSpec; import com.hedera.services.bdd.spec.keys.KeyShape; import com.hedera.services.bdd.spec.transactions.contract.HapiParserUtil; -import com.hedera.services.bdd.suites.HapiSuite; import com.hederahashgraph.api.proto.java.AccountID; import com.hederahashgraph.api.proto.java.TokenID; import com.hederahashgraph.api.proto.java.TokenType; import java.math.BigInteger; import java.util.List; import java.util.concurrent.atomic.AtomicReference; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; +import java.util.stream.Stream; +import org.junit.jupiter.api.DynamicTest; import org.junit.jupiter.api.Tag; -@HapiTestSuite(fuzzyMatch = true) @Tag(SMART_CONTRACT) -public class DelegatePrecompileSuite extends HapiSuite { - - private static final Logger log = LogManager.getLogger(DelegatePrecompileSuite.class); - +public class DelegatePrecompileSuite { private static final long GAS_TO_OFFER = 4_000_000L; private static final KeyShape SIMPLE_AND_DELEGATE_KEY_SHAPE = KeyShape.threshOf(1, KeyShape.SIMPLE, DELEGATE_CONTRACT); @@ -90,22 +84,8 @@ public class DelegatePrecompileSuite extends HapiSuite { private static final String DELEGATE_BURN_CALL_WITH_DELEGATE_CONTRACT_KEY_TXN = "delegateBurnCallWithDelegateContractKeyTxn"; - public static void main(String... args) { - new DelegatePrecompileSuite().runSuiteAsync(); - } - - @Override - public boolean canRunConcurrent() { - return true; - } - - @Override - public List getSpecsInSuite() { - return List.of(delegateCallForTransfer(), delegateCallForBurn(), delegateCallForMint()); - } - @HapiTest - final HapiSpec delegateCallForTransfer() { + final Stream delegateCallForTransfer() { final AtomicReference accountID = new AtomicReference<>(); final AtomicReference vanillaTokenTokenID = new AtomicReference<>(); final AtomicReference receiverID = new AtomicReference<>(); @@ -128,7 +108,9 @@ final HapiSpec delegateCallForTransfer() { cryptoCreate(ACCOUNT).exposingCreatedIdTo(accountID::set), cryptoCreate(RECEIVER).exposingCreatedIdTo(receiverID::set), uploadInitCode(OUTER_CONTRACT, NESTED_CONTRACT), - contractCreate(NESTED_CONTRACT), + // Refusing ethereum create conversion, because we get INVALID_SIGNATURE upon tokenAssociate, + // since we have CONTRACT_ID key + contractCreate(NESTED_CONTRACT).refusingEthConversion(), tokenAssociate(NESTED_CONTRACT, VANILLA_TOKEN), tokenAssociate(ACCOUNT, VANILLA_TOKEN), tokenAssociate(RECEIVER, VANILLA_TOKEN), @@ -137,7 +119,12 @@ final HapiSpec delegateCallForTransfer() { .when(withOpContext((spec, opLog) -> allRunFor( spec, contractCreate( - OUTER_CONTRACT, asHeadlongAddress(getNestedContractAddress(NESTED_CONTRACT, spec))), + OUTER_CONTRACT, + asHeadlongAddress(getNestedContractAddress(NESTED_CONTRACT, spec))) + // Refusing ethereum create conversion, because we get INVALID_SIGNATURE upon + // tokenAssociate, + // since we have CONTRACT_ID key + .refusingEthConversion(), tokenAssociate(OUTER_CONTRACT, VANILLA_TOKEN), newKeyNamed(SIMPLE_AND_DELEGATE_KEY_NAME) .shape(SIMPLE_AND_DELEGATE_KEY_SHAPE.signedWith(sigs(ON, OUTER_CONTRACT))), @@ -166,7 +153,7 @@ OUTER_CONTRACT, asHeadlongAddress(getNestedContractAddress(NESTED_CONTRACT, spec } @HapiTest - final HapiSpec delegateCallForBurn() { + final Stream delegateCallForBurn() { final AtomicReference vanillaTokenTokenID = new AtomicReference<>(); return defaultHapiSpec( @@ -219,7 +206,7 @@ OUTER_CONTRACT, asHeadlongAddress(getNestedContractAddress(NESTED_CONTRACT, spec } @HapiTest - final HapiSpec delegateCallForMint() { + final Stream delegateCallForMint() { final AtomicReference vanillaTokenTokenID = new AtomicReference<>(); return defaultHapiSpec( @@ -270,9 +257,4 @@ OUTER_CONTRACT, asHeadlongAddress(getNestedContractAddress(NESTED_CONTRACT, spec .newTotalSupply(51)), getAccountBalance(TOKEN_TREASURY).hasTokenBalance(VANILLA_TOKEN, 51)); } - - @Override - protected Logger getResultsLogger() { - return log; - } } diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/precompile/DeleteTokenPrecompileSuite.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/precompile/DeleteTokenPrecompileSuite.java index 7848153aedc1..cb7d3a54ad55 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/precompile/DeleteTokenPrecompileSuite.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/precompile/DeleteTokenPrecompileSuite.java @@ -44,6 +44,9 @@ import static com.hedera.services.bdd.spec.utilops.UtilVerbs.childRecordsCheck; import static com.hedera.services.bdd.spec.utilops.UtilVerbs.newKeyNamed; import static com.hedera.services.bdd.spec.utilops.UtilVerbs.withOpContext; +import static com.hedera.services.bdd.suites.HapiSuite.GENESIS; +import static com.hedera.services.bdd.suites.HapiSuite.ONE_HBAR; +import static com.hedera.services.bdd.suites.HapiSuite.TOKEN_TREASURY; import static com.hedera.services.bdd.suites.contract.Utils.asHexedAddress; import static com.hedera.services.bdd.suites.token.TokenAssociationSpecs.VANILLA_TOKEN; import static com.hedera.services.bdd.suites.utils.contracts.precompile.HTSPrecompileResult.htsPrecompileResult; @@ -54,24 +57,17 @@ import static com.hederahashgraph.api.proto.java.TokenType.NON_FUNGIBLE_UNIQUE; import com.hedera.services.bdd.junit.HapiTest; -import com.hedera.services.bdd.junit.HapiTestSuite; -import com.hedera.services.bdd.spec.HapiSpec; import com.hedera.services.bdd.spec.keys.KeyShape; -import com.hedera.services.bdd.suites.HapiSuite; import com.hederahashgraph.api.proto.java.AccountID; import com.hederahashgraph.api.proto.java.TokenID; import java.util.List; import java.util.concurrent.atomic.AtomicReference; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; +import java.util.stream.Stream; +import org.junit.jupiter.api.DynamicTest; import org.junit.jupiter.api.Tag; -@HapiTestSuite @Tag(SMART_CONTRACT) -public class DeleteTokenPrecompileSuite extends HapiSuite { - - private static final Logger log = LogManager.getLogger(DeleteTokenPrecompileSuite.class); - +public class DeleteTokenPrecompileSuite { private static final long GAS_TO_OFFER = 4_000_000L; public static final String DELETE_TOKEN_CONTRACT = "DeleteTokenContract"; public static final String TOKEN_DELETE_FUNCTION = "tokenDelete"; @@ -82,22 +78,8 @@ public class DeleteTokenPrecompileSuite extends HapiSuite { public static final String THRESHOLD_KEY = "ThreshKey"; private static final KeyShape THRESHOLD_KEY_SHAPE = KeyShape.threshOf(1, ED25519, CONTRACT); - public static void main(String... args) { - new DeleteTokenPrecompileSuite().runSuiteAsync(); - } - - @Override - public boolean canRunConcurrent() { - return true; - } - - @Override - public List getSpecsInSuite() { - return List.of(deleteFungibleToken(), deleteNftToken()); - } - @HapiTest - final HapiSpec deleteFungibleToken() { + final Stream deleteFungibleToken() { final AtomicReference vanillaTokenID = new AtomicReference<>(); final AtomicReference accountID = new AtomicReference<>(); final var tokenAlreadyDeletedTxn = "tokenAlreadyDeletedTxn"; @@ -157,7 +139,7 @@ final HapiSpec deleteFungibleToken() { } @HapiTest - final HapiSpec deleteNftToken() { + final Stream deleteNftToken() { final AtomicReference vanillaTokenID = new AtomicReference<>(); final AtomicReference accountID = new AtomicReference<>(); final var notAnAdminTxn = "notAnAdminTxn"; @@ -211,9 +193,4 @@ final HapiSpec deleteNftToken() { .contractCallResult(htsPrecompileResult() .withStatus(INVALID_FULL_PREFIX_SIGNATURE_FOR_PRECOMPILE))))); } - - @Override - protected Logger getResultsLogger() { - return log; - } } diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/precompile/DeleteTokenPrecompileV1SecurityModelSuite.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/precompile/DeleteTokenPrecompileV1SecurityModelSuite.java index 1c0351979ec9..429929e8a19d 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/precompile/DeleteTokenPrecompileV1SecurityModelSuite.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/precompile/DeleteTokenPrecompileV1SecurityModelSuite.java @@ -51,14 +51,15 @@ import static com.hederahashgraph.api.proto.java.TokenType.FUNGIBLE_COMMON; import static com.hederahashgraph.api.proto.java.TokenType.NON_FUNGIBLE_UNIQUE; -import com.hedera.services.bdd.spec.HapiSpec; import com.hedera.services.bdd.suites.HapiSuite; import com.hederahashgraph.api.proto.java.AccountID; import com.hederahashgraph.api.proto.java.TokenID; import java.util.List; import java.util.concurrent.atomic.AtomicReference; +import java.util.stream.Stream; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; +import org.junit.jupiter.api.DynamicTest; public class DeleteTokenPrecompileV1SecurityModelSuite extends HapiSuite { @@ -82,11 +83,11 @@ public boolean canRunConcurrent() { } @Override - public List getSpecsInSuite() { + public List> getSpecsInSuite() { return List.of(deleteFungibleTokenWithNegativeCases(), deleteNftTokenWithNegativeCases()); } - final HapiSpec deleteFungibleTokenWithNegativeCases() { + final Stream deleteFungibleTokenWithNegativeCases() { final AtomicReference vanillaTokenID = new AtomicReference<>(); final var tokenAlreadyDeletedTxn = "tokenAlreadyDeletedTxn"; @@ -146,7 +147,7 @@ final HapiSpec deleteFungibleTokenWithNegativeCases() { htsPrecompileResult().withStatus(TOKEN_WAS_DELETED))))); } - final HapiSpec deleteNftTokenWithNegativeCases() { + final Stream deleteNftTokenWithNegativeCases() { final AtomicReference vanillaTokenID = new AtomicReference<>(); final var notAnAdminTxn = "notAnAdminTxn"; diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/precompile/DissociatePrecompileSuite.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/precompile/DissociatePrecompileSuite.java index fe4b633f6054..452b7ac7bc78 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/precompile/DissociatePrecompileSuite.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/precompile/DissociatePrecompileSuite.java @@ -37,6 +37,7 @@ import static com.hedera.services.bdd.spec.utilops.UtilVerbs.childRecordsCheck; import static com.hedera.services.bdd.spec.utilops.UtilVerbs.newKeyNamed; import static com.hedera.services.bdd.spec.utilops.UtilVerbs.withOpContext; +import static com.hedera.services.bdd.suites.HapiSuite.TOKEN_TREASURY; import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.CONTRACT_REVERT_EXECUTED; import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.INVALID_ACCOUNT_ID; import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.INVALID_TOKEN_ID; @@ -45,22 +46,16 @@ import com.esaulpaugh.headlong.abi.Address; import com.hedera.services.bdd.junit.HapiTest; -import com.hedera.services.bdd.junit.HapiTestSuite; -import com.hedera.services.bdd.spec.HapiSpec; import com.hedera.services.bdd.spec.keys.KeyShape; -import com.hedera.services.bdd.suites.HapiSuite; import com.hederahashgraph.api.proto.java.TokenType; import java.util.List; import java.util.concurrent.atomic.AtomicReference; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; +import java.util.stream.Stream; +import org.junit.jupiter.api.DynamicTest; import org.junit.jupiter.api.Tag; -@HapiTestSuite @Tag(SMART_CONTRACT) -public class DissociatePrecompileSuite extends HapiSuite { - - private static final Logger log = LogManager.getLogger(DissociatePrecompileSuite.class); +public class DissociatePrecompileSuite { private static final String NEGATIVE_DISSOCIATIONS_CONTRACT = "NegativeDissociationsContract"; private static final long GAS_TO_OFFER = 4_000_000L; private static final String ACCOUNT = "anybody"; @@ -69,26 +64,8 @@ public class DissociatePrecompileSuite extends HapiSuite { private static final String CONTRACT_KEY = "ContractKey"; private static final KeyShape KEY_SHAPE = KeyShape.threshOf(1, ED25519, CONTRACT); - public static void main(String... args) { - new DissociatePrecompileSuite().runSuiteAsync(); - } - - @Override - public boolean canRunConcurrent() { - return true; - } - - @Override - public List getSpecsInSuite() { - return allOf(negativeSpecs()); - } - - List negativeSpecs() { - return List.of(dissociateTokensNegativeScenarios(), dissociateTokenNegativeScenarios()); - } - @HapiTest - final HapiSpec dissociateTokensNegativeScenarios() { + final Stream dissociateTokensNegativeScenarios() { final AtomicReference
    tokenAddress1 = new AtomicReference<>(); final AtomicReference
    tokenAddress2 = new AtomicReference<>(); final AtomicReference
    accountAddress = new AtomicReference<>(); @@ -207,7 +184,7 @@ nonExistingTokenArray, SUCCESS, recordWith().status(SUCCESS)), } @HapiTest - final HapiSpec dissociateTokenNegativeScenarios() { + final Stream dissociateTokenNegativeScenarios() { final AtomicReference
    tokenAddress = new AtomicReference<>(); final AtomicReference
    accountAddress = new AtomicReference<>(); final var nonExistingAccount = "nonExistingAccount"; @@ -286,9 +263,4 @@ final HapiSpec dissociateTokenNegativeScenarios() { CONTRACT_REVERT_EXECUTED, recordWith().status(INVALID_TOKEN_ID))); } - - @Override - protected Logger getResultsLogger() { - return log; - } } diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/precompile/DissociatePrecompileV1SecurityModelSuite.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/precompile/DissociatePrecompileV1SecurityModelSuite.java index f95ddd01b2c9..a6f063c48178 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/precompile/DissociatePrecompileV1SecurityModelSuite.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/precompile/DissociatePrecompileV1SecurityModelSuite.java @@ -56,7 +56,6 @@ import com.esaulpaugh.headlong.abi.Address; import com.google.protobuf.ByteString; -import com.hedera.services.bdd.spec.HapiSpec; import com.hedera.services.bdd.spec.transactions.contract.HapiParserUtil; import com.hedera.services.bdd.suites.HapiSuite; import com.hederahashgraph.api.proto.java.AccountID; @@ -66,8 +65,10 @@ import java.nio.charset.StandardCharsets; import java.util.List; import java.util.concurrent.atomic.AtomicReference; +import java.util.stream.Stream; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; +import org.junit.jupiter.api.DynamicTest; @SuppressWarnings("java:S1192") // "string literal should not be duplicated" - this rule makes test suites worse public class DissociatePrecompileV1SecurityModelSuite extends HapiSuite { @@ -94,7 +95,7 @@ public boolean canRunConcurrent() { } @Override - public List getSpecsInSuite() { + public List> getSpecsInSuite() { return List.of( dissociatePrecompileHasExpectedSemanticsForDeletedTokens(), nestedDissociateWorksAsExpected(), @@ -102,7 +103,7 @@ public List getSpecsInSuite() { } /* -- Not specifically required in the HTS Precompile Test Plan -- */ - public HapiSpec dissociatePrecompileHasExpectedSemanticsForDeletedTokens() { + final Stream dissociatePrecompileHasExpectedSemanticsForDeletedTokens() { final var tbdUniqToken = "UniqToBeDeleted"; final var zeroBalanceFrozen = "0bFrozen"; final var zeroBalanceUnfrozen = "0bUnfrozen"; @@ -274,7 +275,7 @@ public HapiSpec dissociatePrecompileHasExpectedSemanticsForDeletedTokens() { } /* -- Not specifically required in the HTS Precompile Test Plan -- */ - final HapiSpec nestedDissociateWorksAsExpected() { + final Stream nestedDissociateWorksAsExpected() { final AtomicReference accountID = new AtomicReference<>(); final AtomicReference vanillaTokenID = new AtomicReference<>(); @@ -323,7 +324,7 @@ OUTER_CONTRACT, asHeadlongAddress(getNestedContractAddress(NESTED_CONTRACT, spec } /* -- HSCS-PREC-007 from HTS Precompile Test Plan -- */ - public HapiSpec multiplePrecompileDissociationWithSigsForFungibleWorks() { + final Stream multiplePrecompileDissociationWithSigsForFungibleWorks() { final AtomicReference knowableTokenTokenID = new AtomicReference<>(); final AtomicReference vanillaTokenID = new AtomicReference<>(); final AtomicReference accountID = new AtomicReference<>(); diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/precompile/ERCPrecompileSuite.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/precompile/ERCPrecompileSuite.java index 0113cf12c45f..e7adc3003964 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/precompile/ERCPrecompileSuite.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/precompile/ERCPrecompileSuite.java @@ -58,6 +58,12 @@ import static com.hedera.services.bdd.spec.utilops.records.SnapshotMatchMode.NONDETERMINISTIC_CONTRACT_CALL_RESULTS; import static com.hedera.services.bdd.spec.utilops.records.SnapshotMatchMode.NONDETERMINISTIC_FUNCTION_PARAMETERS; import static com.hedera.services.bdd.spec.utilops.records.SnapshotMatchMode.NONDETERMINISTIC_TRANSACTION_FEES; +import static com.hedera.services.bdd.suites.HapiSuite.DEFAULT_PAYER; +import static com.hedera.services.bdd.suites.HapiSuite.GENESIS; +import static com.hedera.services.bdd.suites.HapiSuite.ONE_HBAR; +import static com.hedera.services.bdd.suites.HapiSuite.ONE_HUNDRED_HBARS; +import static com.hedera.services.bdd.suites.HapiSuite.ONE_MILLION_HBARS; +import static com.hedera.services.bdd.suites.HapiSuite.TOKEN_TREASURY; import static com.hedera.services.bdd.suites.contract.Utils.asAddress; import static com.hedera.services.bdd.suites.contract.Utils.asHexedAddress; import static com.hedera.services.bdd.suites.contract.Utils.asToken; @@ -83,12 +89,9 @@ import com.google.protobuf.ByteString; import com.hedera.node.app.hapi.utils.contracts.ParsingConstants.FunctionType; import com.hedera.services.bdd.junit.HapiTest; -import com.hedera.services.bdd.junit.HapiTestSuite; import com.hedera.services.bdd.spec.HapiPropertySource; -import com.hedera.services.bdd.spec.HapiSpec; import com.hedera.services.bdd.spec.transactions.contract.HapiParserUtil; import com.hedera.services.bdd.spec.transactions.token.TokenMovement; -import com.hedera.services.bdd.suites.HapiSuite; import com.hederahashgraph.api.proto.java.AccountID; import com.hederahashgraph.api.proto.java.TokenID; import com.hederahashgraph.api.proto.java.TokenSupplyType; @@ -97,13 +100,14 @@ import java.nio.charset.StandardCharsets; import java.util.List; import java.util.concurrent.atomic.AtomicReference; +import java.util.stream.Stream; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; +import org.junit.jupiter.api.DynamicTest; import org.junit.jupiter.api.Tag; -@HapiTestSuite(fuzzyMatch = true) @Tag(SMART_CONTRACT) -public class ERCPrecompileSuite extends HapiSuite { +public class ERCPrecompileSuite { private static final Logger log = LogManager.getLogger(ERCPrecompileSuite.class); private static final long GAS_TO_OFFER = 1_000_000L; @@ -164,66 +168,8 @@ public class ERCPrecompileSuite extends HapiSuite { public static final String TRANSFER_SIGNATURE = "Transfer(address,address,uint256)"; private static final String NESTED_ERC_20_CONTRACT = "NestedERC20Contract"; - public static void main(String... args) { - new ERCPrecompileSuite().runSuiteAsync(); - } - - @Override - public boolean canRunConcurrent() { - return true; - } - - @Override - public List getSpecsInSuite() { - return allOf(erc20(), erc721()); - } - - List erc20() { - return List.of( - getErc20TokenName(), - getErc20TokenSymbol(), - getErc20TokenDecimals(), - getErc20TotalSupply(), - getErc20BalanceOfAccount(), - transferErc20Token(), - transferErc20TokenFailWithAccount(), - erc20Allowance(), - erc20Approve(), - someErc20ApproveAllowanceScenariosPass(), - someErc20NegativeTransferFromScenariosPass(), - someErc20ApproveAllowanceScenarioInOneCall(), - getErc20TokenDecimalsFromErc721TokenFails(), - transferErc20TokenReceiverContract(), - directCallsWorkForErc20(), - erc20TransferFromAllowance(), - erc20TransferFromSelf(), - transferErc20TokenFromContractWithNoApproval()); - } - - List erc721() { - return List.of( - getErc721TokenName(), - getErc721Symbol(), - getErc721TokenURI(), - getErc721OwnerOf(), - getErc721BalanceOf(), - getErc721TotalSupply(), - erc721TokenApprove(), - erc721GetApproved(), - getErc721TokenURIFromErc20TokenFails(), - getErc721OwnerOfFromErc20TokenFails(), - directCallsWorkForErc721(), - someErc721ApproveAndRemoveScenariosPass(), - someErc721NegativeTransferFromScenariosPass(), - erc721TransferFromWithApproval(), - erc721TransferFromWithApproveForAll(), - someErc721IsApprovedForAllScenariosPass(), - getErc721IsApprovedForAll(), - someErc721SetApprovedForAllScenariosPass()); - } - @HapiTest - final HapiSpec getErc20TokenName() { + final Stream getErc20TokenName() { return defaultHapiSpec( "getErc20TokenName", NONDETERMINISTIC_TRANSACTION_FEES, NONDETERMINISTIC_FUNCTION_PARAMETERS) .given( @@ -263,7 +209,7 @@ final HapiSpec getErc20TokenName() { } @HapiTest - final HapiSpec getErc20TokenSymbol() { + final Stream getErc20TokenSymbol() { final var tokenSymbol = "F"; final AtomicReference tokenAddr = new AtomicReference<>(); @@ -310,7 +256,7 @@ final HapiSpec getErc20TokenSymbol() { } @HapiTest - final HapiSpec getErc20TokenDecimals() { + final Stream getErc20TokenDecimals() { final var decimals = 10; final var decimalsTxn = "decimalsTxn"; final AtomicReference tokenAddr = new AtomicReference<>(); @@ -362,7 +308,7 @@ final HapiSpec getErc20TokenDecimals() { } @HapiTest - final HapiSpec getErc20TotalSupply() { + final Stream getErc20TotalSupply() { final var totalSupply = 50; final var supplyTxn = "supplyTxn"; final AtomicReference tokenAddr = new AtomicReference<>(); @@ -409,7 +355,7 @@ final HapiSpec getErc20TotalSupply() { } @HapiTest - final HapiSpec getErc20BalanceOfAccount() { + final Stream getErc20BalanceOfAccount() { final var balanceTxn = "balanceTxn"; final var zeroBalanceTxn = "zBalanceTxn"; final AtomicReference tokenAddr = new AtomicReference<>(); @@ -487,7 +433,7 @@ final HapiSpec getErc20BalanceOfAccount() { } @HapiTest - final HapiSpec transferErc20Token() { + final Stream transferErc20Token() { final AtomicReference tokenAddr = new AtomicReference<>(); final AtomicReference accountAddr = new AtomicReference<>(); @@ -511,7 +457,9 @@ final HapiSpec transferErc20Token() { .exposingCreatedIdTo(id -> tokenAddr.set( HapiPropertySource.asHexedSolidityAddress(HapiPropertySource.asToken(id)))), uploadInitCode(ERC_20_CONTRACT), - contractCreate(ERC_20_CONTRACT), + // Refusing ethereum create conversion, because we get INVALID_SIGNATURE upon tokenAssociate, + // since we have CONTRACT_ID key + contractCreate(ERC_20_CONTRACT).refusingEthConversion(), tokenAssociate(ACCOUNT, List.of(FUNGIBLE_TOKEN)), tokenAssociate(RECIPIENT, List.of(FUNGIBLE_TOKEN)), tokenAssociate(ERC_20_CONTRACT, List.of(FUNGIBLE_TOKEN)), @@ -577,7 +525,7 @@ final HapiSpec transferErc20Token() { } @HapiTest - final HapiSpec transferErc20TokenFailWithAccount() { + final Stream transferErc20TokenFailWithAccount() { final AtomicReference tokenAddr = new AtomicReference<>(); final AtomicReference accountAddr = new AtomicReference<>(); @@ -601,7 +549,9 @@ final HapiSpec transferErc20TokenFailWithAccount() { .exposingCreatedIdTo(id -> tokenAddr.set( HapiPropertySource.asHexedSolidityAddress(HapiPropertySource.asToken(id)))), uploadInitCode(ERC_20_CONTRACT), - contractCreate(ERC_20_CONTRACT), + // Refusing ethereum create conversion, because we get INVALID_SIGNATURE upon tokenAssociate, + // since we have CONTRACT_ID key + contractCreate(ERC_20_CONTRACT).refusingEthConversion(), tokenAssociate(ACCOUNT, List.of(FUNGIBLE_TOKEN)), tokenAssociate(RECIPIENT, List.of(FUNGIBLE_TOKEN)), tokenAssociate(ERC_20_CONTRACT, List.of(FUNGIBLE_TOKEN)), @@ -625,7 +575,7 @@ final HapiSpec transferErc20TokenFailWithAccount() { } @HapiTest - final HapiSpec transferErc20TokenReceiverContract() { + final Stream transferErc20TokenReceiverContract() { final var nestedContract = NESTED_ERC_20_CONTRACT; return defaultHapiSpec( @@ -645,8 +595,10 @@ final HapiSpec transferErc20TokenReceiverContract() { .adminKey(MULTI_KEY) .supplyKey(MULTI_KEY), uploadInitCode(ERC_20_CONTRACT, nestedContract), - contractCreate(ERC_20_CONTRACT), - contractCreate(nestedContract), + // Refusing ethereum create conversion, because we get INVALID_SIGNATURE upon tokenAssociate, + // since we have CONTRACT_ID key + contractCreate(ERC_20_CONTRACT).refusingEthConversion(), + contractCreate(nestedContract).refusingEthConversion(), tokenAssociate(ACCOUNT, List.of(FUNGIBLE_TOKEN)), tokenAssociate(RECIPIENT, List.of(FUNGIBLE_TOKEN)), tokenAssociate(ERC_20_CONTRACT, List.of(FUNGIBLE_TOKEN)), @@ -703,7 +655,7 @@ final HapiSpec transferErc20TokenReceiverContract() { } @HapiTest - final HapiSpec transferErc20TokenFromContractWithNoApproval() { + final Stream transferErc20TokenFromContractWithNoApproval() { final var transferFromOtherContractWithSignaturesTxn = "transferFromOtherContractWithSignaturesTxn"; final var nestedContract = NESTED_ERC_20_CONTRACT; @@ -724,8 +676,14 @@ final HapiSpec transferErc20TokenFromContractWithNoApproval() { .supplyKey(MULTI_KEY), uploadInitCode(ERC_20_CONTRACT, nestedContract), newKeyNamed(TRANSFER_SIG_NAME).shape(SIMPLE.signedWith(ON)), - contractCreate(ERC_20_CONTRACT).adminKey(TRANSFER_SIG_NAME), - contractCreate(nestedContract).adminKey(TRANSFER_SIG_NAME)) + // Refusing ethereum create conversion, because we get INVALID_SIGNATURE upon tokenAssociate, + // since we have CONTRACT_ID key + contractCreate(ERC_20_CONTRACT) + .adminKey(TRANSFER_SIG_NAME) + .refusingEthConversion(), + contractCreate(nestedContract) + .adminKey(TRANSFER_SIG_NAME) + .refusingEthConversion()) .when(withOpContext((spec, opLog) -> allRunFor( spec, tokenAssociate(ACCOUNT, List.of(FUNGIBLE_TOKEN)), @@ -775,7 +733,7 @@ final HapiSpec transferErc20TokenFromContractWithNoApproval() { } @HapiTest - final HapiSpec erc20Allowance() { + final Stream erc20Allowance() { return defaultHapiSpec( "erc20Allowance", NONDETERMINISTIC_TRANSACTION_FEES, NONDETERMINISTIC_FUNCTION_PARAMETERS) .given( @@ -830,7 +788,7 @@ final HapiSpec erc20Allowance() { } @HapiTest - final HapiSpec erc20Approve() { + final Stream erc20Approve() { final var approveTxn = "approveTxn"; return defaultHapiSpec( @@ -852,7 +810,9 @@ final HapiSpec erc20Approve() { .adminKey(MULTI_KEY) .supplyKey(MULTI_KEY), uploadInitCode(ERC_20_CONTRACT), - contractCreate(ERC_20_CONTRACT), + // Refusing ethereum create conversion, because we get INVALID_SIGNATURE upon tokenAssociate, + // since we have CONTRACT_ID key + contractCreate(ERC_20_CONTRACT).refusingEthConversion(), tokenAssociate(OWNER, FUNGIBLE_TOKEN), tokenAssociate(ERC_20_CONTRACT, FUNGIBLE_TOKEN)) .when(withOpContext((spec, opLog) -> allRunFor( @@ -875,7 +835,7 @@ final HapiSpec erc20Approve() { } @HapiTest - final HapiSpec getErc20TokenDecimalsFromErc721TokenFails() { + final Stream getErc20TokenDecimalsFromErc721TokenFails() { final var invalidDecimalsTxn = "decimalsFromErc721Txn"; return defaultHapiSpec( @@ -911,7 +871,7 @@ final HapiSpec getErc20TokenDecimalsFromErc721TokenFails() { } @HapiTest - final HapiSpec getErc721TokenName() { + final Stream getErc721TokenName() { return defaultHapiSpec( "getErc721TokenName", HIGHLY_NON_DETERMINISTIC_FEES, NONDETERMINISTIC_FUNCTION_PARAMETERS) .given( @@ -951,7 +911,7 @@ final HapiSpec getErc721TokenName() { } @HapiTest - final HapiSpec getErc721Symbol() { + final Stream getErc721Symbol() { final var tokenSymbol = "N"; return defaultHapiSpec( @@ -993,7 +953,7 @@ final HapiSpec getErc721Symbol() { } @HapiTest - final HapiSpec getErc721TokenURI() { + final Stream getErc721TokenURI() { final var tokenURITxn = "tokenURITxn"; final var nonExistingTokenURITxn = "nonExistingTokenURITxn"; final var ERC721MetadataNonExistingToken = "ERC721Metadata: URI query for nonexistent token"; @@ -1057,7 +1017,7 @@ final HapiSpec getErc721TokenURI() { } @HapiTest - final HapiSpec getErc721TotalSupply() { + final Stream getErc721TotalSupply() { return defaultHapiSpec( "getErc721TotalSupply", NONDETERMINISTIC_TRANSACTION_FEES, NONDETERMINISTIC_FUNCTION_PARAMETERS) .given( @@ -1096,7 +1056,7 @@ final HapiSpec getErc721TotalSupply() { } @HapiTest - final HapiSpec getErc721BalanceOf() { + final Stream getErc721BalanceOf() { final var zeroBalanceOfTxn = "zbalanceOfTxn"; return defaultHapiSpec( @@ -1163,7 +1123,7 @@ final HapiSpec getErc721BalanceOf() { } @HapiTest - final HapiSpec getErc721OwnerOf() { + final Stream getErc721OwnerOf() { final var ownerOfTxn = "ownerOfTxn"; final AtomicReference ownerAddr = new AtomicReference<>(); final AtomicReference tokenAddr = new AtomicReference<>(); @@ -1225,7 +1185,7 @@ ERC_721_CONTRACT, OWNER_OF, asHeadlongAddress(tokenAddr.get()), BigInteger.ONE) // Expects revert @HapiTest - final HapiSpec getErc721TokenURIFromErc20TokenFails() { + final Stream getErc721TokenURIFromErc20TokenFails() { final var invalidTokenURITxn = "tokenURITxnFromErc20"; return defaultHapiSpec( @@ -1260,7 +1220,7 @@ final HapiSpec getErc721TokenURIFromErc20TokenFails() { } @HapiTest - final HapiSpec getErc721OwnerOfFromErc20TokenFails() { + final Stream getErc721OwnerOfFromErc20TokenFails() { final var invalidOwnerOfTxn = "ownerOfTxnFromErc20Token"; return defaultHapiSpec( @@ -1297,7 +1257,7 @@ final HapiSpec getErc721OwnerOfFromErc20TokenFails() { } @HapiTest - final HapiSpec directCallsWorkForErc20() { + final Stream directCallsWorkForErc20() { final AtomicReference tokenNum = new AtomicReference<>(); final var tokenSymbol = "FDFGF"; @@ -1448,7 +1408,7 @@ final HapiSpec directCallsWorkForErc20() { } @HapiTest - final HapiSpec someErc721NegativeTransferFromScenariosPass() { + final Stream someErc721NegativeTransferFromScenariosPass() { final AtomicReference tokenMirrorAddr = new AtomicReference<>(); final AtomicReference contractMirrorAddr = new AtomicReference<>(); final AtomicReference aCivilianMirrorAddr = new AtomicReference<>(); @@ -1467,7 +1427,12 @@ final HapiSpec someErc721NegativeTransferFromScenariosPass() { cryptoCreate(B_CIVILIAN) .exposingCreatedIdTo(id -> bCivilianMirrorAddr.set(asHexedSolidityAddress(id))), uploadInitCode(SOME_ERC_721_SCENARIOS), - contractCreate(SOME_ERC_721_SCENARIOS).adminKey(MULTI_KEY_NAME), + contractCreate(SOME_ERC_721_SCENARIOS) + .adminKey(MULTI_KEY_NAME) + // Refusing ethereum create conversion, because we get INVALID_SIGNATURE upon + // tokenAssociate, + // since we have CONTRACT_ID key + .refusingEthConversion(), tokenCreate(NF_TOKEN) .supplyKey(MULTI_KEY_NAME) .tokenType(NON_FUNGIBLE_UNIQUE) @@ -1562,7 +1527,7 @@ final HapiSpec someErc721NegativeTransferFromScenariosPass() { } @HapiTest - final HapiSpec someErc721ApproveAndRemoveScenariosPass() { + final Stream someErc721ApproveAndRemoveScenariosPass() { final AtomicReference tokenMirrorAddr = new AtomicReference<>(); final AtomicReference aCivilianMirrorAddr = new AtomicReference<>(); final AtomicReference bCivilianMirrorAddr = new AtomicReference<>(); @@ -1581,7 +1546,12 @@ final HapiSpec someErc721ApproveAndRemoveScenariosPass() { cryptoCreate(B_CIVILIAN) .exposingCreatedIdTo(id -> bCivilianMirrorAddr.set(asHexedSolidityAddress(id))), uploadInitCode(SOME_ERC_721_SCENARIOS), - contractCreate(SOME_ERC_721_SCENARIOS).adminKey(MULTI_KEY_NAME), + contractCreate(SOME_ERC_721_SCENARIOS) + .adminKey(MULTI_KEY_NAME) + // Refusing ethereum create conversion, because we get INVALID_SIGNATURE upon + // tokenAssociate, + // since we have CONTRACT_ID key + .refusingEthConversion(), tokenCreate(NF_TOKEN) .supplyKey(MULTI_KEY_NAME) .tokenType(NON_FUNGIBLE_UNIQUE) @@ -1763,7 +1733,7 @@ final HapiSpec someErc721ApproveAndRemoveScenariosPass() { } @HapiTest - final HapiSpec someErc20ApproveAllowanceScenariosPass() { + final Stream someErc20ApproveAllowanceScenariosPass() { final AtomicReference tokenMirrorAddr = new AtomicReference<>(); final AtomicReference contractMirrorAddr = new AtomicReference<>(); final AtomicReference aCivilianMirrorAddr = new AtomicReference<>(); @@ -1782,7 +1752,12 @@ final HapiSpec someErc20ApproveAllowanceScenariosPass() { cryptoCreate(B_CIVILIAN) .exposingCreatedIdTo(id -> bCivilianMirrorAddr.set(asHexedSolidityAddress(id))), uploadInitCode(SOME_ERC_20_SCENARIOS), - contractCreate(SOME_ERC_20_SCENARIOS).adminKey(MULTI_KEY_NAME), + contractCreate(SOME_ERC_20_SCENARIOS) + .adminKey(MULTI_KEY_NAME) + // Refusing ethereum create conversion, because we get INVALID_SIGNATURE upon + // tokenAssociate, + // since we have CONTRACT_ID key + .refusingEthConversion(), tokenCreate(TOKEN) .supplyKey(MULTI_KEY_NAME) .tokenType(FUNGIBLE_COMMON) @@ -1919,7 +1894,7 @@ final HapiSpec someErc20ApproveAllowanceScenariosPass() { } @HapiTest - final HapiSpec someErc20NegativeTransferFromScenariosPass() { + final Stream someErc20NegativeTransferFromScenariosPass() { final AtomicReference tokenMirrorAddr = new AtomicReference<>(); final AtomicReference contractMirrorAddr = new AtomicReference<>(); final AtomicReference aCivilianMirrorAddr = new AtomicReference<>(); @@ -1934,7 +1909,12 @@ final HapiSpec someErc20NegativeTransferFromScenariosPass() { cryptoCreate(B_CIVILIAN) .exposingCreatedIdTo(id -> bCivilianMirrorAddr.set(asHexedSolidityAddress(id))), uploadInitCode(SOME_ERC_20_SCENARIOS), - contractCreate(SOME_ERC_20_SCENARIOS).adminKey(MULTI_KEY_NAME), + contractCreate(SOME_ERC_20_SCENARIOS) + .adminKey(MULTI_KEY_NAME) + // Refusing ethereum create conversion, because we get INVALID_SIGNATURE upon + // tokenAssociate, + // since we have CONTRACT_ID key + .refusingEthConversion(), tokenCreate(TOKEN) .supplyKey(MULTI_KEY_NAME) .tokenType(FUNGIBLE_COMMON) @@ -2051,7 +2031,7 @@ final HapiSpec someErc20NegativeTransferFromScenariosPass() { } @HapiTest - final HapiSpec someErc20ApproveAllowanceScenarioInOneCall() { + final Stream someErc20ApproveAllowanceScenarioInOneCall() { final AtomicReference tokenMirrorAddr = new AtomicReference<>(); final AtomicReference contractMirrorAddr = new AtomicReference<>(); final AtomicReference aCivilianMirrorAddr = new AtomicReference<>(); @@ -2070,7 +2050,12 @@ final HapiSpec someErc20ApproveAllowanceScenarioInOneCall() { cryptoCreate(B_CIVILIAN) .exposingCreatedIdTo(id -> bCivilianMirrorAddr.set(asHexedSolidityAddress(id))), uploadInitCode(SOME_ERC_20_SCENARIOS), - contractCreate(SOME_ERC_20_SCENARIOS).adminKey(MULTI_KEY_NAME), + contractCreate(SOME_ERC_20_SCENARIOS) + .adminKey(MULTI_KEY_NAME) + // Refusing ethereum create conversion, because we get INVALID_SIGNATURE upon + // tokenAssociate, + // since we have CONTRACT_ID key + .refusingEthConversion(), tokenCreate(TOKEN) .supplyKey(MULTI_KEY_NAME) .tokenType(FUNGIBLE_COMMON) @@ -2110,7 +2095,7 @@ final HapiSpec someErc20ApproveAllowanceScenarioInOneCall() { } @HapiTest - final HapiSpec directCallsWorkForErc721() { + final Stream directCallsWorkForErc721() { final AtomicReference tokenNum = new AtomicReference<>(); @@ -2253,7 +2238,7 @@ final HapiSpec directCallsWorkForErc721() { } @HapiTest - final HapiSpec someErc721IsApprovedForAllScenariosPass() { + final Stream someErc721IsApprovedForAllScenariosPass() { final AtomicReference tokenMirrorAddr = new AtomicReference<>(); final AtomicReference contractMirrorAddr = new AtomicReference<>(); final AtomicReference aCivilianMirrorAddr = new AtomicReference<>(); @@ -2269,7 +2254,12 @@ final HapiSpec someErc721IsApprovedForAllScenariosPass() { cryptoCreate(A_CIVILIAN) .exposingCreatedIdTo(id -> aCivilianMirrorAddr.set(asHexedSolidityAddress(id))), uploadInitCode(SOME_ERC_721_SCENARIOS), - contractCreate(SOME_ERC_721_SCENARIOS).adminKey(MULTI_KEY_NAME), + contractCreate(SOME_ERC_721_SCENARIOS) + .adminKey(MULTI_KEY_NAME) + // Refusing ethereum create conversion, because we get INVALID_SIGNATURE upon + // tokenAssociate, + // since we have CONTRACT_ID key + .refusingEthConversion(), tokenCreate(NF_TOKEN) .supplyKey(MULTI_KEY_NAME) .tokenType(NON_FUNGIBLE_UNIQUE) @@ -2383,7 +2373,7 @@ final HapiSpec someErc721IsApprovedForAllScenariosPass() { } @HapiTest - final HapiSpec someErc721SetApprovedForAllScenariosPass() { + final Stream someErc721SetApprovedForAllScenariosPass() { final AtomicReference tokenMirrorAddr = new AtomicReference<>(); final AtomicReference contractMirrorAddr = new AtomicReference<>(); final AtomicReference aCivilianMirrorAddr = new AtomicReference<>(); @@ -2400,7 +2390,12 @@ final HapiSpec someErc721SetApprovedForAllScenariosPass() { cryptoCreate(A_CIVILIAN) .exposingCreatedIdTo(id -> aCivilianMirrorAddr.set(asHexedSolidityAddress(id))), uploadInitCode(SOME_ERC_721_SCENARIOS), - contractCreate(SOME_ERC_721_SCENARIOS).adminKey(MULTI_KEY_NAME), + contractCreate(SOME_ERC_721_SCENARIOS) + .adminKey(MULTI_KEY_NAME) + // Refusing ethereum create conversion, because we get INVALID_SIGNATURE upon + // tokenAssociate, + // since we have CONTRACT_ID key + .refusingEthConversion(), tokenCreate(NF_TOKEN) .supplyKey(MULTI_KEY_NAME) .tokenType(NON_FUNGIBLE_UNIQUE) @@ -2516,7 +2511,7 @@ OPERATOR_DOES_NOT_EXISTS, SUCCESS, recordWith().status(INVALID_ALLOWANCE_SPENDER } @HapiTest - final HapiSpec getErc721IsApprovedForAll() { + final Stream getErc721IsApprovedForAll() { final var notApprovedTxn = "notApprovedTxn"; final var approvedForAllTxn = "approvedForAllTxn"; @@ -2607,7 +2602,7 @@ final HapiSpec getErc721IsApprovedForAll() { } @HapiTest - final HapiSpec erc721TokenApprove() { + final Stream erc721TokenApprove() { return defaultHapiSpec( "erc721TokenApprove", NONDETERMINISTIC_FUNCTION_PARAMETERS, @@ -2625,7 +2620,9 @@ final HapiSpec erc721TokenApprove() { .adminKey(MULTI_KEY) .supplyKey(MULTI_KEY), uploadInitCode(ERC_721_CONTRACT), - contractCreate(ERC_721_CONTRACT), + // Refusing ethereum create conversion, because we get INVALID_SIGNATURE upon tokenAssociate, + // since we have CONTRACT_ID key + contractCreate(ERC_721_CONTRACT).refusingEthConversion(), mintToken(NON_FUNGIBLE_TOKEN, List.of(FIRST_META)), tokenAssociate(ACCOUNT, List.of(NON_FUNGIBLE_TOKEN)), tokenAssociate(RECIPIENT, List.of(NON_FUNGIBLE_TOKEN)), @@ -2649,7 +2646,7 @@ final HapiSpec erc721TokenApprove() { } @HapiTest - final HapiSpec erc721GetApproved() { + final Stream erc721GetApproved() { final var theSpender2 = "spender2"; return defaultHapiSpec( @@ -2711,7 +2708,7 @@ final HapiSpec erc721GetApproved() { } @HapiTest - final HapiSpec erc20TransferFromAllowance() { + final Stream erc20TransferFromAllowance() { final var allowanceTxn2 = "allowanceTxn2"; return defaultHapiSpec( @@ -2732,7 +2729,9 @@ final HapiSpec erc20TransferFromAllowance() { .adminKey(MULTI_KEY) .supplyKey(MULTI_KEY), uploadInitCode(ERC_20_CONTRACT), - contractCreate(ERC_20_CONTRACT), + // Refusing ethereum create conversion, because we get INVALID_SIGNATURE upon tokenAssociate, + // since we have CONTRACT_ID key + contractCreate(ERC_20_CONTRACT).refusingEthConversion(), tokenAssociate(OWNER, FUNGIBLE_TOKEN), tokenAssociate(RECIPIENT, FUNGIBLE_TOKEN), tokenAssociate(ERC_20_CONTRACT, FUNGIBLE_TOKEN), @@ -2811,7 +2810,7 @@ final HapiSpec erc20TransferFromAllowance() { } @HapiTest - final HapiSpec erc20TransferFromSelf() { + final Stream erc20TransferFromSelf() { return defaultHapiSpec("erc20TransferFromSelf", NONDETERMINISTIC_FUNCTION_PARAMETERS) .given( newKeyNamed(MULTI_KEY), @@ -2826,7 +2825,9 @@ final HapiSpec erc20TransferFromSelf() { .adminKey(MULTI_KEY) .supplyKey(MULTI_KEY), uploadInitCode(ERC_20_CONTRACT), - contractCreate(ERC_20_CONTRACT), + // Refusing ethereum create conversion, because we get INVALID_SIGNATURE upon tokenAssociate, + // since we have CONTRACT_ID key + contractCreate(ERC_20_CONTRACT).refusingEthConversion(), tokenAssociate(RECIPIENT, FUNGIBLE_TOKEN), tokenAssociate(ERC_20_CONTRACT, FUNGIBLE_TOKEN), cryptoTransfer(moving(10, FUNGIBLE_TOKEN).between(TOKEN_TREASURY, ERC_20_CONTRACT))) @@ -2857,7 +2858,7 @@ final HapiSpec erc20TransferFromSelf() { } @HapiTest - final HapiSpec erc721TransferFromWithApproval() { + final Stream erc721TransferFromWithApproval() { return defaultHapiSpec( "erc721TransferFromWithApproval", NONDETERMINISTIC_FUNCTION_PARAMETERS, @@ -2876,7 +2877,9 @@ final HapiSpec erc721TransferFromWithApproval() { .adminKey(MULTI_KEY) .supplyKey(MULTI_KEY), uploadInitCode(ERC_721_CONTRACT), - contractCreate(ERC_721_CONTRACT), + // Refusing ethereum create conversion, because we get INVALID_SIGNATURE upon tokenAssociate, + // since we have CONTRACT_ID key + contractCreate(ERC_721_CONTRACT).refusingEthConversion(), tokenAssociate(OWNER, NON_FUNGIBLE_TOKEN), tokenAssociate(SPENDER, NON_FUNGIBLE_TOKEN), tokenAssociate(RECIPIENT, NON_FUNGIBLE_TOKEN), @@ -2940,7 +2943,7 @@ final HapiSpec erc721TransferFromWithApproval() { } @HapiTest - final HapiSpec erc721TransferFromWithApproveForAll() { + final Stream erc721TransferFromWithApproveForAll() { return defaultHapiSpec( "erc721TransferFromWithApproveForAll", NONDETERMINISTIC_TRANSACTION_FEES, @@ -2959,7 +2962,9 @@ final HapiSpec erc721TransferFromWithApproveForAll() { .adminKey(MULTI_KEY) .supplyKey(MULTI_KEY), uploadInitCode(ERC_721_CONTRACT), - contractCreate(ERC_721_CONTRACT), + // Refusing ethereum create conversion, because we get INVALID_SIGNATURE upon tokenAssociate, + // since we have CONTRACT_ID key + contractCreate(ERC_721_CONTRACT).refusingEthConversion(), tokenAssociate(OWNER, NON_FUNGIBLE_TOKEN), tokenAssociate(SPENDER, NON_FUNGIBLE_TOKEN), tokenAssociate(RECIPIENT, NON_FUNGIBLE_TOKEN), @@ -2997,9 +3002,4 @@ final HapiSpec erc721TransferFromWithApproveForAll() { getAccountDetails(OWNER).logged()))) .then(); } - - @Override - protected Logger getResultsLogger() { - return log; - } } diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/precompile/ERCPrecompileV1SecurityModelSuite.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/precompile/ERCPrecompileV1SecurityModelSuite.java index 33996ae8b0ff..33bb3a608229 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/precompile/ERCPrecompileV1SecurityModelSuite.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/precompile/ERCPrecompileV1SecurityModelSuite.java @@ -42,15 +42,16 @@ import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.SUCCESS; import com.esaulpaugh.headlong.abi.Address; -import com.hedera.services.bdd.spec.HapiSpec; import com.hedera.services.bdd.spec.queries.crypto.ExpectedTokenRel; import com.hedera.services.bdd.spec.transactions.contract.HapiParserUtil; import com.hedera.services.bdd.suites.HapiSuite; import java.math.BigInteger; import java.util.List; import java.util.concurrent.atomic.AtomicReference; +import java.util.stream.Stream; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; +import org.junit.jupiter.api.DynamicTest; public class ERCPrecompileV1SecurityModelSuite extends HapiSuite { @@ -73,19 +74,19 @@ public boolean canRunConcurrent() { } @Override - public List getSpecsInSuite() { + public List> getSpecsInSuite() { return allOf(erc20(), erc721()); } - List erc20() { + List> erc20() { return List.of(transferErc20TokenAliasedSender()); } - List erc721() { + List> erc721() { return List.of(); } - final HapiSpec transferErc20TokenAliasedSender() { + final Stream transferErc20TokenAliasedSender() { final var aliasedTransferTxn = "aliasedTransferTxn"; final var addLiquidityTxn = "addLiquidityTxn"; final var create2Txn = "create2Txn"; diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/precompile/FreezeUnfreezeTokenPrecompileSuite.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/precompile/FreezeUnfreezeTokenPrecompileSuite.java index 8e144581d337..dc40ad803bc6 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/precompile/FreezeUnfreezeTokenPrecompileSuite.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/precompile/FreezeUnfreezeTokenPrecompileSuite.java @@ -36,6 +36,11 @@ import static com.hedera.services.bdd.spec.utilops.UtilVerbs.newKeyNamed; import static com.hedera.services.bdd.spec.utilops.UtilVerbs.withOpContext; import static com.hedera.services.bdd.spec.utilops.records.SnapshotMatchMode.NONDETERMINISTIC_FUNCTION_PARAMETERS; +import static com.hedera.services.bdd.suites.HapiSuite.GENESIS; +import static com.hedera.services.bdd.suites.HapiSuite.ONE_HBAR; +import static com.hedera.services.bdd.suites.HapiSuite.ONE_HUNDRED_HBARS; +import static com.hedera.services.bdd.suites.HapiSuite.SECP_256K1_SHAPE; +import static com.hedera.services.bdd.suites.HapiSuite.TOKEN_TREASURY; import static com.hedera.services.bdd.suites.contract.Utils.asAddress; import static com.hedera.services.bdd.suites.contract.Utils.asToken; import static com.hedera.services.bdd.suites.token.TokenAssociationSpecs.VANILLA_TOKEN; @@ -44,23 +49,16 @@ import static com.hederahashgraph.api.proto.java.TokenType.FUNGIBLE_COMMON; import com.hedera.services.bdd.junit.HapiTest; -import com.hedera.services.bdd.junit.HapiTestSuite; -import com.hedera.services.bdd.spec.HapiSpec; import com.hedera.services.bdd.spec.transactions.contract.HapiParserUtil; -import com.hedera.services.bdd.suites.HapiSuite; import com.hederahashgraph.api.proto.java.AccountID; import com.hederahashgraph.api.proto.java.TokenID; -import java.util.List; import java.util.concurrent.atomic.AtomicReference; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; +import java.util.stream.Stream; +import org.junit.jupiter.api.DynamicTest; import org.junit.jupiter.api.Tag; -@HapiTestSuite(fuzzyMatch = true) @Tag(SMART_CONTRACT) -public class FreezeUnfreezeTokenPrecompileSuite extends HapiSuite { - - private static final Logger log = LogManager.getLogger(FreezeUnfreezeTokenPrecompileSuite.class); +public class FreezeUnfreezeTokenPrecompileSuite { public static final String FREEZE_CONTRACT = "FreezeUnfreezeContract"; private static final String IS_FROZEN_FUNC = "isTokenFrozen"; public static final String TOKEN_FREEZE_FUNC = "tokenFreeze"; @@ -68,30 +66,14 @@ public class FreezeUnfreezeTokenPrecompileSuite extends HapiSuite { private static final String ACCOUNT = "anybody"; private static final String FREEZE_KEY = "freezeKey"; private static final String MULTI_KEY = "purpose"; + private static final String TREASURY = "treasury"; private static final long GAS_TO_OFFER = 4_000_000L; private static final String INVALID_ADDRESS = "0x0000000000000000000000000000000000123456"; - - public static void main(String... args) { - new FreezeUnfreezeTokenPrecompileSuite().runSuiteAsync(); - } - - @Override - public boolean canRunConcurrent() { - return true; - } - - @Override - protected Logger getResultsLogger() { - return log; - } - - @Override - public List getSpecsInSuite() { - return List.of(isFrozenHappyPathWithAliasLocalCall(), noTokenIdReverts()); - } + public static final String PARTY = "party"; + public static final String TOKEN = "token"; @HapiTest - final HapiSpec noTokenIdReverts() { + final Stream noTokenIdReverts() { final AtomicReference vanillaTokenID = new AtomicReference<>(); final AtomicReference accountID = new AtomicReference<>(); return defaultHapiSpec("noTokenIdReverts", NONDETERMINISTIC_FUNCTION_PARAMETERS) @@ -142,7 +124,7 @@ final HapiSpec noTokenIdReverts() { } @HapiTest - final HapiSpec isFrozenHappyPathWithAliasLocalCall() { + final Stream isFrozenHappyPathWithAliasLocalCall() { final AtomicReference vanillaTokenID = new AtomicReference<>(); final AtomicReference autoCreatedAccountId = new AtomicReference<>(); final String accountAlias = "accountAlias"; diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/precompile/FreezeUnfreezeTokenPrecompileV1SecurityModelSuite.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/precompile/FreezeUnfreezeTokenPrecompileV1SecurityModelSuite.java index a832a7581f17..334d5f9981fb 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/precompile/FreezeUnfreezeTokenPrecompileV1SecurityModelSuite.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/precompile/FreezeUnfreezeTokenPrecompileV1SecurityModelSuite.java @@ -56,7 +56,6 @@ import static com.hederahashgraph.api.proto.java.TokenType.NON_FUNGIBLE_UNIQUE; import com.hedera.node.app.hapi.utils.contracts.ParsingConstants; -import com.hedera.services.bdd.spec.HapiSpec; import com.hedera.services.bdd.spec.queries.crypto.ExpectedTokenRel; import com.hedera.services.bdd.spec.transactions.contract.HapiParserUtil; import com.hedera.services.bdd.suites.HapiSuite; @@ -65,8 +64,10 @@ import com.hederahashgraph.api.proto.java.TokenID; import java.util.List; import java.util.concurrent.atomic.AtomicReference; +import java.util.stream.Stream; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; +import org.junit.jupiter.api.DynamicTest; public class FreezeUnfreezeTokenPrecompileV1SecurityModelSuite extends HapiSuite { @@ -101,14 +102,14 @@ protected Logger getResultsLogger() { } @Override - public List getSpecsInSuite() { + public List> getSpecsInSuite() { return List.of( freezeUnfreezeFungibleWithNegativeCases(), freezeUnfreezeNftsWithNegativeCases(), isFrozenHappyPathWithLocalCall()); } - final HapiSpec freezeUnfreezeFungibleWithNegativeCases() { + final Stream freezeUnfreezeFungibleWithNegativeCases() { final AtomicReference withoutKeyID = new AtomicReference<>(); final AtomicReference vanillaTokenID = new AtomicReference<>(); final AtomicReference accountID = new AtomicReference<>(); @@ -222,7 +223,7 @@ final HapiSpec freezeUnfreezeFungibleWithNegativeCases() { htsPrecompileResult().withStatus(TOKEN_HAS_NO_FREEZE_KEY))))); } - final HapiSpec freezeUnfreezeNftsWithNegativeCases() { + final Stream freezeUnfreezeNftsWithNegativeCases() { final AtomicReference vanillaTokenID = new AtomicReference<>(); final AtomicReference accountID = new AtomicReference<>(); return propertyPreservingHapiSpec("freezeUnfreezeNftsWithNegativeCases") @@ -312,7 +313,7 @@ final HapiSpec freezeUnfreezeNftsWithNegativeCases() { .withIsFrozen(false))))); } - final HapiSpec isFrozenHappyPathWithLocalCall() { + final Stream isFrozenHappyPathWithLocalCall() { final AtomicReference accountID = new AtomicReference<>(); final AtomicReference vanillaTokenID = new AtomicReference<>(); return propertyPreservingHapiSpec("isFrozenHappyPathWithLocalCall") diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/precompile/GrantRevokeKycSuite.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/precompile/GrantRevokeKycSuite.java index 5cc3d6601fff..90068c722a4c 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/precompile/GrantRevokeKycSuite.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/precompile/GrantRevokeKycSuite.java @@ -20,7 +20,12 @@ import static com.hedera.services.bdd.spec.HapiSpec.defaultHapiSpec; import static com.hedera.services.bdd.spec.assertions.ContractFnResultAsserts.resultWith; import static com.hedera.services.bdd.spec.assertions.TransactionRecordAsserts.recordWith; +import static com.hedera.services.bdd.spec.keys.KeyShape.CONTRACT; +import static com.hedera.services.bdd.spec.keys.KeyShape.ED25519; +import static com.hedera.services.bdd.spec.keys.KeyShape.ON; +import static com.hedera.services.bdd.spec.keys.KeyShape.sigs; import static com.hedera.services.bdd.spec.queries.QueryVerbs.contractCallLocal; +import static com.hedera.services.bdd.spec.queries.QueryVerbs.getAccountDetails; import static com.hedera.services.bdd.spec.queries.QueryVerbs.getAliasedAccountInfo; import static com.hedera.services.bdd.spec.transactions.TxnVerbs.contractCall; import static com.hedera.services.bdd.spec.transactions.TxnVerbs.contractCreate; @@ -29,6 +34,7 @@ import static com.hedera.services.bdd.spec.transactions.TxnVerbs.cryptoUpdate; import static com.hedera.services.bdd.spec.transactions.TxnVerbs.tokenAssociate; import static com.hedera.services.bdd.spec.transactions.TxnVerbs.tokenCreate; +import static com.hedera.services.bdd.spec.transactions.TxnVerbs.tokenUpdate; import static com.hedera.services.bdd.spec.transactions.TxnVerbs.uploadInitCode; import static com.hedera.services.bdd.spec.transactions.crypto.HapiCryptoTransfer.tinyBarsFromAccountToAlias; import static com.hedera.services.bdd.spec.utilops.CustomSpecAssert.allRunFor; @@ -36,6 +42,11 @@ import static com.hedera.services.bdd.spec.utilops.UtilVerbs.newKeyNamed; import static com.hedera.services.bdd.spec.utilops.UtilVerbs.withOpContext; import static com.hedera.services.bdd.spec.utilops.records.SnapshotMatchMode.NONDETERMINISTIC_FUNCTION_PARAMETERS; +import static com.hedera.services.bdd.suites.HapiSuite.GENESIS; +import static com.hedera.services.bdd.suites.HapiSuite.ONE_HBAR; +import static com.hedera.services.bdd.suites.HapiSuite.ONE_HUNDRED_HBARS; +import static com.hedera.services.bdd.suites.HapiSuite.SECP_256K1_SHAPE; +import static com.hedera.services.bdd.suites.HapiSuite.TOKEN_TREASURY; import static com.hedera.services.bdd.suites.contract.Utils.asAddress; import static com.hedera.services.bdd.suites.contract.Utils.asToken; import static com.hedera.services.bdd.suites.token.TokenAssociationSpecs.VANILLA_TOKEN; @@ -44,27 +55,25 @@ import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.CONTRACT_REVERT_EXECUTED; import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.INVALID_SIGNATURE; import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.INVALID_TOKEN_ID; +import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.SUCCESS; import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.TOKEN_HAS_NO_KYC_KEY; import static com.hederahashgraph.api.proto.java.TokenType.FUNGIBLE_COMMON; +import com.hedera.node.app.hapi.utils.contracts.ParsingConstants; import com.hedera.services.bdd.junit.HapiTest; -import com.hedera.services.bdd.junit.HapiTestSuite; -import com.hedera.services.bdd.spec.HapiSpec; +import com.hedera.services.bdd.spec.keys.KeyShape; +import com.hedera.services.bdd.spec.queries.crypto.ExpectedTokenRel; import com.hedera.services.bdd.spec.transactions.contract.HapiParserUtil; -import com.hedera.services.bdd.suites.HapiSuite; import com.hederahashgraph.api.proto.java.AccountID; import com.hederahashgraph.api.proto.java.TokenID; -import java.util.List; +import com.hederahashgraph.api.proto.java.TokenKycStatus; import java.util.concurrent.atomic.AtomicReference; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; +import java.util.stream.Stream; +import org.junit.jupiter.api.DynamicTest; import org.junit.jupiter.api.Tag; -@HapiTestSuite(fuzzyMatch = true) @Tag(SMART_CONTRACT) -public class GrantRevokeKycSuite extends HapiSuite { - - private static final Logger log = LogManager.getLogger(GrantRevokeKycSuite.class); +public class GrantRevokeKycSuite { public static final String GRANT_REVOKE_KYC_CONTRACT = "GrantRevokeKyc"; private static final String IS_KYC_GRANTED = "isKycGranted"; public static final String TOKEN_GRANT_KYC = "tokenGrantKyc"; @@ -76,36 +85,12 @@ public class GrantRevokeKycSuite extends HapiSuite { private static final String KYC_KEY = "kycKey"; private static final String NON_KYC_KEY = "nonKycKey"; private static final String TOKEN_WITHOUT_KEY = "withoutKey"; - - public static void main(String... args) { - new GrantRevokeKycSuite().runSuiteAsync(); - } - - @Override - public boolean canRunConcurrent() { - return true; - } - - @Override - protected Logger getResultsLogger() { - return log; - } - - @Override - public List getSpecsInSuite() { - return allOf(positiveSpecs(), negativeSpecs()); - } - - List negativeSpecs() { - return List.of(grantRevokeKycFail()); - } - - List positiveSpecs() { - return List.of(grantRevokeKycSpecWithAliasLocalCall()); - } + private static final String THRESHOLD_KEY = "THRESHOLD_KEY"; + private static final String ADMIN_KEY = "ADMIN_KEY"; + private static final KeyShape THRESHOLD_KEY_SHAPE = KeyShape.threshOf(1, ED25519, CONTRACT); @HapiTest - final HapiSpec grantRevokeKycFail() { + final Stream grantRevokeKycFail() { final AtomicReference vanillaTokenID = new AtomicReference<>(); final AtomicReference accountID = new AtomicReference<>(); final AtomicReference secondAccountID = new AtomicReference<>(); @@ -282,7 +267,7 @@ final HapiSpec grantRevokeKycFail() { } @HapiTest - final HapiSpec grantRevokeKycSpecWithAliasLocalCall() { + final Stream grantRevokeKycSpecWithAliasLocalCall() { final AtomicReference vanillaTokenID = new AtomicReference<>(); final AtomicReference autoCreatedAccountId = new AtomicReference<>(); final String accountAlias = "accountAlias"; @@ -340,4 +325,103 @@ final HapiSpec grantRevokeKycSpecWithAliasLocalCall() { // .withIsFrozen(false))))))) ); } + + @HapiTest + final Stream grantRevokeKycSpec() { + final AtomicReference vanillaTokenID = new AtomicReference<>(); + final AtomicReference accountID = new AtomicReference<>(); + final AtomicReference secondAccountID = new AtomicReference<>(); + + return defaultHapiSpec("grantRevokeKycSpec") + .given( + newKeyNamed(KYC_KEY), + cryptoCreate(ACCOUNT) + .balance(100 * ONE_HBAR) + .key(KYC_KEY) + .exposingCreatedIdTo(accountID::set), + cryptoCreate(SECOND_ACCOUNT).exposingCreatedIdTo(secondAccountID::set), + cryptoCreate(TOKEN_TREASURY), + cryptoCreate(ADMIN_KEY), + tokenCreate(VANILLA_TOKEN) + .tokenType(FUNGIBLE_COMMON) + .treasury(TOKEN_TREASURY) + .adminKey(ADMIN_KEY) + .kycKey(KYC_KEY) + .initialSupply(1_000) + .exposingCreatedIdTo(id -> vanillaTokenID.set(asToken(id))), + uploadInitCode(GRANT_REVOKE_KYC_CONTRACT), + contractCreate(GRANT_REVOKE_KYC_CONTRACT), + tokenAssociate(ACCOUNT, VANILLA_TOKEN), + tokenAssociate(SECOND_ACCOUNT, VANILLA_TOKEN), + newKeyNamed(THRESHOLD_KEY) + .shape(THRESHOLD_KEY_SHAPE.signedWith(sigs(ON, GRANT_REVOKE_KYC_CONTRACT))), + tokenUpdate(VANILLA_TOKEN).kycKey(THRESHOLD_KEY).signedByPayerAnd(ADMIN_KEY)) + .when(withOpContext((spec, opLog) -> allRunFor( + spec, + contractCall( + GRANT_REVOKE_KYC_CONTRACT, + TOKEN_GRANT_KYC, + HapiParserUtil.asHeadlongAddress(asAddress(vanillaTokenID.get())), + HapiParserUtil.asHeadlongAddress(asAddress(secondAccountID.get()))) + .signedBy(ACCOUNT) + .payingWith(ACCOUNT) + .alsoSigningWithFullPrefix(THRESHOLD_KEY) + .via("GrantKycTx") + .gas(GAS_TO_OFFER), + getAccountDetails(SECOND_ACCOUNT) + .hasToken(ExpectedTokenRel.relationshipWith(VANILLA_TOKEN) + .kyc(TokenKycStatus.Granted)), + contractCallLocal( + GRANT_REVOKE_KYC_CONTRACT, + IS_KYC_GRANTED, + HapiParserUtil.asHeadlongAddress(asAddress(vanillaTokenID.get())), + HapiParserUtil.asHeadlongAddress(asAddress(secondAccountID.get()))), + contractCall( + GRANT_REVOKE_KYC_CONTRACT, + TOKEN_REVOKE_KYC, + HapiParserUtil.asHeadlongAddress(asAddress(vanillaTokenID.get())), + HapiParserUtil.asHeadlongAddress(asAddress(secondAccountID.get()))) + .signedBy(ACCOUNT) + .payingWith(ACCOUNT) + .alsoSigningWithFullPrefix(THRESHOLD_KEY) + .via("RevokeKycTx") + .gas(GAS_TO_OFFER), + contractCall( + GRANT_REVOKE_KYC_CONTRACT, + IS_KYC_GRANTED, + HapiParserUtil.asHeadlongAddress(asAddress(vanillaTokenID.get())), + HapiParserUtil.asHeadlongAddress(asAddress(secondAccountID.get()))) + .signedBy(ACCOUNT) + .payingWith(ACCOUNT) + .alsoSigningWithFullPrefix(THRESHOLD_KEY) + .via("IsKycTx") + .gas(GAS_TO_OFFER)))) + .then( + childRecordsCheck( + "GrantKycTx", + SUCCESS, + recordWith() + .status(SUCCESS) + .contractCallResult(resultWith() + .contractCallResult( + htsPrecompileResult().withStatus(SUCCESS)))), + childRecordsCheck( + "RevokeKycTx", + SUCCESS, + recordWith() + .status(SUCCESS) + .contractCallResult(resultWith() + .contractCallResult( + htsPrecompileResult().withStatus(SUCCESS)))), + childRecordsCheck( + "IsKycTx", + SUCCESS, + recordWith() + .status(SUCCESS) + .contractCallResult(resultWith() + .contractCallResult(htsPrecompileResult() + .forFunction(ParsingConstants.FunctionType.HAPI_IS_KYC) + .withIsKyc(false) + .withStatus(SUCCESS))))); + } } diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/precompile/GrantRevokeKycV1SecurityModelSuite.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/precompile/GrantRevokeKycV1SecurityModelSuite.java index 1d7366ccb8f6..51b3bdbc98c9 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/precompile/GrantRevokeKycV1SecurityModelSuite.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/precompile/GrantRevokeKycV1SecurityModelSuite.java @@ -42,7 +42,6 @@ import static com.hederahashgraph.api.proto.java.TokenType.FUNGIBLE_COMMON; import com.hedera.node.app.hapi.utils.contracts.ParsingConstants.FunctionType; -import com.hedera.services.bdd.spec.HapiSpec; import com.hedera.services.bdd.spec.queries.crypto.ExpectedTokenRel; import com.hedera.services.bdd.spec.transactions.contract.HapiParserUtil; import com.hedera.services.bdd.suites.HapiSuite; @@ -51,8 +50,10 @@ import com.hederahashgraph.api.proto.java.TokenKycStatus; import java.util.List; import java.util.concurrent.atomic.AtomicReference; +import java.util.stream.Stream; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; +import org.junit.jupiter.api.DynamicTest; @SuppressWarnings("java:S1192") // "string literal should not be duplicated" - this rule makes test suites worse public class GrantRevokeKycV1SecurityModelSuite extends HapiSuite { @@ -83,19 +84,19 @@ protected Logger getResultsLogger() { } @Override - public List getSpecsInSuite() { + public List> getSpecsInSuite() { return allOf(positiveSpecs(), negativeSpecs()); } - List negativeSpecs() { + List> negativeSpecs() { return List.of(); } - List positiveSpecs() { + List> positiveSpecs() { return List.of(grantRevokeKycSpec()); } - final HapiSpec grantRevokeKycSpec() { + final Stream grantRevokeKycSpec() { final AtomicReference vanillaTokenID = new AtomicReference<>(); final AtomicReference accountID = new AtomicReference<>(); final AtomicReference secondAccountID = new AtomicReference<>(); diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/precompile/HRCPrecompileSuite.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/precompile/HRCPrecompileSuite.java index 839142b8f593..724ff8d310fb 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/precompile/HRCPrecompileSuite.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/precompile/HRCPrecompileSuite.java @@ -26,7 +26,9 @@ import static com.hedera.services.bdd.spec.transactions.TxnVerbs.contractCreate; import static com.hedera.services.bdd.spec.transactions.TxnVerbs.cryptoCreate; import static com.hedera.services.bdd.spec.transactions.TxnVerbs.cryptoTransfer; +import static com.hedera.services.bdd.spec.transactions.TxnVerbs.mintToken; import static com.hedera.services.bdd.spec.transactions.TxnVerbs.tokenCreate; +import static com.hedera.services.bdd.spec.transactions.TxnVerbs.tokenDelete; import static com.hedera.services.bdd.spec.transactions.TxnVerbs.uploadInitCode; import static com.hedera.services.bdd.spec.transactions.contract.HapiParserUtil.asHeadlongAddress; import static com.hedera.services.bdd.spec.transactions.token.TokenMovement.moving; @@ -39,6 +41,8 @@ import static com.hedera.services.bdd.spec.utilops.records.SnapshotMatchMode.NONDETERMINISTIC_FUNCTION_PARAMETERS; import static com.hedera.services.bdd.spec.utilops.records.SnapshotMatchMode.NONDETERMINISTIC_NONCE; import static com.hedera.services.bdd.spec.utilops.records.SnapshotMatchMode.NONDETERMINISTIC_TRANSACTION_FEES; +import static com.hedera.services.bdd.suites.HapiSuite.ONE_HUNDRED_HBARS; +import static com.hedera.services.bdd.suites.HapiSuite.TOKEN_TREASURY; import static com.hedera.services.bdd.suites.contract.Utils.asHexedAddress; import static com.hedera.services.bdd.suites.contract.Utils.asToken; import static com.hedera.services.bdd.suites.contract.Utils.getABIFor; @@ -49,23 +53,20 @@ import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.TOKEN_NOT_ASSOCIATED_TO_ACCOUNT; import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.TRANSACTION_REQUIRES_ZERO_TOKEN_BALANCES; +import com.google.protobuf.ByteString; import com.hedera.services.bdd.junit.HapiTest; -import com.hedera.services.bdd.junit.HapiTestSuite; -import com.hedera.services.bdd.spec.HapiSpec; -import com.hedera.services.bdd.suites.HapiSuite; +import com.hedera.services.bdd.spec.transactions.token.TokenMovement; +import com.hedera.services.bdd.suites.contract.Utils; import com.hederahashgraph.api.proto.java.TokenSupplyType; import com.hederahashgraph.api.proto.java.TokenType; import java.util.List; import java.util.concurrent.atomic.AtomicReference; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; +import java.util.stream.Stream; +import org.junit.jupiter.api.DynamicTest; import org.junit.jupiter.api.Tag; -@HapiTestSuite(fuzzyMatch = true) @Tag(SMART_CONTRACT) -public class HRCPrecompileSuite extends HapiSuite { - - private static final Logger log = LogManager.getLogger(HRCPrecompileSuite.class); +public class HRCPrecompileSuite { private static final String MULTI_KEY = "multikey"; private static final String FUNGIBLE_TOKEN = "fungibleToken"; private static final String FUNGIBLE_TOKEN_2 = "fungibleToken2"; @@ -85,28 +86,73 @@ public class HRCPrecompileSuite extends HapiSuite { private static final String ASSOCIATE = "associate"; private static final String DISSOCIATE = "dissociate"; - public static void main(String... args) { - new HRCPrecompileSuite().runSuiteAsync(); - } - - @Override - public boolean canRunConcurrent() { - return true; - } + @HapiTest + final Stream hrcCanDissociateFromDeletedToken() { + final AtomicReference nonfungibleTokenNum = new AtomicReference<>(); - @Override - public List getSpecsInSuite() { - return List.of( - hrcNftAndFungibleTokenAssociateFromEOA(), - hrcNFTAndFungibleTokenAssociateFromContract(), - hrcTokenAssociateFromSameEOATwiceShouldFail(), - hrcTokenDissociateWhenNotAssociatedShouldFail(), - hrcTokenDissociateWhenBalanceNotZeroShouldFail(), - hrcTooManyTokenAssociateShouldFail()); + return defaultHapiSpec("hrcCanDissociateFromDeletedToken", NONDETERMINISTIC_FUNCTION_PARAMETERS) + .given( + newKeyNamed(MULTI_KEY), + cryptoCreate(ACCOUNT).balance(100 * ONE_HUNDRED_HBARS), + cryptoCreate(TOKEN_TREASURY), + tokenCreate(NON_FUNGIBLE_TOKEN) + .tokenType(TokenType.NON_FUNGIBLE_UNIQUE) + .name(TOKEN_NAME) + .symbol(TOKEN_SYMBOL) + .initialSupply(0) + .treasury(TOKEN_TREASURY) + .adminKey(MULTI_KEY) + .supplyKey(MULTI_KEY) + .exposingCreatedIdTo(nonfungibleTokenNum::set), + mintToken(NON_FUNGIBLE_TOKEN, List.of(ByteString.copyFromUtf8("PRICELESS"))) + .payingWith(ACCOUNT) + .via("mintTxn"), + uploadInitCode(HRC), + contractCreate(HRC)) + .when(withOpContext((spec, opLog) -> { + var nonfungibleTokenAddress = asHexedSolidityAddress(asToken(nonfungibleTokenNum.get())); + allRunFor( + spec, + // Associate non-fungible token + contractCallWithFunctionAbi( + nonfungibleTokenAddress, + getABIFor(Utils.FunctionType.FUNCTION, ASSOCIATE, HRC)) + .payingWith(ACCOUNT) + .gas(1_000_000) + .via(ASSOCIATE_TXN_2), + cryptoTransfer(TokenMovement.movingUnique(NON_FUNGIBLE_TOKEN, 1) + .between(TOKEN_TREASURY, ACCOUNT)), + tokenDelete(NON_FUNGIBLE_TOKEN).via("deleteTxn"), + // Dissociate non-fungible token + contractCallWithFunctionAbi( + nonfungibleTokenAddress, + getABIFor(Utils.FunctionType.FUNCTION, DISSOCIATE, HRC)) + .payingWith(ACCOUNT) + .gas(1_000_000) + .via(DISSOCIATE_TXN_2)); + })) + .then(withOpContext((spec, ignore) -> allRunFor( + spec, + childRecordsCheck( + ASSOCIATE_TXN_2, + SUCCESS, + recordWith() + .status(SUCCESS) + .contractCallResult(resultWith() + .contractCallResult( + htsPrecompileResult().withStatus(SUCCESS)))), + childRecordsCheck( + DISSOCIATE_TXN_2, + SUCCESS, + recordWith() + .status(SUCCESS) + .contractCallResult(resultWith() + .contractCallResult( + htsPrecompileResult().withStatus(SUCCESS))))))); } @HapiTest - final HapiSpec hrcNftAndFungibleTokenAssociateFromEOA() { + final Stream hrcNftAndFungibleTokenAssociateFromEOA() { final AtomicReference fungibleTokenNum = new AtomicReference<>(); final AtomicReference nonfungibleTokenNum = new AtomicReference<>(); @@ -218,7 +264,7 @@ final HapiSpec hrcNftAndFungibleTokenAssociateFromEOA() { } @HapiTest - final HapiSpec hrcNFTAndFungibleTokenAssociateFromContract() { + final Stream hrcNFTAndFungibleTokenAssociateFromContract() { return defaultHapiSpec( "hrcNFTAndFungibleTokenAssociateFromContract", NONDETERMINISTIC_TRANSACTION_FEES, @@ -329,7 +375,7 @@ final HapiSpec hrcNFTAndFungibleTokenAssociateFromContract() { } @HapiTest - final HapiSpec hrcTokenAssociateFromSameEOATwiceShouldFail() { + final Stream hrcTokenAssociateFromSameEOATwiceShouldFail() { final AtomicReference fungibleTokenNum = new AtomicReference<>(); return defaultHapiSpec( @@ -398,7 +444,7 @@ final HapiSpec hrcTokenAssociateFromSameEOATwiceShouldFail() { } @HapiTest - final HapiSpec hrcTokenDissociateWhenNotAssociatedShouldFail() { + final Stream hrcTokenDissociateWhenNotAssociatedShouldFail() { final AtomicReference fungibleTokenNum = new AtomicReference<>(); return defaultHapiSpec("hrcTokenDissociateWhenNotAssociatedShouldFail", NONDETERMINISTIC_TRANSACTION_FEES) @@ -446,7 +492,7 @@ final HapiSpec hrcTokenDissociateWhenNotAssociatedShouldFail() { } @HapiTest - final HapiSpec hrcTokenDissociateWhenBalanceNotZeroShouldFail() { + final Stream hrcTokenDissociateWhenBalanceNotZeroShouldFail() { final AtomicReference fungibleTokenNum = new AtomicReference<>(); return defaultHapiSpec( @@ -517,7 +563,7 @@ final HapiSpec hrcTokenDissociateWhenBalanceNotZeroShouldFail() { } @HapiTest - final HapiSpec hrcTooManyTokenAssociateShouldFail() { + final Stream hrcTooManyTokenAssociateShouldFail() { final AtomicReference fungibleTokenNum1 = new AtomicReference<>(); final AtomicReference fungibleTokenNum2 = new AtomicReference<>(); final AtomicReference fungibleTokenNum3 = new AtomicReference<>(); @@ -627,9 +673,4 @@ final HapiSpec hrcTooManyTokenAssociateShouldFail() { .contractCallResult(htsPrecompileResult() .withStatus(TOKENS_PER_ACCOUNT_LIMIT_EXCEEDED))))))); } - - @Override - protected Logger getResultsLogger() { - return log; - } } diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/precompile/LazyCreateThroughPrecompileSuite.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/precompile/LazyCreateThroughPrecompileSuite.java index 85078ef37ef7..9098341b6f58 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/precompile/LazyCreateThroughPrecompileSuite.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/precompile/LazyCreateThroughPrecompileSuite.java @@ -58,6 +58,13 @@ import static com.hedera.services.bdd.spec.utilops.records.SnapshotMatchMode.NONDETERMINISTIC_FUNCTION_PARAMETERS; import static com.hedera.services.bdd.spec.utilops.records.SnapshotMatchMode.NONDETERMINISTIC_NONCE; import static com.hedera.services.bdd.spec.utilops.records.SnapshotMatchMode.NONDETERMINISTIC_TRANSACTION_FEES; +import static com.hedera.services.bdd.suites.HapiSuite.DEFAULT_PAYER; +import static com.hedera.services.bdd.suites.HapiSuite.EMPTY_KEY; +import static com.hedera.services.bdd.suites.HapiSuite.GENESIS; +import static com.hedera.services.bdd.suites.HapiSuite.ONE_HBAR; +import static com.hedera.services.bdd.suites.HapiSuite.ONE_HUNDRED_HBARS; +import static com.hedera.services.bdd.suites.HapiSuite.SECP_256K1_SHAPE; +import static com.hedera.services.bdd.suites.HapiSuite.THREE_MONTHS_IN_SECONDS; import static com.hedera.services.bdd.suites.contract.Utils.asAddress; import static com.hedera.services.bdd.suites.contract.Utils.headlongFromHexed; import static com.hedera.services.bdd.suites.contract.Utils.mirrorAddrWith; @@ -80,13 +87,10 @@ import com.hedera.node.app.hapi.utils.ByteStringUtils; import com.hedera.node.app.hapi.utils.contracts.ParsingConstants.FunctionType; import com.hedera.services.bdd.junit.HapiTest; -import com.hedera.services.bdd.junit.HapiTestSuite; import com.hedera.services.bdd.spec.HapiPropertySource; -import com.hedera.services.bdd.spec.HapiSpec; import com.hedera.services.bdd.spec.HapiSpecOperation; import com.hedera.services.bdd.spec.assertions.AccountInfoAsserts; import com.hedera.services.bdd.spec.transactions.contract.HapiParserUtil; -import com.hedera.services.bdd.suites.HapiSuite; import com.hederahashgraph.api.proto.java.TokenSupplyType; import com.hederahashgraph.api.proto.java.TokenType; import java.math.BigInteger; @@ -96,15 +100,16 @@ import java.util.concurrent.atomic.AtomicReference; import java.util.stream.IntStream; import java.util.stream.LongStream; +import java.util.stream.Stream; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.apache.tuweni.bytes.Bytes; import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.DynamicTest; import org.junit.jupiter.api.Tag; -@HapiTestSuite(fuzzyMatch = true) @Tag(SMART_CONTRACT) -public class LazyCreateThroughPrecompileSuite extends HapiSuite { +public class LazyCreateThroughPrecompileSuite { private static final Logger log = LogManager.getLogger(LazyCreateThroughPrecompileSuite.class); private static final long GAS_TO_OFFER = 4_000_000L; @@ -144,34 +149,8 @@ public class LazyCreateThroughPrecompileSuite extends HapiSuite { private static final String NOT_ENOUGH_GAS_TXN = "NOT_ENOUGH_GAS_TXN"; private static final String ECDSA_KEY = "abcdECDSAkey"; - public static void main(String... args) { - new LazyCreateThroughPrecompileSuite().runSuiteAsync(); - } - - @Override - public boolean canRunConcurrent() { - return true; - } - - @Override - protected Logger getResultsLogger() { - return log; - } - - @Override - public List getSpecsInSuite() { - return List.of( - erc20TransferLazyCreate(), - erc20TransferFromLazyCreate(), - erc721TransferFromLazyCreate(), - htsTransferFromFungibleTokenLazyCreate(), - htsTransferFromForNFTLazyCreate(), - resourceLimitExceededRevertsAllRecords(), - autoCreationFailsWithMirrorAddress()); - } - @HapiTest - HapiSpec resourceLimitExceededRevertsAllRecords() { + final Stream resourceLimitExceededRevertsAllRecords() { final var n = 4; // preceding child record limit is 3 final var nft = "nft"; final var nftKey = NFT_KEY; @@ -242,7 +221,7 @@ HapiSpec resourceLimitExceededRevertsAllRecords() { } @HapiTest - HapiSpec autoCreationFailsWithMirrorAddress() { + final Stream autoCreationFailsWithMirrorAddress() { final var nft = "nft"; final var nftKey = "nftKeyHere"; final var creationAttempt = CREATION_ATTEMPT; @@ -285,7 +264,7 @@ HapiSpec autoCreationFailsWithMirrorAddress() { } @HapiTest - final HapiSpec erc20TransferLazyCreate() { + final Stream erc20TransferLazyCreate() { final AtomicReference tokenAddr = new AtomicReference<>(); return defaultHapiSpec( @@ -308,7 +287,9 @@ final HapiSpec erc20TransferLazyCreate() { .exposingCreatedIdTo(id -> tokenAddr.set( HapiPropertySource.asHexedSolidityAddress(HapiPropertySource.asToken(id)))), uploadInitCode(ERC_20_CONTRACT), - contractCreate(ERC_20_CONTRACT), + // Refusing ethereum create conversion, because we get INVALID_SIGNATURE upon tokenAssociate, + // since we have CONTRACT_ID key. Also, because of gas difference + contractCreate(ERC_20_CONTRACT).refusingEthConversion(), tokenAssociate(ERC_20_CONTRACT, List.of(FUNGIBLE_TOKEN)), cryptoTransfer(moving(5, FUNGIBLE_TOKEN).between(TOKEN_TREASURY, ERC_20_CONTRACT))) .when(withOpContext((spec, opLog) -> { @@ -318,6 +299,9 @@ final HapiSpec erc20TransferLazyCreate() { final var alias = ByteStringUtils.wrapUnsafely(addressBytes); allRunFor( spec, + // Refusing ethereum create conversion, because we get INVALID_SIGNATURE upon + // tokenAssociate, + // since we have CONTRACT_ID key. Also, because of gas difference contractCall( ERC_20_CONTRACT, TRANSFER_THEN_REVERT, @@ -325,6 +309,7 @@ final HapiSpec erc20TransferLazyCreate() { asAddress(spec.registry().getTokenID(FUNGIBLE_TOKEN))), HapiParserUtil.asHeadlongAddress(addressBytes), BigInteger.valueOf(2)) + .refusingEthConversion() .via(TRANSFER_THEN_REVERT_TXN) .gas(GAS_TO_OFFER) .hasKnownStatus(CONTRACT_REVERT_EXECUTED), @@ -335,6 +320,7 @@ final HapiSpec erc20TransferLazyCreate() { asAddress(spec.registry().getTokenID(FUNGIBLE_TOKEN))), HapiParserUtil.asHeadlongAddress(addressBytes), BigInteger.valueOf(2)) + .refusingEthConversion() .via(TRANSFER_TXN) .gas(780_000L) .hasKnownStatus(SUCCESS), @@ -366,9 +352,8 @@ final HapiSpec erc20TransferLazyCreate() { .then(); } - // Expected INSUFFICIENT_GAS but was REVERTED_SUCCESS @HapiTest - final HapiSpec erc20TransferFromLazyCreate() { + final Stream erc20TransferFromLazyCreate() { return defaultHapiSpec( "erc20TransferFromLazyCreate", NONDETERMINISTIC_TRANSACTION_FEES, @@ -391,7 +376,10 @@ final HapiSpec erc20TransferFromLazyCreate() { .adminKey(MULTI_KEY) .supplyKey(MULTI_KEY), uploadInitCode(ERC_20_CONTRACT), - contractCreate(ERC_20_CONTRACT), + // Refusing ethereum create conversion, because we get INVALID_SIGNATURE upon tokenAssociate, + // since we have CONTRACT_ID key. Also, because of gas difference + + contractCreate(ERC_20_CONTRACT).refusingEthConversion(), tokenAssociate(OWNER, FUNGIBLE_TOKEN), tokenAssociate(RECIPIENT, FUNGIBLE_TOKEN), tokenAssociate(ERC_20_CONTRACT, FUNGIBLE_TOKEN), @@ -420,6 +408,7 @@ final HapiSpec erc20TransferFromLazyCreate() { asAddress(spec.registry().getAccountID(OWNER))), HapiParserUtil.asHeadlongAddress(addressBytes), BigInteger.TWO) + .refusingEthConversion() .gas(500_000) .via(NOT_ENOUGH_GAS_TXN) .hasKnownStatus(CONTRACT_REVERT_EXECUTED), @@ -433,6 +422,7 @@ final HapiSpec erc20TransferFromLazyCreate() { asAddress(spec.registry().getAccountID(OWNER))), HapiParserUtil.asHeadlongAddress(addressBytes), BigInteger.TWO) + .refusingEthConversion() .gas(4_000_000) .via(TRANSFER_FROM_ACCOUNT_REVERT_TXN) .hasKnownStatus(CONTRACT_REVERT_EXECUTED), @@ -447,6 +437,7 @@ final HapiSpec erc20TransferFromLazyCreate() { asAddress(spec.registry().getAccountID(OWNER))), HapiParserUtil.asHeadlongAddress(addressBytes), BigInteger.TWO) + .refusingEthConversion() .gas(780_000L) .via(TRANSFER_FROM_ACCOUNT_TXN) .hasKnownStatus(SUCCESS), @@ -483,7 +474,7 @@ final HapiSpec erc20TransferFromLazyCreate() { } @HapiTest - final HapiSpec erc721TransferFromLazyCreate() { + final Stream erc721TransferFromLazyCreate() { return defaultHapiSpec( "erc721TransferFromLazyCreate", NONDETERMINISTIC_FUNCTION_PARAMETERS, @@ -504,7 +495,9 @@ final HapiSpec erc721TransferFromLazyCreate() { .adminKey(MULTI_KEY) .supplyKey(MULTI_KEY), uploadInitCode(ERC_721_CONTRACT), - contractCreate(ERC_721_CONTRACT), + // Refusing ethereum create conversion, because we get INVALID_SIGNATURE upon tokenAssociate, + // since we have CONTRACT_ID key. Also, because of gas difference + contractCreate(ERC_721_CONTRACT).refusingEthConversion(), tokenAssociate(OWNER, NON_FUNGIBLE_TOKEN), tokenAssociate(SPENDER, NON_FUNGIBLE_TOKEN), tokenAssociate(ERC_721_CONTRACT, NON_FUNGIBLE_TOKEN), @@ -525,6 +518,9 @@ final HapiSpec erc721TransferFromLazyCreate() { .signedBy(DEFAULT_PAYER, OWNER) .fee(ONE_HBAR), getTokenNftInfo(NON_FUNGIBLE_TOKEN, 1L).hasSpenderID(ERC_721_CONTRACT), + // Refusing ethereum create conversion, because we get INVALID_SIGNATURE upon + // tokenAssociate, + // since we have CONTRACT_ID key. Also, because of gas difference contractCall( ERC_721_CONTRACT, TRANSFER_FROM_THEN_REVERT, @@ -534,6 +530,7 @@ final HapiSpec erc721TransferFromLazyCreate() { asAddress(spec.registry().getAccountID(OWNER))), HapiParserUtil.asHeadlongAddress(addressBytes), BigInteger.valueOf(1)) + .refusingEthConversion() .via(TRANSFER_FROM_ACCOUNT_REVERT_TXN) .gas(GAS_TO_OFFER) .hasKnownStatus(CONTRACT_REVERT_EXECUTED), @@ -546,6 +543,7 @@ final HapiSpec erc721TransferFromLazyCreate() { asAddress(spec.registry().getAccountID(OWNER))), HapiParserUtil.asHeadlongAddress(addressBytes), BigInteger.valueOf(1)) + .refusingEthConversion() .via(TRANSFER_FROM_ACCOUNT_TXN) .gas(780_000L) .hasKnownStatus(SUCCESS), @@ -578,7 +576,7 @@ final HapiSpec erc721TransferFromLazyCreate() { } @HapiTest - final HapiSpec htsTransferFromFungibleTokenLazyCreate() { + final Stream htsTransferFromFungibleTokenLazyCreate() { final var allowance = 10L; final var successfulTransferFromTxn = "txn"; return defaultHapiSpec( @@ -656,7 +654,7 @@ final HapiSpec htsTransferFromFungibleTokenLazyCreate() { } @HapiTest - final HapiSpec htsTransferFromForNFTLazyCreate() { + final Stream htsTransferFromForNFTLazyCreate() { return defaultHapiSpec( "htsTransferFromForNFTLazyCreate", NONDETERMINISTIC_FUNCTION_PARAMETERS, @@ -725,7 +723,7 @@ final HapiSpec htsTransferFromForNFTLazyCreate() { } @HapiTest - final HapiSpec revertedAutoCreationRollsBackEvenIfTopLevelSucceeds() { + final Stream revertedAutoCreationRollsBackEvenIfTopLevelSucceeds() { return defaultHapiSpec( "revertedAutoCreationRollsBackEvenIfTopLevelSucceeds", NONDETERMINISTIC_TRANSACTION_FEES, @@ -740,7 +738,8 @@ final HapiSpec revertedAutoCreationRollsBackEvenIfTopLevelSucceeds() { .initialSupply(0L) .supplyKey(MULTI_KEY), uploadInitCode(AUTO_CREATION_MODES), - contractCreate(AUTO_CREATION_MODES), + // Adding refusingEthConversion() due to fee differences + contractCreate(AUTO_CREATION_MODES).refusingEthConversion(), mintToken(NFT_TOKEN, List.of(META1, META2)), cryptoApproveAllowance() .payingWith(DEFAULT_PAYER) @@ -767,6 +766,7 @@ final HapiSpec revertedAutoCreationRollsBackEvenIfTopLevelSucceeds() { .gas(GAS_TO_OFFER) .via(TRANSFER_TXN) .alsoSigningWithFullPrefix(OWNER) + .refusingEthConversion() .hasKnownStatus(SUCCESS)); })) .then( diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/precompile/LazyCreateThroughPrecompileV1SecurityModelSuite.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/precompile/LazyCreateThroughPrecompileV1SecurityModelSuite.java index 2e3a04ef3fe1..0a9cc35ef58d 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/precompile/LazyCreateThroughPrecompileV1SecurityModelSuite.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/precompile/LazyCreateThroughPrecompileV1SecurityModelSuite.java @@ -79,7 +79,6 @@ import com.hedera.node.app.hapi.utils.ByteStringUtils; import com.hedera.node.app.hapi.utils.contracts.ParsingConstants.FunctionType; import com.hedera.services.bdd.spec.HapiPropertySource; -import com.hedera.services.bdd.spec.HapiSpec; import com.hedera.services.bdd.spec.assertions.AccountInfoAsserts; import com.hedera.services.bdd.spec.assertions.ContractInfoAsserts; import com.hedera.services.bdd.spec.transactions.contract.HapiParserUtil; @@ -96,10 +95,12 @@ import java.util.concurrent.atomic.AtomicReference; import java.util.stream.IntStream; import java.util.stream.LongStream; +import java.util.stream.Stream; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.apache.tuweni.bytes.Bytes; import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.DynamicTest; @SuppressWarnings("java:S1192") // "string literal should not be duplicated" - this rule makes test suites worse public class LazyCreateThroughPrecompileV1SecurityModelSuite extends HapiSuite { @@ -154,7 +155,7 @@ protected Logger getResultsLogger() { } @Override - public List getSpecsInSuite() { + public List> getSpecsInSuite() { return List.of( cryptoTransferV1LazyCreate(), cryptoTransferV2LazyCreate(), @@ -169,7 +170,7 @@ public List getSpecsInSuite() { canCreateViaFungibleWithFractionalFee()); } - HapiSpec hollowAccountSigningReqsStillEnforced() { + final Stream hollowAccountSigningReqsStillEnforced() { final var nft = "nft"; final var nftKey = NFT_KEY; final var creationAttempt = CREATION_ATTEMPT; @@ -231,7 +232,7 @@ HapiSpec hollowAccountSigningReqsStillEnforced() { recordWith().status(INVALID_FULL_PREFIX_SIGNATURE_FOR_PRECOMPILE)))); } - HapiSpec revertedAutoCreationRollsBackEvenIfTopLevelSucceeds() { + final Stream revertedAutoCreationRollsBackEvenIfTopLevelSucceeds() { final var nft = "nft"; final var nftKey = NFT_KEY; final var creationAttempt = CREATION_ATTEMPT; @@ -278,7 +279,7 @@ HapiSpec revertedAutoCreationRollsBackEvenIfTopLevelSucceeds() { } @SuppressWarnings("java:S5960") - HapiSpec canCreateViaFungibleWithFractionalFee() { + final Stream canCreateViaFungibleWithFractionalFee() { final var ft = "ft"; final var ftKey = NFT_KEY; final var creationAttempt = CREATION_ATTEMPT; @@ -336,7 +337,7 @@ HapiSpec canCreateViaFungibleWithFractionalFee() { .then(); } - HapiSpec canCreateMultipleHollows() { + final Stream canCreateMultipleHollows() { final var n = 3; final var nft = "nft"; final var nftKey = NFT_KEY; @@ -380,7 +381,7 @@ HapiSpec canCreateMultipleHollows() { .then(getTxnRecord(creationAttempt).andAllChildRecords().logged()); } - final HapiSpec cryptoTransferV1LazyCreate() { + final Stream cryptoTransferV1LazyCreate() { final var NESTED_LAZY_PRECOMPILE_CONTRACT = "LazyPrecompileTransfers"; final var FUNGIBLE_TOKEN_2 = "ftnt"; return propertyPreservingHapiSpec("cryptoTransferV1LazyCreate") @@ -565,7 +566,7 @@ final HapiSpec cryptoTransferV1LazyCreate() { .then(); } - final HapiSpec cryptoTransferV2LazyCreate() { + final Stream cryptoTransferV2LazyCreate() { final var NESTED_LAZY_PRECOMPILE_CONTRACT = "LazyPrecompileTransfersAtomic"; final var FUNGIBLE_TOKEN_2 = "ftnt"; final var INIT_BALANCE = 10 * ONE_HUNDRED_HBARS; @@ -739,7 +740,7 @@ final HapiSpec cryptoTransferV2LazyCreate() { .then(); } - final HapiSpec transferTokenLazyCreate() { + final Stream transferTokenLazyCreate() { final AtomicReference tokenAddr = new AtomicReference<>(); return propertyPreservingHapiSpec("transferTokenLazyCreate") @@ -802,7 +803,7 @@ final HapiSpec transferTokenLazyCreate() { .then(); } - final HapiSpec transferTokensToEVMAddressAliasRevertAndTransferAgainSuccessfully() { + final Stream transferTokensToEVMAddressAliasRevertAndTransferAgainSuccessfully() { final AtomicReference tokenAddr = new AtomicReference<>(); return propertyPreservingHapiSpec("transferTokensToEVMAddressAliasRevertAndTransferAgainSuccessfully") @@ -868,7 +869,7 @@ final HapiSpec transferTokensToEVMAddressAliasRevertAndTransferAgainSuccessfully .then(); } - final HapiSpec transferNftLazyCreate() { + final Stream transferNftLazyCreate() { return propertyPreservingHapiSpec("transferNftLazyCreate") .preserving(CONTRACTS_MAX_NUM_WITH_HAPI_SIGS_ACCESS) .given( @@ -931,7 +932,7 @@ final HapiSpec transferNftLazyCreate() { .then(); } - final HapiSpec transferNftsLazyCreate() { + final Stream transferNftsLazyCreate() { return propertyPreservingHapiSpec("transferNftsLazyCreate") .preserving(CONTRACTS_MAX_NUM_WITH_HAPI_SIGS_ACCESS) .given( @@ -996,7 +997,7 @@ final HapiSpec transferNftsLazyCreate() { .then(); } - final HapiSpec htsTransferFromFungibleTokenLazyCreate() { + final Stream htsTransferFromFungibleTokenLazyCreate() { final var allowance = 10L; final var successfulTransferFromTxn = "txn"; return propertyPreservingHapiSpec("htsTransferFromFungibleTokenLazyCreate") diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/precompile/MixedHTSPrecompileTestsV1SecurityModelSuite.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/precompile/MixedHTSPrecompileTestsV1SecurityModelSuite.java index 322272aced93..6042fbaef55f 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/precompile/MixedHTSPrecompileTestsV1SecurityModelSuite.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/precompile/MixedHTSPrecompileTestsV1SecurityModelSuite.java @@ -47,7 +47,6 @@ import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.TOKEN_NOT_ASSOCIATED_TO_ACCOUNT; import com.esaulpaugh.headlong.abi.Address; -import com.hedera.services.bdd.spec.HapiSpec; import com.hedera.services.bdd.spec.assertions.ContractInfoAsserts; import com.hedera.services.bdd.spec.transactions.contract.HapiParserUtil; import com.hedera.services.bdd.spec.transactions.token.TokenMovement; @@ -56,8 +55,10 @@ import com.hederahashgraph.api.proto.java.TokenType; import java.util.List; import java.util.concurrent.atomic.AtomicLong; +import java.util.stream.Stream; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; +import org.junit.jupiter.api.DynamicTest; public class MixedHTSPrecompileTestsV1SecurityModelSuite extends HapiSuite { @@ -83,13 +84,13 @@ public boolean canRunConcurrent() { } @Override - public List getSpecsInSuite() { + public List> getSpecsInSuite() { return List.of( hscsPrec021TryCatchConstructOnlyRollsBackTheFailedPrecompile(), createTokenWithFixedFeeThenTransferAndAssessFee()); } - final HapiSpec hscsPrec021TryCatchConstructOnlyRollsBackTheFailedPrecompile() { + final Stream hscsPrec021TryCatchConstructOnlyRollsBackTheFailedPrecompile() { final var theAccount = "anybody"; final var token = "Token"; final var outerContract = "AssociateTryCatch"; @@ -139,7 +140,7 @@ final HapiSpec hscsPrec021TryCatchConstructOnlyRollsBackTheFailedPrecompile() { .hasKnownStatus(SUCCESS)); } - final HapiSpec createTokenWithFixedFeeThenTransferAndAssessFee() { + final Stream createTokenWithFixedFeeThenTransferAndAssessFee() { final var createTokenNum = new AtomicLong(); final var CONTRACT_ADMIN_KEY = "contractAdminKey"; final var FEE_COLLECTOR = "feeCollector"; diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/precompile/PauseUnpauseTokenAccountPrecompileSuite.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/precompile/PauseUnpauseTokenAccountPrecompileSuite.java index 249fd0d4418d..0786d3c0bd83 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/precompile/PauseUnpauseTokenAccountPrecompileSuite.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/precompile/PauseUnpauseTokenAccountPrecompileSuite.java @@ -42,6 +42,9 @@ import static com.hedera.services.bdd.spec.utilops.UtilVerbs.withOpContext; import static com.hedera.services.bdd.spec.utilops.records.SnapshotMatchMode.NONDETERMINISTIC_CONTRACT_CALL_RESULTS; import static com.hedera.services.bdd.spec.utilops.records.SnapshotMatchMode.NONDETERMINISTIC_FUNCTION_PARAMETERS; +import static com.hedera.services.bdd.suites.HapiSuite.GENESIS; +import static com.hedera.services.bdd.suites.HapiSuite.ONE_HBAR; +import static com.hedera.services.bdd.suites.HapiSuite.TOKEN_TREASURY; import static com.hedera.services.bdd.suites.contract.Utils.asHexedAddress; import static com.hedera.services.bdd.suites.contract.Utils.asToken; import static com.hedera.services.bdd.suites.token.TokenAssociationSpecs.MULTI_KEY; @@ -58,23 +61,16 @@ import static com.hederahashgraph.api.proto.java.TokenType.NON_FUNGIBLE_UNIQUE; import com.hedera.services.bdd.junit.HapiTest; -import com.hedera.services.bdd.junit.HapiTestSuite; -import com.hedera.services.bdd.spec.HapiSpec; import com.hedera.services.bdd.spec.keys.KeyShape; -import com.hedera.services.bdd.suites.HapiSuite; import com.hederahashgraph.api.proto.java.AccountID; import com.hederahashgraph.api.proto.java.TokenID; -import java.util.List; import java.util.concurrent.atomic.AtomicReference; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; +import java.util.stream.Stream; +import org.junit.jupiter.api.DynamicTest; import org.junit.jupiter.api.Tag; -@HapiTestSuite(fuzzyMatch = true) @Tag(SMART_CONTRACT) -public class PauseUnpauseTokenAccountPrecompileSuite extends HapiSuite { - - private static final Logger log = LogManager.getLogger(PauseUnpauseTokenAccountPrecompileSuite.class); +public class PauseUnpauseTokenAccountPrecompileSuite { public static final String PAUSE_UNPAUSE_CONTRACT = "PauseUnpauseTokenAccount"; private static final String PAUSE_FUNGIBLE_TXN = "pauseFungibleTxn"; @@ -88,6 +84,7 @@ public class PauseUnpauseTokenAccountPrecompileSuite extends HapiSuite { private static final KeyShape THRESHOLD_KEY_SHAPE = KeyShape.threshOf(1, ED25519, CONTRACT); private static final String ACCOUNT = "account"; + private static final String TREASURY = "treasury"; public static final long INITIAL_BALANCE = 1_000_000_000L; private static final long GAS_TO_OFFER = 4_000_000L; @@ -97,33 +94,8 @@ public class PauseUnpauseTokenAccountPrecompileSuite extends HapiSuite { public static final String UNPAUSE_TX = "UnpauseTx"; public static final String PAUSE_TX = "PauseTx"; - public static void main(String... args) { - new PauseUnpauseTokenAccountPrecompileSuite().runSuiteAsync(); - } - - @Override - public boolean canRunConcurrent() { - return true; - } - - @Override - protected Logger getResultsLogger() { - return log; - } - - @Override - public List getSpecsInSuite() { - return List.of( - noTokenIdReverts(), - noAccountKeyReverts(), - pauseFungibleToken(), - unpauseFungibleToken(), - pauseNonFungibleToken(), - unpauseNonFungibleToken()); - } - @HapiTest - HapiSpec pauseFungibleToken() { + final Stream pauseFungibleToken() { final AtomicReference vanillaTokenID = new AtomicReference<>(); return defaultHapiSpec("PauseFungibleToken") .given( @@ -194,7 +166,7 @@ HapiSpec pauseFungibleToken() { } @HapiTest - HapiSpec unpauseFungibleToken() { + final Stream unpauseFungibleToken() { final AtomicReference vanillaTokenID = new AtomicReference<>(); return defaultHapiSpec("unpauseFungibleToken") .given( @@ -246,7 +218,7 @@ HapiSpec unpauseFungibleToken() { } @HapiTest - HapiSpec pauseNonFungibleToken() { + final Stream pauseNonFungibleToken() { final AtomicReference vanillaTokenID = new AtomicReference<>(); return defaultHapiSpec("pauseNonFungibleToken") .given( @@ -319,7 +291,7 @@ HapiSpec pauseNonFungibleToken() { } @HapiTest - HapiSpec unpauseNonFungibleToken() { + final Stream unpauseNonFungibleToken() { final AtomicReference vanillaTokenID = new AtomicReference<>(); return defaultHapiSpec("unpauseNonFungibleToken") .given( @@ -373,7 +345,7 @@ HapiSpec unpauseNonFungibleToken() { } @HapiTest - final HapiSpec noTokenIdReverts() { + final Stream noTokenIdReverts() { final AtomicReference vanillaTokenID = new AtomicReference<>(); return defaultHapiSpec("noTokenIdReverts") .given( @@ -417,7 +389,7 @@ PAUSE_TX, CONTRACT_REVERT_EXECUTED, recordWith().status(INVALID_TOKEN_ID)), } @HapiTest - final HapiSpec noAccountKeyReverts() { + final Stream noAccountKeyReverts() { final AtomicReference accountID = new AtomicReference<>(); final AtomicReference vanillaTokenID = new AtomicReference<>(); return defaultHapiSpec( diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/precompile/PauseUnpauseTokenAccountPrecompileV1SecurityModelSuite.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/precompile/PauseUnpauseTokenAccountPrecompileV1SecurityModelSuite.java index ab36ccdf5ab8..850a56a2ffe3 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/precompile/PauseUnpauseTokenAccountPrecompileV1SecurityModelSuite.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/precompile/PauseUnpauseTokenAccountPrecompileV1SecurityModelSuite.java @@ -50,13 +50,14 @@ import static com.hederahashgraph.api.proto.java.TokenType.FUNGIBLE_COMMON; import static com.hederahashgraph.api.proto.java.TokenType.NON_FUNGIBLE_UNIQUE; -import com.hedera.services.bdd.spec.HapiSpec; import com.hedera.services.bdd.suites.HapiSuite; import com.hederahashgraph.api.proto.java.TokenID; import java.util.List; import java.util.concurrent.atomic.AtomicReference; +import java.util.stream.Stream; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; +import org.junit.jupiter.api.DynamicTest; @SuppressWarnings("java:S1192") // "string literal should not be duplicated" - this rule makes test suites worse public class PauseUnpauseTokenAccountPrecompileV1SecurityModelSuite extends HapiSuite { @@ -97,7 +98,7 @@ protected Logger getResultsLogger() { } @Override - public List getSpecsInSuite() { + public List> getSpecsInSuite() { return List.of( pauseFungibleTokenHappyPath(), unpauseFungibleTokenHappyPath(), @@ -105,7 +106,7 @@ public List getSpecsInSuite() { unpauseNonFungibleTokenHappyPath()); } - HapiSpec pauseFungibleTokenHappyPath() { + final Stream pauseFungibleTokenHappyPath() { final AtomicReference vanillaTokenID = new AtomicReference<>(); return propertyPreservingHapiSpec("PauseFungibleTokenHappyPath") .preserving(CONTRACTS_ALLOW_SYSTEM_USE_OF_HAPI_SIGS, CONTRACTS_MAX_NUM_WITH_HAPI_SIGS_ACCESS) @@ -178,7 +179,7 @@ HapiSpec pauseFungibleTokenHappyPath() { htsPrecompileResult().withStatus(TOKEN_WAS_DELETED))))); } - HapiSpec unpauseFungibleTokenHappyPath() { + final Stream unpauseFungibleTokenHappyPath() { final AtomicReference vanillaTokenID = new AtomicReference<>(); return propertyPreservingHapiSpec("UnpauseFungibleTokenHappyPath") .preserving(CONTRACTS_ALLOW_SYSTEM_USE_OF_HAPI_SIGS, CONTRACTS_MAX_NUM_WITH_HAPI_SIGS_ACCESS) @@ -231,7 +232,7 @@ HapiSpec unpauseFungibleTokenHappyPath() { getTokenInfo(VANILLA_TOKEN).hasPauseStatus(Unpaused)); } - HapiSpec pauseNonFungibleTokenHappyPath() { + final Stream pauseNonFungibleTokenHappyPath() { final AtomicReference vanillaTokenID = new AtomicReference<>(); return propertyPreservingHapiSpec("PauseNonFungibleTokenHappyPath") .preserving(CONTRACTS_ALLOW_SYSTEM_USE_OF_HAPI_SIGS, CONTRACTS_MAX_NUM_WITH_HAPI_SIGS_ACCESS) @@ -306,7 +307,7 @@ HapiSpec pauseNonFungibleTokenHappyPath() { htsPrecompileResult().withStatus(TOKEN_WAS_DELETED))))); } - HapiSpec unpauseNonFungibleTokenHappyPath() { + final Stream unpauseNonFungibleTokenHappyPath() { final AtomicReference vanillaTokenID = new AtomicReference<>(); return propertyPreservingHapiSpec("UnpauseNonFungibleTokenHappyPath") .preserving(CONTRACTS_ALLOW_SYSTEM_USE_OF_HAPI_SIGS, CONTRACTS_MAX_NUM_WITH_HAPI_SIGS_ACCESS) diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/precompile/PrngPrecompileSuite.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/precompile/PrngPrecompileSuite.java index 36d675767e61..5bd3788c6259 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/precompile/PrngPrecompileSuite.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/precompile/PrngPrecompileSuite.java @@ -32,6 +32,7 @@ import static com.hedera.services.bdd.spec.utilops.UtilVerbs.withOpContext; import static com.hedera.services.bdd.spec.utilops.records.SnapshotMatchMode.NONDETERMINISTIC_CONTRACT_CALL_RESULTS; import static com.hedera.services.bdd.spec.utilops.records.SnapshotMatchMode.NONDETERMINISTIC_TRANSACTION_FEES; +import static com.hedera.services.bdd.suites.HapiSuite.ONE_HBAR; import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.CONTRACT_REVERT_EXECUTED; import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.INSUFFICIENT_GAS; import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.INVALID_FEE_SUBMITTED; @@ -40,28 +41,21 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import com.hedera.services.bdd.junit.HapiTest; -import com.hedera.services.bdd.junit.HapiTestSuite; -import com.hedera.services.bdd.spec.HapiSpec; -import com.hedera.services.bdd.suites.HapiSuite; import com.swirlds.common.utility.CommonUtils; import java.util.ArrayList; import java.util.HashSet; import java.util.List; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; +import java.util.stream.Stream; import org.apache.tuweni.bytes.Bytes; +import org.junit.jupiter.api.DynamicTest; import org.junit.jupiter.api.Tag; -@HapiTestSuite(fuzzyMatch = true) @Tag(SMART_CONTRACT) -public class PrngPrecompileSuite extends HapiSuite { - - private static final Logger log = LogManager.getLogger(PrngPrecompileSuite.class); +public class PrngPrecompileSuite { private static final long GAS_TO_OFFER = 400_000L; private static final String THE_GRACEFULLY_FAILING_PRNG_CONTRACT = "GracefullyFailingPrng"; private static final String THE_PRNG_CONTRACT = "PrngSystemContract"; private static final String BOB = "bob"; - private static final String TX = "tx"; private static final String GET_SEED = "getPseudorandomSeed"; private static final String GET_SEED_PAYABLE = "getPseudorandomSeedPayable"; @@ -72,34 +66,8 @@ public class PrngPrecompileSuite extends HapiSuite { + "000d83bf9a1083bf9a1000d83bf9a10000000d83bf9a10000000000000000026e790000000000000000000000000000" + "00000000d83bf9a1000000000d83bf9a1000"; - public static void main(String... args) { - new PrngPrecompileSuite().runSuiteAsync(); - } - - @Override - public boolean canRunConcurrent() { - return true; - } - - @Override - public List getSpecsInSuite() { - return allOf(positiveSpecs(), negativeSpecs()); - } - - List negativeSpecs() { - return List.of( - functionCallWithLessThanFourBytesFailsGracefully(), - nonSupportedAbiCallGracefullyFails(), - invalidLargeInputFails(), - emptyInputCallFails()); - } - - List positiveSpecs() { - return List.of(prngPrecompileHappyPathWorks(), multipleCallsHaveIndependentResults()); - } - @HapiTest - final HapiSpec multipleCallsHaveIndependentResults() { + final Stream multipleCallsHaveIndependentResults() { final var prng = THE_PRNG_CONTRACT; final var gasToOffer = 400_000; final var numCalls = 5; @@ -147,7 +115,7 @@ final HapiSpec multipleCallsHaveIndependentResults() { } @HapiTest - final HapiSpec emptyInputCallFails() { + final Stream emptyInputCallFails() { final var prng = THE_PRNG_CONTRACT; final var emptyInputCall = "emptyInputCall"; return defaultHapiSpec("emptyInputCallFails", NONDETERMINISTIC_TRANSACTION_FEES) @@ -175,7 +143,7 @@ final HapiSpec emptyInputCallFails() { } @HapiTest - final HapiSpec invalidLargeInputFails() { + final Stream invalidLargeInputFails() { final var prng = THE_PRNG_CONTRACT; final var largeInputCall = "largeInputCall"; return defaultHapiSpec("invalidLargeInputFails", NONDETERMINISTIC_TRANSACTION_FEES) @@ -203,7 +171,7 @@ final HapiSpec invalidLargeInputFails() { } @HapiTest - final HapiSpec nonSupportedAbiCallGracefullyFails() { + final Stream nonSupportedAbiCallGracefullyFails() { final var prng = THE_GRACEFULLY_FAILING_PRNG_CONTRACT; final var failedCall = "failedCall"; return defaultHapiSpec("nonSupportedAbiCallGracefullyFails", NONDETERMINISTIC_TRANSACTION_FEES) @@ -226,7 +194,7 @@ final HapiSpec nonSupportedAbiCallGracefullyFails() { } @HapiTest - final HapiSpec functionCallWithLessThanFourBytesFailsGracefully() { + final Stream functionCallWithLessThanFourBytesFailsGracefully() { final var lessThan4Bytes = "lessThan4Bytes"; return defaultHapiSpec("functionCallWithLessThanFourBytesFailsGracefully", NONDETERMINISTIC_TRANSACTION_FEES) .given(cryptoCreate(BOB), uploadInitCode(THE_PRNG_CONTRACT), contractCreate(THE_PRNG_CONTRACT)) @@ -254,7 +222,7 @@ final HapiSpec functionCallWithLessThanFourBytesFailsGracefully() { } @HapiTest - final HapiSpec prngPrecompileHappyPathWorks() { + final Stream prngPrecompileHappyPathWorks() { final var prng = THE_PRNG_CONTRACT; final var randomBits = "randomBits"; return defaultHapiSpec( @@ -279,7 +247,7 @@ GET_SEED, prng, isRandomResult(new Object[] {new byte[32]})))) } @HapiTest - final HapiSpec prngPrecompileInvalidFeeSubmitted() { + final Stream prngPrecompileInvalidFeeSubmitted() { final var TX = "TX"; return defaultHapiSpec( "prngPrecompileInvalidFeeSubmitted", @@ -294,12 +262,12 @@ final HapiSpec prngPrecompileInvalidFeeSubmitted() { .hasKnownStatus(CONTRACT_REVERT_EXECUTED)) .then(getTxnRecord(TX) .andAllChildRecords() - .hasChildRecordCount(1) + .hasNonStakingChildRecordCount(1) .hasChildRecords(recordWith().status(INVALID_FEE_SUBMITTED))); } @HapiTest - private HapiSpec prngPrecompileInsufficientGas() { + final Stream prngPrecompileInsufficientGas() { final var prng = THE_PRNG_CONTRACT; final var randomBits = "randomBits"; return defaultHapiSpec("prngPrecompileInsufficientGas") @@ -313,9 +281,4 @@ private HapiSpec prngPrecompileInsufficientGas() { .logged())) .then(); } - - @Override - protected Logger getResultsLogger() { - return log; - } } diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/precompile/RedirectPrecompileSuite.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/precompile/RedirectPrecompileSuite.java index b352fcfa8d60..7762d67937f0 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/precompile/RedirectPrecompileSuite.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/precompile/RedirectPrecompileSuite.java @@ -27,29 +27,23 @@ import static com.hedera.services.bdd.spec.utilops.records.SnapshotMatchMode.NONDETERMINISTIC_CONTRACT_CALL_RESULTS; import static com.hedera.services.bdd.spec.utilops.records.SnapshotMatchMode.NONDETERMINISTIC_FUNCTION_PARAMETERS; import static com.hedera.services.bdd.spec.utilops.records.SnapshotMatchMode.NONDETERMINISTIC_TRANSACTION_FEES; +import static com.hedera.services.bdd.suites.HapiSuite.ONE_HUNDRED_HBARS; +import static com.hedera.services.bdd.suites.HapiSuite.TOKEN_TREASURY; import static com.hedera.services.bdd.suites.contract.Utils.asAddress; import static com.hedera.services.bdd.suites.utils.contracts.precompile.HTSPrecompileResult.htsPrecompileResult; import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.*; import com.hedera.node.app.hapi.utils.contracts.ParsingConstants; import com.hedera.services.bdd.junit.HapiTest; -import com.hedera.services.bdd.junit.HapiTestSuite; -import com.hedera.services.bdd.spec.HapiSpec; import com.hedera.services.bdd.spec.transactions.contract.HapiParserUtil; -import com.hedera.services.bdd.suites.HapiSuite; import com.hederahashgraph.api.proto.java.TokenID; import com.hederahashgraph.api.proto.java.TokenType; -import java.util.List; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; +import java.util.stream.Stream; +import org.junit.jupiter.api.DynamicTest; import org.junit.jupiter.api.Tag; -@HapiTestSuite(fuzzyMatch = true) @Tag(SMART_CONTRACT) -public class RedirectPrecompileSuite extends HapiSuite { - - private static final Logger log = LogManager.getLogger(RedirectPrecompileSuite.class); - +public class RedirectPrecompileSuite { private static final String FUNGIBLE_TOKEN = "fungibleToken"; private static final String MULTI_KEY = "purpose"; private static final String ACCOUNT = "anybody"; @@ -57,22 +51,8 @@ public class RedirectPrecompileSuite extends HapiSuite { private static final String NULL_CONTRACT = "RedirectNullContract"; private static final String TXN = "txn"; - public static void main(String... args) { - new RedirectPrecompileSuite().runSuiteAsync(); - } - - @Override - public boolean canRunConcurrent() { - return true; - } - - @Override - public List getSpecsInSuite() { - return List.of(balanceOf(), redirectToInvalidToken(), redirectToNullSelector()); - } - @HapiTest - final HapiSpec balanceOf() { + final Stream balanceOf() { final var totalSupply = 50; return defaultHapiSpec( "balanceOf", NONDETERMINISTIC_CONTRACT_CALL_RESULTS, NONDETERMINISTIC_FUNCTION_PARAMETERS) @@ -114,7 +94,7 @@ final HapiSpec balanceOf() { } @HapiTest - final HapiSpec redirectToInvalidToken() { + final Stream redirectToInvalidToken() { return defaultHapiSpec( "redirectToInvalidToken", NONDETERMINISTIC_TRANSACTION_FEES, @@ -152,7 +132,7 @@ final HapiSpec redirectToInvalidToken() { } @HapiTest - final HapiSpec redirectToNullSelector() { + final Stream redirectToNullSelector() { return defaultHapiSpec( "redirectToNullSelector", NONDETERMINISTIC_TRANSACTION_FEES, @@ -181,9 +161,4 @@ final HapiSpec redirectToNullSelector() { .gas(1_000_000)))) .then(); } - - @Override - protected Logger getResultsLogger() { - return log; - } } diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/precompile/SigningReqsSuite.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/precompile/SigningReqsSuite.java index 318c8318da31..898083ba4201 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/precompile/SigningReqsSuite.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/precompile/SigningReqsSuite.java @@ -27,14 +27,15 @@ import static com.hedera.services.bdd.spec.utilops.records.SnapshotMatchMode.NONDETERMINISTIC_CONTRACT_CALL_RESULTS; import static com.hedera.services.bdd.spec.utilops.records.SnapshotMatchMode.NONDETERMINISTIC_FUNCTION_PARAMETERS; import static com.hedera.services.bdd.spec.utilops.records.SnapshotMatchMode.NONDETERMINISTIC_TRANSACTION_FEES; +import static com.hedera.services.bdd.suites.HapiSuite.ONE_HBAR; +import static com.hedera.services.bdd.suites.HapiSuite.ONE_HUNDRED_HBARS; +import static com.hedera.services.bdd.suites.HapiSuite.THREE_MONTHS_IN_SECONDS; import static com.hedera.services.bdd.suites.file.FileUpdateSuite.*; import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.CONTRACT_REVERT_EXECUTED; import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.INVALID_FULL_PREFIX_SIGNATURE_FOR_PRECOMPILE; import com.esaulpaugh.headlong.abi.Address; import com.hedera.services.bdd.junit.HapiTest; -import com.hedera.services.bdd.junit.HapiTestSuite; -import com.hedera.services.bdd.spec.*; import com.hedera.services.bdd.spec.assertions.*; import com.hedera.services.bdd.spec.keys.KeyShape; import com.hedera.services.bdd.spec.utilops.CustomSpecAssert; @@ -42,18 +43,17 @@ import com.hederahashgraph.api.proto.java.TokenID; import java.util.*; import java.util.concurrent.atomic.*; +import java.util.stream.Stream; import org.apache.logging.log4j.*; +import org.junit.jupiter.api.DynamicTest; import org.junit.jupiter.api.Tag; // Some of the test cases cannot be converted to use eth calls, // since they use admin keys, which are held by the txn payer. // In the case of an eth txn, we revoke the payers keys and the txn would fail. // The only way an eth account to create a token is the admin key to be of a contractId type. -@HapiTestSuite(fuzzyMatch = true) @Tag(SMART_CONTRACT) -public class SigningReqsSuite extends HapiSuite { - private static final Logger log = LogManager.getLogger(SigningReqsSuite.class); - +public class SigningReqsSuite { private static final String FIRST_CREATE_TXN = "firstCreateTxn"; private static final String SECOND_CREATE_TXN = "secondCreateTxn"; private static final long DEFAULT_AMOUNT_TO_SEND = 20 * ONE_HBAR; @@ -63,22 +63,8 @@ public class SigningReqsSuite extends HapiSuite { public static final String AUTO_RENEW = "autoRenew"; public static final int GAS_TO_OFFER = 1_000_000; - public static void main(String... args) { - new SigningReqsSuite().runSuiteAsync(); - } - - @Override - public boolean canRunConcurrent() { - return true; - } - - @Override - public List getSpecsInSuite() { - return List.of(autoRenewAccountCanUseLegacySigActivationIfConfigured()); - } - @HapiTest - final HapiSpec autoRenewAccountCanUseLegacySigActivationIfConfigured() { + final Stream autoRenewAccountCanUseLegacySigActivationIfConfigured() { final var autoRenew = AUTO_RENEW; final AtomicReference
    autoRenewMirrorAddr = new AtomicReference<>(); final AtomicLong contractId = new AtomicLong(); @@ -97,7 +83,8 @@ final HapiSpec autoRenewAccountCanUseLegacySigActivationIfConfigured() { uploadInitCode(MINIMAL_CREATIONS_CONTRACT), contractCreate(MINIMAL_CREATIONS_CONTRACT) .exposingNumTo(contractId::set) - .gas(GAS_TO_OFFER), + .gas(GAS_TO_OFFER) + .refusingEthConversion(), cryptoCreate(autoRenew) .keyShape(origKey.signedWith(sigs(ON, MINIMAL_CREATIONS_CONTRACT))) .exposingCreatedIdTo(id -> autoRenewMirrorAddr.set(idAsHeadlongAddress(id)))) @@ -148,9 +135,4 @@ final HapiSpec autoRenewAccountCanUseLegacySigActivationIfConfigured() { sourcing(() -> getTokenInfo(asTokenString(createdToken.get())).hasAutoRenewAccount(autoRenew))); } - - @Override - protected Logger getResultsLogger() { - return log; - } } diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/precompile/SigningReqsV1SecurityModelSuite.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/precompile/SigningReqsV1SecurityModelSuite.java index 1a9e6c22ae15..5e498298071b 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/precompile/SigningReqsV1SecurityModelSuite.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/precompile/SigningReqsV1SecurityModelSuite.java @@ -42,7 +42,6 @@ import static org.junit.jupiter.api.Assertions.assertTrue; import com.esaulpaugh.headlong.abi.Address; -import com.hedera.services.bdd.spec.HapiSpec; import com.hedera.services.bdd.spec.assertions.TransactionRecordAsserts; import com.hedera.services.bdd.spec.transactions.crypto.HapiCryptoCreate; import com.hedera.services.bdd.suites.HapiSuite; @@ -50,8 +49,10 @@ import java.util.List; import java.util.concurrent.atomic.AtomicLong; import java.util.concurrent.atomic.AtomicReference; +import java.util.stream.Stream; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; +import org.junit.jupiter.api.DynamicTest; // Some of the test cases cannot be converted to use eth calls, // since they use admin keys, which are held by the txn payer. @@ -80,7 +81,7 @@ public boolean canRunConcurrent() { } @Override - public List getSpecsInSuite() { + public List> getSpecsInSuite() { return List.of( newAutoRenewAccountMustSignUpdate(), newTreasuryAccountMustSignUpdate(), @@ -90,7 +91,7 @@ public List getSpecsInSuite() { } @SuppressWarnings("java:S5960") // "assertions should not be used in production code" - not production - final HapiSpec selfDenominatedFixedCollectorMustSign() { + final Stream selfDenominatedFixedCollectorMustSign() { final var fcKey = "fcKey"; final var arKey = AR_KEY; final var feeCollector = "feeCollector"; @@ -163,7 +164,7 @@ final HapiSpec selfDenominatedFixedCollectorMustSign() { } @SuppressWarnings("java:S5960") // "assertions should not be used in production code" - not production - final HapiSpec fractionalFeeCollectorMustSign() { + final Stream fractionalFeeCollectorMustSign() { final var fcKey = "fcKey"; final var arKey = AR_KEY; final var feeCollector = "feeCollector"; @@ -232,7 +233,7 @@ final HapiSpec fractionalFeeCollectorMustSign() { }))); } - final HapiSpec autoRenewAccountMustSignCreation() { + final Stream autoRenewAccountMustSignCreation() { final var arKey = AR_KEY; final var autoRenew = AUTO_RENEW; final AtomicReference
    autoRenewAlias = new AtomicReference<>(); @@ -292,7 +293,7 @@ final HapiSpec autoRenewAccountMustSignCreation() { getTokenInfo(asTokenString(createdToken.get())).hasAutoRenewAccount(autoRenew))); } - final HapiSpec newTreasuryAccountMustSignUpdate() { + final Stream newTreasuryAccountMustSignUpdate() { final var ft = "fungibleToken"; final var ntKey = "ntKey"; final var updateTxn = "updateTxn"; @@ -346,7 +347,7 @@ final HapiSpec newTreasuryAccountMustSignUpdate() { getTokenInfo(ft).hasTreasury(TOKEN_TREASURY)); } - final HapiSpec newAutoRenewAccountMustSignUpdate() { + final Stream newAutoRenewAccountMustSignUpdate() { final var ft = "fungibleToken"; final var narKey = "narKey"; final var adminKey = "adminKey"; diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/precompile/TokenAndTypeCheckSuite.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/precompile/TokenAndTypeCheckSuite.java index 56d7ccac0a51..33210840be4d 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/precompile/TokenAndTypeCheckSuite.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/precompile/TokenAndTypeCheckSuite.java @@ -32,6 +32,8 @@ import static com.hedera.services.bdd.spec.utilops.CustomSpecAssert.allRunFor; import static com.hedera.services.bdd.spec.utilops.UtilVerbs.childRecordsCheck; import static com.hedera.services.bdd.spec.utilops.UtilVerbs.withOpContext; +import static com.hedera.services.bdd.suites.HapiSuite.ONE_HUNDRED_HBARS; +import static com.hedera.services.bdd.suites.HapiSuite.TOKEN_TREASURY; import static com.hedera.services.bdd.suites.contract.Utils.asAddress; import static com.hedera.services.bdd.suites.contract.Utils.asToken; import static com.hedera.services.bdd.suites.token.TokenAssociationSpecs.VANILLA_TOKEN; @@ -42,50 +44,24 @@ import static com.hederahashgraph.api.proto.java.TokenType.FUNGIBLE_COMMON; import com.hedera.services.bdd.junit.HapiTest; -import com.hedera.services.bdd.junit.HapiTestSuite; -import com.hedera.services.bdd.spec.HapiSpec; import com.hedera.services.bdd.spec.transactions.contract.HapiParserUtil; -import com.hedera.services.bdd.suites.HapiSuite; import com.hederahashgraph.api.proto.java.TokenID; import java.math.BigInteger; -import java.util.List; import java.util.concurrent.atomic.AtomicReference; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; +import java.util.stream.Stream; +import org.junit.jupiter.api.DynamicTest; import org.junit.jupiter.api.Tag; -@HapiTestSuite(fuzzyMatch = true) @Tag(SMART_CONTRACT) -public class TokenAndTypeCheckSuite extends HapiSuite { - - private static final Logger log = LogManager.getLogger(TokenAndTypeCheckSuite.class); +public class TokenAndTypeCheckSuite { private static final String TOKEN_AND_TYPE_CHECK_CONTRACT = "TokenAndTypeCheck"; private static final String ACCOUNT = "anybody"; private static final int GAS_TO_OFFER = 1_000_000; private static final String GET_TOKEN_TYPE = "getType"; private static final String IS_TOKEN = "isAToken"; - public static void main(String... args) { - new TokenAndTypeCheckSuite().runSuiteAsync(); - } - - @Override - public boolean canRunConcurrent() { - return true; - } - - @Override - protected Logger getResultsLogger() { - return log; - } - - @Override - public List getSpecsInSuite() { - return List.of(checkTokenAndTypeStandardCases(), checkTokenAndTypeNegativeCases()); - } - @HapiTest - final HapiSpec checkTokenAndTypeStandardCases() { + final Stream checkTokenAndTypeStandardCases() { final AtomicReference vanillaTokenID = new AtomicReference<>(); return defaultHapiSpec("checkTokenAndTypeStandardCases") @@ -127,7 +103,7 @@ final HapiSpec checkTokenAndTypeStandardCases() { // Should just return false on isToken() check for missing token type @HapiTest - final HapiSpec checkTokenAndTypeNegativeCases() { + final Stream checkTokenAndTypeNegativeCases() { final AtomicReference vanillaTokenID = new AtomicReference<>(); final var notAnAddress = new byte[20]; diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/precompile/TokenExpiryInfoSuite.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/precompile/TokenExpiryInfoSuite.java index 2f972aa524d5..b3ea0adeb337 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/precompile/TokenExpiryInfoSuite.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/precompile/TokenExpiryInfoSuite.java @@ -33,6 +33,9 @@ import static com.hedera.services.bdd.spec.utilops.UtilVerbs.withOpContext; import static com.hedera.services.bdd.spec.utilops.records.SnapshotMatchMode.NONDETERMINISTIC_CONTRACT_CALL_RESULTS; import static com.hedera.services.bdd.spec.utilops.records.SnapshotMatchMode.NONDETERMINISTIC_FUNCTION_PARAMETERS; +import static com.hedera.services.bdd.suites.HapiSuite.GENESIS; +import static com.hedera.services.bdd.suites.HapiSuite.THREE_MONTHS_IN_SECONDS; +import static com.hedera.services.bdd.suites.HapiSuite.TOKEN_TREASURY; import static com.hedera.services.bdd.suites.contract.Utils.asAddress; import static com.hedera.services.bdd.suites.contract.Utils.asToken; import static com.hedera.services.bdd.suites.token.TokenAssociationSpecs.VANILLA_TOKEN; @@ -43,51 +46,25 @@ import com.hedera.node.app.hapi.utils.contracts.ParsingConstants.FunctionType; import com.hedera.services.bdd.junit.HapiTest; -import com.hedera.services.bdd.junit.HapiTestSuite; -import com.hedera.services.bdd.spec.HapiSpec; import com.hedera.services.bdd.spec.transactions.contract.HapiParserUtil; -import com.hedera.services.bdd.suites.HapiSuite; import com.hedera.services.bdd.suites.utils.contracts.precompile.TokenKeyType; import com.hederahashgraph.api.proto.java.TokenID; import com.hederahashgraph.api.proto.java.TokenSupplyType; -import java.util.List; import java.util.concurrent.atomic.AtomicReference; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; +import java.util.stream.Stream; +import org.junit.jupiter.api.DynamicTest; import org.junit.jupiter.api.Tag; -@HapiTestSuite(fuzzyMatch = true) @Tag(SMART_CONTRACT) -public class TokenExpiryInfoSuite extends HapiSuite { - - private static final Logger log = LogManager.getLogger(TokenExpiryInfoSuite.class); +public class TokenExpiryInfoSuite { private static final String TOKEN_EXPIRY_CONTRACT = "TokenExpiryContract"; private static final String AUTO_RENEW_ACCOUNT = "autoRenewAccount"; private static final String ADMIN_KEY = TokenKeyType.ADMIN_KEY.name(); public static final String GET_EXPIRY_INFO_FOR_TOKEN = "getExpiryInfoForToken"; public static final int GAS_TO_OFFER = 1_000_000; - public static void main(String... args) { - new TokenExpiryInfoSuite().runSuiteAsync(); - } - - @Override - public boolean canRunConcurrent() { - return true; - } - - @Override - protected Logger getResultsLogger() { - return log; - } - - @Override - public List getSpecsInSuite() { - return List.of(getExpiryInfoForToken()); - } - @HapiTest - final HapiSpec getExpiryInfoForToken() { + final Stream getExpiryInfoForToken() { final AtomicReference vanillaTokenID = new AtomicReference<>(); diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/precompile/TokenExpiryInfoV1SecurityModelSuite.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/precompile/TokenExpiryInfoV1SecurityModelSuite.java index 17c52abec8ab..a27f2ddd9671 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/precompile/TokenExpiryInfoV1SecurityModelSuite.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/precompile/TokenExpiryInfoV1SecurityModelSuite.java @@ -48,7 +48,6 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import com.hedera.node.app.hapi.utils.contracts.ParsingConstants.FunctionType; -import com.hedera.services.bdd.spec.HapiSpec; import com.hedera.services.bdd.spec.HapiSpecSetup; import com.hedera.services.bdd.spec.keys.SigControl; import com.hedera.services.bdd.spec.transactions.contract.HapiParserUtil; @@ -59,8 +58,10 @@ import com.hederahashgraph.api.proto.java.TokenSupplyType; import java.util.List; import java.util.concurrent.atomic.AtomicReference; +import java.util.stream.Stream; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; +import org.junit.jupiter.api.DynamicTest; public class TokenExpiryInfoV1SecurityModelSuite extends HapiSuite { @@ -93,13 +94,13 @@ protected Logger getResultsLogger() { } @Override - public List getSpecsInSuite() { + public List> getSpecsInSuite() { return List.of(updateExpiryInfoForToken(), updateExpiryInfoForTokenAndReadLatestInfo()); } @SuppressWarnings({"java:S5960", "java:S1192" }) // using `assertThat` in production code - except this isn't production code - final HapiSpec updateExpiryInfoForToken() { + final Stream updateExpiryInfoForToken() { final AtomicReference vanillaTokenID = new AtomicReference<>(); final AtomicReference updatedAutoRenewAccountID = new AtomicReference<>(); @@ -251,7 +252,7 @@ final HapiSpec updateExpiryInfoForToken() { } @SuppressWarnings("java:S1192") // "use already defined const instead of copying its value here" - not this time - final HapiSpec updateExpiryInfoForTokenAndReadLatestInfo() { + final Stream updateExpiryInfoForTokenAndReadLatestInfo() { final AtomicReference vanillaTokenID = new AtomicReference<>(); final AtomicReference updatedAutoRenewAccountID = new AtomicReference<>(); diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/precompile/TokenInfoHTSSuite.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/precompile/TokenInfoHTSSuite.java index 5612bc1b7c42..45496d0a8f0c 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/precompile/TokenInfoHTSSuite.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/precompile/TokenInfoHTSSuite.java @@ -49,6 +49,10 @@ import static com.hedera.services.bdd.spec.utilops.records.SnapshotMatchMode.HIGHLY_NON_DETERMINISTIC_FEES; import static com.hedera.services.bdd.spec.utilops.records.SnapshotMatchMode.NONDETERMINISTIC_CONTRACT_CALL_RESULTS; import static com.hedera.services.bdd.spec.utilops.records.SnapshotMatchMode.NONDETERMINISTIC_FUNCTION_PARAMETERS; +import static com.hedera.services.bdd.suites.HapiSuite.DEFAULT_PAYER; +import static com.hedera.services.bdd.suites.HapiSuite.ONE_HBAR; +import static com.hedera.services.bdd.suites.HapiSuite.THREE_MONTHS_IN_SECONDS; +import static com.hedera.services.bdd.suites.HapiSuite.TOKEN_TREASURY; import static com.hedera.services.bdd.suites.contract.Utils.asAddress; import static com.hedera.services.bdd.suites.utils.contracts.precompile.HTSPrecompileResult.htsPrecompileResult; import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.CONTRACT_REVERT_EXECUTED; @@ -59,12 +63,10 @@ import com.google.protobuf.ByteString; import com.hedera.node.app.hapi.utils.contracts.ParsingConstants.FunctionType; import com.hedera.services.bdd.junit.HapiTest; -import com.hedera.services.bdd.junit.HapiTestSuite; import com.hedera.services.bdd.spec.HapiSpec; import com.hedera.services.bdd.spec.queries.token.HapiGetTokenInfo; import com.hedera.services.bdd.spec.transactions.contract.HapiParserUtil; import com.hedera.services.bdd.spec.transactions.token.TokenMovement; -import com.hedera.services.bdd.suites.HapiSuite; import com.hedera.services.bdd.suites.utils.contracts.precompile.TokenKeyType; import com.hederahashgraph.api.proto.java.AccountID; import com.hederahashgraph.api.proto.java.CustomFee; @@ -87,17 +89,13 @@ import java.util.List; import java.util.OptionalLong; import java.util.concurrent.atomic.AtomicReference; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; +import java.util.stream.Stream; import org.apache.tuweni.bytes.Bytes; +import org.junit.jupiter.api.DynamicTest; import org.junit.jupiter.api.Tag; -@HapiTestSuite(fuzzyMatch = true) @Tag(SMART_CONTRACT) -public class TokenInfoHTSSuite extends HapiSuite { - - private static final Logger LOG = LogManager.getLogger(TokenInfoHTSSuite.class); - +public class TokenInfoHTSSuite { private static final String TOKEN_INFO_CONTRACT = "TokenInfoContract"; private static final String ADMIN_KEY = TokenKeyType.ADMIN_KEY.name(); private static final String KYC_KEY = TokenKeyType.KYC_KEY.name(); @@ -136,42 +134,8 @@ public class TokenInfoHTSSuite extends HapiSuite { private static final int MAX_SUPPLY = 1000; public static final String GET_CUSTOM_FEES_FOR_TOKEN = "getCustomFeesForToken"; - public static void main(final String... args) { - new TokenInfoHTSSuite().runSuiteAsync(); - } - - @Override - public boolean canRunConcurrent() { - return true; - } - - @Override - public List getSpecsInSuite() { - return allOf(positiveSpecs(), negativeSpecs()); - } - - List negativeSpecs() { - return List.of( - getInfoOnDeletedFungibleTokenWorks(), - getInfoOnInvalidFungibleTokenFails(), - getInfoOnInvalidNonFungibleTokenFails(), - getInfoForFungibleTokenByNFTTokenAddressWorks(), - getInfoForNFTByFungibleTokenAddressFails(), - getInfoForTokenByAccountAddressFails(), - getTokenCustomFeesNegativeCases()); - } - - List positiveSpecs() { - return List.of( - happyPathGetTokenInfo(), - happyPathGetFungibleTokenInfo(), - happyPathGetNonFungibleTokenInfo(), - happyPathGetTokenCustomFees(), - happyPathGetNonFungibleTokenCustomFees()); - } - @HapiTest - final HapiSpec happyPathGetTokenInfo() { + final Stream happyPathGetTokenInfo() { final AtomicReference targetLedgerId = new AtomicReference<>(); return defaultHapiSpec( "HappyPathGetTokenInfo", @@ -269,7 +233,7 @@ final HapiSpec happyPathGetTokenInfo() { } @HapiTest - final HapiSpec happyPathGetFungibleTokenInfo() { + final Stream happyPathGetFungibleTokenInfo() { final int decimals = 1; final AtomicReference targetLedgerId = new AtomicReference<>(); return defaultHapiSpec( @@ -368,7 +332,7 @@ final HapiSpec happyPathGetFungibleTokenInfo() { } @HapiTest - final HapiSpec happyPathGetNonFungibleTokenInfo() { + final Stream happyPathGetNonFungibleTokenInfo() { final int maxSupply = 10; final ByteString meta = ByteString.copyFrom(META.getBytes(StandardCharsets.UTF_8)); final AtomicReference targetLedgerId = new AtomicReference<>(); @@ -484,7 +448,7 @@ final HapiSpec happyPathGetNonFungibleTokenInfo() { } @HapiTest - final HapiSpec getInfoOnDeletedFungibleTokenWorks() { + final Stream getInfoOnDeletedFungibleTokenWorks() { return defaultHapiSpec( "getInfoOnDeletedFungibleTokenWorks", HIGHLY_NON_DETERMINISTIC_FEES, @@ -534,7 +498,7 @@ final HapiSpec getInfoOnDeletedFungibleTokenWorks() { } @HapiTest - final HapiSpec getInfoOnInvalidFungibleTokenFails() { + final Stream getInfoOnInvalidFungibleTokenFails() { return defaultHapiSpec( "getInfoOnInvalidFungibleTokenFails", HIGHLY_NON_DETERMINISTIC_FEES, @@ -629,7 +593,7 @@ final HapiSpec getInfoOnInvalidFungibleTokenFails() { } @HapiTest - final HapiSpec getInfoOnDeletedNonFungibleTokenWorks() { + final Stream getInfoOnDeletedNonFungibleTokenWorks() { final ByteString meta = ByteString.copyFrom(META.getBytes(StandardCharsets.UTF_8)); return defaultHapiSpec( "getInfoOnDeletedNonFungibleTokenFails", @@ -676,7 +640,7 @@ final HapiSpec getInfoOnDeletedNonFungibleTokenWorks() { } @HapiTest - final HapiSpec getInfoOnInvalidNonFungibleTokenFails() { + final Stream getInfoOnInvalidNonFungibleTokenFails() { final ByteString meta = ByteString.copyFrom(META.getBytes(StandardCharsets.UTF_8)); return defaultHapiSpec("getInfoOnInvalidNonFungibleTokenFails", FULLY_NONDETERMINISTIC) .given( @@ -756,7 +720,7 @@ final HapiSpec getInfoOnInvalidNonFungibleTokenFails() { } @HapiTest - final HapiSpec getInfoForTokenByAccountAddressFails() { + final Stream getInfoForTokenByAccountAddressFails() { return defaultHapiSpec("getInfoForTokenByAccountAddressFails") .given( cryptoCreate(TOKEN_TREASURY).balance(0L), @@ -796,7 +760,7 @@ final HapiSpec getInfoForTokenByAccountAddressFails() { } @HapiTest - final HapiSpec getInfoForNFTByFungibleTokenAddressFails() { + final Stream getInfoForNFTByFungibleTokenAddressFails() { return defaultHapiSpec("getInfoForNFTByFungibleTokenAddressFails") .given( cryptoCreate(TOKEN_TREASURY).balance(0L), @@ -841,7 +805,7 @@ final HapiSpec getInfoForNFTByFungibleTokenAddressFails() { @HapiTest // FUTURE: This test ensures matching mono === mod behavior. We should consider revising the behavior of allowing // NonFungibleToken to be passed to getInfoForFungibleToken and resulting SUCCESS status. - final HapiSpec getInfoForFungibleTokenByNFTTokenAddressWorks() { + final Stream getInfoForFungibleTokenByNFTTokenAddressWorks() { final ByteString meta = ByteString.copyFrom(META.getBytes(StandardCharsets.UTF_8)); return defaultHapiSpec("getInfoForFungibleTokenByNFTTokenAddressWorks") .given( @@ -884,7 +848,7 @@ final HapiSpec getInfoForFungibleTokenByNFTTokenAddressWorks() { } @HapiTest - final HapiSpec happyPathGetTokenCustomFees() { + final Stream happyPathGetTokenCustomFees() { return defaultHapiSpec( "HappyPathGetTokenCustomFees", HIGHLY_NON_DETERMINISTIC_FEES, @@ -963,7 +927,7 @@ final HapiSpec happyPathGetTokenCustomFees() { } @HapiTest - final HapiSpec happyPathGetNonFungibleTokenCustomFees() { + final Stream happyPathGetNonFungibleTokenCustomFees() { final int maxSupply = 10; final ByteString meta = ByteString.copyFrom(META.getBytes(StandardCharsets.UTF_8)); return defaultHapiSpec( @@ -1037,7 +1001,7 @@ final HapiSpec happyPathGetNonFungibleTokenCustomFees() { } @HapiTest - final HapiSpec getTokenCustomFeesNegativeCases() { + final Stream getTokenCustomFeesNegativeCases() { final int maxSupply = 10; final var accountAddressForToken = "accountAddressForToken"; final var tokenWithNoFees = "tokenWithNoFees"; @@ -1340,9 +1304,4 @@ private Key getTokenKeyFromSpec(final HapiSpec spec, final TokenKeyType type) { private ByteString fromString(final String value) { return ByteString.copyFrom(Bytes.fromHexString(value).toArray()); } - - @Override - protected Logger getResultsLogger() { - return LOG; - } } diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/precompile/TokenInfoHTSV1SecurityModelSuite.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/precompile/TokenInfoHTSV1SecurityModelSuite.java index 33099e4dce7c..c00d4f81aa3f 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/precompile/TokenInfoHTSV1SecurityModelSuite.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/precompile/TokenInfoHTSV1SecurityModelSuite.java @@ -80,9 +80,11 @@ import java.util.List; import java.util.OptionalLong; import java.util.concurrent.atomic.AtomicReference; +import java.util.stream.Stream; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.apache.tuweni.bytes.Bytes; +import org.junit.jupiter.api.DynamicTest; @SuppressWarnings("java:S1192") // "string literal should not be duplicated" - this rule makes test suites worse public class TokenInfoHTSV1SecurityModelSuite extends HapiSuite { @@ -142,15 +144,15 @@ public boolean canRunConcurrent() { } @Override - public List getSpecsInSuite() { + public List> getSpecsInSuite() { return allOf(positiveSpecs(), negativeSpecs()); } - List negativeSpecs() { + List> negativeSpecs() { return List.of(); } - List positiveSpecs() { + List> positiveSpecs() { return List.of( happyPathUpdateTokenInfoAndGetLatestInfo(), happyPathUpdateFungibleTokenInfoAndGetLatestInfo(), @@ -158,7 +160,7 @@ List positiveSpecs() { happyPathUpdateTokenKeysAndReadLatestInformation()); } - final HapiSpec happyPathUpdateTokenInfoAndGetLatestInfo() { + final Stream happyPathUpdateTokenInfoAndGetLatestInfo() { final int decimals = 1; final AtomicReference targetLedgerId = new AtomicReference<>(); return propertyPreservingHapiSpec("happyPathUpdateTokenInfoAndGetLatestInfo") @@ -269,7 +271,7 @@ final HapiSpec happyPathUpdateTokenInfoAndGetLatestInfo() { })); } - final HapiSpec happyPathUpdateFungibleTokenInfoAndGetLatestInfo() { + final Stream happyPathUpdateFungibleTokenInfoAndGetLatestInfo() { final int decimals = 1; final AtomicReference targetLedgerId = new AtomicReference<>(); return propertyPreservingHapiSpec("happyPathUpdateFungibleTokenInfoAndGetLatestInfo") @@ -377,7 +379,7 @@ final HapiSpec happyPathUpdateFungibleTokenInfoAndGetLatestInfo() { })); } - final HapiSpec happyPathUpdateNonFungibleTokenInfoAndGetLatestInfo() { + final Stream happyPathUpdateNonFungibleTokenInfoAndGetLatestInfo() { final int maxSupply = 10; final ByteString meta = ByteString.copyFrom(META.getBytes(StandardCharsets.UTF_8)); final AtomicReference targetLedgerId = new AtomicReference<>(); @@ -498,7 +500,7 @@ final HapiSpec happyPathUpdateNonFungibleTokenInfoAndGetLatestInfo() { })); } - final HapiSpec happyPathUpdateTokenKeysAndReadLatestInformation() { + final Stream happyPathUpdateTokenKeysAndReadLatestInformation() { final String TOKEN_INFO_AS_KEY = "TOKEN_INFO_CONTRACT_KEY"; return propertyPreservingHapiSpec("happyPathUpdateTokenKeysAndReadLatestInformation") .preserving(CONTRACTS_ALLOW_SYSTEM_USE_OF_HAPI_SIGS, CONTRACTS_MAX_NUM_WITH_HAPI_SIGS_ACCESS) diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/precompile/TokenUpdatePrecompileSuite.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/precompile/TokenUpdatePrecompileSuite.java index 05128cbdfa16..5793bad262e9 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/precompile/TokenUpdatePrecompileSuite.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/precompile/TokenUpdatePrecompileSuite.java @@ -17,14 +17,18 @@ package com.hedera.services.bdd.suites.contract.precompile; import static com.hedera.services.bdd.junit.TestTags.SMART_CONTRACT; +import static com.hedera.services.bdd.junit.TestTags.TOKEN; import static com.hedera.services.bdd.spec.HapiSpec.defaultHapiSpec; import static com.hedera.services.bdd.spec.assertions.TransactionRecordAsserts.recordWith; import static com.hedera.services.bdd.spec.keys.KeyShape.CONTRACT; import static com.hedera.services.bdd.spec.keys.KeyShape.DELEGATE_CONTRACT; import static com.hedera.services.bdd.spec.keys.KeyShape.ED25519; import static com.hedera.services.bdd.spec.keys.KeyShape.SECP256K1; +import static com.hedera.services.bdd.spec.keys.KeyShape.sigs; import static com.hedera.services.bdd.spec.keys.SigControl.ED25519_ON; +import static com.hedera.services.bdd.spec.keys.SigControl.ON; import static com.hedera.services.bdd.spec.queries.QueryVerbs.contractCallLocal; +import static com.hedera.services.bdd.spec.queries.QueryVerbs.getTokenInfo; import static com.hedera.services.bdd.spec.transactions.TxnVerbs.contractCall; import static com.hedera.services.bdd.spec.transactions.TxnVerbs.contractCreate; import static com.hedera.services.bdd.spec.transactions.TxnVerbs.cryptoCreate; @@ -41,36 +45,37 @@ import static com.hedera.services.bdd.spec.utilops.UtilVerbs.newKeyNamed; import static com.hedera.services.bdd.spec.utilops.UtilVerbs.sourcing; import static com.hedera.services.bdd.spec.utilops.UtilVerbs.withOpContext; +import static com.hedera.services.bdd.suites.HapiSuite.ONE_HBAR; +import static com.hedera.services.bdd.suites.HapiSuite.ONE_MILLION_HBARS; +import static com.hedera.services.bdd.suites.HapiSuite.TOKEN_TREASURY; import static com.hedera.services.bdd.suites.contract.Utils.asAddress; import static com.hedera.services.bdd.suites.contract.Utils.asToken; import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.BUSY; import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.CONTRACT_REVERT_EXECUTED; +import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.INVALID_SIGNATURE; import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.INVALID_TOKEN_ID; import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.KEY_NOT_PROVIDED; +import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.SUCCESS; import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.TOKEN_IS_IMMUTABLE; import static com.hederahashgraph.api.proto.java.TokenType.FUNGIBLE_COMMON; import com.google.protobuf.ByteString; import com.hedera.services.bdd.junit.HapiTest; -import com.hedera.services.bdd.junit.HapiTestSuite; -import com.hedera.services.bdd.spec.HapiSpec; +import com.hedera.services.bdd.spec.keys.KeyShape; import com.hedera.services.bdd.spec.transactions.contract.HapiParserUtil; -import com.hedera.services.bdd.suites.HapiSuite; +import com.hederahashgraph.api.proto.java.AccountID; import com.hederahashgraph.api.proto.java.TokenID; +import com.hederahashgraph.api.proto.java.TokenSupplyType; import com.hederahashgraph.api.proto.java.TokenType; import java.math.BigInteger; import java.util.List; import java.util.concurrent.atomic.AtomicReference; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; +import java.util.stream.Stream; +import org.junit.jupiter.api.DynamicTest; import org.junit.jupiter.api.Tag; -@HapiTestSuite @Tag(SMART_CONTRACT) -public class TokenUpdatePrecompileSuite extends HapiSuite { - - private static final Logger log = LogManager.getLogger(TokenUpdatePrecompileSuite.class); - +public class TokenUpdatePrecompileSuite { private static final long GAS_TO_OFFER = 4_000_000L; private static final long AUTO_RENEW_PERIOD = 8_000_000L; private static final String ACCOUNT = "account"; @@ -89,40 +94,13 @@ public class TokenUpdatePrecompileSuite extends HapiSuite { private static final String DELEGATE_KEY = "tokenUpdateAsKeyDelegate"; private static final String ACCOUNT_TO_ASSOCIATE = "account3"; private static final String ACCOUNT_TO_ASSOCIATE_KEY = "associateKey"; - public static final String CUSTOM_NAME = "customName"; - public static final String CUSTOM_SYMBOL = "Ω"; - public static final String CUSTOM_MEMO = "Omega"; + private static final String AUTO_RENEW_ACCOUNT = "autoRenewAccount"; private static final long ADMIN_KEY_TYPE = 1L; private static final long SUPPLY_KEY_TYPE = 16L; - - public static void main(String... args) { - new TokenUpdatePrecompileSuite().runSuiteAsync(); - } - - @Override - public boolean canRunConcurrent() { - return true; - } - - @Override - protected Logger getResultsLogger() { - return log; - } - - @Override - public List getSpecsInSuite() { - return allOf(negativeCases()); - } - - List negativeCases() { - return List.of( - updateTokenWithInvalidKeyValues(), - updateNftTokenKeysWithWrongTokenIdAndMissingAdminKey(), - getTokenKeyForNonFungibleNegative()); - } + private static final KeyShape KEY_SHAPE = KeyShape.threshOf(1, ED25519, CONTRACT); @HapiTest - final HapiSpec updateTokenWithInvalidKeyValues() { + final Stream updateTokenWithInvalidKeyValues() { final AtomicReference vanillaTokenID = new AtomicReference<>(); return defaultHapiSpec("updateTokenWithInvalidKeyValues") .given( @@ -171,7 +149,7 @@ final HapiSpec updateTokenWithInvalidKeyValues() { } @HapiTest - public HapiSpec updateNftTokenKeysWithWrongTokenIdAndMissingAdminKey() { + final Stream updateNftTokenKeysWithWrongTokenIdAndMissingAdminKey() { final AtomicReference nftToken = new AtomicReference<>(); return defaultHapiSpec("updateNftTokenKeysWithWrongTokenIdAndMissingAdminKey") .given( @@ -252,7 +230,7 @@ public HapiSpec updateNftTokenKeysWithWrongTokenIdAndMissingAdminKey() { } @HapiTest - public HapiSpec getTokenKeyForNonFungibleNegative() { + final Stream getTokenKeyForNonFungibleNegative() { final AtomicReference nftToken = new AtomicReference<>(); return defaultHapiSpec("getTokenKeyForNonFungibleNegative") .given( @@ -317,4 +295,769 @@ public HapiSpec getTokenKeyForNonFungibleNegative() { // .withTokenKeyValue(Key.newBuilder().build())))) )))); } + + @HapiTest + final Stream tokenUpdateSingleFieldCases() { + final var tokenInfoUpdateContract = "TokenInfoSingularUpdate"; + final var newTokenTreasury = "new treasury"; + final var oldName = "Old Name"; + final var sym = "SYM"; + final var memo = "Memo"; + final var newName = "New Name"; + final var sym1 = "SYM1"; + final var newMemo = "New Memo"; + final var updateToEdKey = "updateTokenKeyEd"; + final var updateToContractId = "updateTokenKeyContractId"; + final var contractIdKey = "contractIdKey"; + final AtomicReference token = new AtomicReference<>(); + final AtomicReference newTreasury = new AtomicReference<>(); + final AtomicReference autoRenewAccount = new AtomicReference<>(); + return defaultHapiSpec("tokenUpdateNegativeCases") + .given( + cryptoCreate(TOKEN_TREASURY), + uploadInitCode(tokenInfoUpdateContract), + contractCreate(tokenInfoUpdateContract).gas(GAS_TO_OFFER), + newKeyNamed(MULTI_KEY).shape(KEY_SHAPE.signedWith(sigs(ON, tokenInfoUpdateContract))), + newKeyNamed(ED25519KEY).shape(ED25519), + newKeyNamed(contractIdKey).shape(CONTRACT.signedWith(tokenInfoUpdateContract)), + cryptoCreate(ACCOUNT).balance(ONE_MILLION_HBARS).key(MULTI_KEY), + cryptoCreate(newTokenTreasury).key(MULTI_KEY).exposingCreatedIdTo(newTreasury::set), + cryptoCreate(AUTO_RENEW_ACCOUNT) + .balance(ONE_MILLION_HBARS) + .key(MULTI_KEY) + .exposingCreatedIdTo(autoRenewAccount::set), + tokenCreate(TOKEN) + .tokenType(FUNGIBLE_COMMON) + .name(oldName) + .symbol(sym) + .entityMemo(memo) + .autoRenewAccount(ACCOUNT) + .autoRenewPeriod(AUTO_RENEW_PERIOD) + .supplyType(TokenSupplyType.INFINITE) + .treasury(TOKEN_TREASURY) + .adminKey(MULTI_KEY) + .supplyKey(MULTI_KEY) + .feeScheduleKey(MULTI_KEY) + .pauseKey(MULTI_KEY) + .wipeKey(MULTI_KEY) + .freezeKey(MULTI_KEY) + .kycKey(MULTI_KEY) + .initialSupply(1_000) + .exposingCreatedIdTo(id -> token.set(asToken(id))), + tokenAssociate(ACCOUNT, TOKEN), + tokenAssociate(AUTO_RENEW_ACCOUNT, TOKEN), + tokenAssociate(newTokenTreasury, TOKEN), + grantTokenKyc(TOKEN, ACCOUNT), + cryptoTransfer(moving(500, TOKEN).between(TOKEN_TREASURY, ACCOUNT))) + .when(withOpContext((spec, opLog) -> allRunFor( + spec, + + // Try updating token name + contractCall( + tokenInfoUpdateContract, + "updateTokenName", + HapiParserUtil.asHeadlongAddress(asAddress(token.get())), + newName) + .via("updateOnlyName") + .logged() + .signingWith(MULTI_KEY) + .payingWith(ACCOUNT) + .gas(GAS_TO_OFFER) + .hasKnownStatus(SUCCESS), + getTokenInfo(TOKEN) + .logged() + .hasTokenType(TokenType.FUNGIBLE_COMMON) + .hasSymbol(sym) + .hasName(newName) + .hasEntityMemo(memo) + .hasTreasury(TOKEN_TREASURY) + .hasAutoRenewAccount(ACCOUNT) + .hasAutoRenewPeriod(AUTO_RENEW_PERIOD) + .hasSupplyType(TokenSupplyType.INFINITE) + .searchKeysGlobally() + .hasAdminKey(MULTI_KEY) + .hasKycKey(MULTI_KEY) + .hasFreezeKey(MULTI_KEY) + .hasWipeKey(MULTI_KEY) + .hasFeeScheduleKey(MULTI_KEY) + .hasSupplyKey(MULTI_KEY) + .hasPauseKey(MULTI_KEY), + + // Try updating token symbol + contractCall( + tokenInfoUpdateContract, + "updateTokenSymbol", + HapiParserUtil.asHeadlongAddress(asAddress(token.get())), + sym1) + .via("updateOnlySym") + .logged() + .signingWith(MULTI_KEY) + .payingWith(ACCOUNT) + .gas(GAS_TO_OFFER) + .hasKnownStatus(SUCCESS), + getTokenInfo(TOKEN) + .logged() + .hasTokenType(TokenType.FUNGIBLE_COMMON) + .hasSymbol(sym1) + .hasName(newName) + .hasEntityMemo(memo) + .hasTreasury(TOKEN_TREASURY) + .hasAutoRenewAccount(ACCOUNT) + .hasAutoRenewPeriod(AUTO_RENEW_PERIOD) + .hasSupplyType(TokenSupplyType.INFINITE) + .searchKeysGlobally() + .hasAdminKey(MULTI_KEY) + .hasKycKey(MULTI_KEY) + .hasFreezeKey(MULTI_KEY) + .hasWipeKey(MULTI_KEY) + .hasFeeScheduleKey(MULTI_KEY) + .hasSupplyKey(MULTI_KEY) + .hasPauseKey(MULTI_KEY), + + // Try updating token memo + contractCall( + tokenInfoUpdateContract, + "updateTokenMemo", + HapiParserUtil.asHeadlongAddress(asAddress(token.get())), + newMemo) + .via("updateOnlyMemo") + .logged() + .signingWith(MULTI_KEY) + .payingWith(ACCOUNT) + .gas(GAS_TO_OFFER) + .hasKnownStatus(SUCCESS), + getTokenInfo(TOKEN) + .logged() + .hasTokenType(TokenType.FUNGIBLE_COMMON) + .hasSymbol(sym1) + .hasName(newName) + .hasEntityMemo(newMemo) + .hasTreasury(TOKEN_TREASURY) + .hasAutoRenewAccount(ACCOUNT) + .hasAutoRenewPeriod(AUTO_RENEW_PERIOD) + .hasSupplyType(TokenSupplyType.INFINITE) + .searchKeysGlobally() + .hasAdminKey(MULTI_KEY) + .hasKycKey(MULTI_KEY) + .hasFreezeKey(MULTI_KEY) + .hasWipeKey(MULTI_KEY) + .hasFeeScheduleKey(MULTI_KEY) + .hasSupplyKey(MULTI_KEY) + .hasPauseKey(MULTI_KEY), + + // Try updating token treasury + contractCall( + tokenInfoUpdateContract, + "updateTokenTreasury", + HapiParserUtil.asHeadlongAddress(asAddress(token.get())), + HapiParserUtil.asHeadlongAddress(asAddress(newTreasury.get()))) + .via("updateOnlyTreasury") + .logged() + .signingWith(MULTI_KEY) + .payingWith(ACCOUNT) + .gas(GAS_TO_OFFER) + .hasKnownStatus(SUCCESS), + getTokenInfo(TOKEN) + .logged() + .hasTokenType(TokenType.FUNGIBLE_COMMON) + .hasSymbol(sym1) + .hasName(newName) + .hasEntityMemo(newMemo) + .hasTreasury(newTokenTreasury) + .hasAutoRenewAccount(ACCOUNT) + .hasAutoRenewPeriod(AUTO_RENEW_PERIOD) + .hasSupplyType(TokenSupplyType.INFINITE) + .searchKeysGlobally() + .hasAdminKey(MULTI_KEY) + .hasKycKey(MULTI_KEY) + .hasFreezeKey(MULTI_KEY) + .hasWipeKey(MULTI_KEY) + .hasFeeScheduleKey(MULTI_KEY) + .hasSupplyKey(MULTI_KEY) + .hasPauseKey(MULTI_KEY), + + // Try updating auto-renew account + contractCall( + tokenInfoUpdateContract, + "updateTokenAutoRenewAccount", + HapiParserUtil.asHeadlongAddress(asAddress(token.get())), + HapiParserUtil.asHeadlongAddress(asAddress(autoRenewAccount.get()))) + .via("updateOnlyAutoRenewAccount") + .logged() + .signingWith(MULTI_KEY) + .payingWith(ACCOUNT) + .gas(GAS_TO_OFFER) + .hasKnownStatus(SUCCESS), + getTokenInfo(TOKEN) + .logged() + .hasTokenType(TokenType.FUNGIBLE_COMMON) + .hasSymbol(sym1) + .hasName(newName) + .hasEntityMemo(newMemo) + .hasTreasury(newTokenTreasury) + .hasAutoRenewAccount(AUTO_RENEW_ACCOUNT) + .hasAutoRenewPeriod(AUTO_RENEW_PERIOD) + .hasSupplyType(TokenSupplyType.INFINITE) + .searchKeysGlobally() + .hasAdminKey(MULTI_KEY) + .hasKycKey(MULTI_KEY) + .hasFreezeKey(MULTI_KEY) + .hasWipeKey(MULTI_KEY) + .hasFeeScheduleKey(MULTI_KEY) + .hasSupplyKey(MULTI_KEY) + .hasPauseKey(MULTI_KEY), + + // Try updating auto-renew period + contractCall( + tokenInfoUpdateContract, + "updateTokenAutoRenewPeriod", + HapiParserUtil.asHeadlongAddress(asAddress(token.get())), + AUTO_RENEW_PERIOD - 1000L) + .via("updateOnlyAutoRenewPeriod") + .logged() + .signingWith(MULTI_KEY) + .payingWith(ACCOUNT) + .gas(GAS_TO_OFFER) + .hasKnownStatus(SUCCESS), + getTokenInfo(TOKEN) + .logged() + .hasTokenType(TokenType.FUNGIBLE_COMMON) + .hasSymbol(sym1) + .hasName(newName) + .hasEntityMemo(newMemo) + .hasTreasury(newTokenTreasury) + .hasAutoRenewAccount(AUTO_RENEW_ACCOUNT) + .hasAutoRenewPeriod(AUTO_RENEW_PERIOD - 1000L) + .hasSupplyType(TokenSupplyType.INFINITE) + .searchKeysGlobally() + .hasAdminKey(MULTI_KEY) + .hasKycKey(MULTI_KEY) + .hasFreezeKey(MULTI_KEY) + .hasWipeKey(MULTI_KEY) + .hasFeeScheduleKey(MULTI_KEY) + .hasSupplyKey(MULTI_KEY) + .hasPauseKey(MULTI_KEY), + + // Try update token admin key + contractCall( + tokenInfoUpdateContract, + updateToEdKey, + HapiParserUtil.asHeadlongAddress(asAddress(token.get())), + spec.registry() + .getKey(ED25519KEY) + .getEd25519() + .toByteArray(), + 0) + .via("updateOnlyAdminKeyWithEd") + .logged() + .signedBy(ED25519KEY, MULTI_KEY) + .payingWith(ACCOUNT) + .gas(GAS_TO_OFFER) + .hasKnownStatus(CONTRACT_REVERT_EXECUTED), + getTokenInfo(TOKEN) + .logged() + .hasTokenType(TokenType.FUNGIBLE_COMMON) + .hasSymbol(sym1) + .hasName(newName) + .hasEntityMemo(newMemo) + .hasTreasury(newTokenTreasury) + .hasAutoRenewAccount(AUTO_RENEW_ACCOUNT) + .hasAutoRenewPeriod(AUTO_RENEW_PERIOD - 1000L) + .hasSupplyType(TokenSupplyType.INFINITE) + .searchKeysGlobally() + .hasAdminKey(MULTI_KEY) + .hasKycKey(MULTI_KEY) + .hasFreezeKey(MULTI_KEY) + .hasWipeKey(MULTI_KEY) + .hasFeeScheduleKey(MULTI_KEY) + .hasSupplyKey(MULTI_KEY) + .hasPauseKey(MULTI_KEY), + contractCall( + tokenInfoUpdateContract, + updateToContractId, + HapiParserUtil.asHeadlongAddress(asAddress(token.get())), + HapiParserUtil.asHeadlongAddress(asAddress(spec.registry() + .getKey(contractIdKey) + .getContractID())), + 0) + .via("updateOnlyAdminKey") + .logged() + .signedBy(ED25519KEY, MULTI_KEY) + .payingWith(ACCOUNT) + .gas(GAS_TO_OFFER) + .hasKnownStatus(SUCCESS), + getTokenInfo(TOKEN) + .logged() + .hasTokenType(TokenType.FUNGIBLE_COMMON) + .hasSymbol(sym1) + .hasName(newName) + .hasEntityMemo(newMemo) + .hasTreasury(newTokenTreasury) + .hasAutoRenewAccount(AUTO_RENEW_ACCOUNT) + .hasAutoRenewPeriod(AUTO_RENEW_PERIOD - 1000L) + .hasSupplyType(TokenSupplyType.INFINITE) + .searchKeysGlobally() + .hasAdminKey(contractIdKey) + .hasKycKey(MULTI_KEY) + .hasFreezeKey(MULTI_KEY) + .hasWipeKey(MULTI_KEY) + .hasFeeScheduleKey(MULTI_KEY) + .hasSupplyKey(MULTI_KEY) + .hasPauseKey(MULTI_KEY), + + // Try update token kyc key + contractCall( + tokenInfoUpdateContract, + updateToEdKey, + HapiParserUtil.asHeadlongAddress(asAddress(token.get())), + spec.registry() + .getKey(ED25519KEY) + .getEd25519() + .toByteArray(), + 1) + .via("updateOnlyKycKeyWithEd") + .logged() + .signedBy(ED25519KEY, MULTI_KEY) + .payingWith(ACCOUNT) + .gas(GAS_TO_OFFER) + .hasKnownStatus(SUCCESS), + getTokenInfo(TOKEN) + .logged() + .hasTokenType(TokenType.FUNGIBLE_COMMON) + .hasSymbol(sym1) + .hasName(newName) + .hasEntityMemo(newMemo) + .hasTreasury(newTokenTreasury) + .hasAutoRenewAccount(AUTO_RENEW_ACCOUNT) + .hasAutoRenewPeriod(AUTO_RENEW_PERIOD - 1000L) + .hasSupplyType(TokenSupplyType.INFINITE) + .searchKeysGlobally() + .hasAdminKey(contractIdKey) + .hasKycKey(ED25519KEY) + .hasFreezeKey(MULTI_KEY) + .hasWipeKey(MULTI_KEY) + .hasFeeScheduleKey(MULTI_KEY) + .hasSupplyKey(MULTI_KEY) + .hasPauseKey(MULTI_KEY), + contractCall( + tokenInfoUpdateContract, + updateToContractId, + HapiParserUtil.asHeadlongAddress(asAddress(token.get())), + HapiParserUtil.asHeadlongAddress(asAddress(spec.registry() + .getKey(contractIdKey) + .getContractID())), + 1) + .via("updateOnlyKycKey") + .logged() + .signedBy(MULTI_KEY) + .payingWith(ACCOUNT) + .gas(GAS_TO_OFFER) + .hasKnownStatus(SUCCESS), + getTokenInfo(TOKEN) + .logged() + .hasTokenType(TokenType.FUNGIBLE_COMMON) + .hasSymbol(sym1) + .hasName(newName) + .hasEntityMemo(newMemo) + .hasTreasury(newTokenTreasury) + .hasAutoRenewAccount(AUTO_RENEW_ACCOUNT) + .hasAutoRenewPeriod(AUTO_RENEW_PERIOD - 1000L) + .hasSupplyType(TokenSupplyType.INFINITE) + .searchKeysGlobally() + .hasAdminKey(contractIdKey) + .hasKycKey(contractIdKey) + .hasFreezeKey(MULTI_KEY) + .hasWipeKey(MULTI_KEY) + .hasFeeScheduleKey(MULTI_KEY) + .hasSupplyKey(MULTI_KEY) + .hasPauseKey(MULTI_KEY), + + // Try update token freeze key + contractCall( + tokenInfoUpdateContract, + updateToEdKey, + HapiParserUtil.asHeadlongAddress(asAddress(token.get())), + spec.registry() + .getKey(ED25519KEY) + .getEd25519() + .toByteArray(), + 2) + .via("updateOnlyFreezeKeyWithEd") + .logged() + .signedBy(contractIdKey, MULTI_KEY) + .payingWith(ACCOUNT) + .gas(GAS_TO_OFFER) + .hasKnownStatus(SUCCESS), + getTokenInfo(TOKEN) + .logged() + .hasTokenType(TokenType.FUNGIBLE_COMMON) + .hasSymbol(sym1) + .hasName(newName) + .hasEntityMemo(newMemo) + .hasTreasury(newTokenTreasury) + .hasAutoRenewAccount(AUTO_RENEW_ACCOUNT) + .hasAutoRenewPeriod(AUTO_RENEW_PERIOD - 1000L) + .hasSupplyType(TokenSupplyType.INFINITE) + .searchKeysGlobally() + .hasAdminKey(contractIdKey) + .hasKycKey(contractIdKey) + .hasFreezeKey(ED25519KEY) + .hasWipeKey(MULTI_KEY) + .hasFeeScheduleKey(MULTI_KEY) + .hasSupplyKey(MULTI_KEY) + .hasPauseKey(MULTI_KEY), + contractCall( + tokenInfoUpdateContract, + updateToContractId, + HapiParserUtil.asHeadlongAddress(asAddress(token.get())), + HapiParserUtil.asHeadlongAddress(asAddress(spec.registry() + .getKey(contractIdKey) + .getContractID())), + 2) + .via("updateOnlyFreezeKey") + .logged() + .signedBy(ED25519KEY, MULTI_KEY) + .payingWith(ACCOUNT) + .gas(GAS_TO_OFFER) + .hasKnownStatus(SUCCESS), + getTokenInfo(TOKEN) + .logged() + .hasTokenType(TokenType.FUNGIBLE_COMMON) + .hasSymbol(sym1) + .hasName(newName) + .hasEntityMemo(newMemo) + .hasTreasury(newTokenTreasury) + .hasAutoRenewAccount(AUTO_RENEW_ACCOUNT) + .hasAutoRenewPeriod(AUTO_RENEW_PERIOD - 1000L) + .hasSupplyType(TokenSupplyType.INFINITE) + .searchKeysGlobally() + .hasAdminKey(contractIdKey) + .hasKycKey(contractIdKey) + .hasFreezeKey(contractIdKey) + .hasWipeKey(MULTI_KEY) + .hasFeeScheduleKey(MULTI_KEY) + .hasSupplyKey(MULTI_KEY) + .hasPauseKey(MULTI_KEY), + + // Try update token wipe key + contractCall( + tokenInfoUpdateContract, + updateToEdKey, + HapiParserUtil.asHeadlongAddress(asAddress(token.get())), + spec.registry() + .getKey(ED25519KEY) + .getEd25519() + .toByteArray(), + 3) + .via("updateOnlyWipeKeyWithEd") + .logged() + .signedBy(contractIdKey, MULTI_KEY) + .payingWith(ACCOUNT) + .gas(GAS_TO_OFFER) + .hasKnownStatus(SUCCESS), + getTokenInfo(TOKEN) + .logged() + .hasTokenType(TokenType.FUNGIBLE_COMMON) + .hasSymbol(sym1) + .hasName(newName) + .hasEntityMemo(newMemo) + .hasTreasury(newTokenTreasury) + .hasAutoRenewAccount(AUTO_RENEW_ACCOUNT) + .hasAutoRenewPeriod(AUTO_RENEW_PERIOD - 1000L) + .hasSupplyType(TokenSupplyType.INFINITE) + .searchKeysGlobally() + .hasAdminKey(contractIdKey) + .hasKycKey(contractIdKey) + .hasFreezeKey(contractIdKey) + .hasWipeKey(ED25519KEY) + .hasFeeScheduleKey(MULTI_KEY) + .hasSupplyKey(MULTI_KEY) + .hasPauseKey(MULTI_KEY), + contractCall( + tokenInfoUpdateContract, + updateToContractId, + HapiParserUtil.asHeadlongAddress(asAddress(token.get())), + HapiParserUtil.asHeadlongAddress(asAddress(spec.registry() + .getKey(contractIdKey) + .getContractID())), + 3) + .via("updateOnlyWipeKey") + .logged() + .signedBy(contractIdKey, MULTI_KEY) + .payingWith(ACCOUNT) + .gas(GAS_TO_OFFER) + .hasKnownStatus(SUCCESS), + getTokenInfo(TOKEN) + .logged() + .hasTokenType(TokenType.FUNGIBLE_COMMON) + .hasSymbol(sym1) + .hasName(newName) + .hasEntityMemo(newMemo) + .hasTreasury(newTokenTreasury) + .hasAutoRenewAccount(AUTO_RENEW_ACCOUNT) + .hasAutoRenewPeriod(AUTO_RENEW_PERIOD - 1000L) + .hasSupplyType(TokenSupplyType.INFINITE) + .searchKeysGlobally() + .hasAdminKey(contractIdKey) + .hasKycKey(contractIdKey) + .hasFreezeKey(contractIdKey) + .hasWipeKey(contractIdKey) + .hasFeeScheduleKey(MULTI_KEY) + .hasSupplyKey(MULTI_KEY) + .hasPauseKey(MULTI_KEY), + + // Try update token supply key + contractCall( + tokenInfoUpdateContract, + updateToEdKey, + HapiParserUtil.asHeadlongAddress(asAddress(token.get())), + spec.registry() + .getKey(ED25519KEY) + .getEd25519() + .toByteArray(), + 4) + .via("updateOnlySupplyKeyWithEd") + .logged() + .signedBy(contractIdKey, MULTI_KEY) + .payingWith(ACCOUNT) + .gas(GAS_TO_OFFER) + .hasKnownStatus(SUCCESS), + getTokenInfo(TOKEN) + .logged() + .hasTokenType(TokenType.FUNGIBLE_COMMON) + .hasSymbol(sym1) + .hasName(newName) + .hasEntityMemo(newMemo) + .hasTreasury(newTokenTreasury) + .hasAutoRenewAccount(AUTO_RENEW_ACCOUNT) + .hasAutoRenewPeriod(AUTO_RENEW_PERIOD - 1000L) + .hasSupplyType(TokenSupplyType.INFINITE) + .searchKeysGlobally() + .hasAdminKey(contractIdKey) + .hasKycKey(contractIdKey) + .hasFreezeKey(contractIdKey) + .hasWipeKey(contractIdKey) + .hasSupplyKey(ED25519KEY) + .hasFeeScheduleKey(MULTI_KEY) + .hasPauseKey(MULTI_KEY), + contractCall( + tokenInfoUpdateContract, + updateToContractId, + HapiParserUtil.asHeadlongAddress(asAddress(token.get())), + HapiParserUtil.asHeadlongAddress(asAddress(spec.registry() + .getKey(contractIdKey) + .getContractID())), + 4) + .via("updateOnlySupplyKey") + .logged() + .signedBy(contractIdKey, MULTI_KEY) + .payingWith(ACCOUNT) + .gas(GAS_TO_OFFER) + .hasKnownStatus(SUCCESS), + getTokenInfo(TOKEN) + .logged() + .hasTokenType(TokenType.FUNGIBLE_COMMON) + .hasSymbol(sym1) + .hasName(newName) + .hasEntityMemo(newMemo) + .hasTreasury(newTokenTreasury) + .hasAutoRenewAccount(AUTO_RENEW_ACCOUNT) + .hasAutoRenewPeriod(AUTO_RENEW_PERIOD - 1000L) + .hasSupplyType(TokenSupplyType.INFINITE) + .searchKeysGlobally() + .hasAdminKey(contractIdKey) + .hasKycKey(contractIdKey) + .hasFreezeKey(contractIdKey) + .hasWipeKey(contractIdKey) + .hasSupplyKey(contractIdKey) + .hasPauseKey(MULTI_KEY) + .hasFeeScheduleKey(MULTI_KEY) + .hasPauseKey(MULTI_KEY), + + // Try update token fee schedule key + contractCall( + tokenInfoUpdateContract, + updateToEdKey, + HapiParserUtil.asHeadlongAddress(asAddress(token.get())), + spec.registry() + .getKey(ED25519KEY) + .getEd25519() + .toByteArray(), + 5) + .via("updateOnlyFeeKeyWithEd") + .logged() + .signedBy(contractIdKey, MULTI_KEY) + .payingWith(ACCOUNT) + .gas(GAS_TO_OFFER) + .hasKnownStatus(SUCCESS), + getTokenInfo(TOKEN) + .logged() + .hasTokenType(TokenType.FUNGIBLE_COMMON) + .hasSymbol(sym1) + .hasName(newName) + .hasEntityMemo(newMemo) + .hasTreasury(newTokenTreasury) + .hasAutoRenewAccount(AUTO_RENEW_ACCOUNT) + .hasAutoRenewPeriod(AUTO_RENEW_PERIOD - 1000L) + .hasSupplyType(TokenSupplyType.INFINITE) + .searchKeysGlobally() + .hasAdminKey(contractIdKey) + .hasKycKey(contractIdKey) + .hasFreezeKey(contractIdKey) + .hasWipeKey(contractIdKey) + .hasSupplyKey(contractIdKey) + .hasFeeScheduleKey(ED25519KEY) + .hasPauseKey(MULTI_KEY), + contractCall( + tokenInfoUpdateContract, + updateToContractId, + HapiParserUtil.asHeadlongAddress(asAddress(token.get())), + HapiParserUtil.asHeadlongAddress(asAddress(spec.registry() + .getKey(contractIdKey) + .getContractID())), + 5) + .via("updateOnlyFeeKey") + .logged() + .signedBy(contractIdKey, MULTI_KEY) + .payingWith(ACCOUNT) + .gas(GAS_TO_OFFER) + .hasKnownStatus(SUCCESS), + getTokenInfo(TOKEN) + .logged() + .hasTokenType(TokenType.FUNGIBLE_COMMON) + .hasSymbol(sym1) + .hasName(newName) + .hasEntityMemo(newMemo) + .hasTreasury(newTokenTreasury) + .hasAutoRenewAccount(AUTO_RENEW_ACCOUNT) + .hasAutoRenewPeriod(AUTO_RENEW_PERIOD - 1000L) + .hasSupplyType(TokenSupplyType.INFINITE) + .searchKeysGlobally() + .hasAdminKey(contractIdKey) + .hasKycKey(contractIdKey) + .hasFreezeKey(contractIdKey) + .hasWipeKey(contractIdKey) + .hasSupplyKey(contractIdKey) + .hasFeeScheduleKey(contractIdKey) + .hasPauseKey(MULTI_KEY), + + // Try update token pause key + contractCall( + tokenInfoUpdateContract, + updateToEdKey, + HapiParserUtil.asHeadlongAddress(asAddress(token.get())), + spec.registry() + .getKey(ED25519KEY) + .getEd25519() + .toByteArray(), + 6) + .via("updateOnlyPauseKeyWithEd") + .logged() + .signedBy(contractIdKey, MULTI_KEY) + .payingWith(ACCOUNT) + .gas(GAS_TO_OFFER) + .hasKnownStatus(SUCCESS), + getTokenInfo(TOKEN) + .logged() + .hasTokenType(TokenType.FUNGIBLE_COMMON) + .hasSymbol(sym1) + .hasName(newName) + .hasEntityMemo(newMemo) + .hasTreasury(newTokenTreasury) + .hasAutoRenewAccount(AUTO_RENEW_ACCOUNT) + .hasAutoRenewPeriod(AUTO_RENEW_PERIOD - 1000L) + .hasSupplyType(TokenSupplyType.INFINITE) + .searchKeysGlobally() + .hasAdminKey(contractIdKey) + .hasKycKey(contractIdKey) + .hasFreezeKey(contractIdKey) + .hasWipeKey(contractIdKey) + .hasSupplyKey(contractIdKey) + .hasFeeScheduleKey(contractIdKey) + .hasPauseKey(ED25519KEY), + contractCall( + tokenInfoUpdateContract, + updateToContractId, + HapiParserUtil.asHeadlongAddress(asAddress(token.get())), + HapiParserUtil.asHeadlongAddress(asAddress(spec.registry() + .getKey(contractIdKey) + .getContractID())), + 6) + .via("updateOnlyPauseKey") + .logged() + .signedBy(contractIdKey, MULTI_KEY) + .payingWith(ACCOUNT) + .gas(GAS_TO_OFFER) + .hasKnownStatus(SUCCESS), + getTokenInfo(TOKEN) + .logged() + .hasTokenType(TokenType.FUNGIBLE_COMMON) + .hasSymbol(sym1) + .hasName(newName) + .hasEntityMemo(newMemo) + .hasTreasury(newTokenTreasury) + .hasAutoRenewAccount(AUTO_RENEW_ACCOUNT) + .hasAutoRenewPeriod(AUTO_RENEW_PERIOD - 1000L) + .hasSupplyType(TokenSupplyType.INFINITE) + .searchKeysGlobally() + .hasAdminKey(contractIdKey) + .hasKycKey(contractIdKey) + .hasFreezeKey(contractIdKey) + .hasWipeKey(contractIdKey) + .hasSupplyKey(contractIdKey) + .hasFeeScheduleKey(contractIdKey) + .hasPauseKey(contractIdKey)))) + .then( + childRecordsCheck( + "updateOnlyName", SUCCESS, recordWith().status(SUCCESS)), + childRecordsCheck("updateOnlySym", SUCCESS, recordWith().status(SUCCESS)), + childRecordsCheck( + "updateOnlyMemo", SUCCESS, recordWith().status(SUCCESS)), + childRecordsCheck( + "updateOnlyTreasury", SUCCESS, recordWith().status(SUCCESS)), + childRecordsCheck( + "updateOnlyAutoRenewAccount", + SUCCESS, + recordWith().status(SUCCESS)), + childRecordsCheck( + "updateOnlyAutoRenewPeriod", + SUCCESS, + recordWith().status(SUCCESS)), + childRecordsCheck( + "updateOnlyAdminKeyWithEd", + CONTRACT_REVERT_EXECUTED, + recordWith().status(INVALID_SIGNATURE)), + childRecordsCheck( + "updateOnlyAdminKey", SUCCESS, recordWith().status(SUCCESS)), + childRecordsCheck( + "updateOnlyKycKeyWithEd", SUCCESS, recordWith().status(SUCCESS)), + childRecordsCheck( + "updateOnlyKycKey", SUCCESS, recordWith().status(SUCCESS)), + childRecordsCheck( + "updateOnlyFreezeKeyWithEd", + SUCCESS, + recordWith().status(SUCCESS)), + childRecordsCheck( + "updateOnlyFreezeKey", SUCCESS, recordWith().status(SUCCESS)), + childRecordsCheck( + "updateOnlyWipeKeyWithEd", SUCCESS, recordWith().status(SUCCESS)), + childRecordsCheck( + "updateOnlyWipeKey", SUCCESS, recordWith().status(SUCCESS)), + childRecordsCheck( + "updateOnlySupplyKeyWithEd", + SUCCESS, + recordWith().status(SUCCESS)), + childRecordsCheck( + "updateOnlySupplyKey", SUCCESS, recordWith().status(SUCCESS)), + childRecordsCheck( + "updateOnlyFeeKeyWithEd", SUCCESS, recordWith().status(SUCCESS)), + childRecordsCheck( + "updateOnlyFeeKey", SUCCESS, recordWith().status(SUCCESS)), + childRecordsCheck( + "updateOnlyPauseKeyWithEd", + SUCCESS, + recordWith().status(SUCCESS)), + childRecordsCheck( + "updateOnlyPauseKey", SUCCESS, recordWith().status(SUCCESS))); + } } diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/precompile/TokenUpdatePrecompileV1SecurityModelSuite.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/precompile/TokenUpdatePrecompileV1SecurityModelSuite.java index e57797e4be1e..a835f7b5aecc 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/precompile/TokenUpdatePrecompileV1SecurityModelSuite.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/precompile/TokenUpdatePrecompileV1SecurityModelSuite.java @@ -65,7 +65,6 @@ import com.google.protobuf.ByteString; import com.hedera.node.app.hapi.utils.contracts.ParsingConstants; -import com.hedera.services.bdd.spec.HapiSpec; import com.hedera.services.bdd.spec.assertions.TransactionRecordAsserts; import com.hedera.services.bdd.spec.transactions.TxnUtils; import com.hedera.services.bdd.spec.transactions.contract.HapiParserUtil; @@ -78,8 +77,10 @@ import java.util.ArrayList; import java.util.List; import java.util.concurrent.atomic.AtomicReference; +import java.util.stream.Stream; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; +import org.junit.jupiter.api.DynamicTest; @SuppressWarnings("java:S1192") // "string literal should not be duplicated" - this rule makes test suites worse public class TokenUpdatePrecompileV1SecurityModelSuite extends HapiSuite { @@ -136,11 +137,11 @@ protected Logger getResultsLogger() { } @Override - public List getSpecsInSuite() { + public List> getSpecsInSuite() { return allOf(positiveCases(), negativeCases()); } - List positiveCases() { + List> positiveCases() { return List.of( updateTokenWithKeysHappyPath(), updateNftTreasuryWithAndWithoutAdminKey(), @@ -149,11 +150,11 @@ List positiveCases() { updateTokenWithoutNameSymbolMemo()); } - List negativeCases() { + List> negativeCases() { return List.of(updateWithTooLongNameAndSymbol(), updateTokenWithKeysNegative()); } - final HapiSpec updateTokenWithKeysHappyPath() { + final Stream updateTokenWithKeysHappyPath() { final AtomicReference vanillaTokenID = new AtomicReference<>(); return propertyPreservingHapiSpec("updateTokenWithKeysHappyPath") .preserving(CONTRACTS_MAX_NUM_WITH_HAPI_SIGS_ACCESS) @@ -267,7 +268,7 @@ final HapiSpec updateTokenWithKeysHappyPath() { .hasPauseKey(TOKEN_UPDATE_AS_KEY))); } - public HapiSpec updateNftTreasuryWithAndWithoutAdminKey() { + final Stream updateNftTreasuryWithAndWithoutAdminKey() { final var newTokenTreasury = "newTokenTreasury"; final var NO_ADMIN_TOKEN = "noAdminKeyToken"; final AtomicReference noAdminKeyToken = new AtomicReference<>(); @@ -343,7 +344,7 @@ public HapiSpec updateNftTreasuryWithAndWithoutAdminKey() { .logged()); } - public HapiSpec updateWithTooLongNameAndSymbol() { + final Stream updateWithTooLongNameAndSymbol() { final var tooLongString = "ORIGINAL" + TxnUtils.randomUppercase(101); final var tooLongSymbolTxn = "tooLongSymbolTxn"; final AtomicReference vanillaTokenID = new AtomicReference<>(); @@ -407,7 +408,7 @@ public HapiSpec updateWithTooLongNameAndSymbol() { TransactionRecordAsserts.recordWith().status(TOKEN_SYMBOL_TOO_LONG))))); } - final HapiSpec updateTokenWithKeysNegative() { + final Stream updateTokenWithKeysNegative() { final var updateTokenWithKeysFunc = "updateTokenWithKeys"; final var NO_FEE_SCHEDULE_KEY_TXN = "NO_FEE_SCHEDULE_KEY_TXN"; final var NO_PAUSE_KEY_TXN = "NO_PAUSE_KEY_TXN"; @@ -645,7 +646,7 @@ final HapiSpec updateTokenWithKeysNegative() { TransactionRecordAsserts.recordWith().status(TOKEN_HAS_NO_KYC_KEY))))); } - final HapiSpec updateOnlyTokenKeysAndGetTheUpdatedValues() { + final Stream updateOnlyTokenKeysAndGetTheUpdatedValues() { final AtomicReference vanillaTokenID = new AtomicReference<>(); return propertyPreservingHapiSpec("updateOnlyTokenKeysAndGetTheUpdatedValues") @@ -839,7 +840,7 @@ final HapiSpec updateOnlyTokenKeysAndGetTheUpdatedValues() { spec.registry().getKey(TOKEN_UPDATE_AS_KEY)))))))); } - public HapiSpec updateOnlyKeysForNonFungibleToken() { + final Stream updateOnlyKeysForNonFungibleToken() { final AtomicReference nftToken = new AtomicReference<>(); return propertyPreservingHapiSpec("updateOnlyKeysForNonFungibleToken") .preserving(CONTRACTS_MAX_NUM_WITH_HAPI_SIGS_ACCESS) @@ -908,7 +909,7 @@ public HapiSpec updateOnlyKeysForNonFungibleToken() { .hasPauseKey(TOKEN_UPDATE_AS_KEY)))); } - final HapiSpec updateTokenWithoutNameSymbolMemo() { + final Stream updateTokenWithoutNameSymbolMemo() { final var updateTokenWithoutNameSymbolMemoFunc = "updateTokenWithoutNameSymbolMemo"; final AtomicReference vanillaTokenID = new AtomicReference<>(); return propertyPreservingHapiSpec("updateTokenWithoutNameSymbolMemo") diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/precompile/TopLevelSigsCanBeToggledByPrecompileTypeSuite.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/precompile/TopLevelSigsCanBeToggledByPrecompileTypeSuite.java deleted file mode 100644 index 22f2ab795555..000000000000 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/precompile/TopLevelSigsCanBeToggledByPrecompileTypeSuite.java +++ /dev/null @@ -1,797 +0,0 @@ -/* - * Copyright (C) 2021-2024 Hedera Hashgraph, LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.hedera.services.bdd.suites.contract.precompile; - -import static com.hedera.services.bdd.spec.HapiSpec.propertyPreservingHapiSpec; -import static com.hedera.services.bdd.spec.assertions.TransactionRecordAsserts.recordWith; -import static com.hedera.services.bdd.spec.keys.KeyShape.CONTRACT; -import static com.hedera.services.bdd.spec.keys.KeyShape.DELEGATE_CONTRACT; -import static com.hedera.services.bdd.spec.keys.KeyShape.ED25519; -import static com.hedera.services.bdd.spec.keys.KeyShape.SECP256K1; -import static com.hedera.services.bdd.spec.keys.SigControl.ED25519_ON; -import static com.hedera.services.bdd.spec.keys.SigControl.SECP256K1_ON; -import static com.hedera.services.bdd.spec.transactions.TxnVerbs.contractCall; -import static com.hedera.services.bdd.spec.transactions.TxnVerbs.contractCreate; -import static com.hedera.services.bdd.spec.transactions.TxnVerbs.cryptoCreate; -import static com.hedera.services.bdd.spec.transactions.TxnVerbs.cryptoTransfer; -import static com.hedera.services.bdd.spec.transactions.TxnVerbs.grantTokenKyc; -import static com.hedera.services.bdd.spec.transactions.TxnVerbs.tokenAssociate; -import static com.hedera.services.bdd.spec.transactions.TxnVerbs.tokenCreate; -import static com.hedera.services.bdd.spec.transactions.TxnVerbs.uploadInitCode; -import static com.hedera.services.bdd.spec.transactions.contract.HapiParserUtil.asHeadlongAddress; -import static com.hedera.services.bdd.spec.transactions.token.TokenMovement.moving; -import static com.hedera.services.bdd.spec.utilops.CustomSpecAssert.allRunFor; -import static com.hedera.services.bdd.spec.utilops.UtilVerbs.childRecordsCheck; -import static com.hedera.services.bdd.spec.utilops.UtilVerbs.newKeyNamed; -import static com.hedera.services.bdd.spec.utilops.UtilVerbs.overriding; -import static com.hedera.services.bdd.spec.utilops.UtilVerbs.sourcing; -import static com.hedera.services.bdd.spec.utilops.UtilVerbs.withOpContext; -import static com.hedera.services.bdd.suites.contract.Utils.asAddress; -import static com.hedera.services.bdd.suites.contract.Utils.asHexedAddress; -import static com.hedera.services.bdd.suites.contract.Utils.asToken; -import static com.hedera.services.bdd.suites.contract.precompile.AssociatePrecompileSuite.THE_CONTRACT; -import static com.hedera.services.bdd.suites.contract.precompile.AssociatePrecompileSuite.TOKEN_ASSOCIATE_FUNCTION; -import static com.hedera.services.bdd.suites.contract.precompile.ContractBurnHTSSuite.BURN_TOKEN_WITH_EVENT; -import static com.hedera.services.bdd.suites.contract.precompile.ContractBurnHTSSuite.CREATION_TX; -import static com.hedera.services.bdd.suites.contract.precompile.ContractBurnHTSSuite.THE_BURN_CONTRACT; -import static com.hedera.services.bdd.suites.contract.precompile.ContractMintHTSSuite.MINT_CONTRACT; -import static com.hedera.services.bdd.suites.contract.precompile.ContractMintHTSSuite.MINT_FUNGIBLE_TOKEN_WITH_EVENT; -import static com.hedera.services.bdd.suites.contract.precompile.CreatePrecompileSuite.ACCOUNT_TO_ASSOCIATE; -import static com.hedera.services.bdd.suites.contract.precompile.CreatePrecompileSuite.ACCOUNT_TO_ASSOCIATE_KEY; -import static com.hedera.services.bdd.suites.contract.precompile.CreatePrecompileSuite.AUTO_RENEW_PERIOD; -import static com.hedera.services.bdd.suites.contract.precompile.CreatePrecompileSuite.DEFAULT_AMOUNT_TO_SEND; -import static com.hedera.services.bdd.suites.contract.precompile.CreatePrecompileSuite.ECDSA_KEY; -import static com.hedera.services.bdd.suites.contract.precompile.CreatePrecompileSuite.ED25519KEY; -import static com.hedera.services.bdd.suites.contract.precompile.CryptoTransferHTSSuite.DELEGATE_KEY; -import static com.hedera.services.bdd.suites.contract.precompile.FreezeUnfreezeTokenPrecompileSuite.FREEZE_CONTRACT; -import static com.hedera.services.bdd.suites.contract.precompile.FreezeUnfreezeTokenPrecompileSuite.TOKEN_FREEZE_FUNC; -import static com.hedera.services.bdd.suites.contract.precompile.FreezeUnfreezeTokenPrecompileSuite.TOKEN_UNFREEZE_FUNC; -import static com.hedera.services.bdd.suites.contract.precompile.GrantRevokeKycSuite.GRANT_REVOKE_KYC_CONTRACT; -import static com.hedera.services.bdd.suites.contract.precompile.GrantRevokeKycSuite.TOKEN_GRANT_KYC; -import static com.hedera.services.bdd.suites.contract.precompile.GrantRevokeKycSuite.TOKEN_REVOKE_KYC; -import static com.hedera.services.bdd.suites.contract.precompile.PauseUnpauseTokenAccountPrecompileSuite.PAUSE_TOKEN_ACCOUNT_FUNCTION_NAME; -import static com.hedera.services.bdd.suites.contract.precompile.PauseUnpauseTokenAccountPrecompileSuite.PAUSE_UNPAUSE_CONTRACT; -import static com.hedera.services.bdd.suites.contract.precompile.PauseUnpauseTokenAccountPrecompileSuite.UNPAUSE_TOKEN_ACCOUNT_FUNCTION_NAME; -import static com.hedera.services.bdd.suites.contract.precompile.TokenUpdatePrecompileSuite.CUSTOM_MEMO; -import static com.hedera.services.bdd.suites.contract.precompile.TokenUpdatePrecompileSuite.CUSTOM_NAME; -import static com.hedera.services.bdd.suites.contract.precompile.TokenUpdatePrecompileSuite.CUSTOM_SYMBOL; -import static com.hedera.services.bdd.suites.contract.precompile.TokenUpdatePrecompileSuite.TOKEN_UPDATE_AS_KEY; -import static com.hedera.services.bdd.suites.contract.precompile.TokenUpdatePrecompileSuite.TOKEN_UPDATE_CONTRACT; -import static com.hedera.services.bdd.suites.contract.precompile.V1SecurityModelOverrides.CONTRACTS_ALLOW_SYSTEM_USE_OF_HAPI_SIGS; -import static com.hedera.services.bdd.suites.crypto.CryptoApproveAllowanceSuite.FREEZE_KEY; -import static com.hedera.services.bdd.suites.crypto.CryptoApproveAllowanceSuite.KYC_KEY; -import static com.hedera.services.bdd.suites.crypto.CryptoApproveAllowanceSuite.PAUSE_KEY; -import static com.hedera.services.bdd.suites.token.TokenAssociationSpecs.VANILLA_TOKEN; -import static com.hedera.services.bdd.suites.token.TokenTransactSpecs.SUPPLY_KEY; -import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.CONTRACT_REVERT_EXECUTED; -import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.INVALID_FULL_PREFIX_SIGNATURE_FOR_PRECOMPILE; -import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.INVALID_SIGNATURE; -import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.SUCCESS; -import static com.hederahashgraph.api.proto.java.TokenType.FUNGIBLE_COMMON; - -import com.esaulpaugh.headlong.abi.Address; -import com.hedera.services.bdd.spec.HapiPropertySource; -import com.hedera.services.bdd.spec.HapiSpec; -import com.hedera.services.bdd.spec.transactions.contract.HapiParserUtil; -import com.hedera.services.bdd.suites.HapiSuite; -import com.hederahashgraph.api.proto.java.AccountID; -import com.hederahashgraph.api.proto.java.TokenID; -import com.hederahashgraph.api.proto.java.TokenType; -import java.math.BigInteger; -import java.util.List; -import java.util.concurrent.atomic.AtomicReference; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; - -@SuppressWarnings("java:S1192") // "string literal should not be duplicated" - this rule makes test suites worse -public class TopLevelSigsCanBeToggledByPrecompileTypeSuite extends HapiSuite { - - private static final Logger log = LogManager.getLogger(TopLevelSigsCanBeToggledByPrecompileTypeSuite.class); - - public static final String DELETE_TOKEN_CONTRACT = "DeleteTokenContract"; - public static final String TOKEN_DELETE_FUNCTION = "tokenDelete"; - public static final String WIPE_CONTRACT = "WipeTokenAccount"; - public static final String ADMIN_ACCOUNT = "admin"; - public static final String TOKEN = "yahcliToken"; - private static final String ACCOUNT = "anybody"; - private static final String SECOND_ACCOUNT = "anybodySecond"; - public static final String WIPE_KEY = "wipeKey"; - private static final String MULTI_KEY = "purpose"; - public static final int GAS_TO_OFFER = 1_000_000; - public static final String WIPE_FUNGIBLE_TOKEN = "wipeFungibleToken"; - - public static void main(String... args) { - new TopLevelSigsCanBeToggledByPrecompileTypeSuite().runSuiteSync(); - } - - @Override - public List getSpecsInSuite() { - return List.of( - canToggleTopLevelSigUsageForAssociatePrecompile(), - canToggleTopLevelSigUsageForBurnPrecompile(), - canToggleTopLevelSigUsageForMintPrecompile(), - canToggleTopLevelSigUsageForDeletePrecompile(), - canToggleTopLevelSigUsageForFreezeAndUnfreezePrecompile(), - canToggleTopLevelSigUsageForGrantKycAndRevokeKycPrecompile(), - canToggleTopLevelSigUsageForPauseAndUnpausePrecompile(), - canToggleTopLevelSigUsageForUpdatePrecompile(), - canToggleTopLevelSigUsageForWipePrecompile()); - } - - final HapiSpec canToggleTopLevelSigUsageForWipePrecompile() { - final var failedWipeTxn = "failedWipeTxn"; - final var succeededWipeTxn = "succeededWipeTxn"; - - final AtomicReference adminAccountID = new AtomicReference<>(); - final AtomicReference accountID = new AtomicReference<>(); - final AtomicReference vanillaTokenID = new AtomicReference<>(); - return propertyPreservingHapiSpec("CanToggleTopLevelSigUsageForWipePrecompile") - .preserving(CONTRACTS_ALLOW_SYSTEM_USE_OF_HAPI_SIGS) - .given( - newKeyNamed(WIPE_KEY), - cryptoCreate(ADMIN_ACCOUNT).exposingCreatedIdTo(adminAccountID::set), - cryptoCreate(ACCOUNT).exposingCreatedIdTo(accountID::set), - cryptoCreate(TOKEN_TREASURY), - tokenCreate(VANILLA_TOKEN) - .tokenType(FUNGIBLE_COMMON) - .treasury(TOKEN_TREASURY) - .wipeKey(WIPE_KEY) - .initialSupply(1_000) - .exposingCreatedIdTo(id -> vanillaTokenID.set(asToken(id))), - uploadInitCode(WIPE_CONTRACT), - contractCreate(WIPE_CONTRACT), - tokenAssociate(ACCOUNT, VANILLA_TOKEN), - cryptoTransfer(moving(500, VANILLA_TOKEN).between(TOKEN_TREASURY, ACCOUNT)), - // First revoke use of top-level signatures from all precompiles - overriding(CONTRACTS_ALLOW_SYSTEM_USE_OF_HAPI_SIGS, "")) - .when( - // Trying to wipe token with top-level signatures should fail - sourcing(() -> contractCall( - WIPE_CONTRACT, - WIPE_FUNGIBLE_TOKEN, - HapiParserUtil.asHeadlongAddress(asAddress(vanillaTokenID.get())), - HapiParserUtil.asHeadlongAddress(asAddress(accountID.get())), - 10L) - .payingWith(ADMIN_ACCOUNT) - .alsoSigningWithFullPrefix(WIPE_KEY) - .via(failedWipeTxn) - .gas(GAS_TO_OFFER) - .hasKnownStatus(CONTRACT_REVERT_EXECUTED)), - // But now restore use of top-level signatures for the token wipe precompile - overriding(CONTRACTS_ALLOW_SYSTEM_USE_OF_HAPI_SIGS, "TokenAccountWipe"), - // Now the same call should succeed - sourcing(() -> contractCall( - WIPE_CONTRACT, - WIPE_FUNGIBLE_TOKEN, - HapiParserUtil.asHeadlongAddress(asAddress(vanillaTokenID.get())), - HapiParserUtil.asHeadlongAddress(asAddress(accountID.get())), - 10L) - .payingWith(ADMIN_ACCOUNT) - .alsoSigningWithFullPrefix(WIPE_KEY) - .via(succeededWipeTxn) - .gas(GAS_TO_OFFER) - .hasKnownStatus(SUCCESS))) - .then( - // Confirm the failure was due to the top-level signature being unavailable - childRecordsCheck( - failedWipeTxn, - CONTRACT_REVERT_EXECUTED, - recordWith().status(INVALID_SIGNATURE))); - } - - final HapiSpec canToggleTopLevelSigUsageForUpdatePrecompile() { - final var failedUpdateTxn = "failedUpdateTxn"; - final var succeededUpdateTxn = "succeededUpdateTxn"; - - final AtomicReference vanillaTokenID = new AtomicReference<>(); - return propertyPreservingHapiSpec("CanToggleTopLevelSigUsageForWipePrecompile") - .preserving(CONTRACTS_ALLOW_SYSTEM_USE_OF_HAPI_SIGS) - .given( - newKeyNamed(ED25519KEY).shape(ED25519), - newKeyNamed(ECDSA_KEY).shape(SECP256K1), - newKeyNamed(ACCOUNT_TO_ASSOCIATE_KEY), - newKeyNamed(MULTI_KEY).shape(ED25519_ON), - cryptoCreate(TOKEN_TREASURY), - cryptoCreate(ACCOUNT).balance(ONE_MILLION_HBARS).key(MULTI_KEY), - cryptoCreate(ACCOUNT_TO_ASSOCIATE).key(ACCOUNT_TO_ASSOCIATE_KEY), - uploadInitCode(TOKEN_UPDATE_CONTRACT), - contractCreate(TOKEN_UPDATE_CONTRACT), - tokenCreate(VANILLA_TOKEN) - .tokenType(FUNGIBLE_COMMON) - .treasury(TOKEN_TREASURY) - .adminKey(MULTI_KEY) - .supplyKey(MULTI_KEY) - .feeScheduleKey(MULTI_KEY) - .pauseKey(MULTI_KEY) - .wipeKey(MULTI_KEY) - .freezeKey(MULTI_KEY) - .kycKey(MULTI_KEY) - .initialSupply(1_000) - .exposingCreatedIdTo(id -> vanillaTokenID.set(asToken(id))), - tokenAssociate(ACCOUNT, VANILLA_TOKEN), - grantTokenKyc(VANILLA_TOKEN, ACCOUNT), - cryptoTransfer(moving(500, VANILLA_TOKEN).between(TOKEN_TREASURY, ACCOUNT)), - // First revoke use of top-level signatures from all precompiles - overriding(CONTRACTS_ALLOW_SYSTEM_USE_OF_HAPI_SIGS, "")) - .when( - // Trying to update token with top-level signatures should fail - withOpContext((spec, opLog) -> allRunFor( - spec, - contractCall( - TOKEN_UPDATE_CONTRACT, - "updateTokenWithAllFields", - HapiParserUtil.asHeadlongAddress(asAddress(vanillaTokenID.get())), - HapiParserUtil.asHeadlongAddress(asAddress( - spec.registry().getAccountID(ACCOUNT))), - spec.registry() - .getKey(ED25519KEY) - .getEd25519() - .toByteArray(), - spec.registry() - .getKey(ECDSA_KEY) - .getECDSASecp256K1() - .toByteArray(), - HapiParserUtil.asHeadlongAddress(asAddress( - spec.registry().getContractId(TOKEN_UPDATE_CONTRACT))), - HapiParserUtil.asHeadlongAddress(asAddress( - spec.registry().getAccountID(ACCOUNT))), - AUTO_RENEW_PERIOD, - CUSTOM_NAME, - CUSTOM_SYMBOL, - CUSTOM_MEMO) - .via(failedUpdateTxn) - .gas(GAS_TO_OFFER) - .sending(DEFAULT_AMOUNT_TO_SEND) - .alsoSigningWithFullPrefix(MULTI_KEY) - .payingWith(ACCOUNT) - .hasKnownStatus(CONTRACT_REVERT_EXECUTED), - // But now restore use of top-level signatures for - // the token update precompile - overriding(CONTRACTS_ALLOW_SYSTEM_USE_OF_HAPI_SIGS, "TokenUpdate"), - contractCall( - TOKEN_UPDATE_CONTRACT, - "updateTokenWithAllFields", - HapiParserUtil.asHeadlongAddress(asAddress(vanillaTokenID.get())), - HapiParserUtil.asHeadlongAddress(asAddress( - spec.registry().getAccountID(ACCOUNT))), - spec.registry() - .getKey(ED25519KEY) - .getEd25519() - .toByteArray(), - spec.registry() - .getKey(ECDSA_KEY) - .getECDSASecp256K1() - .toByteArray(), - HapiParserUtil.asHeadlongAddress(asAddress( - spec.registry().getContractId(TOKEN_UPDATE_CONTRACT))), - HapiParserUtil.asHeadlongAddress(asAddress( - spec.registry().getAccountID(ACCOUNT))), - AUTO_RENEW_PERIOD, - CUSTOM_NAME, - CUSTOM_SYMBOL, - CUSTOM_MEMO) - .via(succeededUpdateTxn) - .gas(GAS_TO_OFFER) - .sending(DEFAULT_AMOUNT_TO_SEND) - .alsoSigningWithFullPrefix(MULTI_KEY) - .payingWith(ACCOUNT) - .hasKnownStatus(SUCCESS))), - newKeyNamed(DELEGATE_KEY).shape(DELEGATE_CONTRACT.signedWith(TOKEN_UPDATE_CONTRACT)), - newKeyNamed(TOKEN_UPDATE_AS_KEY).shape(CONTRACT.signedWith(TOKEN_UPDATE_CONTRACT))) - .then( - // Confirm the failure was due to the top-level signature being unavailable - childRecordsCheck( - failedUpdateTxn, - CONTRACT_REVERT_EXECUTED, - recordWith().status(INVALID_SIGNATURE))); - } - - final HapiSpec canToggleTopLevelSigUsageForPauseAndUnpausePrecompile() { - final var failedPauseTxn = "failedPauseTxn"; - final var failedUnpauseTxn = "failedUnpauseTxn"; - final var succeededPauseTxn = "succeededPauseTxn"; - final var succeededUnpauseTxn = "succeededUnpauseTxn"; - - final AtomicReference vanillaTokenID = new AtomicReference<>(); - final AtomicReference accountID = new AtomicReference<>(); - - return propertyPreservingHapiSpec("CanToggleTopLevelSigUsageForPauseAndUnpausePrecompile") - .preserving(CONTRACTS_ALLOW_SYSTEM_USE_OF_HAPI_SIGS) - .given( - newKeyNamed(PAUSE_KEY), - newKeyNamed(MULTI_KEY), - cryptoCreate(ACCOUNT).balance(100 * ONE_HBAR).exposingCreatedIdTo(accountID::set), - cryptoCreate(TOKEN_TREASURY), - tokenCreate(VANILLA_TOKEN) - .tokenType(FUNGIBLE_COMMON) - .treasury(TOKEN_TREASURY) - .adminKey(MULTI_KEY) - .pauseKey(PAUSE_KEY) - .initialSupply(1_000) - .exposingCreatedIdTo(id -> vanillaTokenID.set(asToken(id))), - uploadInitCode(PAUSE_UNPAUSE_CONTRACT), - contractCreate(PAUSE_UNPAUSE_CONTRACT), - tokenAssociate(ACCOUNT, VANILLA_TOKEN), - cryptoTransfer(moving(500, VANILLA_TOKEN).between(TOKEN_TREASURY, ACCOUNT)), - // First revoke use of top-level signatures from all precompiles - overriding(CONTRACTS_ALLOW_SYSTEM_USE_OF_HAPI_SIGS, "")) - .when( - // Trying to pause with top-level signatures should fail - sourcing(() -> contractCall( - PAUSE_UNPAUSE_CONTRACT, - PAUSE_TOKEN_ACCOUNT_FUNCTION_NAME, - asHeadlongAddress(asAddress(vanillaTokenID.get()))) - .hasKnownStatus(CONTRACT_REVERT_EXECUTED) - .alsoSigningWithFullPrefix(PAUSE_KEY) - .payingWith(ACCOUNT) - .gas(GAS_TO_OFFER) - .via(failedPauseTxn)), - // But now restore use of top-level signatures for the pause precompile - overriding(CONTRACTS_ALLOW_SYSTEM_USE_OF_HAPI_SIGS, "TokenPause"), - // Now the same call should succeed - sourcing(() -> contractCall( - PAUSE_UNPAUSE_CONTRACT, - PAUSE_TOKEN_ACCOUNT_FUNCTION_NAME, - asHeadlongAddress(asAddress(vanillaTokenID.get()))) - .hasKnownStatus(SUCCESS) - .alsoSigningWithFullPrefix(PAUSE_KEY) - .payingWith(ACCOUNT) - .gas(GAS_TO_OFFER) - .via(succeededPauseTxn)), - // revoke use of top-level signatures from all precompiles again - overriding(CONTRACTS_ALLOW_SYSTEM_USE_OF_HAPI_SIGS, ""), - // Now the same call should succeed - sourcing(() -> contractCall( - PAUSE_UNPAUSE_CONTRACT, - UNPAUSE_TOKEN_ACCOUNT_FUNCTION_NAME, - asHeadlongAddress(asAddress(vanillaTokenID.get()))) - .hasKnownStatus(CONTRACT_REVERT_EXECUTED) - .alsoSigningWithFullPrefix(PAUSE_KEY) - .payingWith(ACCOUNT) - .gas(GAS_TO_OFFER) - .via(failedUnpauseTxn)), - // But now restore use of top-level signatures for the unpause precompile - overriding(CONTRACTS_ALLOW_SYSTEM_USE_OF_HAPI_SIGS, "TokenUnpause"), - // Now the same call should succeed - sourcing(() -> contractCall( - PAUSE_UNPAUSE_CONTRACT, - UNPAUSE_TOKEN_ACCOUNT_FUNCTION_NAME, - asHeadlongAddress(asAddress(vanillaTokenID.get()))) - .hasKnownStatus(SUCCESS) - .alsoSigningWithFullPrefix(PAUSE_KEY) - .payingWith(ACCOUNT) - .gas(GAS_TO_OFFER) - .via(succeededUnpauseTxn))) - .then( - // Confirm the failure was due to the top-level signature being unavailable - childRecordsCheck( - failedPauseTxn, - CONTRACT_REVERT_EXECUTED, - recordWith().status(INVALID_SIGNATURE)), - childRecordsCheck( - failedUnpauseTxn, - CONTRACT_REVERT_EXECUTED, - recordWith().status(INVALID_SIGNATURE))); - } - - final HapiSpec canToggleTopLevelSigUsageForAssociatePrecompile() { - final var tokenToAssociate = "tokenToAssociate"; - final var accountToBeAssociated = "accountToBeAssociated"; - final var failedAssociateTxn = "failedAssociateTxn"; - final var succeededAssociateTxn = "succeededAssociateTxn"; - final AtomicReference
    accountAddress = new AtomicReference<>(); - final AtomicReference
    tokenAddress = new AtomicReference<>(); - return propertyPreservingHapiSpec("CanToggleTopLevelSigUsageForAssociatePrecompile") - .preserving(CONTRACTS_ALLOW_SYSTEM_USE_OF_HAPI_SIGS) - .given( - uploadInitCode(THE_CONTRACT), - contractCreate(THE_CONTRACT), - cryptoCreate(accountToBeAssociated) - .keyShape(SECP256K1_ON) - .exposingEvmAddressTo(accountAddress::set), - tokenCreate(tokenToAssociate).exposingAddressTo(tokenAddress::set), - // First revoke use of top-level signatures from all precompiles - overriding(CONTRACTS_ALLOW_SYSTEM_USE_OF_HAPI_SIGS, "")) - .when( - // Trying to associate with top-level signatures should fail - sourcing(() -> contractCall( - THE_CONTRACT, - TOKEN_ASSOCIATE_FUNCTION, - accountAddress.get(), - tokenAddress.get()) - .gas(GAS_TO_OFFER) - .alsoSigningWithFullPrefix(accountToBeAssociated) - .via(failedAssociateTxn) - .hasKnownStatus(CONTRACT_REVERT_EXECUTED)), - // But now restore use of top-level signatures for the associate precompile - overriding(CONTRACTS_ALLOW_SYSTEM_USE_OF_HAPI_SIGS, "TokenAssociateToAccount"), - // Now the same call should succeed - sourcing(() -> contractCall( - THE_CONTRACT, - TOKEN_ASSOCIATE_FUNCTION, - accountAddress.get(), - tokenAddress.get()) - .gas(GAS_TO_OFFER) - .alsoSigningWithFullPrefix(accountToBeAssociated) - .via(succeededAssociateTxn) - .hasKnownStatus(SUCCESS))) - .then( - // Confirm the failure was due to the top-level signature being unavailable - childRecordsCheck( - failedAssociateTxn, - CONTRACT_REVERT_EXECUTED, - recordWith().status(INVALID_FULL_PREFIX_SIGNATURE_FOR_PRECOMPILE))); - } - - final HapiSpec canToggleTopLevelSigUsageForBurnPrecompile() { - final var failedBurnTxn = "failedBurnTxn"; - final var succeededBurnTxn = "succeededBurnTxn"; - - return propertyPreservingHapiSpec("CanToggleTopLevelSigUsageForBurnPrecompile") - .preserving(CONTRACTS_ALLOW_SYSTEM_USE_OF_HAPI_SIGS) - .given( - newKeyNamed(MULTI_KEY), - newKeyNamed(SUPPLY_KEY), - cryptoCreate(ALICE).balance(10 * ONE_HUNDRED_HBARS), - cryptoCreate(TOKEN_TREASURY), - tokenCreate(TOKEN) - .tokenType(TokenType.FUNGIBLE_COMMON) - .initialSupply(50L) - .supplyKey(SUPPLY_KEY) - .adminKey(MULTI_KEY) - .treasury(TOKEN_TREASURY), - uploadInitCode(THE_BURN_CONTRACT), - withOpContext((spec, opLog) -> allRunFor( - spec, - contractCreate( - THE_BURN_CONTRACT, - asHeadlongAddress(asHexedAddress( - spec.registry().getTokenID(TOKEN)))) - .payingWith(ALICE) - .via(CREATION_TX) - .gas(GAS_TO_OFFER))), - // First revoke use of top-level signatures from all precompiles - overriding(CONTRACTS_ALLOW_SYSTEM_USE_OF_HAPI_SIGS, "")) - .when( - // Trying to burn with top-level signatures should fail - sourcing(() -> contractCall( - THE_BURN_CONTRACT, BURN_TOKEN_WITH_EVENT, BigInteger.valueOf(10L), new long[0]) - .payingWith(ALICE) - .alsoSigningWithFullPrefix(SUPPLY_KEY) - .gas(GAS_TO_OFFER) - .via(failedBurnTxn) - .hasKnownStatus(CONTRACT_REVERT_EXECUTED)), - // But now restore use of top-level signatures for the burn precompile - overriding(CONTRACTS_ALLOW_SYSTEM_USE_OF_HAPI_SIGS, "TokenBurn"), - // Now the same call should succeed - sourcing(() -> contractCall( - THE_BURN_CONTRACT, BURN_TOKEN_WITH_EVENT, BigInteger.valueOf(10L), new long[0]) - .payingWith(ALICE) - .alsoSigningWithFullPrefix(SUPPLY_KEY) - .gas(GAS_TO_OFFER) - .via(succeededBurnTxn) - .hasKnownStatus(SUCCESS))) - .then( - // Confirm the failure was due to the top-level signature being unavailable - childRecordsCheck( - failedBurnTxn, - CONTRACT_REVERT_EXECUTED, - recordWith().status(INVALID_FULL_PREFIX_SIGNATURE_FOR_PRECOMPILE))); - } - - final HapiSpec canToggleTopLevelSigUsageForMintPrecompile() { - final var tokenToMint = "tokenToMint"; - final var failedMintTxn = "failedMintTxn"; - final var succeededMintTxn = "succeededMintTxn"; - - final AtomicReference
    accountAddress = new AtomicReference<>(); - final AtomicReference fungible = new AtomicReference<>(); - return propertyPreservingHapiSpec("CanToggleTopLevelSigUsageForMintPrecompile") - .preserving(CONTRACTS_ALLOW_SYSTEM_USE_OF_HAPI_SIGS) - .given( - newKeyNamed(MULTI_KEY), - newKeyNamed(SUPPLY_KEY), - cryptoCreate("mint account") - .keyShape(SECP256K1_ON) - .exposingEvmAddressTo(accountAddress::set) - .balance(ONE_MILLION_HBARS) - .payingWith(GENESIS), - cryptoCreate(TOKEN_TREASURY), - tokenCreate(tokenToMint) - .tokenType(TokenType.FUNGIBLE_COMMON) - .initialSupply(0) - .treasury(TOKEN_TREASURY) - .adminKey(MULTI_KEY) - .supplyKey(SUPPLY_KEY) - .exposingCreatedIdTo(idLit -> fungible.set(HapiPropertySource.asToken(idLit))), - uploadInitCode(MINT_CONTRACT), - sourcing(() -> contractCreate( - MINT_CONTRACT, HapiParserUtil.asHeadlongAddress(asAddress(fungible.get())))), - // First revoke use of top-level signatures from all precompiles - overriding(CONTRACTS_ALLOW_SYSTEM_USE_OF_HAPI_SIGS, "")) - .when( - // Trying to mint with top-level signatures should fail - sourcing(() -> contractCall( - MINT_CONTRACT, MINT_FUNGIBLE_TOKEN_WITH_EVENT, BigInteger.valueOf(10L)) - .gas(GAS_TO_OFFER) - .alsoSigningWithFullPrefix(SUPPLY_KEY) - .via(failedMintTxn) - .hasKnownStatus(CONTRACT_REVERT_EXECUTED)), - // But now restore use of top-level signatures for the mint precompile - overriding(CONTRACTS_ALLOW_SYSTEM_USE_OF_HAPI_SIGS, "TokenMint"), - // Now the same call should succeed - sourcing(() -> contractCall( - MINT_CONTRACT, MINT_FUNGIBLE_TOKEN_WITH_EVENT, BigInteger.valueOf(10L)) - .gas(GAS_TO_OFFER) - .alsoSigningWithFullPrefix(SUPPLY_KEY) - .via(succeededMintTxn) - .hasKnownStatus(SUCCESS))) - .then( - // Confirm the failure was due to the top-level signature being unavailable - childRecordsCheck( - failedMintTxn, - CONTRACT_REVERT_EXECUTED, - recordWith().status(INVALID_FULL_PREFIX_SIGNATURE_FOR_PRECOMPILE))); - } - - final HapiSpec canToggleTopLevelSigUsageForDeletePrecompile() { - final var failedDeleteTxn = "failedDeleteTxn"; - final var succeededDeleteTxn = "succeededDeleteTxn"; - final AtomicReference accountID = new AtomicReference<>(); - final AtomicReference vanillaTokenID = new AtomicReference<>(); - return propertyPreservingHapiSpec("CanToggleTopLevelSigUsageForDeletePrecompile") - .preserving(CONTRACTS_ALLOW_SYSTEM_USE_OF_HAPI_SIGS) - .given( - newKeyNamed(MULTI_KEY), - cryptoCreate(ACCOUNT) - .key(MULTI_KEY) - .balance(100 * ONE_HBAR) - .exposingCreatedIdTo(accountID::set), - cryptoCreate(TOKEN_TREASURY), - tokenCreate(VANILLA_TOKEN) - .tokenType(FUNGIBLE_COMMON) - .treasury(TOKEN_TREASURY) - .adminKey(MULTI_KEY) - .exposingCreatedIdTo(id -> vanillaTokenID.set(HapiPropertySource.asToken(id))) - .initialSupply(1110), - uploadInitCode(DELETE_TOKEN_CONTRACT), - contractCreate(DELETE_TOKEN_CONTRACT), - tokenAssociate(ACCOUNT, VANILLA_TOKEN), - cryptoTransfer(moving(500, VANILLA_TOKEN).between(TOKEN_TREASURY, ACCOUNT)), - // First revoke use of top-level signatures from all precompiles - overriding(CONTRACTS_ALLOW_SYSTEM_USE_OF_HAPI_SIGS, "")) - .when( - // Trying to delete with top-level signatures should fail - sourcing(() -> contractCall( - DELETE_TOKEN_CONTRACT, - TOKEN_DELETE_FUNCTION, - asHeadlongAddress(asHexedAddress(vanillaTokenID.get()))) - .gas(GAS_TO_OFFER) - .alsoSigningWithFullPrefix(MULTI_KEY) - .via(failedDeleteTxn) - .hasKnownStatus(CONTRACT_REVERT_EXECUTED)), - // But now restore use of top-level signatures for the delete precompile - overriding(CONTRACTS_ALLOW_SYSTEM_USE_OF_HAPI_SIGS, "TokenDelete"), - // Now the same call should succeed - sourcing(() -> contractCall( - DELETE_TOKEN_CONTRACT, - TOKEN_DELETE_FUNCTION, - asHeadlongAddress(asHexedAddress(vanillaTokenID.get()))) - .gas(GAS_TO_OFFER) - .alsoSigningWithFullPrefix(MULTI_KEY) - .via(succeededDeleteTxn) - .hasKnownStatus(SUCCESS))) - .then( - // Confirm the failure was due to the top-level signature being unavailable - childRecordsCheck( - failedDeleteTxn, - CONTRACT_REVERT_EXECUTED, - recordWith().status(INVALID_SIGNATURE))); - } - - final HapiSpec canToggleTopLevelSigUsageForFreezeAndUnfreezePrecompile() { - final var failedFreezeTxn = "failedFreezeTxn"; - final var failedUnfreezeTxn = "failedUnfreezeTxn"; - final var succeededFreezeTxn = "succeededFreezeTxn"; - final var succeededUnfreezeTxn = "succeededUnfreezeTxn"; - - final AtomicReference vanillaTokenID = new AtomicReference<>(); - final AtomicReference accountID = new AtomicReference<>(); - - return propertyPreservingHapiSpec("CanToggleTopLevelSigUsageForFreezeAndUnfreezePrecompile") - .preserving(CONTRACTS_ALLOW_SYSTEM_USE_OF_HAPI_SIGS) - .given( - newKeyNamed(FREEZE_KEY), - newKeyNamed(MULTI_KEY), - cryptoCreate(ACCOUNT).balance(100 * ONE_HBAR).exposingCreatedIdTo(accountID::set), - cryptoCreate(TOKEN_TREASURY), - tokenCreate(VANILLA_TOKEN) - .tokenType(FUNGIBLE_COMMON) - .treasury(TOKEN_TREASURY) - .adminKey(MULTI_KEY) - .freezeKey(FREEZE_KEY) - .initialSupply(1_000) - .exposingCreatedIdTo(id -> vanillaTokenID.set(asToken(id))), - uploadInitCode(FREEZE_CONTRACT), - contractCreate(FREEZE_CONTRACT), - tokenAssociate(ACCOUNT, VANILLA_TOKEN), - cryptoTransfer(moving(500, VANILLA_TOKEN).between(TOKEN_TREASURY, ACCOUNT)), - // First revoke use of top-level signatures from all precompiles - overriding(CONTRACTS_ALLOW_SYSTEM_USE_OF_HAPI_SIGS, "")) - .when( - // Trying to freezing with top-level signatures should fail - sourcing(() -> contractCall( - FREEZE_CONTRACT, - TOKEN_FREEZE_FUNC, - asHeadlongAddress(asAddress(vanillaTokenID.get())), - asHeadlongAddress(asAddress(accountID.get()))) - .hasKnownStatus(CONTRACT_REVERT_EXECUTED) - .alsoSigningWithFullPrefix(FREEZE_KEY) - .payingWith(ACCOUNT) - .gas(GAS_TO_OFFER) - .via(failedFreezeTxn)), - // But now restore use of top-level signatures for the freeze precompile - overriding(CONTRACTS_ALLOW_SYSTEM_USE_OF_HAPI_SIGS, "TokenFreezeAccount"), - // Now the same call should succeed - sourcing(() -> contractCall( - FREEZE_CONTRACT, - TOKEN_FREEZE_FUNC, - asHeadlongAddress(asAddress(vanillaTokenID.get())), - asHeadlongAddress(asAddress(accountID.get()))) - .hasKnownStatus(SUCCESS) - .alsoSigningWithFullPrefix(FREEZE_KEY) - .payingWith(ACCOUNT) - .gas(GAS_TO_OFFER) - .via(succeededFreezeTxn)), - // revoke use of top-level signatures from all precompiles again - overriding(CONTRACTS_ALLOW_SYSTEM_USE_OF_HAPI_SIGS, ""), - // Now the same call should succeed - sourcing(() -> contractCall( - FREEZE_CONTRACT, - TOKEN_UNFREEZE_FUNC, - asHeadlongAddress(asAddress(vanillaTokenID.get())), - asHeadlongAddress(asAddress(accountID.get()))) - .hasKnownStatus(CONTRACT_REVERT_EXECUTED) - .alsoSigningWithFullPrefix(FREEZE_KEY) - .payingWith(ACCOUNT) - .gas(GAS_TO_OFFER) - .via(failedUnfreezeTxn)), - // But now restore use of top-level signatures for the unfreeze precompile - overriding(CONTRACTS_ALLOW_SYSTEM_USE_OF_HAPI_SIGS, "TokenUnfreezeAccount"), - // Now the same call should succeed - sourcing(() -> contractCall( - FREEZE_CONTRACT, - TOKEN_UNFREEZE_FUNC, - asHeadlongAddress(asAddress(vanillaTokenID.get())), - asHeadlongAddress(asAddress(accountID.get()))) - .hasKnownStatus(SUCCESS) - .alsoSigningWithFullPrefix(FREEZE_KEY) - .payingWith(ACCOUNT) - .gas(GAS_TO_OFFER) - .via(succeededUnfreezeTxn))) - .then( - // Confirm the failure was due to the top-level signature being unavailable - childRecordsCheck( - failedFreezeTxn, - CONTRACT_REVERT_EXECUTED, - recordWith().status(INVALID_SIGNATURE)), - childRecordsCheck( - failedUnfreezeTxn, - CONTRACT_REVERT_EXECUTED, - recordWith().status(INVALID_SIGNATURE))); - } - - final HapiSpec canToggleTopLevelSigUsageForGrantKycAndRevokeKycPrecompile() { - final var failedGrantTxn = "failedGrantTxn"; - final var failedRevokeTxn = "failedRevokeTxn"; - final var succeededGrantTxn = "succeededGrantTxn"; - final var succeededRevokeTxn = "succeededRevokeTxn"; - - final AtomicReference vanillaTokenID = new AtomicReference<>(); - final AtomicReference accountID = new AtomicReference<>(); - final AtomicReference secondAccountID = new AtomicReference<>(); - - return propertyPreservingHapiSpec("canToggleTopLevelSigUsageForGrantKycAndRevokeKycPrecompile") - .preserving(CONTRACTS_ALLOW_SYSTEM_USE_OF_HAPI_SIGS) - .given( - newKeyNamed(KYC_KEY), - cryptoCreate(ACCOUNT) - .balance(100 * ONE_HBAR) - .key(KYC_KEY) - .exposingCreatedIdTo(accountID::set), - cryptoCreate(SECOND_ACCOUNT).exposingCreatedIdTo(secondAccountID::set), - cryptoCreate(TOKEN_TREASURY), - tokenCreate(VANILLA_TOKEN) - .tokenType(FUNGIBLE_COMMON) - .treasury(TOKEN_TREASURY) - .kycKey(KYC_KEY) - .initialSupply(1_000) - .exposingCreatedIdTo(id -> vanillaTokenID.set(asToken(id))), - uploadInitCode(GRANT_REVOKE_KYC_CONTRACT), - contractCreate(GRANT_REVOKE_KYC_CONTRACT), - tokenAssociate(ACCOUNT, VANILLA_TOKEN), - tokenAssociate(SECOND_ACCOUNT, VANILLA_TOKEN), - // First revoke use of top-level signatures from all precompiles - overriding(CONTRACTS_ALLOW_SYSTEM_USE_OF_HAPI_SIGS, "")) - .when( - // Trying to grant kyc with top-level signatures should fail - sourcing(() -> contractCall( - GRANT_REVOKE_KYC_CONTRACT, - TOKEN_GRANT_KYC, - asHeadlongAddress(asAddress(vanillaTokenID.get())), - asHeadlongAddress(asAddress(secondAccountID.get()))) - .hasKnownStatus(CONTRACT_REVERT_EXECUTED) - .alsoSigningWithFullPrefix(KYC_KEY) - .payingWith(ACCOUNT) - .gas(GAS_TO_OFFER) - .via(failedGrantTxn)), - // But now restore use of top-level signatures for the grant kyc precompile - overriding(CONTRACTS_ALLOW_SYSTEM_USE_OF_HAPI_SIGS, "TokenGrantKycToAccount"), - // Now the same call should succeed - sourcing(() -> contractCall( - GRANT_REVOKE_KYC_CONTRACT, - TOKEN_GRANT_KYC, - asHeadlongAddress(asAddress(vanillaTokenID.get())), - asHeadlongAddress(asAddress(secondAccountID.get()))) - .hasKnownStatus(SUCCESS) - .alsoSigningWithFullPrefix(KYC_KEY) - .payingWith(ACCOUNT) - .gas(GAS_TO_OFFER) - .via(succeededGrantTxn)), - // revoke use of top-level signatures from all precompiles again - overriding(CONTRACTS_ALLOW_SYSTEM_USE_OF_HAPI_SIGS, ""), - // Now the same call should succeed - sourcing(() -> contractCall( - GRANT_REVOKE_KYC_CONTRACT, - TOKEN_REVOKE_KYC, - asHeadlongAddress(asAddress(vanillaTokenID.get())), - asHeadlongAddress(asAddress(secondAccountID.get()))) - .hasKnownStatus(CONTRACT_REVERT_EXECUTED) - .alsoSigningWithFullPrefix(KYC_KEY) - .payingWith(ACCOUNT) - .gas(GAS_TO_OFFER) - .via(failedRevokeTxn)), - // But now restore use of top-level signatures for the revoke kyc precompile - overriding(CONTRACTS_ALLOW_SYSTEM_USE_OF_HAPI_SIGS, "TokenRevokeKycToAccount"), - // Now the same call should succeed - sourcing(() -> contractCall( - GRANT_REVOKE_KYC_CONTRACT, - TOKEN_REVOKE_KYC, - asHeadlongAddress(asAddress(vanillaTokenID.get())), - asHeadlongAddress(asAddress(secondAccountID.get()))) - .hasKnownStatus(SUCCESS) - .alsoSigningWithFullPrefix(KYC_KEY) - .payingWith(ACCOUNT) - .gas(GAS_TO_OFFER) - .via(succeededRevokeTxn))) - .then( - // Confirm the failure was due to the top-level signature being unavailable - childRecordsCheck( - failedGrantTxn, - CONTRACT_REVERT_EXECUTED, - recordWith().status(INVALID_SIGNATURE)), - childRecordsCheck( - failedRevokeTxn, - CONTRACT_REVERT_EXECUTED, - recordWith().status(INVALID_SIGNATURE))); - } - - @Override - protected Logger getResultsLogger() { - return log; - } -} diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/precompile/WipeTokenAccountPrecompileSuite.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/precompile/WipeTokenAccountPrecompileSuite.java index ae85219a08ae..46e760a654dc 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/precompile/WipeTokenAccountPrecompileSuite.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/precompile/WipeTokenAccountPrecompileSuite.java @@ -44,6 +44,8 @@ import static com.hedera.services.bdd.spec.utilops.UtilVerbs.childRecordsCheck; import static com.hedera.services.bdd.spec.utilops.UtilVerbs.newKeyNamed; import static com.hedera.services.bdd.spec.utilops.UtilVerbs.withOpContext; +import static com.hedera.services.bdd.suites.HapiSuite.GENESIS; +import static com.hedera.services.bdd.suites.HapiSuite.TOKEN_TREASURY; import static com.hedera.services.bdd.suites.contract.Utils.asAddress; import static com.hedera.services.bdd.suites.contract.Utils.asToken; import static com.hedera.services.bdd.suites.token.TokenAssociationSpecs.VANILLA_TOKEN; @@ -58,23 +60,18 @@ import static com.hederahashgraph.api.proto.java.TokenType.NON_FUNGIBLE_UNIQUE; import com.hedera.services.bdd.junit.HapiTest; -import com.hedera.services.bdd.junit.HapiTestSuite; -import com.hedera.services.bdd.spec.HapiSpec; import com.hedera.services.bdd.spec.keys.KeyShape; import com.hedera.services.bdd.spec.transactions.contract.HapiParserUtil; -import com.hedera.services.bdd.suites.HapiSuite; import com.hederahashgraph.api.proto.java.AccountID; import com.hederahashgraph.api.proto.java.TokenID; import java.util.List; import java.util.concurrent.atomic.AtomicReference; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; +import java.util.stream.Stream; +import org.junit.jupiter.api.DynamicTest; import org.junit.jupiter.api.Tag; -@HapiTestSuite @Tag(SMART_CONTRACT) -public class WipeTokenAccountPrecompileSuite extends HapiSuite { - private static final Logger log = LogManager.getLogger(WipeTokenAccountPrecompileSuite.class); +public class WipeTokenAccountPrecompileSuite { public static final String WIPE_CONTRACT = "WipeTokenAccount"; public static final String ADMIN_ACCOUNT = "admin"; private static final String ACCOUNT = "anybody"; @@ -88,27 +85,8 @@ public class WipeTokenAccountPrecompileSuite extends HapiSuite { public static final String THRESHOLD_KEY = "ThreshKey"; private static final KeyShape THRESHOLD_KEY_SHAPE = KeyShape.threshOf(1, ED25519, CONTRACT); - public static void main(String... args) { - new WipeTokenAccountPrecompileSuite().runSuiteAsync(); - } - - @Override - protected Logger getResultsLogger() { - return log; - } - - @Override - public boolean canRunConcurrent() { - return true; - } - - @Override - public List getSpecsInSuite() { - return List.of(wipeFungibleTokenScenarios(), wipeNonFungibleTokenScenarios()); - } - @HapiTest - final HapiSpec wipeFungibleTokenScenarios() { + final Stream wipeFungibleTokenScenarios() { final AtomicReference adminAccountID = new AtomicReference<>(); final AtomicReference accountID = new AtomicReference<>(); final AtomicReference secondAccountID = new AtomicReference<>(); @@ -228,7 +206,7 @@ final HapiSpec wipeFungibleTokenScenarios() { } @HapiTest - final HapiSpec wipeNonFungibleTokenScenarios() { + final Stream wipeNonFungibleTokenScenarios() { final AtomicReference adminAccountID = new AtomicReference<>(); final AtomicReference accountID = new AtomicReference<>(); final AtomicReference vanillaTokenID = new AtomicReference<>(); diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/precompile/WipeTokenAccountPrecompileV1SecurityModelSuite.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/precompile/WipeTokenAccountPrecompileV1SecurityModelSuite.java index d3fde7d34143..596315688e33 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/precompile/WipeTokenAccountPrecompileV1SecurityModelSuite.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/precompile/WipeTokenAccountPrecompileV1SecurityModelSuite.java @@ -55,15 +55,16 @@ import static com.hederahashgraph.api.proto.java.TokenType.FUNGIBLE_COMMON; import static com.hederahashgraph.api.proto.java.TokenType.NON_FUNGIBLE_UNIQUE; -import com.hedera.services.bdd.spec.HapiSpec; import com.hedera.services.bdd.spec.transactions.contract.HapiParserUtil; import com.hedera.services.bdd.suites.HapiSuite; import com.hederahashgraph.api.proto.java.AccountID; import com.hederahashgraph.api.proto.java.TokenID; import java.util.List; import java.util.concurrent.atomic.AtomicReference; +import java.util.stream.Stream; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; +import org.junit.jupiter.api.DynamicTest; public class WipeTokenAccountPrecompileV1SecurityModelSuite extends HapiSuite { @@ -93,11 +94,11 @@ public boolean canRunConcurrent() { } @Override - public List getSpecsInSuite() { + public List> getSpecsInSuite() { return List.of(wipeFungibleTokenScenarios(), wipeNonFungibleTokenScenarios()); } - final HapiSpec wipeFungibleTokenScenarios() { + final Stream wipeFungibleTokenScenarios() { final AtomicReference adminAccountID = new AtomicReference<>(); final AtomicReference accountID = new AtomicReference<>(); final AtomicReference secondAccountID = new AtomicReference<>(); @@ -221,7 +222,7 @@ final HapiSpec wipeFungibleTokenScenarios() { getAccountBalance(ACCOUNT).hasTokenBalance(VANILLA_TOKEN, 490)); } - final HapiSpec wipeNonFungibleTokenScenarios() { + final Stream wipeNonFungibleTokenScenarios() { final AtomicReference adminAccountID = new AtomicReference<>(); final AtomicReference accountID = new AtomicReference<>(); final AtomicReference vanillaTokenID = new AtomicReference<>(); diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/records/LogsSuite.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/records/LogsSuite.java index 038699593487..16871add3301 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/records/LogsSuite.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/records/LogsSuite.java @@ -31,45 +31,21 @@ import static com.hedera.services.bdd.suites.contract.Utils.parsedToByteString; import com.hedera.services.bdd.junit.HapiTest; -import com.hedera.services.bdd.junit.HapiTestSuite; -import com.hedera.services.bdd.spec.HapiSpec; -import com.hedera.services.bdd.suites.HapiSuite; import java.math.BigInteger; import java.util.List; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; +import java.util.stream.Stream; +import org.junit.jupiter.api.DynamicTest; import org.junit.jupiter.api.Tag; -@HapiTestSuite @Tag(SMART_CONTRACT) -public class LogsSuite extends HapiSuite { +public class LogsSuite { private static final long GAS_TO_OFFER = 25_000L; - private static final Logger log = LogManager.getLogger(LogsSuite.class); private static final String CONTRACT = "Logs"; - public static void main(String... args) { - new LogsSuite().runSuiteAsync(); - } - - @Override - public boolean canRunConcurrent() { - return true; - } - - @Override - protected Logger getResultsLogger() { - return log; - } - - @Override - public List getSpecsInSuite() { - return List.of(log0Works(), log1Works(), log2Works(), log3Works(), log4Works()); - } - @HapiTest - final HapiSpec log0Works() { + final Stream log0Works() { return defaultHapiSpec("log0Works", NONDETERMINISTIC_TRANSACTION_FEES) .given(uploadInitCode(CONTRACT), contractCreate(CONTRACT)) .when(contractCall(CONTRACT, "log0", BigInteger.valueOf(15)) @@ -83,7 +59,7 @@ final HapiSpec log0Works() { } @HapiTest - final HapiSpec log1Works() { + final Stream log1Works() { return defaultHapiSpec("log1Works", NONDETERMINISTIC_TRANSACTION_FEES) .given(uploadInitCode(CONTRACT), contractCreate(CONTRACT)) .when(contractCall(CONTRACT, "log1", BigInteger.valueOf(15)) @@ -100,7 +76,7 @@ final HapiSpec log1Works() { } @HapiTest - final HapiSpec log2Works() { + final Stream log2Works() { return defaultHapiSpec("log2Works", NONDETERMINISTIC_TRANSACTION_FEES) .given(uploadInitCode(CONTRACT), contractCreate(CONTRACT)) .when(contractCall(CONTRACT, "log2", BigInteger.ONE, BigInteger.TWO) @@ -119,7 +95,7 @@ final HapiSpec log2Works() { } @HapiTest - final HapiSpec log3Works() { + final Stream log3Works() { return defaultHapiSpec("log3Works", NONDETERMINISTIC_TRANSACTION_FEES) .given(uploadInitCode(CONTRACT), contractCreate(CONTRACT)) .when(contractCall(CONTRACT, "log3", BigInteger.ONE, BigInteger.TWO, BigInteger.valueOf(3)) @@ -139,7 +115,7 @@ final HapiSpec log3Works() { } @HapiTest - final HapiSpec log4Works() { + final Stream log4Works() { return defaultHapiSpec("log4Works", NONDETERMINISTIC_TRANSACTION_FEES) .given(uploadInitCode(CONTRACT), contractCreate(CONTRACT)) .when(contractCall( diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/records/RecordsSuite.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/records/RecordsSuite.java index 338b6d287a10..9b68c8faacae 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/records/RecordsSuite.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/records/RecordsSuite.java @@ -22,47 +22,57 @@ import static com.hedera.services.bdd.spec.transactions.TxnVerbs.contractCall; import static com.hedera.services.bdd.spec.transactions.TxnVerbs.contractCreate; import static com.hedera.services.bdd.spec.transactions.TxnVerbs.cryptoCreate; +import static com.hedera.services.bdd.spec.transactions.TxnVerbs.cryptoTransfer; +import static com.hedera.services.bdd.spec.transactions.TxnVerbs.ethereumCall; import static com.hedera.services.bdd.spec.transactions.TxnVerbs.uploadInitCode; +import static com.hedera.services.bdd.spec.transactions.crypto.HapiCryptoTransfer.tinyBarsFromAccountToAlias; +import static com.hedera.services.bdd.spec.utilops.CustomSpecAssert.allRunFor; import static com.hedera.services.bdd.spec.utilops.UtilVerbs.assertionsHold; +import static com.hedera.services.bdd.spec.utilops.UtilVerbs.newKeyNamed; +import static com.hedera.services.bdd.spec.utilops.UtilVerbs.waitUntilStartOfNextAdhocPeriod; +import static com.hedera.services.bdd.spec.utilops.UtilVerbs.withOpContext; +import static com.hedera.services.bdd.spec.utilops.records.SnapshotMatchMode.NONDETERMINISTIC_ETHEREUM_DATA; +import static com.hedera.services.bdd.spec.utilops.records.SnapshotMatchMode.NONDETERMINISTIC_LOG_DATA; import static com.hedera.services.bdd.spec.utilops.records.SnapshotMatchMode.NONDETERMINISTIC_TRANSACTION_FEES; +import static com.hedera.services.bdd.suites.HapiSuite.GENESIS; +import static com.hedera.services.bdd.suites.HapiSuite.ONE_HUNDRED_HBARS; +import static com.hedera.services.bdd.suites.HapiSuite.ONE_MILLION_HBARS; +import static com.hedera.services.bdd.suites.HapiSuite.RELAYER; +import static com.hedera.services.bdd.suites.HapiSuite.SECP_256K1_SHAPE; +import static com.hedera.services.bdd.suites.HapiSuite.SECP_256K1_SOURCE_KEY; +import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.RECORD_NOT_FOUND; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; +import com.google.common.primitives.Longs; +import com.hedera.node.app.hapi.utils.ethereum.EthTxData; import com.hedera.services.bdd.junit.HapiTest; -import com.hedera.services.bdd.junit.HapiTestSuite; -import com.hedera.services.bdd.spec.HapiSpec; +import com.hedera.services.bdd.spec.HapiSpecSetup; import com.hedera.services.bdd.spec.utilops.CustomSpecAssert; -import com.hedera.services.bdd.suites.HapiSuite; import com.hederahashgraph.api.proto.java.AccountAmount; import com.hederahashgraph.api.proto.java.AccountID; +import com.hederahashgraph.api.proto.java.ResponseCodeEnum; +import com.hederahashgraph.api.proto.java.Timestamp; +import edu.umd.cs.findbugs.annotations.NonNull; import java.math.BigInteger; import java.util.ArrayList; +import java.util.Arrays; import java.util.List; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; +import java.util.Objects; +import java.util.stream.Stream; +import org.apache.tuweni.bytes.Bytes32; import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.DynamicTest; import org.junit.jupiter.api.Tag; -@HapiTestSuite(fuzzyMatch = true) @Tag(SMART_CONTRACT) -public class RecordsSuite extends HapiSuite { - - private static final Logger log = LogManager.getLogger(RecordsSuite.class); - - public static void main(String... args) { - new RecordsSuite().runSuiteAsync(); - } - - @Override - public List getSpecsInSuite() { - return List.of(bigCall(), txRecordsContainValidTransfers()); - } - - @Override - public boolean canRunConcurrent() { - return true; - } +public class RecordsSuite { + public static final String LOG_NOW = "logNow"; + public static final String AUTO_ACCOUNT = "autoAccount"; @HapiTest - HapiSpec bigCall() { + final Stream bigCall() { final var contract = "BigBig"; final var txName = "BigCall"; final long byteArraySize = (long) (87.5 * 1_024); @@ -80,7 +90,7 @@ HapiSpec bigCall() { } @HapiTest - HapiSpec txRecordsContainValidTransfers() { + final Stream txRecordsContainValidTransfers() { final var contract = "ParentChildTransfer"; return defaultHapiSpec("TXRecordsContainValidTransfers", NONDETERMINISTIC_TRANSACTION_FEES) @@ -126,6 +136,178 @@ HapiSpec txRecordsContainValidTransfers() { })); } + @SuppressWarnings("java:S5960") + @HapiTest + final Stream blck003ReturnsTimestampOfTheBlock() { + final var contract = "EmitBlockTimestamp"; + final var firstCall = "firstCall"; + final var secondCall = "secondCall"; + + return defaultHapiSpec("returnsTimestampOfTheBlock", NONDETERMINISTIC_ETHEREUM_DATA, NONDETERMINISTIC_LOG_DATA) + .given( + newKeyNamed(SECP_256K1_SOURCE_KEY).shape(SECP_256K1_SHAPE), + cryptoCreate(RELAYER).balance(6 * ONE_MILLION_HBARS), + cryptoTransfer(tinyBarsFromAccountToAlias(GENESIS, SECP_256K1_SOURCE_KEY, ONE_HUNDRED_HBARS)) + .via(AUTO_ACCOUNT), + getTxnRecord(AUTO_ACCOUNT).andAllChildRecords(), + uploadInitCode(contract), + contractCreate(contract)) + .when( + // Ensure we submit these two transactions in the same block + waitUntilStartOfNextAdhocPeriod(2_000), + ethereumCall(contract, LOG_NOW) + .type(EthTxData.EthTransactionType.EIP1559) + .signingWith(SECP_256K1_SOURCE_KEY) + .payingWith(RELAYER) + .nonce(0) + .maxFeePerGas(50L) + .gasLimit(1_000_000L) + .via(firstCall) + .deferStatusResolution() + .hasKnownStatus(ResponseCodeEnum.SUCCESS), + ethereumCall(contract, LOG_NOW) + .type(EthTxData.EthTransactionType.EIP1559) + .signingWith(SECP_256K1_SOURCE_KEY) + .payingWith(RELAYER) + .nonce(1) + .maxFeePerGas(50L) + .gasLimit(1_000_000L) + .via(secondCall) + .deferStatusResolution() + .hasKnownStatus(ResponseCodeEnum.SUCCESS)) + .then(withOpContext((spec, opLog) -> { + final var firstBlockOp = getTxnRecord(firstCall).hasRetryAnswerOnlyPrecheck(RECORD_NOT_FOUND); + final var recordOp = getTxnRecord(secondCall).hasRetryAnswerOnlyPrecheck(RECORD_NOT_FOUND); + allRunFor(spec, firstBlockOp, recordOp); + + final var firstCallRecord = firstBlockOp.getResponseRecord(); + final var firstCallLogs = + firstCallRecord.getContractCallResult().getLogInfoList(); + final var firstCallTimeLogData = + firstCallLogs.get(0).getData().toByteArray(); + final var firstCallTimestamp = + Longs.fromByteArray(Arrays.copyOfRange(firstCallTimeLogData, 24, 32)); + + final var secondCallRecord = recordOp.getResponseRecord(); + final var secondCallLogs = + secondCallRecord.getContractCallResult().getLogInfoList(); + final var secondCallTimeLogData = + secondCallLogs.get(0).getData().toByteArray(); + final var secondCallTimestamp = + Longs.fromByteArray(Arrays.copyOfRange(secondCallTimeLogData, 24, 32)); + + final var firstBlockPeriod = canonicalBlockPeriod(firstCallRecord.getConsensusTimestamp()); + final var secondBlockPeriod = canonicalBlockPeriod(secondCallRecord.getConsensusTimestamp()); + + // In general both calls will be handled in the same block period, and should hence have the + // same Ethereum block timestamp; but timing fluctuations in CI _can_ cause them to be handled + // in different block periods, so we allow for that here as well + if (firstBlockPeriod < secondBlockPeriod) { + assertTrue( + firstCallTimestamp < secondCallTimestamp, + "Block timestamps should change from period " + firstBlockPeriod + " to " + + secondBlockPeriod); + } else { + assertEquals(firstCallTimestamp, secondCallTimestamp, "Block timestamps should be equal"); + } + })); + } + + @HapiTest + final Stream blck001And002And003And004ReturnsCorrectBlockProperties() { + final var contract = "EmitBlockTimestamp"; + final var firstBlock = "firstBlock"; + final var secondBlock = "secondBlock"; + + return defaultHapiSpec( + "returnsCorrectBlockProperties", NONDETERMINISTIC_ETHEREUM_DATA, NONDETERMINISTIC_LOG_DATA) + .given( + newKeyNamed(SECP_256K1_SOURCE_KEY).shape(SECP_256K1_SHAPE), + cryptoCreate(RELAYER).balance(6 * ONE_MILLION_HBARS), + cryptoTransfer(tinyBarsFromAccountToAlias(GENESIS, SECP_256K1_SOURCE_KEY, ONE_HUNDRED_HBARS)) + .via(AUTO_ACCOUNT), + getTxnRecord(AUTO_ACCOUNT).andAllChildRecords(), + uploadInitCode(contract), + contractCreate(contract)) + .when( + waitUntilStartOfNextAdhocPeriod(2_000L), + ethereumCall(contract, LOG_NOW) + .type(EthTxData.EthTransactionType.EIP1559) + .signingWith(SECP_256K1_SOURCE_KEY) + .payingWith(RELAYER) + .nonce(0) + .maxFeePerGas(50L) + .gasLimit(1_000_000L) + .via(firstBlock) + .deferStatusResolution() + .hasKnownStatus(ResponseCodeEnum.SUCCESS), + // Make sure we submit the next transaction in the next block + waitUntilStartOfNextAdhocPeriod(2_000L), + ethereumCall(contract, LOG_NOW) + .type(EthTxData.EthTransactionType.EIP1559) + .signingWith(SECP_256K1_SOURCE_KEY) + .payingWith(RELAYER) + .nonce(1) + .maxFeePerGas(50L) + .gasLimit(1_000_000L) + .via(secondBlock) + .hasKnownStatus(ResponseCodeEnum.SUCCESS)) + .then(withOpContext((spec, opLog) -> { + final var firstBlockOp = getTxnRecord(firstBlock).hasRetryAnswerOnlyPrecheck(RECORD_NOT_FOUND); + final var recordOp = getTxnRecord(secondBlock).hasRetryAnswerOnlyPrecheck(RECORD_NOT_FOUND); + allRunFor(spec, firstBlockOp, recordOp); + + // First block info + final var firstBlockRecord = firstBlockOp.getResponseRecord(); + final var firstBlockLogs = + firstBlockRecord.getContractCallResult().getLogInfoList(); + final var firstBlockTimeLogData = + firstBlockLogs.get(0).getData().toByteArray(); + final var firstBlockTimestamp = + Longs.fromByteArray(Arrays.copyOfRange(firstBlockTimeLogData, 24, 32)); + final var firstBlockHashLogData = + firstBlockLogs.get(1).getData().toByteArray(); + final var firstBlockNumber = Longs.fromByteArray(Arrays.copyOfRange(firstBlockHashLogData, 24, 32)); + final var firstBlockHash = Bytes32.wrap(Arrays.copyOfRange(firstBlockHashLogData, 32, 64)); + + assertEquals(Bytes32.ZERO, firstBlockHash); + + // Second block info + final var secondBlockRecord = recordOp.getResponseRecord(); + final var secondBlockLogs = + secondBlockRecord.getContractCallResult().getLogInfoList(); + assertEquals(2, secondBlockLogs.size()); + final var secondBlockTimeLogData = + secondBlockLogs.get(0).getData().toByteArray(); + final var secondBlockTimestamp = + Longs.fromByteArray(Arrays.copyOfRange(secondBlockTimeLogData, 24, 32)); + + assertNotEquals(firstBlockTimestamp, secondBlockTimestamp, "Block timestamps should change"); + + final var secondBlockHashLogData = + secondBlockLogs.get(1).getData().toByteArray(); + final var secondBlockNumber = + Longs.fromByteArray(Arrays.copyOfRange(secondBlockHashLogData, 24, 32)); + + assertEquals(firstBlockNumber + 1, secondBlockNumber, "Wrong previous block number"); + + final var secondBlockHash = Bytes32.wrap(Arrays.copyOfRange(secondBlockHashLogData, 32, 64)); + + assertEquals(Bytes32.ZERO, secondBlockHash); + })); + } + + /** + * Returns the canonical block period for the given consensus timestamp. + * + * @param consensusTimestamp the consensus timestamp + * @return the canonical block period + */ + private long canonicalBlockPeriod(@NonNull final Timestamp consensusTimestamp) { + return Objects.requireNonNull(consensusTimestamp).getSeconds() + / Long.parseLong(HapiSpecSetup.getDefaultNodeProps().get("hedera.recordStream.logPeriod")); + } + private long sumAmountsInTransferList(List transferList) { var sumToReturn = 0L; for (AccountAmount currAccAmount : transferList) { @@ -133,9 +315,4 @@ private long sumAmountsInTransferList(List transferList) { } return sumToReturn; } - - @Override - protected Logger getResultsLogger() { - return log; - } } diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/traceability/TraceabilitySuite.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/traceability/TraceabilitySuite.java index 698392b3970d..00dfa193501a 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/traceability/TraceabilitySuite.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/traceability/TraceabilitySuite.java @@ -48,15 +48,36 @@ import static com.hedera.services.bdd.spec.utilops.UtilVerbs.sourcing; import static com.hedera.services.bdd.spec.utilops.UtilVerbs.withOpContext; import static com.hedera.services.bdd.spec.utilops.records.SnapshotMatchMode.ALLOW_SKIPPED_ENTITY_IDS; +import static com.hedera.services.bdd.spec.utilops.records.SnapshotMatchMode.FULLY_NONDETERMINISTIC; import static com.hedera.services.bdd.spec.utilops.records.SnapshotMatchMode.HIGHLY_NON_DETERMINISTIC_FEES; import static com.hedera.services.bdd.spec.utilops.records.SnapshotMatchMode.NONDETERMINISTIC_CONTRACT_CALL_RESULTS; import static com.hedera.services.bdd.spec.utilops.records.SnapshotMatchMode.NONDETERMINISTIC_ETHEREUM_DATA; import static com.hedera.services.bdd.spec.utilops.records.SnapshotMatchMode.NONDETERMINISTIC_FUNCTION_PARAMETERS; import static com.hedera.services.bdd.spec.utilops.records.SnapshotMatchMode.NONDETERMINISTIC_NONCE; import static com.hedera.services.bdd.spec.utilops.records.SnapshotMatchMode.NONDETERMINISTIC_TRANSACTION_FEES; +import static com.hedera.services.bdd.suites.HapiSuite.DEFAULT_PAYER; +import static com.hedera.services.bdd.suites.HapiSuite.EMPTY_KEY; +import static com.hedera.services.bdd.suites.HapiSuite.FIVE_HBARS; +import static com.hedera.services.bdd.suites.HapiSuite.GENESIS; +import static com.hedera.services.bdd.suites.HapiSuite.MAX_UINT256_VALUE; +import static com.hedera.services.bdd.suites.HapiSuite.ONE_HBAR; +import static com.hedera.services.bdd.suites.HapiSuite.ONE_HUNDRED_HBARS; +import static com.hedera.services.bdd.suites.HapiSuite.ONE_MILLION_HBARS; +import static com.hedera.services.bdd.suites.HapiSuite.RELAYER; +import static com.hedera.services.bdd.suites.HapiSuite.SECP_256K1_SHAPE; +import static com.hedera.services.bdd.suites.HapiSuite.SECP_256K1_SOURCE_KEY; +import static com.hedera.services.bdd.suites.HapiSuite.TOKEN_TREASURY; +import static com.hedera.services.bdd.suites.SidecarAwareHapiSuite.assertNoMismatchedSidecars; +import static com.hedera.services.bdd.suites.SidecarAwareHapiSuite.expectContractActionSidecarFor; +import static com.hedera.services.bdd.suites.SidecarAwareHapiSuite.expectContractBytecode; +import static com.hedera.services.bdd.suites.SidecarAwareHapiSuite.expectContractBytecodeSidecarFor; +import static com.hedera.services.bdd.suites.SidecarAwareHapiSuite.expectContractBytecodeWithMinimalFieldsSidecarFor; +import static com.hedera.services.bdd.suites.SidecarAwareHapiSuite.expectContractStateChangesSidecarFor; +import static com.hedera.services.bdd.suites.SidecarAwareHapiSuite.expectFailedContractBytecodeSidecarFor; +import static com.hedera.services.bdd.suites.SidecarAwareHapiSuite.initializeSidecarWatcher; +import static com.hedera.services.bdd.suites.SidecarAwareHapiSuite.tearDownSidecarWatcher; import static com.hedera.services.bdd.suites.contract.Utils.aaWith; import static com.hedera.services.bdd.suites.contract.Utils.asAddress; -import static com.hedera.services.bdd.suites.contract.Utils.asSolidityAddress; import static com.hedera.services.bdd.suites.contract.Utils.asToken; import static com.hedera.services.bdd.suites.contract.Utils.captureOneChildCreate2MetaFor; import static com.hedera.services.bdd.suites.contract.Utils.extractBytecodeUnhexed; @@ -86,7 +107,6 @@ import static com.hederahashgraph.api.proto.java.TokenType.NON_FUNGIBLE_UNIQUE; import static com.swirlds.common.utility.CommonUtils.hex; import static org.hyperledger.besu.crypto.Hash.keccak256; -import static org.hyperledger.besu.evm.frame.ExceptionalHaltReason.DefaultExceptionalHaltReason.PRECOMPILE_ERROR; import com.esaulpaugh.headlong.abi.Address; import com.esaulpaugh.headlong.abi.Function; @@ -96,16 +116,14 @@ import com.hedera.node.app.hapi.utils.ethereum.EthTxData; import com.hedera.node.app.hapi.utils.ethereum.EthTxData.EthTransactionType; import com.hedera.services.bdd.junit.HapiTest; -import com.hedera.services.bdd.junit.HapiTestSuite; +import com.hedera.services.bdd.junit.OrderedInIsolation; import com.hedera.services.bdd.spec.HapiPropertySource; -import com.hedera.services.bdd.spec.HapiSpec; import com.hedera.services.bdd.spec.assertions.StateChange; import com.hedera.services.bdd.spec.assertions.StorageChange; import com.hedera.services.bdd.spec.queries.meta.HapiGetTxnRecord; import com.hedera.services.bdd.spec.transactions.TxnUtils; import com.hedera.services.bdd.spec.transactions.TxnVerbs; import com.hedera.services.bdd.spec.transactions.contract.HapiParserUtil; -import com.hedera.services.bdd.suites.SidecarAwareHapiSuite; import com.hedera.services.stream.proto.CallOperationType; import com.hedera.services.stream.proto.ContractAction; import com.hederahashgraph.api.proto.java.AccountID; @@ -119,19 +137,18 @@ import java.util.Arrays; import java.util.List; import java.util.concurrent.atomic.AtomicReference; +import java.util.stream.Stream; import org.apache.commons.lang3.ArrayUtils; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.apache.tuweni.bytes.Bytes; -import org.junit.jupiter.api.MethodOrderer; +import org.junit.jupiter.api.DynamicTest; import org.junit.jupiter.api.Order; import org.junit.jupiter.api.Tag; -import org.junit.jupiter.api.TestMethodOrder; -@HapiTestSuite(fuzzyMatch = true) -@TestMethodOrder(MethodOrderer.OrderAnnotation.class) +@OrderedInIsolation @Tag(SMART_CONTRACT) -public class TraceabilitySuite extends SidecarAwareHapiSuite { +public class TraceabilitySuite { private static final Logger log = LogManager.getLogger(TraceabilitySuite.class); @@ -159,46 +176,9 @@ public class TraceabilitySuite extends SidecarAwareHapiSuite { private static final String LAZY_CREATE_PROPERTY = "lazyCreation.enabled"; public static final String SIDECARS_PROP = "contracts.sidecars"; - public static void main(final String... args) { - new TraceabilitySuite().runSuiteSync(); - } - - @Override - public List getSpecsInSuite() { - return List.of( - suiteSetup(), - traceabilityE2EScenario1(), - traceabilityE2EScenario2(), - traceabilityE2EScenario3(), - traceabilityE2EScenario4(), - traceabilityE2EScenario5(), - traceabilityE2EScenario6(), - traceabilityE2EScenario7(), - traceabilityE2EScenario8(), - traceabilityE2EScenario9(), - traceabilityE2EScenario10(), - traceabilityE2EScenario11(), - traceabilityE2EScenario12(), - traceabilityE2EScenario13(), - traceabilityE2EScenario14(), - traceabilityE2EScenario15(), - traceabilityE2EScenario16(), - traceabilityE2EScenario17(), - traceabilityE2EScenario18(), - traceabilityE2EScenario19(), - traceabilityE2EScenario20(), - traceabilityE2EScenario21(), - vanillaBytecodeSidecar(), - vanillaBytecodeSidecar2(), - actionsShowPropagatedRevert(), - ethereumLazyCreateExportsExpectedSidecars(), - hollowAccountCreate2MergeExportsExpectedSidecars(), - assertSidecars()); - } - @HapiTest @Order(1) - final HapiSpec suiteSetup() { + final Stream suiteSetup() { return defaultHapiSpec("suiteSetup") .given() .when() @@ -211,7 +191,7 @@ final HapiSpec suiteSetup() { @HapiTest @Order(2) - final HapiSpec traceabilityE2EScenario1() { + final Stream traceabilityE2EScenario1() { return defaultHapiSpec( "traceabilityE2EScenario1", NONDETERMINISTIC_FUNCTION_PARAMETERS, @@ -585,7 +565,7 @@ final HapiSpec traceabilityE2EScenario1() { @HapiTest @Order(3) - final HapiSpec traceabilityE2EScenario2() { + final Stream traceabilityE2EScenario2() { return defaultHapiSpec( "traceabilityE2EScenario2", NONDETERMINISTIC_FUNCTION_PARAMETERS, @@ -993,7 +973,7 @@ final HapiSpec traceabilityE2EScenario2() { @HapiTest @Order(4) - final HapiSpec traceabilityE2EScenario3() { + final Stream traceabilityE2EScenario3() { return defaultHapiSpec( "traceabilityE2EScenario3", NONDETERMINISTIC_FUNCTION_PARAMETERS, HIGHLY_NON_DETERMINISTIC_FEES) .given( @@ -1403,7 +1383,7 @@ final HapiSpec traceabilityE2EScenario3() { @HapiTest @Order(5) - final HapiSpec traceabilityE2EScenario4() { + final Stream traceabilityE2EScenario4() { return defaultHapiSpec( "traceabilityE2EScenario4", NONDETERMINISTIC_FUNCTION_PARAMETERS, HIGHLY_NON_DETERMINISTIC_FEES) .given( @@ -1696,8 +1676,8 @@ final HapiSpec traceabilityE2EScenario4() { @HapiTest @Order(6) - final HapiSpec traceabilityE2EScenario5() { - return defaultHapiSpec("traceabilityE2EScenario5", NONDETERMINISTIC_FUNCTION_PARAMETERS) + final Stream traceabilityE2EScenario5() { + return defaultHapiSpec("traceabilityE2EScenario5", FULLY_NONDETERMINISTIC) .given( uploadInitCode(TRACEABILITY), contractCreate(TRACEABILITY, BigInteger.valueOf(55), BigInteger.TWO, BigInteger.TWO) @@ -2002,11 +1982,8 @@ final HapiSpec traceabilityE2EScenario5() { @HapiTest @Order(7) - final HapiSpec traceabilityE2EScenario6() { - return defaultHapiSpec( - "traceabilityE2EScenario6", - NONDETERMINISTIC_FUNCTION_PARAMETERS, - NONDETERMINISTIC_TRANSACTION_FEES) + final Stream traceabilityE2EScenario6() { + return defaultHapiSpec("traceabilityE2EScenario6", FULLY_NONDETERMINISTIC) .given( uploadInitCode(TRACEABILITY), contractCreate(TRACEABILITY, BigInteger.TWO, BigInteger.valueOf(3), BigInteger.valueOf(4)) @@ -2341,7 +2318,7 @@ final HapiSpec traceabilityE2EScenario6() { @HapiTest @Order(8) - final HapiSpec traceabilityE2EScenario7() { + final Stream traceabilityE2EScenario7() { return defaultHapiSpec("traceabilityE2EScenario7", NONDETERMINISTIC_FUNCTION_PARAMETERS) .given( uploadInitCode(TRACEABILITY_CALLCODE), @@ -2732,8 +2709,11 @@ final HapiSpec traceabilityE2EScenario7() { @HapiTest @Order(9) - final HapiSpec traceabilityE2EScenario8() { - return defaultHapiSpec("traceabilityE2EScenario8", NONDETERMINISTIC_FUNCTION_PARAMETERS) + final Stream traceabilityE2EScenario8() { + return defaultHapiSpec( + "traceabilityE2EScenario8", + NONDETERMINISTIC_FUNCTION_PARAMETERS, + NONDETERMINISTIC_TRANSACTION_FEES) .given( uploadInitCode(TRACEABILITY_CALLCODE), contractCreate(TRACEABILITY_CALLCODE, BigInteger.valueOf(55), BigInteger.TWO, BigInteger.TWO) @@ -3084,9 +3064,8 @@ final HapiSpec traceabilityE2EScenario8() { @HapiTest @Order(10) - final HapiSpec traceabilityE2EScenario9() { - return defaultHapiSpec( - "traceabilityE2EScenario9", NONDETERMINISTIC_FUNCTION_PARAMETERS, HIGHLY_NON_DETERMINISTIC_FEES) + final Stream traceabilityE2EScenario9() { + return defaultHapiSpec("traceabilityE2EScenario9", FULLY_NONDETERMINISTIC) .given( uploadInitCode(TRACEABILITY), contractCreate(TRACEABILITY, BigInteger.valueOf(55), BigInteger.TWO, BigInteger.TWO) @@ -3392,8 +3371,11 @@ final HapiSpec traceabilityE2EScenario9() { @HapiTest @Order(11) - final HapiSpec traceabilityE2EScenario10() { - return defaultHapiSpec("traceabilityE2EScenario10", NONDETERMINISTIC_FUNCTION_PARAMETERS) + final Stream traceabilityE2EScenario10() { + return defaultHapiSpec( + "traceabilityE2EScenario10", + NONDETERMINISTIC_FUNCTION_PARAMETERS, + NONDETERMINISTIC_TRANSACTION_FEES) .given( uploadInitCode(TRACEABILITY), contractCreate(TRACEABILITY, BigInteger.TWO, BigInteger.valueOf(3), BigInteger.valueOf(4)) @@ -3735,7 +3717,7 @@ final HapiSpec traceabilityE2EScenario10() { @HapiTest @Order(12) - final HapiSpec traceabilityE2EScenario11() { + final Stream traceabilityE2EScenario11() { return defaultHapiSpec("traceabilityE2EScenario11", NONDETERMINISTIC_FUNCTION_PARAMETERS) .given( uploadInitCode(TRACEABILITY), @@ -4013,7 +3995,7 @@ final HapiSpec traceabilityE2EScenario11() { @HapiTest @Order(13) - final HapiSpec traceabilityE2EScenario12() { + final Stream traceabilityE2EScenario12() { final var contract = "CreateTrivial"; final var scenario12 = "traceabilityE2EScenario12"; return defaultHapiSpec(scenario12) @@ -4046,9 +4028,9 @@ final HapiSpec traceabilityE2EScenario12() { @HapiTest @Order(14) - HapiSpec traceabilityE2EScenario13() { + final Stream traceabilityE2EScenario13() { final AtomicReference accountIDAtomicReference = new AtomicReference<>(); - return defaultHapiSpec("traceabilityE2EScenario13", NONDETERMINISTIC_ETHEREUM_DATA, NONDETERMINISTIC_NONCE) + return defaultHapiSpec("traceabilityE2EScenario13", FULLY_NONDETERMINISTIC) .given( newKeyNamed(SECP_256K1_SOURCE_KEY).shape(SECP_256K1_SHAPE), cryptoCreate(RELAYER).balance(6 * ONE_MILLION_HBARS), @@ -4090,9 +4072,8 @@ HapiSpec traceabilityE2EScenario13() { @HapiTest @Order(15) - final HapiSpec traceabilityE2EScenario14() { - return defaultHapiSpec( - "traceabilityE2EScenario14", NONDETERMINISTIC_ETHEREUM_DATA, NONDETERMINISTIC_TRANSACTION_FEES) + final Stream traceabilityE2EScenario14() { + return defaultHapiSpec("traceabilityE2EScenario14", FULLY_NONDETERMINISTIC) .given( newKeyNamed(SECP_256K1_SOURCE_KEY).shape(SECP_256K1_SHAPE), cryptoCreate(RELAYER).balance(6 * ONE_MILLION_HBARS), @@ -4135,7 +4116,7 @@ final HapiSpec traceabilityE2EScenario14() { @HapiTest @Order(16) - HapiSpec traceabilityE2EScenario15() { + final Stream traceabilityE2EScenario15() { final String GET_BYTECODE = "getBytecode"; final String DEPLOY = "deploy"; final var CREATE_2_TXN = "Create2Txn"; @@ -4245,7 +4226,7 @@ HapiSpec traceabilityE2EScenario15() { .setRecipientContract( spec.registry() .getContractId(contract)) - .setGasUsed(80135) + .setGasUsed(80193) .setOutput(EMPTY) .setInput( encodeFunctionCall( @@ -4260,7 +4241,7 @@ HapiSpec traceabilityE2EScenario15() { .setCallingContract( spec.registry() .getContractId(contract)) - .setGas(3870609) + .setGas(3870552) .setRecipientContract(childId) .setGasUsed(44936) .setValue(tcValue) @@ -4282,15 +4263,12 @@ HapiSpec traceabilityE2EScenario15() { @HapiTest @Order(17) - HapiSpec traceabilityE2EScenario16() { + final Stream traceabilityE2EScenario16() { final AtomicReference vanillaTokenID = new AtomicReference<>(); final String PRECOMPILE_CALLER = "PrecompileCaller"; final String txn = "payTxn"; final String toHash = "toHash"; - return defaultHapiSpec( - "traceabilityE2EScenario16", - NONDETERMINISTIC_FUNCTION_PARAMETERS, - NONDETERMINISTIC_TRANSACTION_FEES) + return defaultHapiSpec("traceabilityE2EScenario16", FULLY_NONDETERMINISTIC) .given( tokenCreate("goodToken") .tokenType(TokenType.FUNGIBLE_COMMON) @@ -4395,7 +4373,7 @@ HapiSpec traceabilityE2EScenario16() { @HapiTest @Order(18) - final HapiSpec traceabilityE2EScenario17() { + final Stream traceabilityE2EScenario17() { return defaultHapiSpec("traceabilityE2EScenario17") .given( uploadInitCode(REVERTING_CONTRACT), @@ -4454,7 +4432,7 @@ final HapiSpec traceabilityE2EScenario17() { @HapiTest @Order(19) - final HapiSpec traceabilityE2EScenario18() { + final Stream traceabilityE2EScenario18() { return defaultHapiSpec("traceabilityE2EScenario18") .given(uploadInitCode(REVERTING_CONTRACT)) .when(contractCreate(REVERTING_CONTRACT, BigInteger.valueOf(4)) @@ -4479,12 +4457,11 @@ final HapiSpec traceabilityE2EScenario18() { @HapiTest @Order(20) - HapiSpec traceabilityE2EScenario19() { + final Stream traceabilityE2EScenario19() { final var RECEIVER = "RECEIVER"; final var hbarsToSend = 1; final var transferTxn = "payTxn"; - return defaultHapiSpec( - "traceabilityE2EScenario19", NONDETERMINISTIC_ETHEREUM_DATA, NONDETERMINISTIC_TRANSACTION_FEES) + return defaultHapiSpec("traceabilityE2EScenario19", FULLY_NONDETERMINISTIC) .given( newKeyNamed(SECP_256K1_SOURCE_KEY).shape(SECP_256K1_SHAPE), cryptoCreate(RECEIVER).balance(0L), @@ -4524,8 +4501,8 @@ HapiSpec traceabilityE2EScenario19() { @HapiTest @Order(21) - final HapiSpec traceabilityE2EScenario20() { - return defaultHapiSpec("traceabilityE2EScenario20", NONDETERMINISTIC_TRANSACTION_FEES) + final Stream traceabilityE2EScenario20() { + return defaultHapiSpec("traceabilityE2EScenario20", HIGHLY_NON_DETERMINISTIC_FEES) .given(uploadInitCode(REVERTING_CONTRACT)) .when(contractCreate(REVERTING_CONTRACT, BigInteger.valueOf(6)) .via(FIRST_CREATE_TXN) @@ -4550,8 +4527,8 @@ final HapiSpec traceabilityE2EScenario20() { @HapiTest @Order(22) - final HapiSpec traceabilityE2EScenario21() { - return defaultHapiSpec("traceabilityE2EScenario21") + final Stream traceabilityE2EScenario21() { + return defaultHapiSpec("traceabilityE2EScenario21", FULLY_NONDETERMINISTIC) .given( uploadInitCode(REVERTING_CONTRACT), contractCreate(REVERTING_CONTRACT, BigInteger.valueOf(6)) @@ -4600,18 +4577,21 @@ final HapiSpec traceabilityE2EScenario21() { .setInput(encodeFunctionCall(REVERTING_CONTRACT, "callingWrongAddress")) .build(), ContractAction.newBuilder() - .setCallType(CALL) + .setCallType(PRECOMPILE) .setCallingContract( spec.registry().getContractId(REVERTING_CONTRACT)) .setCallOperationType(CallOperationType.OP_CALL) .setCallDepth(1) - .setGas(960576) .setInput(ByteStringUtils.wrapUnsafely( Function.parse("boo" + "(uint256)") .encodeCallWithArgs(BigInteger.valueOf(234)) .array())) + .setGas(960576) .setGasUsed(960576) - .setError(ByteString.copyFromUtf8(PRECOMPILE_ERROR.name())) + .setRecipientContract(ContractID.newBuilder() + .setContractNum(0) + .build()) + .setOutput(EMPTY) /* For EVM v0.34 use this code block instead: @@ -4619,13 +4599,12 @@ final HapiSpec traceabilityE2EScenario21() { .setError(ByteString.copyFromUtf8(INVALID_SOLIDITY_ADDRESS.name())) */ - .setTargetedAddress(ByteString.copyFrom(asSolidityAddress(0, 0, 0))) .build()))))); } @HapiTest @Order(23) - final HapiSpec vanillaBytecodeSidecar() { + final Stream vanillaBytecodeSidecar() { final var EMPTY_CONSTRUCTOR_CONTRACT = "EmptyConstructor"; final var vanillaBytecodeSidecar = "vanillaBytecodeSidecar"; final var firstTxn = "firstTxn"; @@ -4660,7 +4639,7 @@ final HapiSpec vanillaBytecodeSidecar() { @HapiTest @Order(24) - final HapiSpec vanillaBytecodeSidecar2() { + final Stream vanillaBytecodeSidecar2() { final var contract = "CreateTrivial"; final String trivialCreate = "vanillaBytecodeSidecar2"; final var firstTxn = "firstTxn"; @@ -4692,7 +4671,7 @@ final HapiSpec vanillaBytecodeSidecar2() { @HapiTest @Order(25) - final HapiSpec actionsShowPropagatedRevert() { + final Stream actionsShowPropagatedRevert() { final var APPROVE_BY_DELEGATE = "ApproveByDelegateCall"; final var badApproval = "BadApproval"; final var somebody = "somebody"; @@ -4875,7 +4854,7 @@ final HapiSpec actionsShowPropagatedRevert() { @HapiTest @Order(26) - final HapiSpec ethereumLazyCreateExportsExpectedSidecars() { + final Stream ethereumLazyCreateExportsExpectedSidecars() { final var RECIPIENT_KEY = "lazyAccountRecipient"; final var RECIPIENT_KEY2 = "lazyAccountRecipient2"; final var lazyCreateTxn = "lazyCreateTxn"; @@ -4885,7 +4864,8 @@ final HapiSpec ethereumLazyCreateExportsExpectedSidecars() { "ethereumLazyCreateExportsExpectedSidecars", NONDETERMINISTIC_ETHEREUM_DATA, NONDETERMINISTIC_NONCE, - ALLOW_SKIPPED_ENTITY_IDS) + ALLOW_SKIPPED_ENTITY_IDS, + NONDETERMINISTIC_CONTRACT_CALL_RESULTS) .preserving(CHAIN_ID_PROPERTY, LAZY_CREATE_PROPERTY, "contracts.evm.version") .given( overridingThree( @@ -4972,7 +4952,7 @@ final HapiSpec ethereumLazyCreateExportsExpectedSidecars() { @SuppressWarnings("java:S5960") @HapiTest @Order(27) - final HapiSpec hollowAccountCreate2MergeExportsExpectedSidecars() { + final Stream hollowAccountCreate2MergeExportsExpectedSidecars() { final var tcValue = 1_234L; final var create2Factory = "Create2Factory"; final var creation = "creation"; @@ -5107,7 +5087,7 @@ create2Factory, GET_BYTECODE, asHeadlongAddress(factoryEvmAddress.get()), salt) .setRecipientContract( spec.registry() .getContractId(create2Factory)) - .setGasUsed(80135) + .setGasUsed(80193) .setOutput(EMPTY) .setInput( encodeFunctionCall( @@ -5122,7 +5102,7 @@ create2Factory, GET_BYTECODE, asHeadlongAddress(factoryEvmAddress.get()), salt) .setCallingContract( spec.registry() .getContractId(create2Factory)) - .setGas(3870609) + .setGas(3870552) // recipient should be the // original hollow account id as // a contract @@ -5146,15 +5126,10 @@ create2Factory, GET_BYTECODE, asHeadlongAddress(factoryEvmAddress.get()), salt) @HapiTest @Order(Integer.MAX_VALUE) - public final HapiSpec assertSidecars() { + public final Stream assertSidecars() { return defaultHapiSpec("assertSidecars") .given() .when(tearDownSidecarWatcher()) .then(assertNoMismatchedSidecars()); } - - @Override - protected Logger getResultsLogger() { - return log; - } } diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/crypto/AutoAccountCreationSuite.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/crypto/AutoAccountCreationSuite.java index 1cc90e82160d..34706820d31e 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/crypto/AutoAccountCreationSuite.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/crypto/AutoAccountCreationSuite.java @@ -68,6 +68,16 @@ import static com.hedera.services.bdd.spec.utilops.records.SnapshotMatchMode.EXPECT_STREAMLINED_INGEST_RECORDS; import static com.hedera.services.bdd.spec.utilops.records.SnapshotMatchMode.HIGHLY_NON_DETERMINISTIC_FEES; import static com.hedera.services.bdd.spec.utilops.records.SnapshotMatchMode.NONDETERMINISTIC_TRANSACTION_FEES; +import static com.hedera.services.bdd.suites.HapiSuite.DEFAULT_PAYER; +import static com.hedera.services.bdd.suites.HapiSuite.FUNDING; +import static com.hedera.services.bdd.suites.HapiSuite.GENESIS; +import static com.hedera.services.bdd.suites.HapiSuite.ONE_HBAR; +import static com.hedera.services.bdd.suites.HapiSuite.ONE_HUNDRED_HBARS; +import static com.hedera.services.bdd.suites.HapiSuite.ONE_MILLION_HBARS; +import static com.hedera.services.bdd.suites.HapiSuite.SECP_256K1_SHAPE; +import static com.hedera.services.bdd.suites.HapiSuite.SECP_256K1_SOURCE_KEY; +import static com.hedera.services.bdd.suites.HapiSuite.THREE_MONTHS_IN_SECONDS; +import static com.hedera.services.bdd.suites.HapiSuite.TOKEN_TREASURY; import static com.hedera.services.bdd.suites.contract.Utils.aaWith; import static com.hedera.services.bdd.suites.contract.Utils.accountId; import static com.hedera.services.bdd.suites.contract.Utils.ocWith; @@ -92,10 +102,7 @@ import com.google.protobuf.ByteString; import com.hedera.services.bdd.junit.HapiTest; -import com.hedera.services.bdd.junit.HapiTestSuite; -import com.hedera.services.bdd.spec.HapiSpec; import com.hedera.services.bdd.spec.keys.KeyShape; -import com.hedera.services.bdd.suites.HapiSuite; import com.hederahashgraph.api.proto.java.AccountID; import com.hederahashgraph.api.proto.java.ContractID; import com.hederahashgraph.api.proto.java.Key; @@ -112,16 +119,18 @@ import java.util.Arrays; import java.util.List; import java.util.Optional; +import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicLong; import java.util.concurrent.atomic.AtomicReference; +import java.util.stream.Stream; import org.apache.commons.lang3.tuple.Pair; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; +import org.junit.jupiter.api.DynamicTest; import org.junit.jupiter.api.Tag; -@HapiTestSuite @Tag(CRYPTO) -public class AutoAccountCreationSuite extends HapiSuite { +public class AutoAccountCreationSuite { private static final Logger LOG = LogManager.getLogger(AutoAccountCreationSuite.class); private static final long INITIAL_BALANCE = 1000L; @@ -166,62 +175,9 @@ public class AutoAccountCreationSuite extends HapiSuite { private static final String HBAR_XFER = "hbarXfer"; private static final String NFT_XFER = "nftXfer"; private static final String FT_XFER = "ftXfer"; - private static final String ERC20_ABI = "ERC20ABI"; - - public static void main(String... args) { - new AutoAccountCreationSuite().runSuiteAsync(); - } - - @Override - public boolean canRunConcurrent() { - return true; - } - - @Override - protected Logger getResultsLogger() { - return LOG; - } - - @Override - public List getSpecsInSuite() { - return List.of( - /* --- Hbar auto creates */ - autoAccountCreationsHappyPath(), - autoAccountCreationBadAlias(), - autoAccountCreationUnsupportedAlias(), - transferToAccountAutoCreatedUsingAlias(), - transferToAccountAutoCreatedUsingAccount(), - transferFromAliasToAlias(), - transferFromAliasToAccount(), - multipleAutoAccountCreations(), - accountCreatedIfAliasUsedAsPubKey(), - aliasCanBeUsedOnManyAccountsNotAsAlias(), - autoAccountCreationWorksWhenUsingAliasOfDeletedAccount(), - canGetBalanceAndInfoViaAlias(), - noStakePeriodStartIfNotStakingToNode(), - /* -- HTS auto creates -- */ - canAutoCreateWithFungibleTokenTransfersToAlias(), - canAutoCreateWithNftTransferToEvmAddress(), - multipleTokenTransfersSucceed(), - canAutoCreateWithNftTransfersToAlias(), - autoCreateWithNftFallBackFeeFails(), - repeatedAliasInSameTransferListFails(), - canAutoCreateWithHbarAndTokenTransfers(), - payerBalanceIsReflectsAllChangesBeforeFeeCharging(), - /* --- HIP 583 --- */ - hollowAccountCreationWithCryptoTransfer(), - failureAfterHollowAccountCreationReclaimsAlias(), - transferHbarsToEVMAddressAlias(), - transferFungibleToEVMAddressAlias(), - transferNonFungibleToEVMAddressAlias(), - transferHbarsToECDSAKey(), - cannotAutoCreateWithTxnToLongZero(), - aliasedPayerDoesntWork()); - } @HapiTest - private HapiSpec aliasedPayerDoesntWork() { - final AtomicReference aliasId = new AtomicReference<>(); + final Stream aliasedPayerDoesntWork() { return defaultHapiSpec("aliasedPayerDoesntWork") .given( newKeyNamed(ALIAS), @@ -244,7 +200,7 @@ private HapiSpec aliasedPayerDoesntWork() { } @HapiTest - final HapiSpec canAutoCreateWithHbarAndTokenTransfers() { + final Stream canAutoCreateWithHbarAndTokenTransfers() { final var initialTokenSupply = 1000; return defaultHapiSpec("canAutoCreateWithHbarAndTokenTransfers", EXPECT_STREAMLINED_INGEST_RECORDS) .given( @@ -277,7 +233,7 @@ final HapiSpec canAutoCreateWithHbarAndTokenTransfers() { } @HapiTest - final HapiSpec repeatedAliasInSameTransferListFails() { + final Stream repeatedAliasInSameTransferListFails() { final AtomicReference ftId = new AtomicReference<>(); final AtomicReference nftId = new AtomicReference<>(); final AtomicReference partyId = new AtomicReference<>(); @@ -340,7 +296,7 @@ final HapiSpec repeatedAliasInSameTransferListFails() { } @HapiTest - final HapiSpec autoCreateWithNftFallBackFeeFails() { + final Stream autoCreateWithNftFallBackFeeFails() { final var firstRoyaltyCollector = "firstRoyaltyCollector"; return defaultHapiSpec("autoCreateWithNftFallBackFeeFails", HIGHLY_NON_DETERMINISTIC_FEES) .given( @@ -403,7 +359,7 @@ final HapiSpec autoCreateWithNftFallBackFeeFails() { } @HapiTest - final HapiSpec canAutoCreateWithNftTransfersToAlias() { + final Stream canAutoCreateWithNftTransfersToAlias() { final var civilianBal = 10 * ONE_HBAR; // The expected fee to transfer four serial numbers of two token types to a receiver with // no auto-creation; note it is approximate because the fee will vary slightly with the @@ -490,10 +446,11 @@ final HapiSpec canAutoCreateWithNftTransfersToAlias() { } @HapiTest - final HapiSpec canAutoCreateWithNftTransferToEvmAddress() { + final Stream canAutoCreateWithNftTransferToEvmAddress() { final var civilianBal = 10 * ONE_HBAR; final var nftTransfer = "multiNftTransfer"; final AtomicReference parentConsTime = new AtomicReference<>(); + final AtomicBoolean hasNodeStakeUpdate = new AtomicBoolean(false); return defaultHapiSpec("canAutoCreateWithNftTransferToEvmAddress") .given( @@ -525,17 +482,22 @@ final HapiSpec canAutoCreateWithNftTransferToEvmAddress() { .then( getTxnRecord(nftTransfer) .exposingTo(record -> parentConsTime.set(record.getConsensusTimestamp())) + .exposingAllTo(records -> hasNodeStakeUpdate.set( + records.size() > 1 && isEndOfStakingPeriodRecord(records.get(1)))) .andAllChildRecords() .hasNonStakingChildRecordCount(1) .logged(), sourcing(() -> childRecordsCheck( nftTransfer, SUCCESS, - recordWith().status(SUCCESS).consensusTimeImpliedByNonce(parentConsTime.get(), -1)))); + recordWith() + .status(SUCCESS) + .consensusTimeImpliedByNonce( + parentConsTime.get(), hasNodeStakeUpdate.get() ? -2 : -1)))); } @HapiTest - final HapiSpec multipleTokenTransfersSucceed() { + final Stream multipleTokenTransfersSucceed() { final var initialTokenSupply = 1000; final var multiTokenXfer = "multiTokenXfer"; @@ -624,7 +586,7 @@ final HapiSpec multipleTokenTransfersSucceed() { } @HapiTest - final HapiSpec payerBalanceIsReflectsAllChangesBeforeFeeCharging() { + final Stream payerBalanceIsReflectsAllChangesBeforeFeeCharging() { final var secondAliasKey = "secondAlias"; final var secondPayer = "secondPayer"; final AtomicLong totalAutoCreationFees = new AtomicLong(); @@ -673,7 +635,7 @@ final HapiSpec payerBalanceIsReflectsAllChangesBeforeFeeCharging() { } @HapiTest - final HapiSpec canAutoCreateWithFungibleTokenTransfersToAlias() { + final Stream canAutoCreateWithFungibleTokenTransfersToAlias() { final var initialTokenSupply = 1000; final var sameTokenXfer = "sameTokenXfer"; // The expected (network + service) fee for two token transfers to a receiver @@ -756,7 +718,7 @@ final HapiSpec canAutoCreateWithFungibleTokenTransfersToAlias() { } @HapiTest - final HapiSpec noStakePeriodStartIfNotStakingToNode() { + final Stream noStakePeriodStartIfNotStakingToNode() { final var user = "user"; final var contract = "contract"; return defaultHapiSpec("noStakePeriodStartIfNotStakingToNode", NONDETERMINISTIC_TRANSACTION_FEES) @@ -775,7 +737,7 @@ final HapiSpec noStakePeriodStartIfNotStakingToNode() { } @HapiTest - final HapiSpec hollowAccountCreationWithCryptoTransfer() { + final Stream hollowAccountCreationWithCryptoTransfer() { final var initialTokenSupply = 1000; final AtomicReference ftId = new AtomicReference<>(); final AtomicReference nftId = new AtomicReference<>(); @@ -880,7 +842,7 @@ final HapiSpec hollowAccountCreationWithCryptoTransfer() { } @HapiTest - final HapiSpec failureAfterHollowAccountCreationReclaimsAlias() { + final Stream failureAfterHollowAccountCreationReclaimsAlias() { final var underfunded = "underfunded"; final var secondTransferTxn = "SecondTransferTxn"; final AtomicReference targetAddress = new AtomicReference<>(); @@ -928,7 +890,7 @@ final HapiSpec failureAfterHollowAccountCreationReclaimsAlias() { } @HapiTest - final HapiSpec canGetBalanceAndInfoViaAlias() { + final Stream canGetBalanceAndInfoViaAlias() { final var ed25519SourceKey = "ed25519Alias"; final var secp256k1SourceKey = "secp256k1Alias"; final var secp256k1Shape = KeyShape.SECP256K1; @@ -977,7 +939,7 @@ final HapiSpec canGetBalanceAndInfoViaAlias() { } @HapiTest - final HapiSpec aliasCanBeUsedOnManyAccountsNotAsAlias() { + final Stream aliasCanBeUsedOnManyAccountsNotAsAlias() { return defaultHapiSpec("aliasCanBeUsedOnManyAccountsNotAsAlias") .given( /* have alias key on other accounts and tokens not as alias */ @@ -1011,7 +973,7 @@ final HapiSpec aliasCanBeUsedOnManyAccountsNotAsAlias() { } @HapiTest - final HapiSpec accountCreatedIfAliasUsedAsPubKey() { + final Stream accountCreatedIfAliasUsedAsPubKey() { return defaultHapiSpec("accountCreatedIfAliasUsedAsPubKey") .given( newKeyNamed(ALIAS), @@ -1038,7 +1000,7 @@ final HapiSpec accountCreatedIfAliasUsedAsPubKey() { } @HapiTest - final HapiSpec autoAccountCreationWorksWhenUsingAliasOfDeletedAccount() { + final Stream autoAccountCreationWorksWhenUsingAliasOfDeletedAccount() { return defaultHapiSpec("autoAccountCreationWorksWhenUsingAliasOfDeletedAccount") .given( newKeyNamed(ALIAS), @@ -1070,7 +1032,7 @@ final HapiSpec autoAccountCreationWorksWhenUsingAliasOfDeletedAccount() { } @HapiTest - final HapiSpec transferFromAliasToAlias() { + final Stream transferFromAliasToAlias() { return defaultHapiSpec("transferFromAliasToAlias") .given( newKeyNamed(ALIAS), @@ -1095,7 +1057,7 @@ final HapiSpec transferFromAliasToAlias() { } @HapiTest - final HapiSpec transferFromAliasToAccount() { + final Stream transferFromAliasToAccount() { final var payer = PAYER_4; final var alias = ALIAS; return defaultHapiSpec("transferFromAliasToAccount") @@ -1120,7 +1082,7 @@ final HapiSpec transferFromAliasToAccount() { } @HapiTest - final HapiSpec transferToAccountAutoCreatedUsingAccount() { + final Stream transferToAccountAutoCreatedUsingAccount() { return defaultHapiSpec("transferToAccountAutoCreatedUsingAccount") .given(newKeyNamed(TRANSFER_ALIAS), cryptoCreate(PAYER).balance(INITIAL_BALANCE * ONE_HBAR)) .when( @@ -1152,7 +1114,7 @@ final HapiSpec transferToAccountAutoCreatedUsingAccount() { } @HapiTest - final HapiSpec transferToAccountAutoCreatedUsingAlias() { + final Stream transferToAccountAutoCreatedUsingAlias() { return defaultHapiSpec("transferToAccountAutoCreatedUsingAlias") .given(newKeyNamed(ALIAS), cryptoCreate(PAYER).balance(INITIAL_BALANCE * ONE_HBAR)) .when( @@ -1178,7 +1140,7 @@ final HapiSpec transferToAccountAutoCreatedUsingAlias() { } @HapiTest - final HapiSpec autoAccountCreationUnsupportedAlias() { + final Stream autoAccountCreationUnsupportedAlias() { final var threshKeyAlias = Key.newBuilder() .setThresholdKey(ThresholdKey.newBuilder() .setThreshold(2) @@ -1222,7 +1184,7 @@ final HapiSpec autoAccountCreationUnsupportedAlias() { } @HapiTest - final HapiSpec autoAccountCreationBadAlias() { + final Stream autoAccountCreationBadAlias() { final var invalidAlias = VALID_25519_ALIAS.substring(0, 10); return defaultHapiSpec("autoAccountCreationBadAlias") @@ -1234,7 +1196,7 @@ final HapiSpec autoAccountCreationBadAlias() { } @HapiTest - final HapiSpec autoAccountCreationsHappyPath() { + final Stream autoAccountCreationsHappyPath() { final var creationTime = new AtomicLong(); final long transferFee = 185030L; return defaultHapiSpec("autoAccountCreationsHappyPath", NONDETERMINISTIC_TRANSACTION_FEES) @@ -1342,7 +1304,7 @@ private void assertAliasBalanceAndFeeInChildRecord( } @HapiTest - final HapiSpec multipleAutoAccountCreations() { + final Stream multipleAutoAccountCreations() { return defaultHapiSpec("multipleAutoAccountCreations") .given(cryptoCreate(PAYER).balance(INITIAL_BALANCE * ONE_HBAR)) .when( @@ -1375,7 +1337,7 @@ final HapiSpec multipleAutoAccountCreations() { } @HapiTest - final HapiSpec transferHbarsToEVMAddressAlias() { + final Stream transferHbarsToEVMAddressAlias() { final AtomicReference partyId = new AtomicReference<>(); final AtomicReference partyAlias = new AtomicReference<>(); @@ -1432,7 +1394,7 @@ final HapiSpec transferHbarsToEVMAddressAlias() { } @HapiTest - final HapiSpec transferHbarsToECDSAKey() { + final Stream transferHbarsToECDSAKey() { final AtomicReference evmAddress = new AtomicReference<>(); final var transferToECDSA = "transferToЕCDSA"; @@ -1479,7 +1441,7 @@ final HapiSpec transferHbarsToECDSAKey() { } @HapiTest - final HapiSpec transferFungibleToEVMAddressAlias() { + final Stream transferFungibleToEVMAddressAlias() { final var fungibleToken = "fungibleToken"; final AtomicReference ftId = new AtomicReference<>(); @@ -1575,7 +1537,7 @@ final HapiSpec transferFungibleToEVMAddressAlias() { } @HapiTest - final HapiSpec transferNonFungibleToEVMAddressAlias() { + final Stream transferNonFungibleToEVMAddressAlias() { final var nonFungibleToken = "nonFungibleToken"; final AtomicReference nftId = new AtomicReference<>(); final AtomicReference partyId = new AtomicReference<>(); @@ -1674,7 +1636,7 @@ final HapiSpec transferNonFungibleToEVMAddressAlias() { } @HapiTest - final HapiSpec cannotAutoCreateWithTxnToLongZero() { + final Stream cannotAutoCreateWithTxnToLongZero() { final AtomicReference evmAddress = new AtomicReference<>(); final var longZeroAddress = ByteString.copyFrom(CommonUtils.unhex("0000000000000000000000000000000fffffffff")); diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/crypto/AutoAccountUpdateSuite.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/crypto/AutoAccountUpdateSuite.java index 385d5f75a157..b89298300551 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/crypto/AutoAccountUpdateSuite.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/crypto/AutoAccountUpdateSuite.java @@ -30,18 +30,18 @@ import static com.hedera.services.bdd.spec.utilops.UtilVerbs.newKeyNamed; import static com.hedera.services.bdd.spec.utilops.UtilVerbs.withOpContext; import static com.hedera.services.bdd.spec.utilops.records.SnapshotMatchMode.NONDETERMINISTIC_TRANSACTION_FEES; +import static com.hedera.services.bdd.suites.HapiSuite.DEFAULT_PAYER; +import static com.hedera.services.bdd.suites.HapiSuite.ONE_HBAR; +import static com.hedera.services.bdd.suites.HapiSuite.ONE_HUNDRED_HBARS; +import static com.hedera.services.bdd.suites.HapiSuite.THREE_MONTHS_IN_SECONDS; import static com.hedera.services.bdd.suites.crypto.AutoCreateUtils.updateSpecFor; import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.INVALID_SIGNATURE; import com.hedera.services.bdd.junit.HapiTest; -import com.hedera.services.bdd.junit.HapiTestSuite; -import com.hedera.services.bdd.spec.HapiSpec; import com.hedera.services.bdd.spec.keys.KeyShape; import com.hedera.services.bdd.spec.keys.SigControl; -import com.hedera.services.bdd.suites.HapiSuite; -import java.util.List; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; +import java.util.stream.Stream; +import org.junit.jupiter.api.DynamicTest; import org.junit.jupiter.api.Tag; /** @@ -50,10 +50,8 @@ * to decrease the expiration time of any entity, so we cannot test the behavior of the network when * the auto-created account is about to expire. */ -@HapiTestSuite @Tag(CRYPTO) -public class AutoAccountUpdateSuite extends HapiSuite { - private static final Logger log = LogManager.getLogger(AutoAccountUpdateSuite.class); +public class AutoAccountUpdateSuite { public static final long INITIAL_BALANCE = 1000L; private static final String PAYER = "payer"; @@ -62,27 +60,8 @@ public class AutoAccountUpdateSuite extends HapiSuite { public static final String TRANSFER_TXN_2 = "transferTxn2"; private static final String TRANSFER_TXN_3 = "transferTxn3"; - public static void main(String... args) { - new AutoAccountUpdateSuite().runSuiteSync(); - } - - @Override - protected Logger getResultsLogger() { - return log; - } - - @Override - public List getSpecsInSuite() { - return List.of(updateKeyOnAutoCreatedAccount(), modifySigRequiredAfterAutoAccountCreation()); - } - - @Override - public boolean canRunConcurrent() { - return true; - } - @HapiTest - final HapiSpec modifySigRequiredAfterAutoAccountCreation() { + final Stream modifySigRequiredAfterAutoAccountCreation() { return defaultHapiSpec("modifySigRequiredAfterAutoAccountCreation") .given(newKeyNamed(ALIAS), cryptoCreate(PAYER).balance(INITIAL_BALANCE * ONE_HBAR)) .when( @@ -125,7 +104,7 @@ final HapiSpec modifySigRequiredAfterAutoAccountCreation() { } @HapiTest - final HapiSpec updateKeyOnAutoCreatedAccount() { + final Stream updateKeyOnAutoCreatedAccount() { final var complexKey = "complexKey"; SigControl ENOUGH_UNIQUE_SIGS = KeyShape.threshSigs( diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/crypto/CryptoApproveAllowanceSuite.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/crypto/CryptoApproveAllowanceSuite.java index bb914b277923..bfb82b444ecd 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/crypto/CryptoApproveAllowanceSuite.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/crypto/CryptoApproveAllowanceSuite.java @@ -54,6 +54,12 @@ import static com.hedera.services.bdd.spec.utilops.UtilVerbs.validateChargedUsdWithin; import static com.hedera.services.bdd.spec.utilops.records.SnapshotMatchMode.EXPECT_STREAMLINED_INGEST_RECORDS; import static com.hedera.services.bdd.spec.utilops.records.SnapshotMatchMode.NONDETERMINISTIC_TRANSACTION_FEES; +import static com.hedera.services.bdd.suites.HapiSuite.DEFAULT_PAYER; +import static com.hedera.services.bdd.suites.HapiSuite.FUNDING; +import static com.hedera.services.bdd.suites.HapiSuite.GENESIS; +import static com.hedera.services.bdd.suites.HapiSuite.ONE_HBAR; +import static com.hedera.services.bdd.suites.HapiSuite.ONE_HUNDRED_HBARS; +import static com.hedera.services.bdd.suites.HapiSuite.TOKEN_TREASURY; import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.AMOUNT_EXCEEDS_TOKEN_MAX_SUPPLY; import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.DELEGATING_SPENDER_CANNOT_GRANT_APPROVE_FOR_ALL; import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.DELEGATING_SPENDER_DOES_NOT_HAVE_APPROVE_FOR_ALL; @@ -77,25 +83,18 @@ import com.google.protobuf.ByteString; import com.hedera.services.bdd.junit.HapiTest; -import com.hedera.services.bdd.junit.HapiTestSuite; -import com.hedera.services.bdd.spec.HapiSpec; -import com.hedera.services.bdd.suites.HapiSuite; import com.hederahashgraph.api.proto.java.NftTransfer; import com.hederahashgraph.api.proto.java.TokenSupplyType; import com.hederahashgraph.api.proto.java.TokenTransferList; import com.hederahashgraph.api.proto.java.TokenType; import java.util.List; import java.util.concurrent.atomic.AtomicLong; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; +import java.util.stream.Stream; +import org.junit.jupiter.api.DynamicTest; import org.junit.jupiter.api.Tag; -@HapiTestSuite @Tag(CRYPTO) -public class CryptoApproveAllowanceSuite extends HapiSuite { - - private static final Logger log = LogManager.getLogger(CryptoApproveAllowanceSuite.class); - +public class CryptoApproveAllowanceSuite { public static final String OWNER = "owner"; public static final String SPENDER = "spender"; private static final String RECEIVER = "receiver"; @@ -104,7 +103,6 @@ public class CryptoApproveAllowanceSuite extends HapiSuite { public static final String NON_FUNGIBLE_TOKEN = "nonFungible"; public static final String TOKEN_WITH_CUSTOM_FEE = "tokenWithCustomFee"; private static final String SUPPLY_KEY = "supplyKey"; - private static final String SENDER_TXN = "senderTxn"; public static final String SCHEDULED_TXN = "scheduledTxn"; public static final String NFT_TOKEN_MINT_TXN = "nftTokenMint"; public static final String FUNGIBLE_TOKEN_MINT_TXN = "tokenMint"; @@ -122,49 +120,8 @@ public class CryptoApproveAllowanceSuite extends HapiSuite { public static final String KYC_KEY = "kycKey"; public static final String PAUSE_KEY = "pauseKey"; - public static void main(String... args) { - new CryptoApproveAllowanceSuite().runSuiteAsync(); - } - - @Override - public boolean canRunConcurrent() { - return true; - } - - @Override - @SuppressWarnings("java:S3878") - public List getSpecsInSuite() { - return List.of( - canHaveMultipleOwners(), - noOwnerDefaultsToPayer(), - invalidSpenderFails(), - invalidOwnerFails(), - happyPathWorks(), - emptyAllowancesRejected(), - negativeAmountFailsForFungible(), - tokenNotAssociatedToAccountFails(), - invalidTokenTypeFails(), - validatesSerialNums(), - tokenExceedsMaxSupplyFails(), - succeedsWhenTokenPausedFrozenKycRevoked(), - serialsInAscendingOrder(), - feesAsExpected(), - cannotHaveMultipleAllowedSpendersForTheSameNFTSerial(), - approveForAllDoesNotSetExplicitNFTSpender(), - canGrantNftAllowancesWithTreasuryOwner(), - canGrantFungibleAllowancesWithTreasuryOwner(), - approveForAllSpenderCanDelegateOnNFT(), - duplicateEntriesGetsReplacedWithDifferentTxn(), - duplicateKeysAndSerialsInSameTxnDoesntThrow(), - scheduledCryptoApproveAllowanceWorks(), - canDeleteAllowanceFromDeletedSpender(), - cannotPayForAnyTransactionWithContractAccount(), - transferringMissingNftViaApprovalFailsWithInvalidNftId(), - approveNegativeCases()); - } - @HapiTest - final HapiSpec cannotPayForAnyTransactionWithContractAccount() { + final Stream cannotPayForAnyTransactionWithContractAccount() { final var cryptoAdminKey = "cryptoAdminKey"; final var contractNum = new AtomicLong(); final var contract = "PayableConstructor"; @@ -178,14 +135,14 @@ final HapiSpec cannotPayForAnyTransactionWithContractAccount() { .exposingNumTo(contractNum::set)) .when() .then(sourcing(() -> cryptoTransfer(tinyBarsFromTo(contract, FUNDING, 1)) - .signedBy(cryptoAdminKey) .fee(ONE_HBAR) .payingWith("0.0." + contractNum.longValue()) + .signedBy(cryptoAdminKey) .hasPrecheck(PAYER_ACCOUNT_NOT_FOUND))); } @HapiTest - final HapiSpec transferringMissingNftViaApprovalFailsWithInvalidNftId() { + final Stream transferringMissingNftViaApprovalFailsWithInvalidNftId() { return defaultHapiSpec("TransferringMissingNftViaApprovalFailsWithInvalidNftId") .given( newKeyNamed(SUPPLY_KEY), @@ -225,7 +182,7 @@ final HapiSpec transferringMissingNftViaApprovalFailsWithInvalidNftId() { } @HapiTest - final HapiSpec canDeleteAllowanceFromDeletedSpender() { + final Stream canDeleteAllowanceFromDeletedSpender() { return defaultHapiSpec("canDeleteAllowanceFromDeletedSpender", NONDETERMINISTIC_TRANSACTION_FEES) .given( newKeyNamed(SUPPLY_KEY), @@ -322,7 +279,7 @@ final HapiSpec canDeleteAllowanceFromDeletedSpender() { } @HapiTest - final HapiSpec duplicateKeysAndSerialsInSameTxnDoesntThrow() { + final Stream duplicateKeysAndSerialsInSameTxnDoesntThrow() { return defaultHapiSpec("duplicateKeysAndSerialsInSameTxnDoesntThrow", NONDETERMINISTIC_TRANSACTION_FEES) .given( newKeyNamed(SUPPLY_KEY), @@ -408,7 +365,7 @@ final HapiSpec duplicateKeysAndSerialsInSameTxnDoesntThrow() { } @HapiTest - final HapiSpec approveForAllSpenderCanDelegateOnNFT() { + final Stream approveForAllSpenderCanDelegateOnNFT() { final String delegatingSpender = "delegatingSpender"; final String newSpender = "newSpender"; return defaultHapiSpec("approveForAllSpenderCanDelegateOnNFT", NONDETERMINISTIC_TRANSACTION_FEES) @@ -472,7 +429,7 @@ final HapiSpec approveForAllSpenderCanDelegateOnNFT() { } @HapiTest - final HapiSpec canGrantFungibleAllowancesWithTreasuryOwner() { + final Stream canGrantFungibleAllowancesWithTreasuryOwner() { return defaultHapiSpec("canGrantFungibleAllowancesWithTreasuryOwner", NONDETERMINISTIC_TRANSACTION_FEES) .given( newKeyNamed(SUPPLY_KEY), @@ -507,7 +464,7 @@ final HapiSpec canGrantFungibleAllowancesWithTreasuryOwner() { } @HapiTest - final HapiSpec canGrantNftAllowancesWithTreasuryOwner() { + final Stream canGrantNftAllowancesWithTreasuryOwner() { return defaultHapiSpec("canGrantNftAllowancesWithTreasuryOwner", NONDETERMINISTIC_TRANSACTION_FEES) .given( newKeyNamed(SUPPLY_KEY), @@ -551,7 +508,7 @@ final HapiSpec canGrantNftAllowancesWithTreasuryOwner() { } @HapiTest - final HapiSpec invalidOwnerFails() { + final Stream invalidOwnerFails() { return defaultHapiSpec("invalidOwnerFails", NONDETERMINISTIC_TRANSACTION_FEES) .given( newKeyNamed(SUPPLY_KEY), @@ -615,7 +572,7 @@ final HapiSpec invalidOwnerFails() { } @HapiTest - final HapiSpec invalidSpenderFails() { + final Stream invalidSpenderFails() { return defaultHapiSpec("invalidSpenderFails") .given( newKeyNamed(SUPPLY_KEY), @@ -672,7 +629,7 @@ final HapiSpec invalidSpenderFails() { } @HapiTest - final HapiSpec noOwnerDefaultsToPayer() { + final Stream noOwnerDefaultsToPayer() { return defaultHapiSpec("noOwnerDefaultsToPayer", NONDETERMINISTIC_TRANSACTION_FEES) .given( newKeyNamed(SUPPLY_KEY), @@ -731,7 +688,7 @@ final HapiSpec noOwnerDefaultsToPayer() { } @HapiTest - final HapiSpec canHaveMultipleOwners() { + final Stream canHaveMultipleOwners() { return defaultHapiSpec("canHaveMultipleOwners", NONDETERMINISTIC_TRANSACTION_FEES) .given( newKeyNamed(SUPPLY_KEY), @@ -826,7 +783,7 @@ final HapiSpec canHaveMultipleOwners() { } @HapiTest - final HapiSpec feesAsExpected() { + final Stream feesAsExpected() { return defaultHapiSpec("feesAsExpected", NONDETERMINISTIC_TRANSACTION_FEES) .given( newKeyNamed(SUPPLY_KEY), @@ -951,7 +908,7 @@ final HapiSpec feesAsExpected() { } @HapiTest - final HapiSpec serialsInAscendingOrder() { + final Stream serialsInAscendingOrder() { return defaultHapiSpec("serialsInAscendingOrder") .given( newKeyNamed(SUPPLY_KEY), @@ -997,7 +954,7 @@ final HapiSpec serialsInAscendingOrder() { } @HapiTest - final HapiSpec succeedsWhenTokenPausedFrozenKycRevoked() { + final Stream succeedsWhenTokenPausedFrozenKycRevoked() { return defaultHapiSpec("succeedsWhenTokenPausedFrozenKycRevoked", NONDETERMINISTIC_TRANSACTION_FEES) .given( newKeyNamed(SUPPLY_KEY), @@ -1088,7 +1045,7 @@ final HapiSpec succeedsWhenTokenPausedFrozenKycRevoked() { } @HapiTest - final HapiSpec tokenExceedsMaxSupplyFails() { + final Stream tokenExceedsMaxSupplyFails() { return defaultHapiSpec("tokenExceedsMaxSupplyFails", NONDETERMINISTIC_TRANSACTION_FEES) .given( newKeyNamed(SUPPLY_KEY), @@ -1115,7 +1072,7 @@ final HapiSpec tokenExceedsMaxSupplyFails() { } @HapiTest - final HapiSpec validatesSerialNums() { + final Stream validatesSerialNums() { return defaultHapiSpec("validatesSerialNums") .given( newKeyNamed(SUPPLY_KEY), @@ -1164,7 +1121,7 @@ final HapiSpec validatesSerialNums() { } @HapiTest - final HapiSpec invalidTokenTypeFails() { + final Stream invalidTokenTypeFails() { return defaultHapiSpec("invalidTokenTypeFails") .given( newKeyNamed(SUPPLY_KEY), @@ -1214,7 +1171,7 @@ final HapiSpec invalidTokenTypeFails() { } @HapiTest - final HapiSpec emptyAllowancesRejected() { + final Stream emptyAllowancesRejected() { return defaultHapiSpec("emptyAllowancesRejected") .given(cryptoCreate(OWNER).balance(ONE_HUNDRED_HBARS).maxAutomaticTokenAssociations(10)) .when(cryptoApproveAllowance().hasPrecheck(EMPTY_ALLOWANCES).fee(ONE_HUNDRED_HBARS)) @@ -1222,7 +1179,7 @@ final HapiSpec emptyAllowancesRejected() { } @HapiTest - final HapiSpec tokenNotAssociatedToAccountFails() { + final Stream tokenNotAssociatedToAccountFails() { return defaultHapiSpec("tokenNotAssociatedToAccountFails") .given( newKeyNamed(SUPPLY_KEY), @@ -1273,7 +1230,7 @@ final HapiSpec tokenNotAssociatedToAccountFails() { } @HapiTest - final HapiSpec negativeAmountFailsForFungible() { + final Stream negativeAmountFailsForFungible() { return defaultHapiSpec("negativeAmountFailsForFungible") .given( newKeyNamed(SUPPLY_KEY), @@ -1328,7 +1285,7 @@ final HapiSpec negativeAmountFailsForFungible() { } @HapiTest - final HapiSpec happyPathWorks() { + final Stream happyPathWorks() { return defaultHapiSpec("happyPathWorks", NONDETERMINISTIC_TRANSACTION_FEES) .given( newKeyNamed(SUPPLY_KEY), @@ -1394,7 +1351,7 @@ final HapiSpec happyPathWorks() { } @HapiTest - final HapiSpec duplicateEntriesGetsReplacedWithDifferentTxn() { + final Stream duplicateEntriesGetsReplacedWithDifferentTxn() { return defaultHapiSpec("duplicateEntriesGetsReplacedWithDifferentTxn", NONDETERMINISTIC_TRANSACTION_FEES) .given( newKeyNamed(SUPPLY_KEY), @@ -1486,7 +1443,7 @@ final HapiSpec duplicateEntriesGetsReplacedWithDifferentTxn() { } @HapiTest - final HapiSpec cannotHaveMultipleAllowedSpendersForTheSameNFTSerial() { + final Stream cannotHaveMultipleAllowedSpendersForTheSameNFTSerial() { return defaultHapiSpec( "CannotHaveMultipleAllowedSpendersForTheSameNFTSerial", NONDETERMINISTIC_TRANSACTION_FEES) .given( @@ -1566,7 +1523,7 @@ final HapiSpec cannotHaveMultipleAllowedSpendersForTheSameNFTSerial() { } @HapiTest - final HapiSpec approveForAllDoesNotSetExplicitNFTSpender() { + final Stream approveForAllDoesNotSetExplicitNFTSpender() { return defaultHapiSpec("approveForAllDoesNotSetExplicitNFTSpender", NONDETERMINISTIC_TRANSACTION_FEES) .given( newKeyNamed(SUPPLY_KEY), @@ -1606,7 +1563,7 @@ final HapiSpec approveForAllDoesNotSetExplicitNFTSpender() { } @HapiTest - final HapiSpec scheduledCryptoApproveAllowanceWorks() { + final Stream scheduledCryptoApproveAllowanceWorks() { return defaultHapiSpec("ScheduledCryptoApproveAllowanceWorks", NONDETERMINISTIC_TRANSACTION_FEES) .given( newKeyNamed(SUPPLY_KEY), @@ -1683,7 +1640,7 @@ final HapiSpec scheduledCryptoApproveAllowanceWorks() { } @HapiTest - final HapiSpec approveNegativeCases() { + final Stream approveNegativeCases() { final var tryApprovingTheSender = "tryApprovingTheSender"; final var tryApprovingAboveBalance = "tryApprovingAboveBalance"; final var tryApprovingNFTToOwner = "tryApprovingNFTToOwner"; @@ -1756,9 +1713,4 @@ final HapiSpec approveNegativeCases() { getAccountBalance(OWNER).hasTokenBalance(NON_FUNGIBLE_TOKEN, 3L), getAccountBalance(SPENDER).hasTokenBalance(NON_FUNGIBLE_TOKEN, 0L)); } - - @Override - protected Logger getResultsLogger() { - return log; - } } diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/crypto/CryptoCornerCasesSuite.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/crypto/CryptoCornerCasesSuite.java index f8271e88d476..b6cd70e8e5d8 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/crypto/CryptoCornerCasesSuite.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/crypto/CryptoCornerCasesSuite.java @@ -28,42 +28,21 @@ import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.PAYER_ACCOUNT_NOT_FOUND; import com.hedera.services.bdd.junit.HapiTest; -import com.hedera.services.bdd.junit.HapiTestSuite; -import com.hedera.services.bdd.spec.HapiSpec; import com.hedera.services.bdd.spec.HapiSpecSetup; import com.hedera.services.bdd.spec.transactions.TxnUtils; -import com.hedera.services.bdd.suites.HapiSuite; import com.hederahashgraph.api.proto.java.AccountID; import com.hederahashgraph.api.proto.java.Transaction; import java.time.Clock; import java.time.Instant; -import java.util.List; +import java.util.stream.Stream; import org.apache.commons.lang3.RandomStringUtils; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; +import org.junit.jupiter.api.DynamicTest; import org.junit.jupiter.api.Tag; -@HapiTestSuite @Tag(CRYPTO) -public class CryptoCornerCasesSuite extends HapiSuite { - private static final Logger log = LogManager.getLogger(CryptoCornerCasesSuite.class); +public class CryptoCornerCasesSuite { private static final String NEW_PAYEE = "newPayee"; - public static void main(String... args) { - new CryptoCornerCasesSuite().runSuiteSync(); - } - - @Override - public List getSpecsInSuite() { - return List.of( - invalidNodeAccount(), - invalidTransactionBody(), - invalidTransactionPayerAccountNotFound(), - invalidTransactionMemoTooLong(), - invalidTransactionDuration(), - invalidTransactionStartTime()); - } - private static Transaction removeTransactionBody(Transaction txn) { return txn.toBuilder() .setBodyBytes(Transaction.getDefaultInstance().getBodyBytes()) @@ -71,7 +50,7 @@ private static Transaction removeTransactionBody(Transaction txn) { } @HapiTest - final HapiSpec invalidTransactionBody() { + final Stream invalidTransactionBody() { return defaultHapiSpec("InvalidTransactionBody") .given() .when() @@ -93,7 +72,7 @@ private static Transaction replaceTxnNodeAccount(Transaction txn) { } @HapiTest - final HapiSpec invalidNodeAccount() { + final Stream invalidNodeAccount() { return defaultHapiSpec("InvalidNodeAccount") .given() .when() @@ -108,7 +87,7 @@ private static Transaction replaceTxnDuration(Transaction txn) { } @HapiTest - final HapiSpec invalidTransactionDuration() { + final Stream invalidTransactionDuration() { return defaultHapiSpec("InvalidTransactionDuration") .given() .when() @@ -124,7 +103,7 @@ private static Transaction replaceTxnMemo(Transaction txn) { } @HapiTest - final HapiSpec invalidTransactionMemoTooLong() { + final Stream invalidTransactionMemoTooLong() { return defaultHapiSpec("InvalidTransactionMemoTooLong") .given() .when() @@ -144,7 +123,7 @@ private static Transaction replaceTxnPayerAccount(Transaction txn) { } @HapiTest - final HapiSpec invalidTransactionPayerAccountNotFound() { + final Stream invalidTransactionPayerAccountNotFound() { return defaultHapiSpec("InvalidTransactionDuration") .given() .when() @@ -160,7 +139,7 @@ private static Transaction replaceTxnStartTtime(Transaction txn) { } @HapiTest - final HapiSpec invalidTransactionStartTime() { + final Stream invalidTransactionStartTime() { return defaultHapiSpec("InvalidTransactionStartTime") .given() .when() @@ -169,9 +148,4 @@ final HapiSpec invalidTransactionStartTime() { .withTxnTransform(CryptoCornerCasesSuite::replaceTxnStartTtime) .hasPrecheckFrom(INVALID_TRANSACTION_START, INVALID_TRANSACTION)); } - - @Override - protected Logger getResultsLogger() { - return log; - } } diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/crypto/CryptoCreateForSuiteRunner.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/crypto/CryptoCreateForSuiteRunner.java index 7a7e8f83b0a0..e86201f3357d 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/crypto/CryptoCreateForSuiteRunner.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/crypto/CryptoCreateForSuiteRunner.java @@ -25,16 +25,17 @@ import static com.hedera.services.bdd.spec.utilops.UtilVerbs.withOpContext; import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.SUCCESS; -import com.hedera.services.bdd.spec.HapiSpec; import com.hedera.services.bdd.spec.utilops.LoadTest; import com.hedera.services.bdd.suites.HapiSuite; import com.hedera.services.bdd.suites.SuiteRunner; import com.hederahashgraph.api.proto.java.AccountID; import java.util.List; import java.util.Map; +import java.util.stream.Stream; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.DynamicTest; /** * When running JRS regression tests using SuiteRunner we need to create unique payer accounts for @@ -64,12 +65,12 @@ public boolean getDeferResultsSummary() { } @Override - public List getSpecsInSuite() { + public List> getSpecsInSuite() { return List.of(createAccount()); } @SuppressWarnings({"java:S5960", "java:S1141", "java:S1135"}) - final HapiSpec createAccount() { + final Stream createAccount() { int maxRetries = 5; return customHapiSpec("CreatePayerAccountForEachClient") .withProperties(Map.of("nodes", nodes, "default.node", "0.0." + defaultNode)) diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/crypto/CryptoCreateSuite.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/crypto/CryptoCreateSuite.java index f283c7a88204..f26c263df388 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/crypto/CryptoCreateSuite.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/crypto/CryptoCreateSuite.java @@ -41,6 +41,13 @@ import static com.hedera.services.bdd.spec.utilops.UtilVerbs.validateChargedUsd; import static com.hedera.services.bdd.spec.utilops.UtilVerbs.withOpContext; import static com.hedera.services.bdd.spec.utilops.mod.ModificationUtils.withSuccessivelyVariedBodyIds; +import static com.hedera.services.bdd.suites.HapiSuite.GENESIS; +import static com.hedera.services.bdd.suites.HapiSuite.ONE_HBAR; +import static com.hedera.services.bdd.suites.HapiSuite.ONE_HUNDRED_HBARS; +import static com.hedera.services.bdd.suites.HapiSuite.SECP_256K1_SHAPE; +import static com.hedera.services.bdd.suites.HapiSuite.SECP_256K1_SOURCE_KEY; +import static com.hedera.services.bdd.suites.HapiSuite.THREE_MONTHS_IN_SECONDS; +import static com.hedera.services.bdd.suites.HapiSuite.ZERO_BYTE_MEMO; import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.ALIAS_ALREADY_ASSIGNED; import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.AUTORENEW_DURATION_NOT_IN_RANGE; import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.BAD_ENCODING; @@ -55,26 +62,20 @@ import com.google.protobuf.ByteString; import com.hedera.services.bdd.junit.HapiTest; -import com.hedera.services.bdd.junit.HapiTestSuite; -import com.hedera.services.bdd.spec.HapiSpec; import com.hedera.services.bdd.spec.keys.KeyShape; -import com.hedera.services.bdd.suites.HapiSuite; import com.hederahashgraph.api.proto.java.Key; import com.hederahashgraph.api.proto.java.KeyList; +import com.hederahashgraph.api.proto.java.RealmID; +import com.hederahashgraph.api.proto.java.ShardID; import com.hederahashgraph.api.proto.java.ThresholdKey; import com.swirlds.common.utility.CommonUtils; -import java.util.List; import java.util.concurrent.atomic.AtomicReference; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; +import java.util.stream.Stream; +import org.junit.jupiter.api.DynamicTest; import org.junit.jupiter.api.Tag; -@HapiTestSuite @Tag(CRYPTO) -public class CryptoCreateSuite extends HapiSuite { - - private static final Logger log = LogManager.getLogger(CryptoCreateSuite.class); - +public class CryptoCreateSuite { public static final String ACCOUNT = "account"; public static final String ANOTHER_ACCOUNT = "anotherAccount"; public static final String ED_25519_KEY = "ed25519Alias"; @@ -86,47 +87,8 @@ public class CryptoCreateSuite extends HapiSuite { public static final String EMPTY_KEY_STRING = "emptyKey"; private static final String ED_KEY = "EDKEY"; - public static void main(String... args) { - new CryptoCreateSuite().runSuiteAsync(); - } - - @Override - public boolean canRunConcurrent() { - return true; - } - - @Override - public List getSpecsInSuite() { - return List.of( - createAnAccountEmptyThresholdKey(), - createAnAccountEmptyKeyList(), - createAnAccountEmptyNestedKey(), - createAnAccountInvalidKeyList(), - createAnAccountInvalidNestedKeyList(), - createAnAccountInvalidThresholdKey(), - createAnAccountInvalidNestedThresholdKey(), - createAnAccountThresholdKeyWithInvalidThreshold(), - createAnAccountInvalidED25519(), - syntaxChecksAreAsExpected(), - usdFeeAsExpected(), - createAnAccountWithStakingFields(), - /* --- HIP-583 --- */ - createAnAccountWithECDSAAlias(), - createAnAccountWithED25519Alias(), - createAnAccountWithECKeyAndNoAlias(), - createAnAccountWithEDKeyAndNoAlias(), - createAnAccountWithED25519KeyAndED25519Alias(), - createAnAccountWithECKeyAndECKeyAlias(), - createAnAccountWithEVMAddressAliasFromSameKey(), - createAnAccountWithEVMAddressAliasFromDifferentKey(), - createAnAccountWithECDSAKeyAliasDifferentThanAdminKeyShouldFail(), - createAnAccountWithEDKeyAliasDifferentThanAdminKeyShouldFail(), - cannotCreateAnAccountWithLongZeroKeyButCanUseEvmAddress(), - successfullyRecreateAccountWithSameAliasAfterDeletion()); - } - @HapiTest - public HapiSpec idVariantsTreatedAsExpected() { + final Stream idVariantsTreatedAsExpected() { return defaultHapiSpec("idVariantsTreatedAsExpected") .given() .when() @@ -135,7 +97,7 @@ public HapiSpec idVariantsTreatedAsExpected() { } @HapiTest - final HapiSpec createAnAccountWithStakingFields() { + final Stream createAnAccountWithStakingFields() { return defaultHapiSpec("createAnAccountWithStakingFields") .given( cryptoCreate("civilianWORewardStakingNode") @@ -190,7 +152,7 @@ final HapiSpec createAnAccountWithStakingFields() { } @HapiTest - final HapiSpec cannotCreateAnAccountWithLongZeroKeyButCanUseEvmAddress() { + final Stream cannotCreateAnAccountWithLongZeroKeyButCanUseEvmAddress() { final AtomicReference secp256k1Key = new AtomicReference<>(); final AtomicReference evmAddress = new AtomicReference<>(); final var ecdsaKey = "ecdsaKey"; @@ -216,7 +178,7 @@ final HapiSpec cannotCreateAnAccountWithLongZeroKeyButCanUseEvmAddress() { /* Prior to 0.13.0, a "canonical" CryptoCreate (one sig, 3 month auto-renew) cost 1¢. */ @HapiTest - final HapiSpec usdFeeAsExpected() { + final Stream usdFeeAsExpected() { double preV13PriceUsd = 0.01; double v13PriceUsd = 0.05; double autoAssocSlotPrice = 0.0018; @@ -277,7 +239,7 @@ final HapiSpec usdFeeAsExpected() { } @HapiTest - public HapiSpec syntaxChecksAreAsExpected() { + final Stream syntaxChecksAreAsExpected() { return defaultHapiSpec("SyntaxChecksAreAsExpected") .given() .when() @@ -287,7 +249,7 @@ public HapiSpec syntaxChecksAreAsExpected() { } @HapiTest - final HapiSpec createAnAccountEmptyThresholdKey() { + final Stream createAnAccountEmptyThresholdKey() { KeyShape shape = threshOf(0, 0); long initialBalance = 10_000L; @@ -302,9 +264,11 @@ final HapiSpec createAnAccountEmptyThresholdKey() { } @HapiTest - final HapiSpec createAnAccountEmptyKeyList() { + final Stream createAnAccountEmptyKeyList() { KeyShape shape = listOf(0); long initialBalance = 10_000L; + ShardID shardID = ShardID.newBuilder().build(); + RealmID realmID = RealmID.newBuilder().build(); return defaultHapiSpec("createAnAccountEmptyKeyList") .given() @@ -313,6 +277,8 @@ final HapiSpec createAnAccountEmptyKeyList() { cryptoCreate(NO_KEYS) .keyShape(shape) .balance(initialBalance) + .shardId(shardID) + .realmId(realmID) .logged() .hasPrecheck(KEY_REQUIRED) // In modular code this error is thrown in handle, but it is fixed using dynamic property @@ -322,7 +288,7 @@ final HapiSpec createAnAccountEmptyKeyList() { } @HapiTest - final HapiSpec createAnAccountEmptyNestedKey() { + final Stream createAnAccountEmptyNestedKey() { KeyShape emptyThresholdShape = threshOf(0, 0); KeyShape emptyListShape = listOf(0); KeyShape shape = threshOf(2, emptyThresholdShape, emptyListShape); @@ -340,7 +306,7 @@ final HapiSpec createAnAccountEmptyNestedKey() { // One of element in key list is not valid @HapiTest - final HapiSpec createAnAccountInvalidKeyList() { + final Stream createAnAccountInvalidKeyList() { KeyShape emptyThresholdShape = threshOf(0, 0); KeyShape shape = listOf(SIMPLE, SIMPLE, emptyThresholdShape); long initialBalance = 10_000L; @@ -357,7 +323,7 @@ final HapiSpec createAnAccountInvalidKeyList() { // One of element in nested key list is not valid @HapiTest - final HapiSpec createAnAccountInvalidNestedKeyList() { + final Stream createAnAccountInvalidNestedKeyList() { KeyShape invalidListShape = listOf(SIMPLE, SIMPLE, listOf(0)); KeyShape shape = listOf(SIMPLE, SIMPLE, invalidListShape); long initialBalance = 10_000L; @@ -374,7 +340,7 @@ final HapiSpec createAnAccountInvalidNestedKeyList() { // One of element in threshold key is not valid @HapiTest - final HapiSpec createAnAccountInvalidThresholdKey() { + final Stream createAnAccountInvalidThresholdKey() { KeyShape emptyListShape = listOf(0); KeyShape thresholdShape = threshOf(1, SIMPLE, SIMPLE, emptyListShape); long initialBalance = 10_000L; @@ -431,7 +397,7 @@ final HapiSpec createAnAccountInvalidThresholdKey() { } @HapiTest - final HapiSpec createAnAccountInvalidNestedThresholdKey() { + final Stream createAnAccountInvalidNestedThresholdKey() { KeyShape goodShape = threshOf(2, 3); KeyShape thresholdShape0 = threshOf(0, SIMPLE, SIMPLE, SIMPLE); KeyShape thresholdShape4 = threshOf(4, SIMPLE, SIMPLE, SIMPLE); @@ -460,7 +426,7 @@ final HapiSpec createAnAccountInvalidNestedThresholdKey() { } @HapiTest - final HapiSpec createAnAccountThresholdKeyWithInvalidThreshold() { + final Stream createAnAccountThresholdKeyWithInvalidThreshold() { KeyShape thresholdShape0 = threshOf(0, SIMPLE, SIMPLE, SIMPLE); KeyShape thresholdShape4 = threshOf(4, SIMPLE, SIMPLE, SIMPLE); @@ -483,7 +449,7 @@ final HapiSpec createAnAccountThresholdKeyWithInvalidThreshold() { } @HapiTest - final HapiSpec createAnAccountInvalidED25519() { + final Stream createAnAccountInvalidED25519() { long initialBalance = 10_000L; Key emptyKey = Key.newBuilder().setEd25519(ByteString.EMPTY).build(); Key shortKey = @@ -515,7 +481,7 @@ final HapiSpec createAnAccountInvalidED25519() { } @HapiTest - final HapiSpec createAnAccountWithECDSAAlias() { + final Stream createAnAccountWithECDSAAlias() { return defaultHapiSpec("CreateAnAccountWithECDSAAlias") .given(newKeyNamed(SECP_256K1_SOURCE_KEY).shape(SECP_256K1_SHAPE)) .when(withOpContext((spec, opLog) -> { @@ -531,7 +497,7 @@ final HapiSpec createAnAccountWithECDSAAlias() { } @HapiTest - final HapiSpec createAnAccountWithED25519Alias() { + final Stream createAnAccountWithED25519Alias() { return defaultHapiSpec("CreateAnAccountWithED25519Alias") .given(newKeyNamed(ED_25519_KEY).shape(KeyShape.ED25519)) .when(withOpContext((spec, opLog) -> { @@ -547,7 +513,7 @@ final HapiSpec createAnAccountWithED25519Alias() { } @HapiTest - final HapiSpec createAnAccountWithECKeyAndNoAlias() { + final Stream createAnAccountWithECKeyAndNoAlias() { return defaultHapiSpec("CreateAnAccountWithECKeyAndNoAlias") .given(newKeyNamed(SECP_256K1_SOURCE_KEY).shape(SECP_256K1_SHAPE)) .when(withOpContext((spec, opLog) -> { @@ -592,7 +558,7 @@ final HapiSpec createAnAccountWithECKeyAndNoAlias() { } @HapiTest - final HapiSpec createAnAccountWithEDKeyAndNoAlias() { + final Stream createAnAccountWithEDKeyAndNoAlias() { return defaultHapiSpec("CreateAnAccountWithEDKeyAndNoAlias") .given(newKeyNamed(ED_25519_KEY).shape(KeyShape.ED25519)) .when(cryptoCreate(ACCOUNT).key(ED_25519_KEY)) @@ -601,7 +567,7 @@ final HapiSpec createAnAccountWithEDKeyAndNoAlias() { } @HapiTest - final HapiSpec createAnAccountWithED25519KeyAndED25519Alias() { + final Stream createAnAccountWithED25519KeyAndED25519Alias() { return defaultHapiSpec("CreateAnAccountWithED25519KeyAndED25519Alias") .given(newKeyNamed(ED_25519_KEY).shape(KeyShape.ED25519)) .when(withOpContext((spec, opLog) -> { @@ -618,7 +584,7 @@ final HapiSpec createAnAccountWithED25519KeyAndED25519Alias() { } @HapiTest - final HapiSpec createAnAccountWithECKeyAndECKeyAlias() { + final Stream createAnAccountWithECKeyAndECKeyAlias() { return defaultHapiSpec("CreateAnAccountWithECKeyAndECKeyAlias") .given(newKeyNamed(SECP_256K1_SOURCE_KEY).shape(SECP_256K1_SHAPE)) .when(withOpContext((spec, opLog) -> { @@ -654,7 +620,7 @@ final HapiSpec createAnAccountWithECKeyAndECKeyAlias() { } @HapiTest - final HapiSpec createAnAccountWithECDSAKeyAliasDifferentThanAdminKeyShouldFail() { + final Stream createAnAccountWithECDSAKeyAliasDifferentThanAdminKeyShouldFail() { return defaultHapiSpec("createAnAccountWithECDSAKeyAliasDifferentThanAdminKeyShouldFail") .given( newKeyNamed(SECP_256K1_SOURCE_KEY).shape(SECP_256K1_SHAPE), @@ -674,7 +640,7 @@ final HapiSpec createAnAccountWithECDSAKeyAliasDifferentThanAdminKeyShouldFail() } @HapiTest - final HapiSpec createAnAccountWithEVMAddressAliasFromSameKey() { + final Stream createAnAccountWithEVMAddressAliasFromSameKey() { final var edKey = "edKey"; return defaultHapiSpec("createAnAccountWithEVMAddressAliasFromSameKey") .given( @@ -721,7 +687,7 @@ final HapiSpec createAnAccountWithEVMAddressAliasFromSameKey() { } @HapiTest - final HapiSpec createAnAccountWithEVMAddressAliasFromDifferentKey() { + final Stream createAnAccountWithEVMAddressAliasFromDifferentKey() { return defaultHapiSpec("createAnAccountWithEVMAddressAliasFromDifferentKey") .given( newKeyNamed(SECP_256K1_SOURCE_KEY).shape(SECP_256K1_SHAPE), @@ -761,7 +727,7 @@ final HapiSpec createAnAccountWithEVMAddressAliasFromDifferentKey() { } @HapiTest - final HapiSpec createAnAccountWithEDKeyAliasDifferentThanAdminKeyShouldFail() { + final Stream createAnAccountWithEDKeyAliasDifferentThanAdminKeyShouldFail() { return defaultHapiSpec("createAnAccountWithEDKeyAliasDifferentThanAdminKeyShouldFail") .given( newKeyNamed(SECP_256K1_SOURCE_KEY).shape(SECP_256K1_SHAPE), @@ -781,7 +747,7 @@ final HapiSpec createAnAccountWithEDKeyAliasDifferentThanAdminKeyShouldFail() { } @HapiTest - final HapiSpec successfullyRecreateAccountWithSameAliasAfterDeletion() { + final Stream successfullyRecreateAccountWithSameAliasAfterDeletion() { return defaultHapiSpec("successfullyRecreateAccountWithSameAliasAfterDeletion") .given( newKeyNamed(SECP_256K1_SOURCE_KEY).shape(SECP_256K1_SHAPE), @@ -812,9 +778,4 @@ final HapiSpec successfullyRecreateAccountWithSameAliasAfterDeletion() { })) .then(getAccountInfo(ACCOUNT).has(accountWith().balance(ONE_HBAR))); } - - @Override - protected Logger getResultsLogger() { - return log; - } } diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/crypto/CryptoDeleteAllowanceSuite.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/crypto/CryptoDeleteAllowanceSuite.java index 0f5f127c7314..39642a5359a6 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/crypto/CryptoDeleteAllowanceSuite.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/crypto/CryptoDeleteAllowanceSuite.java @@ -16,8 +16,10 @@ package com.hedera.services.bdd.suites.crypto; +import static com.hedera.services.bdd.junit.ContextRequirement.PROPERTY_OVERRIDES; import static com.hedera.services.bdd.junit.TestTags.CRYPTO; import static com.hedera.services.bdd.spec.HapiSpec.defaultHapiSpec; +import static com.hedera.services.bdd.spec.HapiSpec.propertyPreservingHapiSpec; import static com.hedera.services.bdd.spec.assertions.AccountDetailsAsserts.accountDetailsWith; import static com.hedera.services.bdd.spec.queries.QueryVerbs.getAccountDetails; import static com.hedera.services.bdd.spec.queries.QueryVerbs.getTokenNftInfo; @@ -40,9 +42,17 @@ import static com.hedera.services.bdd.spec.transactions.token.TokenMovement.moving; import static com.hedera.services.bdd.spec.transactions.token.TokenMovement.movingUnique; import static com.hedera.services.bdd.spec.utilops.UtilVerbs.newKeyNamed; +import static com.hedera.services.bdd.spec.utilops.UtilVerbs.overriding; import static com.hedera.services.bdd.spec.utilops.UtilVerbs.submitModified; import static com.hedera.services.bdd.spec.utilops.UtilVerbs.validateChargedUsdWithin; import static com.hedera.services.bdd.spec.utilops.mod.ModificationUtils.withSuccessivelyVariedBodyIds; +import static com.hedera.services.bdd.suites.HapiSuite.APP_PROPERTIES; +import static com.hedera.services.bdd.suites.HapiSuite.DEFAULT_PAYER; +import static com.hedera.services.bdd.suites.HapiSuite.EXCHANGE_RATE_CONTROL; +import static com.hedera.services.bdd.suites.HapiSuite.GENESIS; +import static com.hedera.services.bdd.suites.HapiSuite.ONE_HBAR; +import static com.hedera.services.bdd.suites.HapiSuite.ONE_HUNDRED_HBARS; +import static com.hedera.services.bdd.suites.HapiSuite.TOKEN_TREASURY; import static com.hedera.services.bdd.suites.crypto.CryptoApproveAllowanceSuite.OWNER; import static com.hedera.services.bdd.suites.crypto.CryptoApproveAllowanceSuite.SPENDER; import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.EMPTY_ALLOWANCES; @@ -56,48 +66,19 @@ import com.google.protobuf.ByteString; import com.hedera.services.bdd.junit.HapiTest; -import com.hedera.services.bdd.junit.HapiTestSuite; -import com.hedera.services.bdd.spec.HapiSpec; -import com.hedera.services.bdd.suites.HapiSuite; +import com.hedera.services.bdd.junit.LeakyHapiTest; import com.hederahashgraph.api.proto.java.TokenSupplyType; import com.hederahashgraph.api.proto.java.TokenType; import java.util.List; import java.util.Map; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; +import java.util.stream.Stream; +import org.junit.jupiter.api.DynamicTest; import org.junit.jupiter.api.Tag; -@HapiTestSuite @Tag(CRYPTO) -public class CryptoDeleteAllowanceSuite extends HapiSuite { - private static final Logger log = LogManager.getLogger(CryptoDeleteAllowanceSuite.class); - - public static void main(String... args) { - new CryptoDeleteAllowanceSuite().runSuiteSync(); - } - - @Override - public List getSpecsInSuite() { - return List.of(new HapiSpec[] { - happyPathWorks(), - approvedForAllNotAffectedOnDelete(), - noOwnerDefaultsToPayerInDeleteAllowance(), - invalidOwnerFails(), - canDeleteMultipleOwners(), - emptyAllowancesDeleteRejected(), - tokenNotAssociatedToAccountFailsOnDeleteAllowance(), - invalidTokenTypeFailsInDeleteAllowance(), - validatesSerialNums(), - exceedsTransactionLimit(), - succeedsWhenTokenPausedFrozenKycRevoked(), - feesAsExpected(), - duplicateEntriesDoesntThrow(), - canDeleteAllowanceForDeletedSpender() - }); - } - +public class CryptoDeleteAllowanceSuite { @HapiTest - public HapiSpec idVariantsTreatedAsExpected() { + final Stream idVariantsTreatedAsExpected() { return defaultHapiSpec("idVariantsTreatedAsExpected") .given( newKeyNamed("supplyKey"), @@ -135,7 +116,7 @@ public HapiSpec idVariantsTreatedAsExpected() { } @HapiTest - final HapiSpec canDeleteAllowanceForDeletedSpender() { + final Stream canDeleteAllowanceForDeletedSpender() { final String owner = "owner"; final String spender = "spender"; final String nft = "nft"; @@ -192,7 +173,7 @@ final HapiSpec canDeleteAllowanceForDeletedSpender() { } @HapiTest - final HapiSpec duplicateEntriesDoesntThrow() { + final Stream duplicateEntriesDoesntThrow() { final String owner = "owner"; final String spender = "spender"; final String token = "token"; @@ -269,7 +250,7 @@ final HapiSpec duplicateEntriesDoesntThrow() { } @HapiTest - final HapiSpec invalidOwnerFails() { + final Stream invalidOwnerFails() { final String owner = "owner"; final String spender = "spender"; final String token = "token"; @@ -325,7 +306,7 @@ final HapiSpec invalidOwnerFails() { } @HapiTest - final HapiSpec feesAsExpected() { + final Stream feesAsExpected() { final String owner = "owner"; final String spender = "spender"; final String token = "token"; @@ -413,7 +394,7 @@ final HapiSpec feesAsExpected() { } @HapiTest - final HapiSpec succeedsWhenTokenPausedFrozenKycRevoked() { + final Stream succeedsWhenTokenPausedFrozenKycRevoked() { final String owner = "owner"; final String spender = "spender"; final String spender1 = "spender1"; @@ -509,21 +490,19 @@ final HapiSpec succeedsWhenTokenPausedFrozenKycRevoked() { "hedera.allowances.maxAccountLimit", "100"))); } - @HapiTest - final HapiSpec exceedsTransactionLimit() { + @LeakyHapiTest(PROPERTY_OVERRIDES) + final Stream exceedsTransactionLimit() { final String owner = "owner"; final String spender = "spender"; final String spender1 = "spender1"; final String spender2 = "spender2"; final String token = "token"; final String nft = "nft"; - return defaultHapiSpec("exceedsTransactionLimit") + return propertyPreservingHapiSpec("exceedsTransactionLimit") + .preserving("hedera.allowances.maxTransactionLimit") .given( + overriding("hedera.allowances.maxTransactionLimit", "4"), newKeyNamed("supplyKey"), - fileUpdate(APP_PROPERTIES) - .fee(ONE_HUNDRED_HBARS) - .payingWith(EXCHANGE_RATE_CONTROL) - .overridingProps(Map.of("hedera.allowances.maxTransactionLimit", "4")), cryptoCreate(owner).balance(ONE_HUNDRED_HBARS).maxAutomaticTokenAssociations(10), cryptoCreate(spender).balance(ONE_HUNDRED_HBARS), cryptoCreate(spender1).balance(ONE_HUNDRED_HBARS), @@ -576,16 +555,11 @@ final HapiSpec exceedsTransactionLimit() { .addNftDeleteAllowance(owner, nft, List.of(1L)) .addNftDeleteAllowance(owner, nft, List.of(1L)) .hasPrecheck(MAX_ALLOWANCES_EXCEEDED)) - .then( - // reset - fileUpdate(APP_PROPERTIES) - .fee(ONE_HUNDRED_HBARS) - .payingWith(EXCHANGE_RATE_CONTROL) - .overridingProps(Map.of("hedera.allowances.maxTransactionLimit", "20"))); + .then(); } @HapiTest - final HapiSpec validatesSerialNums() { + final Stream validatesSerialNums() { final String owner = "owner"; final String spender = "spender"; final String nft = "nft"; @@ -641,7 +615,7 @@ final HapiSpec validatesSerialNums() { } @HapiTest - final HapiSpec invalidTokenTypeFailsInDeleteAllowance() { + final Stream invalidTokenTypeFailsInDeleteAllowance() { final String owner = "owner"; final String spender = "spender"; final String token = "token"; @@ -670,7 +644,7 @@ final HapiSpec invalidTokenTypeFailsInDeleteAllowance() { } @HapiTest - final HapiSpec emptyAllowancesDeleteRejected() { + final Stream emptyAllowancesDeleteRejected() { final String owner = "owner"; return defaultHapiSpec("emptyAllowancesDeleteRejected") .given(cryptoCreate(owner).balance(ONE_HUNDRED_HBARS).maxAutomaticTokenAssociations(10)) @@ -679,7 +653,7 @@ final HapiSpec emptyAllowancesDeleteRejected() { } @HapiTest - final HapiSpec tokenNotAssociatedToAccountFailsOnDeleteAllowance() { + final Stream tokenNotAssociatedToAccountFailsOnDeleteAllowance() { final String owner = "owner"; final String spender = "spender"; final String token = "token"; @@ -727,7 +701,7 @@ final HapiSpec tokenNotAssociatedToAccountFailsOnDeleteAllowance() { } @HapiTest - final HapiSpec canDeleteMultipleOwners() { + final Stream canDeleteMultipleOwners() { final String owner1 = "owner1"; final String owner2 = "owner2"; final String spender = "spender"; @@ -819,7 +793,7 @@ final HapiSpec canDeleteMultipleOwners() { } @HapiTest - final HapiSpec noOwnerDefaultsToPayerInDeleteAllowance() { + final Stream noOwnerDefaultsToPayerInDeleteAllowance() { final String payer = "payer"; final String spender = "spender"; final String spender1 = "spender1"; @@ -882,7 +856,7 @@ final HapiSpec noOwnerDefaultsToPayerInDeleteAllowance() { } @HapiTest - final HapiSpec approvedForAllNotAffectedOnDelete() { + final Stream approvedForAllNotAffectedOnDelete() { final String owner = "owner"; final String spender = "spender"; final String token = "token"; @@ -957,7 +931,7 @@ final HapiSpec approvedForAllNotAffectedOnDelete() { } @HapiTest - final HapiSpec happyPathWorks() { + final Stream happyPathWorks() { final String owner = "owner"; final String spender = "spender"; final String spender1 = "spender1"; @@ -1027,9 +1001,4 @@ final HapiSpec happyPathWorks() { .has(accountDetailsWith().nftApprovedForAllAllowancesCount(1)), getTokenNftInfo(nft, 3L).hasNoSpender()); } - - @Override - protected Logger getResultsLogger() { - return log; - } } diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/crypto/CryptoDeleteSuite.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/crypto/CryptoDeleteSuite.java index 40cc1efe040d..a7ae56f56e2e 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/crypto/CryptoDeleteSuite.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/crypto/CryptoDeleteSuite.java @@ -40,6 +40,10 @@ import static com.hedera.services.bdd.spec.utilops.mod.ModificationUtils.withSuccessivelyVariedBodyIds; import static com.hedera.services.bdd.spec.utilops.records.SnapshotMatchMode.FULLY_NONDETERMINISTIC; import static com.hedera.services.bdd.spec.utilops.records.SnapshotMatchMode.NONDETERMINISTIC_TRANSACTION_FEES; +import static com.hedera.services.bdd.suites.HapiSuite.DEFAULT_PAYER; +import static com.hedera.services.bdd.suites.HapiSuite.FUNDING; +import static com.hedera.services.bdd.suites.HapiSuite.GENESIS; +import static com.hedera.services.bdd.suites.HapiSuite.TOKEN_TREASURY; import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.ACCOUNT_DELETED; import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.ACCOUNT_ID_DOES_NOT_EXIST; import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.ACCOUNT_IS_TREASURY; @@ -48,48 +52,23 @@ import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.TRANSACTION_REQUIRES_ZERO_TOKEN_BALANCES; import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.TRANSFER_ACCOUNT_SAME_AS_DELETE_ACCOUNT; +import com.hedera.services.bdd.junit.ContextRequirement; import com.hedera.services.bdd.junit.HapiTest; -import com.hedera.services.bdd.junit.HapiTestSuite; -import com.hedera.services.bdd.spec.HapiSpec; +import com.hedera.services.bdd.junit.LeakyHapiTest; import com.hedera.services.bdd.spec.HapiSpecSetup; -import com.hedera.services.bdd.suites.HapiSuite; -import java.util.List; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; +import java.util.stream.Stream; +import org.junit.jupiter.api.DynamicTest; import org.junit.jupiter.api.Tag; -@HapiTestSuite @Tag(CRYPTO) -public class CryptoDeleteSuite extends HapiSuite { - static final Logger log = LogManager.getLogger(CryptoDeleteSuite.class); +public class CryptoDeleteSuite { private static final long TOKEN_INITIAL_SUPPLY = 500; private static final String TRANSFER_ACCOUNT = "transferAccount"; private static final String TREASURY = "treasury"; private static final String ACCOUNT_TO_BE_DELETED = "toBeDeleted"; - public static void main(String... args) { - new CryptoDeleteSuite().runSuiteSync(); - } - - @Override - protected Logger getResultsLogger() { - return log; - } - - @Override - public List getSpecsInSuite() { - return List.of( - fundsTransferOnDelete(), - cannotDeleteAccountsWithNonzeroTokenBalances(), - cannotDeleteAlreadyDeletedAccount(), - cannotDeleteAccountWithSameBeneficiary(), - cannotDeleteTreasuryAccount(), - deletedAccountCannotBePayer(), - canQueryForRecordsWithDeletedPayers()); - } - @HapiTest - final HapiSpec accountIdVariantsTreatedAsExpected() { + final Stream accountIdVariantsTreatedAsExpected() { return defaultHapiSpec("accountIdVariantsTreatedAsExpected") .given(cryptoCreate(TRANSFER_ACCOUNT), cryptoCreate(ACCOUNT_TO_BE_DELETED)) .when() @@ -97,41 +76,34 @@ final HapiSpec accountIdVariantsTreatedAsExpected() { .transfer(TRANSFER_ACCOUNT))); } - @HapiTest - // In this transactionFee is not set in mono-service record, because it fails in node due diligence. - // But it feels right to set it. So setting this HIGHLY_NONDETERMINISTIC_TRANSACTION_FEES - final HapiSpec deletedAccountCannotBePayer() { - // Account Names - String SUBMITTING_NODE_ACCOUNT = "0.0.3"; - String BENEFICIARY_ACCOUNT = "beneficiaryAccountForDeletedAccount"; - - // Snapshot Names - String SUBMITTING_NODE_PRE_TRANSFER = "submittingNodePreTransfer"; - String SUBMITTING_NODE_AFTER_BALANCE_LOAD = "submittingNodeAfterBalanceLoad"; - + @LeakyHapiTest(ContextRequirement.SYSTEM_ACCOUNT_BALANCES) + final Stream deletedAccountCannotBePayer() { + final var submittingNodeAccount = "0.0.3"; + final var beneficiaryAccount = "beneficiaryAccountForDeletedAccount"; + final var submittingNodePreTransfer = "submittingNodePreTransfer"; + final var submittingNodeAfterBalanceLoad = "submittingNodeAfterBalanceLoad"; return defaultHapiSpec("DeletedAccountCannotBePayer", FULLY_NONDETERMINISTIC) .given( cryptoCreate(ACCOUNT_TO_BE_DELETED), - cryptoCreate(BENEFICIARY_ACCOUNT).balance(0L)) + cryptoCreate(beneficiaryAccount).balance(0L)) .when() .then( - balanceSnapshot(SUBMITTING_NODE_PRE_TRANSFER, SUBMITTING_NODE_ACCOUNT), - cryptoTransfer(tinyBarsFromTo(GENESIS, SUBMITTING_NODE_ACCOUNT, 1000000000)), - balanceSnapshot(SUBMITTING_NODE_AFTER_BALANCE_LOAD, SUBMITTING_NODE_ACCOUNT), + balanceSnapshot(submittingNodePreTransfer, submittingNodeAccount), + cryptoTransfer(tinyBarsFromTo(GENESIS, submittingNodeAccount, 1000000000)), + balanceSnapshot(submittingNodeAfterBalanceLoad, submittingNodeAccount), cryptoDelete(ACCOUNT_TO_BE_DELETED) - .transfer(BENEFICIARY_ACCOUNT) + .transfer(beneficiaryAccount) .deferStatusResolution(), - cryptoTransfer(tinyBarsFromTo(BENEFICIARY_ACCOUNT, GENESIS, 1)) + cryptoTransfer(tinyBarsFromTo(beneficiaryAccount, GENESIS, 1)) .payingWith(ACCOUNT_TO_BE_DELETED) .hasKnownStatus(PAYER_ACCOUNT_DELETED), - getAccountBalance(SUBMITTING_NODE_ACCOUNT) - .hasTinyBars( - approxChangeFromSnapshot(SUBMITTING_NODE_AFTER_BALANCE_LOAD, -100000, 50000)) + getAccountBalance(submittingNodeAccount) + .hasTinyBars(approxChangeFromSnapshot(submittingNodeAfterBalanceLoad, -100000, 50000)) .logged()); } @HapiTest - final HapiSpec canQueryForRecordsWithDeletedPayers() { + final Stream canQueryForRecordsWithDeletedPayers() { final var stillQueryableTxn = "stillQueryableTxn"; return defaultHapiSpec("CanQueryForRecordsWithDeletedPayers") .given(cryptoCreate(ACCOUNT_TO_BE_DELETED)) @@ -144,7 +116,7 @@ final HapiSpec canQueryForRecordsWithDeletedPayers() { } @HapiTest - final HapiSpec fundsTransferOnDelete() { + final Stream fundsTransferOnDelete() { long B = HapiSpecSetup.getDefaultInstance().defaultBalance(); return defaultHapiSpec("FundsTransferOnDelete") @@ -163,7 +135,7 @@ final HapiSpec fundsTransferOnDelete() { } @HapiTest - final HapiSpec cannotDeleteAccountsWithNonzeroTokenBalances() { + final Stream cannotDeleteAccountsWithNonzeroTokenBalances() { return defaultHapiSpec("CannotDeleteAccountsWithNonzeroTokenBalances") .given( newKeyNamed("admin"), @@ -201,7 +173,7 @@ final HapiSpec cannotDeleteAccountsWithNonzeroTokenBalances() { } @HapiTest - final HapiSpec cannotDeleteAlreadyDeletedAccount() { + final Stream cannotDeleteAlreadyDeletedAccount() { return defaultHapiSpec("CannotDeleteAlreadyDeletedAccount", NONDETERMINISTIC_TRANSACTION_FEES) .given(cryptoCreate(ACCOUNT_TO_BE_DELETED), cryptoCreate(TRANSFER_ACCOUNT)) .when(cryptoDelete(ACCOUNT_TO_BE_DELETED) @@ -213,7 +185,7 @@ final HapiSpec cannotDeleteAlreadyDeletedAccount() { } @HapiTest - final HapiSpec mustSpecifyTargetId() { + final Stream mustSpecifyTargetId() { return defaultHapiSpec("mustSpecifyTargetId") .given() .when() @@ -224,7 +196,7 @@ final HapiSpec mustSpecifyTargetId() { } @HapiTest - final HapiSpec cannotDeleteAccountWithSameBeneficiary() { + final Stream cannotDeleteAccountWithSameBeneficiary() { return defaultHapiSpec("CannotDeleteAccountWithSameBeneficiary") .given(cryptoCreate(ACCOUNT_TO_BE_DELETED)) .when() @@ -234,7 +206,7 @@ final HapiSpec cannotDeleteAccountWithSameBeneficiary() { } @HapiTest - final HapiSpec cannotDeleteTreasuryAccount() { + final Stream cannotDeleteTreasuryAccount() { return defaultHapiSpec("CannotDeleteTreasuryAccount", NONDETERMINISTIC_TRANSACTION_FEES) .given(cryptoCreate(TREASURY), cryptoCreate(TRANSFER_ACCOUNT)) .when(tokenCreate("toBeTransferred") diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/crypto/CryptoGetInfoRegression.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/crypto/CryptoGetInfoRegression.java index 82cd275d3da8..e5512e890e77 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/crypto/CryptoGetInfoRegression.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/crypto/CryptoGetInfoRegression.java @@ -34,6 +34,12 @@ import static com.hedera.services.bdd.spec.transactions.token.TokenMovement.moving; import static com.hedera.services.bdd.spec.transactions.token.TokenMovement.movingUnique; import static com.hedera.services.bdd.spec.utilops.UtilVerbs.newKeyNamed; +import static com.hedera.services.bdd.suites.HapiSuite.ADDRESS_BOOK_CONTROL; +import static com.hedera.services.bdd.suites.HapiSuite.APP_PROPERTIES; +import static com.hedera.services.bdd.suites.HapiSuite.CIVILIAN_PAYER; +import static com.hedera.services.bdd.suites.HapiSuite.GENESIS; +import static com.hedera.services.bdd.suites.HapiSuite.ONE_HUNDRED_HBARS; +import static com.hedera.services.bdd.suites.HapiSuite.TOKEN_TREASURY; import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.ACCOUNT_DELETED; import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.INSUFFICIENT_PAYER_BALANCE; import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.INSUFFICIENT_TX_FEE; @@ -44,48 +50,24 @@ import com.google.protobuf.ByteString; import com.hedera.services.bdd.junit.HapiTest; -import com.hedera.services.bdd.junit.HapiTestSuite; -import com.hedera.services.bdd.spec.HapiSpec; import com.hedera.services.bdd.spec.keys.KeyShape; -import com.hedera.services.bdd.suites.HapiSuite; import com.hederahashgraph.api.proto.java.TokenSupplyType; import com.hederahashgraph.api.proto.java.TokenType; import java.util.List; import java.util.Map; +import java.util.stream.Stream; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; +import org.junit.jupiter.api.DynamicTest; import org.junit.jupiter.api.Tag; -@HapiTestSuite @Tag(CRYPTO) -public class CryptoGetInfoRegression extends HapiSuite { +public class CryptoGetInfoRegression { static final Logger log = LogManager.getLogger(CryptoGetInfoRegression.class); - public static void main(String... args) { - new CryptoGetInfoRegression().runSuiteSync(); - } - - @Override - protected Logger getResultsLogger() { - return log; - } - - @Override - public List getSpecsInSuite() { - return List.of(new HapiSpec[] { - failsForDeletedAccount(), - failsForMissingAccount(), - failsForMissingPayment(), - failsForMalformedPayment(), - failsForUnfundablePayment(), - succeedsNormally(), - fetchesOnlyALimitedTokenAssociations() - }); - } - /** For Demo purpose : The limit on each account info and account balance queries is set to 5 */ @HapiTest - final HapiSpec fetchesOnlyALimitedTokenAssociations() { + final Stream fetchesOnlyALimitedTokenAssociations() { final int infoLimit = 3; final var account = "test"; final var aKey = "tokenKey"; @@ -197,7 +179,7 @@ final HapiSpec fetchesOnlyALimitedTokenAssociations() { } @HapiTest - final HapiSpec succeedsNormally() { + final Stream succeedsNormally() { long balance = 1_234_567L; KeyShape misc = listOf(SIMPLE, listOf(2)); @@ -238,7 +220,7 @@ final HapiSpec succeedsNormally() { } @HapiTest - final HapiSpec failsForMissingAccount() { + final Stream failsForMissingAccount() { return defaultHapiSpec("FailsForMissingAccount") .given() .when() @@ -246,7 +228,7 @@ final HapiSpec failsForMissingAccount() { } @HapiTest - final HapiSpec failsForMalformedPayment() { + final Stream failsForMalformedPayment() { return defaultHapiSpec("FailsForMalformedPayment") .given(newKeyNamed("wrong").shape(SIMPLE)) .when() @@ -254,7 +236,7 @@ final HapiSpec failsForMalformedPayment() { } @HapiTest - final HapiSpec failsForUnfundablePayment() { + final Stream failsForUnfundablePayment() { long everything = 1_234L; return defaultHapiSpec("FailsForUnfundablePayment") .given(cryptoCreate("brokePayer").balance(everything)) @@ -266,7 +248,7 @@ final HapiSpec failsForUnfundablePayment() { } @HapiTest - final HapiSpec failsForInsufficientPayment() { + final Stream failsForInsufficientPayment() { return defaultHapiSpec("FailsForInsufficientPayment") .given(cryptoCreate(CIVILIAN_PAYER)) .when() @@ -277,7 +259,7 @@ final HapiSpec failsForInsufficientPayment() { } @HapiTest // this test needs to be updated for both mono and module code. - final HapiSpec failsForMissingPayment() { + final Stream failsForMissingPayment() { return defaultHapiSpec("FailsForMissingPayment") .given() .when() @@ -287,7 +269,7 @@ final HapiSpec failsForMissingPayment() { } @HapiTest - final HapiSpec failsForDeletedAccount() { + final Stream failsForDeletedAccount() { return defaultHapiSpec("FailsForDeletedAccount") .given(cryptoCreate("toBeDeleted")) .when(cryptoDelete("toBeDeleted").transfer(GENESIS)) diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/crypto/CryptoGetRecordsRegression.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/crypto/CryptoGetRecordsRegression.java index 1bf1bb4dc635..0f716e071fb1 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/crypto/CryptoGetRecordsRegression.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/crypto/CryptoGetRecordsRegression.java @@ -27,6 +27,7 @@ import static com.hedera.services.bdd.spec.transactions.TxnVerbs.cryptoTransfer; import static com.hedera.services.bdd.spec.transactions.crypto.HapiCryptoTransfer.tinyBarsFromTo; import static com.hedera.services.bdd.spec.utilops.UtilVerbs.newKeyNamed; +import static com.hedera.services.bdd.suites.HapiSuite.GENESIS; import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.ACCOUNT_DELETED; import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.INSUFFICIENT_PAYER_BALANCE; import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.INSUFFICIENT_TX_FEE; @@ -36,49 +37,20 @@ import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.SUCCESS; import com.hedera.services.bdd.junit.HapiTest; -import com.hedera.services.bdd.junit.HapiTestSuite; -import com.hedera.services.bdd.spec.HapiSpec; import com.hedera.services.bdd.spec.assertions.AssertUtils; -import com.hedera.services.bdd.suites.HapiSuite; -import java.util.List; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; +import java.util.stream.Stream; +import org.junit.jupiter.api.DynamicTest; import org.junit.jupiter.api.Tag; -@HapiTestSuite @Tag(CRYPTO) -public class CryptoGetRecordsRegression extends HapiSuite { - static final Logger log = LogManager.getLogger(CryptoGetRecordsRegression.class); +public class CryptoGetRecordsRegression { private static final String LOW_THRESH_PAYER = "lowThreshPayer"; private static final String ACCOUNT_TO_BE_DELETED = "toBeDeleted"; private static final String ACCOUNT_1 = "account1"; private static final String PAYER = "payer"; - public static void main(String... args) { - new CryptoGetRecordsRegression().runSuiteSync(); - } - - @Override - protected Logger getResultsLogger() { - return log; - } - - @Override - public List getSpecsInSuite() { - return List.of(new HapiSpec[] { - failsForDeletedAccount(), - failsForMissingAccount(), - failsForInvalidTrxBody(), - failsForInsufficientPayment(), - failsForMalformedPayment(), - failsForUnfundablePayment(), - succeedsNormally(), - getAccountRecords_testForDuplicates() - }); - } - @HapiTest - final HapiSpec succeedsNormally() { + final Stream succeedsNormally() { String memo = "Dim galleries, dusky corridors got past..."; return defaultHapiSpec("SucceedsNormally") @@ -97,7 +69,7 @@ final HapiSpec succeedsNormally() { } @HapiTest - final HapiSpec failsForMissingAccount() { + final Stream failsForMissingAccount() { return defaultHapiSpec("FailsForMissingAccount") .given() .when() @@ -107,7 +79,7 @@ final HapiSpec failsForMissingAccount() { } @HapiTest - final HapiSpec failsForMalformedPayment() { + final Stream failsForMalformedPayment() { return defaultHapiSpec("FailsForMalformedPayment") .given(newKeyNamed("wrong").shape(SIMPLE)) .when() @@ -115,7 +87,7 @@ final HapiSpec failsForMalformedPayment() { } @HapiTest - final HapiSpec failsForUnfundablePayment() { + final Stream failsForUnfundablePayment() { long everything = 1_234L; return defaultHapiSpec("FailsForUnfundablePayment") .given(cryptoCreate("brokePayer").balance(everything)) @@ -127,7 +99,7 @@ final HapiSpec failsForUnfundablePayment() { } @HapiTest - final HapiSpec failsForInsufficientPayment() { + final Stream failsForInsufficientPayment() { return defaultHapiSpec("FailsForInsufficientPayment") .given(cryptoCreate(PAYER)) .when() @@ -138,7 +110,7 @@ final HapiSpec failsForInsufficientPayment() { } @HapiTest - final HapiSpec failsForInvalidTrxBody() { + final Stream failsForInvalidTrxBody() { return defaultHapiSpec("failsForInvalidTrxBody") .given() .when() @@ -148,7 +120,7 @@ final HapiSpec failsForInvalidTrxBody() { } @HapiTest - final HapiSpec failsForDeletedAccount() { + final Stream failsForDeletedAccount() { return defaultHapiSpec("FailsForDeletedAccount") .given(cryptoCreate(ACCOUNT_TO_BE_DELETED)) .when(cryptoDelete(ACCOUNT_TO_BE_DELETED).transfer(GENESIS)) @@ -160,7 +132,7 @@ final HapiSpec failsForDeletedAccount() { } @HapiTest - final HapiSpec getAccountRecords_testForDuplicates() { + final Stream getAccountRecords_testForDuplicates() { return defaultHapiSpec("testForDuplicateAccountRecords") .given( cryptoCreate(ACCOUNT_1).balance(5000000000000L).sendThreshold(1L), diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/crypto/CryptoTransferSuite.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/crypto/CryptoTransferSuite.java index 8c3fbe984b0c..4a418830a9d1 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/crypto/CryptoTransferSuite.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/crypto/CryptoTransferSuite.java @@ -24,6 +24,7 @@ import static com.hedera.services.bdd.spec.HapiPropertySource.asTopicString; import static com.hedera.services.bdd.spec.HapiSpec.defaultHapiSpec; import static com.hedera.services.bdd.spec.assertions.AccountInfoAsserts.accountWith; +import static com.hedera.services.bdd.spec.assertions.AccountInfoAsserts.changeFromSnapshot; import static com.hedera.services.bdd.spec.assertions.AutoAssocAsserts.accountTokenPairsInAnyOrder; import static com.hedera.services.bdd.spec.assertions.TransactionRecordAsserts.includingFungibleMovement; import static com.hedera.services.bdd.spec.assertions.TransactionRecordAsserts.includingNonfungibleMovement; @@ -83,6 +84,7 @@ import static com.hedera.services.bdd.spec.transactions.token.TokenMovement.movingWithAllowance; import static com.hedera.services.bdd.spec.transactions.token.TokenMovement.movingWithDecimals; import static com.hedera.services.bdd.spec.utilops.CustomSpecAssert.allRunFor; +import static com.hedera.services.bdd.spec.utilops.UtilVerbs.balanceSnapshot; import static com.hedera.services.bdd.spec.utilops.UtilVerbs.newKeyNamed; import static com.hedera.services.bdd.spec.utilops.UtilVerbs.sleepFor; import static com.hedera.services.bdd.spec.utilops.UtilVerbs.sourcing; @@ -96,6 +98,15 @@ import static com.hedera.services.bdd.spec.utilops.records.SnapshotMatchMode.FULLY_NONDETERMINISTIC; import static com.hedera.services.bdd.spec.utilops.records.SnapshotMatchMode.HIGHLY_NON_DETERMINISTIC_FEES; import static com.hedera.services.bdd.spec.utilops.records.SnapshotMatchMode.NONDETERMINISTIC_TRANSACTION_FEES; +import static com.hedera.services.bdd.suites.HapiSuite.DEFAULT_PAYER; +import static com.hedera.services.bdd.suites.HapiSuite.FEE_COLLECTOR; +import static com.hedera.services.bdd.suites.HapiSuite.FUNDING; +import static com.hedera.services.bdd.suites.HapiSuite.GENESIS; +import static com.hedera.services.bdd.suites.HapiSuite.NODE_REWARD; +import static com.hedera.services.bdd.suites.HapiSuite.ONE_HBAR; +import static com.hedera.services.bdd.suites.HapiSuite.ONE_HUNDRED_HBARS; +import static com.hedera.services.bdd.suites.HapiSuite.STAKING_REWARD; +import static com.hedera.services.bdd.suites.HapiSuite.TOKEN_TREASURY; import static com.hedera.services.bdd.suites.contract.Utils.aaWith; import static com.hedera.services.bdd.suites.contract.Utils.accountId; import static com.hedera.services.bdd.suites.contract.Utils.captureOneChildCreate2MetaFor; @@ -133,14 +144,11 @@ import com.google.protobuf.ByteString; import com.hedera.node.app.hapi.utils.ByteStringUtils; import com.hedera.services.bdd.junit.HapiTest; -import com.hedera.services.bdd.junit.HapiTestSuite; -import com.hedera.services.bdd.spec.HapiSpec; import com.hedera.services.bdd.spec.HapiSpecOperation; import com.hedera.services.bdd.spec.HapiSpecSetup; import com.hedera.services.bdd.spec.assertions.AccountDetailsAsserts; import com.hedera.services.bdd.spec.keys.SigControl; import com.hedera.services.bdd.spec.utilops.UtilVerbs; -import com.hedera.services.bdd.suites.HapiSuite; import com.hederahashgraph.api.proto.java.AccountID; import com.hederahashgraph.api.proto.java.Key; import com.hederahashgraph.api.proto.java.TokenID; @@ -153,14 +161,15 @@ import java.util.OptionalLong; import java.util.concurrent.atomic.AtomicReference; import java.util.stream.IntStream; +import java.util.stream.Stream; import org.apache.commons.lang3.tuple.Pair; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; +import org.junit.jupiter.api.DynamicTest; import org.junit.jupiter.api.Tag; -@HapiTestSuite @Tag(CRYPTO) -public class CryptoTransferSuite extends HapiSuite { +public class CryptoTransferSuite { private static final Logger LOG = LogManager.getLogger(CryptoTransferSuite.class); private static final String OWNER = "owner"; private static final String OTHER_OWNER = "otherOwner"; @@ -214,54 +223,8 @@ public class CryptoTransferSuite extends HapiSuite { private static final String OTHER_ACCOUNT = "otheraccount"; private static final String TOKEN_METADATA = "Please mind the vase."; - public static void main(String... args) { - new CryptoTransferSuite().runSuiteAsync(); - } - - @Override - public List getSpecsInSuite() { - return List.of( - transferWithMissingAccountGetsInvalidAccountId(), - complexKeyAcctPaysForOwnTransfer(), - twoComplexKeysRequired(), - specialAccountsBalanceCheck(), - tokenTransferFeesScaleAsExpected(), - okToSetInvalidPaymentHeaderForCostAnswer(), - baseCryptoTransferFeeChargedAsExpected(), - autoAssociationRequiresOpenSlots(), - royaltyCollectorsCanUseAutoAssociation(), - royaltyCollectorsCannotUseAutoAssociationWithoutOpenSlots(), - dissociatedRoyaltyCollectorsCanUseAutoAssociation(), - hbarAndFungibleSelfTransfersRejectedBothInPrecheckAndHandle(), - transferToNonAccountEntitiesReturnsInvalidAccountId(), - nftSelfTransfersRejectedBothInPrecheckAndHandle(), - checksExpectedDecimalsForFungibleTokenTransferList(), - allowanceTransfersWorkAsExpected(), - allowanceTransfersWithComplexTransfersWork(), - canUseMirrorAliasesForNonContractXfers(), - canUseEip1014AliasesForXfers(), - cannotTransferFromImmutableAccounts(), - nftTransfersCannotRepeatSerialNos(), - vanillaTransferSucceeds(), - aliasKeysAreValidated(), - hapiTransferFromForNFTWithCustomFeesWithAllowance(), - hapiTransferFromForFungibleTokenWithCustomFeesWithAllowance(), - okToRepeatSerialNumbersInWipeList(), - okToRepeatSerialNumbersInBurnList(), - canUseAliasAndAccountCombinations(), - testTransferToSystemAccounts(), - testTransferToSystemAccountsAndCheckSenderBalance(), - transferInvalidTokenIdWithDecimals(), - insufficientBalanceForCustomFeeFails()); - } - - @Override - public boolean canRunConcurrent() { - return true; - } - @HapiTest - public HapiSpec insufficientBalanceForCustomFeeFails() { + final Stream insufficientBalanceForCustomFeeFails() { final var operatorKey = "operatorKey"; final var accountId1Key = "accountId1Key"; final var accountId2Key = "accountId2Key"; @@ -295,7 +258,7 @@ public HapiSpec insufficientBalanceForCustomFeeFails() { } @HapiTest - public HapiSpec idVariantsTreatedAsExpected() { + final Stream idVariantsTreatedAsExpected() { return defaultHapiSpec("idVariantsTreatedAsExpected") .given( newKeyNamed("supplyKey"), @@ -317,7 +280,7 @@ public HapiSpec idVariantsTreatedAsExpected() { } @HapiTest - final HapiSpec okToRepeatSerialNumbersInWipeList() { + final Stream okToRepeatSerialNumbersInWipeList() { final var ownerWith4AutoAssoc = "ownerWith4AutoAssoc"; return defaultHapiSpec("OkToRepeatSerialNumbersInWipeList") .given( @@ -354,7 +317,7 @@ final HapiSpec okToRepeatSerialNumbersInWipeList() { } @HapiTest - final HapiSpec okToRepeatSerialNumbersInBurnList() { + final Stream okToRepeatSerialNumbersInBurnList() { return defaultHapiSpec("okToRepeatSerialNumbersInBurnList") .given( newKeyNamed(SUPPLY_KEY), @@ -386,7 +349,7 @@ final HapiSpec okToRepeatSerialNumbersInBurnList() { } @HapiTest // fees differ expected 46889349 actual 46887567 - final HapiSpec canUseAliasAndAccountCombinations() { + final Stream canUseAliasAndAccountCombinations() { final AtomicReference ftId = new AtomicReference<>(); final AtomicReference nftId = new AtomicReference<>(); final AtomicReference partyId = new AtomicReference<>(); @@ -436,7 +399,7 @@ final HapiSpec canUseAliasAndAccountCombinations() { } @HapiTest - final HapiSpec aliasKeysAreValidated() { + final Stream aliasKeysAreValidated() { final var validAlias = "validAlias"; final var invalidAlias = "invalidAlias"; @@ -467,7 +430,7 @@ final HapiSpec aliasKeysAreValidated() { // https://github.com/hashgraph/hedera-services/issues/2875 @HapiTest - final HapiSpec canUseMirrorAliasesForNonContractXfers() { + final Stream canUseMirrorAliasesForNonContractXfers() { final AtomicReference ftId = new AtomicReference<>(); final AtomicReference nftId = new AtomicReference<>(); final AtomicReference partyId = new AtomicReference<>(); @@ -549,7 +512,7 @@ final HapiSpec canUseMirrorAliasesForNonContractXfers() { @SuppressWarnings("java:S5669") @HapiTest - final HapiSpec canUseEip1014AliasesForXfers() { + final Stream canUseEip1014AliasesForXfers() { final var partyCreation2 = "partyCreation2"; final var counterCreation2 = "counterCreation2"; final var contract = "CreateDonor"; @@ -674,7 +637,7 @@ final HapiSpec canUseEip1014AliasesForXfers() { } @HapiTest - final HapiSpec cannotTransferFromImmutableAccounts() { + final Stream cannotTransferFromImmutableAccounts() { final var contract = "PayableConstructor"; final var multiKey = "swiss"; @@ -773,7 +736,7 @@ final HapiSpec cannotTransferFromImmutableAccounts() { } @HapiTest // fees differ 44071858 vs 44071845 - final HapiSpec allowanceTransfersWithComplexTransfersWork() { + final Stream allowanceTransfersWithComplexTransfersWork() { return defaultHapiSpec("AllowanceTransfersWithComplexTransfersWork", NONDETERMINISTIC_TRANSACTION_FEES) .given( newKeyNamed(ADMIN_KEY), @@ -897,7 +860,7 @@ final HapiSpec allowanceTransfersWithComplexTransfersWork() { } @HapiTest - final HapiSpec allowanceTransfersWorkAsExpected() { + final Stream allowanceTransfersWorkAsExpected() { return defaultHapiSpec("AllowanceTransfersWorkAsExpected", NONDETERMINISTIC_TRANSACTION_FEES) .given( newKeyNamed(ADMIN_KEY), @@ -1135,7 +1098,7 @@ final HapiSpec allowanceTransfersWorkAsExpected() { } @HapiTest - final HapiSpec checksExpectedDecimalsForFungibleTokenTransferList() { + final Stream checksExpectedDecimalsForFungibleTokenTransferList() { return defaultHapiSpec("checksExpectedDecimalsForFungibleTokenTransferList", NONDETERMINISTIC_TRANSACTION_FEES) .given( newKeyNamed(MULTI_KEY), @@ -1180,7 +1143,7 @@ final HapiSpec checksExpectedDecimalsForFungibleTokenTransferList() { } @HapiTest - final HapiSpec nftTransfersCannotRepeatSerialNos() { + final Stream nftTransfersCannotRepeatSerialNos() { final var aParty = "aParty"; final var bParty = "bParty"; final var cParty = "cParty"; @@ -1224,7 +1187,7 @@ final HapiSpec nftTransfersCannotRepeatSerialNos() { } @HapiTest - final HapiSpec nftSelfTransfersRejectedBothInPrecheckAndHandle() { + final Stream nftSelfTransfersRejectedBothInPrecheckAndHandle() { final var owningParty = OWNING_PARTY; final var multipurpose = MULTI_KEY; final var nftType = "nftType"; @@ -1258,7 +1221,7 @@ final HapiSpec nftSelfTransfersRejectedBothInPrecheckAndHandle() { } @HapiTest - final HapiSpec hbarAndFungibleSelfTransfersRejectedBothInPrecheckAndHandle() { + final Stream hbarAndFungibleSelfTransfersRejectedBothInPrecheckAndHandle() { final var uncheckedHbarTxn = "uncheckedHbarTxn"; final var uncheckedFtTxn = "uncheckedFtTxn"; @@ -1299,7 +1262,7 @@ final HapiSpec hbarAndFungibleSelfTransfersRejectedBothInPrecheckAndHandle() { } @HapiTest - final HapiSpec dissociatedRoyaltyCollectorsCanUseAutoAssociation() { + final Stream dissociatedRoyaltyCollectorsCanUseAutoAssociation() { final var commonWithCustomFees = "commonWithCustomFees"; final var fractionalCollector = "fractionalCollector"; final var selfDenominatedCollector = "selfDenominatedCollector"; @@ -1358,7 +1321,7 @@ final HapiSpec dissociatedRoyaltyCollectorsCanUseAutoAssociation() { } @HapiTest - final HapiSpec royaltyCollectorsCanUseAutoAssociation() { + final Stream royaltyCollectorsCanUseAutoAssociation() { final var uniqueWithRoyalty = "uniqueWithRoyalty"; final var firstFungible = "firstFungible"; final var secondFungible = "secondFungible"; @@ -1466,7 +1429,7 @@ final HapiSpec royaltyCollectorsCanUseAutoAssociation() { } @HapiTest - final HapiSpec royaltyCollectorsCannotUseAutoAssociationWithoutOpenSlots() { + final Stream royaltyCollectorsCannotUseAutoAssociationWithoutOpenSlots() { final var uniqueWithRoyalty = "uniqueWithRoyalty"; final var someFungible = "firstFungible"; final var royaltyCollectorNoSlots = "royaltyCollectorNoSlots"; @@ -1518,7 +1481,7 @@ final HapiSpec royaltyCollectorsCannotUseAutoAssociationWithoutOpenSlots() { } @HapiTest - final HapiSpec autoAssociationRequiresOpenSlots() { + final Stream autoAssociationRequiresOpenSlots() { final String tokenA = "tokenA"; final String tokenB = "tokenB"; final String firstUser = "firstUser"; @@ -1583,7 +1546,7 @@ final HapiSpec autoAssociationRequiresOpenSlots() { } @HapiTest - final HapiSpec baseCryptoTransferFeeChargedAsExpected() { + final Stream baseCryptoTransferFeeChargedAsExpected() { final var expectedHbarXferPriceUsd = 0.0001; final var expectedHtsXferPriceUsd = 0.001; final var expectedNftXferPriceUsd = 0.001; @@ -1673,7 +1636,7 @@ final HapiSpec baseCryptoTransferFeeChargedAsExpected() { } @HapiTest - final HapiSpec okToSetInvalidPaymentHeaderForCostAnswer() { + final Stream okToSetInvalidPaymentHeaderForCostAnswer() { return defaultHapiSpec("OkToSetInvalidPaymentHeaderForCostAnswer") .given(cryptoTransfer(tinyBarsFromTo(DEFAULT_PAYER, FUNDING, 1L)) .via("misc")) @@ -1685,7 +1648,7 @@ final HapiSpec okToSetInvalidPaymentHeaderForCostAnswer() { @SuppressWarnings("java:S5960") @HapiTest - final HapiSpec tokenTransferFeesScaleAsExpected() { + final Stream tokenTransferFeesScaleAsExpected() { return defaultHapiSpec("TokenTransferFeesScaleAsExpected", NONDETERMINISTIC_TRANSACTION_FEES) .given( cryptoCreate("a"), @@ -1825,7 +1788,7 @@ public static String sdec(double d, int numDecimals) { } @HapiTest - final HapiSpec transferToNonAccountEntitiesReturnsInvalidAccountId() { + final Stream transferToNonAccountEntitiesReturnsInvalidAccountId() { AtomicReference invalidAccountId = new AtomicReference<>(); return defaultHapiSpec("TransferToNonAccountEntitiesReturnsInvalidAccountId", EXPECT_STREAMLINED_INGEST_RECORDS) @@ -1844,7 +1807,7 @@ final HapiSpec transferToNonAccountEntitiesReturnsInvalidAccountId() { } @HapiTest - final HapiSpec complexKeyAcctPaysForOwnTransfer() { + final Stream complexKeyAcctPaysForOwnTransfer() { SigControl enoughUniqueSigs = SigControl.threshSigs( 2, SigControl.threshSigs(1, OFF, OFF, OFF, OFF, OFF, OFF, ON), @@ -1863,7 +1826,7 @@ final HapiSpec complexKeyAcctPaysForOwnTransfer() { } @HapiTest - final HapiSpec twoComplexKeysRequired() { + final Stream twoComplexKeysRequired() { SigControl payerShape = threshOf(2, threshOf(1, 7), threshOf(3, 7)); SigControl receiverShape = SigControl.threshSigs(3, threshOf(2, 2), threshOf(3, 5), ON); @@ -1892,7 +1855,7 @@ final HapiSpec twoComplexKeysRequired() { } @HapiTest - final HapiSpec specialAccountsBalanceCheck() { + final Stream specialAccountsBalanceCheck() { return defaultHapiSpec("SpecialAccountsBalanceCheck") .given() .when() @@ -1902,7 +1865,7 @@ final HapiSpec specialAccountsBalanceCheck() { } @HapiTest - final HapiSpec transferWithMissingAccountGetsInvalidAccountId() { + final Stream transferWithMissingAccountGetsInvalidAccountId() { return defaultHapiSpec("transferWithMissingAccountGetsInvalidAccountId", EXPECT_STREAMLINED_INGEST_RECORDS) .given(cryptoCreate(PAYEE_SIG_REQ).receiverSigRequired(true)) .when(cryptoTransfer(tinyBarsFromTo("1.2.3", PAYEE_SIG_REQ, 1_000L)) @@ -1912,7 +1875,7 @@ final HapiSpec transferWithMissingAccountGetsInvalidAccountId() { } @HapiTest - final HapiSpec vanillaTransferSucceeds() { + final Stream vanillaTransferSucceeds() { long initialBalance = HapiSpecSetup.getDefaultInstance().defaultBalance(); return defaultHapiSpec("VanillaTransferSucceeds", FULLY_NONDETERMINISTIC) @@ -1943,7 +1906,7 @@ final HapiSpec vanillaTransferSucceeds() { } @HapiTest - final HapiSpec hapiTransferFromForNFTWithCustomFeesWithAllowance() { + final Stream hapiTransferFromForNFTWithCustomFeesWithAllowance() { final var NFT_TOKEN_WITH_FIXED_HBAR_FEE = "nftTokenWithFixedHbarFee"; final var NFT_TOKEN_WITH_FIXED_TOKEN_FEE = "nftTokenWithFixedTokenFee"; final var NFT_TOKEN_WITH_ROYALTY_FEE_WITH_HBAR_FALLBACK = "nftTokenWithRoyaltyFeeWithHbarFallback"; @@ -2093,7 +2056,7 @@ final HapiSpec hapiTransferFromForNFTWithCustomFeesWithAllowance() { } @HapiTest - final HapiSpec hapiTransferFromForFungibleTokenWithCustomFeesWithAllowance() { + final Stream hapiTransferFromForFungibleTokenWithCustomFeesWithAllowance() { final var FUNGIBLE_TOKEN_WITH_FIXED_HBAR_FEE = "fungibleTokenWithFixedHbarFee"; final var FUNGIBLE_TOKEN_WITH_FIXED_TOKEN_FEE = "fungibleTokenWithFixedTokenFee"; final var FUNGIBLE_TOKEN_WITH_FRACTIONAL_FEE = "fungibleTokenWithFractionalTokenFee"; @@ -2182,10 +2145,10 @@ final HapiSpec hapiTransferFromForFungibleTokenWithCustomFeesWithAllowance() { } @HapiTest - final HapiSpec testTransferToSystemAccounts() { + final Stream testTransferToSystemAccounts() { final var contract = "CryptoTransfer"; final var systemAccounts = List.of(359L, 360L, 361L); - final HapiSpecOperation[] opsArray = new HapiSpecOperation[systemAccounts.size() * 3]; + final var opsArray = new HapiSpecOperation[systemAccounts.size() * 3]; for (int i = 0; i < systemAccounts.size(); i++) { opsArray[i] = contractCall(contract, "sendViaTransfer", mirrorAddrWith(systemAccounts.get(i))) @@ -2219,7 +2182,7 @@ final HapiSpec testTransferToSystemAccounts() { } @HapiTest - final HapiSpec testTransferToSystemAccountsAndCheckSenderBalance() { + final Stream testTransferToSystemAccountsAndCheckSenderBalance() { final var transferContract = "CryptoTransfer"; final var balanceContract = "BalanceChecker46Version"; final var senderAccount = "detachedSenderAccount"; @@ -2241,7 +2204,7 @@ final HapiSpec testTransferToSystemAccountsAndCheckSenderBalance() { } @HapiTest - final HapiSpec transferInvalidTokenIdWithDecimals() { + final Stream transferInvalidTokenIdWithDecimals() { return defaultHapiSpec("transferInvalidTokenIdWithDecimals", FULLY_NONDETERMINISTIC) .given(cryptoCreate(TREASURY), withOpContext((spec, opLog) -> { final var acctCreate = cryptoCreate(PAYER).balance(ONE_HUNDRED_HBARS); @@ -2258,7 +2221,23 @@ final HapiSpec transferInvalidTokenIdWithDecimals() { } @HapiTest - final HapiSpec netAdjustmentsMustBeZero() { + final Stream balancesChangeOnTransfer() { + return defaultHapiSpec("BalancesChangeOnTransfer") + .given( + cryptoCreate("sponsor"), + cryptoCreate("beneficiary"), + balanceSnapshot("sponsorBefore", "sponsor"), + balanceSnapshot("beneficiaryBefore", "beneficiary")) + .when(cryptoTransfer(tinyBarsFromTo("sponsor", "beneficiary", 1L)) + .payingWith(GENESIS) + .memo("Hello World!")) + .then( + getAccountBalance("sponsor").hasTinyBars(changeFromSnapshot("sponsorBefore", -1L)), + getAccountBalance("beneficiary").hasTinyBars(changeFromSnapshot("beneficiaryBefore", +1L))); + } + + @HapiTest + final Stream netAdjustmentsMustBeZero() { final AtomicReference partyId = new AtomicReference<>(); final AtomicReference counterId = new AtomicReference<>(); final AtomicReference otherAccountId = new AtomicReference<>(); @@ -2296,7 +2275,7 @@ final HapiSpec netAdjustmentsMustBeZero() { } @HapiTest - final HapiSpec customFeesCannotCauseOverflow() { + final Stream customFeesCannotCauseOverflow() { final var secondFeeCollector = "secondFeeCollector"; return defaultHapiSpec("fixedFeesCannotCauseOverflow") .given( @@ -2318,9 +2297,4 @@ final HapiSpec customFeesCannotCauseOverflow() { .then(cryptoTransfer(moving(1L, "ft").between(PARTY, COUNTERPARTY)) .hasKnownStatus(INSUFFICIENT_SENDER_ACCOUNT_BALANCE_FOR_CUSTOM_FEE)); } - - @Override - protected Logger getResultsLogger() { - return LOG; - } } diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/crypto/CryptoUpdateSuite.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/crypto/CryptoUpdateSuite.java index 3d7121a0ebc3..5f623b4d3439 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/crypto/CryptoUpdateSuite.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/crypto/CryptoUpdateSuite.java @@ -47,6 +47,11 @@ import static com.hedera.services.bdd.spec.utilops.mod.ModificationUtils.withSuccessivelyVariedBodyIds; import static com.hedera.services.bdd.spec.utilops.records.SnapshotMatchMode.EXPECT_STREAMLINED_INGEST_RECORDS; import static com.hedera.services.bdd.spec.utilops.records.SnapshotMatchMode.NONDETERMINISTIC_TRANSACTION_FEES; +import static com.hedera.services.bdd.suites.HapiSuite.GENESIS; +import static com.hedera.services.bdd.suites.HapiSuite.ONE_HBAR; +import static com.hedera.services.bdd.suites.HapiSuite.ONE_HUNDRED_HBARS; +import static com.hedera.services.bdd.suites.HapiSuite.THREE_MONTHS_IN_SECONDS; +import static com.hedera.services.bdd.suites.HapiSuite.ZERO_BYTE_MEMO; import static com.hedera.services.bdd.suites.contract.hapi.ContractUpdateSuite.ADMIN_KEY; import static com.hederahashgraph.api.proto.java.HederaFunctionality.CryptoUpdate; import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.EXISTING_AUTOMATIC_ASSOCIATIONS_EXCEED_GIVEN_LIMIT; @@ -58,32 +63,24 @@ import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.SUCCESS; import com.hedera.services.bdd.junit.HapiTest; -import com.hedera.services.bdd.junit.HapiTestSuite; -import com.hedera.services.bdd.spec.HapiSpec; import com.hedera.services.bdd.spec.HapiSpecSetup; import com.hedera.services.bdd.spec.assertions.AccountInfoAsserts; import com.hedera.services.bdd.spec.assertions.ContractInfoAsserts; import com.hedera.services.bdd.spec.keys.KeyLabel; import com.hedera.services.bdd.spec.keys.KeyShape; import com.hedera.services.bdd.spec.keys.SigControl; -import com.hedera.services.bdd.suites.HapiSuite; import com.hederahashgraph.api.proto.java.ContractID; import com.hederahashgraph.api.proto.java.Key; import com.hederahashgraph.api.proto.java.TokenType; import java.time.Instant; -import java.util.List; -import java.util.Map; import java.util.Set; import java.util.concurrent.atomic.AtomicLong; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; +import java.util.stream.Stream; +import org.junit.jupiter.api.DynamicTest; import org.junit.jupiter.api.Tag; -@HapiTestSuite @Tag(CRYPTO) -public class CryptoUpdateSuite extends HapiSuite { - private static final Logger log = LogManager.getLogger(CryptoUpdateSuite.class); - +public class CryptoUpdateSuite { private static final long DEFAULT_MAX_LIFETIME = Long.parseLong(HapiSpecSetup.getDefaultNodeProps().get("entities.maxLifetime")); public static final String REPEATING_KEY = "repeatingKey"; @@ -91,10 +88,6 @@ public class CryptoUpdateSuite extends HapiSuite { public static final String ORIG_KEY = "origKey"; public static final String UPD_KEY = "updKey"; - public static void main(String... args) { - new CryptoUpdateSuite().runSuiteConcurrentWithOverrides(Map.of("spec.autoScheduledTxns", "")); - } - private final SigControl twoLevelThresh = SigControl.threshSigs( 2, SigControl.threshSigs(1, ANY, ANY, ANY, ANY, ANY, ANY, ANY), @@ -118,32 +111,8 @@ public static void main(String... args) { private final String TARGET_KEY = "twoLevelThreshWithOverlap"; private final String TARGET_ACCOUNT = "complexKeyAccount"; - @Override - public boolean canRunConcurrent() { - return true; - } - - @Override - public List getSpecsInSuite() { - return List.of( - updateWithUniqueSigs(), - updateWithOverlappingSigs(), - updateWithOneEffectiveSig(), - canUpdateMemo(), - updateFailsWithInsufficientSigs(), - cannotSetThresholdNegative(), - updateWithEmptyKeyFails(), - updateFailsIfMissingSigs(), - updateFailsWithContractKey(), - updateFailsWithOverlyLongLifetime(), - usdFeeAsExpectedCryptoUpdate(), - sysAccountKeyUpdateBySpecialWontNeedNewKeyTxnSign(), - updateMaxAutoAssociationsWorks(), - updateStakingFieldsWorks()); - } - @HapiTest - final HapiSpec idVariantsTreatedAsExpected() { + final Stream idVariantsTreatedAsExpected() { return defaultHapiSpec("idVariantsTreatedAsExpected") .given(cryptoCreate("user").stakedAccountId("0.0.20").declinedReward(true)) .when() @@ -152,7 +121,7 @@ final HapiSpec idVariantsTreatedAsExpected() { } @HapiTest - final HapiSpec updateStakingFieldsWorks() { + final Stream updateStakingFieldsWorks() { return defaultHapiSpec("updateStakingFieldsWorks", NONDETERMINISTIC_TRANSACTION_FEES) .given( newKeyNamed(ADMIN_KEY), @@ -202,7 +171,7 @@ final HapiSpec updateStakingFieldsWorks() { } @HapiTest - final HapiSpec usdFeeAsExpectedCryptoUpdate() { + final Stream usdFeeAsExpectedCryptoUpdate() { double autoAssocSlotPrice = 0.0018; double baseFee = 0.00022; double plusOneSlotFee = baseFee + autoAssocSlotPrice; @@ -257,7 +226,7 @@ final HapiSpec usdFeeAsExpectedCryptoUpdate() { } @HapiTest - final HapiSpec updateFailsWithOverlyLongLifetime() { + final Stream updateFailsWithOverlyLongLifetime() { final var smallBuffer = 12_345L; final var excessiveExpiry = DEFAULT_MAX_LIFETIME + Instant.now().getEpochSecond() + smallBuffer; return defaultHapiSpec("UpdateFailsWithOverlyLongLifetime") @@ -267,7 +236,7 @@ final HapiSpec updateFailsWithOverlyLongLifetime() { } @HapiTest - final HapiSpec sysAccountKeyUpdateBySpecialWontNeedNewKeyTxnSign() { + final Stream sysAccountKeyUpdateBySpecialWontNeedNewKeyTxnSign() { String sysAccount = "0.0.99"; String randomAccount = "randomAccount"; String firstKey = "firstKey"; @@ -293,7 +262,7 @@ final HapiSpec sysAccountKeyUpdateBySpecialWontNeedNewKeyTxnSign() { } @HapiTest - final HapiSpec canUpdateMemo() { + final Stream canUpdateMemo() { String firstMemo = "First"; String secondMemo = "Second"; return defaultHapiSpec("CanUpdateMemo") @@ -309,7 +278,7 @@ final HapiSpec canUpdateMemo() { } @HapiTest - final HapiSpec updateWithUniqueSigs() { + final Stream updateWithUniqueSigs() { return defaultHapiSpec("UpdateWithUniqueSigs", NONDETERMINISTIC_TRANSACTION_FEES) .given( newKeyNamed(TARGET_KEY).shape(twoLevelThresh).labels(overlappingKeys), @@ -321,7 +290,7 @@ final HapiSpec updateWithUniqueSigs() { } @HapiTest - final HapiSpec updateWithOneEffectiveSig() { + final Stream updateWithOneEffectiveSig() { KeyLabel oneUniqueKey = complex(complex("X", "X", "X", "X", "X", "X", "X"), complex("X", "X", "X", "X", "X", "X", "X")); SigControl singleSig = SigControl.threshSigs( @@ -341,7 +310,7 @@ final HapiSpec updateWithOneEffectiveSig() { } @HapiTest - final HapiSpec updateWithOverlappingSigs() { + final Stream updateWithOverlappingSigs() { return defaultHapiSpec("UpdateWithOverlappingSigs", NONDETERMINISTIC_TRANSACTION_FEES) .given( newKeyNamed(TARGET_KEY).shape(twoLevelThresh).labels(overlappingKeys), @@ -354,7 +323,7 @@ final HapiSpec updateWithOverlappingSigs() { } @HapiTest - final HapiSpec updateFailsWithContractKey() { + final Stream updateFailsWithContractKey() { AtomicLong id = new AtomicLong(); final var CONTRACT = "Multipurpose"; return defaultHapiSpec( @@ -376,7 +345,7 @@ final HapiSpec updateFailsWithContractKey() { } @HapiTest - final HapiSpec updateFailsWithInsufficientSigs() { + final Stream updateFailsWithInsufficientSigs() { return defaultHapiSpec("UpdateFailsWithInsufficientSigs", NONDETERMINISTIC_TRANSACTION_FEES) .given( newKeyNamed(TARGET_KEY).shape(twoLevelThresh).labels(overlappingKeys), @@ -389,7 +358,7 @@ final HapiSpec updateFailsWithInsufficientSigs() { } @HapiTest - final HapiSpec cannotSetThresholdNegative() { + final Stream cannotSetThresholdNegative() { return defaultHapiSpec("CannotSetThresholdNegative", NONDETERMINISTIC_TRANSACTION_FEES) .given(cryptoCreate(TEST_ACCOUNT)) .when() @@ -397,7 +366,7 @@ final HapiSpec cannotSetThresholdNegative() { } @HapiTest - final HapiSpec updateFailsIfMissingSigs() { + final Stream updateFailsIfMissingSigs() { SigControl origKeySigs = SigControl.threshSigs(3, ON, ON, SigControl.threshSigs(1, OFF, ON)); SigControl updKeySigs = SigControl.listSigs(ON, OFF, SigControl.threshSigs(1, ON, OFF, OFF, OFF)); @@ -416,7 +385,7 @@ final HapiSpec updateFailsIfMissingSigs() { } @HapiTest - final HapiSpec updateWithEmptyKeyFails() { + final Stream updateWithEmptyKeyFails() { SigControl updKeySigs = threshOf(0, 0); return defaultHapiSpec("updateWithEmptyKeyFails", NONDETERMINISTIC_TRANSACTION_FEES) @@ -428,7 +397,7 @@ final HapiSpec updateWithEmptyKeyFails() { } @HapiTest - final HapiSpec updateMaxAutoAssociationsWorks() { + final Stream updateMaxAutoAssociationsWorks() { final int maxAllowedAssociations = 5000; final int originalMax = 2; final int newBadMax = originalMax - 1; @@ -487,9 +456,4 @@ final HapiSpec updateMaxAutoAssociationsWorks() { .newMaxAutomaticAssociations(maxAllowedAssociations + 1) .hasKnownStatus(REQUESTED_NUM_AUTOMATIC_ASSOCIATIONS_EXCEEDS_ASSOCIATION_LIMIT)); } - - @Override - protected Logger getResultsLogger() { - return log; - } } diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/crypto/CrytoCreateSuiteWithUTF8.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/crypto/CrytoCreateSuiteWithUTF8.java deleted file mode 100644 index 9067440a65ad..000000000000 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/crypto/CrytoCreateSuiteWithUTF8.java +++ /dev/null @@ -1,78 +0,0 @@ -/* - * Copyright (C) 2020-2024 Hedera Hashgraph, LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.hedera.services.bdd.suites.crypto; - -import static com.hedera.services.bdd.junit.TestTags.CRYPTO; -import static com.hedera.services.bdd.spec.HapiSpec.UTF8Mode.FALSE; -import static com.hedera.services.bdd.spec.HapiSpec.customHapiSpec; -import static com.hedera.services.bdd.spec.HapiSpec.defaultHapiSpec; -import static com.hedera.services.bdd.spec.queries.QueryVerbs.getTxnRecord; -import static com.hedera.services.bdd.spec.transactions.TxnVerbs.cryptoCreate; - -import com.hedera.services.bdd.junit.HapiTest; -import com.hedera.services.bdd.junit.HapiTestSuite; -import com.hedera.services.bdd.spec.HapiSpec; -import com.hedera.services.bdd.suites.HapiSuite; -import java.util.Arrays; -import java.util.List; -import java.util.Map; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; -import org.junit.jupiter.api.Tag; - -@HapiTestSuite -@Tag(CRYPTO) -public class CrytoCreateSuiteWithUTF8 extends HapiSuite { - private static final Logger log = LogManager.getLogger(CrytoCreateSuiteWithUTF8.class); - - HapiSpec.UTF8Mode utf8Mode = FALSE; - - public static void main(String... args) { - new CrytoCreateSuiteWithUTF8().runSuiteSync(); - } - - @Override - public List getSpecsInSuite() { - return allOf(positiveTests()); - } - - private List positiveTests() { - return Arrays.asList(createCryptoTxvWithUTF8Memo(), cryptoCreateTxnCustomSpec()); - } - - @HapiTest - final HapiSpec createCryptoTxvWithUTF8Memo() { - return defaultHapiSpec("CreateCryptoTxvWithUTF8Memo") - .given(cryptoCreate("UTF8MemoTestAccount").via("utf8MemoTxn")) - .when() - .then(getTxnRecord("utf8MemoTxn").logged()); - } - - @HapiTest - final HapiSpec cryptoCreateTxnCustomSpec() { - return customHapiSpec("UTF8CustomSpecMemoTxn") - .withProperties(Map.of("default.useMemoUTF8", utf8Mode.toString())) - .given(cryptoCreate("UTF8CustomSpecTestAccount").via("utf8CustomSpecMemoTxn")) - .when() - .then(getTxnRecord("utf8CustomSpecMemoTxn").logged()); - } - - @Override - protected Logger getResultsLogger() { - return log; - } -} diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/crypto/HelloWorldSpec.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/crypto/HelloWorldSpec.java deleted file mode 100644 index 13ec3a9f0104..000000000000 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/crypto/HelloWorldSpec.java +++ /dev/null @@ -1,73 +0,0 @@ -/* - * Copyright (C) 2020-2024 Hedera Hashgraph, LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.hedera.services.bdd.suites.crypto; - -import static com.hedera.services.bdd.junit.TestTags.CRYPTO; -import static com.hedera.services.bdd.spec.HapiSpec.defaultHapiSpec; -import static com.hedera.services.bdd.spec.assertions.AccountInfoAsserts.changeFromSnapshot; -import static com.hedera.services.bdd.spec.queries.QueryVerbs.getAccountBalance; -import static com.hedera.services.bdd.spec.transactions.TxnVerbs.cryptoCreate; -import static com.hedera.services.bdd.spec.transactions.TxnVerbs.cryptoTransfer; -import static com.hedera.services.bdd.spec.transactions.crypto.HapiCryptoTransfer.tinyBarsFromTo; -import static com.hedera.services.bdd.spec.utilops.UtilVerbs.balanceSnapshot; - -import com.hedera.services.bdd.junit.HapiTest; -import com.hedera.services.bdd.junit.HapiTestSuite; -import com.hedera.services.bdd.spec.HapiSpec; -import com.hedera.services.bdd.suites.HapiSuite; -import java.util.List; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; -import org.junit.jupiter.api.Tag; - -@HapiTestSuite -@Tag(CRYPTO) -public class HelloWorldSpec extends HapiSuite { - private static final Logger log = LogManager.getLogger(HelloWorldSpec.class); - - public static void main(String... args) { - new HelloWorldSpec().runSuiteSync(); - } - - @Override - public List getSpecsInSuite() { - return List.of(new HapiSpec[] { - balancesChangeOnTransfer(), - }); - } - - @HapiTest - final HapiSpec balancesChangeOnTransfer() { - return defaultHapiSpec("BalancesChangeOnTransfer") - .given( - cryptoCreate("sponsor"), - cryptoCreate("beneficiary"), - balanceSnapshot("sponsorBefore", "sponsor"), - balanceSnapshot("beneficiaryBefore", "beneficiary")) - .when(cryptoTransfer(tinyBarsFromTo("sponsor", "beneficiary", 1L)) - .payingWith(GENESIS) - .memo("Hello World!")) - .then( - getAccountBalance("sponsor").hasTinyBars(changeFromSnapshot("sponsorBefore", -1L)), - getAccountBalance("beneficiary").hasTinyBars(changeFromSnapshot("beneficiaryBefore", +1L))); - } - - @Override - protected Logger getResultsLogger() { - return log; - } -} diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/crypto/HollowAccountFinalizationSuite.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/crypto/HollowAccountFinalizationSuite.java index e993664d2afa..b40f0d27efd7 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/crypto/HollowAccountFinalizationSuite.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/crypto/HollowAccountFinalizationSuite.java @@ -50,6 +50,16 @@ import static com.hedera.services.bdd.spec.utilops.records.SnapshotMatchMode.NONDETERMINISTIC_CONTRACT_CALL_RESULTS; import static com.hedera.services.bdd.spec.utilops.records.SnapshotMatchMode.NONDETERMINISTIC_FUNCTION_PARAMETERS; import static com.hedera.services.bdd.spec.utilops.records.SnapshotMatchMode.NONDETERMINISTIC_TRANSACTION_FEES; +import static com.hedera.services.bdd.suites.HapiSuite.DEFAULT_PAYER; +import static com.hedera.services.bdd.suites.HapiSuite.EMPTY_KEY; +import static com.hedera.services.bdd.suites.HapiSuite.FIVE_HBARS; +import static com.hedera.services.bdd.suites.HapiSuite.GENESIS; +import static com.hedera.services.bdd.suites.HapiSuite.ONE_HBAR; +import static com.hedera.services.bdd.suites.HapiSuite.ONE_HUNDRED_HBARS; +import static com.hedera.services.bdd.suites.HapiSuite.ONE_MILLION_HBARS; +import static com.hedera.services.bdd.suites.HapiSuite.SECP_256K1_SHAPE; +import static com.hedera.services.bdd.suites.HapiSuite.SECP_256K1_SOURCE_KEY; +import static com.hedera.services.bdd.suites.HapiSuite.THREE_MONTHS_IN_SECONDS; import static com.hedera.services.bdd.suites.contract.Utils.aaWith; import static com.hedera.services.bdd.suites.contract.hapi.ContractUpdateSuite.ADMIN_KEY; import static com.hedera.services.bdd.suites.crypto.AutoCreateUtils.createHollowAccountFrom; @@ -62,13 +72,11 @@ import com.esaulpaugh.headlong.abi.Tuple; import com.google.protobuf.ByteString; import com.hedera.services.bdd.junit.HapiTest; -import com.hedera.services.bdd.junit.HapiTestSuite; import com.hedera.services.bdd.spec.HapiSpec; import com.hedera.services.bdd.spec.queries.crypto.HapiGetAccountInfo; import com.hedera.services.bdd.spec.queries.meta.HapiGetTxnRecord; import com.hedera.services.bdd.spec.transactions.crypto.HapiCryptoTransfer; import com.hedera.services.bdd.spec.utilops.records.SnapshotMatchMode; -import com.hedera.services.bdd.suites.HapiSuite; import com.hederahashgraph.api.proto.java.AccountID; import com.hederahashgraph.api.proto.java.TokenID; import com.hederahashgraph.api.proto.java.TokenTransferList; @@ -79,14 +87,12 @@ import java.util.concurrent.atomic.AtomicLong; import java.util.concurrent.atomic.AtomicReference; import java.util.function.Function; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; +import java.util.stream.Stream; +import org.junit.jupiter.api.DynamicTest; import org.junit.jupiter.api.Tag; -@HapiTestSuite @Tag(CRYPTO) -public class HollowAccountFinalizationSuite extends HapiSuite { - private static final Logger LOG = LogManager.getLogger(HollowAccountFinalizationSuite.class); +public class HollowAccountFinalizationSuite { private static final String ANOTHER_SECP_256K1_SOURCE_KEY = "anotherSecp256k1Alias"; private static final String PAY_RECEIVABLE = "PayReceivable"; private static final long INITIAL_BALANCE = 1000L; @@ -100,42 +106,8 @@ public class HollowAccountFinalizationSuite extends HapiSuite { private static final String TOKEN_TREASURY = "treasury"; private static final String VANILLA_TOKEN = "TokenD"; - public static void main(String... args) { - new HollowAccountFinalizationSuite().runSuiteAsync(); - } - - @Override - public boolean canRunConcurrent() { - return true; - } - - @Override - protected Logger getResultsLogger() { - return LOG; - } - - @Override - public List getSpecsInSuite() { - return List.of( - hollowAccountCompletionWithCryptoTransfer(), - hollowAccountCompletionWithContractCreate(), - hollowAccountCompletionWithContractCall(), - hollowAccountCompletionWithTokenAssociation(), - hollowAccountCompletionWithTokenTransfer(), - hollowAccountCompletionViaNonReqSigIsNotAllowed(), - hollowAccountCompletionWhenHollowAccountSigRequiredInOtherReqSigs(), - tooManyHollowAccountFinalizationsShouldFail(), - completedHollowAccountsTransfer(), - hollowAccountFinalizationWhenAccountNotPresentInPreHandle(), - hollowAccountFinalizationOccursOnlyOnceWhenMultipleFinalizationTensComeInAtTheSameTime(), - txnWith2CompletionsAndAnother2PrecedingChildRecords(), - hollowPayerAndOtherReqSignerBothGetCompletedInASingleTransaction(), - hollowAccountCompletionIsPersistedEvenIfTxnFails(), - precompileTransferFromHollowAccountWithNeededSigFailsAndDoesNotFinalizeAccount()); - } - @HapiTest - final HapiSpec hollowAccountCompletionWithTokenTransfer() { + final Stream hollowAccountCompletionWithTokenTransfer() { final var fungibleToken = "fungibleToken"; final AtomicReference ftId = new AtomicReference<>(); final AtomicReference partyId = new AtomicReference<>(); @@ -232,7 +204,7 @@ final HapiSpec hollowAccountCompletionWithTokenTransfer() { } @HapiTest - final HapiSpec hollowAccountCompletionWithTokenAssociation() { + final Stream hollowAccountCompletionWithTokenAssociation() { return defaultHapiSpec("HollowAccountCompletionWithTokenAssociation") .given( newKeyNamed(SECP_256K1_SOURCE_KEY).shape(SECP_256K1_SHAPE), @@ -261,7 +233,7 @@ final HapiSpec hollowAccountCompletionWithTokenAssociation() { } @HapiTest - final HapiSpec hollowAccountFinalizationWhenAccountNotPresentInPreHandle() { + final Stream hollowAccountFinalizationWhenAccountNotPresentInPreHandle() { final var ECDSA_2 = "ECDSA_2"; return defaultHapiSpec("hollowAccountFinalizationWhenAccountNotPresentInPreHandle") .given( @@ -304,7 +276,7 @@ final HapiSpec hollowAccountFinalizationWhenAccountNotPresentInPreHandle() { } @HapiTest - final HapiSpec hollowAccountFinalizationOccursOnlyOnceWhenMultipleFinalizationTensComeInAtTheSameTime() { + final Stream hollowAccountFinalizationOccursOnlyOnceWhenMultipleFinalizationTensComeInAtTheSameTime() { final var ECDSA_2 = "ECDSA_2"; return defaultHapiSpec( "hollowAccountFinalizationOccursOnlyOnceWhenMultipleFinalizationTensComeInAtTheSameTime", @@ -359,7 +331,7 @@ final HapiSpec hollowAccountFinalizationOccursOnlyOnceWhenMultipleFinalizationTe } @HapiTest - final HapiSpec hollowAccountCompletionWithCryptoTransfer() { + final Stream hollowAccountCompletionWithCryptoTransfer() { return defaultHapiSpec("HollowAccountCompletionWithCryptoTransfer") .given(newKeyNamed(SECP_256K1_SOURCE_KEY).shape(SECP_256K1_SHAPE)) .when(createHollowAccountFrom(SECP_256K1_SOURCE_KEY)) @@ -402,7 +374,7 @@ final HapiSpec hollowAccountCompletionWithCryptoTransfer() { } @HapiTest - final HapiSpec hollowAccountCompletionWhenHollowAccountSigRequiredInOtherReqSigs() { + final Stream hollowAccountCompletionWhenHollowAccountSigRequiredInOtherReqSigs() { return defaultHapiSpec("hollowAccountCompletionWhenHollowAccountSigRequiredInOtherReqSigs") .given( newKeyNamed(SECP_256K1_SOURCE_KEY).shape(SECP_256K1_SHAPE), @@ -453,7 +425,7 @@ final HapiSpec hollowAccountCompletionWhenHollowAccountSigRequiredInOtherReqSigs } @HapiTest - final HapiSpec hollowAccountCompletionWithContractCreate() { + final Stream hollowAccountCompletionWithContractCreate() { final var CONTRACT = "CreateTrivial"; return defaultHapiSpec("HollowAccountCompletionWithContractCreate") .given( @@ -483,7 +455,7 @@ final HapiSpec hollowAccountCompletionWithContractCreate() { } @HapiTest - final HapiSpec hollowAccountCompletionWithContractCall() { + final Stream hollowAccountCompletionWithContractCall() { final var DEPOSIT_AMOUNT = 1000; return defaultHapiSpec( "HollowAccountCompletionWithContractCall", @@ -516,7 +488,7 @@ final HapiSpec hollowAccountCompletionWithContractCall() { } @HapiTest - final HapiSpec hollowAccountCompletionViaNonReqSigIsNotAllowed() { + final Stream hollowAccountCompletionViaNonReqSigIsNotAllowed() { final var DEPOSIT_AMOUNT = 1000; return defaultHapiSpec("hollowAccountCompletionViaNonReqSigIsNotAllowed") .given( @@ -556,7 +528,7 @@ final HapiSpec hollowAccountCompletionViaNonReqSigIsNotAllowed() { } @HapiTest - final HapiSpec tooManyHollowAccountFinalizationsShouldFail() { + final Stream tooManyHollowAccountFinalizationsShouldFail() { final var ECDSA_KEY_1 = "ECDSA_KEY_1"; final var ECDSA_KEY_2 = "ECDSA_KEY_2"; final var ECDSA_KEY_3 = "ECDSA_KEY_3"; @@ -615,7 +587,7 @@ final HapiSpec tooManyHollowAccountFinalizationsShouldFail() { } @HapiTest - final HapiSpec completedHollowAccountsTransfer() { + final Stream completedHollowAccountsTransfer() { return defaultHapiSpec("CompletedHollowAccountsTransfer", SnapshotMatchMode.NONDETERMINISTIC_TRANSACTION_FEES) .given( newKeyNamed(SECP_256K1_SOURCE_KEY).shape(SECP_256K1_SHAPE), @@ -706,7 +678,7 @@ final HapiSpec completedHollowAccountsTransfer() { } @HapiTest - final HapiSpec txnWith2CompletionsAndAnother2PrecedingChildRecords() { + final Stream txnWith2CompletionsAndAnother2PrecedingChildRecords() { final var ecdsaKey2 = "ecdsaKey2"; final var recipientKey = "recipient"; final var recipientKey2 = "recipient2"; @@ -779,7 +751,7 @@ final HapiSpec txnWith2CompletionsAndAnother2PrecedingChildRecords() { } @HapiTest - final HapiSpec hollowPayerAndOtherReqSignerBothGetCompletedInASingleTransaction() { + final Stream hollowPayerAndOtherReqSignerBothGetCompletedInASingleTransaction() { final var ecdsaKey2 = "ecdsaKey2"; final var recipientKey = "recipient"; return defaultHapiSpec( @@ -850,7 +822,7 @@ final HapiSpec hollowPayerAndOtherReqSignerBothGetCompletedInASingleTransaction( } @HapiTest - final HapiSpec hollowAccountCompletionIsPersistedEvenIfTxnFails() { + final Stream hollowAccountCompletionIsPersistedEvenIfTxnFails() { return defaultHapiSpec("hollowAccountCompletionIsPersistedEvenIfTxnFails") .given(newKeyNamed(SECP_256K1_SOURCE_KEY).shape(SECP_256K1_SHAPE)) .when(createHollowAccountFrom(SECP_256K1_SOURCE_KEY)) @@ -880,7 +852,7 @@ final HapiSpec hollowAccountCompletionIsPersistedEvenIfTxnFails() { } @HapiTest - final HapiSpec precompileTransferFromHollowAccountWithNeededSigFailsAndDoesNotFinalizeAccount() { + final Stream precompileTransferFromHollowAccountWithNeededSigFailsAndDoesNotFinalizeAccount() { final var receiver = "receiver"; final var ft = "ft"; final String CONTRACT = "CryptoTransfer"; diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/crypto/MiscCryptoSuite.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/crypto/MiscCryptoSuite.java index 7e1a453f344f..1439cc36d861 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/crypto/MiscCryptoSuite.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/crypto/MiscCryptoSuite.java @@ -18,70 +18,65 @@ import static com.hedera.services.bdd.junit.TestTags.CRYPTO; import static com.hedera.services.bdd.spec.HapiSpec.defaultHapiSpec; +import static com.hedera.services.bdd.spec.assertions.AccountDetailsAsserts.accountDetailsWith; import static com.hedera.services.bdd.spec.keys.KeyShape.SIMPLE; import static com.hedera.services.bdd.spec.queries.QueryVerbs.getAccountBalance; import static com.hedera.services.bdd.spec.queries.QueryVerbs.getAccountDetails; +import static com.hedera.services.bdd.spec.queries.QueryVerbs.getAccountDetailsNoPayment; import static com.hedera.services.bdd.spec.queries.QueryVerbs.getAccountInfo; import static com.hedera.services.bdd.spec.queries.QueryVerbs.getAccountRecords; import static com.hedera.services.bdd.spec.queries.QueryVerbs.getReceipt; import static com.hedera.services.bdd.spec.queries.QueryVerbs.getTxnRecord; +import static com.hedera.services.bdd.spec.transactions.TxnVerbs.cryptoApproveAllowance; import static com.hedera.services.bdd.spec.transactions.TxnVerbs.cryptoCreate; import static com.hedera.services.bdd.spec.transactions.TxnVerbs.cryptoTransfer; import static com.hedera.services.bdd.spec.transactions.TxnVerbs.cryptoUpdate; +import static com.hedera.services.bdd.spec.transactions.TxnVerbs.mintToken; +import static com.hedera.services.bdd.spec.transactions.TxnVerbs.tokenAssociate; +import static com.hedera.services.bdd.spec.transactions.TxnVerbs.tokenCreate; import static com.hedera.services.bdd.spec.transactions.crypto.HapiCryptoTransfer.tinyBarsFromTo; +import static com.hedera.services.bdd.spec.transactions.token.TokenMovement.movingUnique; +import static com.hedera.services.bdd.spec.utilops.UtilVerbs.getAccountNftInfosNotSupported; +import static com.hedera.services.bdd.spec.utilops.UtilVerbs.getBySolidityIdNotSupported; +import static com.hedera.services.bdd.spec.utilops.UtilVerbs.getClaimNotSupported; +import static com.hedera.services.bdd.spec.utilops.UtilVerbs.getFastRecordNotSupported; +import static com.hedera.services.bdd.spec.utilops.UtilVerbs.getStakersNotSupported; +import static com.hedera.services.bdd.spec.utilops.UtilVerbs.getTokenNftInfosNotSupported; import static com.hedera.services.bdd.spec.utilops.UtilVerbs.newKeyNamed; import static com.hedera.services.bdd.spec.utilops.UtilVerbs.reduceFeeFor; import static com.hedera.services.bdd.spec.utilops.UtilVerbs.sendModified; +import static com.hedera.services.bdd.spec.utilops.UtilVerbs.sendModifiedWithFixedPayer; import static com.hedera.services.bdd.spec.utilops.UtilVerbs.verifyAddLiveHashNotSupported; import static com.hedera.services.bdd.spec.utilops.UtilVerbs.verifyUserFreezeNotAuthorized; import static com.hedera.services.bdd.spec.utilops.UtilVerbs.withOpContext; import static com.hedera.services.bdd.spec.utilops.mod.ModificationUtils.withSuccessivelyVariedQueryIds; +import static com.hedera.services.bdd.suites.HapiSuite.DEFAULT_PAYER; +import static com.hedera.services.bdd.suites.HapiSuite.FUNDING; +import static com.hedera.services.bdd.suites.HapiSuite.GENESIS; +import static com.hedera.services.bdd.suites.HapiSuite.ONE_HBAR; +import static com.hedera.services.bdd.suites.HapiSuite.ONE_HUNDRED_HBARS; +import static com.hedera.services.bdd.suites.HapiSuite.TOKEN_TREASURY; import static com.hederahashgraph.api.proto.java.HederaFunctionality.CryptoTransfer; import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.INSUFFICIENT_TX_FEE; import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.INVALID_SIGNATURE; +import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.NOT_SUPPORTED; import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.OK; import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.SUCCESS; +import static com.hederahashgraph.api.proto.java.TokenType.NON_FUNGIBLE_UNIQUE; +import com.google.protobuf.ByteString; import com.hedera.services.bdd.junit.HapiTest; -import com.hedera.services.bdd.junit.HapiTestSuite; -import com.hedera.services.bdd.spec.HapiSpec; -import com.hedera.services.bdd.suites.HapiSuite; -import java.util.Arrays; +import com.hederahashgraph.api.proto.java.TokenSupplyType; +import com.hederahashgraph.api.proto.java.TokenType; import java.util.List; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; +import java.util.stream.Stream; +import org.junit.jupiter.api.DynamicTest; import org.junit.jupiter.api.Tag; -@HapiTestSuite @Tag(CRYPTO) -public class MiscCryptoSuite extends HapiSuite { - private static final Logger log = LogManager.getLogger(MiscCryptoSuite.class); - - public static void main(String... args) { - new MiscCryptoSuite().runSuiteSync(); - } - - @Override - public List getSpecsInSuite() { - return allOf( - positiveTests() - // negativeTests() - ); - } - - private List positiveTests() { - return Arrays.asList( - // transferChangesBalance() - // getsGenesisBalance() - reduceTransferFee(), sysAccountKeyUpdateBySpecialWontNeedNewKeyTxnSign()); - } - - private List negativeTests() { - return List.of(updateWithOutOfDateKeyFails()); - } - +public class MiscCryptoSuite { @HapiTest - final HapiSpec unsupportedAndUnauthorizedTransactionsAreNotThrottled() { + final Stream unsupportedAndUnauthorizedTransactionsAreNotThrottled() { return defaultHapiSpec("unsupportedAndUnauthorizedTransactionsAreNotThrottled") .given() .when() @@ -89,7 +84,21 @@ final HapiSpec unsupportedAndUnauthorizedTransactionsAreNotThrottled() { } @HapiTest - final HapiSpec sysAccountKeyUpdateBySpecialWontNeedNewKeyTxnSign() { + final Stream verifyUnsupportedOps() { + return defaultHapiSpec("VerifyUnsupportedOps") + .given() + .when() + .then( + getClaimNotSupported(), + getStakersNotSupported(), + getFastRecordNotSupported(), + getBySolidityIdNotSupported(), + getTokenNftInfosNotSupported(), + getAccountNftInfosNotSupported()); + } + + @HapiTest + final Stream sysAccountKeyUpdateBySpecialWontNeedNewKeyTxnSign() { String sysAccount = "0.0.977"; String randomAccountA = "randomAccountA"; String randomAccountB = "randomAccountB"; @@ -120,7 +129,7 @@ final HapiSpec sysAccountKeyUpdateBySpecialWontNeedNewKeyTxnSign() { } @HapiTest - final HapiSpec reduceTransferFee() { + final Stream reduceTransferFee() { final long REDUCED_NODE_FEE = 2L; final long REDUCED_NETWORK_FEE = 3L; final long REDUCED_SERVICE_FEE = 3L; @@ -145,7 +154,7 @@ final HapiSpec reduceTransferFee() { } @HapiTest - final HapiSpec getsGenesisBalance() { + final Stream getsGenesisBalance() { return defaultHapiSpec("GetsGenesisBalance") .given() .when() @@ -153,7 +162,7 @@ final HapiSpec getsGenesisBalance() { } @HapiTest - public HapiSpec getBalanceIdVariantsTreatedAsExpected() { + final Stream getBalanceIdVariantsTreatedAsExpected() { return defaultHapiSpec("getBalanceIdVariantsTreatedAsExpected") .given() .when() @@ -161,16 +170,17 @@ public HapiSpec getBalanceIdVariantsTreatedAsExpected() { } @HapiTest - public HapiSpec getDetailsIdVariantsTreatedAsExpected() { - return defaultHapiSpec("getBalanceIdVariantsTreatedAsExpected") + final Stream getDetailsIdVariantsTreatedAsExpected() { + return defaultHapiSpec("getDetailsIdVariantsTreatedAsExpected") .given() .when() - .then(sendModified(withSuccessivelyVariedQueryIds(), () -> getAccountDetails(DEFAULT_PAYER) - .payingWith(GENESIS))); + .then(sendModifiedWithFixedPayer( + withSuccessivelyVariedQueryIds(), + () -> getAccountDetails(DEFAULT_PAYER).payingWith(GENESIS))); } @HapiTest - public HapiSpec getRecordsIdVariantsTreatedAsExpected() { + final Stream getRecordsIdVariantsTreatedAsExpected() { return defaultHapiSpec("getRecordsIdVariantsTreatedAsExpected") .given() .when() @@ -178,7 +188,7 @@ public HapiSpec getRecordsIdVariantsTreatedAsExpected() { } @HapiTest - public HapiSpec getInfoIdVariantsTreatedAsExpected() { + final Stream getInfoIdVariantsTreatedAsExpected() { return defaultHapiSpec("getInfoIdVariantsTreatedAsExpected") .given() .when() @@ -186,7 +196,7 @@ public HapiSpec getInfoIdVariantsTreatedAsExpected() { } @HapiTest - public HapiSpec getRecordAndReceiptIdVariantsTreatedAsExpected() { + final Stream getRecordAndReceiptIdVariantsTreatedAsExpected() { return defaultHapiSpec("getRecordIdVariantsTreatedAsExpected") .given(cryptoTransfer(tinyBarsFromTo(DEFAULT_PAYER, FUNDING, 1)).via("spot")) .when() @@ -196,7 +206,7 @@ public HapiSpec getRecordAndReceiptIdVariantsTreatedAsExpected() { } @HapiTest - final HapiSpec transferChangesBalance() { + final Stream transferChangesBalance() { return defaultHapiSpec("TransferChangesBalance") .given(cryptoCreate("newPayee").balance(0L)) .when(cryptoTransfer(tinyBarsFromTo(GENESIS, "newPayee", 1_000_000_000L))) @@ -204,7 +214,7 @@ final HapiSpec transferChangesBalance() { } @HapiTest - final HapiSpec updateWithOutOfDateKeyFails() { + final Stream updateWithOutOfDateKeyFails() { return defaultHapiSpec("UpdateWithOutOfDateKeyFails") .given( newKeyNamed("originalKey"), @@ -219,8 +229,76 @@ final HapiSpec updateWithOutOfDateKeyFails() { .hasKnownStatus(INVALID_SIGNATURE)); } - @Override - protected Logger getResultsLogger() { - return log; + @HapiTest + final Stream getAccountDetailsDemo() { + final String owner = "owner"; + final String spender = "spender"; + final String token = "token"; + final String nft = "nft"; + return defaultHapiSpec("getAccountDetailsDemo") + .given( + newKeyNamed("supplyKey"), + cryptoCreate(owner).balance(ONE_HUNDRED_HBARS).maxAutomaticTokenAssociations(10), + cryptoCreate(spender).balance(ONE_HUNDRED_HBARS), + cryptoCreate(TOKEN_TREASURY) + .balance(100 * ONE_HUNDRED_HBARS) + .maxAutomaticTokenAssociations(10), + tokenCreate(token) + .tokenType(TokenType.FUNGIBLE_COMMON) + .supplyType(TokenSupplyType.FINITE) + .supplyKey("supplyKey") + .maxSupply(1000L) + .initialSupply(10L) + .treasury(TOKEN_TREASURY), + tokenCreate(nft) + .maxSupply(10L) + .initialSupply(0) + .supplyType(TokenSupplyType.FINITE) + .tokenType(NON_FUNGIBLE_UNIQUE) + .supplyKey("supplyKey") + .treasury(TOKEN_TREASURY), + tokenAssociate(owner, token), + tokenAssociate(owner, nft), + mintToken( + nft, + List.of( + ByteString.copyFromUtf8("a"), + ByteString.copyFromUtf8("b"), + ByteString.copyFromUtf8("c"))) + .via("nftTokenMint"), + mintToken(token, 500L).via("tokenMint"), + cryptoTransfer(movingUnique(nft, 1L, 2L, 3L).between(TOKEN_TREASURY, owner))) + .when(cryptoApproveAllowance() + .payingWith(owner) + .addCryptoAllowance(owner, spender, 100L) + .addTokenAllowance(owner, token, spender, 100L) + .addNftAllowance(owner, nft, spender, true, List.of(1L)) + .via("approveTxn") + .fee(ONE_HBAR) + .blankMemo() + .logged()) + .then( + /* NetworkGetExecutionTime requires superuser payer */ + getAccountDetails(owner) + .payingWith(owner) + .hasCostAnswerPrecheck(NOT_SUPPORTED) + .hasAnswerOnlyPrecheck(NOT_SUPPORTED), + getAccountDetails(owner) + .payingWith(GENESIS) + .has(accountDetailsWith() + .cryptoAllowancesCount(1) + .nftApprovedForAllAllowancesCount(1) + .tokenAllowancesCount(1) + .cryptoAllowancesContaining(spender, 100L) + .tokenAllowancesContaining(token, spender, 100L)), + getAccountDetailsNoPayment(owner) + .payingWith(GENESIS) + .has(accountDetailsWith() + .cryptoAllowancesCount(2) + .nftApprovedForAllAllowancesCount(1) + .tokenAllowancesCount(2) + .cryptoAllowancesContaining(spender, 100L) + .tokenAllowancesContaining(token, spender, 100L)) + .hasCostAnswerPrecheck(NOT_SUPPORTED)); } } diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/crypto/NewAccountRecordExists.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/crypto/NewAccountRecordExists.java index a13985142414..2e5398d76339 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/crypto/NewAccountRecordExists.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/crypto/NewAccountRecordExists.java @@ -28,15 +28,16 @@ import static com.hedera.services.bdd.spec.utilops.UtilVerbs.streamMustInclude; import static com.hedera.services.bdd.suites.contract.Utils.asInstant; -import com.hedera.services.bdd.junit.validators.AccountExistenceValidator; -import com.hedera.services.bdd.spec.HapiSpec; +import com.hedera.services.bdd.junit.support.validators.AccountExistenceValidator; import com.hedera.services.bdd.suites.HapiSuite; import java.time.Duration; import java.time.Instant; import java.util.List; import java.util.concurrent.atomic.AtomicReference; +import java.util.stream.Stream; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; +import org.junit.jupiter.api.DynamicTest; public class NewAccountRecordExists extends HapiSuite { @@ -47,11 +48,11 @@ public static void main(String... args) { } @Override - public List getSpecsInSuite() { + public List> getSpecsInSuite() { return List.of(newAccountIsReflectedInRecordStream(), newAccountIsReflectedInRecordStreamV2()); } - final HapiSpec newAccountIsReflectedInRecordStream() { + final Stream newAccountIsReflectedInRecordStream() { final var balance = 1_234_567L; final var novelKey = "novelKey"; final var memo = "It was the best of times"; @@ -74,7 +75,7 @@ final HapiSpec newAccountIsReflectedInRecordStream() { new AccountExistenceValidator(account, consensusTime.get()), Duration.ofMillis(2_100)))); } - final HapiSpec newAccountIsReflectedInRecordStreamV2() { + final Stream newAccountIsReflectedInRecordStreamV2() { final var balance = 1_234_567L; final var memo = "It was the best of times"; final var account = "novel"; diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/crypto/NewContractRecordExists.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/crypto/NewContractRecordExists.java index 75624b9d2b13..8aed5322f78d 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/crypto/NewContractRecordExists.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/crypto/NewContractRecordExists.java @@ -25,15 +25,16 @@ import static com.hedera.services.bdd.suites.contract.Utils.asInstant; import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.SUCCESS; -import com.hedera.services.bdd.junit.validators.ContractExistenceValidator; -import com.hedera.services.bdd.spec.HapiSpec; +import com.hedera.services.bdd.junit.support.validators.ContractExistenceValidator; import com.hedera.services.bdd.suites.HapiSuite; import java.time.Duration; import java.time.Instant; import java.util.List; import java.util.concurrent.atomic.AtomicReference; +import java.util.stream.Stream; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; +import org.junit.jupiter.api.DynamicTest; public class NewContractRecordExists extends HapiSuite { private static final String EMPTY_CONTRACT = "EmptyConstructor"; @@ -44,11 +45,11 @@ public static void main(String... args) { } @Override - public List getSpecsInSuite() { + public List> getSpecsInSuite() { return List.of(newContractIsReflectedInRecordStream()); } - final HapiSpec newContractIsReflectedInRecordStream() { + final Stream newContractIsReflectedInRecordStream() { final var creation = "creation"; final AtomicReference consensusTime = new AtomicReference<>(); return defaultHapiSpec(EMPTY_CONTRACT) diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/crypto/NftTransferSuite.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/crypto/NftTransferSuite.java index 588cc413d381..899c43668d3f 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/crypto/NftTransferSuite.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/crypto/NftTransferSuite.java @@ -28,28 +28,23 @@ import static com.hedera.services.bdd.spec.utilops.UtilVerbs.inParallel; import static com.hedera.services.bdd.spec.utilops.UtilVerbs.logIt; import static com.hedera.services.bdd.spec.utilops.UtilVerbs.newKeyNamed; +import static com.hedera.services.bdd.suites.HapiSuite.GENESIS; +import static com.hedera.services.bdd.suites.HapiSuite.ONE_HUNDRED_HBARS; import static com.hederahashgraph.api.proto.java.TokenType.NON_FUNGIBLE_UNIQUE; import com.google.protobuf.ByteString; import com.hedera.services.bdd.junit.HapiTest; -import com.hedera.services.bdd.junit.HapiTestSuite; -import com.hedera.services.bdd.spec.HapiSpec; import com.hedera.services.bdd.spec.HapiSpecOperation; import com.hedera.services.bdd.spec.transactions.token.TokenMovement; -import com.hedera.services.bdd.suites.HapiSuite; import java.util.List; -import java.util.concurrent.ExecutionException; import java.util.function.IntFunction; import java.util.stream.IntStream; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; +import java.util.stream.Stream; +import org.junit.jupiter.api.DynamicTest; import org.junit.jupiter.api.Tag; -@HapiTestSuite @Tag(CRYPTO) -public class NftTransferSuite extends HapiSuite { - private static final Logger log = LogManager.getLogger(NftTransferSuite.class); - +public class NftTransferSuite { private static final String KEY = "multipurpose"; private static final String USER_ACCOUNT_PREFIX = "party-"; private static final String FEE_COLLECTOR = "feeCollector"; @@ -60,23 +55,6 @@ public class NftTransferSuite extends HapiSuite { private static final int NUM_TOKEN_TYPES = 10; private static final int NUM_ROUNDS = 100; - private static void runTestTask() { - final long startTimeMillis = System.currentTimeMillis(); - new NftTransferSuite().runSuiteSync(); - final long endTimeMillis = System.currentTimeMillis(); - final long deltaMillis = endTimeMillis - startTimeMillis; - System.out.printf("Total time: %.3f%n", deltaMillis / 1000f); - } - - public static void main(String... args) throws ExecutionException, InterruptedException { - runTestTask(); - } - - @Override - public List getSpecsInSuite() { - return List.of(transferNfts()); - } - private static HapiSpecOperation mintTokensFor(String tokenName, int numTokens) { return mintToken( tokenName, @@ -196,15 +174,10 @@ private static HapiSpecOperation setupNftTest() { } @HapiTest - final HapiSpec transferNfts() { + final Stream transferNfts() { return defaultHapiSpec("TransferNfts") .given(setupNftTest(), transferInitial()) .when(seqFor(0, NUM_ROUNDS, NftTransferSuite::transferRound)) .then(); } - - @Override - protected Logger getResultsLogger() { - return log; - } } diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/crypto/QueryPaymentSuite.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/crypto/QueryPaymentSuite.java index 10cca3594cfc..e5310367ee6b 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/crypto/QueryPaymentSuite.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/crypto/QueryPaymentSuite.java @@ -22,59 +22,35 @@ import static com.hedera.services.bdd.spec.queries.QueryVerbs.getAccountInfo; import static com.hedera.services.bdd.spec.transactions.TxnVerbs.cryptoCreate; import static com.hedera.services.bdd.spec.transactions.TxnVerbs.cryptoTransfer; +import static com.hedera.services.bdd.suites.HapiSuite.GENESIS; import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.INSUFFICIENT_PAYER_BALANCE; import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.INSUFFICIENT_TX_FEE; import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.INVALID_RECEIVING_NODE_ACCOUNT; import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.OK; import com.hedera.services.bdd.junit.HapiTest; -import com.hedera.services.bdd.junit.HapiTestSuite; import com.hedera.services.bdd.spec.HapiSpec; -import com.hedera.services.bdd.suites.HapiSuite; import com.hederahashgraph.api.proto.java.AccountAmount; import com.hederahashgraph.api.proto.java.AccountID; import com.hederahashgraph.api.proto.java.TransferList; -import java.util.List; +import java.util.stream.Stream; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; +import org.junit.jupiter.api.DynamicTest; import org.junit.jupiter.api.Tag; -@HapiTestSuite @Tag(CRYPTO) -public class QueryPaymentSuite extends HapiSuite { +public class QueryPaymentSuite { private static final Logger log = LogManager.getLogger(QueryPaymentSuite.class); private static final String NODE = "0.0.3"; - public static void main(String... args) { - new QueryPaymentSuite().runSuiteSync(); - } - - @Override - protected Logger getResultsLogger() { - return log; - } - - @Override - public List getSpecsInSuite() { - return allOf(queryPaymentTests()); - } - - private List queryPaymentTests() { - return List.of(new HapiSpec[] { - queryPaymentsFailsWithInsufficientFunds(), - queryPaymentsSingleBeneficiaryChecked(), - queryPaymentsMultiBeneficiarySucceeds(), - queryPaymentsNotToNodeFails() - }); - } - /* * 1. multiple payers pay amount to node as well as one more beneficiary. But node gets less query payment fee * 2. TransactionPayer will pay for query payment to node and payer has less balance * 3. Transaction payer is not involved in transfers for query payment to node and one or more have less balance */ @HapiTest - final HapiSpec queryPaymentsFailsWithInsufficientFunds() { + final Stream queryPaymentsFailsWithInsufficientFunds() { return defaultHapiSpec("queryPaymentsFailsWithInsufficientFunds") .given( cryptoCreate("a").balance(500_000_000L), @@ -110,7 +86,7 @@ final HapiSpec queryPaymentsFailsWithInsufficientFunds() { * 3. Transaction payer is not involved in transfers for query payment to node and all payers have enough balance */ @HapiTest - final HapiSpec queryPaymentsMultiBeneficiarySucceeds() { + final Stream queryPaymentsMultiBeneficiarySucceeds() { return defaultHapiSpec("queryPaymentsMultiBeneficiarySucceeds") .given( cryptoCreate("a").balance(1_234L), @@ -140,7 +116,7 @@ final HapiSpec queryPaymentsMultiBeneficiarySucceeds() { // Check if multiple payers or single payer pay amount to node @HapiTest - final HapiSpec queryPaymentsSingleBeneficiaryChecked() { + final Stream queryPaymentsSingleBeneficiaryChecked() { return defaultHapiSpec("queryPaymentsSingleBeneficiaryChecked") .given( cryptoCreate("a").balance(500_000_000L), @@ -162,7 +138,7 @@ final HapiSpec queryPaymentsSingleBeneficiaryChecked() { // Check if payment is not done to node @HapiTest - final HapiSpec queryPaymentsNotToNodeFails() { + final Stream queryPaymentsNotToNodeFails() { return defaultHapiSpec("queryPaymentsNotToNodeFails") .given( cryptoCreate("a").balance(500_000_000L), diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/crypto/RandomOps.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/crypto/RandomOps.java deleted file mode 100644 index ba749d267fc4..000000000000 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/crypto/RandomOps.java +++ /dev/null @@ -1,167 +0,0 @@ -/* - * Copyright (C) 2020-2024 Hedera Hashgraph, LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.hedera.services.bdd.suites.crypto; - -import static com.hedera.services.bdd.junit.TestTags.CRYPTO; -import static com.hedera.services.bdd.spec.HapiSpec.customHapiSpec; -import static com.hedera.services.bdd.spec.HapiSpec.defaultHapiSpec; -import static com.hedera.services.bdd.spec.assertions.AccountDetailsAsserts.accountDetailsWith; -import static com.hedera.services.bdd.spec.queries.QueryVerbs.getAccountDetails; -import static com.hedera.services.bdd.spec.queries.QueryVerbs.getAccountDetailsNoPayment; -import static com.hedera.services.bdd.spec.queries.QueryVerbs.getAccountInfo; -import static com.hedera.services.bdd.spec.transactions.TxnVerbs.cryptoApproveAllowance; -import static com.hedera.services.bdd.spec.transactions.TxnVerbs.cryptoCreate; -import static com.hedera.services.bdd.spec.transactions.TxnVerbs.cryptoTransfer; -import static com.hedera.services.bdd.spec.transactions.TxnVerbs.mintToken; -import static com.hedera.services.bdd.spec.transactions.TxnVerbs.tokenAssociate; -import static com.hedera.services.bdd.spec.transactions.TxnVerbs.tokenCreate; -import static com.hedera.services.bdd.spec.transactions.crypto.HapiCryptoTransfer.tinyBarsFromTo; -import static com.hedera.services.bdd.spec.transactions.token.TokenMovement.movingUnique; -import static com.hedera.services.bdd.spec.utilops.UtilVerbs.freezeOnly; -import static com.hedera.services.bdd.spec.utilops.UtilVerbs.newKeyNamed; -import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.NOT_SUPPORTED; -import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.OK; -import static com.hederahashgraph.api.proto.java.TokenType.NON_FUNGIBLE_UNIQUE; - -import com.google.protobuf.ByteString; -import com.hedera.services.bdd.junit.HapiTest; -import com.hedera.services.bdd.junit.HapiTestSuite; -import com.hedera.services.bdd.spec.HapiSpec; -import com.hedera.services.bdd.suites.BddMethodIsNotATest; -import com.hedera.services.bdd.suites.HapiSuite; -import com.hederahashgraph.api.proto.java.TokenSupplyType; -import com.hederahashgraph.api.proto.java.TokenType; -import java.util.List; -import java.util.Map; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; -import org.junit.jupiter.api.Tag; - -@HapiTestSuite -@Tag(CRYPTO) -public class RandomOps extends HapiSuite { - private static final Logger log = LogManager.getLogger(RandomOps.class); - - public static void main(String... args) { - new RandomOps().runSuiteSync(); - } - - @Override - public List getSpecsInSuite() { - return List.of(new HapiSpec[] {freezeDemo(), retryLimitDemo(), getAccountDetailsDemo()}); - } - - @HapiTest - final HapiSpec getAccountDetailsDemo() { - final String owner = "owner"; - final String spender = "spender"; - final String token = "token"; - final String nft = "nft"; - return defaultHapiSpec("getAccountDetailsDemo") - .given( - newKeyNamed("supplyKey"), - cryptoCreate(owner).balance(ONE_HUNDRED_HBARS).maxAutomaticTokenAssociations(10), - cryptoCreate(spender).balance(ONE_HUNDRED_HBARS), - cryptoCreate(TOKEN_TREASURY) - .balance(100 * ONE_HUNDRED_HBARS) - .maxAutomaticTokenAssociations(10), - tokenCreate(token) - .tokenType(TokenType.FUNGIBLE_COMMON) - .supplyType(TokenSupplyType.FINITE) - .supplyKey("supplyKey") - .maxSupply(1000L) - .initialSupply(10L) - .treasury(TOKEN_TREASURY), - tokenCreate(nft) - .maxSupply(10L) - .initialSupply(0) - .supplyType(TokenSupplyType.FINITE) - .tokenType(NON_FUNGIBLE_UNIQUE) - .supplyKey("supplyKey") - .treasury(TOKEN_TREASURY), - tokenAssociate(owner, token), - tokenAssociate(owner, nft), - mintToken( - nft, - List.of( - ByteString.copyFromUtf8("a"), - ByteString.copyFromUtf8("b"), - ByteString.copyFromUtf8("c"))) - .via("nftTokenMint"), - mintToken(token, 500L).via("tokenMint"), - cryptoTransfer(movingUnique(nft, 1L, 2L, 3L).between(TOKEN_TREASURY, owner))) - .when(cryptoApproveAllowance() - .payingWith(owner) - .addCryptoAllowance(owner, spender, 100L) - .addTokenAllowance(owner, token, spender, 100L) - .addNftAllowance(owner, nft, spender, true, List.of(1L)) - .via("approveTxn") - .fee(ONE_HBAR) - .blankMemo() - .logged()) - .then( - /* NetworkGetExecutionTime requires superuser payer */ - getAccountDetails(owner) - .payingWith(owner) - .hasCostAnswerPrecheck(NOT_SUPPORTED) - .hasAnswerOnlyPrecheck(NOT_SUPPORTED), - getAccountDetails(owner) - .payingWith(GENESIS) - .has(accountDetailsWith() - .cryptoAllowancesCount(1) - .nftApprovedForAllAllowancesCount(1) - .tokenAllowancesCount(1) - .cryptoAllowancesContaining(spender, 100L) - .tokenAllowancesContaining(token, spender, 100L)), - getAccountDetailsNoPayment(owner) - .payingWith(GENESIS) - .has(accountDetailsWith() - .cryptoAllowancesCount(2) - .nftApprovedForAllAllowancesCount(1) - .tokenAllowancesCount(2) - .cryptoAllowancesContaining(spender, 100L) - .tokenAllowancesContaining(token, spender, 100L)) - .hasCostAnswerPrecheck(NOT_SUPPORTED)); - } - - @HapiTest - final HapiSpec retryLimitDemo() { - return defaultHapiSpec("RetryLimitDemo") - .given() - .when() - .then( - getAccountInfo("0.0.2").hasRetryAnswerOnlyPrecheck(OK).setRetryLimit(5), - cryptoTransfer(tinyBarsFromTo(GENESIS, NODE, 1L)) - .hasRetryPrecheckFrom(OK) - .setRetryLimit(3), - cryptoTransfer(tinyBarsFromTo(GENESIS, FUNDING, 7L))); - } - - @BddMethodIsNotATest - final HapiSpec freezeDemo() { - return customHapiSpec("FreezeDemo") - .withProperties(Map.of("nodes", "127.0.0.1:50213:0.0.3,127.0.0.1:50214:0.0.4,127.0.0.1:50215:0.0.5")) - .given() - .when() - .then(freezeOnly().startingIn(60).seconds()); - } - - @Override - protected Logger getResultsLogger() { - return log; - } -} diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/crypto/TransferWithCustomFixedFees.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/crypto/TransferWithCustomFixedFees.java index 19ccc94963b1..6f1091532678 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/crypto/TransferWithCustomFixedFees.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/crypto/TransferWithCustomFixedFees.java @@ -39,29 +39,30 @@ import static com.hedera.services.bdd.spec.utilops.CustomSpecAssert.allRunFor; import static com.hedera.services.bdd.spec.utilops.UtilVerbs.newKeyNamed; import static com.hedera.services.bdd.spec.utilops.UtilVerbs.withOpContext; +import static com.hedera.services.bdd.suites.HapiSuite.ONE_HBAR; +import static com.hedera.services.bdd.suites.HapiSuite.ONE_HUNDRED_HBARS; +import static com.hedera.services.bdd.suites.HapiSuite.ONE_MILLION_HBARS; +import static com.hedera.services.bdd.suites.HapiSuite.SECP_256K1_SHAPE; +import static com.hedera.services.bdd.suites.HapiSuite.SECP_256K1_SOURCE_KEY; +import static com.hedera.services.bdd.suites.HapiSuite.THOUSAND_HBAR; import static com.hedera.services.bdd.suites.crypto.AutoAccountUpdateSuite.TRANSFER_TXN_2; import static com.hedera.services.bdd.suites.crypto.AutoCreateUtils.createHollowAccountFrom; import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.*; import com.hedera.node.app.hapi.utils.ByteStringUtils; import com.hedera.services.bdd.junit.HapiTest; -import com.hedera.services.bdd.junit.HapiTestSuite; -import com.hedera.services.bdd.spec.HapiSpec; import com.hedera.services.bdd.spec.HapiSpecOperation; -import com.hedera.services.bdd.suites.HapiSuite; import com.hederahashgraph.api.proto.java.TokenSupplyType; import com.hederahashgraph.api.proto.java.TokenType; import java.util.ArrayList; import java.util.List; import java.util.OptionalLong; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; +import java.util.stream.Stream; +import org.junit.jupiter.api.DynamicTest; import org.junit.jupiter.api.Tag; -@HapiTestSuite @Tag(CRYPTO) -public class TransferWithCustomFixedFees extends HapiSuite { - private static final Logger log = LogManager.getLogger(TransferWithCustomFixedFees.class); +public class TransferWithCustomFixedFees { private static final long hbarFee = 1_000L; private static final long htsFee = 100L; private static final long tokenTotal = 1_000L; @@ -92,67 +93,8 @@ public class TransferWithCustomFixedFees extends HapiSuite { private static final String ivan = "ivan"; - public static void main(String... args) { - new TransferWithCustomFixedFees().runSuiteAsync(); - } - - @Override - public List getSpecsInSuite() { - return allOf(positiveTests(), negativeTests()); - } - - private List positiveTests() { - return List.of( - transferFungibleWithFixedHbarCustomFee(), - transferFungibleWithFixedHtsCustomFee(), - transferNonFungibleWithFixedHbarCustomFee(), - transferNonFungibleWithFixedHtsCustomFee(), - transferFungibleToHollowAccountWithFixedHBarFee(), - transferFungibleToHollowAccountWithFixedHtsFee(), - transferNonFungibleToHollowAccountWithFixedHBarFee(), - transferNonFungibleToHollowAccountWithFixedHtsFee(), - transferApprovedFungibleWithFixedHbarCustomFee(), - transferApprovedFungibleWithFixedHtsCustomFeeAsOwner(), - transferApprovedFungibleWithFixedHtsCustomFeeAsSpender(), - transferApprovedNonFungibleWithFixedHbarCustomFee(), - transferApprovedNonFungibleWithFixedHtsCustomFeeAsOwner(), - transferApprovedNonFungibleWithFixedHtsCustomFeeAsSpender(), - transferFungibleWithThreeFixedHtsCustomFeesWithoutAllCollectorsExempt(), - transferFungibleWithThreeFixedHtsCustomFeesWithAllCollectorsExempt(), - transferFungibleWithFixedHtsCustomFees2Layers(), - transferNonFungibleWithFixedHtsCustomFees2Layers(), - transferMaxFungibleWith10FixedHtsCustomFees2Layers(), - multipleTransfersWithMultipleCustomFees(), - transferWithFractionalCustomFee(), - transferWithInsufficientCustomFee(), - transferMultipleTimesWithFixedFeeInHBarShouldVerifyEachTransferIsPaid(), - transferMultipleTimesWithFixedFeeInCustomFungibleTokenShouldVerifyEachTransferIsPaid()); - } - - private List negativeTests() { - return List.of( - transferFungibleWithFixedHtsCustomFeeNotEnoughBalanceFeeToken(), - transferFungibleWithFixedHtsCustomFeeNotEnoughBalanceTransferToken(), - transferFungibleWithFixedHbarCustomFeeNotEnoughBalance(), - transferNonFungibleWithFixedHtsCustomFeeNotEnoughBalanceFeeToken(), - transferNonFungibleWithFixedHbarCustomFeeNotEnoughBalance(), - transferApprovedFungibleWithFixedHbarCustomFeeNoAllowance(), - transferApprovedFungibleWithFixedHtsCustomFeeNoAllowance(), - transferApprovedNonFungibleWithFixedHbarCustomFeeNoAllowance(), - transferApprovedNonFungibleWithFixedHtsCustomFeeNoAllowance(), - transferFungibleWithFixedHbarCustomFeeAmount0(), - transferFungibleWithFixedHtsCustomFeeAmount0(), - transferNonFungibleWithFixedHbarCustomFeeAmount0(), - transferNonFungibleWithFixedHtsCustomFeeAmount0(), - transferFungibleWithFixedHbarCustomFeeSenderHasOnlyGasAmount(), - transferFungibleWithFixedHtsCustomFeeTotalSupply0(), - transferFungibleWithFixedHtsCustomFeeNotEnoughForGasAndFee(), - transferFungibleWithFixedHtsCustomFees3LayersShouldFail(), - transferNonFungibleWithFixedHtsCustomFees3LayersShouldFail()); - } - @HapiTest - public HapiSpec transferFungibleWithFixedHbarCustomFee() { + final Stream transferFungibleWithFixedHbarCustomFee() { return defaultHapiSpec("transferFungibleWithFixedHbarCustomFee") .given( cryptoCreate(hbarCollector).balance(0L), @@ -188,7 +130,7 @@ final var record = getTxnRecord("transferTx"); } @HapiTest - public HapiSpec transferFungibleWithFixedHbarCustomFeeNotEnoughBalance() { + final Stream transferFungibleWithFixedHbarCustomFeeNotEnoughBalance() { return defaultHapiSpec("transferFungibleWithFixedHbarCustomFeeNotEnoughBalance") .given( cryptoCreate(hbarCollector).balance(0L), @@ -214,7 +156,7 @@ public HapiSpec transferFungibleWithFixedHbarCustomFeeNotEnoughBalance() { } @HapiTest - public HapiSpec transferFungibleWithFixedHtsCustomFee() { + final Stream transferFungibleWithFixedHtsCustomFee() { return defaultHapiSpec("transferFungibleWithFixedHtsCustomFee") .given( cryptoCreate(htsCollector), @@ -243,7 +185,7 @@ public HapiSpec transferFungibleWithFixedHtsCustomFee() { } @HapiTest - public HapiSpec transferFungibleWithFixedHtsCustomFeeNotEnoughBalanceFeeToken() { + final Stream transferFungibleWithFixedHtsCustomFeeNotEnoughBalanceFeeToken() { return defaultHapiSpec("transferFungibleWithFixedHtsCustomFeeNotEnoughBalanceFeeToken()") .given( cryptoCreate(htsCollector), @@ -273,7 +215,7 @@ public HapiSpec transferFungibleWithFixedHtsCustomFeeNotEnoughBalanceFeeToken() } @HapiTest - public HapiSpec transferFungibleWithFixedHtsCustomFeeNotEnoughBalanceTransferToken() { + final Stream transferFungibleWithFixedHtsCustomFeeNotEnoughBalanceTransferToken() { return defaultHapiSpec("transferFungibleWithFixedHtsCustomFeeNotEnoughBalanceTransferToken()") .given( cryptoCreate(htsCollector), @@ -303,7 +245,7 @@ public HapiSpec transferFungibleWithFixedHtsCustomFeeNotEnoughBalanceTransferTok } @HapiTest - public HapiSpec transferNonFungibleWithFixedHbarCustomFee() { + final Stream transferNonFungibleWithFixedHbarCustomFee() { return defaultHapiSpec("transferNonFungibleWithFixedHbarCustomFee") .given( newKeyNamed(NFT_KEY), @@ -343,7 +285,7 @@ final var record = getTxnRecord("transferTx"); } @HapiTest - public HapiSpec transferNonFungibleWithFixedHbarCustomFeeNotEnoughBalance() { + final Stream transferNonFungibleWithFixedHbarCustomFeeNotEnoughBalance() { return defaultHapiSpec("transferNonFungibleWithFixedHbarCustomFeeNotEnoughBalance") .given( newKeyNamed(NFT_KEY), @@ -374,7 +316,7 @@ public HapiSpec transferNonFungibleWithFixedHbarCustomFeeNotEnoughBalance() { } @HapiTest - public HapiSpec transferNonFungibleWithFixedHtsCustomFee() { + final Stream transferNonFungibleWithFixedHtsCustomFee() { return defaultHapiSpec("transferNonFungibleWithFixedHtsCustomFee") .given( newKeyNamed(NFT_KEY), @@ -405,7 +347,7 @@ public HapiSpec transferNonFungibleWithFixedHtsCustomFee() { } @HapiTest - public HapiSpec transferNonFungibleWithFixedHtsCustomFeeNotEnoughBalanceFeeToken() { + final Stream transferNonFungibleWithFixedHtsCustomFeeNotEnoughBalanceFeeToken() { return defaultHapiSpec("transferNonFungibleWithFixedHtsCustomFeeNotEnoughBalanceFeeToken") .given( newKeyNamed(NFT_KEY), @@ -440,7 +382,7 @@ public HapiSpec transferNonFungibleWithFixedHtsCustomFeeNotEnoughBalanceFeeToken } @HapiTest - public HapiSpec transferApprovedFungibleWithFixedHbarCustomFee() { + final Stream transferApprovedFungibleWithFixedHbarCustomFee() { return defaultHapiSpec("transferApprovedFungibleWithFixedHbarCustomFee") .given( cryptoCreate(hbarCollector).balance(0L), @@ -489,7 +431,7 @@ public HapiSpec transferApprovedFungibleWithFixedHbarCustomFee() { } @HapiTest - public HapiSpec transferApprovedFungibleWithFixedHbarCustomFeeNoAllowance() { + final Stream transferApprovedFungibleWithFixedHbarCustomFeeNoAllowance() { return defaultHapiSpec("transferApprovedFungibleWithFixedHbarCustomFeeNoAllowance") .given( cryptoCreate(hbarCollector).balance(0L), @@ -519,7 +461,7 @@ public HapiSpec transferApprovedFungibleWithFixedHbarCustomFeeNoAllowance() { } @HapiTest - public HapiSpec transferApprovedFungibleWithFixedHtsCustomFeeAsOwner() { + final Stream transferApprovedFungibleWithFixedHtsCustomFeeAsOwner() { return defaultHapiSpec("transferApprovedFungibleWithFixedHtsCustomFeeAsOwner") .given( cryptoCreate(htsCollector), @@ -565,7 +507,7 @@ public HapiSpec transferApprovedFungibleWithFixedHtsCustomFeeAsOwner() { } @HapiTest - public HapiSpec transferApprovedFungibleWithFixedHtsCustomFeeAsSpender() { + final Stream transferApprovedFungibleWithFixedHtsCustomFeeAsSpender() { return defaultHapiSpec("transferApprovedFungibleWithFixedHtsCustomFeeAsSpender") .given( cryptoCreate(htsCollector), @@ -611,7 +553,7 @@ public HapiSpec transferApprovedFungibleWithFixedHtsCustomFeeAsSpender() { } @HapiTest - public HapiSpec transferApprovedFungibleWithFixedHtsCustomFeeNoAllowance() { + final Stream transferApprovedFungibleWithFixedHtsCustomFeeNoAllowance() { return defaultHapiSpec("transferApprovedFungibleWithFixedHtsCustomFeeNoAllowance") .given( cryptoCreate(htsCollector), @@ -648,7 +590,7 @@ public HapiSpec transferApprovedFungibleWithFixedHtsCustomFeeNoAllowance() { } @HapiTest - public HapiSpec transferApprovedNonFungibleWithFixedHbarCustomFee() { + final Stream transferApprovedNonFungibleWithFixedHbarCustomFee() { return defaultHapiSpec("transferApprovedNonFungibleWithFixedHbarCustomFee") .given( newKeyNamed(NFT_KEY), @@ -698,7 +640,7 @@ public HapiSpec transferApprovedNonFungibleWithFixedHbarCustomFee() { } @HapiTest - public HapiSpec transferApprovedNonFungibleWithFixedHbarCustomFeeNoAllowance() { + final Stream transferApprovedNonFungibleWithFixedHbarCustomFeeNoAllowance() { return defaultHapiSpec("transferApprovedNonFungibleWithFixedHbarCustomFeeNoAllowance") .given( newKeyNamed(NFT_KEY), @@ -733,7 +675,7 @@ public HapiSpec transferApprovedNonFungibleWithFixedHbarCustomFeeNoAllowance() { } @HapiTest - public HapiSpec transferApprovedNonFungibleWithFixedHtsCustomFeeAsOwner() { + final Stream transferApprovedNonFungibleWithFixedHtsCustomFeeAsOwner() { return defaultHapiSpec("transferApprovedNonFungibleWithFixedHtsCustomFeeAsOwner") .given( newKeyNamed(NFT_KEY), @@ -780,7 +722,7 @@ public HapiSpec transferApprovedNonFungibleWithFixedHtsCustomFeeAsOwner() { } @HapiTest - public HapiSpec transferApprovedNonFungibleWithFixedHtsCustomFeeAsSpender() { + final Stream transferApprovedNonFungibleWithFixedHtsCustomFeeAsSpender() { return defaultHapiSpec("transferApprovedNonFungibleWithFixedHtsCustomFeeAsSpender") .given( newKeyNamed(NFT_KEY), @@ -826,7 +768,7 @@ public HapiSpec transferApprovedNonFungibleWithFixedHtsCustomFeeAsSpender() { } @HapiTest - public HapiSpec transferApprovedNonFungibleWithFixedHtsCustomFeeNoAllowance() { + final Stream transferApprovedNonFungibleWithFixedHtsCustomFeeNoAllowance() { return defaultHapiSpec("transferApprovedNonFungibleWithFixedHtsCustomFeeNoAllowance") .given( newKeyNamed(NFT_KEY), @@ -868,7 +810,7 @@ public HapiSpec transferApprovedNonFungibleWithFixedHtsCustomFeeNoAllowance() { } @HapiTest - public HapiSpec transferFungibleWithThreeFixedHtsCustomFeesWithoutAllCollectorsExempt() { + final Stream transferFungibleWithThreeFixedHtsCustomFeesWithoutAllCollectorsExempt() { final long amountToSend = 400L; return defaultHapiSpec("transferFungibleWithThreeFixedHtsCustomFeesWithoutAllCollectorsExempt") .given( @@ -914,7 +856,7 @@ public HapiSpec transferFungibleWithThreeFixedHtsCustomFeesWithoutAllCollectorsE } @HapiTest - public HapiSpec transferFungibleWithThreeFixedHtsCustomFeesWithAllCollectorsExempt() { + final Stream transferFungibleWithThreeFixedHtsCustomFeesWithAllCollectorsExempt() { final long amountToSend = 400L; return defaultHapiSpec("transferFungibleWithThreeFixedHtsCustomFeesWithAllCollectorsExempt") .given( @@ -960,7 +902,7 @@ public HapiSpec transferFungibleWithThreeFixedHtsCustomFeesWithAllCollectorsExem } @HapiTest - public HapiSpec transferFungibleWithFixedHtsCustomFees2Layers() { + final Stream transferFungibleWithFixedHtsCustomFees2Layers() { return defaultHapiSpec("transferFungibleWithFixedHtsCustomFees2Layers") .given( cryptoCreate(htsCollector), @@ -1002,7 +944,7 @@ public HapiSpec transferFungibleWithFixedHtsCustomFees2Layers() { } @HapiTest - public HapiSpec transferNonFungibleWithFixedHtsCustomFees2Layers() { + final Stream transferNonFungibleWithFixedHtsCustomFees2Layers() { return defaultHapiSpec("transferNonFungibleWithFixedHtsCustomFees2Layers") .given( newKeyNamed(NFT_KEY), @@ -1048,7 +990,7 @@ public HapiSpec transferNonFungibleWithFixedHtsCustomFees2Layers() { } @HapiTest - public HapiSpec transferFungibleWithFixedHtsCustomFees3LayersShouldFail() { + final Stream transferFungibleWithFixedHtsCustomFees3LayersShouldFail() { return defaultHapiSpec("transferFungibleWithFixedHtsCustomFees3LayersShouldFail") .given( cryptoCreate(htsCollector), @@ -1091,7 +1033,7 @@ public HapiSpec transferFungibleWithFixedHtsCustomFees3LayersShouldFail() { } @HapiTest - public HapiSpec transferNonFungibleWithFixedHtsCustomFees3LayersShouldFail() { + final Stream transferNonFungibleWithFixedHtsCustomFees3LayersShouldFail() { return defaultHapiSpec("transferNonFungibleWithFixedHtsCustomFees3LayersShouldFail") .given( newKeyNamed(NFT_KEY), @@ -1138,7 +1080,7 @@ public HapiSpec transferNonFungibleWithFixedHtsCustomFees3LayersShouldFail() { } @HapiTest - public HapiSpec transferMaxFungibleWith10FixedHtsCustomFees2Layers() { + final Stream transferMaxFungibleWith10FixedHtsCustomFees2Layers() { final String fungibleToken3 = "fungibleWithCustomFees3"; final String fungibleToken4 = "fungibleWithCustomFees4"; final String fungibleToken5 = "fungibleWithCustomFees5"; @@ -1273,7 +1215,7 @@ public HapiSpec transferMaxFungibleWith10FixedHtsCustomFees2Layers() { } @HapiTest - public HapiSpec multipleTransfersWithMultipleCustomFees() { + final Stream multipleTransfersWithMultipleCustomFees() { return defaultHapiSpec("multipleTransfersWithMultipleCustomFees") .given( newKeyNamed(NFT_KEY), @@ -1378,7 +1320,7 @@ public HapiSpec multipleTransfersWithMultipleCustomFees() { } @HapiTest - public HapiSpec transferFungibleWithFixedHbarCustomFeeAmount0() { + final Stream transferFungibleWithFixedHbarCustomFeeAmount0() { return defaultHapiSpec("transferFungibleWithFixedHbarCustomFeeAmount0") .given( cryptoCreate(hbarCollector).balance(0L), @@ -1396,7 +1338,7 @@ public HapiSpec transferFungibleWithFixedHbarCustomFeeAmount0() { } @HapiTest - public HapiSpec transferFungibleWithFixedHtsCustomFeeAmount0() { + final Stream transferFungibleWithFixedHtsCustomFeeAmount0() { return defaultHapiSpec("transferFungibleWithFixedHtsCustomFeeAmount0") .given( cryptoCreate(htsCollector), @@ -1416,7 +1358,7 @@ public HapiSpec transferFungibleWithFixedHtsCustomFeeAmount0() { } @HapiTest - public HapiSpec transferNonFungibleWithFixedHbarCustomFeeAmount0() { + final Stream transferNonFungibleWithFixedHbarCustomFeeAmount0() { return defaultHapiSpec("transferNonFungibleWithFixedHbarCustomFeeAmount0") .given( newKeyNamed(NFT_KEY), @@ -1437,7 +1379,7 @@ public HapiSpec transferNonFungibleWithFixedHbarCustomFeeAmount0() { } @HapiTest - public HapiSpec transferNonFungibleWithFixedHtsCustomFeeAmount0() { + final Stream transferNonFungibleWithFixedHtsCustomFeeAmount0() { return defaultHapiSpec("transferNonFungibleWithFixedHtsCustomFeeAmount0") .given( newKeyNamed(NFT_KEY), @@ -1460,7 +1402,7 @@ public HapiSpec transferNonFungibleWithFixedHtsCustomFeeAmount0() { } @HapiTest - public HapiSpec transferFungibleWithFixedHbarCustomFeeSenderHasOnlyGasAmount() { + final Stream transferFungibleWithFixedHbarCustomFeeSenderHasOnlyGasAmount() { final var gasAmount = 1669096L; return defaultHapiSpec("transferFungibleWithFixedHbarCustomFeeSenderHasOnlyGasAmount") .given( @@ -1484,7 +1426,7 @@ public HapiSpec transferFungibleWithFixedHbarCustomFeeSenderHasOnlyGasAmount() { } @HapiTest - public HapiSpec transferFungibleWithFixedHtsCustomFeeTotalSupply0() { + final Stream transferFungibleWithFixedHtsCustomFeeTotalSupply0() { return defaultHapiSpec("transferFungibleWithFixedHtsCustomFeeTotalSupply0") .given( cryptoCreate(htsCollector), @@ -1519,7 +1461,7 @@ public HapiSpec transferFungibleWithFixedHtsCustomFeeTotalSupply0() { } @HapiTest - public HapiSpec transferFungibleWithFixedHtsCustomFeeNotEnoughForGasAndFee() { + final Stream transferFungibleWithFixedHtsCustomFeeNotEnoughForGasAndFee() { final var gasAmount = 1669096L; return defaultHapiSpec("transferFungibleWithFixedHtsCustomFeeNotEnoughForGasAndFee") .given( @@ -1543,7 +1485,7 @@ public HapiSpec transferFungibleWithFixedHtsCustomFeeNotEnoughForGasAndFee() { } @HapiTest - public HapiSpec transferWithFractionalCustomFee() { + final Stream transferWithFractionalCustomFee() { return defaultHapiSpec("transferWithFractionalCustomFee") .given( cryptoCreate(htsCollector).balance(ONE_HUNDRED_HBARS), @@ -1574,7 +1516,7 @@ public HapiSpec transferWithFractionalCustomFee() { } @HapiTest - public HapiSpec transferWithInsufficientCustomFee() { + final Stream transferWithInsufficientCustomFee() { return defaultHapiSpec("transferWithInsufficientCustomFee") .given( cryptoCreate(htsCollector), @@ -1599,7 +1541,7 @@ public HapiSpec transferWithInsufficientCustomFee() { } @HapiTest - public HapiSpec transferMultipleTimesWithFixedFeeInHBarShouldVerifyEachTransferIsPaid() { + final Stream transferMultipleTimesWithFixedFeeInHBarShouldVerifyEachTransferIsPaid() { return defaultHapiSpec("transferMultipleTimesWithFixedFeeShouldVerifyEachTransferIsPaid") .given( cryptoCreate(hbarCollector).balance(ONE_HUNDRED_HBARS), @@ -1643,7 +1585,7 @@ public HapiSpec transferMultipleTimesWithFixedFeeInHBarShouldVerifyEachTransferI } @HapiTest - public HapiSpec transferMultipleTimesWithFixedFeeInCustomFungibleTokenShouldVerifyEachTransferIsPaid() { + final Stream transferMultipleTimesWithFixedFeeInCustomFungibleTokenShouldVerifyEachTransferIsPaid() { return defaultHapiSpec("transferMultipleTimesWithFixedFeeInCustomFungibleTokenShouldVerifyEachTransferIsPaid") .given( cryptoCreate(htsCollector).balance(ONE_HUNDRED_HBARS), @@ -1697,7 +1639,7 @@ public HapiSpec transferMultipleTimesWithFixedFeeInCustomFungibleTokenShouldVeri } @HapiTest - public HapiSpec transferFungibleToHollowAccountWithFixedHBarFee() { + final Stream transferFungibleToHollowAccountWithFixedHBarFee() { return defaultHapiSpec("transferFungibleToHollowAccountWithFixedHBarFee") .given( newKeyNamed(SECP_256K1_SOURCE_KEY).shape(SECP_256K1_SHAPE), @@ -1728,7 +1670,7 @@ public HapiSpec transferFungibleToHollowAccountWithFixedHBarFee() { } @HapiTest - public HapiSpec transferFungibleToHollowAccountWithFixedHtsFee() { + final Stream transferFungibleToHollowAccountWithFixedHtsFee() { return defaultHapiSpec("transferFungibleToHollowAccountWithFixedHtsFee") .given( newKeyNamed(SECP_256K1_SOURCE_KEY).shape(SECP_256K1_SHAPE), @@ -1766,7 +1708,7 @@ public HapiSpec transferFungibleToHollowAccountWithFixedHtsFee() { } @HapiTest - public HapiSpec transferNonFungibleToHollowAccountWithFixedHBarFee() { + final Stream transferNonFungibleToHollowAccountWithFixedHBarFee() { return defaultHapiSpec("transferNonFungibleToHollowAccountWithFixedHBarFee") .given( newKeyNamed(NFT_KEY), @@ -1801,7 +1743,7 @@ public HapiSpec transferNonFungibleToHollowAccountWithFixedHBarFee() { } @HapiTest - public HapiSpec transferNonFungibleToHollowAccountWithFixedHtsFee() { + final Stream transferNonFungibleToHollowAccountWithFixedHtsFee() { return defaultHapiSpec("transferNonFungibleToHollowAccountWithFixedHtsFee") .given( newKeyNamed(NFT_KEY), @@ -1841,9 +1783,4 @@ public HapiSpec transferNonFungibleToHollowAccountWithFixedHtsFee() { getAccountBalance(hollowAccountCollector).hasTokenBalance(feeDenom, htsFee)); })); } - - @Override - protected Logger getResultsLogger() { - return log; - } } diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/crypto/TransferWithCustomFractionalFees.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/crypto/TransferWithCustomFractionalFees.java index 615accbfacac..f731b50c6422 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/crypto/TransferWithCustomFractionalFees.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/crypto/TransferWithCustomFractionalFees.java @@ -28,24 +28,22 @@ import static com.hedera.services.bdd.spec.utilops.CustomSpecAssert.allRunFor; import static com.hedera.services.bdd.spec.utilops.UtilVerbs.newKeyNamed; import static com.hedera.services.bdd.spec.utilops.UtilVerbs.withOpContext; +import static com.hedera.services.bdd.suites.HapiSuite.ONE_HUNDRED_HBARS; +import static com.hedera.services.bdd.suites.HapiSuite.ONE_MILLION_HBARS; +import static com.hedera.services.bdd.suites.HapiSuite.SECP_256K1_SHAPE; +import static com.hedera.services.bdd.suites.HapiSuite.SECP_256K1_SOURCE_KEY; import static com.hedera.services.bdd.suites.crypto.AutoAccountUpdateSuite.TRANSFER_TXN_2; import static com.hedera.services.bdd.suites.crypto.AutoCreateUtils.createHollowAccountFrom; import com.hedera.services.bdd.junit.HapiTest; -import com.hedera.services.bdd.junit.HapiTestSuite; -import com.hedera.services.bdd.spec.HapiSpec; -import com.hedera.services.bdd.suites.HapiSuite; import com.hederahashgraph.api.proto.java.ResponseCodeEnum; -import java.util.List; import java.util.OptionalLong; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; +import java.util.stream.Stream; +import org.junit.jupiter.api.DynamicTest; import org.junit.jupiter.api.Tag; -@HapiTestSuite @Tag(CRYPTO) -public class TransferWithCustomFractionalFees extends HapiSuite { - private static final Logger log = LogManager.getLogger(TransferWithCustomFractionalFees.class); +public class TransferWithCustomFractionalFees { private static final long tokenTotal = 1_000L; private static final long numerator = 1L; private static final long denominator = 10L; @@ -65,54 +63,8 @@ public class TransferWithCustomFractionalFees extends HapiSuite { private static final String carol = "carol"; private static final String ivan = "ivan"; - public static void main(String... args) { - new TransferWithCustomFractionalFees().runSuiteAsync(); - } - - @Override - public List getSpecsInSuite() { - return allOf(positiveTests(), negativeTests()); - } - - private List positiveTests() { - return List.of( - transferWithFractionalCustomFee(), - transferWithFractionalCustomFeeAndHollowAccountCollector(), - transferWithFractionalCustomFeeNumeratorBiggerThanDenominator(), - transferWithFractionalCustomFeeBellowMinimumAmount(), - transferWithFractionalCustomFeeAboveMaximumAmount(), - transferWithFractionalCustomFeeNetOfTransfers(), - transferWithFractionalCustomFeeNetOfTransfersBellowMinimumAmount(), - transferWithFractionalCustomFeeNetOfTransfersAboveMaximumAmount(), - transferWithFractionalCustomFeeAllowance(), - transferWithFractionalCustomFeeAllowanceTokenOwnerIsCollector(), - transferWithFractionalCustomFeesThreeCollectors(), - transferWithFractionalCustomFeesAllCollectorsExempt(), - transferWithFractionalCustomFeesDenominatorMin(), - transferWithFractionalCustomFeesDenominatorMax(), - transferWithFractionalCustomFeeEqToAmount(), - transferWithFractionalCustomFeeGreaterThanAmount(), - transferWithFractionalCustomFeeGreaterThanAmountNetOfTransfers(), - transferWithFractionalCustomFeeMultipleRecipientsShouldRoundFee(), - transferWithFractionalCustomFeeMultipleRecipientsShouldRoundFeeNetOfTransfers(), - transferMultipleTimesWithFractionalFeeTakenFromSender(), - transferMultipleTimesWithFractionalFeeTakenFromReceiver()); - } - - private List negativeTests() { - return List.of(new HapiSpec[] { - transferWithFractionalCustomFeeNegativeMoreThanTen(), - transferWithFractionalCustomFeeZeroDenominator(), - transferWithFractionalCustomFeeNegativeNotEnoughAllowance(), - transferWithFractionalCustomFeeGreaterThanAmountNegative(), - transferWithFractionalCustomFeeNotEnoughBalance(), - transferWithFractionalCustomFeeMultipleRecipientsHasNotEnoughBalance(), - transferWithFractionalCustomFeeMultipleRecipientsNotEnoughBalance() - }); - } - @HapiTest - public HapiSpec transferWithFractionalCustomFeeNegativeMoreThanTen() { + final Stream transferWithFractionalCustomFeeNegativeMoreThanTen() { return defaultHapiSpec("transferWithFractionalCustomFeeNegativeMoreThanTen") .given( cryptoCreate(htsCollector).balance(ONE_HUNDRED_HBARS), @@ -151,7 +103,7 @@ public HapiSpec transferWithFractionalCustomFeeNegativeMoreThanTen() { } @HapiTest - public HapiSpec transferWithFractionalCustomFee() { + final Stream transferWithFractionalCustomFee() { return defaultHapiSpec("transferWithFractionalCustomFee") .given( cryptoCreate(htsCollector).balance(ONE_HUNDRED_HBARS), @@ -178,7 +130,7 @@ public HapiSpec transferWithFractionalCustomFee() { } @HapiTest - public HapiSpec transferWithFractionalCustomFeeZeroDenominator() { + final Stream transferWithFractionalCustomFeeZeroDenominator() { return defaultHapiSpec("transferWithFractionalCustomFeeZeroDenominator") .given( cryptoCreate(htsCollector).balance(ONE_HUNDRED_HBARS), @@ -198,7 +150,7 @@ public HapiSpec transferWithFractionalCustomFeeZeroDenominator() { } @HapiTest - public HapiSpec transferWithFractionalCustomFeeNumeratorBiggerThanDenominator() { + final Stream transferWithFractionalCustomFeeNumeratorBiggerThanDenominator() { return defaultHapiSpec("transferWithFractionalCustomFeeNumeratorBiggerThanDenominator") .given( cryptoCreate(htsCollector).balance(ONE_HUNDRED_HBARS), @@ -225,7 +177,7 @@ public HapiSpec transferWithFractionalCustomFeeNumeratorBiggerThanDenominator() } @HapiTest - public HapiSpec transferWithFractionalCustomFeeNetOfTransfers() { + final Stream transferWithFractionalCustomFeeNetOfTransfers() { return defaultHapiSpec("transferWithFractionalCustomFeeNetOfTransfers") .given( cryptoCreate(htsCollector).balance(ONE_HUNDRED_HBARS), @@ -252,7 +204,7 @@ public HapiSpec transferWithFractionalCustomFeeNetOfTransfers() { } @HapiTest - public HapiSpec transferWithFractionalCustomFeeBellowMinimumAmount() { + final Stream transferWithFractionalCustomFeeBellowMinimumAmount() { return defaultHapiSpec("transferWithFractionalCustomFeeBellowMinimumAmount") .given( cryptoCreate(htsCollector).balance(ONE_HUNDRED_HBARS), @@ -279,7 +231,7 @@ public HapiSpec transferWithFractionalCustomFeeBellowMinimumAmount() { } @HapiTest - public HapiSpec transferWithFractionalCustomFeeAboveMaximumAmount() { + final Stream transferWithFractionalCustomFeeAboveMaximumAmount() { return defaultHapiSpec("transferWithFractionalCustomFeeAboveMaximumAmount") .given( cryptoCreate(htsCollector).balance(ONE_HUNDRED_HBARS), @@ -306,7 +258,7 @@ public HapiSpec transferWithFractionalCustomFeeAboveMaximumAmount() { } @HapiTest - public HapiSpec transferWithFractionalCustomFeeNetOfTransfersBellowMinimumAmount() { + final Stream transferWithFractionalCustomFeeNetOfTransfersBellowMinimumAmount() { return defaultHapiSpec("transferWithFractionalCustomFeeNetOfTransfersBellowMinimumAmount") .given( cryptoCreate(htsCollector).balance(ONE_HUNDRED_HBARS), @@ -333,7 +285,7 @@ public HapiSpec transferWithFractionalCustomFeeNetOfTransfersBellowMinimumAmount } @HapiTest - public HapiSpec transferWithFractionalCustomFeeNetOfTransfersAboveMaximumAmount() { + final Stream transferWithFractionalCustomFeeNetOfTransfersAboveMaximumAmount() { return defaultHapiSpec("transferWithFractionalCustomFeeNetOfTransfersAboveMaximumAmount") .given( cryptoCreate(htsCollector).balance(ONE_HUNDRED_HBARS), @@ -360,7 +312,7 @@ public HapiSpec transferWithFractionalCustomFeeNetOfTransfersAboveMaximumAmount( } @HapiTest - public HapiSpec transferWithFractionalCustomFeeAllowance() { + final Stream transferWithFractionalCustomFeeAllowance() { return defaultHapiSpec("transferWithFractionalCustomFeeAllowance") .given( cryptoCreate(htsCollector).balance(ONE_HUNDRED_HBARS), @@ -399,7 +351,7 @@ public HapiSpec transferWithFractionalCustomFeeAllowance() { } @HapiTest - public HapiSpec transferWithFractionalCustomFeeAllowanceNetOfTransfers() { + final Stream transferWithFractionalCustomFeeAllowanceNetOfTransfers() { return defaultHapiSpec("transferWithFractionalCustomFeeAllowanceNetOfTransfers") .given( cryptoCreate(htsCollector).balance(ONE_HUNDRED_HBARS), @@ -438,7 +390,7 @@ public HapiSpec transferWithFractionalCustomFeeAllowanceNetOfTransfers() { } @HapiTest - public HapiSpec transferWithFractionalCustomFeeAllowanceTokenOwnerIsCollector() { + final Stream transferWithFractionalCustomFeeAllowanceTokenOwnerIsCollector() { return defaultHapiSpec("transferWithFractionalCustomFeeAllowanceTokenOwnerIsCollector") .given( cryptoCreate(htsCollector).balance(ONE_HUNDRED_HBARS), @@ -476,7 +428,7 @@ public HapiSpec transferWithFractionalCustomFeeAllowanceTokenOwnerIsCollector() } @HapiTest - public HapiSpec transferWithFractionalCustomFeeNegativeNotEnoughAllowance() { + final Stream transferWithFractionalCustomFeeNegativeNotEnoughAllowance() { return defaultHapiSpec("transferWithFractionalCustomFeeNegativeNotEnoughAllowance") .given( cryptoCreate(htsCollector).balance(ONE_HUNDRED_HBARS), @@ -511,7 +463,7 @@ public HapiSpec transferWithFractionalCustomFeeNegativeNotEnoughAllowance() { } @HapiTest - public HapiSpec transferWithFractionalCustomFeesThreeCollectors() { + final Stream transferWithFractionalCustomFeesThreeCollectors() { return defaultHapiSpec("transferWithFractionalCustomFeesThreeCollectors") .given( cryptoCreate(alice), @@ -537,7 +489,7 @@ public HapiSpec transferWithFractionalCustomFeesThreeCollectors() { } @HapiTest - public HapiSpec transferWithFractionalCustomFeesAllCollectorsExempt() { + final Stream transferWithFractionalCustomFeesAllCollectorsExempt() { return defaultHapiSpec("transferWithFractionalCustomFeesAllCollectorsExempt") .given( cryptoCreate(alice), @@ -564,7 +516,7 @@ public HapiSpec transferWithFractionalCustomFeesAllCollectorsExempt() { } @HapiTest - public HapiSpec transferWithFractionalCustomFeesDenominatorMin() { + final Stream transferWithFractionalCustomFeesDenominatorMin() { return defaultHapiSpec("transferWithFractionalCustomFeesDenominatorMin") .given( cryptoCreate(alice), @@ -601,7 +553,7 @@ public HapiSpec transferWithFractionalCustomFeesDenominatorMin() { } @HapiTest - public HapiSpec transferWithFractionalCustomFeesDenominatorMax() { + final Stream transferWithFractionalCustomFeesDenominatorMax() { return defaultHapiSpec("transferWithFractionalCustomFeesDenominatorMax") .given( cryptoCreate(alice), @@ -634,7 +586,7 @@ public HapiSpec transferWithFractionalCustomFeesDenominatorMax() { } @HapiTest - public HapiSpec transferWithFractionalCustomFeesMultipleReceivers() { + final Stream transferWithFractionalCustomFeesMultipleReceivers() { return defaultHapiSpec("transferWithFractionalCustomFeesMultipleReceivers") .given( cryptoCreate(htsCollector).balance(ONE_HUNDRED_HBARS), @@ -681,7 +633,7 @@ public HapiSpec transferWithFractionalCustomFeesMultipleReceivers() { } @HapiTest - public HapiSpec transferWithFractionalCustomFeeEqToAmount() { + final Stream transferWithFractionalCustomFeeEqToAmount() { return defaultHapiSpec("transferWithFractionalCustomFeeEqToAmount") .given( cryptoCreate(htsCollector).balance(ONE_HUNDRED_HBARS), @@ -708,7 +660,7 @@ public HapiSpec transferWithFractionalCustomFeeEqToAmount() { } @HapiTest - public HapiSpec transferWithFractionalCustomFeeGreaterThanAmountNegative() { + final Stream transferWithFractionalCustomFeeGreaterThanAmountNegative() { return defaultHapiSpec("transferWithFractionalCustomFeeGreaterThanAmountNegative") .given( cryptoCreate(htsCollector).balance(ONE_HUNDRED_HBARS), @@ -735,7 +687,7 @@ public HapiSpec transferWithFractionalCustomFeeGreaterThanAmountNegative() { } @HapiTest - public HapiSpec transferWithFractionalCustomFeeGreaterThanAmount() { + final Stream transferWithFractionalCustomFeeGreaterThanAmount() { return defaultHapiSpec("transferWithFractionalCustomFeeGreaterThanAmount") .given( cryptoCreate(htsCollector).balance(ONE_HUNDRED_HBARS), @@ -765,7 +717,7 @@ public HapiSpec transferWithFractionalCustomFeeGreaterThanAmount() { } @HapiTest - public HapiSpec transferWithFractionalCustomFeeGreaterThanAmountNetOfTransfers() { + final Stream transferWithFractionalCustomFeeGreaterThanAmountNetOfTransfers() { return defaultHapiSpec("transferWithFractionalCustomFeeGreaterThanAmountNetOfTransfers") .given( cryptoCreate(htsCollector).balance(ONE_HUNDRED_HBARS), @@ -795,7 +747,7 @@ public HapiSpec transferWithFractionalCustomFeeGreaterThanAmountNetOfTransfers() } @HapiTest - public HapiSpec transferWithFractionalCustomFeeMultipleRecipientsShouldRoundFee() { + final Stream transferWithFractionalCustomFeeMultipleRecipientsShouldRoundFee() { return defaultHapiSpec("transferWithFractionalCustomFeeMultipleRecipientsShouldRoundFee") .given( cryptoCreate(htsCollector).balance(ONE_HUNDRED_HBARS), @@ -828,7 +780,7 @@ public HapiSpec transferWithFractionalCustomFeeMultipleRecipientsShouldRoundFee( } @HapiTest - public HapiSpec transferWithFractionalCustomFeeMultipleRecipientsHasNotEnoughBalance() { + final Stream transferWithFractionalCustomFeeMultipleRecipientsHasNotEnoughBalance() { return defaultHapiSpec("transferWithFractionalCustomFeeMultipleRecipientsHasNotEnoughBalance") .given( cryptoCreate(htsCollector).balance(ONE_HUNDRED_HBARS), @@ -861,7 +813,7 @@ public HapiSpec transferWithFractionalCustomFeeMultipleRecipientsHasNotEnoughBal } @HapiTest - public HapiSpec transferWithFractionalCustomFeeMultipleRecipientsShouldRoundFeeNetOfTransfers() { + final Stream transferWithFractionalCustomFeeMultipleRecipientsShouldRoundFeeNetOfTransfers() { return defaultHapiSpec("transferWithFractionalCustomFeeMultipleRecipientsShouldRoundFeeNetOfTransfers") .given( cryptoCreate(htsCollector).balance(ONE_HUNDRED_HBARS), @@ -894,7 +846,7 @@ public HapiSpec transferWithFractionalCustomFeeMultipleRecipientsShouldRoundFeeN } @HapiTest - public HapiSpec transferWithFractionalCustomFeeNotEnoughBalance() { + final Stream transferWithFractionalCustomFeeNotEnoughBalance() { return defaultHapiSpec("transferWithFractionalCustomFeeNotEnoughBalance") .given( cryptoCreate(htsCollector).balance(ONE_HUNDRED_HBARS), @@ -922,7 +874,7 @@ public HapiSpec transferWithFractionalCustomFeeNotEnoughBalance() { } @HapiTest - public HapiSpec transferWithFractionalCustomFeeMultipleRecipientsNotEnoughBalance() { + final Stream transferWithFractionalCustomFeeMultipleRecipientsNotEnoughBalance() { return defaultHapiSpec("transferWithFractionalCustomFeeMultipleRecipientsNotEnoughBalance") .given( cryptoCreate(htsCollector).balance(ONE_HUNDRED_HBARS), @@ -952,7 +904,7 @@ public HapiSpec transferWithFractionalCustomFeeMultipleRecipientsNotEnoughBalanc } @HapiTest - public HapiSpec transferMultipleTimesWithFractionalFeeTakenFromSender() { + final Stream transferMultipleTimesWithFractionalFeeTakenFromSender() { return defaultHapiSpec("transferMultipleTimesWithFractionalFeeTakenFromSender") .given( cryptoCreate(htsCollector).balance(ONE_HUNDRED_HBARS), @@ -996,7 +948,7 @@ public HapiSpec transferMultipleTimesWithFractionalFeeTakenFromSender() { } @HapiTest - public HapiSpec transferMultipleTimesWithFractionalFeeTakenFromReceiver() { + final Stream transferMultipleTimesWithFractionalFeeTakenFromReceiver() { return defaultHapiSpec("transferMultipleTimesWithFractionalFeeTakenFromReceiver") .given( cryptoCreate(htsCollector).balance(ONE_HUNDRED_HBARS), @@ -1040,7 +992,7 @@ public HapiSpec transferMultipleTimesWithFractionalFeeTakenFromReceiver() { } @HapiTest - public HapiSpec transferWithFractionalCustomFeeAndHollowAccountCollector() { + final Stream transferWithFractionalCustomFeeAndHollowAccountCollector() { return defaultHapiSpec("transferWithFractionalCustomFeeAndHollowAccountCollector") .given( newKeyNamed(SECP_256K1_SOURCE_KEY).shape(SECP_256K1_SHAPE), @@ -1076,9 +1028,4 @@ public HapiSpec transferWithFractionalCustomFeeAndHollowAccountCollector() { getAccountBalance(hollowAccountCollector).hasTokenBalance(token, 10L)); })); } - - @Override - protected Logger getResultsLogger() { - return log; - } } diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/crypto/TransferWithCustomRoyaltyFees.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/crypto/TransferWithCustomRoyaltyFees.java index 16f7dce132d9..9f4eff91023a 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/crypto/TransferWithCustomRoyaltyFees.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/crypto/TransferWithCustomRoyaltyFees.java @@ -42,6 +42,10 @@ import static com.hedera.services.bdd.spec.utilops.CustomSpecAssert.allRunFor; import static com.hedera.services.bdd.spec.utilops.UtilVerbs.newKeyNamed; import static com.hedera.services.bdd.spec.utilops.UtilVerbs.withOpContext; +import static com.hedera.services.bdd.suites.HapiSuite.ONE_HUNDRED_HBARS; +import static com.hedera.services.bdd.suites.HapiSuite.ONE_MILLION_HBARS; +import static com.hedera.services.bdd.suites.HapiSuite.SECP_256K1_SHAPE; +import static com.hedera.services.bdd.suites.HapiSuite.SECP_256K1_SOURCE_KEY; import static com.hedera.services.bdd.suites.crypto.AutoAccountUpdateSuite.TRANSFER_TXN_2; import static com.hedera.services.bdd.suites.crypto.AutoCreateUtils.createHollowAccountFrom; import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.INSUFFICIENT_ACCOUNT_BALANCE; @@ -51,19 +55,17 @@ import com.hedera.node.app.hapi.utils.ByteStringUtils; import com.hedera.services.bdd.junit.HapiTest; -import com.hedera.services.bdd.junit.HapiTestSuite; -import com.hedera.services.bdd.spec.HapiSpec; -import com.hedera.services.bdd.suites.HapiSuite; import com.hederahashgraph.api.proto.java.TokenSupplyType; import com.hederahashgraph.api.proto.java.TokenType; import java.util.List; +import java.util.stream.Stream; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; +import org.junit.jupiter.api.DynamicTest; import org.junit.jupiter.api.Tag; -@HapiTestSuite @Tag(CRYPTO) -public class TransferWithCustomRoyaltyFees extends HapiSuite { +public class TransferWithCustomRoyaltyFees { private static final Logger log = LogManager.getLogger(TransferWithCustomRoyaltyFees.class); private static final long numerator = 1L; private static final long denominator = 10L; @@ -83,52 +85,8 @@ public class TransferWithCustomRoyaltyFees extends HapiSuite { private static final String carol = "carol"; private static final String dave = "dave"; - public static void main(String... args) { - new TransferWithCustomRoyaltyFees().runSuiteAsync(); - } - - @Override - public List getSpecsInSuite() { - return List.of( - transferNonFungibleWithRoyaltyHbarFee(), - transferNonFungibleWithRoyaltyFungibleFee(), - transferNonFungibleWithRoyaltyFallbackHbarFee(), - transferNonFungibleWithHollowAccountAndRoyaltyHbarFee(), - transferNonFungibleWithHollowAccountAndRoyaltyFungibleFee(), - transferNonFungibleWithHollowAccountAndRoyaltyFallbackHbarFee(), - transferNonFungibleWithRoyaltyFallbackFungibleFee(), - transferNonFungibleWithRoyaltyHbarFeeInsufficientBalance(), - transferNonFungibleWithRoyaltyFungibleFeeInsufficientBalance(), - transferNonFungibleWithRoyaltyFallbackHbarFeeInsufficientBalance(), - transferNonFungibleWithRoyaltyFallbackFungibleFeeInsufficientBalance(), - transferNonFungibleWithRoyaltyFallbackFungibleFeeNoAssociation(), - transferNonFungibleWithRoyaltyFungibleFeeNoAssociation(), - transferNonFungibleWithRoyaltyHtsFee2Transactions(), - transferNonFungibleWithRoyaltyHtsFee2TokenFees(), - transferNonFungibleWithRoyaltyHtsFeeMinFee(), - transferNonFungibleWithRoyaltyAllowancePassFromSpenderToOwner(), - transferNonFungibleWithRoyaltyRandomFeeSameCollectorAccount(), - transferNonFungibleWithRoyaltyRandomFeeDifCollectorAccount(), - transferNonFungibleWithRoyaltySameHTSTreasuryТоRandom(), - transferNonFungibleWithRoyaltyAnotherHTSTreasuryТоRandom(), - transferNonFungibleWithRoyaltyAllowanceOfTheNFTGiven(), - transferNonFungibleWithRoyaltyAllCollectorsExempt(), - transferNonFungibleWith2LayersRoyaltyFungibleFee(), - transferNonFungibleWith2LayersRoyaltyHbarFee(), - transferNonFungibleWithRoyaltyFromFeeCollector(), - transferMultipleTimesWithRoyaltyWithFallbackFeeShouldVerifyEachTransferIsPaid(), - transferNonFungibleWithMultipleRoyaltyFungibleFee(), - transferNonFungibleWithMultipleRoyaltyFungibleFeeToFeeCollector(), - transferNonFungibleWithRoyaltyFallbackAllowanceNegative(), - transferNonFungibleWithMultipleRoyaltyFungibleFeeNegative(), - transferMultipleTimesWithRoyaltyWithFallbackFeeShouldVerifyEachTransferIsPaid(), - transferMultipleNonFungibleWithRoyaltyFungibleFee(), - transferMultipleNonFungibleWithRoyaltyHbarFee(), - transferMultipleNonFungibleWithRoyaltyHbarAndFungibleFee()); - } - @HapiTest - public HapiSpec transferNonFungibleWithRoyaltyHbarFee() { + final Stream transferNonFungibleWithRoyaltyHbarFee() { final var fourHundredHbars = 4 * ONE_HUNDRED_HBARS; final var twoHundredHbars = 2 * ONE_HUNDRED_HBARS; return defaultHapiSpec("transferNonFungibleWithRoyaltyHbarFee") @@ -161,7 +119,7 @@ public HapiSpec transferNonFungibleWithRoyaltyHbarFee() { } @HapiTest - public HapiSpec transferNonFungibleWithRoyaltyFungibleFee() { + final Stream transferNonFungibleWithRoyaltyFungibleFee() { return defaultHapiSpec("transferNonFungibleWithRoyaltyFungibleFee") .given( newKeyNamed(NFT_KEY), @@ -197,7 +155,7 @@ public HapiSpec transferNonFungibleWithRoyaltyFungibleFee() { } @HapiTest - public HapiSpec transferNonFungibleWithMultipleRoyaltyFungibleFee() { + final Stream transferNonFungibleWithMultipleRoyaltyFungibleFee() { return defaultHapiSpec("transferNonFungibleWithMultipleRoyaltyFungibleFee") .given( newKeyNamed(NFT_KEY), @@ -237,7 +195,7 @@ public HapiSpec transferNonFungibleWithMultipleRoyaltyFungibleFee() { } @HapiTest - public HapiSpec transferNonFungibleWithMultipleRoyaltyFungibleFeeToFeeCollector() { + final Stream transferNonFungibleWithMultipleRoyaltyFungibleFeeToFeeCollector() { return defaultHapiSpec("transferNonFungibleWithMultipleRoyaltyFungibleFeeToFeeCollector") .given( newKeyNamed(NFT_KEY), @@ -276,7 +234,7 @@ public HapiSpec transferNonFungibleWithMultipleRoyaltyFungibleFeeToFeeCollector( } @HapiTest - public HapiSpec transferNonFungibleWithMultipleRoyaltyFungibleFeeNegative() { + final Stream transferNonFungibleWithMultipleRoyaltyFungibleFeeNegative() { return defaultHapiSpec("transferNonFungibleWithMultipleRoyaltyFungibleFeeNegative") .given( newKeyNamed(NFT_KEY), @@ -317,7 +275,7 @@ public HapiSpec transferNonFungibleWithMultipleRoyaltyFungibleFeeNegative() { } @HapiTest - public HapiSpec transferNonFungibleWithRoyaltyFallbackHbarFee() { + final Stream transferNonFungibleWithRoyaltyFallbackHbarFee() { return defaultHapiSpec("transferNonFungibleWithRoyaltyFallbackHbarFee") .given( newKeyNamed(NFT_KEY), @@ -348,7 +306,7 @@ public HapiSpec transferNonFungibleWithRoyaltyFallbackHbarFee() { } @HapiTest - public HapiSpec transferNonFungibleWithRoyaltyFallbackFungibleFee() { + final Stream transferNonFungibleWithRoyaltyFallbackFungibleFee() { return defaultHapiSpec("transferNonFungibleWithRoyaltyFallbackFungibleFee") .given( newKeyNamed(NFT_KEY), @@ -382,7 +340,7 @@ public HapiSpec transferNonFungibleWithRoyaltyFallbackFungibleFee() { } @HapiTest - public HapiSpec transferNonFungibleWithRoyaltyHbarFeeInsufficientBalance() { + final Stream transferNonFungibleWithRoyaltyHbarFeeInsufficientBalance() { final var fourHundredHbars = 4 * ONE_HUNDRED_HBARS; return defaultHapiSpec("transferNonFungibleWithRoyaltyHbarFeeInsufficientBalance") .given( @@ -412,7 +370,7 @@ public HapiSpec transferNonFungibleWithRoyaltyHbarFeeInsufficientBalance() { } @HapiTest - public HapiSpec transferNonFungibleWithRoyaltyFungibleFeeInsufficientBalance() { + final Stream transferNonFungibleWithRoyaltyFungibleFeeInsufficientBalance() { return defaultHapiSpec("transferNonFungibleWithRoyaltyFungibleFeeInsufficientBalance") .given( newKeyNamed(NFT_KEY), @@ -444,7 +402,7 @@ public HapiSpec transferNonFungibleWithRoyaltyFungibleFeeInsufficientBalance() { } @HapiTest - public HapiSpec transferNonFungibleWithRoyaltyFallbackHbarFeeInsufficientBalance() { + final Stream transferNonFungibleWithRoyaltyFallbackHbarFeeInsufficientBalance() { return defaultHapiSpec("transferNonFungibleWithRoyaltyFallbackHbarFeeInsufficientBalance") .given( newKeyNamed(NFT_KEY), @@ -473,7 +431,7 @@ public HapiSpec transferNonFungibleWithRoyaltyFallbackHbarFeeInsufficientBalance } @HapiTest - public HapiSpec transferNonFungibleWithRoyaltyFallbackFungibleFeeInsufficientBalance() { + final Stream transferNonFungibleWithRoyaltyFallbackFungibleFeeInsufficientBalance() { return defaultHapiSpec("transferNonFungibleWithRoyaltyFallbackFungibleFeeInsufficientBalance") .given( newKeyNamed(NFT_KEY), @@ -505,7 +463,7 @@ public HapiSpec transferNonFungibleWithRoyaltyFallbackFungibleFeeInsufficientBal } @HapiTest - public HapiSpec transferNonFungibleWithRoyaltyFallbackFungibleFeeNoAssociation() { + final Stream transferNonFungibleWithRoyaltyFallbackFungibleFeeNoAssociation() { return defaultHapiSpec("transferNonFungibleWithRoyaltyFallbackFungibleFeeNoAssociation") .given( newKeyNamed(NFT_KEY), @@ -536,7 +494,7 @@ public HapiSpec transferNonFungibleWithRoyaltyFallbackFungibleFeeNoAssociation() } @HapiTest - public HapiSpec transferNonFungibleWithRoyaltyFungibleFeeNoAssociation() { + final Stream transferNonFungibleWithRoyaltyFungibleFeeNoAssociation() { return defaultHapiSpec("transferNonFungibleWithRoyaltyFungibleFeeNoAssociation") .given( newKeyNamed(NFT_KEY), @@ -567,7 +525,7 @@ public HapiSpec transferNonFungibleWithRoyaltyFungibleFeeNoAssociation() { } @HapiTest - public HapiSpec transferNonFungibleWithRoyaltyZeroTransaction() { + final Stream transferNonFungibleWithRoyaltyZeroTransaction() { return defaultHapiSpec("transferNonFungibleWithRoyaltyZeroTransaction") .given( newKeyNamed(NFT_KEY), @@ -604,7 +562,7 @@ public HapiSpec transferNonFungibleWithRoyaltyZeroTransaction() { } @HapiTest - public HapiSpec transferNonFungibleWithRoyaltyHtsFee2Transactions() { + final Stream transferNonFungibleWithRoyaltyHtsFee2Transactions() { return defaultHapiSpec("transferNonFungibleWithRoyaltyHtsFee2Transactions") .given( newKeyNamed(NFT_KEY), @@ -637,7 +595,7 @@ public HapiSpec transferNonFungibleWithRoyaltyHtsFee2Transactions() { } @HapiTest - public HapiSpec transferNonFungibleWithRoyaltyHtsFee2TokenFees() { + final Stream transferNonFungibleWithRoyaltyHtsFee2TokenFees() { return defaultHapiSpec("transferNonFungibleWithRoyaltyHtsFee2TokenFees") .given( newKeyNamed(NFT_KEY), @@ -674,7 +632,7 @@ public HapiSpec transferNonFungibleWithRoyaltyHtsFee2TokenFees() { } @HapiTest - public HapiSpec transferNonFungibleWithRoyaltyHtsFeeMinFee() { + final Stream transferNonFungibleWithRoyaltyHtsFeeMinFee() { return defaultHapiSpec("transferNonFungibleWithRoyaltyHtsFeeMinFee") .given( newKeyNamed(NFT_KEY), @@ -716,7 +674,7 @@ public HapiSpec transferNonFungibleWithRoyaltyHtsFeeMinFee() { } @HapiTest - public HapiSpec transferNonFungibleWithRoyaltyFallbackAllowanceNegative() { + final Stream transferNonFungibleWithRoyaltyFallbackAllowanceNegative() { return defaultHapiSpec("transferNonFungibleWithRoyaltyFallbackAllowanceNegative") .given( newKeyNamed(NFT_KEY), @@ -760,7 +718,7 @@ public HapiSpec transferNonFungibleWithRoyaltyFallbackAllowanceNegative() { } @HapiTest - public HapiSpec transferNonFungibleWithRoyaltyAllowancePassFromSpenderToOwner() { + final Stream transferNonFungibleWithRoyaltyAllowancePassFromSpenderToOwner() { return defaultHapiSpec("transferNonFungibleWithRoyaltyAllowancePassFromSpenderToOwner") .given( newKeyNamed(NFT_KEY), @@ -806,7 +764,7 @@ public HapiSpec transferNonFungibleWithRoyaltyAllowancePassFromSpenderToOwner() } @HapiTest - public HapiSpec transferNonFungibleWithRoyaltyRandomFeeSameCollectorAccount() { + final Stream transferNonFungibleWithRoyaltyRandomFeeSameCollectorAccount() { return defaultHapiSpec("transferNonFungibleWithRoyaltyRandomFeeSameCollectorAccount") .given( newKeyNamed(NFT_KEY), @@ -846,7 +804,7 @@ public HapiSpec transferNonFungibleWithRoyaltyRandomFeeSameCollectorAccount() { } @HapiTest - public HapiSpec transferNonFungibleWithRoyaltyRandomFeeDifCollectorAccount() { + final Stream transferNonFungibleWithRoyaltyRandomFeeDifCollectorAccount() { return defaultHapiSpec("transferNonFungibleWithRoyaltyRandomFeeDifCollectorAccount") .given( newKeyNamed(NFT_KEY), @@ -891,7 +849,7 @@ public HapiSpec transferNonFungibleWithRoyaltyRandomFeeDifCollectorAccount() { } @HapiTest - public HapiSpec transferNonFungibleWithRoyaltySameHTSTreasuryТоRandom() { + final Stream transferNonFungibleWithRoyaltySameHTSTreasuryТоRandom() { return defaultHapiSpec("transferNonFungibleWithRoyaltySameHTSTreasuryТоRandom") .given( newKeyNamed(NFT_KEY), @@ -935,7 +893,7 @@ public HapiSpec transferNonFungibleWithRoyaltyRandomFeeDifCollectorAccount() { } @HapiTest - public HapiSpec transferNonFungibleWithRoyaltyAnotherHTSTreasuryТоRandom() { + final Stream transferNonFungibleWithRoyaltyAnotherHTSTreasuryТоRandom() { return defaultHapiSpec("transferNonFungibleWithRoyaltyAnotherHTSTreasuryТоRandom") .given( newKeyNamed(NFT_KEY), @@ -977,7 +935,7 @@ public HapiSpec transferNonFungibleWithRoyaltyRandomFeeDifCollectorAccount() { } @HapiTest - public HapiSpec transferNonFungibleWithRoyaltyAllowanceOfTheNFTGiven() { + final Stream transferNonFungibleWithRoyaltyAllowanceOfTheNFTGiven() { return defaultHapiSpec("transferNonFungibleWithRoyaltyAllowancePassFromSpenderToOwner") .given( newKeyNamed(NFT_KEY), @@ -1028,7 +986,7 @@ public HapiSpec transferNonFungibleWithRoyaltyAllowanceOfTheNFTGiven() { } @HapiTest - public HapiSpec transferNonFungibleWithRoyaltyAllCollectorsExempt() { + final Stream transferNonFungibleWithRoyaltyAllCollectorsExempt() { return defaultHapiSpec("transferNonFungibleWithRoyaltyAllCollectorsExempt") .given( newKeyNamed(NFT_KEY), @@ -1068,7 +1026,7 @@ public HapiSpec transferNonFungibleWithRoyaltyAllCollectorsExempt() { } @HapiTest - public HapiSpec transferNonFungibleWith2LayersRoyaltyFungibleFee() { + final Stream transferNonFungibleWith2LayersRoyaltyFungibleFee() { return defaultHapiSpec("transferNonFungibleWithRoyaltyFungibleFeeNoAssociation") .given( newKeyNamed(NFT_KEY), @@ -1108,7 +1066,7 @@ public HapiSpec transferNonFungibleWith2LayersRoyaltyFungibleFee() { } @HapiTest - public HapiSpec transferNonFungibleWith2LayersRoyaltyHbarFee() { + final Stream transferNonFungibleWith2LayersRoyaltyHbarFee() { return defaultHapiSpec("transferNonFungibleWith2LayersRoyaltyHbarFee") .given( newKeyNamed(NFT_KEY), @@ -1144,7 +1102,7 @@ public HapiSpec transferNonFungibleWith2LayersRoyaltyHbarFee() { } @HapiTest - public HapiSpec transferNonFungibleWithRoyaltyFromFeeCollector() { + final Stream transferNonFungibleWithRoyaltyFromFeeCollector() { return defaultHapiSpec("transferNonFungibleWithRoyaltyFungibleFee") .given( newKeyNamed(NFT_KEY), @@ -1179,7 +1137,7 @@ public HapiSpec transferNonFungibleWithRoyaltyFromFeeCollector() { } @HapiTest - public HapiSpec transferMultipleTimesWithRoyaltyWithFallbackFeeShouldVerifyEachTransferIsPaid() { + final Stream transferMultipleTimesWithRoyaltyWithFallbackFeeShouldVerifyEachTransferIsPaid() { return defaultHapiSpec("transferMultipleTimesWithRoyaltyWithFallbackFeeShouldVerifyEachTransferIsPaid") .given( newKeyNamed(NFT_KEY), @@ -1227,7 +1185,7 @@ public HapiSpec transferMultipleTimesWithRoyaltyWithFallbackFeeShouldVerifyEachT } @HapiTest - public HapiSpec transferMultipleNonFungibleWithRoyaltyFungibleFee() { + final Stream transferMultipleNonFungibleWithRoyaltyFungibleFee() { return defaultHapiSpec("transferMultipleNonFungibleWithRoyaltyFungibleFee") .given( newKeyNamed(NFT_KEY), @@ -1266,7 +1224,7 @@ public HapiSpec transferMultipleNonFungibleWithRoyaltyFungibleFee() { } @HapiTest - public HapiSpec transferMultipleNonFungibleWithRoyaltyHbarFee() { + final Stream transferMultipleNonFungibleWithRoyaltyHbarFee() { return defaultHapiSpec("transferNonFungibleWithRoyaltyHbarFee") .given( newKeyNamed(NFT_KEY), @@ -1298,7 +1256,7 @@ public HapiSpec transferMultipleNonFungibleWithRoyaltyHbarFee() { } @HapiTest - public HapiSpec transferMultipleNonFungibleWithRoyaltyHbarAndFungibleFee() { + final Stream transferMultipleNonFungibleWithRoyaltyHbarAndFungibleFee() { return defaultHapiSpec("transferNonFungibleWithRoyaltyHbarFee") .given( newKeyNamed(NFT_KEY), @@ -1340,7 +1298,7 @@ public HapiSpec transferMultipleNonFungibleWithRoyaltyHbarAndFungibleFee() { } @HapiTest - public HapiSpec transferNonFungibleWithHollowAccountAndRoyaltyHbarFee() { + final Stream transferNonFungibleWithHollowAccountAndRoyaltyHbarFee() { final var fourHundredHbars = 4 * ONE_HUNDRED_HBARS; final var twoHundredHbars = 2 * ONE_HUNDRED_HBARS; return defaultHapiSpec("transferNonFungibleWithHollowAccountAndRoyaltyHbarFee") @@ -1380,7 +1338,7 @@ public HapiSpec transferNonFungibleWithHollowAccountAndRoyaltyHbarFee() { } @HapiTest - public HapiSpec transferNonFungibleWithHollowAccountAndRoyaltyFungibleFee() { + final Stream transferNonFungibleWithHollowAccountAndRoyaltyFungibleFee() { return defaultHapiSpec("transferNonFungibleWithHollowAccountAndRoyaltyFungibleFee") .given( newKeyNamed(NFT_KEY), @@ -1429,7 +1387,7 @@ public HapiSpec transferNonFungibleWithHollowAccountAndRoyaltyFungibleFee() { } @HapiTest - public HapiSpec transferNonFungibleWithHollowAccountAndRoyaltyFallbackHbarFee() { + final Stream transferNonFungibleWithHollowAccountAndRoyaltyFallbackHbarFee() { return defaultHapiSpec("transferNonFungibleWithHollowAccountAndRoyaltyFallbackHbarFee") .given( newKeyNamed(NFT_KEY), @@ -1465,9 +1423,4 @@ public HapiSpec transferNonFungibleWithHollowAccountAndRoyaltyFallbackHbarFee() getAccountBalance(hollowAccountCollector).hasTinyBars(ONE_HUNDRED_HBARS + 100)); })); } - - @Override - protected Logger getResultsLogger() { - return log; - } } diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/crypto/TxnReceiptRegression.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/crypto/TxnReceiptRegression.java deleted file mode 100644 index 6b59562048f8..000000000000 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/crypto/TxnReceiptRegression.java +++ /dev/null @@ -1,145 +0,0 @@ -/* - * Copyright (C) 2020-2024 Hedera Hashgraph, LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.hedera.services.bdd.suites.crypto; - -import static com.hedera.services.bdd.junit.TestTags.CRYPTO; -import static com.hedera.services.bdd.spec.HapiSpec.defaultHapiSpec; -import static com.hedera.services.bdd.spec.queries.QueryVerbs.getReceipt; -import static com.hedera.services.bdd.spec.transactions.TxnVerbs.cryptoCreate; -import static com.hedera.services.bdd.spec.transactions.TxnVerbs.cryptoTransfer; -import static com.hedera.services.bdd.spec.transactions.crypto.HapiCryptoTransfer.tinyBarsFromTo; -import static com.hedera.services.bdd.spec.utilops.UtilVerbs.ifHapiTest; -import static com.hedera.services.bdd.spec.utilops.UtilVerbs.sleepFor; -import static com.hedera.services.bdd.spec.utilops.UtilVerbs.withOpContext; -import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.INSUFFICIENT_PAYER_BALANCE; -import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.INVALID_TRANSACTION_ID; -import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.NOT_SUPPORTED; -import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.RECEIPT_NOT_FOUND; -import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.SUCCESS; -import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.UNKNOWN; - -import com.hedera.services.bdd.junit.HapiTest; -import com.hedera.services.bdd.junit.HapiTestSuite; -import com.hedera.services.bdd.spec.HapiSpec; -import com.hedera.services.bdd.spec.queries.meta.HapiGetReceipt; -import com.hedera.services.bdd.spec.utilops.CustomSpecAssert; -import com.hedera.services.bdd.suites.HapiSuite; -import java.util.List; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; -import org.junit.jupiter.api.Tag; - -@HapiTestSuite -@Tag(CRYPTO) -public class TxnReceiptRegression extends HapiSuite { - static final Logger log = LogManager.getLogger(TxnReceiptRegression.class); - - public static void main(final String... args) { - new TxnReceiptRegression().runSuiteSync(); - } - - @Override - protected Logger getResultsLogger() { - return log; - } - - @Override - public List getSpecsInSuite() { - return List.of(new HapiSpec[] { - returnsInvalidForUnspecifiedTxnId(), - returnsNotSupportedForMissingOp(), - receiptAvailableWithinCacheTtl(), - receiptUnavailableAfterCacheTtl(), - receiptUnavailableIfRejectedInPrecheck(), - receiptNotFoundOnUnknownTransactionID(), - receiptUnknownBeforeConsensus(), - }); - } - - @HapiTest - final HapiSpec returnsInvalidForUnspecifiedTxnId() { - return defaultHapiSpec("ReturnsInvalidForUnspecifiedTxnId") - .given() - .when() - .then(getReceipt("").useDefaultTxnId().hasAnswerOnlyPrecheck(INVALID_TRANSACTION_ID)); - } - - @HapiTest - final HapiSpec returnsNotSupportedForMissingOp() { - return defaultHapiSpec("ReturnsNotSupportedForMissingOp") - .given(cryptoCreate("misc").via("success").balance(1_000L)) - .when() - .then(getReceipt("success").forgetOp().hasAnswerOnlyPrecheck(NOT_SUPPORTED)); - } - - @HapiTest - final HapiSpec receiptUnavailableAfterCacheTtl() { - return defaultHapiSpec("ReceiptUnavailableAfterCacheTtl") - .given() - .when() - .then( - // This extra three minutes isn't worth adding to mono-service checks, but - // especially as it fails now against mod-service, is worthwhile as HapiTest - ifHapiTest( - cryptoCreate("misc").via("success").balance(1_000L), - sleepFor(181_000L), - // Run a transaction to give receipt expiration a chance to occur - cryptoTransfer(tinyBarsFromTo(GENESIS, FUNDING, 1L)), - getReceipt("success").hasAnswerOnlyPrecheck(RECEIPT_NOT_FOUND))); - } - - @HapiTest - final HapiSpec receiptUnknownBeforeConsensus() { - return defaultHapiSpec("ReceiptUnknownBeforeConsensus") - .given() - .when() - .then( - cryptoCreate("misc").via("success").balance(1_000L).deferStatusResolution(), - getReceipt("success").hasPriorityStatus(UNKNOWN)); - } - - @HapiTest - final HapiSpec receiptAvailableWithinCacheTtl() { - return defaultHapiSpec("ReceiptAvailableWithinCacheTtl") - .given(cryptoCreate("misc").via("success").balance(1_000L)) - .when() - .then(getReceipt("success").hasPriorityStatus(SUCCESS)); - } - - @HapiTest - final HapiSpec receiptUnavailableIfRejectedInPrecheck() { - return defaultHapiSpec("ReceiptUnavailableIfRejectedInPrecheck") - .given(cryptoCreate("misc").balance(1_000L)) - .when(cryptoCreate("nope") - .payingWith("misc") - .hasPrecheck(INSUFFICIENT_PAYER_BALANCE) - .via("failingTxn")) - .then(getReceipt("failingTxn").hasAnswerOnlyPrecheck(RECEIPT_NOT_FOUND)); - } - - @HapiTest - final HapiSpec receiptNotFoundOnUnknownTransactionID() { - return defaultHapiSpec("receiptNotFoundOnUnknownTransactionID") - .given() - .when() - .then(withOpContext((spec, ctxLog) -> { - final HapiGetReceipt op = - getReceipt(spec.txns().defaultTransactionID()).hasAnswerOnlyPrecheck(RECEIPT_NOT_FOUND); - CustomSpecAssert.allRunFor(spec, op); - })); - } -} diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/crypto/TxnRecordRegression.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/crypto/TxnRecordRegression.java index 6277e5cdb217..1e11bec614fc 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/crypto/TxnRecordRegression.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/crypto/TxnRecordRegression.java @@ -18,23 +18,33 @@ import static com.hedera.services.bdd.junit.TestTags.CRYPTO; import static com.hedera.services.bdd.spec.HapiSpec.defaultHapiSpec; +import static com.hedera.services.bdd.spec.HapiSpec.hapiTest; +import static com.hedera.services.bdd.spec.queries.QueryVerbs.getReceipt; import static com.hedera.services.bdd.spec.queries.QueryVerbs.getTxnRecord; import static com.hedera.services.bdd.spec.transactions.TxnVerbs.cryptoCreate; import static com.hedera.services.bdd.spec.transactions.TxnVerbs.cryptoDelete; import static com.hedera.services.bdd.spec.transactions.TxnVerbs.cryptoTransfer; import static com.hedera.services.bdd.spec.transactions.crypto.HapiCryptoTransfer.tinyBarsFromTo; +import static com.hedera.services.bdd.spec.utilops.UtilVerbs.ifHapiTest; +import static com.hedera.services.bdd.spec.utilops.UtilVerbs.sleepFor; import static com.hedera.services.bdd.spec.utilops.UtilVerbs.usableTxnIdNamed; +import static com.hedera.services.bdd.spec.utilops.UtilVerbs.withOpContext; +import static com.hedera.services.bdd.suites.HapiSuite.FUNDING; +import static com.hedera.services.bdd.suites.HapiSuite.GENESIS; import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.INSUFFICIENT_PAYER_BALANCE; import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.INVALID_ACCOUNT_ID; +import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.INVALID_TRANSACTION_ID; +import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.NOT_SUPPORTED; +import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.RECEIPT_NOT_FOUND; import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.RECORD_NOT_FOUND; +import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.SUCCESS; +import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.UNKNOWN; import com.hedera.services.bdd.junit.HapiTest; -import com.hedera.services.bdd.junit.HapiTestSuite; -import com.hedera.services.bdd.spec.HapiSpec; -import com.hedera.services.bdd.suites.HapiSuite; -import java.util.List; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; +import com.hedera.services.bdd.spec.queries.meta.HapiGetReceipt; +import com.hedera.services.bdd.spec.utilops.CustomSpecAssert; +import java.util.stream.Stream; +import org.junit.jupiter.api.DynamicTest; import org.junit.jupiter.api.Tag; /** @@ -42,34 +52,10 @@ * *

    Even with a 3s TTL, a number of these tests fail. FUTURE: revisit * */ -@HapiTestSuite @Tag(CRYPTO) -public class TxnRecordRegression extends HapiSuite { - static final Logger log = LogManager.getLogger(TxnRecordRegression.class); - - public static void main(final String... args) { - new TxnRecordRegression().runSuiteSync(); - } - - @Override - protected Logger getResultsLogger() { - return log; - } - - @Override - public List getSpecsInSuite() { - return List.of(new HapiSpec[] { - returnsInvalidForUnspecifiedTxnId(), - recordNotFoundIfNotInPayerState(), - recordUnavailableIfRejectedInPrecheck(), - recordUnavailableBeforeConsensus(), - recordsStillQueryableWithDeletedPayerId(), - }); - } - - // FUTURE: revisit this test, which isn't passing in modular or mono code (even with a 3 second TTL) +public class TxnRecordRegression { @HapiTest - final HapiSpec recordsStillQueryableWithDeletedPayerId() { + final Stream recordsStillQueryableWithDeletedPayerId() { return defaultHapiSpec("DeletedAccountRecordsUnavailableAfterTtl") .given( cryptoCreate("toBeDeletedPayer"), @@ -81,7 +67,7 @@ final HapiSpec recordsStillQueryableWithDeletedPayerId() { } @HapiTest - final HapiSpec returnsInvalidForUnspecifiedTxnId() { + final Stream returnsInvalidForUnspecifiedTxnId() { return defaultHapiSpec("ReturnsInvalidForUnspecifiedTxnId") .given() .when() @@ -89,7 +75,7 @@ final HapiSpec returnsInvalidForUnspecifiedTxnId() { } @HapiTest - final HapiSpec recordNotFoundIfNotInPayerState() { + final Stream recordNotFoundIfNotInPayerState() { return defaultHapiSpec("RecordNotFoundIfNotInPayerState") .given( cryptoCreate("misc").via("success"), @@ -99,7 +85,7 @@ final HapiSpec recordNotFoundIfNotInPayerState() { } @HapiTest - final HapiSpec recordUnavailableBeforeConsensus() { + final Stream recordUnavailableBeforeConsensus() { return defaultHapiSpec("RecordUnavailableBeforeConsensus") .given() .when() @@ -109,7 +95,7 @@ final HapiSpec recordUnavailableBeforeConsensus() { } @HapiTest - final HapiSpec recordUnavailableIfRejectedInPrecheck() { + final Stream recordUnavailableIfRejectedInPrecheck() { return defaultHapiSpec("RecordUnavailableIfRejectedInPrecheck") .given( cryptoCreate("misc").balance(1000L), @@ -120,4 +106,74 @@ final HapiSpec recordUnavailableIfRejectedInPrecheck() { .txnId("failingTxn")) .then(getTxnRecord("failingTxn").hasAnswerOnlyPrecheck(RECORD_NOT_FOUND)); } + + @HapiTest + final Stream getReceiptReturnsInvalidForUnspecifiedTxnId() { + return hapiTest(getReceipt("").useDefaultTxnId().hasAnswerOnlyPrecheck(INVALID_TRANSACTION_ID)); + } + + @HapiTest + final Stream returnsNotSupportedForMissingOp() { + return defaultHapiSpec("ReturnsNotSupportedForMissingOp") + .given(cryptoCreate("misc").via("success").balance(1_000L)) + .when() + .then(getReceipt("success").forgetOp().hasAnswerOnlyPrecheck(NOT_SUPPORTED)); + } + + // (FUTURE) Re-enable once we have a way to manipulate time in the test network + final Stream receiptUnavailableAfterCacheTtl() { + return defaultHapiSpec("ReceiptUnavailableAfterCacheTtl") + .given() + .when() + .then( + // This extra three minutes isn't worth adding to mono-service checks, but + // especially as it fails now against mod-service, is worthwhile as HapiTest + ifHapiTest( + cryptoCreate("misc").via("success").balance(1_000L), + sleepFor(181_000L), + // Run a transaction to give receipt expiration a chance to occur + cryptoTransfer(tinyBarsFromTo(GENESIS, FUNDING, 1L)), + getReceipt("success").hasAnswerOnlyPrecheck(RECEIPT_NOT_FOUND))); + } + + @HapiTest + final Stream receiptUnknownBeforeConsensus() { + return defaultHapiSpec("ReceiptUnknownBeforeConsensus") + .given() + .when() + .then( + cryptoCreate("misc").via("success").balance(1_000L).deferStatusResolution(), + getReceipt("success").hasPriorityStatus(UNKNOWN)); + } + + @HapiTest + final Stream receiptAvailableWithinCacheTtl() { + return defaultHapiSpec("ReceiptAvailableWithinCacheTtl") + .given(cryptoCreate("misc").via("success").balance(1_000L)) + .when() + .then(getReceipt("success").hasPriorityStatus(SUCCESS)); + } + + @HapiTest + final Stream receiptUnavailableIfRejectedInPrecheck() { + return defaultHapiSpec("ReceiptUnavailableIfRejectedInPrecheck") + .given(cryptoCreate("misc").balance(1_000L)) + .when(cryptoCreate("nope") + .payingWith("misc") + .hasPrecheck(INSUFFICIENT_PAYER_BALANCE) + .via("failingTxn")) + .then(getReceipt("failingTxn").hasAnswerOnlyPrecheck(RECEIPT_NOT_FOUND)); + } + + @HapiTest + final Stream receiptNotFoundOnUnknownTransactionID() { + return defaultHapiSpec("receiptNotFoundOnUnknownTransactionID") + .given() + .when() + .then(withOpContext((spec, ctxLog) -> { + final HapiGetReceipt op = + getReceipt(spec.txns().defaultTransactionID()).hasAnswerOnlyPrecheck(RECEIPT_NOT_FOUND); + CustomSpecAssert.allRunFor(spec, op); + })); + } } diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/crypto/UnsupportedQueriesRegression.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/crypto/UnsupportedQueriesRegression.java deleted file mode 100644 index 5e6b6a12d1ad..000000000000 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/crypto/UnsupportedQueriesRegression.java +++ /dev/null @@ -1,71 +0,0 @@ -/* - * Copyright (C) 2020-2024 Hedera Hashgraph, LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.hedera.services.bdd.suites.crypto; - -import static com.hedera.services.bdd.junit.TestTags.CRYPTO; -import static com.hedera.services.bdd.spec.HapiSpec.defaultHapiSpec; -import static com.hedera.services.bdd.spec.utilops.UtilVerbs.getAccountNftInfosNotSupported; -import static com.hedera.services.bdd.spec.utilops.UtilVerbs.getBySolidityIdNotSupported; -import static com.hedera.services.bdd.spec.utilops.UtilVerbs.getClaimNotSupported; -import static com.hedera.services.bdd.spec.utilops.UtilVerbs.getFastRecordNotSupported; -import static com.hedera.services.bdd.spec.utilops.UtilVerbs.getStakersNotSupported; -import static com.hedera.services.bdd.spec.utilops.UtilVerbs.getTokenNftInfosNotSupported; - -import com.hedera.services.bdd.junit.HapiTest; -import com.hedera.services.bdd.junit.HapiTestSuite; -import com.hedera.services.bdd.spec.HapiSpec; -import com.hedera.services.bdd.suites.HapiSuite; -import java.util.List; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; -import org.junit.jupiter.api.Tag; - -@HapiTestSuite -@Tag(CRYPTO) -public class UnsupportedQueriesRegression extends HapiSuite { - static final Logger log = LogManager.getLogger(UnsupportedQueriesRegression.class); - - public static void main(String... args) { - new UnsupportedQueriesRegression().runSuiteSync(); - } - - @Override - protected Logger getResultsLogger() { - return log; - } - - @Override - public List getSpecsInSuite() { - return List.of(new HapiSpec[] { - verifyUnsupportedOps(), - }); - } - - @HapiTest - final HapiSpec verifyUnsupportedOps() { - return defaultHapiSpec("VerifyUnsupportedOps") - .given() - .when() - .then( - getClaimNotSupported(), - getStakersNotSupported(), - getFastRecordNotSupported(), - getBySolidityIdNotSupported(), - getTokenNftInfosNotSupported(), - getAccountNftInfosNotSupported()); - } -} diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/crypto/staking/StakingSuite.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/crypto/staking/StakingSuite.java index e61987e8109e..4bd91b462048 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/crypto/staking/StakingSuite.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/crypto/staking/StakingSuite.java @@ -16,8 +16,10 @@ package com.hedera.services.bdd.suites.crypto.staking; -import static com.hedera.services.bdd.junit.TestTags.TIME_CONSUMING; +import static com.hedera.services.bdd.junit.ContextRequirement.NO_CONCURRENT_STAKE_PERIOD_BOUNDARY_CROSSINGS; +import static com.hedera.services.bdd.junit.TestTags.LONG_RUNNING; import static com.hedera.services.bdd.spec.HapiSpec.defaultHapiSpec; +import static com.hedera.services.bdd.spec.HapiSpec.hapiTest; import static com.hedera.services.bdd.spec.assertions.AccountInfoAsserts.accountWith; import static com.hedera.services.bdd.spec.assertions.ContractInfoAsserts.contractWith; import static com.hedera.services.bdd.spec.assertions.TransactionRecordAsserts.recordWith; @@ -25,7 +27,6 @@ import static com.hedera.services.bdd.spec.queries.QueryVerbs.getAccountInfo; import static com.hedera.services.bdd.spec.queries.QueryVerbs.getContractInfo; import static com.hedera.services.bdd.spec.queries.QueryVerbs.getTxnRecord; -import static com.hedera.services.bdd.spec.transactions.TxnVerbs.contractCall; import static com.hedera.services.bdd.spec.transactions.TxnVerbs.contractCreate; import static com.hedera.services.bdd.spec.transactions.TxnVerbs.contractUpdate; import static com.hedera.services.bdd.spec.transactions.TxnVerbs.cryptoCreate; @@ -38,104 +39,68 @@ import static com.hedera.services.bdd.spec.utilops.UtilVerbs.blockingOrder; import static com.hedera.services.bdd.spec.utilops.UtilVerbs.inParallel; import static com.hedera.services.bdd.spec.utilops.UtilVerbs.overriding; -import static com.hedera.services.bdd.spec.utilops.UtilVerbs.overridingAllOf; +import static com.hedera.services.bdd.spec.utilops.UtilVerbs.overridingThree; import static com.hedera.services.bdd.spec.utilops.UtilVerbs.sleepFor; import static com.hedera.services.bdd.spec.utilops.UtilVerbs.sourcing; import static com.hedera.services.bdd.spec.utilops.UtilVerbs.waitUntilJustBeforeNextStakingPeriod; import static com.hedera.services.bdd.spec.utilops.UtilVerbs.waitUntilStartOfNextStakingPeriod; import static com.hedera.services.bdd.spec.utilops.UtilVerbs.withOpContext; +import static com.hedera.services.bdd.suites.HapiSuite.DEFAULT_PAYER; +import static com.hedera.services.bdd.suites.HapiSuite.FUNDING; +import static com.hedera.services.bdd.suites.HapiSuite.GENESIS; +import static com.hedera.services.bdd.suites.HapiSuite.NODE; +import static com.hedera.services.bdd.suites.HapiSuite.ONE_HBAR; +import static com.hedera.services.bdd.suites.HapiSuite.ONE_HUNDRED_HBARS; +import static com.hedera.services.bdd.suites.HapiSuite.ONE_MILLION_HBARS; +import static com.hedera.services.bdd.suites.HapiSuite.STAKING_REWARD; +import static com.hedera.services.bdd.suites.HapiSuite.TINY_PARTS_PER_WHOLE; import static com.hedera.services.bdd.suites.autorenew.AutoRenewConfigChoices.enableContractAutoRenewWith; import static com.hedera.services.bdd.suites.contract.hapi.ContractCallSuite.PAY_RECEIVABLE_CONTRACT; import static com.hedera.services.bdd.suites.records.ContractRecordsSanityCheckSuite.PAYABLE_CONTRACT; import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.INVALID_STAKING_ID; import com.hedera.services.bdd.junit.HapiTest; -import com.hedera.services.bdd.junit.HapiTestSuite; -import com.hedera.services.bdd.spec.HapiSpec; +import com.hedera.services.bdd.junit.HapiTestLifecycle; +import com.hedera.services.bdd.junit.LeakyHapiTest; +import com.hedera.services.bdd.junit.support.SpecManager; import com.hedera.services.bdd.spec.HapiSpecOperation; -import com.hedera.services.bdd.suites.HapiSuite; import com.hederahashgraph.api.proto.java.AccountAmount; -import java.math.BigInteger; +import edu.umd.cs.findbugs.annotations.NonNull; import java.util.ArrayList; import java.util.List; -import java.util.Map; import java.util.concurrent.atomic.AtomicLong; import java.util.function.IntFunction; import java.util.stream.IntStream; +import java.util.stream.Stream; import org.apache.commons.lang3.tuple.Pair; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; +import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.api.MethodOrderer; -import org.junit.jupiter.api.Order; +import org.junit.jupiter.api.DynamicTest; import org.junit.jupiter.api.Tag; -import org.junit.jupiter.api.TestMethodOrder; -@HapiTestSuite -@TestMethodOrder(MethodOrderer.OrderAnnotation.class) -@Tag(TIME_CONSUMING) -public class StakingSuite extends HapiSuite { - - private static final Logger log = LogManager.getLogger(StakingSuite.class); +@Tag(LONG_RUNNING) +@HapiTestLifecycle +public class StakingSuite { public static final String END_OF_STAKING_PERIOD_CALCULATIONS_MEMO = "End of staking period calculation record"; - private static final long ONE_STAKING_PERIOD = 60_000L; - private static final long BUFFER = 10_000L; - private static final long SOME_REWARD_RATE = 100_000_000_000L; + private static final long SUITE_PER_HBAR_REWARD_RATE = 3_333_333L; private static final String ALICE = "alice"; private static final String BOB = "bob"; private static final String CAROL = "carol"; - private static final long INTER_PERIOD_SLEEP_MS = ONE_STAKING_PERIOD + BUFFER; public static final String STAKING_START_THRESHOLD = "staking.startThreshold"; public static final String REWARD_BALANCE_THRESHOLD = "staking.rewardBalanceThreshold"; public static final String PER_HBAR_REWARD_RATE = "staking.perHbarRewardRate"; public static final String STAKING_REWARD_RATE = "staking.perHbarRewardRate"; public static final String FIRST_TRANSFER = "firstTransfer"; - public static final String FIRST_TXN = "firstTxn"; private static final long STAKING_PERIOD_MINS = 1L; - public static void main(String... args) { - new StakingSuite().runSuiteSync(); - } - - @Override - protected Logger getResultsLogger() { - return log; - } - - @Override - public boolean canRunConcurrent() { - return false; - } - - @Override - public List getSpecsInSuite() { - return List.of( - setUp(), - losingEvenAZeroBalanceStakerTriggersStakeeRewardSituation(), - evenOneTinybarChangeInIndirectStakingAccountTriggersStakeeRewardSituation(), - stakingMetadataUpdateIsRewardOpportunity(), - secondOrderRewardSituationsWork(), - endOfStakingPeriodRecTest(), - rewardsOfDeletedAreRedirectedToBeneficiary(), - canBeRewardedWithoutMinStakeIfSoConfigured(), - zeroRewardEarnedWithZeroWholeHbarsStillSetsSASOLARP(), - autoRenewalsCanTriggerStakingRewards(), - stakeIsManagedCorrectlyInTxnsAroundPeriodBoundaries(), - zeroStakeAccountsHaveMetadataResetOnFirstDayTheyReceiveFunds()); - } - - @HapiTest - @Order(1) @BeforeAll - final HapiSpec setUp() { - return defaultHapiSpec("setUp") - .given( - overriding(STAKING_START_THRESHOLD, "" + 10 * ONE_HBAR), - overriding(PER_HBAR_REWARD_RATE, "" + 3_333_333), - overriding(REWARD_BALANCE_THRESHOLD, "" + 0), - cryptoTransfer(tinyBarsFromTo(GENESIS, STAKING_REWARD, ONE_MILLION_HBARS))) - .when() - .then(); + static void beforeAll(@NonNull final SpecManager specManager) throws Throwable { + specManager.setup( + overridingThree( + STAKING_START_THRESHOLD, "" + 10 * ONE_HBAR, + PER_HBAR_REWARD_RATE, "" + SUITE_PER_HBAR_REWARD_RATE, + REWARD_BALANCE_THRESHOLD, "" + 0), + cryptoTransfer(tinyBarsFromTo(GENESIS, STAKING_REWARD, ONE_MILLION_HBARS))); } /** @@ -144,8 +109,7 @@ final HapiSpec setUp() { * received the expected rewards (all zero). */ @HapiTest - @Order(12) - final HapiSpec zeroStakeAccountsHaveMetadataResetOnFirstDayTheyReceiveFunds() { + final Stream zeroStakeAccountsHaveMetadataResetOnFirstDayTheyReceiveFunds() { final var zeroStakeAccount = "zeroStakeAccount"; final var numZeroStakeAccounts = 10; final var stakePeriodMins = 1L; @@ -160,10 +124,9 @@ final HapiSpec zeroStakeAccountsHaveMetadataResetOnFirstDayTheyReceiveFunds() { cryptoCreate("somebody").stakedNodeId(0).balance(10 * ONE_MILLION_HBARS), // Wait a few periods waitUntilStartOfNextStakingPeriod(stakePeriodMins), + cryptoTransfer(tinyBarsFromTo(GENESIS, FUNDING, 1L)), waitUntilStartOfNextStakingPeriod(stakePeriodMins), - waitUntilStartOfNextStakingPeriod(stakePeriodMins), - waitUntilStartOfNextStakingPeriod(stakePeriodMins), - waitUntilStartOfNextStakingPeriod(stakePeriodMins)) + cryptoTransfer(tinyBarsFromTo(GENESIS, FUNDING, 1L))) .when() .then(sleepFor(5_000), withOpContext((spec, opLog) -> { for (int i = 0; i < numZeroStakeAccounts; i++) { @@ -182,9 +145,9 @@ final HapiSpec zeroStakeAccountsHaveMetadataResetOnFirstDayTheyReceiveFunds() { first, second, third, - getTxnRecord(setupTxn).logged(), - getTxnRecord(fundingTxn).logged(), - getTxnRecord(withdrawingTxn).logged()); + getTxnRecord(setupTxn).hasPaidStakingRewardsCount(0), + getTxnRecord(fundingTxn).hasPaidStakingRewardsCount(0), + getTxnRecord(withdrawingTxn).hasPaidStakingRewardsCount(0)); } })); } @@ -194,8 +157,7 @@ final HapiSpec zeroStakeAccountsHaveMetadataResetOnFirstDayTheyReceiveFunds() { * end of a staking period, only to receive it back shortly after that period starts. */ @HapiTest - @Order(11) - final HapiSpec stakeIsManagedCorrectlyInTxnsAroundPeriodBoundaries() { + final Stream stakeIsManagedCorrectlyInTxnsAroundPeriodBoundaries() { final var alice = "alice"; final var baldwin = "baldwin"; final var stakePeriodMins = 1L; @@ -203,7 +165,7 @@ final HapiSpec stakeIsManagedCorrectlyInTxnsAroundPeriodBoundaries() { final AtomicLong currentBaldwinBalance = new AtomicLong(); final List> rewardsPaid = new ArrayList<>(); - final int numPeriodsToRepeat = 5; + final int numPeriodsToRepeat = 3; final long secsBeforePeriodEndToDoTransfer = 5; final IntFunction returnToAliceTxns = n -> "returnToAlice" + n; final IntFunction sendToBobTxns = n -> "sendToBob" + n; @@ -239,11 +201,21 @@ final HapiSpec stakeIsManagedCorrectlyInTxnsAroundPeriodBoundaries() { final var registry = spec.registry(); final var aliceNum = registry.getAccountID(alice).getAccountNum(); final var baldwinNum = registry.getAccountID(baldwin).getAccountNum(); + final var expectedRewardsPaid = List.of( + List.of(0L, 0L), + // Alice held the balance at the start of the first rewardable period + List.of(3333333000000L, 0L), + List.of(0L, 0L), + // But Baldwin held the balance at the start of the second rewardable period + List.of(0L, 3333333000000L), + List.of(0L, 0L), + // And also the third rewardable period + List.of(0L, 3444442988889L)); for (int i = 0; i < rewardsPaid.size(); i++) { if (i % 2 == 0) { - opLog.info("======= Send-to-Baldwin #{} =======", i / 2); + opLog.info("======= Send-to-Baldwin #{} =======", i / 2 + 1); } else { - opLog.info("======= Return-to-Alice #{} =======", i / 2); + opLog.info("======= Return-to-Alice #{} =======", i / 2 + 1); } final var paidThisTime = rewardsPaid.get(i); var aliceReward = 0L; @@ -259,89 +231,16 @@ final HapiSpec stakeIsManagedCorrectlyInTxnsAroundPeriodBoundaries() { opLog.info("= Alice : {}", aliceReward); opLog.info("= Baldwin : {}", baldwinReward); opLog.info("==============================\n"); + Assertions.assertEquals(expectedRewardsPaid.get(i), List.of(aliceReward, baldwinReward)); } })); } - /** - * Creates a contract staked to a node with a lifetime just over one staking period; waits long - * enough for it to be eligible for rewards, and then triggers its auto-renewal. - * - *

    Since system records aren't queryable via HAPI, it's necessary to add logging in e.g. - * ExpiryRecordsHelper#finalizeAndStream() to inspect the generated record and confirm staking - * rewards are paid. - * - * @return the spec described above - */ @HapiTest - @Order(10) - final HapiSpec autoRenewalsCanTriggerStakingRewards() { - final var initBalance = ONE_HBAR * 1000; - final var minimalLifetime = 3; - final var creation = "creation"; - - return defaultHapiSpec("AutoRenewalsCanTriggerStakingRewards") - .given( - cryptoCreate("miscStaker").stakedNodeId(0).balance(ONE_HUNDRED_HBARS * 1000), - uploadInitCode(PAY_RECEIVABLE_CONTRACT), - waitUntilStartOfNextStakingPeriod(STAKING_PERIOD_MINS)) - .when( - enableContractAutoRenewWith(minimalLifetime, 0), - contractCreate(PAY_RECEIVABLE_CONTRACT) - .gas(2_000_000) - .entityMemo("") - .stakedNodeId(0L) - // Lifetime is in seconds not milliseconds - .autoRenewSecs((INTER_PERIOD_SLEEP_MS + BUFFER) / 1000) - .balance(initBalance) - .via(creation), - waitUntilStartOfNextStakingPeriod(STAKING_PERIOD_MINS), - cryptoTransfer(tinyBarsFromTo(GENESIS, NODE, 1L)), - waitUntilStartOfNextStakingPeriod(STAKING_PERIOD_MINS)) - .then( - cryptoTransfer(tinyBarsFromTo(GENESIS, NODE, 1L)).via("triggerRenewal"), - getTxnRecord("triggerRenewal") - .andAllChildRecords() - .countStakingRecords() - .logged()); - } - - @HapiTest - @Order(8) - final HapiSpec canBeRewardedWithoutMinStakeIfSoConfigured() { - final var patientlyWaiting = "patientlyWaiting"; - - return defaultHapiSpec("CanBeRewardedWithoutMinStakeIfSoConfigured") - .given( - overridingAllOf(Map.of( - "staking.nodeMaxToMinStakeRatios", - "0:2,1:4", - "staking.requireMinStakeToReward", - "true", - STAKING_START_THRESHOLD, - "100_000_000")), - // Create the patiently waiting staker - cryptoCreate(patientlyWaiting).stakedNodeId(0).balance(ONE_HUNDRED_HBARS)) - .when( - waitUntilStartOfNextStakingPeriod(STAKING_PERIOD_MINS), - cryptoTransfer(tinyBarsFromTo(GENESIS, FUNDING, 1L)), - cryptoTransfer(tinyBarsFromTo(patientlyWaiting, FUNDING, 1)), - getAccountBalance(patientlyWaiting).logged(), - // Now we should be rewardable even though node0 is far from minStake - overriding("staking.requireMinStakeToReward", "false"), - waitUntilStartOfNextStakingPeriod(STAKING_PERIOD_MINS), - cryptoTransfer(tinyBarsFromTo(GENESIS, FUNDING, 1L)), - cryptoTransfer(tinyBarsFromTo(patientlyWaiting, FUNDING, 1))) - .then(getAccountBalance(patientlyWaiting).logged()); - } - // HERE - @HapiTest - @Order(5) - final HapiSpec secondOrderRewardSituationsWork() { - final long totalStakeStartCase1 = 3 * ONE_HUNDRED_HBARS; - final long rewardSumHistoryCase1 = SOME_REWARD_RATE / (totalStakeStartCase1 / TINY_PARTS_PER_WHOLE) / 100; - final long alicePendingRewardsCase1 = rewardSumHistoryCase1 * (2 * ONE_HUNDRED_HBARS / TINY_PARTS_PER_WHOLE); - final long bobPendingRewardsCase1 = rewardSumHistoryCase1 * (ONE_HUNDRED_HBARS / TINY_PARTS_PER_WHOLE); + final Stream secondOrderRewardSituationsWork() { + final long alicePendingRewardsCase1 = + SUITE_PER_HBAR_REWARD_RATE * (2 * ONE_HUNDRED_HBARS / TINY_PARTS_PER_WHOLE); + final long bobPendingRewardsCase1 = SUITE_PER_HBAR_REWARD_RATE * (ONE_HUNDRED_HBARS / TINY_PARTS_PER_WHOLE); return defaultHapiSpec("SecondOrderRewardSituationsWork") .given() @@ -353,13 +252,7 @@ final HapiSpec secondOrderRewardSituationsWork() { .then( /* --- period 2 - paid_rewards 0 for first period --- */ cryptoTransfer(tinyBarsFromTo(BOB, ALICE, ONE_HBAR)).via(FIRST_TRANSFER), - getTxnRecord(FIRST_TRANSFER) - .andAllChildRecords() - .countStakingRecords() - .stakingFeeExempted() - .hasChildRecordCount(1) - .hasChildRecords(recordWith().memo(END_OF_STAKING_PERIOD_CALCULATIONS_MEMO)) - .hasPaidStakingRewards(List.of()), + getTxnRecord(FIRST_TRANSFER).hasPaidStakingRewards(List.of()), /* --- second period reward eligible --- */ waitUntilStartOfNextStakingPeriod(STAKING_PERIOD_MINS), @@ -370,11 +263,6 @@ final HapiSpec secondOrderRewardSituationsWork() { getAccountInfo(BOB) .has(accountWith().stakedNodeId(0L).pendingRewards(333333300L)) .logged(), - getTxnRecord("endOfStakingPeriodXfer") - .andAllChildRecords() - .hasChildRecordCount(1) - .countStakingRecords() - .hasChildRecords(recordWith().memo(END_OF_STAKING_PERIOD_CALCULATIONS_MEMO)), getAccountInfo(ALICE) .has(accountWith().stakedNodeId(0L).pendingRewards(alicePendingRewardsCase1)) .logged(), @@ -383,9 +271,6 @@ final HapiSpec secondOrderRewardSituationsWork() { .logged(), cryptoUpdate(CAROL).newStakedAccountId(BOB).via("secondOrderRewardSituation"), getTxnRecord("secondOrderRewardSituation") - .andAllChildRecords() - .countStakingRecords() - .hasStakingFeesPaid() .hasPaidStakingRewards(List.of( Pair.of(ALICE, alicePendingRewardsCase1), Pair.of(BOB, bobPendingRewardsCase1))) .logged(), @@ -393,24 +278,13 @@ final HapiSpec secondOrderRewardSituationsWork() { cryptoTransfer(tinyBarsFromTo(BOB, ALICE, ONE_HBAR)) .payingWith(BOB) .via("expectNoReward"), - getTxnRecord("expectNoReward") - .andAllChildRecords() - .countStakingRecords() - .hasChildRecordCount(0) - .hasStakingFeesPaid() - // .hasPaidStakingRewards(List.of()) - .logged()); + getTxnRecord("expectNoReward").hasStakingFeesPaid().logged()); } @HapiTest - @Order(13) - final HapiSpec pendingRewardsPaidBeforeStakedToMeUpdates() { + final Stream pendingRewardsPaidBeforeStakedToMeUpdates() { return defaultHapiSpec("PendingRewardsPaidBeforeStakedToMeUpdates") - .given( - overriding(STAKING_START_THRESHOLD, "" + 10 * ONE_HBAR), - overriding(PER_HBAR_REWARD_RATE, "" + ONE_HBAR), - overriding(REWARD_BALANCE_THRESHOLD, "" + 0), - cryptoTransfer(tinyBarsFromTo(GENESIS, STAKING_REWARD, ONE_MILLION_HBARS))) + .given() .when( // period 1 cryptoCreate(ALICE).stakedNodeId(0).balance(ONE_HUNDRED_HBARS), cryptoCreate(CAROL).stakedNodeId(0).balance(ONE_HUNDRED_HBARS), @@ -419,50 +293,38 @@ final HapiSpec pendingRewardsPaidBeforeStakedToMeUpdates() { /* --- period 2 - paid_rewards 0 for first period --- */ cryptoTransfer(tinyBarsFromTo(GENESIS, FUNDING, ONE_HBAR)) .via(FIRST_TRANSFER), - getTxnRecord(FIRST_TRANSFER) - .andAllChildRecords() - .countStakingRecords() - .stakingFeeExempted() - .hasChildRecordCount(1) - .hasChildRecords(recordWith().memo(END_OF_STAKING_PERIOD_CALCULATIONS_MEMO)), // alice - 100, carol - 100 /* --- third period reward eligible from period 2--- */ waitUntilStartOfNextStakingPeriod(STAKING_PERIOD_MINS), cryptoUpdate(CAROL).newStakedAccountId(ALICE).via("stakedIdUpdate"), getTxnRecord("stakedIdUpdate") - .andAllChildRecords() - .hasChildRecordCount(1) - .countStakingRecords() - .hasChildRecords(recordWith().memo(END_OF_STAKING_PERIOD_CALCULATIONS_MEMO)) - .hasPaidStakingRewardsCount(2) - .hasPaidStakingRewards( - List.of(Pair.of(ALICE, 100 * ONE_HBAR), Pair.of(CAROL, 100 * ONE_HBAR))), - // alice - 200, stakedToMe - 200 - // carol - 200 + .hasPaidStakingRewards(List.of( + Pair.of(ALICE, 100 * SUITE_PER_HBAR_REWARD_RATE), + Pair.of(CAROL, 100 * SUITE_PER_HBAR_REWARD_RATE))), /* fourth period */ waitUntilStartOfNextStakingPeriod(STAKING_PERIOD_MINS), - cryptoTransfer(tinyBarsFromTo(GENESIS, FUNDING, ONE_HBAR)) - .via("fourthPeriod"), - getTxnRecord("fourthPeriod") - .andAllChildRecords() - .countStakingRecords() - .logged(), + cryptoTransfer(tinyBarsFromTo(GENESIS, FUNDING, ONE_HBAR)), cryptoTransfer(tinyBarsFromTo(GENESIS, ALICE, ONE_HBAR)).via("aliceFirstXfer"), getTxnRecord("aliceFirstXfer") - .hasPaidStakingRewards(List.of(Pair.of(ALICE, 100 * ONE_HBAR))) + // The period we are collecting in is the first that Alice will have the + // full benefits of the paid staking reward and Carol's stake; for now + // her reward is still based on the 100 HBAR she staked herself + .hasPaidStakingRewards(List.of(Pair.of(ALICE, 100 * SUITE_PER_HBAR_REWARD_RATE))) .logged(), waitUntilStartOfNextStakingPeriod(STAKING_PERIOD_MINS), /* fifth period */ cryptoTransfer(tinyBarsFromTo(GENESIS, ALICE, ONE_HBAR)).via("aliceSecondXfer"), getTxnRecord("aliceSecondXfer") - .hasPaidStakingRewards(List.of(Pair.of(ALICE, 400 * ONE_HBAR))) + .hasPaidStakingRewards(List.of(Pair.of( + ALICE, + (200 + 2 * (100 * SUITE_PER_HBAR_REWARD_RATE / TINY_PARTS_PER_WHOLE)) + * SUITE_PER_HBAR_REWARD_RATE))) .logged()); } @HapiTest - @Order(3) - final HapiSpec evenOneTinybarChangeInIndirectStakingAccountTriggersStakeeRewardSituation() { + final Stream evenOneTinybarChangeInIndirectStakingAccountTriggersStakeeRewardSituation() { return defaultHapiSpec("EvenOneTinybarChangeInIndirectStakingAccountTriggersStakeeRewardSituation") .given() .when( @@ -474,15 +336,11 @@ final HapiSpec evenOneTinybarChangeInIndirectStakingAccountTriggersStakeeRewardS waitUntilStartOfNextStakingPeriod(STAKING_PERIOD_MINS)) .then( cryptoTransfer(tinyBarsFromTo(DEFAULT_PAYER, CAROL, 1)).via(FIRST_TRANSFER), - getTxnRecord(FIRST_TRANSFER) - .andAllChildRecords() - .countStakingRecords() - .hasPaidStakingRewardsCount(1)); + getTxnRecord(FIRST_TRANSFER).hasPaidStakingRewardsCount(1)); } @HapiTest - @Order(9) - final HapiSpec zeroRewardEarnedWithZeroWholeHbarsStillSetsSASOLARP() { + final Stream zeroRewardEarnedWithZeroWholeHbarsStillSetsSASOLARP() { return defaultHapiSpec("ZeroRewardEarnedWithZeroWholeHbarsStillSetsSASOLARP") .given( cryptoCreate("helpfulStaker").stakedNodeId(0).balance(ONE_MILLION_HBARS), @@ -496,12 +354,11 @@ final HapiSpec zeroRewardEarnedWithZeroWholeHbarsStillSetsSASOLARP() { waitUntilStartOfNextStakingPeriod(STAKING_PERIOD_MINS)) .then( cryptoTransfer(tinyBarsFromTo(DEFAULT_PAYER, ALICE, 1)).via(FIRST_TRANSFER), - getTxnRecord(FIRST_TRANSFER).countStakingRecords().hasPaidStakingRewardsCount(1)); + getTxnRecord(FIRST_TRANSFER).hasPaidStakingRewardsCount(1)); } @HapiTest - @Order(2) - final HapiSpec losingEvenAZeroBalanceStakerTriggersStakeeRewardSituation() { + final Stream losingEvenAZeroBalanceStakerTriggersStakeeRewardSituation() { return defaultHapiSpec("LosingEvenAZeroBalanceStakerTriggersStakeeRewardSituation") .given() .when( @@ -513,35 +370,22 @@ final HapiSpec losingEvenAZeroBalanceStakerTriggersStakeeRewardSituation() { waitUntilStartOfNextStakingPeriod(STAKING_PERIOD_MINS)) .then( cryptoUpdate(BOB).newStakedNodeId(0L).via(FIRST_TRANSFER), - getTxnRecord(FIRST_TRANSFER) - .andAllChildRecords() - .countStakingRecords() - .hasPaidStakingRewardsCount(1) - .logged()); + getTxnRecord(FIRST_TRANSFER).hasPaidStakingRewardsCount(1)); } @HapiTest - @Order(4) - final HapiSpec stakingMetadataUpdateIsRewardOpportunity() { + final Stream stakingMetadataUpdateIsRewardOpportunity() { return defaultHapiSpec("stakingMetadataUpdateIsRewardOpportunity") .given() .when( cryptoCreate(ALICE).stakedNodeId(0).balance(ONE_HUNDRED_HBARS), - cryptoCreate(BOB).balance(ONE_HUNDRED_HBARS), uploadInitCode(PAYABLE_CONTRACT), contractCreate(PAYABLE_CONTRACT).stakedNodeId(0L).balance(ONE_HUNDRED_HBARS), waitUntilStartOfNextStakingPeriod(STAKING_PERIOD_MINS)) .then( - /* --- Since rewardsSunHistory will be 0 for the first staking period, paid_rewards will be 0 --- */ - cryptoTransfer(tinyBarsFromTo(BOB, ALICE, ONE_HBAR)).via(FIRST_TXN), - getTxnRecord(FIRST_TXN) - .andAllChildRecords() - .countStakingRecords() - .hasChildRecordCount(1) - .hasChildRecords(recordWith().memo(END_OF_STAKING_PERIOD_CALCULATIONS_MEMO)) - .hasPaidStakingRewards(List.of()), + cryptoTransfer(tinyBarsFromTo(GENESIS, FUNDING, 1L)), - /* should receive reward */ + /* Now rewards are eligible */ waitUntilStartOfNextStakingPeriod(STAKING_PERIOD_MINS), cryptoTransfer(tinyBarsFromTo(GENESIS, FUNDING, 1L)), // info queries return rewards @@ -551,81 +395,36 @@ final HapiSpec stakingMetadataUpdateIsRewardOpportunity() { getTxnRecord("acceptsReward") .logged() .andAllChildRecords() - .countStakingRecords() - // .hasChildRecordCount(1) - // - // .hasChildRecords(recordWith().memo(END_OF_STAKING_PERIOD_CALCULATIONS_MEMO)) .hasPaidStakingRewards(List.of(Pair.of(PAYABLE_CONTRACT, 333333300L))), - - // same period should not trigger reward again contractUpdate(PAYABLE_CONTRACT).newStakedNodeId(111L).hasPrecheck(INVALID_STAKING_ID), - contractUpdate(PAYABLE_CONTRACT).newStakedAccountId(BOB).via("samePeriodTxn"), + // Same period should not trigger reward for the contract again, only for Alice + // whose stakedToMe has now changed + contractUpdate(PAYABLE_CONTRACT) + .newStakedAccountId(ALICE) + .via("samePeriodTxn"), getTxnRecord("samePeriodTxn") - .andAllChildRecords() - .countStakingRecords() - .hasChildRecordCount(0) - .hasPaidStakingRewards(List.of()), - waitUntilStartOfNextStakingPeriod(STAKING_PERIOD_MINS), - waitUntilStartOfNextStakingPeriod(STAKING_PERIOD_MINS), - - /* -- trigger a deposit and see if pays expected reward */ - contractCall(PAYABLE_CONTRACT, "deposit", BigInteger.valueOf(1_000L)) - .payingWith(BOB) - .sending(1_000L) - .via("contractRewardTxn"), - getTxnRecord("contractRewardTxn") - .andAllChildRecords() - .countStakingRecords() - .hasChildRecordCount(1) - .hasChildRecords(recordWith().memo(END_OF_STAKING_PERIOD_CALCULATIONS_MEMO)) .logged() - // .hasPaidStakingRewards(List.of(Pair.of(PAYABLE_CONTRACT, - // 500000000L))) - ); + .hasPaidStakingRewards(List.of(Pair.of(ALICE, 100 * SUITE_PER_HBAR_REWARD_RATE)))); } - @HapiTest - @Order(6) - final HapiSpec endOfStakingPeriodRecTest() { - return defaultHapiSpec("EndOfStakingPeriodRecTest") - .given( - cryptoCreate("a1").balance(ONE_MILLION_HBARS).stakedNodeId(0), - cryptoCreate("a2").balance(ONE_MILLION_HBARS).stakedNodeId(0), - cryptoTransfer( - tinyBarsFromTo(GENESIS, STAKING_REWARD, ONE_MILLION_HBARS)) // will trigger staking - ) - .when(waitUntilStartOfNextStakingPeriod(STAKING_PERIOD_MINS)) - .then( - cryptoTransfer(tinyBarsFromTo("a1", "a2", ONE_HBAR)).via("trigger"), - getTxnRecord("trigger") - .logged() - .countStakingRecords() - .hasChildRecordCount(1) - .hasChildRecords(recordWith().memo(END_OF_STAKING_PERIOD_CALCULATIONS_MEMO)), - waitUntilStartOfNextStakingPeriod(STAKING_PERIOD_MINS), - cryptoTransfer(tinyBarsFromTo("a1", "a2", ONE_HBAR)).via("transfer"), - getTxnRecord("transfer") - .countStakingRecords() - .hasChildRecordCount(1) - .hasChildRecords(recordWith().memo(END_OF_STAKING_PERIOD_CALCULATIONS_MEMO)) - .logged(), - cryptoTransfer(tinyBarsFromTo("a1", "a2", ONE_HBAR)).via("noEndOfStakingPeriodRecord"), - getTxnRecord("noEndOfStakingPeriodRecord") - .countStakingRecords() - .hasChildRecordCount(0) - .logged(), - waitUntilStartOfNextStakingPeriod(STAKING_PERIOD_MINS), - cryptoTransfer(tinyBarsFromTo("a1", "a2", ONE_HBAR)).via("transfer1"), - getTxnRecord("transfer1") - .countStakingRecords() - .hasChildRecordCount(1) - .hasChildRecords(recordWith().memo(END_OF_STAKING_PERIOD_CALCULATIONS_MEMO)) - .logged()); + @LeakyHapiTest(NO_CONCURRENT_STAKE_PERIOD_BOUNDARY_CROSSINGS) + final Stream endOfStakingPeriodRecTest() { + return hapiTest( + waitUntilStartOfNextStakingPeriod(STAKING_PERIOD_MINS), + cryptoTransfer(tinyBarsFromTo(GENESIS, FUNDING, 1)).via("trigger"), + getTxnRecord("trigger") + .countStakingRecords() + .hasChildRecordCount(1) + .hasChildRecords(recordWith().memo(END_OF_STAKING_PERIOD_CALCULATIONS_MEMO)), + cryptoTransfer(tinyBarsFromTo(GENESIS, FUNDING, ONE_HBAR)).via("noEndOfStakingPeriodRecord"), + getTxnRecord("noEndOfStakingPeriodRecord") + .countStakingRecords() + .hasChildRecordCount(0) + .logged()); } @HapiTest - @Order(7) - final HapiSpec rewardsOfDeletedAreRedirectedToBeneficiary() { + final Stream rewardsOfDeletedAreRedirectedToBeneficiary() { final var bob = "bob"; final var deletion = "deletion"; return defaultHapiSpec("RewardsOfDeletedAreRedirectedToBeneficiary") @@ -638,12 +437,74 @@ final HapiSpec rewardsOfDeletedAreRedirectedToBeneficiary() { waitUntilStartOfNextStakingPeriod(STAKING_PERIOD_MINS)) .then( cryptoDelete(ALICE).transfer(bob).via(deletion), - getTxnRecord(deletion) - .andAllChildRecords() - .countStakingRecords() - .hasChildRecordCount(1) - .hasChildRecords(recordWith().memo(END_OF_STAKING_PERIOD_CALCULATIONS_MEMO)) - .hasPaidStakingRewards(List.of(Pair.of(bob, 3333333000000L))) - .logged()); + getTxnRecord(deletion).hasPaidStakingRewards(List.of(Pair.of(bob, 3333333000000L)))); + } + + /** + * Creates a contract staked to a node with a lifetime just over one staking period; waits long + * enough for it to be eligible for rewards, and then triggers its auto-renewal. + * + *

    Since system records aren't queryable via HAPI, it's necessary to add logging in e.g. + * ExpiryRecordsHelper#finalizeAndStream() to inspect the generated record and confirm staking + * rewards are paid. + * + * @return the spec described above + */ + // (FUTURE) Enable when expiry/auto-renewal is implemented + final Stream autoRenewalsCanTriggerStakingRewards() { + final var initBalance = ONE_HBAR * 1000; + final var minimalLifetime = 3; + final var creation = "creation"; + + return defaultHapiSpec("AutoRenewalsCanTriggerStakingRewards") + .given( + cryptoCreate("miscStaker").stakedNodeId(0).balance(ONE_HUNDRED_HBARS * 1000), + uploadInitCode(PAY_RECEIVABLE_CONTRACT), + waitUntilStartOfNextStakingPeriod(STAKING_PERIOD_MINS)) + .when( + enableContractAutoRenewWith(minimalLifetime, 0), + contractCreate(PAY_RECEIVABLE_CONTRACT) + .gas(2_000_000) + .entityMemo("") + .stakedNodeId(0L) + // Lifetime is in seconds not minutes, add a 10 second buffer + .autoRenewSecs(STAKING_PERIOD_MINS * 60 + 10) + .balance(initBalance) + .via(creation), + waitUntilStartOfNextStakingPeriod(STAKING_PERIOD_MINS), + cryptoTransfer(tinyBarsFromTo(GENESIS, NODE, 1L)), + waitUntilStartOfNextStakingPeriod(STAKING_PERIOD_MINS)) + .then( + cryptoTransfer(tinyBarsFromTo(GENESIS, NODE, 1L)).via("triggerRenewal") + // (TODO) Verify that the contract was auto-renewed and that staking rewards were paid + ); + } + + // (FUTURE) Delete after confirming min stake will always be zero going forward + final Stream canBeRewardedWithoutMinStakeIfSoConfigured() { + final var patientlyWaiting = "patientlyWaiting"; + + return defaultHapiSpec("CanBeRewardedWithoutMinStakeIfSoConfigured") + .given( + overriding("staking.requireMinStakeToReward", "true"), + cryptoCreate(patientlyWaiting).stakedNodeId(0).balance(ONE_HUNDRED_HBARS)) + .when( + waitUntilStartOfNextStakingPeriod(STAKING_PERIOD_MINS), + cryptoTransfer(tinyBarsFromTo(GENESIS, FUNDING, 1L)), + waitUntilStartOfNextStakingPeriod(STAKING_PERIOD_MINS), + cryptoTransfer(tinyBarsFromTo(GENESIS, FUNDING, 1L)), + cryptoTransfer(tinyBarsFromTo(patientlyWaiting, FUNDING, 1)) + .via("lackingMinStake"), + // If node0 was over minStake, we would have been rewarded + getTxnRecord("lackingMinStake") + .hasPaidStakingRewardsCount(0) + .logged(), + waitUntilStartOfNextStakingPeriod(STAKING_PERIOD_MINS), + cryptoTransfer(tinyBarsFromTo(GENESIS, FUNDING, 1L)), + // Now we should be rewardable even though node0 is far from minStake + overriding("staking.requireMinStakeToReward", "false"), + cryptoTransfer(tinyBarsFromTo(patientlyWaiting, FUNDING, 1)) + .via("minStakeIrrelevant")) + .then(getTxnRecord("lackingMinStake").logged().hasPaidStakingRewardsCount(1)); } } diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/crypto/staking/StartStaking.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/crypto/staking/StartStaking.java index 0dd4d93c2770..6018af685dab 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/crypto/staking/StartStaking.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/crypto/staking/StartStaking.java @@ -27,7 +27,6 @@ import static com.hedera.services.bdd.spec.utilops.UtilVerbs.withOpContext; import static com.hedera.services.bdd.suites.crypto.staking.StakingSuite.STAKING_REWARD_RATE; -import com.hedera.services.bdd.spec.HapiSpec; import com.hedera.services.bdd.spec.HapiSpecOperation; import com.hedera.services.bdd.suites.HapiSuite; import java.io.IOException; @@ -44,8 +43,10 @@ import java.util.concurrent.ConcurrentLinkedQueue; import java.util.function.LongUnaryOperator; import java.util.stream.IntStream; +import java.util.stream.Stream; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; +import org.junit.jupiter.api.DynamicTest; /** * A helper suite that, @@ -139,11 +140,11 @@ public boolean canRunConcurrent() { } @Override - public List getSpecsInSuite() { + public List> getSpecsInSuite() { return List.of(startStakingAndExportCreatedStakers()); } - final HapiSpec startStakingAndExportCreatedStakers() { + final Stream startStakingAndExportCreatedStakers() { final var baseStakerName = "baseStaker"; return customHapiSpec("StartStakingAndExportCreatedStakers") .withProperties(Map.of( diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/ethereum/EthereumSuite.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/ethereum/EthereumSuite.java index 044fa1758d1e..1ecb3dc2b892 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/ethereum/EthereumSuite.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/ethereum/EthereumSuite.java @@ -41,6 +41,7 @@ import static com.hedera.services.bdd.spec.transactions.TxnVerbs.cryptoCreate; import static com.hedera.services.bdd.spec.transactions.TxnVerbs.cryptoDelete; import static com.hedera.services.bdd.spec.transactions.TxnVerbs.cryptoTransfer; +import static com.hedera.services.bdd.spec.transactions.TxnVerbs.cryptoUpdate; import static com.hedera.services.bdd.spec.transactions.TxnVerbs.cryptoUpdateAliased; import static com.hedera.services.bdd.spec.transactions.TxnVerbs.ethereumCall; import static com.hedera.services.bdd.spec.transactions.TxnVerbs.ethereumCallWithFunctionAbi; @@ -67,6 +68,18 @@ import static com.hedera.services.bdd.spec.utilops.records.SnapshotMatchMode.NONDETERMINISTIC_FUNCTION_PARAMETERS; import static com.hedera.services.bdd.spec.utilops.records.SnapshotMatchMode.NONDETERMINISTIC_NONCE; import static com.hedera.services.bdd.spec.utilops.records.SnapshotMatchMode.NONDETERMINISTIC_TRANSACTION_FEES; +import static com.hedera.services.bdd.suites.HapiSuite.DEFAULT_PAYER; +import static com.hedera.services.bdd.suites.HapiSuite.ETH_HASH_KEY; +import static com.hedera.services.bdd.suites.HapiSuite.FIVE_HBARS; +import static com.hedera.services.bdd.suites.HapiSuite.GENESIS; +import static com.hedera.services.bdd.suites.HapiSuite.ONE_HBAR; +import static com.hedera.services.bdd.suites.HapiSuite.ONE_HUNDRED_HBARS; +import static com.hedera.services.bdd.suites.HapiSuite.ONE_MILLION_HBARS; +import static com.hedera.services.bdd.suites.HapiSuite.RELAYER; +import static com.hedera.services.bdd.suites.HapiSuite.SECP_256K1_SHAPE; +import static com.hedera.services.bdd.suites.HapiSuite.SECP_256K1_SOURCE_KEY; +import static com.hedera.services.bdd.suites.HapiSuite.THREE_MONTHS_IN_SECONDS; +import static com.hedera.services.bdd.suites.HapiSuite.TOKEN_TREASURY; import static com.hedera.services.bdd.suites.contract.Utils.aaWith; import static com.hedera.services.bdd.suites.contract.Utils.asAddress; import static com.hedera.services.bdd.suites.contract.Utils.asToken; @@ -95,12 +108,11 @@ import com.hedera.node.app.hapi.utils.ethereum.EthTxData; import com.hedera.node.app.hapi.utils.ethereum.EthTxData.EthTransactionType; import com.hedera.services.bdd.junit.HapiTest; -import com.hedera.services.bdd.junit.HapiTestSuite; import com.hedera.services.bdd.spec.HapiSpec; +import com.hedera.services.bdd.spec.assertions.TransactionRecordAsserts; +import com.hedera.services.bdd.spec.keys.SigControl; import com.hedera.services.bdd.spec.queries.meta.HapiGetTxnRecord; import com.hedera.services.bdd.spec.transactions.TxnUtils; -import com.hedera.services.bdd.suites.BddMethodIsNotATest; -import com.hedera.services.bdd.suites.HapiSuite; import com.hedera.services.bdd.suites.contract.Utils; import com.hederahashgraph.api.proto.java.AccountID; import com.hederahashgraph.api.proto.java.ResponseCodeEnum; @@ -114,23 +126,20 @@ import java.util.Arrays; import java.util.Comparator; import java.util.List; +import java.util.concurrent.atomic.AtomicLong; import java.util.concurrent.atomic.AtomicReference; import java.util.stream.Stream; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; import org.bouncycastle.util.encoders.Hex; import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.DynamicTest; import org.junit.jupiter.api.Tag; -@HapiTestSuite(fuzzyMatch = true) @Tag(SMART_CONTRACT) @SuppressWarnings("java:S5960") -public class EthereumSuite extends HapiSuite { - +public class EthereumSuite { public static final long GAS_LIMIT = 1_000_000; public static final String ERC20_CONTRACT = "ERC20Contract"; public static final String EMIT_SENDER_ORIGIN_CONTRACT = "EmitSenderOrigin"; - private static final Logger log = LogManager.getLogger(EthereumSuite.class); private static final long DEPOSIT_AMOUNT = 20_000L; private static final String PARTY = "party"; private static final String LAZY_MEMO = "lazy-created account"; @@ -145,42 +154,8 @@ public class EthereumSuite extends HapiSuite { private static final String TOTAL_SUPPLY_TX = "totalSupplyTx"; private static final String ERC20_ABI = "ERC20ABI"; - public static void main(String... args) { - new EthereumSuite().runSuiteAsync(); - } - - @Override - public boolean canRunConcurrent() { - return true; - } - - @Override - public List getSpecsInSuite() { - return Stream.concat( - feePaymentMatrix().stream(), - Stream.of( - invalidTxData(), - etx008ContractCreateExecutesWithExpectedRecord(), - etx009CallsToTokenAddresses(), - etx010TransferToCryptoAccountSucceeds(), - etx013PrecompileCallFailsWhenSignatureMissingFromBothEthereumAndHederaTxn(), - etx014ContractCreateInheritsSignerProperties(), - etx009CallsToTokenAddresses(), - originAndSenderAreEthereumSigner(), - etx031InvalidNonceEthereumTxFailsAndChargesRelayer(), - etxSvc003ContractGetBytecodeQueryReturnsDeployedCode(), - sendingLargerBalanceThanAvailableFailsGracefully(), - directTransferWorksForERC20(), - transferHbarsViaEip2930TxSuccessfully(), - callToTokenAddressViaEip2930TxSuccessfully(), - transferTokensViaEip2930TxSuccessfully(), - accountDeletionResetsTheAliasNonce(), - legacyUnprotectedEtxBeforeEIP155WithDefaultChainId())) - .toList(); - } - @HapiTest - HapiSpec sendingLargerBalanceThanAvailableFailsGracefully() { + final Stream sendingLargerBalanceThanAvailableFailsGracefully() { final AtomicReference

    tokenCreateContractAddress = new AtomicReference<>(); return defaultHapiSpec("sendingLargerBalanceThanAvailableFailsGracefully", NONDETERMINISTIC_ETHEREUM_DATA) @@ -229,7 +204,7 @@ HapiSpec sendingLargerBalanceThanAvailableFailsGracefully() { } @HapiTest - HapiSpec etx010TransferToCryptoAccountSucceeds() { + final Stream etx010TransferToCryptoAccountSucceeds() { String RECEIVER = "RECEIVER"; final String aliasBalanceSnapshot = "aliasBalance"; return defaultHapiSpec( @@ -278,7 +253,7 @@ HapiSpec etx010TransferToCryptoAccountSucceeds() { .hasTinyBars(changeFromSnapshot(aliasBalanceSnapshot, -FIVE_HBARS))); } - List feePaymentMatrix() { + List> feePaymentMatrix() { final long gasPrice = 71; final long chargedGasLimit = GAS_LIMIT * 4 / 5; @@ -316,67 +291,66 @@ List feePaymentMatrix() { } @HapiTest - HapiSpec matrixedPayerRelayerTest1() { + final Stream matrixedPayerRelayerTest1() { return feePaymentMatrix().get(0); } @HapiTest - HapiSpec matrixedPayerRelayerTest2() { + final Stream matrixedPayerRelayerTest2() { return feePaymentMatrix().get(1); } @HapiTest - HapiSpec matrixedPayerRelayerTest3() { + final Stream matrixedPayerRelayerTest3() { return feePaymentMatrix().get(2); } @HapiTest - HapiSpec matrixedPayerRelayerTest4() { + final Stream matrixedPayerRelayerTest4() { return feePaymentMatrix().get(3); } @HapiTest - HapiSpec matrixedPayerRelayerTest5() { + final Stream matrixedPayerRelayerTest5() { return feePaymentMatrix().get(4); } @HapiTest - HapiSpec matrixedPayerRelayerTest6() { + final Stream matrixedPayerRelayerTest6() { return feePaymentMatrix().get(5); } @HapiTest - HapiSpec matrixedPayerRelayerTest7() { + final Stream matrixedPayerRelayerTest7() { return feePaymentMatrix().get(6); } @HapiTest - HapiSpec matrixedPayerRelayerTest8() { + final Stream matrixedPayerRelayerTest8() { return feePaymentMatrix().get(7); } @HapiTest - HapiSpec matrixedPayerRelayerTest9() { + final Stream matrixedPayerRelayerTest9() { return feePaymentMatrix().get(8); } @HapiTest - HapiSpec matrixedPayerRelayerTest10() { + final Stream matrixedPayerRelayerTest10() { return feePaymentMatrix().get(9); } @HapiTest - HapiSpec matrixedPayerRelayerTest11() { + final Stream matrixedPayerRelayerTest11() { return feePaymentMatrix().get(10); } @HapiTest - HapiSpec matrixedPayerRelayerTest12() { + final Stream matrixedPayerRelayerTest12() { return feePaymentMatrix().get(11); } - @BddMethodIsNotATest - HapiSpec matrixedPayerRelayerTest( + final Stream matrixedPayerRelayerTest( final boolean success, final long senderGasPrice, final long relayerOffered, final long senderCharged) { return defaultHapiSpec( "feePaymentMatrix " @@ -434,7 +408,7 @@ HapiSpec matrixedPayerRelayerTest( } @HapiTest - HapiSpec invalidTxData() { + final Stream invalidTxData() { return defaultHapiSpec("InvalidTxData") .given( newKeyNamed(SECP_256K1_SOURCE_KEY).shape(SECP_256K1_SHAPE), @@ -459,7 +433,7 @@ HapiSpec invalidTxData() { } @HapiTest - HapiSpec etx014ContractCreateInheritsSignerProperties() { + final Stream etx014ContractCreateInheritsSignerProperties() { final AtomicReference contractID = new AtomicReference<>(); final String MEMO = "memo"; final String PROXY = "proxy"; @@ -510,7 +484,7 @@ HapiSpec etx014ContractCreateInheritsSignerProperties() { } @HapiTest - HapiSpec etx031InvalidNonceEthereumTxFailsAndChargesRelayer() { + final Stream etx031InvalidNonceEthereumTxFailsAndChargesRelayer() { final var relayerSnapshot = "relayer"; final var senderSnapshot = "sender"; return defaultHapiSpec( @@ -555,7 +529,7 @@ HapiSpec etx031InvalidNonceEthereumTxFailsAndChargesRelayer() { } @HapiTest - HapiSpec etx013PrecompileCallFailsWhenSignatureMissingFromBothEthereumAndHederaTxn() { + final Stream etx013PrecompileCallFailsWhenSignatureMissingFromBothEthereumAndHederaTxn() { final AtomicReference fungible = new AtomicReference<>(); final String fungibleToken = TOKEN; final String mintTxn = MINT_TXN; @@ -611,7 +585,7 @@ HELLO_WORLD_MINT_CONTRACT, asHeadlongAddress(asAddress(fungible.get())))), } @HapiTest - HapiSpec etx009CallsToTokenAddresses() { + final Stream etx009CallsToTokenAddresses() { final AtomicReference tokenNum = new AtomicReference<>(); final var totalSupply = 50; @@ -663,7 +637,7 @@ HapiSpec etx009CallsToTokenAddresses() { // ETX-011 and ETX-030 @HapiTest - HapiSpec originAndSenderAreEthereumSigner() { + final Stream originAndSenderAreEthereumSigner() { return defaultHapiSpec( "originAndSenderAreEthereumSigner", NONDETERMINISTIC_ETHEREUM_DATA, @@ -710,7 +684,7 @@ HapiSpec originAndSenderAreEthereumSigner() { } @HapiTest - HapiSpec etx008ContractCreateExecutesWithExpectedRecord() { + final Stream etx008ContractCreateExecutesWithExpectedRecord() { final var txn = "creation"; final var contract = "Fuse"; @@ -792,7 +766,7 @@ final var record = op.getResponseRecord(); } @HapiTest - HapiSpec etxSvc003ContractGetBytecodeQueryReturnsDeployedCode() { + final Stream etxSvc003ContractGetBytecodeQueryReturnsDeployedCode() { final var txn = "creation"; final var contract = "EmptyConstructor"; return HapiSpec.defaultHapiSpec( @@ -822,7 +796,7 @@ HapiSpec etxSvc003ContractGetBytecodeQueryReturnsDeployedCode() { } @HapiTest - HapiSpec directTransferWorksForERC20() { + final Stream directTransferWorksForERC20() { final var tokenSymbol = "FDFGF"; final var tokenTotalSupply = 5; final var tokenTransferAmount = 3; @@ -879,7 +853,7 @@ HapiSpec directTransferWorksForERC20() { } @HapiTest - HapiSpec transferHbarsViaEip2930TxSuccessfully() { + final Stream transferHbarsViaEip2930TxSuccessfully() { final String RECEIVER = "RECEIVER"; final String aliasBalanceSnapshot = "aliasBalance"; return defaultHapiSpec("transferHbarsViaEip2930TxSuccessfully", NONDETERMINISTIC_ETHEREUM_DATA) @@ -927,7 +901,7 @@ HapiSpec transferHbarsViaEip2930TxSuccessfully() { } @HapiTest - HapiSpec callToTokenAddressViaEip2930TxSuccessfully() { + final Stream callToTokenAddressViaEip2930TxSuccessfully() { final AtomicReference tokenNum = new AtomicReference<>(); final var totalSupply = 50; @@ -977,7 +951,7 @@ HapiSpec callToTokenAddressViaEip2930TxSuccessfully() { } @HapiTest - HapiSpec transferTokensViaEip2930TxSuccessfully() { + final Stream transferTokensViaEip2930TxSuccessfully() { final var tokenSymbol = "FDFGF"; final var tokenTotalSupply = 5; final var tokenTransferAmount = 3; @@ -1036,7 +1010,7 @@ HapiSpec transferTokensViaEip2930TxSuccessfully() { } @HapiTest - final HapiSpec accountDeletionResetsTheAliasNonce() { + final Stream accountDeletionResetsTheAliasNonce() { final AtomicReference partyId = new AtomicReference<>(); final AtomicReference partyAlias = new AtomicReference<>(); @@ -1138,7 +1112,7 @@ final HapiSpec accountDeletionResetsTheAliasNonce() { // is calculated -> v = {0,1} + 27 // source: https://eips.ethereum.org/EIPS/eip-155 @HapiTest - HapiSpec legacyUnprotectedEtxBeforeEIP155WithDefaultChainId() { + final Stream legacyUnprotectedEtxBeforeEIP155WithDefaultChainId() { final String DEPOSIT = "deposit"; final long depositAmount = 20_000L; final Integer chainId = 0; @@ -1174,8 +1148,71 @@ HapiSpec legacyUnprotectedEtxBeforeEIP155WithDefaultChainId() { .hasPriority(recordWith().status(SUCCESS))))); } - @Override - protected Logger getResultsLogger() { - return log; + @HapiTest + final Stream etx007FungibleTokenCreateWithFeesHappyPath() { + final var createdTokenNum = new AtomicLong(); + final var feeCollectorAndAutoRenew = "feeCollectorAndAutoRenew"; + final var contract = "TokenCreateContract"; + final var EXISTING_TOKEN = "EXISTING_TOKEN"; + final var firstTxn = "firstCreateTxn"; + final long DEFAULT_AMOUNT_TO_SEND = 20 * ONE_HBAR; + return defaultHapiSpec("etx007FungibleTokenCreateWithFeesHappyPath") + .given( + newKeyNamed(SECP_256K1_SOURCE_KEY).shape(SECP_256K1_SHAPE), + cryptoTransfer(tinyBarsFromAccountToAlias(GENESIS, SECP_256K1_SOURCE_KEY, ONE_HUNDRED_HBARS)) + .via(AUTO_ACCOUNT_TRANSACTION_NAME), + cryptoCreate(feeCollectorAndAutoRenew) + .keyShape(SigControl.ED25519_ON) + .balance(ONE_MILLION_HBARS), + uploadInitCode(contract), + contractCreate(contract).gas(GAS_LIMIT), + tokenCreate(EXISTING_TOKEN).decimals(5), + tokenAssociate(feeCollectorAndAutoRenew, EXISTING_TOKEN), + cryptoUpdate(feeCollectorAndAutoRenew).key(SECP_256K1_SOURCE_KEY)) + .when(withOpContext((spec, opLog) -> allRunFor( + spec, + ethereumCall( + contract, + "createTokenWithAllCustomFeesAvailable", + spec.registry() + .getKey(SECP_256K1_SOURCE_KEY) + .getECDSASecp256K1() + .toByteArray(), + asHeadlongAddress( + asAddress(spec.registry().getAccountID(feeCollectorAndAutoRenew))), + asHeadlongAddress( + asAddress(spec.registry().getTokenID(EXISTING_TOKEN))), + asHeadlongAddress( + asAddress(spec.registry().getAccountID(feeCollectorAndAutoRenew))), + 8_000_000L) + .via(firstTxn) + .gasLimit(GAS_LIMIT) + .payingWith(feeCollectorAndAutoRenew) + .sending(DEFAULT_AMOUNT_TO_SEND) + .hasKnownStatus(SUCCESS) + .exposingResultTo(result -> { + opLog.info("Explicit create result" + " is {}", result[0]); + final var res = (Address) result[0]; + createdTokenNum.set(res.value().longValueExact()); + })))) + .then( + getTxnRecord(firstTxn).andAllChildRecords().logged(), + childRecordsCheck( + firstTxn, + SUCCESS, + TransactionRecordAsserts.recordWith().status(ResponseCodeEnum.SUCCESS)), + withOpContext((spec, ignore) -> { + final var op = getTxnRecord(firstTxn); + allRunFor(spec, op); + + final var callResult = op.getResponseRecord().getContractCallResult(); + final var gasUsed = callResult.getGasUsed(); + final var amount = callResult.getAmount(); + final var gasLimit = callResult.getGas(); + Assertions.assertEquals(DEFAULT_AMOUNT_TO_SEND, amount); + Assertions.assertEquals(GAS_LIMIT, gasLimit); + Assertions.assertTrue(gasUsed > 0L); + Assertions.assertTrue(callResult.hasContractID() && callResult.hasSenderId()); + })); } } diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/ethereum/EthereumV1SecurityModelSuite.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/ethereum/EthereumV1SecurityModelSuite.java index a09d39917d80..16b5eb517c05 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/ethereum/EthereumV1SecurityModelSuite.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/ethereum/EthereumV1SecurityModelSuite.java @@ -56,7 +56,6 @@ import com.hedera.node.app.hapi.utils.contracts.ParsingConstants.FunctionType; import com.hedera.node.app.hapi.utils.ethereum.EthTxData; import com.hedera.node.app.hapi.utils.ethereum.EthTxData.EthTransactionType; -import com.hedera.services.bdd.spec.HapiSpec; import com.hedera.services.bdd.spec.assertions.TransactionRecordAsserts; import com.hedera.services.bdd.spec.keys.SigControl; import com.hedera.services.bdd.spec.transactions.TxnUtils; @@ -68,10 +67,12 @@ import java.util.List; import java.util.concurrent.atomic.AtomicLong; import java.util.concurrent.atomic.AtomicReference; +import java.util.stream.Stream; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.apache.tuweni.bytes.Bytes; import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.DynamicTest; @SuppressWarnings("java:S5960") public class EthereumV1SecurityModelSuite extends HapiSuite { @@ -96,7 +97,7 @@ public boolean canRunConcurrent() { } @Override - public List getSpecsInSuite() { + public List> getSpecsInSuite() { return List.of( etx007FungibleTokenCreateWithFeesHappyPath(), etx012PrecompileCallSucceedsWhenNeededSignatureInEthTxn(), @@ -104,7 +105,7 @@ public List getSpecsInSuite() { setApproveForAllUsingLocalNodeSetupPasses()); } - final HapiSpec setApproveForAllUsingLocalNodeSetupPasses() { + final Stream setApproveForAllUsingLocalNodeSetupPasses() { final AtomicReference spenderAutoCreatedAccountId = new AtomicReference<>(); final AtomicReference tokenCreateContractID = new AtomicReference<>(); final AtomicReference erc721ContractID = new AtomicReference<>(); @@ -310,7 +311,7 @@ final HapiSpec setApproveForAllUsingLocalNodeSetupPasses() { .then(withOpContext((spec, opLog) -> {})); } - final HapiSpec etx012PrecompileCallSucceedsWhenNeededSignatureInEthTxn() { + final Stream etx012PrecompileCallSucceedsWhenNeededSignatureInEthTxn() { final AtomicReference fungible = new AtomicReference<>(); final String fungibleToken = TOKEN; final String mintTxn = MINT_TXN; @@ -365,7 +366,7 @@ HELLO_WORLD_MINT_CONTRACT, asHeadlongAddress(asAddress(fungible.get())))), spec.registry().getBytes(ETH_HASH_KEY))))))); } - final HapiSpec etx013PrecompileCallSucceedsWhenNeededSignatureInHederaTxn() { + final Stream etx013PrecompileCallSucceedsWhenNeededSignatureInHederaTxn() { final AtomicReference fungible = new AtomicReference<>(); final String fungibleToken = TOKEN; final String mintTxn = MINT_TXN; @@ -423,7 +424,7 @@ HELLO_WORLD_MINT_CONTRACT, asHeadlongAddress(asAddress(fungible.get())))), spec.registry().getBytes(ETH_HASH_KEY))))))); } - final HapiSpec etx007FungibleTokenCreateWithFeesHappyPath() { + final Stream etx007FungibleTokenCreateWithFeesHappyPath() { final var createdTokenNum = new AtomicLong(); final var feeCollectorAndAutoRenew = "feeCollectorAndAutoRenew"; final var contract = "TokenCreateContract"; diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/ethereum/HelloWorldEthereumSuite.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/ethereum/HelloWorldEthereumSuite.java index 9cdbf2e84c6f..01cbcd4557f6 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/ethereum/HelloWorldEthereumSuite.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/ethereum/HelloWorldEthereumSuite.java @@ -29,12 +29,19 @@ import static com.hedera.services.bdd.spec.assertions.ContractLogAsserts.logWith; import static com.hedera.services.bdd.spec.assertions.TransactionRecordAsserts.recordWith; import static com.hedera.services.bdd.spec.keys.KeyFactory.KeyType.THRESHOLD; +import static com.hedera.services.bdd.spec.keys.KeyShape.CONTRACT; +import static com.hedera.services.bdd.spec.keys.KeyShape.PREDEFINED_SHAPE; +import static com.hedera.services.bdd.spec.keys.KeyShape.sigs; +import static com.hedera.services.bdd.spec.keys.KeyShape.threshOf; +import static com.hedera.services.bdd.spec.keys.SigControl.SECP256K1_ON; import static com.hedera.services.bdd.spec.queries.QueryVerbs.getAccountBalance; import static com.hedera.services.bdd.spec.queries.QueryVerbs.getAliasedAccountInfo; import static com.hedera.services.bdd.spec.queries.QueryVerbs.getTxnRecord; +import static com.hedera.services.bdd.spec.transactions.TxnVerbs.contractCall; import static com.hedera.services.bdd.spec.transactions.TxnVerbs.contractCreate; import static com.hedera.services.bdd.spec.transactions.TxnVerbs.cryptoCreate; import static com.hedera.services.bdd.spec.transactions.TxnVerbs.cryptoTransfer; +import static com.hedera.services.bdd.spec.transactions.TxnVerbs.cryptoUpdate; import static com.hedera.services.bdd.spec.transactions.TxnVerbs.ethereumCall; import static com.hedera.services.bdd.spec.transactions.TxnVerbs.ethereumContractCreate; import static com.hedera.services.bdd.spec.transactions.TxnVerbs.ethereumCryptoTransferToExplicit; @@ -43,6 +50,7 @@ import static com.hedera.services.bdd.spec.transactions.TxnVerbs.uploadInitCodeWithConstructorArguments; import static com.hedera.services.bdd.spec.transactions.contract.HapiParserUtil.asHeadlongAddress; import static com.hedera.services.bdd.spec.transactions.crypto.HapiCryptoTransfer.tinyBarsFromAccountToAlias; +import static com.hedera.services.bdd.spec.transactions.crypto.HapiCryptoTransfer.tinyBarsFromToWithAlias; import static com.hedera.services.bdd.spec.transactions.token.CustomFeeSpecs.fixedHbarFee; import static com.hedera.services.bdd.spec.utilops.CustomSpecAssert.allRunFor; import static com.hedera.services.bdd.spec.utilops.UtilVerbs.balanceSnapshot; @@ -57,6 +65,18 @@ import static com.hedera.services.bdd.spec.utilops.records.SnapshotMatchMode.NONDETERMINISTIC_ETHEREUM_DATA; import static com.hedera.services.bdd.spec.utilops.records.SnapshotMatchMode.NONDETERMINISTIC_FUNCTION_PARAMETERS; import static com.hedera.services.bdd.spec.utilops.records.SnapshotMatchMode.NONDETERMINISTIC_TRANSACTION_FEES; +import static com.hedera.services.bdd.suites.HapiSuite.DEFAULT_PAYER; +import static com.hedera.services.bdd.suites.HapiSuite.ETH_HASH_KEY; +import static com.hedera.services.bdd.suites.HapiSuite.ETH_SENDER_ADDRESS; +import static com.hedera.services.bdd.suites.HapiSuite.GENESIS; +import static com.hedera.services.bdd.suites.HapiSuite.MAX_CALL_DATA_SIZE; +import static com.hedera.services.bdd.suites.HapiSuite.ONE_HBAR; +import static com.hedera.services.bdd.suites.HapiSuite.ONE_HUNDRED_HBARS; +import static com.hedera.services.bdd.suites.HapiSuite.ONE_MILLION_HBARS; +import static com.hedera.services.bdd.suites.HapiSuite.RELAYER; +import static com.hedera.services.bdd.suites.HapiSuite.SECP_256K1_SHAPE; +import static com.hedera.services.bdd.suites.HapiSuite.SECP_256K1_SOURCE_KEY; +import static com.hedera.services.bdd.suites.HapiSuite.THOUSAND_HBAR; import static com.hedera.services.bdd.suites.contract.Utils.FunctionType.CONSTRUCTOR; import static com.hedera.services.bdd.suites.contract.Utils.eventSignatureOf; import static com.hedera.services.bdd.suites.contract.Utils.getABIFor; @@ -64,32 +84,30 @@ import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.CONTRACT_REVERT_EXECUTED; import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.INSUFFICIENT_TX_FEE; import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.INVALID_ALIAS_KEY; +import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.INVALID_FEE_SUBMITTED; import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.INVALID_FULL_PREFIX_SIGNATURE_FOR_PRECOMPILE; import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.INVALID_SIGNATURE; import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.INVALID_SOLIDITY_ADDRESS; import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.SUCCESS; import static org.apache.commons.lang3.StringUtils.EMPTY; +import static org.junit.jupiter.api.Assertions.assertFalse; import com.google.protobuf.ByteString; import com.hedera.node.app.hapi.utils.ethereum.EthTxData; import com.hedera.services.bdd.junit.HapiTest; -import com.hedera.services.bdd.junit.HapiTestSuite; -import com.hedera.services.bdd.spec.HapiSpec; -import com.hedera.services.bdd.suites.HapiSuite; +import com.hedera.services.bdd.spec.queries.meta.AccountCreationDetails; import com.hedera.services.bdd.suites.contract.Utils; import com.hederahashgraph.api.proto.java.ResponseCodeEnum; import java.math.BigInteger; import java.util.List; import java.util.Optional; import java.util.concurrent.atomic.AtomicReference; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; +import java.util.stream.Stream; +import org.junit.jupiter.api.DynamicTest; import org.junit.jupiter.api.Tag; -@HapiTestSuite(fuzzyMatch = true) @Tag(SMART_CONTRACT) -public class HelloWorldEthereumSuite extends HapiSuite { - private static final Logger log = LogManager.getLogger(HelloWorldEthereumSuite.class); +public class HelloWorldEthereumSuite { public static final long depositAmount = 20_000L; private static final String PAY_RECEIVABLE_CONTRACT = "PayReceivable"; @@ -98,42 +116,94 @@ public class HelloWorldEthereumSuite extends HapiSuite { private static final String CALLDATA_SIZE_CONTRACT = "CalldataSize"; private static final String DEPOSIT = "deposit"; - public static void main(String... args) { - new HelloWorldEthereumSuite().runSuiteAsync(); - } - - @Override - public List getSpecsInSuite() { - return allOf(ethereumCalls(), ethereumCreates()); - } - - @Override - public boolean canRunConcurrent() { - return true; - } + @HapiTest + final Stream canCreateTokenWithCryptoAdminKeyOnlyIfHasTopLevelSig() { + final var cryptoKey = "cryptoKey"; + final var thresholdKey = "thresholdKey"; + final String contract = "TestTokenCreateContract"; + final AtomicReference adminKey = new AtomicReference<>(); + final AtomicReference creationDetails = new AtomicReference<>(); - List ethereumCalls() { - return List.of( - depositSuccess(), - badRelayClient(), - topLevelBurnToZeroAddressReverts(), - topLevelLazyCreateOfMirrorAddressReverts(), - topLevelSendToReceiverSigRequiredAccountReverts(), - internalBurnToZeroAddressReverts(), - ethereumCallWithCalldataBiggerThanMaxSucceeds(), - createWithSelfDestructInConstructorHasSaneRecord()); - } + return defaultHapiSpec("canCreateTokenWithCryptoAdminKeyOnlyIfHasTopLevelSig") + .given( + // Deploy our test contract + uploadInitCode(contract), + contractCreate(contract).gas(5_000_000L), - List ethereumCreates() { - return List.of( - smallContractCreate(), - contractCreateWithConstructorArgs(), - bigContractCreate(), - doesNotCreateChildRecordIfEthereumContractCreateFails()); + // Create an ECDSA key + newKeyNamed(cryptoKey) + .shape(SECP256K1_ON) + .exposingKeyTo( + k -> adminKey.set(k.getECDSASecp256K1().toByteArray())), + // Create an account with an EVM address derived from this key + cryptoTransfer(tinyBarsFromToWithAlias(DEFAULT_PAYER, cryptoKey, 2 * ONE_HUNDRED_HBARS)) + .via("creation"), + // Get its EVM address for later use in the contract call + getTxnRecord("creation") + .exposingCreationDetailsTo(allDetails -> creationDetails.set(allDetails.getFirst())), + // Update key to a threshold key authorizing our contract use this account as a token treasury + newKeyNamed(thresholdKey) + .shape(threshOf(1, PREDEFINED_SHAPE, CONTRACT).signedWith(sigs(cryptoKey, contract))), + sourcing(() -> cryptoUpdate( + asAccountString(creationDetails.get().createdId())) + .key(thresholdKey) + .signedBy(DEFAULT_PAYER, cryptoKey))) + .when( + // First verify we fail to create without the admin key's top-level signature + sourcing(() -> contractCall( + contract, + "createFungibleTokenWithSECP256K1AdminKeyPublic", + // Treasury is the EVM address + creationDetails.get().evmAddress(), + // Admin key is the ECDSA key + adminKey.get()) + .via("creationWithoutTopLevelSig") + .gas(5_000_000L) + .sending(100 * ONE_HBAR) + .hasKnownStatus(CONTRACT_REVERT_EXECUTED)), + // Next verify we succeed when using the top-level SignatureMap to + // sign with the admin key + sourcing(() -> contractCall( + contract, + "createFungibleTokenWithSECP256K1AdminKeyPublic", + creationDetails.get().evmAddress(), + adminKey.get()) + .via("creationActivatingAdminKeyViaSigMap") + .gas(5_000_000L) + .sending(100 * ONE_HBAR) + // This is the important change, include a top-level signature with the admin key + .alsoSigningWithFullPrefix(cryptoKey)), + // Finally confirm we ALSO succeed when providing the admin key's + // signature via an EthereumTransaction signature + cryptoCreate(RELAYER).balance(10 * THOUSAND_HBAR), + sourcing(() -> ethereumCall( + contract, + "createFungibleTokenWithSECP256K1AdminKeyPublic", + creationDetails.get().evmAddress(), + adminKey.get()) + .type(EthTxData.EthTransactionType.EIP1559) + .nonce(0) + .signingWith(cryptoKey) + .payingWith(RELAYER) + .sending(50 * ONE_HBAR) + .maxGasAllowance(ONE_HBAR * 10) + .gasLimit(5_000_000L) + .via("creationActivatingAdminKeyViaEthTxSig"))) + .then( + childRecordsCheck( + "creationWithoutTopLevelSig", + CONTRACT_REVERT_EXECUTED, + recordWith().status(INVALID_FULL_PREFIX_SIGNATURE_FOR_PRECOMPILE)), + getTxnRecord("creationActivatingAdminKeyViaSigMap") + .exposingTokenCreationsTo(createdIds -> + assertFalse(createdIds.isEmpty(), "Top-level sig map creation failed")), + getTxnRecord("creationActivatingAdminKeyViaEthTxSig") + .exposingTokenCreationsTo( + createdIds -> assertFalse(createdIds.isEmpty(), "EthTx sig creation failed"))); } @HapiTest - HapiSpec badRelayClient() { + final Stream badRelayClient() { final var adminKey = "adminKey"; final var exploitToken = "exploitToken"; final var exploitContract = "BadRelayClient"; @@ -206,7 +276,7 @@ HapiSpec badRelayClient() { } @HapiTest - HapiSpec depositSuccess() { + final Stream depositSuccess() { return defaultHapiSpec("depositSuccess", NONDETERMINISTIC_ETHEREUM_DATA, NONDETERMINISTIC_TRANSACTION_FEES) .given( newKeyNamed(SECP_256K1_SOURCE_KEY).shape(SECP_256K1_SHAPE), @@ -275,7 +345,7 @@ HapiSpec depositSuccess() { } @HapiTest - HapiSpec ethereumCallWithCalldataBiggerThanMaxSucceeds() { + final Stream ethereumCallWithCalldataBiggerThanMaxSucceeds() { final var largerThanMaxCalldata = new byte[MAX_CALL_DATA_SIZE + 1]; return defaultHapiSpec( "ethereumCallWithCalldataBiggerThanMaxSucceeds", @@ -317,7 +387,7 @@ HapiSpec ethereumCallWithCalldataBiggerThanMaxSucceeds() { } @HapiTest - HapiSpec createWithSelfDestructInConstructorHasSaneRecord() { + final Stream createWithSelfDestructInConstructorHasSaneRecord() { final var txn = "txn"; final var selfDestructingContract = "FactorySelfDestructConstructor"; // Does nested creates, which appear in reversed order from mono-service @@ -344,7 +414,7 @@ HapiSpec createWithSelfDestructInConstructorHasSaneRecord() { } @HapiTest - HapiSpec smallContractCreate() { + final Stream smallContractCreate() { return defaultHapiSpec("smallContractCreate", NONDETERMINISTIC_ETHEREUM_DATA, NONDETERMINISTIC_TRANSACTION_FEES) .given( newKeyNamed(SECP_256K1_SOURCE_KEY).shape(SECP_256K1_SHAPE), @@ -388,7 +458,7 @@ HapiSpec smallContractCreate() { } @HapiTest - HapiSpec doesNotCreateChildRecordIfEthereumContractCreateFails() { + final Stream doesNotCreateChildRecordIfEthereumContractCreateFails() { final Long insufficientGasAllowance = 1L; return defaultHapiSpec( "doesNotCreateChildRecordIfEthereumContractCreateFails", NONDETERMINISTIC_FUNCTION_PARAMETERS) @@ -412,7 +482,7 @@ HapiSpec doesNotCreateChildRecordIfEthereumContractCreateFails() { } @HapiTest - HapiSpec idVariantsTreatedAsExpected() { + final Stream idVariantsTreatedAsExpected() { final var contractAdminKey = "contractAdminKey"; return defaultHapiSpec( "idVariantsTreatedAsExpected", @@ -437,7 +507,7 @@ HapiSpec idVariantsTreatedAsExpected() { } @HapiTest - HapiSpec bigContractCreate() { + final Stream bigContractCreate() { final var contractAdminKey = "contractAdminKey"; return defaultHapiSpec("bigContractCreate", NONDETERMINISTIC_ETHEREUM_DATA, NONDETERMINISTIC_TRANSACTION_FEES) .given( @@ -483,7 +553,7 @@ HapiSpec bigContractCreate() { } @HapiTest - HapiSpec contractCreateWithConstructorArgs() { + final Stream contractCreateWithConstructorArgs() { final var contractAdminKey = "contractAdminKey"; return defaultHapiSpec( "contractCreateWithConstructorArgs", @@ -542,7 +612,7 @@ HapiSpec contractCreateWithConstructorArgs() { private static final String SEND_TO = "sendTo"; @HapiTest - HapiSpec topLevelBurnToZeroAddressReverts() { + final Stream topLevelBurnToZeroAddressReverts() { final var ethBurnAddress = new byte[20]; return defaultHapiSpec("topLevelBurnToZeroAddressReverts", NONDETERMINISTIC_ETHEREUM_DATA) .given( @@ -561,7 +631,7 @@ HapiSpec topLevelBurnToZeroAddressReverts() { } @HapiTest - HapiSpec topLevelLazyCreateOfMirrorAddressReverts() { + final Stream topLevelLazyCreateOfMirrorAddressReverts() { final var nonExistentMirrorAddress = Utils.asSolidityAddress(0, 0, 666_666); return defaultHapiSpec( "topLevelLazyCreateOfMirrorAddressReverts", @@ -584,7 +654,7 @@ HapiSpec topLevelLazyCreateOfMirrorAddressReverts() { } @HapiTest - HapiSpec topLevelSendToReceiverSigRequiredAccountReverts() { + final Stream topLevelSendToReceiverSigRequiredAccountReverts() { final var receiverSigAccount = "receiverSigAccount"; final AtomicReference receiverMirrorAddr = new AtomicReference<>(); final var preCallBalance = "preCallBalance"; @@ -613,11 +683,8 @@ HapiSpec topLevelSendToReceiverSigRequiredAccountReverts() { } @HapiTest - HapiSpec internalBurnToZeroAddressReverts() { - return defaultHapiSpec( - "internalBurnToZeroAddressReverts", - NONDETERMINISTIC_ETHEREUM_DATA, - NONDETERMINISTIC_TRANSACTION_FEES) + final Stream internalBurnToZeroAddressReverts() { + return defaultHapiSpec("internalBurnToZeroAddressReverts", FULLY_NONDETERMINISTIC) .given( newKeyNamed(SECP_256K1_SOURCE_KEY).shape(SECP_256K1_SHAPE), cryptoCreate(RELAYER).balance(123 * ONE_HUNDRED_HBARS), @@ -632,11 +699,6 @@ HapiSpec internalBurnToZeroAddressReverts() { .maxPriorityGas(2L) .gasLimit(1_000_000L) .sending(depositAmount) - .hasKnownStatus(CONTRACT_REVERT_EXECUTED)); - } - - @Override - protected Logger getResultsLogger() { - return log; + .hasKnownStatus(INVALID_FEE_SUBMITTED)); } } diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/ethereum/NonceSuite.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/ethereum/NonceSuite.java index 1eeca76db779..f378289b13b1 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/ethereum/NonceSuite.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/ethereum/NonceSuite.java @@ -43,10 +43,18 @@ import static com.hedera.services.bdd.spec.utilops.UtilVerbs.remembering; import static com.hedera.services.bdd.spec.utilops.UtilVerbs.sourcing; import static com.hedera.services.bdd.spec.utilops.UtilVerbs.withOpContext; +import static com.hedera.services.bdd.spec.utilops.records.SnapshotMatchMode.FULLY_NONDETERMINISTIC; import static com.hedera.services.bdd.spec.utilops.records.SnapshotMatchMode.HIGHLY_NON_DETERMINISTIC_FEES; import static com.hedera.services.bdd.spec.utilops.records.SnapshotMatchMode.NONDETERMINISTIC_ETHEREUM_DATA; import static com.hedera.services.bdd.spec.utilops.records.SnapshotMatchMode.NONDETERMINISTIC_FUNCTION_PARAMETERS; import static com.hedera.services.bdd.spec.utilops.records.SnapshotMatchMode.NONDETERMINISTIC_TRANSACTION_FEES; +import static com.hedera.services.bdd.suites.HapiSuite.GENESIS; +import static com.hedera.services.bdd.suites.HapiSuite.ONE_HBAR; +import static com.hedera.services.bdd.suites.HapiSuite.ONE_HUNDRED_HBARS; +import static com.hedera.services.bdd.suites.HapiSuite.ONE_MILLION_HBARS; +import static com.hedera.services.bdd.suites.HapiSuite.RELAYER; +import static com.hedera.services.bdd.suites.HapiSuite.SECP_256K1_SHAPE; +import static com.hedera.services.bdd.suites.HapiSuite.SECP_256K1_SOURCE_KEY; import static com.hedera.services.bdd.suites.contract.Utils.mirrorAddrWith; import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.BUSY; import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.CONTRACT_REVERT_EXECUTED; @@ -54,6 +62,7 @@ import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.INSUFFICIENT_PAYER_BALANCE; import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.INSUFFICIENT_TX_FEE; import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.INVALID_ACCOUNT_ID; +import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.INVALID_FEE_SUBMITTED; import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.MAX_CHILD_RECORDS_EXCEEDED; import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.MAX_GAS_LIMIT_EXCEEDED; import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.NEGATIVE_ALLOWANCE_AMOUNT; @@ -62,28 +71,21 @@ import com.hedera.node.app.hapi.utils.ethereum.EthTxData; import com.hedera.node.app.hapi.utils.ethereum.EthTxData.EthTransactionType; import com.hedera.services.bdd.junit.HapiTest; -import com.hedera.services.bdd.junit.HapiTestSuite; import com.hedera.services.bdd.spec.HapiPropertySource; -import com.hedera.services.bdd.spec.HapiSpec; import com.hedera.services.bdd.spec.HapiSpecSetup; -import com.hedera.services.bdd.suites.HapiSuite; import com.hederahashgraph.api.proto.java.AccountID; import java.math.BigInteger; import java.util.HashMap; -import java.util.List; import java.util.Map; import java.util.concurrent.atomic.AtomicLong; import java.util.concurrent.atomic.AtomicReference; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; +import java.util.stream.Stream; +import org.junit.jupiter.api.DynamicTest; import org.junit.jupiter.api.Tag; -@HapiTestSuite(fuzzyMatch = true) @Tag(SMART_CONTRACT) @SuppressWarnings("java:S5960") -public class NonceSuite extends HapiSuite { - - private static final Logger log = LogManager.getLogger(NonceSuite.class); +public class NonceSuite { private static final long LOW_GAS_PRICE = 1L; private static final long ENOUGH_GAS_PRICE = 75L; private static final long ENOUGH_GAS_LIMIT = 150_000L; @@ -105,65 +107,8 @@ public class NonceSuite extends HapiSuite { private static final String CHECK_BALANCE_REPEATEDLY_FUNCTION = "checkBalanceRepeatedly"; private static final String TX = "tx"; - public static void main(String... args) { - new NonceSuite().runSuiteAsync(); - } - - @Override - public boolean canRunConcurrent() { - return true; - } - - @Override - public List getSpecsInSuite() { - return List.of( - // pre-checks - nonceNotUpdatedWhenSignerDoesExistPrecheckFailed(), - nonceNotUpdatedWhenPayerHasInsufficientBalancePrecheckFailed(), - nonceNotUpdatedWhenNegativeMaxGasAllowancePrecheckFailed(), - nonceNotUpdatedWhenInsufficientIntrinsicGasPrecheckFailed(), - nonceNotUpdatedWhenMaxGasPerSecPrecheckFailed(), - // handler checks - nonceNotUpdatedWhenIntrinsicGasHandlerCheckFailed(), - nonceNotUpdatedWhenUserOfferedGasPriceAndAllowanceAreZeroHandlerCheckFailed(), - nonceNotUpdatedWhenOfferedGasPriceIsLessThanCurrentAndSenderDoesNotHaveEnoughBalanceHandlerCheckFailed(), - nonceNotUpdatedWhenOfferedGasPriceIsLessThanCurrentAndGasAllowanceIsLessThanRemainingFeeHandlerCheckFailed(), - nonceNotUpdatedWhenOfferedGasPriceIsBiggerThanCurrentAndSenderDoesNotHaveEnoughBalanceHandlerCheckFailed(), - nonceNotUpdatedWhenSenderDoesNotHaveEnoughBalanceHandlerCheckFailed(), - nonceNotUpdatedForNonEthereumTransaction(), - revertsWhenSenderDoesNotExist(), - // evm smart contract reversions - nonceUpdatedAfterEvmReversionDueContractLogic(), - nonceUpdatedAfterEvmReversionDueInsufficientGas(), - nonceUpdatedAfterEvmReversionDueInsufficientTransferAmount(), - // evm hedera specific reversions - nonceUpdatedAfterEvmReversionDueSendingValueToEthereumPrecompile0x2(), - nonceUpdatedAfterEvmReversionDueSendingValueToHederaPrecompile0x167(), - // evm hedera specific resource validation reversions - nonceUpdatedAfterEvmReversionDueMaxChildRecordsExceeded(), - // successful ethereum transactions via internal calls - nonceUpdatedAfterSuccessfulInternalCall(), - nonceUpdatedAfterSuccessfulInternalTransfer(), - nonceUpdatedAfterSuccessfulInternalContractDeployment(), - // handler checks for contract creation - nonceNotUpdatedWhenIntrinsicGasHandlerCheckFailedEthContractCreate(), - nonceNotUpdatedWhenUserOfferedGasPriceAndAllowanceAreZeroHandlerCheckFailedEthContractCreate(), - nonceNotUpdatedWhenOfferedGasPriceIsLessThanCurrentAndSenderDoesNotHaveEnoughBalanceHandlerCheckFailedEthContractCreate(), - nonceNotUpdatedWhenOfferedGasPriceIsLessThanCurrentAndGasAllowanceIsLessThanRemainingFeeHandlerCheckFailedEthContractCreate(), - nonceNotUpdatedWhenOfferedGasPriceIsBiggerThanCurrentAndSenderDoesNotHaveEnoughBalanceHandlerCheckFailedEthContractCreate(), - nonceNotUpdatedWhenSenderDoesNotHaveEnoughBalanceHandlerCheckFailedEthContractCreate(), - // evm smart contract reversions for contract creation - nonceUpdatedAfterEvmReversionDueContractLogicEthContractCreate(), - nonceUpdatedAfterEvmReversionDueInsufficientGasEthContractCreate(), - nonceUpdatedAfterEvmReversionDueInsufficientTransferAmountEthContractCreate(), - // evm hedera specific reversions for contract creation - nonceUpdatedAfterEvmReversionDueSendingValueToEthereumPrecompileEthContractCreate(), - // successful ethereum contract deploy - nonceUpdatedAfterSuccessfulEthereumContractCreation()); - } - @HapiTest - HapiSpec nonceNotUpdatedWhenSignerDoesExistPrecheckFailed() { + final Stream nonceNotUpdatedWhenSignerDoesExistPrecheckFailed() { return defaultHapiSpec("nonceNotUpdatedWhenSignerDoesExistPrecheckFailed", NONDETERMINISTIC_TRANSACTION_FEES) .given( newKeyNamed(SECP_256K1_SOURCE_KEY).shape(SECP_256K1_SHAPE), @@ -182,7 +127,7 @@ HapiSpec nonceNotUpdatedWhenSignerDoesExistPrecheckFailed() { } @HapiTest - HapiSpec nonceNotUpdatedWhenPayerHasInsufficientBalancePrecheckFailed() { + final Stream nonceNotUpdatedWhenPayerHasInsufficientBalancePrecheckFailed() { return defaultHapiSpec( "nonceNotUpdatedWhenPayerHasInsufficientBalancePrecheckFailed", NONDETERMINISTIC_TRANSACTION_FEES) @@ -204,7 +149,7 @@ HapiSpec nonceNotUpdatedWhenPayerHasInsufficientBalancePrecheckFailed() { } @HapiTest - HapiSpec nonceNotUpdatedWhenNegativeMaxGasAllowancePrecheckFailed() { + final Stream nonceNotUpdatedWhenNegativeMaxGasAllowancePrecheckFailed() { return defaultHapiSpec( "nonceNotUpdatedWhenNegativeMaxGasAllowancePrecheckFailed", NONDETERMINISTIC_TRANSACTION_FEES) .given( @@ -226,7 +171,7 @@ HapiSpec nonceNotUpdatedWhenNegativeMaxGasAllowancePrecheckFailed() { } @HapiTest - HapiSpec nonceNotUpdatedWhenInsufficientIntrinsicGasPrecheckFailed() { + final Stream nonceNotUpdatedWhenInsufficientIntrinsicGasPrecheckFailed() { return defaultHapiSpec( "nonceNotUpdatedWhenInsufficientIntrinsicGasPrecheckFailed", NONDETERMINISTIC_TRANSACTION_FEES) .given( @@ -247,7 +192,7 @@ HapiSpec nonceNotUpdatedWhenInsufficientIntrinsicGasPrecheckFailed() { } @HapiTest - HapiSpec nonceNotUpdatedWhenMaxGasPerSecPrecheckFailed() { + final Stream nonceNotUpdatedWhenMaxGasPerSecPrecheckFailed() { final Map startingProps = new HashMap<>(); final String USE_GAS_THROTTLE_PROP = "contracts.throttle.throttleByGas"; AtomicLong maxGasPerSec = new AtomicLong(); @@ -275,7 +220,7 @@ HapiSpec nonceNotUpdatedWhenMaxGasPerSecPrecheckFailed() { } @HapiTest - HapiSpec nonceNotUpdatedWhenIntrinsicGasHandlerCheckFailed() { + final Stream nonceNotUpdatedWhenIntrinsicGasHandlerCheckFailed() { return defaultHapiSpec( "nonceNotUpdatedWhenIntrinsicGasHandlerCheckFailed", NONDETERMINISTIC_TRANSACTION_FEES, @@ -304,7 +249,7 @@ HapiSpec nonceNotUpdatedWhenIntrinsicGasHandlerCheckFailed() { } @HapiTest - HapiSpec nonceNotUpdatedWhenUserOfferedGasPriceAndAllowanceAreZeroHandlerCheckFailed() { + final Stream nonceNotUpdatedWhenUserOfferedGasPriceAndAllowanceAreZeroHandlerCheckFailed() { return defaultHapiSpec( "nonceNotUpdatedWhenUserOfferedGasPriceAndAllowanceAreZeroHandlerCheckFailed", NONDETERMINISTIC_TRANSACTION_FEES) @@ -334,7 +279,8 @@ HapiSpec nonceNotUpdatedWhenUserOfferedGasPriceAndAllowanceAreZeroHandlerCheckFa } @HapiTest - HapiSpec nonceNotUpdatedWhenOfferedGasPriceIsLessThanCurrentAndSenderDoesNotHaveEnoughBalanceHandlerCheckFailed() { + final Stream + nonceNotUpdatedWhenOfferedGasPriceIsLessThanCurrentAndSenderDoesNotHaveEnoughBalanceHandlerCheckFailed() { return defaultHapiSpec( "nonceNotUpdatedWhenOfferedGasPriceIsLessThanCurrentAndSenderDoesNotHaveEnoughBalanceHandlerCheckFailed", NONDETERMINISTIC_TRANSACTION_FEES) @@ -363,7 +309,7 @@ HapiSpec nonceNotUpdatedWhenOfferedGasPriceIsLessThanCurrentAndSenderDoesNotHave } @HapiTest - HapiSpec + final Stream nonceNotUpdatedWhenOfferedGasPriceIsLessThanCurrentAndGasAllowanceIsLessThanRemainingFeeHandlerCheckFailed() { return defaultHapiSpec( "nonceNotUpdatedWhenOfferedGasPriceIsLessThanCurrentAndGasAllowanceIsLessThanRemainingFeeHandlerCheckFailed", @@ -394,7 +340,7 @@ HapiSpec nonceNotUpdatedWhenOfferedGasPriceIsLessThanCurrentAndSenderDoesNotHave } @HapiTest - HapiSpec + final Stream nonceNotUpdatedWhenOfferedGasPriceIsBiggerThanCurrentAndSenderDoesNotHaveEnoughBalanceHandlerCheckFailed() { return defaultHapiSpec( "nonceNotUpdatedWhenOfferedGasPriceIsBiggerThanCurrentAndSenderDoesNotHaveEnoughBalanceHandlerCheckFailed", @@ -424,7 +370,7 @@ HapiSpec nonceNotUpdatedWhenOfferedGasPriceIsLessThanCurrentAndSenderDoesNotHave } @HapiTest - HapiSpec nonceNotUpdatedWhenSenderDoesNotHaveEnoughBalanceHandlerCheckFailed() { + final Stream nonceNotUpdatedWhenSenderDoesNotHaveEnoughBalanceHandlerCheckFailed() { return defaultHapiSpec( "nonceNotUpdatedWhenSenderDoesNotHaveEnoughBalanceHandlerCheckFailed", NONDETERMINISTIC_TRANSACTION_FEES) @@ -454,7 +400,7 @@ HapiSpec nonceNotUpdatedWhenSenderDoesNotHaveEnoughBalanceHandlerCheckFailed() { } @HapiTest - HapiSpec nonceNotUpdatedForNonEthereumTransaction() { + final Stream nonceNotUpdatedForNonEthereumTransaction() { return defaultHapiSpec("nonceNotUpdatedForNonEthereumTransaction") .given( cryptoCreate(RELAYER).balance(ONE_HUNDRED_HBARS), @@ -469,7 +415,7 @@ HapiSpec nonceNotUpdatedForNonEthereumTransaction() { } @HapiTest - HapiSpec nonceUpdatedAfterSuccessfulInternalCall() { + final Stream nonceUpdatedAfterSuccessfulInternalCall() { return defaultHapiSpec( "nonceUpdatedAfterSuccessfulInternalCall", NONDETERMINISTIC_ETHEREUM_DATA, @@ -496,7 +442,7 @@ HapiSpec nonceUpdatedAfterSuccessfulInternalCall() { } @HapiTest - HapiSpec nonceUpdatedAfterEvmReversionDueContractLogic() { + final Stream nonceUpdatedAfterEvmReversionDueContractLogic() { return defaultHapiSpec( "nonceUpdatedAfterEvmReversionDueContractLogic", NONDETERMINISTIC_ETHEREUM_DATA, @@ -525,7 +471,7 @@ HapiSpec nonceUpdatedAfterEvmReversionDueContractLogic() { } @HapiTest - HapiSpec nonceUpdatedAfterEvmReversionDueInsufficientGas() { + final Stream nonceUpdatedAfterEvmReversionDueInsufficientGas() { return defaultHapiSpec("nonceUpdatedAfterEvmReversionDueInsufficientGas", NONDETERMINISTIC_ETHEREUM_DATA) .given( newKeyNamed(SECP_256K1_SOURCE_KEY).shape(SECP_256K1_SHAPE), @@ -553,7 +499,7 @@ HapiSpec nonceUpdatedAfterEvmReversionDueInsufficientGas() { } @HapiTest - HapiSpec nonceUpdatedAfterEvmReversionDueInsufficientTransferAmount() { + final Stream nonceUpdatedAfterEvmReversionDueInsufficientTransferAmount() { AtomicReference receiverId = new AtomicReference<>(); return defaultHapiSpec( "nonceUpdatedAfterEvmReversionDueInsufficientTransferAmount", @@ -590,7 +536,7 @@ HapiSpec nonceUpdatedAfterEvmReversionDueInsufficientTransferAmount() { } @HapiTest - HapiSpec nonceUpdatedAfterEvmReversionDueSendingValueToEthereumPrecompile0x2() { + final Stream nonceUpdatedAfterEvmReversionDueSendingValueToEthereumPrecompile0x2() { AccountID eth0x2 = AccountID.newBuilder().setAccountNum(2).build(); return defaultHapiSpec( "nonceUpdatedAfterEvmReversionDueSendingValueToEthereumPrecompile0x2", @@ -625,7 +571,7 @@ HapiSpec nonceUpdatedAfterEvmReversionDueSendingValueToEthereumPrecompile0x2() { } @HapiTest - HapiSpec nonceUpdatedAfterEvmReversionDueSendingValueToHederaPrecompile0x167() { + final Stream nonceUpdatedAfterEvmReversionDueSendingValueToHederaPrecompile0x167() { AccountID eth0x167 = AccountID.newBuilder().setAccountNum(2).build(); return defaultHapiSpec( "nonceUpdatedAfterEvmReversionDueSendingValueToHederaPrecompile0x167", @@ -660,7 +606,7 @@ HapiSpec nonceUpdatedAfterEvmReversionDueSendingValueToHederaPrecompile0x167() { } @HapiTest - HapiSpec nonceUpdatedAfterEvmReversionDueMaxChildRecordsExceeded() { + final Stream nonceUpdatedAfterEvmReversionDueMaxChildRecordsExceeded() { final String TOKEN_TREASURY = "treasury"; final String FUNGIBLE_TOKEN = "fungibleToken"; final AtomicReference treasuryMirrorAddr = new AtomicReference<>(); @@ -711,7 +657,7 @@ HapiSpec nonceUpdatedAfterEvmReversionDueMaxChildRecordsExceeded() { } @HapiTest - HapiSpec nonceUpdatedAfterSuccessfulInternalTransfer() { + final Stream nonceUpdatedAfterSuccessfulInternalTransfer() { AtomicReference receiverId = new AtomicReference<>(); return defaultHapiSpec( "nonceUpdatedAfterSuccessfulInternalTransfer", @@ -746,7 +692,7 @@ HapiSpec nonceUpdatedAfterSuccessfulInternalTransfer() { } @HapiTest - HapiSpec nonceUpdatedAfterSuccessfulInternalContractDeployment() { + final Stream nonceUpdatedAfterSuccessfulInternalContractDeployment() { return defaultHapiSpec("nonceUpdatedAfterSuccessfulInternalContractDeployment", NONDETERMINISTIC_ETHEREUM_DATA) .given( cryptoCreate(RECEIVER).balance(0L), @@ -772,7 +718,7 @@ HapiSpec nonceUpdatedAfterSuccessfulInternalContractDeployment() { } @HapiTest - HapiSpec nonceNotUpdatedWhenIntrinsicGasHandlerCheckFailedEthContractCreate() { + final Stream nonceNotUpdatedWhenIntrinsicGasHandlerCheckFailedEthContractCreate() { return defaultHapiSpec( "nonceNotUpdatedWhenIntrinsicGasHandlerCheckFailedEthContractCreate", NONDETERMINISTIC_TRANSACTION_FEES, @@ -800,7 +746,8 @@ HapiSpec nonceNotUpdatedWhenIntrinsicGasHandlerCheckFailedEthContractCreate() { } @HapiTest - HapiSpec nonceNotUpdatedWhenUserOfferedGasPriceAndAllowanceAreZeroHandlerCheckFailedEthContractCreate() { + final Stream + nonceNotUpdatedWhenUserOfferedGasPriceAndAllowanceAreZeroHandlerCheckFailedEthContractCreate() { return defaultHapiSpec( "nonceNotUpdatedWhenUserOfferedGasPriceAndAllowanceAreZeroHandlerCheckFailedEthContractCreate", NONDETERMINISTIC_TRANSACTION_FEES) @@ -829,7 +776,7 @@ HapiSpec nonceNotUpdatedWhenUserOfferedGasPriceAndAllowanceAreZeroHandlerCheckFa } @HapiTest - HapiSpec + final Stream nonceNotUpdatedWhenOfferedGasPriceIsLessThanCurrentAndSenderDoesNotHaveEnoughBalanceHandlerCheckFailedEthContractCreate() { return defaultHapiSpec( "nonceNotUpdatedWhenOfferedGasPriceIsLessThanCurrentAndSenderDoesNotHaveEnoughBalanceHandlerCheckFailedEthContractCreate") @@ -857,7 +804,7 @@ HapiSpec nonceNotUpdatedWhenUserOfferedGasPriceAndAllowanceAreZeroHandlerCheckFa } @HapiTest - HapiSpec + final Stream nonceNotUpdatedWhenOfferedGasPriceIsLessThanCurrentAndGasAllowanceIsLessThanRemainingFeeHandlerCheckFailedEthContractCreate() { return defaultHapiSpec( "nonceNotUpdatedWhenOfferedGasPriceIsLessThanCurrentAndGasAllowanceIsLessThanRemainingFeeHandlerCheckFailedEthContractCreate", @@ -887,7 +834,7 @@ HapiSpec nonceNotUpdatedWhenUserOfferedGasPriceAndAllowanceAreZeroHandlerCheckFa } @HapiTest - HapiSpec + final Stream nonceNotUpdatedWhenOfferedGasPriceIsBiggerThanCurrentAndSenderDoesNotHaveEnoughBalanceHandlerCheckFailedEthContractCreate() { return defaultHapiSpec( "nonceNotUpdatedWhenOfferedGasPriceIsBiggerThanCurrentAndSenderDoesNotHaveEnoughBalanceHandlerCheckFailedEthContractCreate") @@ -915,7 +862,7 @@ HapiSpec nonceNotUpdatedWhenUserOfferedGasPriceAndAllowanceAreZeroHandlerCheckFa } @HapiTest - HapiSpec nonceNotUpdatedWhenSenderDoesNotHaveEnoughBalanceHandlerCheckFailedEthContractCreate() { + final Stream nonceNotUpdatedWhenSenderDoesNotHaveEnoughBalanceHandlerCheckFailedEthContractCreate() { return defaultHapiSpec( "nonceNotUpdatedWhenSenderDoesNotHaveEnoughBalanceHandlerCheckFailedEthContractCreate", NONDETERMINISTIC_TRANSACTION_FEES) @@ -944,7 +891,7 @@ HapiSpec nonceNotUpdatedWhenSenderDoesNotHaveEnoughBalanceHandlerCheckFailedEthC } @HapiTest - HapiSpec nonceUpdatedAfterEvmReversionDueContractLogicEthContractCreate() { + final Stream nonceUpdatedAfterEvmReversionDueContractLogicEthContractCreate() { return defaultHapiSpec( "nonceUpdatedAfterEvmReversionDueContractLogicEthContractCreate", NONDETERMINISTIC_ETHEREUM_DATA, @@ -972,7 +919,7 @@ HapiSpec nonceUpdatedAfterEvmReversionDueContractLogicEthContractCreate() { } @HapiTest - HapiSpec nonceUpdatedAfterEvmReversionDueInsufficientGasEthContractCreate() { + final Stream nonceUpdatedAfterEvmReversionDueInsufficientGasEthContractCreate() { return defaultHapiSpec( "nonceUpdatedAfterEvmReversionDueInsufficientGasEthContractCreate", NONDETERMINISTIC_ETHEREUM_DATA) @@ -999,7 +946,7 @@ HapiSpec nonceUpdatedAfterEvmReversionDueInsufficientGasEthContractCreate() { } @HapiTest - HapiSpec nonceUpdatedAfterEvmReversionDueInsufficientTransferAmountEthContractCreate() { + final Stream nonceUpdatedAfterEvmReversionDueInsufficientTransferAmountEthContractCreate() { return defaultHapiSpec( "nonceUpdatedAfterEvmReversionDueInsufficientTransferAmountEthContractCreate", NONDETERMINISTIC_ETHEREUM_DATA) @@ -1025,10 +972,10 @@ HapiSpec nonceUpdatedAfterEvmReversionDueInsufficientTransferAmountEthContractCr } @HapiTest - HapiSpec nonceUpdatedAfterEvmReversionDueSendingValueToEthereumPrecompileEthContractCreate() { + final Stream nonceUpdatedAfterEvmReversionDueSendingValueToEthereumPrecompileEthContractCreate() { return defaultHapiSpec( "nonceUpdatedAfterEvmReversionDueSendingValueToEthereumPrecompileEthContractCreate", - NONDETERMINISTIC_ETHEREUM_DATA) + FULLY_NONDETERMINISTIC) .given( newKeyNamed(SECP_256K1_SOURCE_KEY).shape(SECP_256K1_SHAPE), cryptoCreate(RELAYER).balance(ONE_MILLION_HBARS), @@ -1041,20 +988,17 @@ HapiSpec nonceUpdatedAfterEvmReversionDueSendingValueToEthereumPrecompileEthCont .payingWith(RELAYER) .nonce(0) .gasLimit(ENOUGH_GAS_LIMIT) - .hasKnownStatus(CONTRACT_REVERT_EXECUTED) + .hasKnownStatus(INVALID_FEE_SUBMITTED) .via(TX)) .then( getAliasedAccountInfo(SECP_256K1_SOURCE_KEY) .has(accountWith().nonce(1L)), - getTxnRecord(TX) - .hasPriority(recordWith() - .status(CONTRACT_REVERT_EXECUTED) - .contractCreateResult(resultWith().signerNonce(1L)))); + getTxnRecord(TX).hasChildRecordCount(0)); } // depends on https://github.com/hashgraph/hedera-services/pull/11359 @HapiTest - HapiSpec nonceUpdatedAfterEvmReversionDueSendingValueToHederaPrecompileEthContractCreate() { + final Stream nonceUpdatedAfterEvmReversionDueSendingValueToHederaPrecompileEthContractCreate() { return defaultHapiSpec("nonceUpdatedAfterEvmReversionDueSendingValueToHederaPrecompileEthContractCreate") .given( newKeyNamed(SECP_256K1_SOURCE_KEY).shape(SECP_256K1_SHAPE), @@ -1080,7 +1024,7 @@ HapiSpec nonceUpdatedAfterEvmReversionDueSendingValueToHederaPrecompileEthContra } @HapiTest - HapiSpec nonceUpdatedAfterSuccessfulEthereumContractCreation() { + final Stream nonceUpdatedAfterSuccessfulEthereumContractCreation() { return defaultHapiSpec("nonceUpdatedAfterSuccessfulEthereumContractCreation", NONDETERMINISTIC_ETHEREUM_DATA) .given( newKeyNamed(SECP_256K1_SOURCE_KEY).shape(SECP_256K1_SHAPE), @@ -1103,7 +1047,7 @@ HapiSpec nonceUpdatedAfterSuccessfulEthereumContractCreation() { } @HapiTest - HapiSpec revertsWhenSenderDoesNotExist() { + final Stream revertsWhenSenderDoesNotExist() { AtomicReference receiverId = new AtomicReference<>(); return defaultHapiSpec("revertsWhenSenderDoesNotExist") .given( @@ -1129,9 +1073,4 @@ HapiSpec revertsWhenSenderDoesNotExist() { .hasPriority( recordWith().contractCallResult(resultWith().signerNonce(0L)))); } - - @Override - protected Logger getResultsLogger() { - return log; - } } diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/fees/AllBaseOpFeesSuite.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/fees/AllBaseOpFeesSuite.java index 3510798cfd45..766d4a844c66 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/fees/AllBaseOpFeesSuite.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/fees/AllBaseOpFeesSuite.java @@ -16,38 +16,64 @@ package com.hedera.services.bdd.suites.fees; +import static com.hedera.services.bdd.junit.TestTags.TOKEN; import static com.hedera.services.bdd.spec.HapiSpec.defaultHapiSpec; +import static com.hedera.services.bdd.spec.keys.ControlForKey.forKey; +import static com.hedera.services.bdd.spec.keys.KeyLabel.complex; import static com.hedera.services.bdd.spec.keys.KeyShape.listOf; +import static com.hedera.services.bdd.spec.keys.SigControl.ANY; +import static com.hedera.services.bdd.spec.keys.SigControl.OFF; +import static com.hedera.services.bdd.spec.keys.SigControl.ON; +import static com.hedera.services.bdd.spec.keys.SigControl.threshSigs; +import static com.hedera.services.bdd.spec.queries.QueryVerbs.getAccountRecords; import static com.hedera.services.bdd.spec.transactions.TxnVerbs.burnToken; +import static com.hedera.services.bdd.spec.transactions.TxnVerbs.createTopic; import static com.hedera.services.bdd.spec.transactions.TxnVerbs.cryptoCreate; import static com.hedera.services.bdd.spec.transactions.TxnVerbs.cryptoTransfer; import static com.hedera.services.bdd.spec.transactions.TxnVerbs.mintToken; +import static com.hedera.services.bdd.spec.transactions.TxnVerbs.submitMessageTo; import static com.hedera.services.bdd.spec.transactions.TxnVerbs.tokenAssociate; import static com.hedera.services.bdd.spec.transactions.TxnVerbs.tokenCreate; import static com.hedera.services.bdd.spec.transactions.TxnVerbs.tokenFreeze; import static com.hedera.services.bdd.spec.transactions.TxnVerbs.tokenUnfreeze; import static com.hedera.services.bdd.spec.transactions.TxnVerbs.wipeTokenAccount; +import static com.hedera.services.bdd.spec.transactions.crypto.HapiCryptoTransfer.tinyBarsFromTo; import static com.hedera.services.bdd.spec.transactions.token.TokenMovement.movingUnique; +import static com.hedera.services.bdd.spec.utilops.CustomSpecAssert.allRunFor; +import static com.hedera.services.bdd.spec.utilops.UtilVerbs.assertionsHold; import static com.hedera.services.bdd.spec.utilops.UtilVerbs.newKeyNamed; import static com.hedera.services.bdd.spec.utilops.UtilVerbs.validateChargedUsdWithin; +import static com.hedera.services.bdd.suites.HapiSuite.FUNDING; +import static com.hedera.services.bdd.suites.HapiSuite.GENESIS; +import static com.hedera.services.bdd.suites.HapiSuite.ONE_HBAR; +import static com.hedera.services.bdd.suites.HapiSuite.ONE_HUNDRED_HBARS; +import static com.hedera.services.bdd.suites.HapiSuite.ONE_MILLION_HBARS; +import static com.hedera.services.bdd.suites.HapiSuite.THREE_MONTHS_IN_SECONDS; +import static com.hedera.services.bdd.suites.HapiSuite.TOKEN_TREASURY; import static com.hedera.services.bdd.suites.utils.MiscEETUtils.metadata; +import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.INSUFFICIENT_TX_FEE; import static com.hederahashgraph.api.proto.java.TokenType.NON_FUNGIBLE_UNIQUE; +import static org.junit.jupiter.api.Assertions.assertEquals; import com.google.protobuf.ByteString; import com.hedera.services.bdd.junit.HapiTest; -import com.hedera.services.bdd.junit.HapiTestSuite; -import com.hedera.services.bdd.spec.HapiSpec; -import com.hedera.services.bdd.suites.HapiSuite; +import com.hedera.services.bdd.spec.keys.KeyLabel; +import com.hedera.services.bdd.spec.keys.SigControl; +import com.hedera.services.bdd.spec.queries.QueryVerbs; +import com.hedera.services.bdd.spec.transactions.TxnUtils; +import com.hederahashgraph.api.proto.java.AccountAmount; +import com.hederahashgraph.api.proto.java.AccountID; import com.hederahashgraph.api.proto.java.TokenSupplyType; +import com.hederahashgraph.api.proto.java.TransactionRecord; import java.time.Instant; import java.util.List; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; - -@HapiTestSuite -public class AllBaseOpFeesSuite extends HapiSuite { - private static final Logger log = LogManager.getLogger(AllBaseOpFeesSuite.class); +import java.util.stream.Stream; +import org.junit.jupiter.api.DynamicTest; +import org.junit.jupiter.api.Tag; +@Tag(TOKEN) +public class AllBaseOpFeesSuite { + private static final String PAYER = "payer"; private static final double ALLOWED_DIFFERENCE_PERCENTAGE = 0.01; private static final String TREASURE_KEY = "treasureKey"; @@ -73,24 +99,8 @@ public class AllBaseOpFeesSuite extends HapiSuite { private static final double EXPECTED_NFT_BURN_PRICE_USD = 0.001; private static final double EXPECTED_NFT_WIPE_PRICE_USD = 0.001; - public static void main(String... args) { - new AllBaseOpFeesSuite().runSuiteSync(); - } - - @Override - public List getSpecsInSuite() { - return allOf(List.of( - baseNftFreezeUnfreezeChargedAsExpected(), - baseCommonFreezeUnfreezeChargedAsExpected(), - baseNftMintOperationIsChargedExpectedFee(), - baseNftWipeOperationIsChargedExpectedFee(), - baseNftBurnOperationIsChargedExpectedFee(), - NftMintsScaleLinearlyBasedOnNumberOfSerialNumbers(), - NftMintsScaleLinearlyBasedOnNumberOfSignatures())); - } - @HapiTest - final HapiSpec baseNftMintOperationIsChargedExpectedFee() { + final Stream baseNftMintOperationIsChargedExpectedFee() { final var standard100ByteMetadata = ByteString.copyFromUtf8( "0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789"); @@ -113,7 +123,7 @@ final HapiSpec baseNftMintOperationIsChargedExpectedFee() { } @HapiTest - final HapiSpec NftMintsScaleLinearlyBasedOnNumberOfSerialNumbers() { + final Stream NftMintsScaleLinearlyBasedOnNumberOfSerialNumbers() { final var expectedFee = 10 * EXPECTED_NFT_MINT_PRICE_USD; final var standard100ByteMetadata = ByteString.copyFromUtf8( "0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789"); @@ -149,7 +159,7 @@ final HapiSpec NftMintsScaleLinearlyBasedOnNumberOfSerialNumbers() { } @HapiTest - final HapiSpec NftMintsScaleLinearlyBasedOnNumberOfSignatures() { + final Stream NftMintsScaleLinearlyBasedOnNumberOfSignatures() { final var numOfSigs = 10; final var extraSigPrice = 0.0006016996; final var expectedFee = EXPECTED_NFT_MINT_PRICE_USD + ((numOfSigs - 1) * extraSigPrice); @@ -175,7 +185,7 @@ final HapiSpec NftMintsScaleLinearlyBasedOnNumberOfSignatures() { } @HapiTest - final HapiSpec baseNftWipeOperationIsChargedExpectedFee() { + final Stream baseNftWipeOperationIsChargedExpectedFee() { return defaultHapiSpec("BaseUniqueWipeOperationIsChargedExpectedFee") .given( newKeyNamed(SUPPLY_KEY), @@ -201,7 +211,7 @@ final HapiSpec baseNftWipeOperationIsChargedExpectedFee() { } @HapiTest - final HapiSpec baseNftBurnOperationIsChargedExpectedFee() { + final Stream baseNftBurnOperationIsChargedExpectedFee() { return defaultHapiSpec("BaseUniqueBurnOperationIsChargedExpectedFee") .given( newKeyNamed(SUPPLY_KEY), @@ -222,7 +232,7 @@ final HapiSpec baseNftBurnOperationIsChargedExpectedFee() { } @HapiTest - final HapiSpec baseNftFreezeUnfreezeChargedAsExpected() { + final Stream baseNftFreezeUnfreezeChargedAsExpected() { return defaultHapiSpec("baseNftFreezeUnfreezeChargedAsExpected") .given( newKeyNamed(TREASURE_KEY), @@ -260,7 +270,7 @@ final HapiSpec baseNftFreezeUnfreezeChargedAsExpected() { } @HapiTest - final HapiSpec baseCommonFreezeUnfreezeChargedAsExpected() { + final Stream baseCommonFreezeUnfreezeChargedAsExpected() { return defaultHapiSpec("baseCommonFreezeUnfreezeChargedAsExpected") .given( newKeyNamed(TREASURE_KEY), @@ -295,8 +305,55 @@ final HapiSpec baseCommonFreezeUnfreezeChargedAsExpected() { validateChargedUsdWithin(UNFREEZE, EXPECTED_UNFREEZE_PRICE_USD, ALLOWED_DIFFERENCE_PERCENTAGE)); } - @Override - protected Logger getResultsLogger() { - return log; + @HapiTest + final Stream feeCalcUsesNumPayerKeys() { + SigControl SHAPE = threshSigs(2, threshSigs(2, ANY, ANY, ANY), threshSigs(2, ANY, ANY, ANY)); + KeyLabel ONE_UNIQUE_KEY = complex(complex("X", "X", "X"), complex("X", "X", "X")); + SigControl SIGN_ONCE = threshSigs(2, threshSigs(3, ON, OFF, OFF), threshSigs(3, OFF, OFF, OFF)); + + return defaultHapiSpec("PayerSigRedundancyRecognized") + .given( + newKeyNamed("repeatingKey").shape(SHAPE).labels(ONE_UNIQUE_KEY), + cryptoCreate("testAccount").key("repeatingKey").balance(1_000_000_000L)) + .when() + .then( + QueryVerbs.getAccountInfo("testAccount") + .sigControl(forKey("repeatingKey", SIGN_ONCE)) + .payingWith("testAccount") + .numPayerSigs(5) + .hasAnswerOnlyPrecheck(INSUFFICIENT_TX_FEE), + QueryVerbs.getAccountInfo("testAccount") + .sigControl(forKey("repeatingKey", SIGN_ONCE)) + .payingWith("testAccount") + .numPayerSigs(6)); + } + + @HapiTest + final Stream payerRecordCreationSanityChecks() { + return defaultHapiSpec("PayerRecordCreationSanityChecks") + .given(cryptoCreate(PAYER)) + .when( + createTopic("ofGeneralInterest").payingWith(PAYER), + cryptoTransfer(tinyBarsFromTo(GENESIS, FUNDING, 1_000L)).payingWith(PAYER), + submitMessageTo("ofGeneralInterest").message("I say!").payingWith(PAYER)) + .then(assertionsHold((spec, opLog) -> { + final var payerId = spec.registry().getAccountID(PAYER); + final var subOp = getAccountRecords(PAYER).logged(); + allRunFor(spec, subOp); + final var records = subOp.getResponse().getCryptoGetAccountRecords().getRecordsList().stream() + .filter(TxnUtils::isNotEndOfStakingPeriodRecord) + .toList(); + assertEquals(3, records.size()); + for (var record : records) { + assertEquals(record.getTransactionFee(), -netChangeIn(record, payerId)); + } + })); + } + + private long netChangeIn(TransactionRecord record, AccountID id) { + return record.getTransferList().getAccountAmountsList().stream() + .filter(aa -> id.equals(aa.getAccountID())) + .mapToLong(AccountAmount::getAmount) + .sum(); } } diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/fees/CongestionPricingSuite.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/fees/CongestionPricingSuite.java index 1912bf116325..7e58e30c7c01 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/fees/CongestionPricingSuite.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/fees/CongestionPricingSuite.java @@ -16,6 +16,8 @@ package com.hedera.services.bdd.suites.fees; +import static com.hedera.services.bdd.junit.ContextRequirement.PROPERTY_OVERRIDES; +import static com.hedera.services.bdd.junit.ContextRequirement.THROTTLE_OVERRIDES; import static com.hedera.services.bdd.spec.HapiSpec.propertyPreservingHapiSpec; import static com.hedera.services.bdd.spec.queries.QueryVerbs.*; import static com.hedera.services.bdd.spec.transactions.TxnVerbs.contractCall; @@ -26,26 +28,28 @@ import static com.hedera.services.bdd.spec.transactions.TxnVerbs.uncheckedSubmit; import static com.hedera.services.bdd.spec.transactions.TxnVerbs.uploadInitCode; import static com.hedera.services.bdd.spec.utilops.UtilVerbs.*; +import static com.hedera.services.bdd.suites.HapiSuite.EXCHANGE_RATE_CONTROL; +import static com.hedera.services.bdd.suites.HapiSuite.GENESIS; +import static com.hedera.services.bdd.suites.HapiSuite.ONE_HBAR; +import static com.hedera.services.bdd.suites.HapiSuite.ONE_HUNDRED_HBARS; +import static com.hedera.services.bdd.suites.HapiSuite.ONE_MILLION_HBARS; +import static com.hedera.services.bdd.suites.HapiSuite.THROTTLE_DEFS; import static com.hedera.services.bdd.suites.utils.sysfiles.serdes.ThrottleDefsLoader.protoDefsFromResource; -import com.hedera.services.bdd.junit.HapiTest; -import com.hedera.services.bdd.junit.HapiTestSuite; -import com.hedera.services.bdd.spec.HapiSpec; +import com.hedera.services.bdd.junit.LeakyHapiTest; import com.hedera.services.bdd.spec.HapiSpecOperation; import com.hedera.services.bdd.spec.HapiSpecSetup; import com.hedera.services.bdd.spec.transactions.crypto.HapiCryptoTransfer; -import com.hedera.services.bdd.suites.HapiSuite; import java.util.Arrays; -import java.util.List; -import java.util.Map; import java.util.concurrent.atomic.AtomicLong; import java.util.stream.IntStream; +import java.util.stream.Stream; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.DynamicTest; -@HapiTestSuite -public class CongestionPricingSuite extends HapiSuite { +public class CongestionPricingSuite { private static final Logger log = LogManager.getLogger(CongestionPricingSuite.class); private static final String FEES_PERCENT_CONGESTION_MULTIPLIERS = "fees.percentCongestionMultipliers"; @@ -59,17 +63,8 @@ public class CongestionPricingSuite extends HapiSuite { private static final String SECOND_ACCOUNT = "second"; private static final String FEE_MONITOR_ACCOUNT = "feeMonitor"; - public static void main(String... args) { - new CongestionPricingSuite().runSuiteSync(); - } - - @Override - public List getSpecsInSuite() { - return List.of(new HapiSpec[] {canUpdateMultipliersDynamically(), canUpdateMultipliersDynamically2()}); - } - - @HapiTest - final HapiSpec canUpdateMultipliersDynamically() { + @LeakyHapiTest({PROPERTY_OVERRIDES, THROTTLE_OVERRIDES}) + final Stream canUpdateMultipliersDynamically() { var artificialLimits = protoDefsFromResource("testSystemFiles/artificial-limits-congestion.json"); var defaultThrottles = protoDefsFromResource("testSystemFiles/throttles-dev.json"); var contract = "Multipurpose"; @@ -79,9 +74,8 @@ final HapiSpec canUpdateMultipliersDynamically() { AtomicLong sevenXPrice = new AtomicLong(); return propertyPreservingHapiSpec("CanUpdateMultipliersDynamically") - .preserving(ACTIVE_PROFILE_PROPERTY) + .preserving(FEES_PERCENT_CONGESTION_MULTIPLIERS, FEES_MIN_CONGESTION_PERIOD) .given( - overriding(ACTIVE_PROFILE_PROPERTY, "DEV"), cryptoCreate(CIVILIAN_ACCOUNT).payingWith(GENESIS).balance(ONE_MILLION_HBARS), uploadInitCode(contract), contractCreate(contract), @@ -97,14 +91,11 @@ final HapiSpec canUpdateMultipliersDynamically() { }) .logged()) .when( - fileUpdate(APP_PROPERTIES) - .fee(ONE_HUNDRED_HBARS) - .payingWith(EXCHANGE_RATE_CONTROL) - .overridingProps(Map.of( - FEES_PERCENT_CONGESTION_MULTIPLIERS, - "1,7x", - FEES_MIN_CONGESTION_PERIOD, - tmpMinCongestionPeriod)), + overridingTwo( + FEES_PERCENT_CONGESTION_MULTIPLIERS, + "1,7x", + FEES_MIN_CONGESTION_PERIOD, + tmpMinCongestionPeriod), fileUpdate(THROTTLE_DEFS) .payingWith(EXCHANGE_RATE_CONTROL) .contents(artificialLimits.toByteArray()), @@ -139,14 +130,6 @@ final HapiSpec canUpdateMultipliersDynamically() { .fee(ONE_HUNDRED_HBARS) .payingWith(EXCHANGE_RATE_CONTROL) .contents(defaultThrottles.toByteArray()), - fileUpdate(APP_PROPERTIES) - .fee(ONE_HUNDRED_HBARS) - .payingWith(EXCHANGE_RATE_CONTROL) - .overridingProps(Map.of( - FEES_PERCENT_CONGESTION_MULTIPLIERS, defaultCongestionMultipliers, - FEES_MIN_CONGESTION_PERIOD, defaultMinCongestionPeriod)), - cryptoTransfer(HapiCryptoTransfer.tinyBarsFromTo(GENESIS, FUNDING, 1)) - .payingWith(GENESIS), /* Check for error after resetting settings. */ withOpContext((spec, opLog) -> Assertions.assertEquals( @@ -156,8 +139,8 @@ final HapiSpec canUpdateMultipliersDynamically() { "~7x multiplier should be in affect!"))); } - @HapiTest - final HapiSpec canUpdateMultipliersDynamically2() { + @LeakyHapiTest({PROPERTY_OVERRIDES, THROTTLE_OVERRIDES}) + final Stream canUpdateMultipliersDynamically2() { var artificialLimits = protoDefsFromResource("testSystemFiles/artificial-limits-congestion.json"); var defaultThrottles = protoDefsFromResource("testSystemFiles/throttles-dev.json"); String tmpMinCongestionPeriod = "1"; @@ -166,9 +149,8 @@ final HapiSpec canUpdateMultipliersDynamically2() { AtomicLong sevenXPrice = new AtomicLong(); return propertyPreservingHapiSpec("CanUpdateMultipliersDynamically2") - .preserving(ACTIVE_PROFILE_PROPERTY) + .preserving(FEES_PERCENT_CONGESTION_MULTIPLIERS, FEES_MIN_CONGESTION_PERIOD) .given( - overriding(ACTIVE_PROFILE_PROPERTY, "DEV"), cryptoCreate(CIVILIAN_ACCOUNT).payingWith(GENESIS).balance(ONE_MILLION_HBARS), cryptoCreate(SECOND_ACCOUNT).payingWith(GENESIS).balance(ONE_HBAR), cryptoCreate(FEE_MONITOR_ACCOUNT).payingWith(GENESIS).balance(ONE_MILLION_HBARS), @@ -182,14 +164,11 @@ final HapiSpec canUpdateMultipliersDynamically2() { }) .logged()) .when( - fileUpdate(APP_PROPERTIES) - .fee(ONE_HUNDRED_HBARS) - .payingWith(EXCHANGE_RATE_CONTROL) - .overridingProps(Map.of( - FEES_PERCENT_CONGESTION_MULTIPLIERS, - "1,7x", - FEES_MIN_CONGESTION_PERIOD, - tmpMinCongestionPeriod)), + overridingTwo( + FEES_PERCENT_CONGESTION_MULTIPLIERS, + "1,7x", + FEES_MIN_CONGESTION_PERIOD, + tmpMinCongestionPeriod), fileUpdate(THROTTLE_DEFS) .payingWith(EXCHANGE_RATE_CONTROL) .contents(artificialLimits.toByteArray()), @@ -222,15 +201,6 @@ final HapiSpec canUpdateMultipliersDynamically2() { .fee(ONE_HUNDRED_HBARS) .payingWith(EXCHANGE_RATE_CONTROL) .contents(defaultThrottles.toByteArray()), - fileUpdate(APP_PROPERTIES) - .fee(ONE_HUNDRED_HBARS) - .payingWith(EXCHANGE_RATE_CONTROL) - .overridingProps(Map.of( - FEES_PERCENT_CONGESTION_MULTIPLIERS, defaultCongestionMultipliers, - FEES_MIN_CONGESTION_PERIOD, defaultMinCongestionPeriod)), - cryptoTransfer(HapiCryptoTransfer.tinyBarsFromTo(GENESIS, FUNDING, 1)) - .payingWith(GENESIS), - /* Check for error after resetting settings. */ withOpContext((spec, opLog) -> Assertions.assertEquals( 7.0, @@ -238,9 +208,4 @@ final HapiSpec canUpdateMultipliersDynamically2() { 0.1, "~7x multiplier should be in affect!"))); } - - @Override - protected Logger getResultsLogger() { - return log; - } } diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/fees/CostOfEverythingSuite.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/fees/CostOfEverythingSuite.java deleted file mode 100644 index 60e84d45c970..000000000000 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/fees/CostOfEverythingSuite.java +++ /dev/null @@ -1,284 +0,0 @@ -/* - * Copyright (C) 2020-2024 Hedera Hashgraph, LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.hedera.services.bdd.suites.fees; - -import static com.hedera.services.bdd.spec.HapiSpec.CostSnapshotMode; -import static com.hedera.services.bdd.spec.HapiSpec.CostSnapshotMode.TAKE; -import static com.hedera.services.bdd.spec.HapiSpec.customHapiSpec; -import static com.hedera.services.bdd.spec.assertions.AssertUtils.inOrder; -import static com.hedera.services.bdd.spec.assertions.ContractFnResultAsserts.isLiteralResult; -import static com.hedera.services.bdd.spec.assertions.ContractFnResultAsserts.resultWith; -import static com.hedera.services.bdd.spec.assertions.TransactionRecordAsserts.recordWith; -import static com.hedera.services.bdd.spec.keys.KeyShape.SIMPLE; -import static com.hedera.services.bdd.spec.keys.KeyShape.listOf; -import static com.hedera.services.bdd.spec.keys.KeyShape.threshOf; -import static com.hedera.services.bdd.spec.queries.QueryVerbs.contractCallLocal; -import static com.hedera.services.bdd.spec.queries.QueryVerbs.getAccountInfo; -import static com.hedera.services.bdd.spec.queries.QueryVerbs.getAccountRecords; -import static com.hedera.services.bdd.spec.queries.QueryVerbs.getTxnRecord; -import static com.hedera.services.bdd.spec.transactions.TxnVerbs.contractCall; -import static com.hedera.services.bdd.spec.transactions.TxnVerbs.contractCreate; -import static com.hedera.services.bdd.spec.transactions.TxnVerbs.cryptoCreate; -import static com.hedera.services.bdd.spec.transactions.TxnVerbs.cryptoTransfer; -import static com.hedera.services.bdd.spec.transactions.TxnVerbs.uploadInitCode; -import static com.hedera.services.bdd.spec.transactions.crypto.HapiCryptoTransfer.tinyBarsFromTo; -import static com.hedera.services.bdd.spec.utilops.UtilVerbs.newKeyNamed; -import static com.hedera.services.bdd.suites.contract.Utils.FunctionType.FUNCTION; -import static com.hedera.services.bdd.suites.contract.Utils.getABIFor; -import static java.util.stream.Collectors.toList; - -import com.hedera.services.bdd.junit.HapiTest; -import com.hedera.services.bdd.junit.HapiTestSuite; -import com.hedera.services.bdd.spec.HapiSpec; -import com.hedera.services.bdd.spec.keys.KeyShape; -import com.hedera.services.bdd.suites.HapiSuite; -import com.hederahashgraph.api.proto.java.AccountAmount; -import com.hederahashgraph.api.proto.java.TransferList; -import java.math.BigInteger; -import java.util.List; -import java.util.Map; -import java.util.stream.Stream; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; - -@HapiTestSuite -public class CostOfEverythingSuite extends HapiSuite { - private static final Logger log = LogManager.getLogger(CostOfEverythingSuite.class); - private static final String PAYING_SENDER = "payingSender"; - private static final String RECEIVER = "receiver"; - private static final String CANONICAL = "canonical"; - private static final String CIVILIAN = "civilian"; - private static final String HAIR_TRIGGER_PAYER = "hairTriggerPayer"; - private static final String COST_SNAPSHOT_MODE = "cost.snapshot.mode"; - - CostSnapshotMode costSnapshotMode = TAKE; - // CostSnapshotMode costSnapshotMode = COMPARE; - - public static void main(String... args) { - new CostOfEverythingSuite().runSuiteSync(); - } - - @Override - public List getSpecsInSuite() { - return Stream.of( - // cryptoCreatePaths(), - // cryptoTransferPaths(), - // cryptoGetAccountInfoPaths(), - // cryptoGetAccountRecordsPaths(), - // transactionGetRecordPaths(), - miscContractCreatesAndCalls()) - .map(Stream::of) - .reduce(Stream.empty(), Stream::concat) - .collect(toList()); - } - - HapiSpec[] transactionGetRecordPaths() { - return new HapiSpec[] { - txnGetCreateRecord(), txnGetSmallTransferRecord(), txnGetLargeTransferRecord(), - }; - } - - @HapiTest - HapiSpec miscContractCreatesAndCalls() { - // Note that contracts are prohibited to sending value to system - // accounts below 0.0.750 - Object[] donationArgs = new Object[] {800L, "Hey, Ma!"}; - final var multipurposeContract = "Multipurpose"; - final var lookupContract = "BalanceLookup"; - - return customHapiSpec("MiscContractCreatesAndCalls") - .withProperties(Map.of(COST_SNAPSHOT_MODE, costSnapshotMode.toString())) - .given( - cryptoCreate(CIVILIAN).balance(ONE_HUNDRED_HBARS), - uploadInitCode(multipurposeContract, lookupContract)) - .when( - contractCreate(multipurposeContract) - .payingWith(CIVILIAN) - .balance(652), - contractCreate(lookupContract).payingWith(CIVILIAN).balance(256)) - .then( - contractCall(multipurposeContract, "believeIn", 256L).payingWith(CIVILIAN), - contractCallLocal(multipurposeContract, "pick") - .payingWith(CIVILIAN) - .logged() - .has(resultWith() - .resultThruAbi( - getABIFor(FUNCTION, "pick", multipurposeContract), - isLiteralResult(new Object[] {256L}))), - contractCall(multipurposeContract, "donate", donationArgs) - .payingWith(CIVILIAN), - contractCallLocal(lookupContract, "lookup", spec -> new Object[] { - BigInteger.valueOf(spec.registry() - .getAccountID(CIVILIAN) - .getAccountNum()) - }) - .payingWith(CIVILIAN) - .logged()); - } - - @HapiTest - HapiSpec txnGetCreateRecord() { - return customHapiSpec("TxnGetCreateRecord") - .withProperties(Map.of(COST_SNAPSHOT_MODE, costSnapshotMode.toString())) - .given(cryptoCreate(HAIR_TRIGGER_PAYER).balance(99_999_999_999L).sendThreshold(1L)) - .when(cryptoCreate("somebodyElse") - .payingWith(HAIR_TRIGGER_PAYER) - .via("txn")) - .then(getTxnRecord("txn").logged()); - } - - @HapiTest - HapiSpec txnGetSmallTransferRecord() { - return customHapiSpec("TxnGetSmalTransferRecord") - .withProperties(Map.of(COST_SNAPSHOT_MODE, costSnapshotMode.toString())) - .given(cryptoCreate(HAIR_TRIGGER_PAYER).sendThreshold(1L)) - .when(cryptoTransfer(tinyBarsFromTo(GENESIS, FUNDING, 1L)) - .payingWith(HAIR_TRIGGER_PAYER) - .via("txn")) - .then(getTxnRecord("txn").logged()); - } - - @HapiTest - HapiSpec txnGetLargeTransferRecord() { - return customHapiSpec("TxnGetLargeTransferRecord") - .withProperties(Map.of(COST_SNAPSHOT_MODE, costSnapshotMode.toString())) - .given( - cryptoCreate(HAIR_TRIGGER_PAYER).sendThreshold(1L), - cryptoCreate("a"), - cryptoCreate("b"), - cryptoCreate("c"), - cryptoCreate("d")) - .when(cryptoTransfer(spec -> TransferList.newBuilder() - .addAccountAmounts(aa(spec, GENESIS, -4L)) - .addAccountAmounts(aa(spec, "a", 1L)) - .addAccountAmounts(aa(spec, "b", 1L)) - .addAccountAmounts(aa(spec, "c", 1L)) - .addAccountAmounts(aa(spec, "d", 1L)) - .build()) - .payingWith(HAIR_TRIGGER_PAYER) - .via("txn")) - .then(getTxnRecord("txn").logged()); - } - - private AccountAmount aa(HapiSpec spec, String id, long amount) { - return AccountAmount.newBuilder() - .setAmount(amount) - .setAccountID(spec.registry().getAccountID(id)) - .build(); - } - - HapiSpec[] cryptoGetAccountRecordsPaths() { - return new HapiSpec[] { - cryptoGetRecordsHappyPathS(), cryptoGetRecordsHappyPathM(), cryptoGetRecordsHappyPathL(), - }; - } - - @HapiTest - HapiSpec cryptoGetRecordsHappyPathS() { - return customHapiSpec("CryptoGetRecordsHappyPathS") - .withProperties(Map.of(COST_SNAPSHOT_MODE, costSnapshotMode.toString())) - .given(cryptoCreate(HAIR_TRIGGER_PAYER).sendThreshold(1L)) - .when(cryptoTransfer(tinyBarsFromTo(GENESIS, FUNDING, 1L)).payingWith(HAIR_TRIGGER_PAYER)) - .then(getAccountRecords(HAIR_TRIGGER_PAYER).has(inOrder(recordWith()))); - } - - @HapiTest - HapiSpec cryptoGetRecordsHappyPathM() { - return customHapiSpec("CryptoGetRecordsHappyPathM") - .withProperties(Map.of(COST_SNAPSHOT_MODE, costSnapshotMode.toString())) - .given(cryptoCreate(HAIR_TRIGGER_PAYER).sendThreshold(1L)) - .when( - cryptoTransfer(tinyBarsFromTo(GENESIS, FUNDING, 1L)).payingWith(HAIR_TRIGGER_PAYER), - cryptoTransfer(tinyBarsFromTo(GENESIS, FUNDING, 1L)).payingWith(HAIR_TRIGGER_PAYER)) - .then(getAccountRecords(HAIR_TRIGGER_PAYER).has(inOrder(recordWith(), recordWith()))); - } - - @HapiTest - HapiSpec cryptoGetRecordsHappyPathL() { - return customHapiSpec("CryptoGetRecordsHappyPathL") - .withProperties(Map.of(COST_SNAPSHOT_MODE, costSnapshotMode.toString())) - .given(cryptoCreate(HAIR_TRIGGER_PAYER).sendThreshold(1L)) - .when( - cryptoTransfer(tinyBarsFromTo(GENESIS, FUNDING, 1L)).payingWith(HAIR_TRIGGER_PAYER), - cryptoTransfer(tinyBarsFromTo(GENESIS, FUNDING, 1L)).payingWith(HAIR_TRIGGER_PAYER), - cryptoTransfer(tinyBarsFromTo(GENESIS, FUNDING, 1L)).payingWith(HAIR_TRIGGER_PAYER), - cryptoTransfer(tinyBarsFromTo(GENESIS, FUNDING, 1L)).payingWith(HAIR_TRIGGER_PAYER), - cryptoTransfer(tinyBarsFromTo(GENESIS, FUNDING, 1L)).payingWith(HAIR_TRIGGER_PAYER)) - .then(getAccountRecords(HAIR_TRIGGER_PAYER) - .has(inOrder(recordWith(), recordWith(), recordWith(), recordWith(), recordWith()))); - } - - HapiSpec[] cryptoGetAccountInfoPaths() { - return new HapiSpec[] {cryptoGetAccountInfoHappyPath()}; - } - - @HapiTest - HapiSpec cryptoGetAccountInfoHappyPath() { - KeyShape smallKey = threshOf(1, 3); - KeyShape midsizeKey = listOf(SIMPLE, listOf(2), threshOf(1, 2)); - KeyShape hugeKey = threshOf(4, SIMPLE, SIMPLE, listOf(4), listOf(3), listOf(2)); - - return customHapiSpec("CryptoGetAccountInfoHappyPath") - .withProperties(Map.of(COST_SNAPSHOT_MODE, costSnapshotMode.toString())) - .given( - newKeyNamed("smallKey").shape(smallKey), - newKeyNamed("midsizeKey").shape(midsizeKey), - newKeyNamed("hugeKey").shape(hugeKey)) - .when( - cryptoCreate("small").key("smallKey"), - cryptoCreate("midsize").key("midsizeKey"), - cryptoCreate("huge").key("hugeKey")) - .then(getAccountInfo("small"), getAccountInfo("midsize"), getAccountInfo("huge")); - } - - HapiSpec[] cryptoCreatePaths() { - return new HapiSpec[] { - cryptoCreateSimpleKey(), - }; - } - - @HapiTest - HapiSpec cryptoCreateSimpleKey() { - KeyShape shape = SIMPLE; - - return customHapiSpec("SuccessfulCryptoCreate") - .withProperties(Map.of(COST_SNAPSHOT_MODE, costSnapshotMode.toString())) - .given(newKeyNamed("key").shape(shape)) - .when() - .then(cryptoCreate("a").key("key")); - } - - HapiSpec[] cryptoTransferPaths() { - return new HapiSpec[] { - cryptoTransferGenesisToFunding(), - }; - } - - @HapiTest - HapiSpec cryptoTransferGenesisToFunding() { - return customHapiSpec("CryptoTransferGenesisToFunding") - .withProperties(Map.of(COST_SNAPSHOT_MODE, costSnapshotMode.toString())) - .given() - .when() - .then(cryptoTransfer(tinyBarsFromTo(GENESIS, FUNDING, 1_000L))); - } - - @Override - protected Logger getResultsLogger() { - return log; - } -} diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/fees/CreateAndUpdateOps.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/fees/CreateAndUpdateOps.java deleted file mode 100644 index 90f4eda9ec09..000000000000 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/fees/CreateAndUpdateOps.java +++ /dev/null @@ -1,213 +0,0 @@ -/* - * Copyright (C) 2021-2024 Hedera Hashgraph, LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.hedera.services.bdd.suites.fees; - -import static com.hedera.services.bdd.spec.HapiSpec.CostSnapshotMode; -import static com.hedera.services.bdd.spec.HapiSpec.CostSnapshotMode.TAKE; -import static com.hedera.services.bdd.spec.HapiSpec.customHapiSpec; -import static com.hedera.services.bdd.spec.keys.KeyShape.SIMPLE; -import static com.hedera.services.bdd.spec.keys.KeyShape.listOf; -import static com.hedera.services.bdd.spec.queries.QueryVerbs.getAccountInfo; -import static com.hedera.services.bdd.spec.queries.QueryVerbs.getFileInfo; -import static com.hedera.services.bdd.spec.queries.QueryVerbs.getTxnRecord; -import static com.hedera.services.bdd.spec.transactions.TxnVerbs.cryptoCreate; -import static com.hedera.services.bdd.spec.transactions.TxnVerbs.cryptoUpdate; -import static com.hedera.services.bdd.spec.transactions.TxnVerbs.fileCreate; -import static com.hedera.services.bdd.spec.transactions.TxnVerbs.fileUpdate; -import static com.hedera.services.bdd.spec.utilops.CustomSpecAssert.allRunFor; -import static com.hedera.services.bdd.spec.utilops.UtilVerbs.newKeyNamed; -import static com.hedera.services.bdd.spec.utilops.UtilVerbs.sourcing; -import static com.hedera.services.bdd.spec.utilops.UtilVerbs.withOpContext; - -import com.hedera.services.bdd.spec.HapiSpec; -import com.hedera.services.bdd.spec.keys.KeyShape; -import com.hedera.services.bdd.spec.transactions.TxnUtils; -import com.hedera.services.bdd.suites.HapiSuite; -import java.util.List; -import java.util.Map; -import java.util.concurrent.atomic.AtomicLong; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; - -public class CreateAndUpdateOps extends HapiSuite { - private static final Logger log = LogManager.getLogger(CreateAndUpdateOps.class); - - long feeToOffer = ONE_HUNDRED_HBARS; - long paymentToOffer = ONE_HBAR; - long payerBalance = feeToOffer * 1000; - CostSnapshotMode costSnapshotMode = TAKE; - // CostSnapshotMode costSnapshotMode = COMPARE; - - public static void main(String... args) { - new CreateAndUpdateOps().runSuiteSync(); - } - - @Override - public List getSpecsInSuite() { - return List.of(new HapiSpec[] { - // variousCryptoMutations(), - variousFileMutations(), - }); - } - - HapiSpec variousFileMutations() { - KeyShape smallWacl = listOf(1); - KeyShape largeWacl = listOf(3); - byte[] smallContents = TxnUtils.randomUtf8Bytes(1_024); - byte[] mediumContents = TxnUtils.randomUtf8Bytes(2_048); - byte[] largeContents = TxnUtils.randomUtf8Bytes(4_096); - long shortExpiry = 100_000L; - long mediumExpiry = 10 * shortExpiry; - long eternalExpiry = 10 * mediumExpiry; - AtomicLong consensusNow = new AtomicLong(); - - return customHapiSpec("VariousFileMutations") - .withProperties(Map.of("cost.snapshot.mode", costSnapshotMode.toString())) - .given( - newKeyNamed("sk").shape(smallWacl), - newKeyNamed("lk").shape(largeWacl), - cryptoCreate("payer") - .via("payerCreation") - .fee(feeToOffer) - .balance(payerBalance), - withOpContext((spec, opLog) -> { - var lookup = getTxnRecord("payerCreation").nodePayment(paymentToOffer); - allRunFor(spec, lookup); - var record = lookup.getResponseRecord(); - consensusNow.set(record.getConsensusTimestamp().getSeconds()); - })) - .when( - sourcing(() -> fileCreate("sksc") - .fee(feeToOffer) - .payingWith("payer") - .key("sk") - .contents(smallContents) - .expiry(consensusNow.get() + shortExpiry)), - sourcing(() -> fileCreate("skmc") - .fee(feeToOffer) - .payingWith("payer") - .key("sk") - .contents(mediumContents) - .expiry(consensusNow.get() + mediumExpiry)), - sourcing(() -> fileCreate("sklc") - .fee(feeToOffer) - .payingWith("payer") - .key("sk") - .contents(largeContents) - .expiry(consensusNow.get() + eternalExpiry))) - .then( - fileUpdate("sksc") - .fee(feeToOffer) - .payingWith("payer") - .wacl("lk") - .extendingExpiryBy(mediumExpiry - shortExpiry), - fileUpdate("skmc") - .fee(feeToOffer) - .payingWith("payer") - .wacl("lk") - .extendingExpiryBy(eternalExpiry - mediumExpiry), - getFileInfo("sksc"), - getFileInfo("skmc"), - getFileInfo("sklc")); - } - - HapiSpec variousCryptoMutations() { - KeyShape smallKey = SIMPLE; - KeyShape largeKey = listOf(3); - long shortExpiry = 100_000L; - long mediumExpiry = 10 * shortExpiry; - long eternalExpiry = 10 * mediumExpiry; - AtomicLong consensusNow = new AtomicLong(); - - return customHapiSpec("VariousCryptoMutations") - .withProperties(Map.of("cost.snapshot.mode", costSnapshotMode.toString())) - .given( - newKeyNamed("sk").shape(smallKey), - newKeyNamed("lk").shape(largeKey), - cryptoCreate("payer") - .via("payerCreation") - .fee(feeToOffer) - .balance(payerBalance), - withOpContext((spec, opLog) -> { - var lookup = getTxnRecord("payerCreation").nodePayment(paymentToOffer); - allRunFor(spec, lookup); - var record = lookup.getResponseRecord(); - consensusNow.set(record.getConsensusTimestamp().getSeconds()); - }), - cryptoCreate("proxy").fee(feeToOffer)) - .when( - cryptoCreate("sksenp") - .fee(feeToOffer) - .payingWith("payer") - .key("sk") - .autoRenewSecs(shortExpiry), - cryptoCreate("sksep") - .fee(feeToOffer) - .payingWith("payer") - .proxy("0.0.2") - .key("sk") - .autoRenewSecs(shortExpiry), - cryptoCreate("skmenp") - .fee(feeToOffer) - .payingWith("payer") - .key("sk") - .autoRenewSecs(mediumExpiry), - cryptoCreate("skmep") - .fee(feeToOffer) - .payingWith("payer") - .proxy("0.0.2") - .key("sk") - .autoRenewSecs(mediumExpiry), - cryptoCreate("skeenp") - .fee(feeToOffer) - .payingWith("payer") - .key("sk") - .autoRenewSecs(eternalExpiry), - cryptoCreate("skeep") - .fee(feeToOffer) - .payingWith("payer") - .proxy("0.0.2") - .key("sk") - .autoRenewSecs(eternalExpiry)) - .then( - sourcing(() -> cryptoUpdate("sksenp") - .fee(feeToOffer) - .payingWith("payer") - .newProxy("proxy") - .key("lk") - .expiring(consensusNow.get() + mediumExpiry)), - sourcing(() -> cryptoUpdate("skmenp") - .fee(feeToOffer) - .payingWith("payer") - .newProxy("proxy") - .key("lk") - .expiring(consensusNow.get() + eternalExpiry)), - sourcing(() -> cryptoUpdate("skeenp") - .fee(feeToOffer) - .payingWith("payer") - .newProxy("proxy") - .key("lk")), - getAccountInfo("sksenp"), - getAccountInfo("skmenp"), - getAccountInfo("skeenp")); - } - - @Override - protected Logger getResultsLogger() { - return log; - } -} diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/fees/SpecialAccountsAreExempted.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/fees/FeeScheduleUpdateWaiverTest.java similarity index 77% rename from hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/fees/SpecialAccountsAreExempted.java rename to hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/fees/FeeScheduleUpdateWaiverTest.java index d37c7164aefb..0f2f911623d4 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/fees/SpecialAccountsAreExempted.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/fees/FeeScheduleUpdateWaiverTest.java @@ -25,41 +25,22 @@ import static com.hedera.services.bdd.spec.transactions.TxnVerbs.fileUpdate; import static com.hedera.services.bdd.spec.transactions.crypto.HapiCryptoTransfer.tinyBarsFromTo; import static com.hedera.services.bdd.spec.utilops.UtilVerbs.balanceSnapshot; +import static com.hedera.services.bdd.suites.HapiSuite.FEE_SCHEDULE; +import static com.hedera.services.bdd.suites.HapiSuite.FEE_SCHEDULE_CONTROL; +import static com.hedera.services.bdd.suites.HapiSuite.GENESIS; import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.FEE_SCHEDULE_FILE_PART_UPLOADED; import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.SUCCESS; -import com.hedera.services.bdd.junit.HapiTest; -import com.hedera.services.bdd.junit.HapiTestSuite; -import com.hedera.services.bdd.spec.HapiSpec; -import com.hedera.services.bdd.spec.annotations.LeakyFeeSchedule; -import com.hedera.services.bdd.suites.HapiSuite; +import com.hedera.services.bdd.junit.ContextRequirement; +import com.hedera.services.bdd.junit.LeakyHapiTest; import com.hederahashgraph.api.proto.java.ResponseCodeEnum; import java.nio.file.Path; -import java.util.List; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; +import java.util.stream.Stream; +import org.junit.jupiter.api.DynamicTest; -@HapiTestSuite -public class SpecialAccountsAreExempted extends HapiSuite { - private static final Logger log = LogManager.getLogger(SpecialAccountsAreExempted.class); - - public static void main(String... args) { - new SpecialAccountsAreExempted().runSuiteAsync(); - } - - @Override - public boolean canRunConcurrent() { - return false; - } - - @Override - public List getSpecsInSuite() { - return List.of(feeScheduleControlAccountIsntCharged()); - } - - @LeakyFeeSchedule - @HapiTest - final HapiSpec feeScheduleControlAccountIsntCharged() { +public class FeeScheduleUpdateWaiverTest { + @LeakyHapiTest(ContextRequirement.NO_CONCURRENT_CREATIONS) + final Stream feeScheduleControlAccountIsntCharged() { ResponseCodeEnum[] acceptable = {SUCCESS, FEE_SCHEDULE_FILE_PART_UPLOADED}; return defaultHapiSpec("FeeScheduleControlAccountIsntCharged") @@ -90,9 +71,4 @@ final HapiSpec feeScheduleControlAccountIsntCharged() { .path(Path.of("./", "part4-feeSchedule.bin").toString())) .then(getAccountBalance(FEE_SCHEDULE_CONTROL).hasTinyBars(changeFromSnapshot("pre", 0))); } - - @Override - protected Logger getResultsLogger() { - return log; - } } diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/fees/OverlappingKeysSuite.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/fees/OverlappingKeysSuite.java deleted file mode 100644 index a60bf026dfa3..000000000000 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/fees/OverlappingKeysSuite.java +++ /dev/null @@ -1,90 +0,0 @@ -/* - * Copyright (C) 2020-2024 Hedera Hashgraph, LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.hedera.services.bdd.suites.fees; - -import static com.hedera.services.bdd.spec.HapiSpec.defaultHapiSpec; -import static com.hedera.services.bdd.spec.keys.ControlForKey.forKey; -import static com.hedera.services.bdd.spec.keys.KeyLabel.complex; -import static com.hedera.services.bdd.spec.keys.SigControl.ANY; -import static com.hedera.services.bdd.spec.keys.SigControl.OFF; -import static com.hedera.services.bdd.spec.keys.SigControl.ON; -import static com.hedera.services.bdd.spec.keys.SigControl.threshSigs; -import static com.hedera.services.bdd.spec.transactions.TxnVerbs.cryptoCreate; -import static com.hedera.services.bdd.spec.utilops.UtilVerbs.newKeyNamed; -import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.INSUFFICIENT_TX_FEE; - -import com.hedera.services.bdd.junit.HapiTest; -import com.hedera.services.bdd.junit.HapiTestSuite; -import com.hedera.services.bdd.spec.HapiSpec; -import com.hedera.services.bdd.spec.keys.KeyLabel; -import com.hedera.services.bdd.spec.keys.SigControl; -import com.hedera.services.bdd.spec.queries.QueryVerbs; -import com.hedera.services.bdd.suites.HapiSuite; -import java.util.Arrays; -import java.util.List; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; - -@HapiTestSuite -public class OverlappingKeysSuite extends HapiSuite { - private static final Logger log = LogManager.getLogger(OverlappingKeysSuite.class); - - public static void main(String... args) { - new OverlappingKeysSuite().runSuiteAsync(); - } - - @Override - public List getSpecsInSuite() { - return allOf(positiveTests(), negativeTests()); - } - - private List positiveTests() { - return Arrays.asList(feeCalcUsesNumPayerKeys()); - } - - private List negativeTests() { - return Arrays.asList(); - } - - @HapiTest - final HapiSpec feeCalcUsesNumPayerKeys() { - SigControl SHAPE = threshSigs(2, threshSigs(2, ANY, ANY, ANY), threshSigs(2, ANY, ANY, ANY)); - KeyLabel ONE_UNIQUE_KEY = complex(complex("X", "X", "X"), complex("X", "X", "X")); - SigControl SIGN_ONCE = threshSigs(2, threshSigs(3, ON, OFF, OFF), threshSigs(3, OFF, OFF, OFF)); - - return defaultHapiSpec("PayerSigRedundancyRecognized") - .given( - newKeyNamed("repeatingKey").shape(SHAPE).labels(ONE_UNIQUE_KEY), - cryptoCreate("testAccount").key("repeatingKey").balance(1_000_000_000L)) - .when() - .then( - QueryVerbs.getAccountInfo("testAccount") - .sigControl(forKey("repeatingKey", SIGN_ONCE)) - .payingWith("testAccount") - .numPayerSigs(5) - .hasAnswerOnlyPrecheck(INSUFFICIENT_TX_FEE), - QueryVerbs.getAccountInfo("testAccount") - .sigControl(forKey("repeatingKey", SIGN_ONCE)) - .payingWith("testAccount") - .numPayerSigs(6)); - } - - @Override - protected Logger getResultsLogger() { - return log; - } -} diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/fees/QueryPaymentExploitsSuite.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/fees/QueryPaymentExploitsSuite.java deleted file mode 100644 index 3d0e3517efcb..000000000000 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/fees/QueryPaymentExploitsSuite.java +++ /dev/null @@ -1,58 +0,0 @@ -/* - * Copyright (C) 2020-2024 Hedera Hashgraph, LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.hedera.services.bdd.suites.fees; - -import static com.hedera.services.bdd.spec.HapiSpec.defaultHapiSpec; -import static com.hedera.services.bdd.spec.queries.QueryVerbs.getAccountInfo; -import static com.hedera.services.bdd.spec.transactions.TxnVerbs.cryptoCreate; - -import com.hedera.services.bdd.spec.HapiSpec; -import com.hedera.services.bdd.suites.HapiSuite; -import java.util.List; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; - -public class QueryPaymentExploitsSuite extends HapiSuite { - private static final Logger log = LogManager.getLogger(QueryPaymentExploitsSuite.class); - - public static void main(String... args) { - new QueryPaymentExploitsSuite().runSuiteSync(); - } - - @Override - public List getSpecsInSuite() { - return List.of( - // clientCanRecycleNodePaymentTxn(), - clientCanUseEmptyTxnForCostAnswer()); - } - - public static HapiSpec clientCanUseEmptyTxnForCostAnswer() { - return defaultHapiSpec("ClientCanUseDefaultPaymentForCostAnswer") - .given(cryptoCreate("misc")) - .when() - .then(getAccountInfo("misc").useEmptyTxnAsCostPayment()); - } - - public static HapiSpec clientCanRecycleNodePaymentTxn() { - return defaultHapiSpec("ClientCanRecycleNodePaymentTxn").given().when().then(); - } - - @Override - protected Logger getResultsLogger() { - return log; - } -} diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/file/DiverseStateCreation.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/file/DiverseStateCreation.java index 851011ddccc0..e496296ca17e 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/file/DiverseStateCreation.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/file/DiverseStateCreation.java @@ -34,7 +34,6 @@ import com.fasterxml.jackson.databind.ObjectMapper; import com.google.protobuf.ByteString; -import com.hedera.services.bdd.spec.HapiSpec; import com.hedera.services.bdd.spec.keys.KeyShape; import com.hedera.services.bdd.suites.HapiSuite; import com.swirlds.common.utility.CommonUtils; @@ -46,8 +45,10 @@ import java.util.List; import java.util.Map; import java.util.OptionalLong; +import java.util.stream.Stream; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; +import org.junit.jupiter.api.DynamicTest; /** * Client that creates a state with at least one of every type of blob; and a bit of diversity @@ -98,7 +99,7 @@ public static void main(String... args) throws IOException { } @Override - public List getSpecsInSuite() { + public List> getSpecsInSuite() { try { SMALL_CONTENTS = Files.newInputStream(Paths.get(SMALL_CONTENTS_LOC)).readAllBytes(); MEDIUM_CONTENTS = @@ -111,7 +112,7 @@ public List getSpecsInSuite() { return List.of(createDiverseState()); } - final HapiSpec createDiverseState() { + final Stream createDiverseState() { final KeyShape SMALL_SHAPE = listOf(threshOf(1, 3)); final KeyShape MEDIUM_SHAPE = listOf(SIMPLE, threshOf(2, 3)); final KeyShape LARGE_SHAPE = listOf( diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/file/DiverseStateValidation.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/file/DiverseStateValidation.java index b02dff1e7f1d..837cf805462b 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/file/DiverseStateValidation.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/file/DiverseStateValidation.java @@ -46,7 +46,6 @@ import static com.hedera.services.bdd.suites.file.DiverseStateCreation.STATE_META_JSON_LOC; import com.fasterxml.jackson.databind.ObjectMapper; -import com.hedera.services.bdd.spec.HapiSpec; import com.hedera.services.bdd.suites.HapiSuite; import com.swirlds.common.utility.CommonUtils; import java.io.IOException; @@ -57,8 +56,10 @@ import java.util.List; import java.util.Map; import java.util.concurrent.atomic.AtomicReference; +import java.util.stream.Stream; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; +import org.junit.jupiter.api.DynamicTest; /** * Client that validates the blobs mentioned in a JSON metadata file created by {@link @@ -79,7 +80,7 @@ public static void main(String... args) throws IOException { private final AtomicReference> hexedBytecode = new AtomicReference<>(); @Override - public List getSpecsInSuite() { + public List> getSpecsInSuite() { try { SMALL_CONTENTS = Files.newInputStream(Paths.get(SMALL_CONTENTS_LOC)).readAllBytes(); LARGE_CONTENTS = Files.newInputStream(Paths.get(LARGE_CONTENTS_LOC)).readAllBytes(); @@ -91,7 +92,7 @@ public List getSpecsInSuite() { } @SuppressWarnings("unchecked") - final HapiSpec validateDiverseState() { + final Stream validateDiverseState() { return defaultHapiSpec("ValidateDiverseState") .given(withOpContext((spec, opLog) -> { final var om = new ObjectMapper(); diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/file/ExchangeRateControlSuite.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/file/ExchangeRateControlSuite.java index bd7721e03888..c017a17a88dc 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/file/ExchangeRateControlSuite.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/file/ExchangeRateControlSuite.java @@ -22,49 +22,32 @@ import static com.hedera.services.bdd.spec.transactions.TxnVerbs.cryptoTransfer; import static com.hedera.services.bdd.spec.transactions.TxnVerbs.fileUpdate; import static com.hedera.services.bdd.spec.transactions.crypto.HapiCryptoTransfer.tinyBarsFromTo; +import static com.hedera.services.bdd.suites.HapiSuite.ADEQUATE_FUNDS; +import static com.hedera.services.bdd.suites.HapiSuite.EXCHANGE_RATES; +import static com.hedera.services.bdd.suites.HapiSuite.EXCHANGE_RATE_CONTROL; +import static com.hedera.services.bdd.suites.HapiSuite.GENESIS; +import static com.hedera.services.bdd.suites.HapiSuite.ONE_HUNDRED_HBARS; +import static com.hedera.services.bdd.suites.HapiSuite.SYSTEM_ADMIN; import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.AUTHORIZATION_FAILED; import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.EXCHANGE_RATE_CHANGE_LIMIT_EXCEEDED; import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.SUCCESS; import com.google.protobuf.ByteString; import com.hedera.services.bdd.junit.HapiTest; -import com.hedera.services.bdd.junit.HapiTestSuite; -import com.hedera.services.bdd.spec.HapiSpec; +import com.hedera.services.bdd.junit.OrderedInIsolation; import com.hedera.services.bdd.spec.transactions.file.HapiFileUpdate; -import com.hedera.services.bdd.suites.HapiSuite; -import java.util.Arrays; -import java.util.List; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; - -@HapiTestSuite -public class ExchangeRateControlSuite extends HapiSuite { - private static final Logger log = LogManager.getLogger(ExchangeRateControlSuite.class); - - public static void main(String... args) { - new ExchangeRateControlSuite().runSuiteSync(); - } - - @Override - public List getSpecsInSuite() { - return allOf(positiveTests(), negativeTests()); - } - - private List positiveTests() { - return Arrays.asList(midnightRateChangesWhenAcct50UpdatesFile112(), acct57CanMakeSmallChanges()); - } - - private List negativeTests() { - return Arrays.asList(anonCantUpdateRates(), acct57CantMakeLargeChanges()); - } +import java.util.stream.Stream; +import org.junit.jupiter.api.DynamicTest; +@OrderedInIsolation +public class ExchangeRateControlSuite { final HapiFileUpdate resetRatesOp = fileUpdate(EXCHANGE_RATES) .payingWith(EXCHANGE_RATE_CONTROL) .fee(ADEQUATE_FUNDS) .contents(spec -> spec.ratesProvider().rateSetWith(1, 12).toByteString()); @HapiTest - final HapiSpec acct57CanMakeSmallChanges() { + final Stream acct57CanMakeSmallChanges() { return defaultHapiSpec("Acct57CanMakeSmallChanges") .given( resetRatesOp, @@ -85,7 +68,7 @@ final HapiSpec acct57CanMakeSmallChanges() { } @HapiTest - final HapiSpec midnightRateChangesWhenAcct50UpdatesFile112() { + final Stream midnightRateChangesWhenAcct50UpdatesFile112() { return defaultHapiSpec("MidnightRateChangesWhenAcct50UpdatesFile112") .given( resetRatesOp, @@ -139,7 +122,7 @@ final HapiSpec midnightRateChangesWhenAcct50UpdatesFile112() { } @HapiTest - final HapiSpec anonCantUpdateRates() { + final Stream anonCantUpdateRates() { return defaultHapiSpec("AnonCantUpdateRates") .given(resetRatesOp, cryptoCreate("randomAccount")) .when() @@ -150,7 +133,7 @@ final HapiSpec anonCantUpdateRates() { } @HapiTest - final HapiSpec acct57CantMakeLargeChanges() { + final Stream acct57CantMakeLargeChanges() { return defaultHapiSpec("Acct57CantMakeLargeChanges") .given( resetRatesOp, @@ -163,9 +146,4 @@ final HapiSpec acct57CantMakeLargeChanges() { .payingWith(EXCHANGE_RATE_CONTROL) .hasKnownStatus(EXCHANGE_RATE_CHANGE_LIMIT_EXCEEDED)); } - - @Override - protected Logger getResultsLogger() { - return log; - } } diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/file/FetchSystemFiles.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/file/FetchSystemFiles.java deleted file mode 100644 index a80ca0d5e3cc..000000000000 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/file/FetchSystemFiles.java +++ /dev/null @@ -1,114 +0,0 @@ -/* - * Copyright (C) 2020-2024 Hedera Hashgraph, LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.hedera.services.bdd.suites.file; - -import static com.hedera.services.bdd.spec.HapiSpec.customHapiSpec; -import static com.hedera.services.bdd.spec.queries.QueryVerbs.getFileContents; - -import com.hedera.services.bdd.junit.HapiTest; -import com.hedera.services.bdd.junit.HapiTestSuite; -import com.hedera.services.bdd.spec.HapiSpec; -import com.hedera.services.bdd.suites.HapiSuite; -import com.hederahashgraph.api.proto.java.CurrentAndNextFeeSchedule; -import com.hederahashgraph.api.proto.java.ExchangeRateSet; -import com.hederahashgraph.api.proto.java.NodeAddressBook; -import com.hederahashgraph.api.proto.java.ServicesConfigurationList; -import java.nio.file.Path; -import java.util.List; -import java.util.Map; -import java.util.function.Function; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; - -@HapiTestSuite -public class FetchSystemFiles extends HapiSuite { - private static final Logger log = LogManager.getLogger(FetchSystemFiles.class); - - public static void main(String... args) { - new FetchSystemFiles().runSuiteSync(); - } - - private static final String TARGET_DIR = "./remote-system-files"; - - @Override - public List getSpecsInSuite() { - return List.of(fetchFiles()); - } - - /** Fetches the system files from a running network and saves them to the local file system. */ - @HapiTest - final HapiSpec fetchFiles() { - return customHapiSpec("FetchFiles") - .withProperties(Map.of( - "fees.useFixedOffer", "true", - "fees.fixedOffer", "100000000")) - .given() - .when() - .then( - getFileContents(NODE_DETAILS) - .saveTo(path("nodeDetails.bin")) - .saveReadableTo(unchecked(NodeAddressBook::parseFrom), path("nodeDetails.txt")), - getFileContents(ADDRESS_BOOK) - .saveTo(path("addressBook.bin")) - .saveReadableTo(unchecked(NodeAddressBook::parseFrom), path("addressBook.txt")), - getFileContents(NODE_DETAILS) - .saveTo(path("nodeDetails.bin")) - .saveReadableTo(unchecked(NodeAddressBook::parseFrom), path("nodeDetails.txt")), - getFileContents(EXCHANGE_RATES) - .saveTo(path("exchangeRates.bin")) - .saveReadableTo(unchecked(ExchangeRateSet::parseFrom), path("exchangeRates.txt")), - getFileContents(APP_PROPERTIES) - .saveTo(path("appProperties.bin")) - .saveReadableTo( - unchecked(ServicesConfigurationList::parseFrom), path("appProperties.txt")), - getFileContents(API_PERMISSIONS) - .saveTo(path("apiPermissions.bin")) - .saveReadableTo( - unchecked(ServicesConfigurationList::parseFrom), path("appPermissions.txt")), - getFileContents(FEE_SCHEDULE) - .saveTo(path("feeSchedule.bin")) - .fee(300_000L) - .nodePayment(40L) - .saveReadableTo( - unchecked(CurrentAndNextFeeSchedule::parseFrom), path("feeSchedule.txt"))); - } - - @FunctionalInterface - public interface CheckedParser { - Object parseFrom(byte[] bytes) throws Exception; - } - - static Function unchecked(CheckedParser parser) { - return bytes -> { - try { - return parser.parseFrom(bytes).toString(); - } catch (Exception e) { - e.printStackTrace(); - return " due to " + e.getMessage() + "!"; - } - }; - } - - private String path(String file) { - return Path.of(TARGET_DIR, file).toString(); - } - - @Override - protected Logger getResultsLogger() { - return log; - } -} diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/file/FileAppendSuite.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/file/FileAppendSuite.java index d66f2b83f34f..14744f62b492 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/file/FileAppendSuite.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/file/FileAppendSuite.java @@ -16,7 +16,9 @@ package com.hedera.services.bdd.suites.file; +import static com.hedera.services.bdd.junit.ContextRequirement.PROPERTY_OVERRIDES; import static com.hedera.services.bdd.spec.HapiSpec.defaultHapiSpec; +import static com.hedera.services.bdd.spec.HapiSpec.propertyPreservingHapiSpec; import static com.hedera.services.bdd.spec.queries.QueryVerbs.getFileContents; import static com.hedera.services.bdd.spec.queries.QueryVerbs.getFileInfo; import static com.hedera.services.bdd.spec.transactions.TxnVerbs.cryptoCreate; @@ -24,41 +26,27 @@ import static com.hedera.services.bdd.spec.transactions.TxnVerbs.fileCreate; import static com.hedera.services.bdd.spec.utilops.UtilVerbs.newKeyListNamed; import static com.hedera.services.bdd.spec.utilops.UtilVerbs.newKeyNamed; +import static com.hedera.services.bdd.spec.utilops.UtilVerbs.overriding; import static com.hedera.services.bdd.spec.utilops.UtilVerbs.sendModified; import static com.hedera.services.bdd.spec.utilops.UtilVerbs.submitModified; import static com.hedera.services.bdd.spec.utilops.UtilVerbs.validateChargedUsdWithin; import static com.hedera.services.bdd.spec.utilops.mod.ModificationUtils.withSuccessivelyVariedBodyIds; import static com.hedera.services.bdd.spec.utilops.mod.ModificationUtils.withSuccessivelyVariedQueryIds; +import static com.hedera.services.bdd.suites.HapiSuite.ONE_HUNDRED_HBARS; +import static com.hedera.services.bdd.suites.HapiSuite.THREE_MONTHS_IN_SECONDS; +import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.MAX_FILE_SIZE_EXCEEDED; import com.hedera.services.bdd.junit.HapiTest; -import com.hedera.services.bdd.junit.HapiTestSuite; -import com.hedera.services.bdd.spec.HapiSpec; -import com.hedera.services.bdd.suites.HapiSuite; +import com.hedera.services.bdd.junit.LeakyHapiTest; +import java.util.Arrays; import java.util.List; import java.util.UUID; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; - -@HapiTestSuite -public class FileAppendSuite extends HapiSuite { - private static final Logger log = LogManager.getLogger(FileAppendSuite.class); - - public static void main(String... args) { - new FileAppendSuite().runSuiteAsync(); - } - - @Override - public List getSpecsInSuite() { - return List.of(vanillaAppendSucceeds(), baseOpsHaveExpectedPrices()); - } - - @Override - public boolean canRunConcurrent() { - return true; - } +import java.util.stream.Stream; +import org.junit.jupiter.api.DynamicTest; +public class FileAppendSuite { @HapiTest - public HapiSpec appendIdVariantsTreatedAsExpected() { + final Stream appendIdVariantsTreatedAsExpected() { return defaultHapiSpec("idVariantsTreatedAsExpected") .given(fileCreate("file").contents("ABC")) .when() @@ -67,7 +55,7 @@ public HapiSpec appendIdVariantsTreatedAsExpected() { } @HapiTest - public HapiSpec getContentsIdVariantsTreatedAsExpected() { + final Stream getContentsIdVariantsTreatedAsExpected() { return defaultHapiSpec("getContentsIdVariantsTreatedAsExpected") .given(fileCreate("file").contents("ABC")) .when() @@ -75,7 +63,7 @@ public HapiSpec getContentsIdVariantsTreatedAsExpected() { } @HapiTest - public HapiSpec getInfoIdVariantsTreatedAsExpected() { + final Stream getInfoIdVariantsTreatedAsExpected() { return defaultHapiSpec("getInfoIdVariantsTreatedAsExpected") .given(fileCreate("file").contents("ABC")) .when() @@ -83,7 +71,7 @@ public HapiSpec getInfoIdVariantsTreatedAsExpected() { } @HapiTest - public HapiSpec baseOpsHaveExpectedPrices() { + final Stream baseOpsHaveExpectedPrices() { final var civilian = "NonExemptPayer"; final var expectedAppendFeesPriceUsd = 0.05; @@ -116,7 +104,7 @@ public HapiSpec baseOpsHaveExpectedPrices() { } @HapiTest - final HapiSpec vanillaAppendSucceeds() { + final Stream vanillaAppendSucceeds() { final byte[] first4K = randomUtf8Bytes(BYTES_4K); final byte[] next4k = randomUtf8Bytes(BYTES_4K); final byte[] all8k = new byte[2 * BYTES_4K]; @@ -129,6 +117,21 @@ final HapiSpec vanillaAppendSucceeds() { .then(getFileContents("test").hasContents(ignore -> all8k)); } + @LeakyHapiTest(PROPERTY_OVERRIDES) + final Stream handleRejectsOversized() { + byte[] BYTES_3K_MINUS1 = new byte[3 * 1024 - 1]; + Arrays.fill(BYTES_3K_MINUS1, (byte) 0xAB); + byte[] BYTES_1 = new byte[] {(byte) 0xAB}; + + return propertyPreservingHapiSpec("handleRejectsMissingWacl") + .preserving("files.maxSizeKb") + .given(overriding("files.maxSizeKb", "3")) + .when( + fileCreate("file").contents(BYTES_3K_MINUS1), + fileAppend("file").content(BYTES_1)) + .then(fileAppend("file").content(BYTES_1).hasKnownStatus(MAX_FILE_SIZE_EXCEEDED)); + } + private final int BYTES_4K = 4 * (1 << 10); private byte[] randomUtf8Bytes(int n) { @@ -141,9 +144,4 @@ private byte[] randomUtf8Bytes(int n) { } return data; } - - @Override - protected Logger getResultsLogger() { - return log; - } } diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/file/FileCreateSuite.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/file/FileCreateSuite.java index dfe24bb50478..e84ec7c6688e 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/file/FileCreateSuite.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/file/FileCreateSuite.java @@ -16,6 +16,7 @@ package com.hedera.services.bdd.suites.file; +import static com.hedera.services.bdd.spec.HapiSpec.customHapiSpec; import static com.hedera.services.bdd.spec.HapiSpec.defaultHapiSpec; import static com.hedera.services.bdd.spec.assertions.AccountInfoAsserts.changeFromSnapshot; import static com.hedera.services.bdd.spec.keys.ControlForKey.forKey; @@ -30,60 +31,58 @@ import static com.hedera.services.bdd.spec.queries.QueryVerbs.getFileInfo; import static com.hedera.services.bdd.spec.transactions.TxnVerbs.cryptoTransfer; import static com.hedera.services.bdd.spec.transactions.TxnVerbs.fileCreate; +import static com.hedera.services.bdd.spec.transactions.TxnVerbs.fileDelete; import static com.hedera.services.bdd.spec.transactions.TxnVerbs.fileUpdate; import static com.hedera.services.bdd.spec.transactions.crypto.HapiCryptoTransfer.tinyBarsFromTo; import static com.hedera.services.bdd.spec.utilops.UtilVerbs.balanceSnapshot; import static com.hedera.services.bdd.spec.utilops.UtilVerbs.submitModified; import static com.hedera.services.bdd.spec.utilops.UtilVerbs.withTargetLedgerId; import static com.hedera.services.bdd.spec.utilops.mod.ModificationUtils.withSuccessivelyVariedBodyIds; +import static com.hedera.services.bdd.suites.HapiSuite.ADDRESS_BOOK; +import static com.hedera.services.bdd.suites.HapiSuite.API_PERMISSIONS; +import static com.hedera.services.bdd.suites.HapiSuite.APP_PROPERTIES; +import static com.hedera.services.bdd.suites.HapiSuite.EXCHANGE_RATES; +import static com.hedera.services.bdd.suites.HapiSuite.EXCHANGE_RATE_CONTROL; +import static com.hedera.services.bdd.suites.HapiSuite.FEE_SCHEDULE; +import static com.hedera.services.bdd.suites.HapiSuite.GENESIS; +import static com.hedera.services.bdd.suites.HapiSuite.NODE_DETAILS; +import static com.hedera.services.bdd.suites.HapiSuite.ZERO_BYTE_MEMO; import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.AUTORENEW_DURATION_NOT_IN_RANGE; +import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.INVALID_FILE_ID; import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.INVALID_NODE_ACCOUNT; import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.INVALID_SIGNATURE; import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.INVALID_ZERO_BYTE_IN_STRING; +import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.OK; +import com.google.protobuf.ByteString; import com.hedera.services.bdd.junit.HapiTest; -import com.hedera.services.bdd.junit.HapiTestSuite; -import com.hedera.services.bdd.spec.HapiSpec; import com.hedera.services.bdd.spec.HapiSpecSetup; +import com.hedera.services.bdd.spec.keys.ControlForKey; import com.hedera.services.bdd.spec.keys.KeyShape; import com.hedera.services.bdd.spec.keys.SigControl; +import com.hedera.services.bdd.spec.queries.QueryVerbs; import com.hedera.services.bdd.spec.transactions.TxnUtils; -import com.hedera.services.bdd.suites.HapiSuite; +import com.hedera.services.bdd.spec.utilops.UtilVerbs; import com.hederahashgraph.api.proto.java.AccountID; +import com.hederahashgraph.api.proto.java.CurrentAndNextFeeSchedule; +import com.hederahashgraph.api.proto.java.ExchangeRateSet; +import com.hederahashgraph.api.proto.java.NodeAddressBook; +import com.hederahashgraph.api.proto.java.ResponseCodeEnum; +import com.hederahashgraph.api.proto.java.ServicesConfigurationList; import com.hederahashgraph.api.proto.java.Transaction; import java.nio.file.Path; -import java.util.List; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; - -@HapiTestSuite -public class FileCreateSuite extends HapiSuite { - private static final Logger log = LogManager.getLogger(FileCreateSuite.class); +import java.time.Instant; +import java.util.Map; +import java.util.function.Function; +import java.util.stream.Stream; +import org.junit.jupiter.api.DynamicTest; +public class FileCreateSuite { private static final long defaultMaxLifetime = Long.parseLong(HapiSpecSetup.getDefaultNodeProps().get("entities.maxLifetime")); - public static void main(String... args) { - new FileCreateSuite().runSuiteAsync(); - } - - @Override - public boolean canRunConcurrent() { - return true; - } - - @Override - public List getSpecsInSuite() { - return List.of( - createWithMemoWorks(), - createFailsWithMissingSigs(), - createFailsWithPayerAccountNotFound(), - createFailsWithExcessiveLifetime(), - exchangeRateControlAccountIsntCharged()); - } - @HapiTest - final HapiSpec exchangeRateControlAccountIsntCharged() { + final Stream exchangeRateControlAccountIsntCharged() { return defaultHapiSpec("ExchangeRateControlAccountIsntCharged") .given( cryptoTransfer(tinyBarsFromTo(GENESIS, EXCHANGE_RATE_CONTROL, 1_000_000_000_000L)), @@ -96,7 +95,7 @@ final HapiSpec exchangeRateControlAccountIsntCharged() { } @HapiTest - final HapiSpec createFailsWithExcessiveLifetime() { + final Stream createFailsWithExcessiveLifetime() { return defaultHapiSpec("CreateFailsWithExcessiveLifetime") .given() .when() @@ -106,7 +105,7 @@ final HapiSpec createFailsWithExcessiveLifetime() { } @HapiTest - public HapiSpec idVariantsTreatedAsExpected() { + final Stream idVariantsTreatedAsExpected() { return defaultHapiSpec("idVariantsTreatedAsExpected") .given() .when() @@ -115,7 +114,7 @@ public HapiSpec idVariantsTreatedAsExpected() { } @HapiTest - final HapiSpec createWithMemoWorks() { + final Stream createWithMemoWorks() { String memo = "Really quite something!"; return defaultHapiSpec("createWithMemoWorks") @@ -128,7 +127,7 @@ final HapiSpec createWithMemoWorks() { } @HapiTest - final HapiSpec createFailsWithMissingSigs() { + final Stream createFailsWithMissingSigs() { KeyShape shape = listOf(SIMPLE, threshOf(2, 3), threshOf(1, 3)); SigControl validSig = shape.signedWith(sigs(ON, sigs(ON, ON, OFF), sigs(OFF, OFF, ON))); SigControl invalidSig = shape.signedWith(sigs(OFF, sigs(ON, ON, OFF), sigs(OFF, OFF, ON))); @@ -154,7 +153,7 @@ private static Transaction replaceTxnNodeAccount(Transaction txn) { } @HapiTest - final HapiSpec createFailsWithPayerAccountNotFound() { + final Stream createFailsWithPayerAccountNotFound() { KeyShape shape = listOf(SIMPLE, threshOf(2, 3), threshOf(1, 3)); SigControl validSig = shape.signedWith(sigs(ON, sigs(ON, ON, OFF), sigs(OFF, OFF, ON))); @@ -169,8 +168,102 @@ final HapiSpec createFailsWithPayerAccountNotFound() { .hasPrecheckFrom(INVALID_NODE_ACCOUNT)); } - @Override - protected Logger getResultsLogger() { - return log; + @HapiTest + final Stream precheckRejectsBadEffectiveAutoRenewPeriod() { + var now = Instant.now(); + System.out.println(now.getEpochSecond()); + + return defaultHapiSpec("precheckRejectsBadEffectiveAutoRenewPeriod") + .given() + .when() + .then(fileCreate("notHere") + .lifetime(-60L) + .hasPrecheck(ResponseCodeEnum.AUTORENEW_DURATION_NOT_IN_RANGE)); + } + + @HapiTest + final Stream targetsAppear() { + var lifetime = 100_000L; + var requestedExpiry = Instant.now().getEpochSecond() + lifetime; + var contents = "SOMETHING".getBytes(); + var newWacl = listOf(SIMPLE, listOf(3), threshOf(1, 3)); + var newWaclSigs = newWacl.signedWith(sigs(ON, sigs(ON, ON, ON), sigs(OFF, OFF, ON))); + + return defaultHapiSpec("targetsAppear") + .given(UtilVerbs.newKeyNamed("newWacl").shape(newWacl)) + .when(fileCreate("file") + .via("createTxn") + .contents(contents) + .key("newWacl") + .expiry(requestedExpiry) + .signedBy(GENESIS, "newWacl") + .sigControl(ControlForKey.forKey("newWacl", newWaclSigs))) + .then( + QueryVerbs.getFileInfo("file") + .hasDeleted(false) + .hasWacl("newWacl") + .hasExpiryPassing(expiry -> expiry == requestedExpiry), + QueryVerbs.getFileContents("file") + .hasByteStringContents(ignore -> ByteString.copyFrom(contents))); + } + + @HapiTest + final Stream getsExpectedRejections() { + return defaultHapiSpec("getsExpectedRejections") + .given(fileCreate("tbd"), fileDelete("tbd")) + .when() + .then( + getFileInfo("1.2.3").nodePayment(1_234L).hasAnswerOnlyPrecheck(INVALID_FILE_ID), + getFileContents("1.2.3").nodePayment(1_234L).hasAnswerOnlyPrecheck(INVALID_FILE_ID), + getFileContents("tbd") + .nodePayment(1_234L) + .hasAnswerOnlyPrecheck(OK) + .logged(), + getFileInfo("tbd") + .nodePayment(1_234L) + .hasAnswerOnlyPrecheck(OK) + .hasDeleted(true) + .logged()); + } + + /** + * Fetches the system files from a running network and validates they can be + * parsed as the expected protobuf messages. */ + @HapiTest + final Stream fetchFiles() { + return customHapiSpec("FetchFiles") + .withProperties(Map.of( + "fees.useFixedOffer", "true", + "fees.fixedOffer", "100000000")) + .given() + .when() + .then( + getFileContents(NODE_DETAILS).andValidate(unchecked(NodeAddressBook::parseFrom)::apply), + getFileContents(ADDRESS_BOOK).andValidate(unchecked(NodeAddressBook::parseFrom)::apply), + getFileContents(EXCHANGE_RATES).andValidate(unchecked(ExchangeRateSet::parseFrom)::apply), + getFileContents(APP_PROPERTIES) + .andValidate(unchecked(ServicesConfigurationList::parseFrom)::apply), + getFileContents(API_PERMISSIONS) + .andValidate(unchecked(ServicesConfigurationList::parseFrom)::apply), + getFileContents(FEE_SCHEDULE) + .fee(300_000L) + .nodePayment(40L) + .andValidate(unchecked(CurrentAndNextFeeSchedule::parseFrom)::apply)); + } + + @FunctionalInterface + public interface CheckedParser { + Object parseFrom(byte[] bytes) throws Exception; + } + + static Function unchecked(CheckedParser parser) { + return bytes -> { + try { + return parser.parseFrom(bytes).toString(); + } catch (Exception e) { + e.printStackTrace(); + return " due to " + e.getMessage() + "!"; + } + }; } } diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/file/FileDeleteSuite.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/file/FileDeleteSuite.java index 9419a33d14d3..e528793fbcaf 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/file/FileDeleteSuite.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/file/FileDeleteSuite.java @@ -29,33 +29,19 @@ import static com.hedera.services.bdd.spec.transactions.TxnVerbs.fileDelete; import static com.hedera.services.bdd.spec.utilops.UtilVerbs.submitModified; import static com.hedera.services.bdd.spec.utilops.mod.ModificationUtils.withSuccessivelyVariedBodyIds; +import static com.hedera.services.bdd.suites.HapiSuite.GENESIS; import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.OK; import com.hedera.services.bdd.junit.HapiTest; -import com.hedera.services.bdd.junit.HapiTestSuite; -import com.hedera.services.bdd.spec.HapiSpec; import com.hedera.services.bdd.spec.keys.KeyShape; import com.hedera.services.bdd.spec.keys.SigControl; -import com.hedera.services.bdd.suites.HapiSuite; -import java.util.List; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; - -@HapiTestSuite -public class FileDeleteSuite extends HapiSuite { - private static final Logger log = LogManager.getLogger(FileDeleteSuite.class); - - public static void main(String... args) { - new FileDeleteSuite().runSuiteSync(); - } - - @Override - public List getSpecsInSuite() { - return List.of(getDeletedFileInfo(), canDeleteWithAnyOneOfTopLevelKeyList()); - } +import com.hederahashgraph.api.proto.java.ResponseCodeEnum; +import java.util.stream.Stream; +import org.junit.jupiter.api.DynamicTest; +public class FileDeleteSuite { @HapiTest - public HapiSpec idVariantsTreatedAsExpected() { + final Stream idVariantsTreatedAsExpected() { return defaultHapiSpec("idVariantsTreatedAsExpected") .given(fileCreate("file").contents("ABC")) .when() @@ -63,7 +49,7 @@ public HapiSpec idVariantsTreatedAsExpected() { } @HapiTest - final HapiSpec canDeleteWithAnyOneOfTopLevelKeyList() { + final Stream canDeleteWithAnyOneOfTopLevelKeyList() { KeyShape shape = listOf(SIMPLE, threshOf(1, 2), listOf(2)); SigControl deleteSigs = shape.signedWith(sigs(ON, sigs(OFF, OFF), sigs(ON, OFF))); @@ -74,15 +60,26 @@ final HapiSpec canDeleteWithAnyOneOfTopLevelKeyList() { } @HapiTest - final HapiSpec getDeletedFileInfo() { + final Stream getDeletedFileInfo() { return defaultHapiSpec("getDeletedFileInfo") .given(fileCreate("deletedFile").logged()) .when(fileDelete("deletedFile").logged()) .then(getFileInfo("deletedFile").hasAnswerOnlyPrecheck(OK).hasDeleted(true)); } - @Override - protected Logger getResultsLogger() { - return log; + @HapiTest + final Stream handleRejectsMissingFile() { + return defaultHapiSpec("handleRejectsMissingFile") + .given() + .when() + .then(fileDelete("1.2.3").signedBy(GENESIS).hasKnownStatus(ResponseCodeEnum.INVALID_FILE_ID)); + } + + @HapiTest + final Stream handleRejectsDeletedFile() { + return defaultHapiSpec("handleRejectsDeletedFile") + .given(fileCreate("tbd")) + .when(fileDelete("tbd")) + .then(fileDelete("tbd").hasKnownStatus(ResponseCodeEnum.FILE_DELETED)); } } diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/file/FileUpdateSuite.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/file/FileUpdateSuite.java index 6c57be5eba9a..ad482252e95a 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/file/FileUpdateSuite.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/file/FileUpdateSuite.java @@ -16,6 +16,9 @@ package com.hedera.services.bdd.suites.file; +import static com.hedera.services.bdd.junit.ContextRequirement.PERMISSION_OVERRIDES; +import static com.hedera.services.bdd.junit.ContextRequirement.PROPERTY_OVERRIDES; +import static com.hedera.services.bdd.junit.ContextRequirement.UPGRADE_FILE_CONTENT; import static com.hedera.services.bdd.spec.HapiSpec.defaultHapiSpec; import static com.hedera.services.bdd.spec.HapiSpec.propertyPreservingHapiSpec; import static com.hedera.services.bdd.spec.assertions.ContractFnResultAsserts.resultWith; @@ -63,6 +66,18 @@ import static com.hedera.services.bdd.spec.utilops.UtilVerbs.usableTxnIdNamed; import static com.hedera.services.bdd.spec.utilops.UtilVerbs.withOpContext; import static com.hedera.services.bdd.spec.utilops.mod.ModificationUtils.withSuccessivelyVariedBodyIds; +import static com.hedera.services.bdd.suites.HapiSuite.ADDRESS_BOOK_CONTROL; +import static com.hedera.services.bdd.suites.HapiSuite.API_PERMISSIONS; +import static com.hedera.services.bdd.suites.HapiSuite.APP_PROPERTIES; +import static com.hedera.services.bdd.suites.HapiSuite.DEFAULT_PAYER; +import static com.hedera.services.bdd.suites.HapiSuite.FUNDING; +import static com.hedera.services.bdd.suites.HapiSuite.GENESIS; +import static com.hedera.services.bdd.suites.HapiSuite.ONE_HBAR; +import static com.hedera.services.bdd.suites.HapiSuite.ONE_HUNDRED_HBARS; +import static com.hedera.services.bdd.suites.HapiSuite.THREE_MONTHS_IN_SECONDS; +import static com.hedera.services.bdd.suites.HapiSuite.TINY_PARTS_PER_WHOLE; +import static com.hedera.services.bdd.suites.HapiSuite.ZERO_BYTE_MEMO; +import static com.hedera.services.bdd.suites.HapiSuite.flattened; import static com.hedera.services.bdd.suites.utils.contracts.SimpleBytesResult.bigIntResult; import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.AUTORENEW_DURATION_NOT_IN_RANGE; import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.BUSY; @@ -92,42 +107,25 @@ import com.google.protobuf.ByteString; import com.hedera.services.bdd.junit.HapiTest; -import com.hedera.services.bdd.junit.HapiTestSuite; -import com.hedera.services.bdd.spec.HapiSpec; +import com.hedera.services.bdd.junit.LeakyHapiTest; import com.hedera.services.bdd.spec.HapiSpecSetup; import com.hedera.services.bdd.spec.keys.SigControl; import com.hedera.services.bdd.spec.transactions.TxnUtils; import com.hedera.services.bdd.spec.transactions.TxnVerbs; import com.hedera.services.bdd.spec.utilops.UtilVerbs; -import com.hedera.services.bdd.suites.BddMethodIsNotATest; -import com.hedera.services.bdd.suites.HapiSuite; import com.hedera.services.bdd.suites.token.TokenAssociationSpecs; import com.swirlds.common.utility.CommonUtils; import java.math.BigInteger; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; -import java.util.List; import java.util.Map; import java.util.concurrent.atomic.AtomicLong; +import java.util.stream.Stream; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; +import org.junit.jupiter.api.DynamicTest; -/** - * NOTE: 1. This test suite covers the test08UpdateFile() test scenarios from the legacy - * FileServiceIT test class after the FileServiceIT class is removed since all other test scenarios - * in this class are already covered by test suites under - * com.hedera.services.legacy.regression.suites.file and - * com.hedera.services.legacy.regression.suites.crpto. - * - *

    2. While this class now provides minimal coverage for proto's FileUpdate transaction, we shall - * add more positive and negative test scenarios to cover FileUpdate, such as missing (partial) keys - * for update, for update of expirationTime, for modifying keys field, etc. - * - *

    We'll come back to add all missing test scenarios for this and other test suites once we are - * done with cleaning up old test cases. - */ -@HapiTestSuite -public class FileUpdateSuite extends HapiSuite { +public class FileUpdateSuite { private static final Logger log = LogManager.getLogger(FileUpdateSuite.class); private static final String CONTRACT = "CreateTrivial"; private static final String CREATE_TXN = "create"; @@ -164,34 +162,8 @@ public class FileUpdateSuite extends HapiSuite { public static final String STAKING_FEES_NODE_REWARD_PERCENTAGE = "staking.fees.nodeRewardPercentage"; public static final String STAKING_FEES_STAKING_REWARD_PERCENTAGE = "staking.fees.stakingRewardPercentage"; - public static void main(String... args) { - new FileUpdateSuite().runSuiteSync(); - } - - @Override - @SuppressWarnings("java:S3878") - public List getSpecsInSuite() { - return List.of( - vanillaUpdateSucceeds(), - updateFeesCompatibleWithCreates(), - apiPermissionsChangeDynamically(), - cannotUpdateExpirationPastMaxLifetime(), - optimisticSpecialFileUpdate(), - associateHasExpectedSemantics(), - notTooManyFeeScheduleCanBeCreated(), - allUnusedGasIsRefundedIfSoConfigured(), - maxRefundIsEnforced(), - gasLimitOverMaxGasLimitFailsPrecheck(), - kvLimitsEnforced(), - serviceFeeRefundedIfConsGasExhausted(), - chainIdChangesDynamically(), - entitiesNotCreatableAfterUsageLimitsReached(), - rentItemizedAsExpectedWithOverridePriceTiers(), - messageSubmissionSizeChange()); - } - @HapiTest - public HapiSpec idVariantsTreatedAsExpected() { + final Stream idVariantsTreatedAsExpected() { return defaultHapiSpec("idVariantsTreatedAsExpected") .given(fileCreate("file").contents("ABC")) .when() @@ -199,8 +171,8 @@ public HapiSpec idVariantsTreatedAsExpected() { .contents("DEF"))); } - @HapiTest - final HapiSpec associateHasExpectedSemantics() { + @LeakyHapiTest(PROPERTY_OVERRIDES) + final Stream associateHasExpectedSemantics() { return propertyPreservingHapiSpec("AssociateHasExpectedSemantics") .preserving("tokens.maxRelsPerInfoQuery") .given(flattened((Object[]) TokenAssociationSpecs.basicKeysAndTokens())) @@ -239,8 +211,8 @@ final HapiSpec associateHasExpectedSemantics() { .logged()); } - @HapiTest - public HapiSpec notTooManyFeeScheduleCanBeCreated() { + @LeakyHapiTest(PROPERTY_OVERRIDES) + final Stream notTooManyFeeScheduleCanBeCreated() { final var denom = "fungible"; final var token = "token"; return defaultHapiSpec("OnlyValidCustomFeeScheduleCanBeCreated") @@ -255,8 +227,8 @@ public HapiSpec notTooManyFeeScheduleCanBeCreated() { .then(overriding(MAX_CUSTOM_FEES_PROP, DEFAULT_MAX_CUSTOM_FEES)); } - @HapiTest - final HapiSpec optimisticSpecialFileUpdate() { + @LeakyHapiTest(UPGRADE_FILE_CONTENT) + final Stream optimisticSpecialFileUpdate() { final var appendsPerBurst = 128; final var specialFile = "0.0.159"; final var contents = randomUtf8Bytes(64 * BYTES_4K); @@ -273,8 +245,8 @@ final HapiSpec optimisticSpecialFileUpdate() { .then(getFileInfo(specialFile).hasMemo(CommonUtils.hex(expectedHash))); } - @HapiTest - final HapiSpec apiPermissionsChangeDynamically() { + @LeakyHapiTest(PERMISSION_OVERRIDES) + final Stream apiPermissionsChangeDynamically() { final var civilian = CIVILIAN; return defaultHapiSpec("ApiPermissionsChangeDynamically") .given( @@ -298,8 +270,8 @@ final HapiSpec apiPermissionsChangeDynamically() { tokenCreate("secondPoc").payingWith(civilian)); } - @HapiTest - final HapiSpec updateFeesCompatibleWithCreates() { + @LeakyHapiTest(PROPERTY_OVERRIDES) + final Stream updateFeesCompatibleWithCreates() { final long origLifetime = 7_200_000L; final long extension = 700_000L; final byte[] old2k = randomUtf8Bytes(BYTES_4K / 2); @@ -345,7 +317,7 @@ final HapiSpec updateFeesCompatibleWithCreates() { } @HapiTest - final HapiSpec vanillaUpdateSucceeds() { + final Stream vanillaUpdateSucceeds() { final byte[] old4K = randomUtf8Bytes(BYTES_4K); final byte[] new4k = randomUtf8Bytes(BYTES_4K); final String firstMemo = "Originally"; @@ -365,7 +337,7 @@ final HapiSpec vanillaUpdateSucceeds() { } @HapiTest - public HapiSpec cannotUpdateImmutableFile() { + final Stream cannotUpdateImmutableFile() { final String file1 = "FILE_1"; final String file2 = "FILE_2"; return defaultHapiSpec("CannotUpdateImmutableFile") @@ -385,7 +357,7 @@ public HapiSpec cannotUpdateImmutableFile() { } @HapiTest - final HapiSpec cannotUpdateExpirationPastMaxLifetime() { + final Stream cannotUpdateExpirationPastMaxLifetime() { return defaultHapiSpec("CannotUpdateExpirationPastMaxLifetime") .given(fileCreate("test")) .when() @@ -394,8 +366,8 @@ final HapiSpec cannotUpdateExpirationPastMaxLifetime() { .hasPrecheck(AUTORENEW_DURATION_NOT_IN_RANGE)); } - @HapiTest - final HapiSpec maxRefundIsEnforced() { + @LeakyHapiTest(PROPERTY_OVERRIDES) + final Stream maxRefundIsEnforced() { return propertyPreservingHapiSpec("MaxRefundIsEnforced") .preserving(MAX_REFUND_GAS_PROP) .given(overriding(MAX_REFUND_GAS_PROP, "5"), uploadInitCode(CONTRACT), contractCreate(CONTRACT)) @@ -406,8 +378,8 @@ final HapiSpec maxRefundIsEnforced() { } // C.f. https://github.com/hashgraph/hedera-services/pull/8908 - @HapiTest - final HapiSpec allUnusedGasIsRefundedIfSoConfigured() { + @LeakyHapiTest(PROPERTY_OVERRIDES) + final Stream allUnusedGasIsRefundedIfSoConfigured() { return propertyPreservingHapiSpec("AllUnusedGasIsRefundedIfSoConfigured") .preserving(MAX_REFUND_GAS_PROP) .given( @@ -420,8 +392,8 @@ final HapiSpec allUnusedGasIsRefundedIfSoConfigured() { .has(resultWith().gasUsed(26_515))); } - @HapiTest - final HapiSpec gasLimitOverMaxGasLimitFailsPrecheck() { + @LeakyHapiTest(PROPERTY_OVERRIDES) + final Stream gasLimitOverMaxGasLimitFailsPrecheck() { return propertyPreservingHapiSpec("GasLimitOverMaxGasLimitFailsPrecheck") .preserving(CONS_MAX_GAS_PROP) .given( @@ -435,8 +407,8 @@ final HapiSpec gasLimitOverMaxGasLimitFailsPrecheck() { .hasCostAnswerPrecheckFrom(MAX_GAS_LIMIT_EXCEEDED, BUSY)); } - @HapiTest - final HapiSpec kvLimitsEnforced() { + @LeakyHapiTest(PROPERTY_OVERRIDES) + final Stream kvLimitsEnforced() { final var contract = "User"; final var gasToOffer = 1_000_000; @@ -498,8 +470,8 @@ final HapiSpec kvLimitsEnforced() { } @SuppressWarnings("java:S5960") - @HapiTest - final HapiSpec serviceFeeRefundedIfConsGasExhausted() { + @LeakyHapiTest(PROPERTY_OVERRIDES) + final Stream serviceFeeRefundedIfConsGasExhausted() { final var contract = "User"; final var gasToOffer = Long.parseLong(DEFAULT_MAX_CONS_GAS); final var civilian = "payer"; @@ -551,8 +523,8 @@ final HapiSpec serviceFeeRefundedIfConsGasExhausted() { })); } - @HapiTest - public HapiSpec chainIdChangesDynamically() { + @LeakyHapiTest(PROPERTY_OVERRIDES) + final Stream chainIdChangesDynamically() { final var chainIdUser = "ChainIdUser"; final var otherChainId = 0xABCDL; final var firstCallTxn = "firstCallTxn"; @@ -587,8 +559,8 @@ public HapiSpec chainIdChangesDynamically() { .then(); } - @HapiTest - final HapiSpec entitiesNotCreatableAfterUsageLimitsReached() { + @LeakyHapiTest(PROPERTY_OVERRIDES) + final Stream entitiesNotCreatableAfterUsageLimitsReached() { final var notToBe = "ne'erToBe"; return propertyPreservingHapiSpec("EntitiesNotCreatableAfterUsageLimitsReached") .preserving( @@ -620,8 +592,8 @@ final HapiSpec entitiesNotCreatableAfterUsageLimitsReached() { .then(); } - @BddMethodIsNotATest - final HapiSpec rentItemizedAsExpectedWithOverridePriceTiers() { + // (FUTURE) Re-enable when contract rent is enabled + final Stream rentItemizedAsExpectedWithOverridePriceTiers() { final var slotUser = "SlotUser"; final var creation = "creation"; final var aSet = "aSet"; @@ -710,12 +682,13 @@ final HapiSpec rentItemizedAsExpectedWithOverridePriceTiers() { .then(); } - @HapiTest - final HapiSpec messageSubmissionSizeChange() { + @LeakyHapiTest(PROPERTY_OVERRIDES) + final Stream messageSubmissionSizeChange() { final var defaultMaxBytesAllowed = 1024; final var longMessage = TxnUtils.randomUtf8Bytes(defaultMaxBytesAllowed); - return defaultHapiSpec("messageSubmissionSizeChange") + return propertyPreservingHapiSpec("messageSubmissionSizeChange") + .preserving("consensus.message.maxBytesAllowed") .given(newKeyNamed("submitKey"), createTopic(TEST_TOPIC).submitKeyName("submitKey")) .when( cryptoCreate(CIVILIAN), @@ -725,17 +698,10 @@ final HapiSpec messageSubmissionSizeChange() { .hasRetryPrecheckFrom(BUSY) .hasKnownStatus(SUCCESS), overriding("consensus.message.maxBytesAllowed", String.valueOf(defaultMaxBytesAllowed - 1))) - .then( - submitMessageTo(TEST_TOPIC) - .message(longMessage) - .payingWith(CIVILIAN) - .hasRetryPrecheckFrom(BUSY) - .hasKnownStatus(MESSAGE_SIZE_TOO_LARGE), - overriding("consensus.message.maxBytesAllowed", String.valueOf(defaultMaxBytesAllowed))); - } - - @Override - protected Logger getResultsLogger() { - return log; + .then(submitMessageTo(TEST_TOPIC) + .message(longMessage) + .payingWith(CIVILIAN) + .hasRetryPrecheckFrom(BUSY) + .hasKnownStatus(MESSAGE_SIZE_TOO_LARGE)); } } diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/file/MidnightUpdateRateSuite.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/file/MidnightUpdateRateSuite.java index 25c54f1f848a..a1b6d9083791 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/file/MidnightUpdateRateSuite.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/file/MidnightUpdateRateSuite.java @@ -21,37 +21,30 @@ import static com.hedera.services.bdd.spec.transactions.TxnVerbs.cryptoTransfer; import static com.hedera.services.bdd.spec.transactions.TxnVerbs.fileUpdate; import static com.hedera.services.bdd.spec.transactions.crypto.HapiCryptoTransfer.tinyBarsFromTo; +import static com.hedera.services.bdd.suites.HapiSuite.ADEQUATE_FUNDS; +import static com.hedera.services.bdd.suites.HapiSuite.EXCHANGE_RATES; +import static com.hedera.services.bdd.suites.HapiSuite.EXCHANGE_RATE_CONTROL; +import static com.hedera.services.bdd.suites.HapiSuite.GENESIS; import com.google.protobuf.ByteString; -import com.hedera.services.bdd.spec.HapiSpec; import com.hedera.services.bdd.spec.transactions.file.HapiFileUpdate; import com.hedera.services.bdd.spec.utilops.UtilVerbs; -import com.hedera.services.bdd.suites.HapiSuite; import java.text.ParseException; -import java.util.List; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; +import java.util.stream.Stream; +import org.junit.jupiter.api.DynamicTest; // The test in this suite was extracted from ExchangeRateControlSuite. It has to be run shortly // before midnight, which is not practical. Either we add functionality to mock time in e2e-test // or we move this test to the xtests. // // https://github.com/hashgraph/hedera-services/issues/8950 -public class MidnightUpdateRateSuite extends HapiSuite { - - private static final Logger log = LogManager.getLogger(MidnightUpdateRateSuite.class); - +public class MidnightUpdateRateSuite { final HapiFileUpdate resetRatesOp = fileUpdate(EXCHANGE_RATES) .payingWith(EXCHANGE_RATE_CONTROL) .fee(ADEQUATE_FUNDS) .contents(spec -> spec.ratesProvider().rateSetWith(1, 12).toByteString()); - @Override - public List getSpecsInSuite() { - return List.of(); - } - - final HapiSpec acct57UpdatesMidnightRateAtMidNight() throws ParseException { + final Stream acct57UpdatesMidnightRateAtMidNight() throws ParseException { return defaultHapiSpec("Acct57UpdatesMidnightRateAtMidNight") .given(resetRatesOp, cryptoTransfer(tinyBarsFromTo(GENESIS, EXCHANGE_RATE_CONTROL, ADEQUATE_FUNDS))) .when( @@ -82,9 +75,4 @@ final HapiSpec acct57UpdatesMidnightRateAtMidNight() throws ParseException { .hasContents(spec -> spec.registry().getBytes("newRates")), resetRatesOp); } - - @Override - protected Logger getResultsLogger() { - return log; - } } diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/file/PermissionSemanticsSpec.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/file/PermissionSemanticsSpec.java index 4072b9b67157..b7520c7969ea 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/file/PermissionSemanticsSpec.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/file/PermissionSemanticsSpec.java @@ -33,50 +33,31 @@ import static com.hedera.services.bdd.spec.transactions.crypto.HapiCryptoTransfer.tinyBarsFromTo; import static com.hedera.services.bdd.spec.utilops.UtilVerbs.newKeyNamed; import static com.hedera.services.bdd.spec.utilops.UtilVerbs.withOpContext; +import static com.hedera.services.bdd.suites.HapiSuite.ADDRESS_BOOK_CONTROL; +import static com.hedera.services.bdd.suites.HapiSuite.GENESIS; +import static com.hedera.services.bdd.suites.HapiSuite.NODE_DETAILS; +import static com.hedera.services.bdd.suites.HapiSuite.ONE_HUNDRED_HBARS; import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.INVALID_SIGNATURE; import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.UNAUTHORIZED; import com.google.protobuf.ByteString; import com.hedera.services.bdd.junit.HapiTest; -import com.hedera.services.bdd.junit.HapiTestSuite; -import com.hedera.services.bdd.spec.HapiSpec; import com.hedera.services.bdd.spec.keys.ControlForKey; import com.hedera.services.bdd.spec.keys.KeyFactory; import com.hedera.services.bdd.spec.keys.KeyShape; -import com.hedera.services.bdd.suites.HapiSuite; -import java.util.List; import java.util.concurrent.atomic.AtomicLong; import java.util.concurrent.atomic.AtomicReference; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; +import java.util.stream.Stream; +import org.junit.jupiter.api.DynamicTest; -@HapiTestSuite -public class PermissionSemanticsSpec extends HapiSuite { - private static final Logger log = LogManager.getLogger(PermissionSemanticsSpec.class); +public class PermissionSemanticsSpec { public static final String NEVER_TO_BE_USED = "neverToBeUsed"; public static final String CIVILIAN = "civilian"; public static final String ETERNAL = "eternal"; public static final String WACL = "wacl"; - public static void main(String... args) { - new PermissionSemanticsSpec().runSuiteAsync(); - } - - @Override - public boolean canRunConcurrent() { - return true; - } - - @Override - public List getSpecsInSuite() { - return List.of( - allowsDeleteWithOneTopLevelSig(), - supportsImmutableFiles(), - addressBookAdminExemptFromFeesGivenAuthorizedOps()); - } - @HapiTest - final HapiSpec addressBookAdminExemptFromFeesGivenAuthorizedOps() { + final Stream addressBookAdminExemptFromFeesGivenAuthorizedOps() { long amount = 100 * 100_000_000L; AtomicReference origContents = new AtomicReference<>(); return defaultHapiSpec("AddressBookAdminExemptFromFeesGivenAuthorizedOps") @@ -100,7 +81,7 @@ final HapiSpec addressBookAdminExemptFromFeesGivenAuthorizedOps() { } @HapiTest - final HapiSpec supportsImmutableFiles() { + final Stream supportsImmutableFiles() { long extensionSecs = 666L; AtomicLong approxExpiry = new AtomicLong(); @@ -142,7 +123,7 @@ final HapiSpec supportsImmutableFiles() { } @HapiTest - final HapiSpec allowsDeleteWithOneTopLevelSig() { + final Stream allowsDeleteWithOneTopLevelSig() { KeyShape wacl = KeyShape.listOf(KeyShape.SIMPLE, KeyShape.listOf(2)); var deleteSig = wacl.signedWith(sigs(ON, sigs(OFF, OFF))); @@ -170,9 +151,4 @@ final HapiSpec allowsDeleteWithOneTopLevelSig() { .hasKnownStatus(INVALID_SIGNATURE), fileDelete("tbd").signedBy(GENESIS, WACL).sigControl(ControlForKey.forKey(WACL, deleteSig))); } - - @Override - protected Logger getResultsLogger() { - return log; - } } diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/file/ProtectedFilesUpdateSuite.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/file/ProtectedFilesUpdateSuite.java index e6bd0db36966..21d841819cb7 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/file/ProtectedFilesUpdateSuite.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/file/ProtectedFilesUpdateSuite.java @@ -22,6 +22,17 @@ import static com.hedera.services.bdd.spec.queries.QueryVerbs.getFileContents; import static com.hedera.services.bdd.spec.transactions.TxnVerbs.cryptoCreate; import static com.hedera.services.bdd.spec.transactions.TxnVerbs.fileUpdate; +import static com.hedera.services.bdd.suites.HapiSuite.ADDRESS_BOOK; +import static com.hedera.services.bdd.suites.HapiSuite.ADDRESS_BOOK_CONTROL; +import static com.hedera.services.bdd.suites.HapiSuite.API_PERMISSIONS; +import static com.hedera.services.bdd.suites.HapiSuite.APP_PROPERTIES; +import static com.hedera.services.bdd.suites.HapiSuite.EXCHANGE_RATES; +import static com.hedera.services.bdd.suites.HapiSuite.EXCHANGE_RATE_CONTROL; +import static com.hedera.services.bdd.suites.HapiSuite.FEE_SCHEDULE; +import static com.hedera.services.bdd.suites.HapiSuite.FEE_SCHEDULE_CONTROL; +import static com.hedera.services.bdd.suites.HapiSuite.GENESIS; +import static com.hedera.services.bdd.suites.HapiSuite.NODE_DETAILS; +import static com.hedera.services.bdd.suites.HapiSuite.SYSTEM_ADMIN; import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.AUTHORIZATION_FAILED; import com.fasterxml.jackson.core.JsonProcessingException; @@ -29,31 +40,28 @@ import com.google.protobuf.ByteString; import com.google.protobuf.InvalidProtocolBufferException; import com.hedera.services.bdd.junit.HapiTest; -import com.hedera.services.bdd.junit.HapiTestSuite; +import com.hedera.services.bdd.junit.OrderedInIsolation; import com.hedera.services.bdd.spec.HapiPropertySource; -import com.hedera.services.bdd.spec.HapiSpec; import com.hedera.services.bdd.spec.HapiSpecOperation; import com.hedera.services.bdd.spec.queries.file.HapiGetFileContents; import com.hedera.services.bdd.spec.utilops.CustomSpecAssert; import com.hedera.services.bdd.spec.utilops.UtilVerbs; -import com.hedera.services.bdd.suites.BddMethodIsNotATest; -import com.hedera.services.bdd.suites.HapiSuite; import com.hedera.services.bdd.suites.utils.sysfiles.AddressBookPojo; import com.hederahashgraph.api.proto.java.NodeAddress; import com.hederahashgraph.api.proto.java.NodeAddressBook; import com.swirlds.common.utility.CommonUtils; -import java.util.Arrays; -import java.util.List; import java.util.Map; import java.util.SplittableRandom; import java.util.function.UnaryOperator; +import java.util.stream.Stream; import org.apache.commons.lang3.ArrayUtils; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.DynamicTest; -@HapiTestSuite -public class ProtectedFilesUpdateSuite extends HapiSuite { +@OrderedInIsolation +public class ProtectedFilesUpdateSuite { private static final String IGNORE = "ignore"; private static final String TARGET_MEMO = "0.0.5"; private static final String REPLACE_MEMO = "0.0.6"; @@ -64,53 +72,124 @@ public class ProtectedFilesUpdateSuite extends HapiSuite { // The number of chars that separate a property and its value private static final int PROPERTY_VALUE_SPACE_LENGTH = 2; - public static void main(String... args) { - new ProtectedFilesUpdateSuite().runSuiteSync(); - } - - @Override - public List getSpecsInSuite() { - return allOf(positiveTests(), negativeTests()); - } - - private List positiveTests() { - return List.of( - account2CanUpdateApplicationProperties(), - account50CanUpdateApplicationProperties(), - account2CanUpdateApiPermissions(), - account50CanUpdateApiPermissions(), - account2CanUpdateAddressBook(), - account50CanUpdateAddressBook(), - account55CanUpdateAddressBook(), - account2CanUpdateNodeDetails(), - account50CanUpdateNodeDetails(), - account55CanUpdateNodeDetails(), - account2CanUpdateFeeSchedule(), - account50CanUpdateFeeSchedule(), - account56CanUpdateFeeSchedule(), - account2CanUpdateExchangeRates(), - account50CanUpdateExchangeRates(), - account57CanUpdateExchangeRates()); - } - - private List negativeTests() { - return Arrays.asList( - unauthorizedAccountCannotUpdateApplicationProperties(), - unauthorizedAccountCannotUpdateApiPermissions(), - unauthorizedAccountCannotUpdateAddressBook(), - unauthorizedAccountCannotUpdateNodeDetails(), - unauthorizedAccountCannotUpdateFeeSchedule(), - unauthorizedAccountCannotUpdateExchangeRates()); - } - - @BddMethodIsNotATest - final HapiSpec specialAccountCanUpdateSpecialPropertyFile( + @HapiTest + final Stream account2CanUpdateApplicationProperties() { + return specialAccountCanUpdateSpecialPropertyFile(GENESIS, APP_PROPERTIES, "throttlingTps", "10"); + } + + @HapiTest + final Stream account50CanUpdateApplicationProperties() { + return specialAccountCanUpdateSpecialPropertyFile(SYSTEM_ADMIN, APP_PROPERTIES, "getReceiptTps", "100"); + } + + @HapiTest + final Stream unauthorizedAccountCannotUpdateApplicationProperties() { + return unauthorizedAccountCannotUpdateSpecialFile(APP_PROPERTIES, NEW_CONTENTS); + } + + @HapiTest + final Stream account2CanUpdateApiPermissions() { + return specialAccountCanUpdateSpecialPropertyFile(GENESIS, API_PERMISSIONS, "createTopic", "1-*"); + } + + @HapiTest + final Stream account50CanUpdateApiPermissions() { + return specialAccountCanUpdateSpecialPropertyFile(SYSTEM_ADMIN, API_PERMISSIONS, "updateFile", "1-*"); + } + + @HapiTest + final Stream unauthorizedAccountCannotUpdateApiPermissions() { + return unauthorizedAccountCannotUpdateSpecialFile(API_PERMISSIONS, NEW_CONTENTS); + } + + @HapiTest + final Stream account2CanUpdateAddressBook() { + return specialAccountCanUpdateSpecialFile( + GENESIS, ADDRESS_BOOK, true, contents -> extendedBioAddressBook(contents, TARGET_MEMO, REPLACE_MEMO)); + } + + @HapiTest + final Stream account50CanUpdateAddressBook() { + return specialAccountCanUpdateSpecialFile(SYSTEM_ADMIN, ADDRESS_BOOK, TARGET_MEMO, REPLACE_MEMO); + } + + @HapiTest + final Stream account55CanUpdateAddressBook() { + return specialAccountCanUpdateSpecialFile(ADDRESS_BOOK_CONTROL, ADDRESS_BOOK, TARGET_MEMO, REPLACE_MEMO, false); + } + + @HapiTest + final Stream unauthorizedAccountCannotUpdateAddressBook() { + return unauthorizedAccountCannotUpdateSpecialFile(ADDRESS_BOOK, NEW_CONTENTS); + } + + @HapiTest + final Stream account2CanUpdateNodeDetails() { + return specialAccountCanUpdateSpecialFile( + GENESIS, NODE_DETAILS, true, contents -> extendedBioNodeDetails(contents, TARGET_MEMO, REPLACE_MEMO)); + } + + @HapiTest + final Stream account50CanUpdateNodeDetails() { + return specialAccountCanUpdateSpecialFile(SYSTEM_ADMIN, NODE_DETAILS, TARGET_MEMO, REPLACE_MEMO); + } + + @HapiTest + final Stream account55CanUpdateNodeDetails() { + return specialAccountCanUpdateSpecialFile(ADDRESS_BOOK_CONTROL, NODE_DETAILS, TARGET_MEMO, REPLACE_MEMO, false); + } + + @HapiTest + final Stream unauthorizedAccountCannotUpdateNodeDetails() { + return unauthorizedAccountCannotUpdateSpecialFile(NODE_DETAILS, NEW_CONTENTS); + } + + @HapiTest + final Stream account2CanUpdateFeeSchedule() { + return specialAccountCanUpdateSpecialFile(GENESIS, FEE_SCHEDULE, IGNORE, IGNORE); + } + + @HapiTest + final Stream account50CanUpdateFeeSchedule() { + return specialAccountCanUpdateSpecialFile(SYSTEM_ADMIN, FEE_SCHEDULE, IGNORE, IGNORE); + } + + @HapiTest + final Stream account56CanUpdateFeeSchedule() { + return specialAccountCanUpdateSpecialFile(FEE_SCHEDULE_CONTROL, FEE_SCHEDULE, IGNORE, IGNORE); + } + + @HapiTest + final Stream unauthorizedAccountCannotUpdateFeeSchedule() { + return unauthorizedAccountCannotUpdateSpecialFile(FEE_SCHEDULE, NEW_CONTENTS); + } + + @HapiTest + final Stream account2CanUpdateExchangeRates() { + return specialAccountCanUpdateSpecialFile(GENESIS, EXCHANGE_RATES, IGNORE, IGNORE); + } + + @HapiTest + final Stream account50CanUpdateExchangeRates() { + return specialAccountCanUpdateSpecialFile(SYSTEM_ADMIN, EXCHANGE_RATES, IGNORE, IGNORE); + } + + @HapiTest + final Stream account57CanUpdateExchangeRates() { + return specialAccountCanUpdateSpecialFile(EXCHANGE_RATE_CONTROL, EXCHANGE_RATES, IGNORE, IGNORE); + } + + @HapiTest + final Stream unauthorizedAccountCannotUpdateExchangeRates() { + return unauthorizedAccountCannotUpdateSpecialFile(EXCHANGE_RATES, NEW_CONTENTS); + } + + final Stream specialAccountCanUpdateSpecialPropertyFile( final String specialAccount, final String specialFile, final String property, final String expected) { return specialAccountCanUpdateSpecialPropertyFile(specialAccount, specialFile, property, expected, true); } - @BddMethodIsNotATest - final HapiSpec specialAccountCanUpdateSpecialPropertyFile( + final Stream specialAccountCanUpdateSpecialPropertyFile( final String specialAccount, final String specialFile, final String property, @@ -143,14 +222,12 @@ private HapiSpecOperation propertyFileValidationOp( }); } - @BddMethodIsNotATest - final HapiSpec specialAccountCanUpdateSpecialFile( + final Stream specialAccountCanUpdateSpecialFile( final String specialAccount, final String specialFile, final String target, final String replacement) { return specialAccountCanUpdateSpecialFile(specialAccount, specialFile, target, replacement, true); } - @BddMethodIsNotATest - final HapiSpec specialAccountCanUpdateSpecialFile( + final Stream specialAccountCanUpdateSpecialFile( final String specialAccount, final String specialFile, final String target, @@ -165,8 +242,7 @@ final HapiSpec specialAccountCanUpdateSpecialFile( : (new String(contents).replace(target, replacement)).getBytes()); } - @BddMethodIsNotATest - final HapiSpec specialAccountCanUpdateSpecialFile( + final Stream specialAccountCanUpdateSpecialFile( final String specialAccount, final String specialFile, final boolean isFree, @@ -207,8 +283,8 @@ private HapiSpecOperation[] validateAndCleanUpOps( return ArrayUtils.addAll(accountBalanceUnchanged, opsArray); } - @BddMethodIsNotATest - final HapiSpec unauthorizedAccountCannotUpdateSpecialFile(final String specialFile, final String newContents) { + final Stream unauthorizedAccountCannotUpdateSpecialFile( + final String specialFile, final String newContents) { return defaultHapiSpec("UnauthorizedAccountCannotUpdate" + specialFile) .given(cryptoCreate("unauthorizedAccount")) .when() @@ -218,123 +294,6 @@ final HapiSpec unauthorizedAccountCannotUpdateSpecialFile(final String specialFi .hasPrecheck(AUTHORIZATION_FAILED)); } - @HapiTest - final HapiSpec account2CanUpdateApplicationProperties() { - return specialAccountCanUpdateSpecialPropertyFile(GENESIS, APP_PROPERTIES, "throttlingTps", "10"); - } - - @HapiTest - final HapiSpec account50CanUpdateApplicationProperties() { - return specialAccountCanUpdateSpecialPropertyFile(SYSTEM_ADMIN, APP_PROPERTIES, "getReceiptTps", "100"); - } - - @HapiTest - final HapiSpec unauthorizedAccountCannotUpdateApplicationProperties() { - return unauthorizedAccountCannotUpdateSpecialFile(APP_PROPERTIES, NEW_CONTENTS); - } - - @HapiTest - final HapiSpec account2CanUpdateApiPermissions() { - return specialAccountCanUpdateSpecialPropertyFile(GENESIS, API_PERMISSIONS, "createTopic", "1-*"); - } - - @HapiTest - final HapiSpec account50CanUpdateApiPermissions() { - return specialAccountCanUpdateSpecialPropertyFile(SYSTEM_ADMIN, API_PERMISSIONS, "updateFile", "1-*"); - } - - @HapiTest - final HapiSpec unauthorizedAccountCannotUpdateApiPermissions() { - return unauthorizedAccountCannotUpdateSpecialFile(API_PERMISSIONS, NEW_CONTENTS); - } - - @HapiTest - final HapiSpec account2CanUpdateAddressBook() { - return specialAccountCanUpdateSpecialFile( - GENESIS, ADDRESS_BOOK, true, contents -> extendedBioAddressBook(contents, TARGET_MEMO, REPLACE_MEMO)); - } - - @HapiTest - final HapiSpec account50CanUpdateAddressBook() { - return specialAccountCanUpdateSpecialFile(SYSTEM_ADMIN, ADDRESS_BOOK, TARGET_MEMO, REPLACE_MEMO); - } - - @HapiTest - final HapiSpec account55CanUpdateAddressBook() { - return specialAccountCanUpdateSpecialFile(ADDRESS_BOOK_CONTROL, ADDRESS_BOOK, TARGET_MEMO, REPLACE_MEMO, false); - } - - @HapiTest - final HapiSpec unauthorizedAccountCannotUpdateAddressBook() { - return unauthorizedAccountCannotUpdateSpecialFile(ADDRESS_BOOK, NEW_CONTENTS); - } - - @HapiTest - final HapiSpec account2CanUpdateNodeDetails() { - return specialAccountCanUpdateSpecialFile( - GENESIS, NODE_DETAILS, true, contents -> extendedBioNodeDetails(contents, TARGET_MEMO, REPLACE_MEMO)); - } - - @HapiTest - final HapiSpec account50CanUpdateNodeDetails() { - return specialAccountCanUpdateSpecialFile(SYSTEM_ADMIN, NODE_DETAILS, TARGET_MEMO, REPLACE_MEMO); - } - - @HapiTest - final HapiSpec account55CanUpdateNodeDetails() { - return specialAccountCanUpdateSpecialFile(ADDRESS_BOOK_CONTROL, NODE_DETAILS, TARGET_MEMO, REPLACE_MEMO, false); - } - - @HapiTest - final HapiSpec unauthorizedAccountCannotUpdateNodeDetails() { - return unauthorizedAccountCannotUpdateSpecialFile(NODE_DETAILS, NEW_CONTENTS); - } - - @HapiTest - final HapiSpec account2CanUpdateFeeSchedule() { - return specialAccountCanUpdateSpecialFile(GENESIS, FEE_SCHEDULE, IGNORE, IGNORE); - } - - @HapiTest - final HapiSpec account50CanUpdateFeeSchedule() { - return specialAccountCanUpdateSpecialFile(SYSTEM_ADMIN, FEE_SCHEDULE, IGNORE, IGNORE); - } - - @HapiTest - final HapiSpec account56CanUpdateFeeSchedule() { - return specialAccountCanUpdateSpecialFile(FEE_SCHEDULE_CONTROL, FEE_SCHEDULE, IGNORE, IGNORE); - } - - @HapiTest - final HapiSpec unauthorizedAccountCannotUpdateFeeSchedule() { - return unauthorizedAccountCannotUpdateSpecialFile(FEE_SCHEDULE, NEW_CONTENTS); - } - - @HapiTest - final HapiSpec account2CanUpdateExchangeRates() { - return specialAccountCanUpdateSpecialFile(GENESIS, EXCHANGE_RATES, IGNORE, IGNORE); - } - - @HapiTest - final HapiSpec account50CanUpdateExchangeRates() { - return specialAccountCanUpdateSpecialFile(SYSTEM_ADMIN, EXCHANGE_RATES, IGNORE, IGNORE); - } - - @HapiTest - final HapiSpec account57CanUpdateExchangeRates() { - return specialAccountCanUpdateSpecialFile(EXCHANGE_RATE_CONTROL, EXCHANGE_RATES, IGNORE, IGNORE); - } - - @HapiTest - final HapiSpec unauthorizedAccountCannotUpdateExchangeRates() { - return unauthorizedAccountCannotUpdateSpecialFile(EXCHANGE_RATES, NEW_CONTENTS); - } - - @Override - protected Logger getResultsLogger() { - return log; - } - private byte[] extendedBioAddressBook(byte[] contents, String targetMemo, String replaceMemo) { var r = new SplittableRandom(); try { diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/file/ValidateNewAddressBook.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/file/ValidateNewAddressBook.java deleted file mode 100644 index 81748313079e..000000000000 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/file/ValidateNewAddressBook.java +++ /dev/null @@ -1,70 +0,0 @@ -/* - * Copyright (C) 2021-2024 Hedera Hashgraph, LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.hedera.services.bdd.suites.file; - -import static com.hedera.services.bdd.spec.HapiSpec.defaultHapiSpec; -import static com.hedera.services.bdd.spec.queries.QueryVerbs.getFileContents; -import static com.hedera.services.bdd.suites.file.FetchSystemFiles.unchecked; -import static com.hedera.services.bdd.suites.utils.sysfiles.serdes.StandardSerdes.SYS_FILE_SERDES; - -import com.hedera.services.bdd.spec.HapiSpec; -import com.hedera.services.bdd.suites.HapiSuite; -import com.hederahashgraph.api.proto.java.NodeAddressBook; -import java.nio.file.Path; -import java.util.List; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; - -public class ValidateNewAddressBook extends HapiSuite { - private static final Logger log = LogManager.getLogger(ValidateNewAddressBook.class); - - public static void main(String... args) { - new ValidateNewAddressBook().runSuiteSync(); - } - - final String TARGET_DIR = "./remote-system-files"; - - @Override - public List getSpecsInSuite() { - return List.of(fetchFiles()); - } - - final HapiSpec fetchFiles() { - return defaultHapiSpec("ValidateNewAddressBook") - .given() - .when() - .then( - getFileContents(NODE_DETAILS) - .saveTo(path("nodeDetails.bin")) - .saveReadableTo(unchecked(NodeAddressBook::parseFrom), path("nodeDetails.json")), - getFileContents(ADDRESS_BOOK) - .saveTo(path("addressBook.bin")) - .saveReadableTo(unchecked(NodeAddressBook::parseFrom), path("addressBook.txt")), - getFileContents(NODE_DETAILS) - .saveTo(path("nodeDetails.bin")) - .saveReadableTo(SYS_FILE_SERDES.get(102L)::fromRawFile, path("nodeDetails.json"))); - } - - private String path(String file) { - return Path.of(TARGET_DIR, file).toString(); - } - - @Override - protected Logger getResultsLogger() { - return log; - } -} diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/file/negative/AppendFailuresSpec.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/file/negative/AppendFailuresSpec.java deleted file mode 100644 index 8ef7f8b12f43..000000000000 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/file/negative/AppendFailuresSpec.java +++ /dev/null @@ -1,69 +0,0 @@ -/* - * Copyright (C) 2020-2024 Hedera Hashgraph, LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.hedera.services.bdd.suites.file.negative; - -import static com.hedera.services.bdd.spec.HapiSpec.defaultHapiSpec; -import static com.hedera.services.bdd.spec.queries.QueryVerbs.getFileContents; -import static com.hedera.services.bdd.spec.transactions.TxnVerbs.fileAppend; -import static com.hedera.services.bdd.spec.transactions.TxnVerbs.fileCreate; -import static com.hedera.services.bdd.spec.utilops.UtilVerbs.overriding; -import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.MAX_FILE_SIZE_EXCEEDED; - -import com.hedera.services.bdd.junit.HapiTest; -import com.hedera.services.bdd.junit.HapiTestSuite; -import com.hedera.services.bdd.spec.HapiSpec; -import com.hedera.services.bdd.suites.HapiSuite; -import java.util.Arrays; -import java.util.List; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; - -@HapiTestSuite -public class AppendFailuresSpec extends HapiSuite { - private static final Logger log = LogManager.getLogger(AppendFailuresSpec.class); - - public static void main(String... args) { - new AppendFailuresSpec().runSuiteSync(); - } - - @Override - public List getSpecsInSuite() { - return List.of(handleRejectsOversized()); - } - - @HapiTest - final HapiSpec handleRejectsOversized() { - byte[] BYTES_3K_MINUS1 = new byte[3 * 1024 - 1]; - Arrays.fill(BYTES_3K_MINUS1, (byte) 0xAB); - byte[] BYTES_1 = new byte[] {(byte) 0xAB}; - - return defaultHapiSpec("handleRejectsMissingWacl") - .given( - getFileContents(APP_PROPERTIES).saveTo("tmp-application.properties"), - overriding("files.maxSizeKb", "3")) - .when( - fileCreate("file").contents(BYTES_3K_MINUS1), - fileAppend("file").content(BYTES_1), - fileAppend("file").content(BYTES_1).hasKnownStatus(MAX_FILE_SIZE_EXCEEDED)) - .then(overriding("files.maxSizeKb", "1024")); - } - - @Override - protected Logger getResultsLogger() { - return log; - } -} diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/file/negative/CreateFailuresSpec.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/file/negative/CreateFailuresSpec.java deleted file mode 100644 index 37a80b19940c..000000000000 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/file/negative/CreateFailuresSpec.java +++ /dev/null @@ -1,65 +0,0 @@ -/* - * Copyright (C) 2020-2024 Hedera Hashgraph, LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.hedera.services.bdd.suites.file.negative; - -import static com.hedera.services.bdd.spec.HapiSpec.defaultHapiSpec; -import static com.hedera.services.bdd.spec.transactions.TxnVerbs.fileCreate; - -import com.hedera.services.bdd.junit.HapiTest; -import com.hedera.services.bdd.junit.HapiTestSuite; -import com.hedera.services.bdd.spec.HapiSpec; -import com.hedera.services.bdd.suites.HapiSuite; -import com.hederahashgraph.api.proto.java.ResponseCodeEnum; -import java.time.Instant; -import java.util.List; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; - -@HapiTestSuite -public class CreateFailuresSpec extends HapiSuite { - private static final Logger log = LogManager.getLogger(CreateFailuresSpec.class); - - public static void main(String... args) { - new CreateFailuresSpec().runSuiteSync(); - } - - @Override - public List getSpecsInSuite() { - return List.of(new HapiSpec[] { - // handleRejectsMissingWacl(), - precheckRejectsBadEffectiveAutoRenewPeriod(), - }); - } - - @HapiTest - final HapiSpec precheckRejectsBadEffectiveAutoRenewPeriod() { - var now = Instant.now(); - System.out.println(now.getEpochSecond()); - - return defaultHapiSpec("precheckRejectsBadEffectiveAutoRenewPeriod") - .given() - .when() - .then(fileCreate("notHere") - .lifetime(-60L) - .hasPrecheck(ResponseCodeEnum.AUTORENEW_DURATION_NOT_IN_RANGE)); - } - - @Override - protected Logger getResultsLogger() { - return log; - } -} diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/file/negative/DeleteFailuresSpec.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/file/negative/DeleteFailuresSpec.java deleted file mode 100644 index 9d87c0f643f5..000000000000 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/file/negative/DeleteFailuresSpec.java +++ /dev/null @@ -1,65 +0,0 @@ -/* - * Copyright (C) 2020-2024 Hedera Hashgraph, LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.hedera.services.bdd.suites.file.negative; - -import static com.hedera.services.bdd.spec.HapiSpec.defaultHapiSpec; -import static com.hedera.services.bdd.spec.transactions.TxnVerbs.fileCreate; -import static com.hedera.services.bdd.spec.transactions.TxnVerbs.fileDelete; - -import com.hedera.services.bdd.junit.HapiTest; -import com.hedera.services.bdd.junit.HapiTestSuite; -import com.hedera.services.bdd.spec.HapiSpec; -import com.hedera.services.bdd.suites.HapiSuite; -import com.hederahashgraph.api.proto.java.ResponseCodeEnum; -import java.util.List; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; - -@HapiTestSuite -public class DeleteFailuresSpec extends HapiSuite { - private static final Logger log = LogManager.getLogger(DeleteFailuresSpec.class); - - public static void main(String... args) { - new DeleteFailuresSpec().runSuiteSync(); - } - - @Override - public List getSpecsInSuite() { - return List.of(handleRejectsMissingFile(), handleRejectsDeletedFile()); - } - - @HapiTest - final HapiSpec handleRejectsMissingFile() { - return defaultHapiSpec("handleRejectsMissingFile") - .given() - .when() - .then(fileDelete("1.2.3").signedBy(GENESIS).hasKnownStatus(ResponseCodeEnum.INVALID_FILE_ID)); - } - - @HapiTest - final HapiSpec handleRejectsDeletedFile() { - return defaultHapiSpec("handleRejectsDeletedFile") - .given(fileCreate("tbd")) - .when(fileDelete("tbd")) - .then(fileDelete("tbd").hasKnownStatus(ResponseCodeEnum.FILE_DELETED)); - } - - @Override - protected Logger getResultsLogger() { - return log; - } -} diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/file/negative/QueryFailuresSpec.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/file/negative/QueryFailuresSpec.java deleted file mode 100644 index 7a219e5d1147..000000000000 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/file/negative/QueryFailuresSpec.java +++ /dev/null @@ -1,76 +0,0 @@ -/* - * Copyright (C) 2020-2024 Hedera Hashgraph, LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.hedera.services.bdd.suites.file.negative; - -import static com.hedera.services.bdd.spec.HapiSpec.defaultHapiSpec; -import static com.hedera.services.bdd.spec.queries.QueryVerbs.getFileContents; -import static com.hedera.services.bdd.spec.queries.QueryVerbs.getFileInfo; -import static com.hedera.services.bdd.spec.transactions.TxnVerbs.fileCreate; -import static com.hedera.services.bdd.spec.transactions.TxnVerbs.fileDelete; -import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.INVALID_FILE_ID; -import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.OK; - -import com.hedera.services.bdd.junit.HapiTest; -import com.hedera.services.bdd.junit.HapiTestSuite; -import com.hedera.services.bdd.spec.HapiSpec; -import com.hedera.services.bdd.suites.HapiSuite; -import java.util.List; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; - -@HapiTestSuite -public class QueryFailuresSpec extends HapiSuite { - private static final Logger log = LogManager.getLogger(QueryFailuresSpec.class); - - public static void main(String... args) { - new QueryFailuresSpec().runSuiteSync(); - } - - @Override - public boolean canRunConcurrent() { - return true; - } - - @Override - public List getSpecsInSuite() { - return List.of(getsExpectedRejections()); - } - - @HapiTest - final HapiSpec getsExpectedRejections() { - return defaultHapiSpec("getsExpectedRejections") - .given(fileCreate("tbd"), fileDelete("tbd")) - .when() - .then( - getFileInfo("1.2.3").nodePayment(1_234L).hasAnswerOnlyPrecheck(INVALID_FILE_ID), - getFileContents("1.2.3").nodePayment(1_234L).hasAnswerOnlyPrecheck(INVALID_FILE_ID), - getFileContents("tbd") - .nodePayment(1_234L) - .hasAnswerOnlyPrecheck(OK) - .logged(), - getFileInfo("tbd") - .nodePayment(1_234L) - .hasAnswerOnlyPrecheck(OK) - .hasDeleted(true) - .logged()); - } - - @Override - protected Logger getResultsLogger() { - return log; - } -} diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/file/negative/UpdateFailuresSpec.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/file/negative/UpdateFailuresSpec.java index 1b0dcc4f83db..25f3e97297c9 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/file/negative/UpdateFailuresSpec.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/file/negative/UpdateFailuresSpec.java @@ -22,6 +22,14 @@ import static com.hedera.services.bdd.spec.transactions.TxnVerbs.fileDelete; import static com.hedera.services.bdd.spec.transactions.TxnVerbs.fileUpdate; import static com.hedera.services.bdd.spec.utilops.UtilVerbs.withOpContext; +import static com.hedera.services.bdd.suites.HapiSuite.ADDRESS_BOOK; +import static com.hedera.services.bdd.suites.HapiSuite.API_PERMISSIONS; +import static com.hedera.services.bdd.suites.HapiSuite.APP_PROPERTIES; +import static com.hedera.services.bdd.suites.HapiSuite.EXCHANGE_RATES; +import static com.hedera.services.bdd.suites.HapiSuite.EXCHANGE_RATE_CONTROL; +import static com.hedera.services.bdd.suites.HapiSuite.FEE_SCHEDULE; +import static com.hedera.services.bdd.suites.HapiSuite.GENESIS; +import static com.hedera.services.bdd.suites.HapiSuite.NODE_DETAILS; import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.AUTHORIZATION_FAILED; import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.AUTORENEW_DURATION_NOT_IN_RANGE; import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.FILE_DELETED; @@ -31,50 +39,22 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import com.hedera.services.bdd.junit.HapiTest; -import com.hedera.services.bdd.junit.HapiTestSuite; -import com.hedera.services.bdd.spec.HapiSpec; import com.hedera.services.bdd.spec.queries.QueryVerbs; import com.hedera.services.bdd.spec.utilops.CustomSpecAssert; import com.hedera.services.bdd.spec.utilops.UtilVerbs; -import com.hedera.services.bdd.suites.HapiSuite; import com.hederahashgraph.api.proto.java.ResponseCodeEnum; import java.time.Instant; -import java.util.List; import java.util.concurrent.atomic.AtomicLong; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; +import java.util.stream.Stream; +import org.junit.jupiter.api.DynamicTest; -@HapiTestSuite -public class UpdateFailuresSpec extends HapiSuite { +public class UpdateFailuresSpec { private static final long A_LOT = 1_234_567_890L; - private static final Logger LOG = LogManager.getLogger(UpdateFailuresSpec.class); private static final String CIVILIAN = "civilian"; - private static final String UNIQUE_PAYER_ACCOUNT = "uniquePayerAccount"; - - public static void main(String... args) { - new UpdateFailuresSpec().runSuiteAsync(); - } - - @Override - public boolean canRunConcurrent() { - return true; - } - - @Override - public List getSpecsInSuite() { - return List.of( - precheckAllowsMissing(), - precheckAllowsDeleted(), - precheckRejectsPrematureExpiry(), - precheckAllowsBadEncoding(), - handleIgnoresEarlierExpiry(), - precheckRejectsUnauthorized(), - confusedUpdateCantExtendExpiry()); - } @HapiTest - final HapiSpec confusedUpdateCantExtendExpiry() { + final Stream confusedUpdateCantExtendExpiry() { // this test verify that the exchange rate file parsed correctly on update, it doesn't check expiry var initialExpiry = new AtomicLong(); var extension = 1_000L; @@ -94,7 +74,7 @@ final HapiSpec confusedUpdateCantExtendExpiry() { } @HapiTest - final HapiSpec precheckRejectsUnauthorized() { + final Stream precheckRejectsUnauthorized() { // this test is to verify that the system files cannot be updated without privileged account return defaultHapiSpec("precheckRejectsUnauthorized") .given(cryptoCreate(CIVILIAN)) @@ -109,7 +89,7 @@ final HapiSpec precheckRejectsUnauthorized() { } @HapiTest - final HapiSpec precheckAllowsMissing() { + final Stream precheckAllowsMissing() { return defaultHapiSpec("PrecheckAllowsMissing") .given() .when() @@ -122,7 +102,7 @@ final HapiSpec precheckAllowsMissing() { } @HapiTest - final HapiSpec precheckAllowsDeleted() { + final Stream precheckAllowsDeleted() { return defaultHapiSpec("PrecheckAllowsDeleted") .given(fileCreate("tbd")) .when(fileDelete("tbd")) @@ -130,7 +110,7 @@ final HapiSpec precheckAllowsDeleted() { } @HapiTest - final HapiSpec precheckRejectsPrematureExpiry() { + final Stream precheckRejectsPrematureExpiry() { long now = Instant.now().getEpochSecond(); return defaultHapiSpec("PrecheckRejectsPrematureExpiry") .given(fileCreate("file")) @@ -142,7 +122,7 @@ final HapiSpec precheckRejectsPrematureExpiry() { } @HapiTest - final HapiSpec precheckAllowsBadEncoding() { + final Stream precheckAllowsBadEncoding() { return defaultHapiSpec("PrecheckAllowsBadEncoding") .given(fileCreate("file")) .when() @@ -156,7 +136,7 @@ final HapiSpec precheckAllowsBadEncoding() { @SuppressWarnings("java:S5960") @HapiTest - final HapiSpec handleIgnoresEarlierExpiry() { + final Stream handleIgnoresEarlierExpiry() { var initialExpiry = new AtomicLong(); return defaultHapiSpec("HandleIgnoresEarlierExpiry") @@ -176,9 +156,4 @@ final HapiSpec handleIgnoresEarlierExpiry() { assertEquals(initialExpiry.get(), currExpiry, "Expiry changed unexpectedly!"); })); } - - @Override - protected Logger getResultsLogger() { - return LOG; - } } diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/file/positive/CreateSuccessSpec.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/file/positive/CreateSuccessSpec.java deleted file mode 100644 index 5376bfd11296..000000000000 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/file/positive/CreateSuccessSpec.java +++ /dev/null @@ -1,84 +0,0 @@ -/* - * Copyright (C) 2020-2024 Hedera Hashgraph, LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.hedera.services.bdd.suites.file.positive; - -import static com.hedera.services.bdd.spec.HapiSpec.defaultHapiSpec; -import static com.hedera.services.bdd.spec.keys.KeyShape.SIMPLE; -import static com.hedera.services.bdd.spec.keys.KeyShape.listOf; -import static com.hedera.services.bdd.spec.keys.KeyShape.sigs; -import static com.hedera.services.bdd.spec.keys.KeyShape.threshOf; -import static com.hedera.services.bdd.spec.keys.SigControl.OFF; -import static com.hedera.services.bdd.spec.keys.SigControl.ON; -import static com.hedera.services.bdd.spec.transactions.TxnVerbs.fileCreate; - -import com.google.protobuf.ByteString; -import com.hedera.services.bdd.junit.HapiTest; -import com.hedera.services.bdd.junit.HapiTestSuite; -import com.hedera.services.bdd.spec.HapiSpec; -import com.hedera.services.bdd.spec.keys.ControlForKey; -import com.hedera.services.bdd.spec.queries.QueryVerbs; -import com.hedera.services.bdd.spec.utilops.UtilVerbs; -import com.hedera.services.bdd.suites.HapiSuite; -import java.time.Instant; -import java.util.List; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; - -@HapiTestSuite -public class CreateSuccessSpec extends HapiSuite { - private static final Logger log = LogManager.getLogger(CreateSuccessSpec.class); - - public static void main(String... args) { - new CreateSuccessSpec().runSuiteSync(); - } - - @Override - public List getSpecsInSuite() { - return List.of(targetsAppear()); - } - - @HapiTest - final HapiSpec targetsAppear() { - var lifetime = 100_000L; - var requestedExpiry = Instant.now().getEpochSecond() + lifetime; - var contents = "SOMETHING".getBytes(); - var newWacl = listOf(SIMPLE, listOf(3), threshOf(1, 3)); - var newWaclSigs = newWacl.signedWith(sigs(ON, sigs(ON, ON, ON), sigs(OFF, OFF, ON))); - - return defaultHapiSpec("targetsAppear") - .given(UtilVerbs.newKeyNamed("newWacl").shape(newWacl)) - .when(fileCreate("file") - .via("createTxn") - .contents(contents) - .key("newWacl") - .expiry(requestedExpiry) - .signedBy(GENESIS, "newWacl") - .sigControl(ControlForKey.forKey("newWacl", newWaclSigs))) - .then( - QueryVerbs.getFileInfo("file") - .hasDeleted(false) - .hasWacl("newWacl") - .hasExpiryPassing(expiry -> expiry == requestedExpiry), - QueryVerbs.getFileContents("file") - .hasByteStringContents(ignore -> ByteString.copyFrom(contents))); - } - - @Override - protected Logger getResultsLogger() { - return log; - } -} diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/file/positive/SysDelSysUndelSpec.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/file/positive/SysDelSysUndelSpec.java index 17ba8fc42aa3..5011a6315239 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/file/positive/SysDelSysUndelSpec.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/file/positive/SysDelSysUndelSpec.java @@ -22,8 +22,13 @@ import static com.hedera.services.bdd.spec.transactions.TxnVerbs.fileCreate; import static com.hedera.services.bdd.spec.transactions.TxnVerbs.systemFileDelete; import static com.hedera.services.bdd.spec.transactions.TxnVerbs.systemFileUndelete; -import static com.hedera.services.bdd.spec.utilops.UtilVerbs.submitModified; +import static com.hedera.services.bdd.spec.utilops.UtilVerbs.submitModifiedWithFixedPayer; import static com.hedera.services.bdd.spec.utilops.mod.ModificationUtils.withSuccessivelyVariedBodyIds; +import static com.hedera.services.bdd.suites.HapiSuite.ADDRESS_BOOK; +import static com.hedera.services.bdd.suites.HapiSuite.GENESIS; +import static com.hedera.services.bdd.suites.HapiSuite.SYSTEM_DELETE_ADMIN; +import static com.hedera.services.bdd.suites.HapiSuite.SYSTEM_UNDELETE_ADMIN; +import static com.hedera.services.bdd.suites.HapiSuite.THREE_MONTHS_IN_SECONDS; import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.AUTHORIZATION_FAILED; import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.ENTITY_NOT_ALLOWED_TO_DELETE; import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.INVALID_FILE_ID; @@ -31,59 +36,35 @@ import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.OK; import com.hedera.services.bdd.junit.HapiTest; -import com.hedera.services.bdd.junit.HapiTestSuite; -import com.hedera.services.bdd.spec.HapiSpec; import com.hedera.services.bdd.spec.utilops.UtilVerbs; -import com.hedera.services.bdd.suites.HapiSuite; import java.time.Instant; -import java.util.List; import java.util.concurrent.atomic.AtomicLong; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; - -@HapiTestSuite -public class SysDelSysUndelSpec extends HapiSuite { - private static final Logger log = LogManager.getLogger(SysDelSysUndelSpec.class); +import java.util.stream.Stream; +import org.junit.jupiter.api.DynamicTest; +public class SysDelSysUndelSpec { byte[] ORIG_FILE = "SOMETHING".getBytes(); - public static void main(String... args) { - new SysDelSysUndelSpec().runSuiteAsync(); - } - - @Override - public boolean canRunConcurrent() { - return true; - } - - @Override - public List getSpecsInSuite() { - return List.of( - systemDeleteThenUndeleteRestoresContentsAndExpiry(), - systemDeleteWithPastExpiryDestroysFile(), - distinguishesAdminPrivileges()); - } - @HapiTest - public HapiSpec sysDelIdVariantsTreatedAsExpected() { + final Stream sysDelIdVariantsTreatedAsExpected() { return defaultHapiSpec("sysDelIdVariantsTreatedAsExpected") .given(fileCreate("misc").contents(ORIG_FILE)) .when() - .then(submitModified(withSuccessivelyVariedBodyIds(), () -> systemFileDelete("misc") + .then(submitModifiedWithFixedPayer(withSuccessivelyVariedBodyIds(), () -> systemFileDelete("misc") .payingWith(SYSTEM_DELETE_ADMIN))); } @HapiTest - public HapiSpec sysUndelIdVariantsTreatedAsExpected() { + final Stream sysUndelIdVariantsTreatedAsExpected() { return defaultHapiSpec("sysUndelIdVariantsTreatedAsExpected") .given(fileCreate("misc").contents(ORIG_FILE)) .when(systemFileDelete("misc").payingWith(SYSTEM_DELETE_ADMIN)) - .then(submitModified(withSuccessivelyVariedBodyIds(), () -> systemFileUndelete("misc") + .then(submitModifiedWithFixedPayer(withSuccessivelyVariedBodyIds(), () -> systemFileUndelete("misc") .payingWith(SYSTEM_UNDELETE_ADMIN))); } @HapiTest - final HapiSpec distinguishesAdminPrivileges() { + final Stream distinguishesAdminPrivileges() { final var lifetime = THREE_MONTHS_IN_SECONDS; return defaultHapiSpec("DistinguishesAdminPrivileges") @@ -100,7 +81,7 @@ final HapiSpec distinguishesAdminPrivileges() { } @HapiTest - final HapiSpec systemDeleteWithPastExpiryDestroysFile() { + final Stream systemDeleteWithPastExpiryDestroysFile() { final var lifetime = THREE_MONTHS_IN_SECONDS; return defaultHapiSpec("systemDeleteWithPastExpiryDestroysFile") @@ -114,7 +95,7 @@ final HapiSpec systemDeleteWithPastExpiryDestroysFile() { } @HapiTest - final HapiSpec systemDeleteThenUndeleteRestoresContentsAndExpiry() { + final Stream systemDeleteThenUndeleteRestoresContentsAndExpiry() { var now = Instant.now().getEpochSecond(); var lifetime = THREE_MONTHS_IN_SECONDS; AtomicLong initExpiry = new AtomicLong(); @@ -140,9 +121,4 @@ final HapiSpec systemDeleteThenUndeleteRestoresContentsAndExpiry() { getFileContents("misc").hasContents(ignore -> ORIG_FILE), getFileInfo("misc").hasExpiry(initExpiry::get)); } - - @Override - protected Logger getResultsLogger() { - return log; - } } diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/freeze/CryptoTransferThenFreezeTest.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/freeze/CryptoTransferThenFreezeTest.java deleted file mode 100644 index df9dbff342ee..000000000000 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/freeze/CryptoTransferThenFreezeTest.java +++ /dev/null @@ -1,64 +0,0 @@ -/* - * Copyright (C) 2020-2024 Hedera Hashgraph, LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.hedera.services.bdd.suites.freeze; - -import static com.hedera.services.bdd.spec.HapiSpec.defaultHapiSpec; -import static com.hedera.services.bdd.spec.utilops.UtilVerbs.freezeOnly; -import static com.hedera.services.bdd.spec.utilops.UtilVerbs.logIt; -import static com.hedera.services.bdd.spec.utilops.UtilVerbs.waitForNodesToFreeze; -import static com.hedera.services.bdd.spec.utilops.UtilVerbs.withOpContext; - -import com.hedera.services.bdd.spec.HapiSpec; -import com.hedera.services.bdd.suites.perf.PerfTestLoadSettings; -import com.hedera.services.bdd.suites.perf.crypto.CryptoTransferLoadTest; -import java.util.List; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; - -public class CryptoTransferThenFreezeTest extends CryptoTransferLoadTest { - private static final Logger log = LogManager.getLogger(CryptoTransferThenFreezeTest.class); - - public static void main(String... args) { - parseArgs(args); - - CryptoTransferThenFreezeTest suite = new CryptoTransferThenFreezeTest(); - suite.runSuiteSync(); - } - - @Override - public List getSpecsInSuite() { - return List.of(runCryptoTransfers(), freezeAfterTransfers()); - } - - final HapiSpec freezeAfterTransfers() { - PerfTestLoadSettings settings = new PerfTestLoadSettings(); - return defaultHapiSpec("FreezeAfterTransfers") - .given( - withOpContext( - (spec, ignore) -> settings.setFrom(spec.setup().ciPropertiesMap())), - logIt(ignore -> settings.toString())) - .when(freezeOnly().startingIn(30).seconds().payingWith(GENESIS)) - .then( - // wait for the nodes to freeze (fails if they don't freeze) - waitForNodesToFreeze(75)); - } - - @Override - protected Logger getResultsLogger() { - return log; - } -} diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/freeze/FreezeAbort.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/freeze/FreezeAbort.java index 75b32774b279..25b95d87652e 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/freeze/FreezeAbort.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/freeze/FreezeAbort.java @@ -18,12 +18,13 @@ import static com.hedera.services.bdd.spec.HapiSpec.defaultHapiSpec; -import com.hedera.services.bdd.spec.HapiSpec; import com.hedera.services.bdd.spec.utilops.UtilVerbs; import com.hedera.services.bdd.suites.HapiSuite; import java.util.List; +import java.util.stream.Stream; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; +import org.junit.jupiter.api.DynamicTest; public final class FreezeAbort extends HapiSuite { private static final Logger log = LogManager.getLogger(FreezeAbort.class); @@ -38,11 +39,11 @@ protected Logger getResultsLogger() { } @Override - public List getSpecsInSuite() { - return List.of(new HapiSpec[] {freezeAbort()}); + public List> getSpecsInSuite() { + return List.of(freezeAbort()); } - final HapiSpec freezeAbort() { + final Stream freezeAbort() { return defaultHapiSpec("FreezeAbort") .given() .when(UtilVerbs.freezeAbort().payingWith(GENESIS)) diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/freeze/FreezeDockerNetwork.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/freeze/FreezeDockerNetwork.java index fc921347ecfc..0a0528c5eaac 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/freeze/FreezeDockerNetwork.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/freeze/FreezeDockerNetwork.java @@ -19,12 +19,13 @@ import static com.hedera.services.bdd.spec.HapiSpec.customHapiSpec; import static com.hedera.services.bdd.spec.utilops.UtilVerbs.freezeOnly; -import com.hedera.services.bdd.spec.HapiSpec; import com.hedera.services.bdd.suites.HapiSuite; import java.util.List; import java.util.Map; +import java.util.stream.Stream; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; +import org.junit.jupiter.api.DynamicTest; public class FreezeDockerNetwork extends HapiSuite { private static final Logger log = LogManager.getLogger(FreezeDockerNetwork.class); @@ -34,13 +35,11 @@ public static void main(String... args) { } @Override - public List getSpecsInSuite() { - return List.of(new HapiSpec[] { - justFreeze(), - }); + public List> getSpecsInSuite() { + return List.of(justFreeze()); } - final HapiSpec justFreeze() { + final Stream justFreeze() { return customHapiSpec("JustFreeze") .withProperties(Map.of("nodes", "127.0.0.1:50213:0.0.3,127.0.0.1:50214:0.0.4,127.0.0.1:50215:0.0.5")) .given() diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/freeze/FreezeIntellijNetwork.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/freeze/FreezeIntellijNetwork.java index 1795464c7f28..a25cb8672ec2 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/freeze/FreezeIntellijNetwork.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/freeze/FreezeIntellijNetwork.java @@ -19,11 +19,12 @@ import static com.hedera.services.bdd.spec.HapiSpec.defaultHapiSpec; import static com.hedera.services.bdd.spec.utilops.UtilVerbs.freezeOnly; -import com.hedera.services.bdd.spec.HapiSpec; import com.hedera.services.bdd.suites.HapiSuite; import java.util.List; +import java.util.stream.Stream; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; +import org.junit.jupiter.api.DynamicTest; public class FreezeIntellijNetwork extends HapiSuite { private static final Logger log = LogManager.getLogger(FreezeIntellijNetwork.class); @@ -33,13 +34,11 @@ public static void main(String... args) { } @Override - public List getSpecsInSuite() { - return List.of(new HapiSpec[] { - justFreeze(), - }); + public List> getSpecsInSuite() { + return List.of(justFreeze()); } - final HapiSpec justFreeze() { + final Stream justFreeze() { return defaultHapiSpec("JustFreeze") .given() .when() diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/freeze/FreezeSuite.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/freeze/FreezeSuite.java deleted file mode 100644 index b5748752f688..000000000000 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/freeze/FreezeSuite.java +++ /dev/null @@ -1,90 +0,0 @@ -/* - * Copyright (C) 2020-2024 Hedera Hashgraph, LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.hedera.services.bdd.suites.freeze; - -import static com.hedera.services.bdd.spec.HapiSpec.defaultHapiSpec; -import static com.hedera.services.bdd.spec.transactions.TxnVerbs.fileUpdate; -import static com.hedera.services.bdd.spec.utilops.UtilVerbs.freezeOnly; -import static com.hedera.services.bdd.suites.utils.ZipUtil.createZip; - -import com.hedera.node.app.hapi.utils.CommonUtils; -import com.hedera.services.bdd.spec.HapiSpec; -import com.hedera.services.bdd.suites.HapiSuite; -import java.io.File; -import java.io.IOException; -import java.nio.file.Files; -import java.util.List; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; - -public class FreezeSuite extends HapiSuite { - private static final Logger log = LogManager.getLogger(FreezeSuite.class); - - private static final String UPLOAD_PATH_PREFIX = "src/main/resource/testfiles/updateFeature/"; - private static final String UPDATE_NEW_FILE = UPLOAD_PATH_PREFIX + "addNewFile/newFile.zip"; - - private static String uploadPath = "updateSettings"; - - public static void main(final String... args) { - if (args.length > 0) { - uploadPath = args[0]; - } - new FreezeSuite().runSuiteSync(); - } - - @Override - protected Logger getResultsLogger() { - return log; - } - - @Override - public List getSpecsInSuite() { - return List.of(uploadNewFile()); - } - - final HapiSpec uploadNewFile() { - String uploadFile = UPDATE_NEW_FILE; - if (uploadPath != null) { - log.info("Creating zip file from {}", uploadPath); - final var zipFile = "Archive.zip"; - createZip(UPLOAD_PATH_PREFIX + uploadPath, zipFile, null); - uploadFile = zipFile; - } - - log.info("Uploading file {}", uploadFile); - final File f = new File(uploadFile); - byte[] bytes = new byte[0]; - try { - bytes = Files.readAllBytes(f.toPath()); - } catch (final IOException e) { - e.printStackTrace(); - } - final byte[] hash = CommonUtils.noThrowSha384HashOf(bytes); - - // mnemonic for file 0.0.150 - final var fileIDString = "UPDATE_FEATURE"; - return defaultHapiSpec("uploadFileAndUpdate") - .given(fileUpdate(fileIDString).path(uploadFile).payingWith(GENESIS)) - .when(freezeOnly() - .withUpdateFile(fileIDString) - .havingHash(hash) - .payingWith(GENESIS) - .startingIn(60) - .seconds()) - .then(); - } -} diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/freeze/FreezeUpgrade.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/freeze/FreezeUpgrade.java index 55dc4fd2d57e..b3d4a425495d 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/freeze/FreezeUpgrade.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/freeze/FreezeUpgrade.java @@ -23,12 +23,13 @@ import static com.hedera.services.bdd.suites.freeze.CommonUpgradeResources.upgradeFileHash; import static com.hedera.services.bdd.suites.freeze.CommonUpgradeResources.upgradeFileId; -import com.hedera.services.bdd.spec.HapiSpec; import com.hedera.services.bdd.spec.utilops.UtilVerbs; import com.hedera.services.bdd.suites.HapiSuite; import java.util.List; +import java.util.stream.Stream; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; +import org.junit.jupiter.api.DynamicTest; public final class FreezeUpgrade extends HapiSuite { private static final Logger log = LogManager.getLogger(FreezeUpgrade.class); @@ -43,11 +44,11 @@ protected Logger getResultsLogger() { } @Override - public List getSpecsInSuite() { - return List.of(new HapiSpec[] {freezeUpgrade()}); + public List> getSpecsInSuite() { + return List.of(freezeUpgrade()); } - final HapiSpec freezeUpgrade() { + final Stream freezeUpgrade() { return defaultHapiSpec("FreezeUpgrade") .given(initializeSettings()) .when(sourcing(() -> UtilVerbs.freezeUpgrade() diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/freeze/PrepareUpgrade.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/freeze/PrepareUpgrade.java index 6430236c95ef..ff2080daf5a4 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/freeze/PrepareUpgrade.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/freeze/PrepareUpgrade.java @@ -22,12 +22,13 @@ import static com.hedera.services.bdd.suites.freeze.CommonUpgradeResources.upgradeFileHash; import static com.hedera.services.bdd.suites.freeze.CommonUpgradeResources.upgradeFileId; -import com.hedera.services.bdd.spec.HapiSpec; import com.hedera.services.bdd.spec.utilops.UtilVerbs; import com.hedera.services.bdd.suites.HapiSuite; import java.util.List; +import java.util.stream.Stream; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; +import org.junit.jupiter.api.DynamicTest; public final class PrepareUpgrade extends HapiSuite { private static final Logger log = LogManager.getLogger(PrepareUpgrade.class); @@ -42,11 +43,11 @@ protected Logger getResultsLogger() { } @Override - public List getSpecsInSuite() { - return List.of(new HapiSpec[] {prepareUpgrade()}); + public List> getSpecsInSuite() { + return List.of(prepareUpgrade()); } - final HapiSpec prepareUpgrade() { + final Stream prepareUpgrade() { return defaultHapiSpec("PrepareUpgrade") .given(initializeSettings()) .when(sourcing(() -> UtilVerbs.prepareUpgrade() diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/freeze/SimpleFreezeOnly.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/freeze/SimpleFreezeOnly.java index ea2b3ad05f80..780e5a093bad 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/freeze/SimpleFreezeOnly.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/freeze/SimpleFreezeOnly.java @@ -20,13 +20,14 @@ import static com.hedera.services.bdd.spec.utilops.UtilVerbs.freezeOnly; import static com.hedera.services.bdd.spec.utilops.UtilVerbs.sleepFor; -import com.hedera.services.bdd.spec.HapiSpec; import com.hedera.services.bdd.suites.HapiSuite; import java.time.Instant; import java.util.Arrays; import java.util.List; +import java.util.stream.Stream; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; +import org.junit.jupiter.api.DynamicTest; public class SimpleFreezeOnly extends HapiSuite { private static final Logger log = LogManager.getLogger(SimpleFreezeOnly.class); @@ -41,11 +42,11 @@ protected Logger getResultsLogger() { } @Override - public List getSpecsInSuite() { + public List> getSpecsInSuite() { return allOf(positiveTests()); } - private List positiveTests() { + private List> positiveTests() { return Arrays.asList(simpleFreezeWithTimestamp()); } @@ -54,7 +55,7 @@ public boolean canRunConcurrent() { return true; } - final HapiSpec simpleFreezeWithTimestamp() { + final Stream simpleFreezeWithTimestamp() { return defaultHapiSpec("SimpleFreezeWithTimeStamp") .given(freezeOnly().payingWith(GENESIS).startingAt(Instant.now().plusSeconds(10))) .when(sleepFor(40000)) diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/freeze/UpdateFileForUpgrade.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/freeze/UpdateFileForUpgrade.java index 8cb70a65dc77..16d05525571d 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/freeze/UpdateFileForUpgrade.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/freeze/UpdateFileForUpgrade.java @@ -24,8 +24,6 @@ import static com.hedera.services.bdd.suites.freeze.CommonUpgradeResources.upgradeFilePath; import com.google.protobuf.ByteString; -import com.hedera.services.bdd.junit.HapiTest; -import com.hedera.services.bdd.spec.HapiSpec; import com.hedera.services.bdd.spec.transactions.TxnUtils; import com.hedera.services.bdd.spec.utilops.UtilVerbs; import com.hedera.services.bdd.suites.HapiSuite; @@ -33,10 +31,12 @@ import java.nio.file.Files; import java.nio.file.Paths; import java.util.List; +import java.util.stream.Stream; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; +import org.junit.jupiter.api.DynamicTest; -public final class UpdateFileForUpgrade extends HapiSuite { +public class UpdateFileForUpgrade extends HapiSuite { private static final Logger log = LogManager.getLogger(UpdateFileForUpgrade.class); public static void main(String... args) { @@ -49,12 +49,11 @@ protected Logger getResultsLogger() { } @Override - public List getSpecsInSuite() { - return List.of(new HapiSpec[] {updateFileForUpgrade()}); + public List> getSpecsInSuite() { + return List.of(updateFileForUpgrade()); } - @HapiTest - final HapiSpec updateFileForUpgrade() { + final Stream updateFileForUpgrade() { return defaultHapiSpec("UpdateFileForUpgrade") .given(initializeSettings()) .when(sourcing(() -> { diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/freeze/UpdateServerFiles.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/freeze/UpdateServerFiles.java index c3495bae4e68..36cb7aefc206 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/freeze/UpdateServerFiles.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/freeze/UpdateServerFiles.java @@ -23,7 +23,6 @@ import com.google.protobuf.ByteString; import com.hedera.node.app.hapi.utils.CommonUtils; -import com.hedera.services.bdd.spec.HapiSpec; import com.hedera.services.bdd.spec.utilops.UtilVerbs; import com.hedera.services.bdd.suites.HapiSuite; import java.io.File; @@ -33,11 +32,16 @@ import java.util.Arrays; import java.util.List; import java.util.Map; +import java.util.stream.Stream; import org.apache.commons.io.FileUtils; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.DynamicTest; +/** + * (FUTURE) Integrate this function to CI in some form? + */ public class UpdateServerFiles extends HapiSuite { private static final Logger log = LogManager.getLogger(UpdateServerFiles.class); private static final String zipFile = "Archive.zip"; @@ -66,17 +70,17 @@ protected Logger getResultsLogger() { } @Override - public List getSpecsInSuite() { + public List> getSpecsInSuite() { return allOf(postiveTests()); } - private List postiveTests() { + private List> postiveTests() { return Arrays.asList(uploadGivenDirectory()); } // Zip all files under target directory and add an unzip and launch script to it // then send to server to update server - final HapiSpec uploadGivenDirectory() { + final Stream uploadGivenDirectory() { log.info("Creating zip file from {}", uploadPath); // create directory if uploadPath doesn't exist diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/freeze/UpgradeSuite.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/freeze/UpgradeSuite.java index f14b83071956..aade86c42786 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/freeze/UpgradeSuite.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/freeze/UpgradeSuite.java @@ -40,7 +40,6 @@ import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.UPDATE_FILE_ID_DOES_NOT_MATCH_PREPARED; import com.google.protobuf.ByteString; -import com.hedera.services.bdd.spec.HapiSpec; import com.hedera.services.bdd.suites.HapiSuite; import com.swirlds.common.utility.CommonUtils; import java.io.IOException; @@ -50,8 +49,10 @@ import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.util.List; +import java.util.stream.Stream; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; +import org.junit.jupiter.api.DynamicTest; public class UpgradeSuite extends HapiSuite { private static final Logger log = LogManager.getLogger(UpgradeSuite.class); @@ -93,27 +94,26 @@ protected Logger getResultsLogger() { } @Override - public List getSpecsInSuite() { - return List.of(new HapiSpec[] { - precheckRejectsUnknownFreezeType(), - freezeOnlyPrecheckRejectsInvalid(), - freezeUpgradeValidationRejectsInvalid(), - prepareUpgradeValidationRejectsInvalid(), - telemetryUpgradeValidationRejectsInvalid(), - canFreezeUpgradeWithPreparedUpgrade(), - canTelemetryUpgradeWithValid(), - freezeAbortIsIdempotent(), - }); + public List> getSpecsInSuite() { + return List.of( + precheckRejectsUnknownFreezeType(), + freezeOnlyPrecheckRejectsInvalid(), + freezeUpgradeValidationRejectsInvalid(), + prepareUpgradeValidationRejectsInvalid(), + telemetryUpgradeValidationRejectsInvalid(), + canFreezeUpgradeWithPreparedUpgrade(), + canTelemetryUpgradeWithValid(), + freezeAbortIsIdempotent()); } - final HapiSpec precheckRejectsUnknownFreezeType() { + final Stream precheckRejectsUnknownFreezeType() { return defaultHapiSpec("PrejeckRejectsUnknownFreezeType") .given() .when() .then(freeze().hasPrecheck(INVALID_FREEZE_TRANSACTION_BODY)); } - final HapiSpec freezeOnlyPrecheckRejectsInvalid() { + final Stream freezeOnlyPrecheckRejectsInvalid() { return defaultHapiSpec("freezeOnlyPrecheckRejectsInvalid") .given() .when() @@ -125,7 +125,7 @@ final HapiSpec freezeOnlyPrecheckRejectsInvalid() { freezeOnly().startingIn(-60).minutes().hasPrecheck(FREEZE_START_TIME_MUST_BE_FUTURE)); } - final HapiSpec freezeUpgradeValidationRejectsInvalid() { + final Stream freezeUpgradeValidationRejectsInvalid() { return defaultHapiSpec("freezeUpgradeValidationRejectsInvalid") .given() .when() @@ -153,14 +153,14 @@ final HapiSpec freezeUpgradeValidationRejectsInvalid() { .hasPrecheck(INVALID_FREEZE_TRANSACTION_BODY)); } - final HapiSpec freezeAbortIsIdempotent() { + final Stream freezeAbortIsIdempotent() { return defaultHapiSpec("FreezeAbortIsIdempotent") .given() .when() .then(freezeAbort().hasKnownStatus(SUCCESS), freezeAbort().hasKnownStatus(SUCCESS)); } - final HapiSpec prepareUpgradeValidationRejectsInvalid() { + final Stream prepareUpgradeValidationRejectsInvalid() { return defaultHapiSpec("PrepareUpgradeValidationRejectsInvalid") .given( fileUpdate(standardUpdateFile) @@ -220,7 +220,7 @@ final HapiSpec prepareUpgradeValidationRejectsInvalid() { freezeAbort()); } - final HapiSpec telemetryUpgradeValidationRejectsInvalid() { + final Stream telemetryUpgradeValidationRejectsInvalid() { return defaultHapiSpec("TelemetryUpgradeValidationRejectsInvalid") .given( cryptoTransfer(tinyBarsFromTo(GENESIS, FREEZE_ADMIN, ONE_HUNDRED_HBARS)), @@ -254,7 +254,7 @@ final HapiSpec telemetryUpgradeValidationRejectsInvalid() { .hasKnownStatus(FREEZE_UPDATE_FILE_HASH_DOES_NOT_MATCH)); } - final HapiSpec canFreezeUpgradeWithPreparedUpgrade() { + final Stream canFreezeUpgradeWithPreparedUpgrade() { return defaultHapiSpec("CanFreezeUpgradeWithPreparedUpgrade") .given( cryptoTransfer(tinyBarsFromTo(GENESIS, FREEZE_ADMIN, ONE_HUNDRED_HBARS)), @@ -284,7 +284,7 @@ final HapiSpec canFreezeUpgradeWithPreparedUpgrade() { freezeAbort()); } - final HapiSpec canTelemetryUpgradeWithValid() { + final Stream canTelemetryUpgradeWithValid() { return defaultHapiSpec("CanTelemetryUpgradeWithValid") .given(cryptoTransfer(tinyBarsFromTo(GENESIS, FREEZE_ADMIN, ONE_HUNDRED_HBARS))) .when( diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/hip796/GeneralSuite.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/hip796/GeneralSuite.java index 92694ce45f63..d3ce7f210d11 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/hip796/GeneralSuite.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/hip796/GeneralSuite.java @@ -32,19 +32,18 @@ import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.INSUFFICIENT_ACCOUNT_BALANCE; import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.SENDER_DOES_NOT_OWN_NFT_SERIAL_NO; -import com.hedera.services.bdd.junit.HapiTest; -import com.hedera.services.bdd.spec.HapiSpec; import com.hedera.services.bdd.suites.HapiSuite; import java.util.List; +import java.util.stream.Stream; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; +import org.junit.jupiter.api.DynamicTest; -// @HapiTestSuite public class GeneralSuite extends HapiSuite { private static final Logger log = LogManager.getLogger(GeneralSuite.class); @Override - public List getSpecsInSuite() { + public List> getSpecsInSuite() { return List.of(canCreateFungibleTokenWithLockingAndPartitioning(), canCreateNFTWithLockingAndPartitioning()); } @@ -55,8 +54,7 @@ public List getSpecsInSuite() { * * @return the HapiSpec for this HIP-796 user story */ - @HapiTest - public HapiSpec canCreateFungibleTokenWithLockingAndPartitioning() { + final Stream canCreateFungibleTokenWithLockingAndPartitioning() { return defaultHapiSpec("CanCreateFungibleTokenWithLockingAndPartitioning") .given(fungibleTokenWithFeatures(PARTITIONING, LOCKING) .withPartitions(RED_PARTITION, BLUE_PARTITION) @@ -82,8 +80,7 @@ public HapiSpec canCreateFungibleTokenWithLockingAndPartitioning() { * * @return the HapiSpec for this HIP-796 user story */ - @HapiTest - public HapiSpec canCreateNFTWithLockingAndPartitioning() { + final Stream canCreateNFTWithLockingAndPartitioning() { return defaultHapiSpec("CanCreateNFTWithLockingAndPartitioning") .given(nonFungibleTokenWithFeatures(PARTITIONING, LOCKING) .withPartitions(RED_PARTITION, BLUE_PARTITION) diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/hip796/InterPartitionMovementSuite.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/hip796/InterPartitionMovementSuite.java index 54a10bd8902e..21a6774ac5d2 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/hip796/InterPartitionMovementSuite.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/hip796/InterPartitionMovementSuite.java @@ -36,22 +36,21 @@ import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.CONTRACT_REVERT_EXECUTED; import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.INVALID_SIGNATURE; -import com.hedera.services.bdd.junit.HapiTest; -import com.hedera.services.bdd.spec.HapiSpec; import com.hedera.services.bdd.suites.HapiSuite; import java.util.List; +import java.util.stream.Stream; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; +import org.junit.jupiter.api.DynamicTest; /** * A suite for user stories Move-1 through Move-5 from HIP-796. */ -// @HapiTestSuite public class InterPartitionMovementSuite extends HapiSuite { private static final Logger log = LogManager.getLogger(InterPartitionMovementSuite.class); @Override - public List getSpecsInSuite() { + public List> getSpecsInSuite() { return List.of( partitionMoveWithoutUserSignature(), partitionMoveWithUserSignature(), @@ -68,8 +67,7 @@ public List getSpecsInSuite() { * * @return the HapiSpec for this HIP-796 user story */ - @HapiTest - final HapiSpec partitionMoveWithoutUserSignature() { + final Stream partitionMoveWithoutUserSignature() { return defaultHapiSpec("PartitionMoveWithoutUserSignature") .given(fungibleTokenWithFeatures(INTER_PARTITION_MANAGEMENT) .withPartitions(RED_PARTITION, BLUE_PARTITION, GREEN_PARTITION) @@ -94,8 +92,7 @@ final HapiSpec partitionMoveWithoutUserSignature() { * * @return the HapiSpec for this HIP-796 user story */ - @HapiTest - final HapiSpec partitionMoveWithUserSignature() { + final Stream partitionMoveWithUserSignature() { return defaultHapiSpec("PartitionMoveWithUserSignature") .given(fungibleTokenWithFeatures(INTER_PARTITION_MANAGEMENT) .withPartitions(RED_PARTITION, BLUE_PARTITION, GREEN_PARTITION) @@ -125,8 +122,7 @@ final HapiSpec partitionMoveWithUserSignature() { * * @return the HapiSpec for this HIP-796 user story */ - @HapiTest - public HapiSpec moveNftsBetweenUserPartitionsWithoutUserSignature() { + final Stream moveNftsBetweenUserPartitionsWithoutUserSignature() { return defaultHapiSpec("MoveNftsBetweenUserPartitionsWithoutUserSignature") .given(nonFungibleTokenWithFeatures(INTER_PARTITION_MANAGEMENT) .withPartitions(RED_PARTITION, BLUE_PARTITION, GREEN_PARTITION) @@ -149,8 +145,7 @@ public HapiSpec moveNftsBetweenUserPartitionsWithoutUserSignature() { * * @return the HapiSpec for this HIP-796 user story */ - @HapiTest - public HapiSpec moveNftsBetweenPartitionsWithUserSignature() { + final Stream moveNftsBetweenPartitionsWithUserSignature() { return defaultHapiSpec("MoveNftsBetweenPartitionsWithUserSignature") .given(nonFungibleTokenWithFeatures(INTER_PARTITION_MANAGEMENT) .withPartitions(RED_PARTITION, BLUE_PARTITION, GREEN_PARTITION) @@ -186,8 +181,7 @@ public HapiSpec moveNftsBetweenPartitionsWithUserSignature() { * * @return the HapiSpec for this HIP-796 user story */ - @HapiTest - public HapiSpec moveTokensViaSmartContractAsPartitionMoveKey() { + final Stream moveTokensViaSmartContractAsPartitionMoveKey() { return defaultHapiSpec("MoveTokensViaSmartContractAsPartitionMoveKey") .given(fungibleTokenWithFeatures(INTER_PARTITION_MANAGEMENT) .withPartitions(RED_PARTITION, BLUE_PARTITION, GREEN_PARTITION) diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/hip796/LockSuite.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/hip796/LockSuite.java index 2a11db819551..bfda2404b451 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/hip796/LockSuite.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/hip796/LockSuite.java @@ -31,22 +31,21 @@ import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.INVALID_ACCOUNT_AMOUNTS; import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.INVALID_NFT_ID; -import com.hedera.services.bdd.junit.HapiTest; -import com.hedera.services.bdd.spec.HapiSpec; import com.hedera.services.bdd.suites.HapiSuite; import java.util.List; +import java.util.stream.Stream; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; +import org.junit.jupiter.api.DynamicTest; /** * A suite for user stories Lock-1 through Lock-8 from HIP-796. */ -// @HapiTestSuite public class LockSuite extends HapiSuite { private static final Logger log = LogManager.getLogger(LockSuite.class); @Override - public List getSpecsInSuite() { + public List> getSpecsInSuite() { return List.of( canLockSubsetOfUnlockedTokens(), canLockSubsetOfUnlockedTokensInPartition(), @@ -63,12 +62,11 @@ public List getSpecsInSuite() { *

    As a `lock-key` holder, I want to lock a subset of the currently held unpartitioned * unlocked fungible tokens held by a user's account without requiring the user's signature. * If an account has `x` unlocked tokens, then the number of tokens that can be additionally - * locked is governed by: `0 <= number_of_tokens_to_be_locked <= x`. + * locked is governed by: `0 <= number_of_tokens_to_be_locked <= x`. * * @return the HapiSpec for this HIP-796 user story */ - @HapiTest - public HapiSpec canLockSubsetOfUnlockedTokens() { + final Stream canLockSubsetOfUnlockedTokens() { return defaultHapiSpec("CanLockSubsetOfUnlockedTokens") .given(fungibleTokenWithFeatures(LOCKING).withRelation(ALICE)) .when(lockUnits(ALICE, TOKEN_UNDER_TEST, FUNGIBLE_INITIAL_BALANCE / 2)) @@ -92,12 +90,11 @@ public HapiSpec canLockSubsetOfUnlockedTokens() { *

    As a `lock-key` holder, I want to lock a subset of the currently held unlocked fungible * tokens held by a user's account in a partition without requiring the user's signature. * If an account has `x` unlocked tokens, then the number of tokens that can be additionally - * locked is governed by: `0 <= number_of_tokens_to_be_locked <= x`. + * locked is governed by: `0 <= number_of_tokens_to_be_locked <= x`. * * @return the HapiSpec for this HIP-796 user story */ - @HapiTest - public HapiSpec canLockSubsetOfUnlockedTokensInPartition() { + final Stream canLockSubsetOfUnlockedTokensInPartition() { return defaultHapiSpec("CanLockSubsetOfUnlockedTokensInPartition") .given(fungibleTokenWithFeatures(LOCKING, PARTITIONING) .withPartition(RED_PARTITION) @@ -115,12 +112,11 @@ public HapiSpec canLockSubsetOfUnlockedTokensInPartition() { *

    As a `lock-key` holder, I want to unlock a subset of the currently held unpartitioned locked * fungible tokens held by a user's account without requiring the user's signature. * If an account has `x` locked tokens, then the number of tokens that can be additionally - * unlocked is governed by: `0 <= number_of_locked_tokens <= x`. + * unlocked is governed by: `0 <= number_of_locked_tokens <= x`. * * @return the HapiSpec for this HIP-796 user story */ - @HapiTest - public HapiSpec canUnlockSubsetOfLockedTokens() { + final Stream canUnlockSubsetOfLockedTokens() { return defaultHapiSpec("CanLockSubsetOfUnlockedTokens") .given(fungibleTokenWithFeatures(LOCKING).withRelation(ALICE)) .when(lockUnits(ALICE, TOKEN_UNDER_TEST, FUNGIBLE_INITIAL_BALANCE / 2)) @@ -140,12 +136,11 @@ public HapiSpec canUnlockSubsetOfLockedTokens() { *

    As a `lock-key` holder, I want to unlock a subset of the currently held locked fungible tokens * held by a user's account in a partition without requiring the user's signature. * If an account has `x` locked tokens in a partition, then the number of tokens that can be - * additionally unlocked is governed by: `0 <= number_of_locked_tokens <= x`. + * additionally unlocked is governed by: `0 <= number_of_locked_tokens <= x`. * * @return the HapiSpec for this HIP-796 user story */ - @HapiTest - public HapiSpec canUnlockSubsetOfLockedTokensInPartition() { + final Stream canUnlockSubsetOfLockedTokensInPartition() { return defaultHapiSpec("CanUnlockSubsetOfLockedTokensInPartition") .given(fungibleTokenWithFeatures(LOCKING) .withPartition(RED_PARTITION) @@ -171,8 +166,7 @@ public HapiSpec canUnlockSubsetOfLockedTokensInPartition() { * * @return the HapiSpec for this HIP-796 user story */ - @HapiTest - public HapiSpec canLockSpecificNFTSerials() { + final Stream canLockSpecificNFTSerials() { return defaultHapiSpec("CanLockSpecificNFTSerials") .given(nonFungibleTokenWithFeatures(LOCKING).withRelation(ALICE, r -> r.ownedSerialNos(1L, 2L))) .when(lockNfts(ALICE, TOKEN_UNDER_TEST, 1L)) @@ -193,8 +187,7 @@ public HapiSpec canLockSpecificNFTSerials() { * * @return the HapiSpec for this HIP-796 user story */ - @HapiTest - public HapiSpec canLockSpecificNFTSerialsInPartition() { + final Stream canLockSpecificNFTSerialsInPartition() { return defaultHapiSpec("CanLockSpecificNFTSerialsInPartition") .given(nonFungibleTokenWithFeatures(LOCKING, PARTITIONING) .withPartition(RED_PARTITION) @@ -218,8 +211,7 @@ public HapiSpec canLockSpecificNFTSerialsInPartition() { * * @return the HapiSpec for this HIP-796 user story */ - @HapiTest - public HapiSpec canUnlockSpecificNFTSerials() { + final Stream canUnlockSpecificNFTSerials() { return defaultHapiSpec("CanUnlockSpecificNFTSerials") .given(nonFungibleTokenWithFeatures(LOCKING).withRelation(ALICE, r -> r.ownedSerialNos(1L, 2L))) .when(lockNfts(ALICE, TOKEN_UNDER_TEST, 1L)) @@ -241,8 +233,7 @@ public HapiSpec canUnlockSpecificNFTSerials() { * * @return the HapiSpec for this HIP-796 user story */ - @HapiTest - public HapiSpec canUnlockSpecificNFTSerialsInPartition() { + final Stream canUnlockSpecificNFTSerialsInPartition() { return defaultHapiSpec("CanUnlockSpecificNFTSerialsInPartition") .given(nonFungibleTokenWithFeatures(LOCKING, PARTITIONING) .withRelation(ALICE, r -> r.onlyForPartition(RED_PARTITION, pr -> pr.ownedSerialNos(1L, 2L)))) diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/hip796/MiscSuite.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/hip796/MiscSuite.java index 96e0bd70776a..0aba573ba0ed 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/hip796/MiscSuite.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/hip796/MiscSuite.java @@ -54,14 +54,14 @@ import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.SPENDER_DOES_NOT_HAVE_ALLOWANCE; import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.TRANSACTION_REQUIRES_ZERO_TOKEN_BALANCES; -import com.hedera.services.bdd.junit.HapiTest; -import com.hedera.services.bdd.spec.HapiSpec; import com.hedera.services.bdd.suites.HapiSuite; import com.hedera.services.bdd.suites.hip796.operations.DesiredAccountTokenRelation; import com.hedera.services.bdd.suites.hip796.operations.TokenFeature; import java.util.List; +import java.util.stream.Stream; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; +import org.junit.jupiter.api.DynamicTest; /** * A suite for user stories Misc-1 through Misc-6 from HIP-796. @@ -69,12 +69,11 @@ // too may parameters @SuppressWarnings("java:S1192") -// @HapiTestSuite public class MiscSuite extends HapiSuite { private static final Logger log = LogManager.getLogger(MiscSuite.class); @Override - public List getSpecsInSuite() { + public List> getSpecsInSuite() { return List.of( tokenOpsUnchangedWithPartitionDefinitions(), rentNotYetChargedForPartitionAndDefinitions(), @@ -93,8 +92,7 @@ public List getSpecsInSuite() { * * @return the HapiSpec for this HIP-796 user story */ - @HapiTest - public HapiSpec tokenOpsUnchangedWithPartitionDefinitions() { + final Stream tokenOpsUnchangedWithPartitionDefinitions() { return defaultHapiSpec("TokenOpsUnchangedWithPartitionDefinitions") .given( // Create a partitioned token with all features @@ -141,8 +139,7 @@ public HapiSpec tokenOpsUnchangedWithPartitionDefinitions() { * * @return the HapiSpec for this HIP-796 user story */ - @HapiTest - public HapiSpec rentNotYetChargedForPartitionAndDefinitions() { + final Stream rentNotYetChargedForPartitionAndDefinitions() { return propertyPreservingHapiSpec("RentNotYetChargedForPartitionAndDefinitions") .preserving("ledger.autoRenewPeriod.minDuration") .given( @@ -182,8 +179,7 @@ public HapiSpec rentNotYetChargedForPartitionAndDefinitions() { * * @return the HapiSpec for this HIP-796 user story */ - @HapiTest - public HapiSpec approvalAllowanceSpecificPartition() { + final Stream approvalAllowanceSpecificPartition() { return defaultHapiSpec("ApprovalAllowanceSpecificPartition") .given( fungibleTokenWithFeatures(PARTITIONING) @@ -242,8 +238,7 @@ public HapiSpec approvalAllowanceSpecificPartition() { * * @return the HapiSpec for this HIP-796 user story */ - @HapiTest - public HapiSpec accountExpiryAndReclamationIsNotEnabled() { + final Stream accountExpiryAndReclamationIsNotEnabled() { return propertyPreservingHapiSpec("AccountExpiryAndReclamationIsNotEnabled") .preserving("ledger.autoRenewPeriod.minDuration") .given( @@ -279,8 +274,7 @@ public HapiSpec accountExpiryAndReclamationIsNotEnabled() { * * @return the HapiSpec for this HIP-796 user story */ - @HapiTest - public HapiSpec accountDeletionWithTokenHoldings() { + final Stream accountDeletionWithTokenHoldings() { return defaultHapiSpec("AccountDeletionWithTokenHoldings") .given(fungibleTokenWithFeatures(PARTITIONING, WIPING) .withPartitions(RED_PARTITION) @@ -304,8 +298,7 @@ public HapiSpec accountDeletionWithTokenHoldings() { * * @return the HapiSpec for this HIP-796 user story */ - @HapiTest - public HapiSpec customFeesAtTokenDefinitionLevel() { + final Stream customFeesAtTokenDefinitionLevel() { return defaultHapiSpec("CustomFeesAtTokenDefinitionLevel") .given(fungibleTokenWithFeatures(PARTITIONING, CUSTOM_FEE_SCHEDULE_MANAGEMENT) .withPartitions(RED_PARTITION, BLUE_PARTITION) diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/hip796/PartitionAssociationsSuite.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/hip796/PartitionAssociationsSuite.java index 956980bdbcd4..bf0e8b6f7864 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/hip796/PartitionAssociationsSuite.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/hip796/PartitionAssociationsSuite.java @@ -36,22 +36,21 @@ import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.ACCOUNT_STILL_OWNS_NFTS; import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.TRANSACTION_REQUIRES_ZERO_TOKEN_BALANCES; -import com.hedera.services.bdd.junit.HapiTest; -import com.hedera.services.bdd.spec.HapiSpec; import com.hedera.services.bdd.suites.HapiSuite; import java.util.List; +import java.util.stream.Stream; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; +import org.junit.jupiter.api.DynamicTest; /** * A suite for user stories Association-1 through Association-7 from HIP-796. */ -// @HapiTestSuite public class PartitionAssociationsSuite extends HapiSuite { private static final Logger log = LogManager.getLogger(PartitionAssociationsSuite.class); @Override - public List getSpecsInSuite() { + public List> getSpecsInSuite() { return List.of( associateWithPartitionedTokenAutomatically(), associateWithPartitionAndToken(), @@ -70,8 +69,7 @@ public List getSpecsInSuite() { * * @return the HapiSpec for this HIP-796 user story */ - @HapiTest - final HapiSpec associateWithPartitionedTokenAutomatically() { + final Stream associateWithPartitionedTokenAutomatically() { return defaultHapiSpec("AssociateWithPartitionedTokenAutomatically") .given(nonFungibleTokenWithFeatures(PARTITIONING) .withPartition(BLUE_PARTITION, p -> p.assignedSerialNos(2L))) @@ -93,8 +91,7 @@ final HapiSpec associateWithPartitionedTokenAutomatically() { * * @return the HapiSpec for this HIP-796 user story */ - @HapiTest - final HapiSpec associateWithPartitionAndToken() { + final Stream associateWithPartitionAndToken() { return defaultHapiSpec("AssociateWithPartitionAndToken") .given( cryptoCreate(ALICE).balance(ONE_HUNDRED_HBARS), @@ -122,8 +119,7 @@ final HapiSpec associateWithPartitionAndToken() { * * @return the HapiSpec for this HIP-796 user story */ - @HapiTest - final HapiSpec autoAssociateSiblingPartitions() { + final Stream autoAssociateSiblingPartitions() { return defaultHapiSpec("AutoAssociateSiblingPartitions") .given(fungibleTokenWithFeatures(PARTITIONING) .withPartitions(RED_PARTITION) @@ -152,8 +148,7 @@ final HapiSpec autoAssociateSiblingPartitions() { * * @return the HapiSpec for this HIP-796 user story */ - @HapiTest - final HapiSpec autoAssociateChildPartitions() { + final Stream autoAssociateChildPartitions() { return defaultHapiSpec("AutoAssociateChildPartitions") .given( cryptoCreate(CIVILIAN_PAYER).balance(ONE_HUNDRED_HBARS), @@ -183,8 +178,7 @@ final HapiSpec autoAssociateChildPartitions() { * * @return the HapiSpec for this HIP-796 user story */ - @HapiTest - final HapiSpec disassociateEmptyPartition() { + final Stream disassociateEmptyPartition() { return defaultHapiSpec("DisassociateEmptyPartition") .given( fungibleTokenWithFeatures(PARTITIONING) @@ -211,8 +205,7 @@ final HapiSpec disassociateEmptyPartition() { * * @return the HapiSpec for this HIP-796 user story */ - @HapiTest - final HapiSpec doNotAllowDisassociateWithNonEmptyPartition() { + final Stream doNotAllowDisassociateWithNonEmptyPartition() { return defaultHapiSpec("DoNotAllowDisassociateWithNonEmptyPartition") .given( fungibleTokenWithFeatures(PARTITIONING) @@ -247,8 +240,7 @@ final HapiSpec doNotAllowDisassociateWithNonEmptyPartition() { * * @return the HapiSpec for this HIP-796 user story */ - @HapiTest - final HapiSpec ensurePartitionsRemovedBeforeTokenDisassociation() { + final Stream ensurePartitionsRemovedBeforeTokenDisassociation() { return defaultHapiSpec("EnsurePartitionsRemovedBeforeTokenDisassociation") .given(fungibleTokenWithFeatures(PARTITIONING) .withPartitions(RED_PARTITION) diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/hip796/PartitionsSuite.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/hip796/PartitionsSuite.java index a9dd99e60940..e8883af818cc 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/hip796/PartitionsSuite.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/hip796/PartitionsSuite.java @@ -61,22 +61,21 @@ import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.TOKEN_IS_PAUSED; import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.TOKEN_WAS_DELETED; -import com.hedera.services.bdd.junit.HapiTest; -import com.hedera.services.bdd.spec.HapiSpec; import com.hedera.services.bdd.suites.HapiSuite; import java.util.List; +import java.util.stream.Stream; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; +import org.junit.jupiter.api.DynamicTest; /** * A suite for user stories Partitions-1 through Partitions-18 from HIP-796. */ -// @HapiTestSuite public class PartitionsSuite extends HapiSuite { private static final Logger log = LogManager.getLogger(PartitionsSuite.class); @Override - public List getSpecsInSuite() { + public List> getSpecsInSuite() { return List.of( createNewPartitionDefinitions(), updatePartitionDefinitionsMemo(), @@ -105,8 +104,7 @@ public List getSpecsInSuite() { * * @return the HapiSpec for this HIP-796 user story */ - @HapiTest - final HapiSpec createNewPartitionDefinitions() { + final Stream createNewPartitionDefinitions() { return defaultHapiSpec("CreateNewPartitionDefinitions") .given(fungibleTokenWithFeatures(PARTITIONING)) .when() @@ -122,8 +120,7 @@ final HapiSpec createNewPartitionDefinitions() { * * @return the HapiSpec for this HIP-796 user story */ - @HapiTest - final HapiSpec updatePartitionDefinitionsMemo() { + final Stream updatePartitionDefinitionsMemo() { return defaultHapiSpec("UpdatePartitionDefinitionsMemo") .given(fungibleTokenWithFeatures(PARTITIONING, ADMIN_CONTROL) .withPartition(RED_PARTITION, p -> p.memo("TBD"))) @@ -139,8 +136,7 @@ final HapiSpec updatePartitionDefinitionsMemo() { * * @return the HapiSpec for this HIP-796 user story */ - @HapiTest - final HapiSpec deletePartitionDefinitions() { + final Stream deletePartitionDefinitions() { return defaultHapiSpec("DeletePartitionDefinitions") .given(fungibleTokenWithFeatures(PARTITIONING, ADMIN_CONTROL).withPartition(RED_PARTITION)) .when( @@ -156,8 +152,7 @@ final HapiSpec deletePartitionDefinitions() { * * @return the HapiSpec for this HIP-796 user story */ - @HapiTest - final HapiSpec transferBetweenPartitions() { + final Stream transferBetweenPartitions() { return defaultHapiSpec("TransferBetweenPartitions") .given(fungibleTokenWithFeatures(PARTITIONING, INTER_PARTITION_MANAGEMENT) .withPartitions(RED_PARTITION, BLUE_PARTITION) @@ -179,8 +174,7 @@ final HapiSpec transferBetweenPartitions() { * * @return the HapiSpec for this HIP-796 user story */ - @HapiTest - final HapiSpec transferNFTsWithinPartitions() { + final Stream transferNFTsWithinPartitions() { return defaultHapiSpec("TransferNFTsWithinPartitions") .given(nonFungibleTokenWithFeatures(PARTITIONING, INTER_PARTITION_MANAGEMENT) .withPartitions(RED_PARTITION, BLUE_PARTITION) @@ -208,8 +202,7 @@ final HapiSpec transferNFTsWithinPartitions() { * * @return the HapiSpec for this HIP-796 user story */ - @HapiTest - final HapiSpec pauseTokenTransfersIncludingPartitions() { + final Stream pauseTokenTransfersIncludingPartitions() { return defaultHapiSpec("PauseTokenTransfersIncludingPartitions") .given(nonFungibleTokenWithFeatures(PARTITIONING, PAUSING) .withPartitions(RED_PARTITION, BLUE_PARTITION) @@ -238,8 +231,7 @@ final HapiSpec pauseTokenTransfersIncludingPartitions() { * * @return the HapiSpec for this HIP-796 user story */ - @HapiTest - final HapiSpec freezeTokenTransfersForAccountIncludingPartitions() { + final Stream freezeTokenTransfersForAccountIncludingPartitions() { return defaultHapiSpec("FreezeTokenTransfersForAccountIncludingPartitions") .given(nonFungibleTokenWithFeatures(PARTITIONING, FREEZING) .withPartitions(RED_PARTITION, BLUE_PARTITION) @@ -267,8 +259,7 @@ final HapiSpec freezeTokenTransfersForAccountIncludingPartitions() { * * @return the HapiSpec for this HIP-796 user story */ - @HapiTest - final HapiSpec requireKycForTokenTransfersIncludingPartitions() { + final Stream requireKycForTokenTransfersIncludingPartitions() { return defaultHapiSpec("RequireKycForTokenTransfersIncludingPartitions") .given(nonFungibleTokenWithFeatures(PARTITIONING, KYC_MANAGEMENT) .withPartitions(RED_PARTITION, BLUE_PARTITION) @@ -296,8 +287,7 @@ final HapiSpec requireKycForTokenTransfersIncludingPartitions() { * * @return the HapiSpec for this HIP-796 user story */ - @HapiTest - final HapiSpec pauseTransfersForSpecificPartition() { + final Stream pauseTransfersForSpecificPartition() { return defaultHapiSpec("PauseTransfersForSpecificPartition") .given(nonFungibleTokenWithFeatures(PARTITIONING, PAUSING) .withPartitions(RED_PARTITION, BLUE_PARTITION) @@ -327,8 +317,7 @@ final HapiSpec pauseTransfersForSpecificPartition() { * * @return the HapiSpec for this HIP-796 user story */ - @HapiTest - final HapiSpec freezeTransfersForSpecificPartitionOnAccount() { + final Stream freezeTransfersForSpecificPartitionOnAccount() { return defaultHapiSpec("FreezeTransfersForSpecificPartitionOnAccount") .given(nonFungibleTokenWithFeatures(PARTITIONING, FREEZING) .withPartitions(RED_PARTITION, BLUE_PARTITION) @@ -358,8 +347,7 @@ final HapiSpec freezeTransfersForSpecificPartitionOnAccount() { * * @return the HapiSpec for this HIP-796 user story */ - @HapiTest - final HapiSpec requireKycForPartitionTransfers() { + final Stream requireKycForPartitionTransfers() { return defaultHapiSpec("RequireKycForPartitionTransfers") .given( cryptoCreate(ALICE), @@ -383,8 +371,7 @@ final HapiSpec requireKycForPartitionTransfers() { * * @return the HapiSpec for this HIP-796 user story */ - @HapiTest - final HapiSpec createFixedSupplyTokenWithPartitionKey() { + final Stream createFixedSupplyTokenWithPartitionKey() { return defaultHapiSpec("CreateFixedSupplyTokenWithPartitionKey") .given( // Without a supply key, this will be a fixed supply token @@ -415,8 +402,7 @@ final HapiSpec createFixedSupplyTokenWithPartitionKey() { * * @return the HapiSpec for this HIP-796 user story */ - @HapiTest - final HapiSpec notHonorDeletionOfTokenWithExistingPartitions() { + final Stream notHonorDeletionOfTokenWithExistingPartitions() { return defaultHapiSpec("NotHonorDeletionOfTokenWithExistingPartitions") .given(fungibleTokenWithFeatures(PARTITIONING) .initialSupply(6L) @@ -443,8 +429,7 @@ final HapiSpec notHonorDeletionOfTokenWithExistingPartitions() { * * @return the HapiSpec for this HIP-796 user story */ - @HapiTest - final HapiSpec mintToSpecificPartitionOfTreasury() { + final Stream mintToSpecificPartitionOfTreasury() { return defaultHapiSpec("MintToSpecificPartitionOfTreasury") .given(fungibleTokenWithFeatures(PARTITIONING, SUPPLY_MANAGEMENT) .initialSupply(99L) @@ -466,8 +451,7 @@ final HapiSpec mintToSpecificPartitionOfTreasury() { * * @return the HapiSpec for this HIP-796 user story */ - @HapiTest - final HapiSpec burnFromSpecificPartitionOfTreasury() { + final Stream burnFromSpecificPartitionOfTreasury() { return defaultHapiSpec("BurnFromSpecificPartitionOfTreasury") .given(fungibleTokenWithFeatures(PARTITIONING, SUPPLY_MANAGEMENT) .initialSupply(99L) @@ -489,8 +473,7 @@ final HapiSpec burnFromSpecificPartitionOfTreasury() { * * @return the HapiSpec for this HIP-796 user story */ - @HapiTest - final HapiSpec wipeFromSpecificPartitionInUserAccount() { + final Stream wipeFromSpecificPartitionInUserAccount() { return defaultHapiSpec("WipeFromSpecificPartitionInUserAccount") .given(nonFungibleTokenWithFeatures(PARTITIONING, WIPING) .withPartitions(RED_PARTITION, BLUE_PARTITION) @@ -511,8 +494,7 @@ final HapiSpec wipeFromSpecificPartitionInUserAccount() { * * @return the HapiSpec for this HIP-796 user story */ - @HapiTest - final HapiSpec smartContractAdministersPartitions() { + final Stream smartContractAdministersPartitions() { return defaultHapiSpec("SmartContractAdministersPartitions") .given(fungibleTokenWithFeatures(PARTITIONING, ADMIN_CONTROL).managedByContract()) .when( @@ -556,8 +538,7 @@ final HapiSpec smartContractAdministersPartitions() { * * @return the HapiSpec for this HIP-796 user story */ - @HapiTest - final HapiSpec freezeOrPauseAtTokenLevelOverridesPartition() { + final Stream freezeOrPauseAtTokenLevelOverridesPartition() { return defaultHapiSpec("FreezeOrPauseAtTokenLevelOverridesPartition") .given(nonFungibleTokenWithFeatures(PARTITIONING, PAUSING, FREEZING) .withPartitions(RED_PARTITION, BLUE_PARTITION) diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/hip796/TokenKeysDefinitionSuite.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/hip796/TokenKeysDefinitionSuite.java index 9243f2ca6294..57ae3fc19d84 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/hip796/TokenKeysDefinitionSuite.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/hip796/TokenKeysDefinitionSuite.java @@ -37,22 +37,21 @@ import static com.hedera.services.bdd.suites.hip796.operations.TokenFeature.LOCKING; import static com.hedera.services.bdd.suites.hip796.operations.TokenFeature.PARTITIONING; -import com.hedera.services.bdd.junit.HapiTest; -import com.hedera.services.bdd.spec.HapiSpec; import com.hedera.services.bdd.suites.HapiSuite; import java.util.List; +import java.util.stream.Stream; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; +import org.junit.jupiter.api.DynamicTest; /** * A suite for user stories Keys-1 through Keys-4 from HIP-796. */ -// @HapiTestSuite public class TokenKeysDefinitionSuite extends HapiSuite { private static final Logger log = LogManager.getLogger(TokenKeysDefinitionSuite.class); @Override - public List getSpecsInSuite() { + public List> getSpecsInSuite() { return List.of( manageLockKeyCapabilities(), managePartitionKeyCapabilities(), @@ -67,8 +66,7 @@ public List getSpecsInSuite() { * * @return the HapiSpec for this HIP-796 user story */ - @HapiTest - final HapiSpec manageLockKeyCapabilities() { + final Stream manageLockKeyCapabilities() { return defaultHapiSpec("ManageLockKeyCapabilities") .given(fungibleTokenWithFeatures(ADMIN_CONTROL, LOCKING), newKeyNamed("newLockKey")) .when( @@ -89,8 +87,7 @@ final HapiSpec manageLockKeyCapabilities() { * * @return the HapiSpec for this HIP-796 user story */ - @HapiTest - final HapiSpec managePartitionKeyCapabilities() { + final Stream managePartitionKeyCapabilities() { return defaultHapiSpec("ManagePartitionKeyCapabilities") .given(fungibleTokenWithFeatures(ADMIN_CONTROL, PARTITIONING), newKeyNamed("newPartitionKey")) .when( @@ -111,8 +108,7 @@ final HapiSpec managePartitionKeyCapabilities() { * * @return the HapiSpec for this HIP-796 user story */ - @HapiTest - final HapiSpec managePartitionMoveKeyCapabilities() { + final Stream managePartitionMoveKeyCapabilities() { return defaultHapiSpec("ManagePartitionMoveKeyCapabilities") .given( fungibleTokenWithFeatures(ADMIN_CONTROL, INTER_PARTITION_MANAGEMENT), @@ -135,8 +131,7 @@ final HapiSpec managePartitionMoveKeyCapabilities() { * * @return the HapiSpec for this HIP-796 user story */ - @HapiTest - final HapiSpec manageKeysViaSmartContract() { + final Stream manageKeysViaSmartContract() { return defaultHapiSpec("ManageKeysViaSmartContract") .given( fungibleTokenWithFeatures(ADMIN_CONTROL, LOCKING, PARTITIONING, INTER_PARTITION_MANAGEMENT) diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/hip796/TransfersSuite.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/hip796/TransfersSuite.java index 6a32bb96f87a..1b2d93aeb4f3 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/hip796/TransfersSuite.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/hip796/TransfersSuite.java @@ -28,22 +28,21 @@ import static com.hedera.services.bdd.suites.hip796.operations.TokenFeature.PARTITIONING; import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.INVALID_ACCOUNT_AMOUNTS; -import com.hedera.services.bdd.junit.HapiTest; -import com.hedera.services.bdd.spec.HapiSpec; import com.hedera.services.bdd.suites.HapiSuite; import java.util.List; +import java.util.stream.Stream; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; +import org.junit.jupiter.api.DynamicTest; /** * A suite for user stories Transfers-1 through Transfers-3 from HIP-796. */ -// @HapiTestSuite public class TransfersSuite extends HapiSuite { private static final Logger log = LogManager.getLogger(TransfersSuite.class); @Override - public List getSpecsInSuite() { + public List> getSpecsInSuite() { return List.of(); } @@ -53,8 +52,7 @@ public List getSpecsInSuite() { * * @return the HapiSpec for this HIP-796 user story */ - @HapiTest - public HapiSpec canTransferTokensToSamePartitionUser() { + final Stream canTransferTokensToSamePartitionUser() { return defaultHapiSpec("CanTransferTokensToSamePartitionUser") .given(fungibleTokenWithFeatures(PARTITIONING) .withPartitions(RED_PARTITION, BLUE_PARTITION) @@ -72,8 +70,7 @@ public HapiSpec canTransferTokensToSamePartitionUser() { * * @return the HapiSpec for this HIP-796 user story */ - @HapiTest - public HapiSpec canTransferTokensToUserWithAutoAssociation() { + final Stream canTransferTokensToUserWithAutoAssociation() { return defaultHapiSpec("CanTransferTokensToUserWithAutoAssociation") .given(fungibleTokenWithFeatures(PARTITIONING) .withPartitions(RED_PARTITION, BLUE_PARTITION) @@ -97,8 +94,7 @@ public HapiSpec canTransferTokensToUserWithAutoAssociation() { * * @return the HapiSpec for this HIP-796 user story */ - @HapiTest - public HapiSpec canTransferTokensToUserAfterUnlock() { + final Stream canTransferTokensToUserAfterUnlock() { return defaultHapiSpec("CanTransferTokensToUserPostUnlock") .given(fungibleTokenWithFeatures(PARTITIONING, LOCKING) .withPartitions(RED_PARTITION, BLUE_PARTITION) diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/issues/Issue1648Suite.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/issues/Issue1648Suite.java deleted file mode 100644 index 884feeac6d38..000000000000 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/issues/Issue1648Suite.java +++ /dev/null @@ -1,76 +0,0 @@ -/* - * Copyright (C) 2020-2024 Hedera Hashgraph, LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.hedera.services.bdd.suites.issues; - -import static com.hedera.services.bdd.spec.HapiSpec.defaultHapiSpec; -import static com.hedera.services.bdd.spec.transactions.TxnVerbs.cryptoCreate; -import static com.hedera.services.bdd.spec.transactions.TxnVerbs.cryptoTransfer; -import static com.hedera.services.bdd.spec.transactions.crypto.HapiCryptoTransfer.tinyBarsFromTo; - -import com.hedera.services.bdd.junit.HapiTest; -import com.hedera.services.bdd.junit.HapiTestSuite; -import com.hedera.services.bdd.spec.HapiSpec; -import com.hedera.services.bdd.spec.utilops.UtilVerbs; -import com.hedera.services.bdd.suites.HapiSuite; -import java.util.List; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; -import org.junit.jupiter.api.Assertions; - -@HapiTestSuite -public class Issue1648Suite extends HapiSuite { - private static final Logger log = LogManager.getLogger(Issue1648Suite.class); - - public static void main(String... args) { - new Issue1648Suite().runSuiteSync(); - } - - @Override - public List getSpecsInSuite() { - return List.of(recordStorageFeeIncreasesWithNumTransfers()); - } - - @HapiTest - public static HapiSpec recordStorageFeeIncreasesWithNumTransfers() { - return defaultHapiSpec("RecordStorageFeeIncreasesWithNumTransfers") - .given( - cryptoCreate("civilian").balance(10 * ONE_HUNDRED_HBARS), - cryptoCreate("A"), - cryptoCreate("B"), - cryptoCreate("C"), - cryptoCreate("D"), - cryptoTransfer(tinyBarsFromTo("A", "B", 1L)) - .payingWith("civilian") - .via("txn1"), - cryptoTransfer(tinyBarsFromTo("A", "B", 1L), tinyBarsFromTo("C", "D", 1L)) - .payingWith("civilian") - .via("txn2")) - .when(UtilVerbs.recordFeeAmount("txn1", "feeForOne"), UtilVerbs.recordFeeAmount("txn2", "feeForTwo")) - .then(UtilVerbs.assertionsHold((spec, assertLog) -> { - long feeForOne = spec.registry().getAmount("feeForOne"); - long feeForTwo = spec.registry().getAmount("feeForTwo"); - assertLog.info("[Record storage] fee for one transfer : {}", feeForOne); - assertLog.info("[Record storage] fee for two transfers: {}", feeForTwo); - Assertions.assertEquals(-1, Long.compare(feeForOne, feeForTwo)); - })); - } - - @Override - protected Logger getResultsLogger() { - return log; - } -} diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/issues/Issue1741Suite.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/issues/Issue1741Suite.java deleted file mode 100644 index 42698fc53ab8..000000000000 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/issues/Issue1741Suite.java +++ /dev/null @@ -1,61 +0,0 @@ -/* - * Copyright (C) 2020-2024 Hedera Hashgraph, LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.hedera.services.bdd.suites.issues; - -import static com.hedera.services.bdd.spec.queries.QueryVerbs.getAccountInfo; -import static com.hedera.services.bdd.spec.transactions.TxnVerbs.cryptoCreate; -import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.INSUFFICIENT_PAYER_BALANCE; - -import com.hedera.services.bdd.junit.HapiTest; -import com.hedera.services.bdd.junit.HapiTestSuite; -import com.hedera.services.bdd.spec.HapiSpec; -import com.hedera.services.bdd.suites.HapiSuite; -import java.util.List; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; - -@HapiTestSuite -public class Issue1741Suite extends HapiSuite { - private static final Logger log = LogManager.getLogger(Issue1741Suite.class); - - public static void main(String... args) { - new Issue1741Suite().runSuiteSync(); - } - - @Override - public List getSpecsInSuite() { - return List.of(queryPaymentTxnMustHavePayerBalanceForBothTransferFeeAndNodePayment()); - } - - @HapiTest - public static HapiSpec queryPaymentTxnMustHavePayerBalanceForBothTransferFeeAndNodePayment() { - final long BALANCE = 1_000_000L; - - return HapiSpec.defaultHapiSpec("QueryPaymentTxnMustHavePayerBalanceForBothTransferFeeAndNodePayment") - .given(cryptoCreate("payer").balance(BALANCE)) - .when() - .then(getAccountInfo("payer") - .nodePayment(BALANCE) - .payingWith("payer") - .hasAnswerOnlyPrecheck(INSUFFICIENT_PAYER_BALANCE)); - } - - @Override - protected Logger getResultsLogger() { - return log; - } -} diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/issues/Issue1742Suite.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/issues/Issue1742Suite.java deleted file mode 100644 index 343b143905e3..000000000000 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/issues/Issue1742Suite.java +++ /dev/null @@ -1,68 +0,0 @@ -/* - * Copyright (C) 2020-2024 Hedera Hashgraph, LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.hedera.services.bdd.suites.issues; - -import static com.hedera.services.bdd.spec.HapiSpec.defaultHapiSpec; -import static com.hedera.services.bdd.spec.transactions.TxnVerbs.cryptoCreate; -import static com.hedera.services.bdd.spec.transactions.TxnVerbs.cryptoTransfer; -import static com.hedera.services.bdd.spec.transactions.crypto.HapiCryptoTransfer.tinyBarsFromTo; -import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.INSUFFICIENT_PAYER_BALANCE; - -import com.hedera.services.bdd.junit.HapiTest; -import com.hedera.services.bdd.junit.HapiTestSuite; -import com.hedera.services.bdd.spec.HapiSpec; -import com.hedera.services.bdd.suites.HapiSuite; -import java.util.List; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; - -@HapiTestSuite -public class Issue1742Suite extends HapiSuite { - private static final Logger log = LogManager.getLogger(Issue1742Suite.class); - - public static void main(String... args) { - new Issue1742Suite().runSuiteSync(); - } - - @Override - public List getSpecsInSuite() { - return List.of(cryptoTransferListShowsOnlyFeesAfterIAB()); - } - - @Override - public boolean canRunConcurrent() { - return false; - } - - @HapiTest - public static HapiSpec cryptoTransferListShowsOnlyFeesAfterIAB() { - final long PAYER_BALANCE = 1_000_000L; - - return defaultHapiSpec("CryptoTransferListShowsOnlyFeesAfterIAB") - .given(cryptoCreate("payer").balance(PAYER_BALANCE)) - .when() - .then(cryptoTransfer(tinyBarsFromTo("payer", GENESIS, PAYER_BALANCE)) - .payingWith("payer") - .via("txn") - .hasPrecheck(INSUFFICIENT_PAYER_BALANCE)); - } - - @Override - protected Logger getResultsLogger() { - return log; - } -} diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/issues/Issue1744Suite.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/issues/Issue1744Suite.java deleted file mode 100644 index 3985803dfde1..000000000000 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/issues/Issue1744Suite.java +++ /dev/null @@ -1,88 +0,0 @@ -/* - * Copyright (C) 2020-2024 Hedera Hashgraph, LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.hedera.services.bdd.suites.issues; - -import static com.hedera.services.bdd.spec.HapiSpec.defaultHapiSpec; -import static com.hedera.services.bdd.spec.queries.QueryVerbs.getTxnRecord; -import static com.hedera.services.bdd.spec.transactions.TxnVerbs.cryptoCreate; -import static com.hedera.services.bdd.spec.transactions.TxnVerbs.cryptoTransfer; -import static com.hedera.services.bdd.spec.transactions.crypto.HapiCryptoTransfer.tinyBarsFromTo; -import static com.hedera.services.bdd.spec.utilops.CustomSpecAssert.allRunFor; - -import com.hedera.services.bdd.junit.HapiTest; -import com.hedera.services.bdd.junit.HapiTestSuite; -import com.hedera.services.bdd.spec.HapiSpec; -import com.hedera.services.bdd.spec.queries.meta.HapiGetTxnRecord; -import com.hedera.services.bdd.spec.utilops.UtilVerbs; -import com.hedera.services.bdd.suites.HapiSuite; -import com.hederahashgraph.api.proto.java.TransactionRecord; -import java.util.List; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; - -@HapiTestSuite -public class Issue1744Suite extends HapiSuite { - private static final Logger log = LogManager.getLogger(Issue1744Suite.class); - private static final String PAYER = "payer"; - - public static void main(String... args) { - new Issue1744Suite().runSuiteSync(); - } - - @Override - public List getSpecsInSuite() { - return List.of(keepsRecordOfPayerIBE()); - } - - @HapiTest - public HapiSpec keepsRecordOfPayerIBE() { - return defaultHapiSpec("KeepsRecordOfPayerIBE") - .given( - cryptoCreate(CIVILIAN_PAYER), - cryptoTransfer(tinyBarsFromTo(GENESIS, FUNDING, 1L)) - .payingWith(CIVILIAN_PAYER) - .via("referenceTxn"), - UtilVerbs.withOpContext((spec, ctxLog) -> { - HapiGetTxnRecord subOp = getTxnRecord("referenceTxn"); - allRunFor(spec, subOp); - TransactionRecord record = subOp.getResponseRecord(); - long fee = record.getTransactionFee(); - spec.registry().saveAmount("fee", fee); - spec.registry().saveAmount("balance", fee * 2); - })) - .when(cryptoCreate(PAYER).balance(spec -> spec.registry().getAmount("balance"))) - .then( - UtilVerbs.inParallel( - cryptoTransfer(tinyBarsFromTo(PAYER, FUNDING, spec -> spec.registry() - .getAmount("fee"))) - .payingWith(PAYER) - .via("txnA") - .hasAnyKnownStatus(), - cryptoTransfer(tinyBarsFromTo(PAYER, FUNDING, spec -> spec.registry() - .getAmount("fee"))) - .payingWith(PAYER) - .via("txnB") - .hasAnyKnownStatus()), - getTxnRecord("txnA").logged(), - getTxnRecord("txnB").logged()); - } - - @Override - protected Logger getResultsLogger() { - return log; - } -} diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/issues/Issue1758Suite.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/issues/Issue1758Suite.java deleted file mode 100644 index 821872b6020e..000000000000 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/issues/Issue1758Suite.java +++ /dev/null @@ -1,71 +0,0 @@ -/* - * Copyright (C) 2020-2024 Hedera Hashgraph, LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.hedera.services.bdd.suites.issues; - -import static com.hedera.services.bdd.spec.HapiSpec.defaultFailingHapiSpec; -import static com.hedera.services.bdd.spec.queries.QueryVerbs.getTxnRecord; -import static com.hedera.services.bdd.spec.transactions.TxnVerbs.cryptoCreate; -import static com.hedera.services.bdd.spec.transactions.TxnVerbs.cryptoTransfer; -import static com.hedera.services.bdd.spec.transactions.crypto.HapiCryptoTransfer.tinyBarsFromTo; -import static com.hedera.services.bdd.spec.utilops.CustomSpecAssert.allRunFor; - -import com.hedera.services.bdd.junit.HapiTest; -import com.hedera.services.bdd.junit.HapiTestSuite; -import com.hedera.services.bdd.spec.HapiSpec; -import com.hedera.services.bdd.spec.queries.meta.HapiGetTxnRecord; -import com.hedera.services.bdd.spec.utilops.UtilVerbs; -import com.hedera.services.bdd.suites.HapiSuite; -import com.hederahashgraph.api.proto.java.TransactionRecord; -import java.util.List; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; - -@HapiTestSuite -public class Issue1758Suite extends HapiSuite { - private static final Logger log = LogManager.getLogger(Issue1758Suite.class); - - public static void main(String... args) { - new Issue1758Suite().runSuiteSync(); - } - - @Override - public List getSpecsInSuite() { - return List.of(allowsCryptoCreatePayerToHaveLessThanTwiceFee()); - } - - @HapiTest - public static HapiSpec allowsCryptoCreatePayerToHaveLessThanTwiceFee() { - return defaultFailingHapiSpec("AllowsCryptoCreatePayerToHaveLessThanTwiceFee") - .given( - cryptoCreate("payer").via("referenceTxn").balance(0L), - UtilVerbs.withOpContext((spec, ctxLog) -> { - HapiGetTxnRecord subOp = getTxnRecord("referenceTxn"); - allRunFor(spec, subOp); - TransactionRecord record = subOp.getResponseRecord(); - long fee = record.getTransactionFee(); - spec.registry().saveAmount("balance", fee * 2 - 1); - })) - .when(cryptoTransfer( - tinyBarsFromTo(GENESIS, "payer", spec -> spec.registry().getAmount("balance")))) - .then(cryptoCreate("irrelevant").balance(0L).payingWith("payer")); - } - - @Override - protected Logger getResultsLogger() { - return log; - } -} diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/issues/Issue1765Suite.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/issues/Issue1765Suite.java index dc43f1e86fbf..6e36efda4d03 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/issues/Issue1765Suite.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/issues/Issue1765Suite.java @@ -16,11 +16,11 @@ package com.hedera.services.bdd.suites.issues; +import static com.hedera.services.bdd.junit.ContextRequirement.SYSTEM_ACCOUNT_BALANCES; import static com.hedera.services.bdd.spec.HapiPropertySource.asContract; import static com.hedera.services.bdd.spec.HapiPropertySource.asFile; import static com.hedera.services.bdd.spec.HapiSpec.defaultHapiSpec; import static com.hedera.services.bdd.spec.assertions.TransactionRecordAsserts.recordWith; -import static com.hedera.services.bdd.spec.queries.QueryVerbs.getAccountBalance; import static com.hedera.services.bdd.spec.queries.QueryVerbs.getTxnRecord; import static com.hedera.services.bdd.spec.transactions.TxnVerbs.contractUpdate; import static com.hedera.services.bdd.spec.transactions.TxnVerbs.fileAppend; @@ -29,51 +29,28 @@ import static com.hedera.services.bdd.spec.utilops.UtilVerbs.takeBalanceSnapshots; import static com.hedera.services.bdd.spec.utilops.UtilVerbs.validateTransferListForBalances; import static com.hedera.services.bdd.spec.utilops.UtilVerbs.withOpContext; +import static com.hedera.services.bdd.suites.HapiSuite.FUNDING; +import static com.hedera.services.bdd.suites.HapiSuite.GENESIS; +import static com.hedera.services.bdd.suites.HapiSuite.NODE; +import static com.hedera.services.bdd.suites.HapiSuite.STAKING_REWARD; +import static com.hedera.services.bdd.suites.HapiSuite.flattened; -import com.hedera.services.bdd.junit.HapiTest; -import com.hedera.services.bdd.junit.HapiTestSuite; -import com.hedera.services.bdd.spec.HapiSpec; +import com.hedera.services.bdd.junit.LeakyHapiTest; import com.hedera.services.bdd.spec.keys.KeyFactory; -import com.hedera.services.bdd.suites.HapiSuite; import com.hederahashgraph.api.proto.java.ResponseCodeEnum; import java.util.List; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; +import java.util.stream.Stream; +import org.junit.jupiter.api.DynamicTest; -@HapiTestSuite -public class Issue1765Suite extends HapiSuite { - private static final Logger log = LogManager.getLogger(Issue1765Suite.class); +public class Issue1765Suite { private static final String ACCOUNT = "1.1.1"; private static final String INVALID_UPDATE_TXN = "invalidUpdateTxn"; private static final String INVALID_APPEND_TXN = "invalidAppendTxn"; private static final String IMAGINARY = "imaginary"; private static final String MEMO_IS = "Turning and turning in the widening gyre"; - public static void main(String... args) { - new Issue1765Suite().runSuiteSync(); - } - - @Override - public List getSpecsInSuite() { - return List.of( - // recordOfInvalidFileAppendSanityChecks(), - // recordOfInvalidAccountUpdateSanityChecks(), - // recordOfInvalidAccountTransferSanityChecks() - // recordOfInvalidFileUpdateSanityChecks() - // recordOfInvalidContractUpdateSanityChecks() - get950Balance()); - } - - @HapiTest - private static HapiSpec get950Balance() { - return defaultHapiSpec("Get950Balance") - .given() - .when() - .then(getAccountBalance("0.0.950").logged()); - } - - @HapiTest - final HapiSpec recordOfInvalidContractUpdateSanityChecks() { + @LeakyHapiTest(SYSTEM_ACCOUNT_BALANCES) + final Stream recordOfInvalidContractUpdateSanityChecks() { final long ADEQUATE_FEE = 100_000_000L; final String INVALID_CONTRACT = IMAGINARY; final String THE_MEMO_IS = MEMO_IS; @@ -95,8 +72,8 @@ final HapiSpec recordOfInvalidContractUpdateSanityChecks() { .hasPriority(recordWith().memo(THE_MEMO_IS))); } - @HapiTest - private static HapiSpec recordOfInvalidFileUpdateSanityChecks() { + @LeakyHapiTest(SYSTEM_ACCOUNT_BALANCES) + final Stream recordOfInvalidFileUpdateSanityChecks() { final long ADEQUATE_FEE = 100_000_000L; final String INVALID_FILE = IMAGINARY; final String THE_MEMO_IS = MEMO_IS; @@ -118,8 +95,8 @@ private static HapiSpec recordOfInvalidFileUpdateSanityChecks() { .hasPriority(recordWith().memo(THE_MEMO_IS))); } - @HapiTest - private static HapiSpec recordOfInvalidFileAppendSanityChecks() { + @LeakyHapiTest(SYSTEM_ACCOUNT_BALANCES) + final Stream recordOfInvalidFileAppendSanityChecks() { final long ADEQUATE_FEE = 100_000_000L; final String INVALID_FILE = IMAGINARY; final String THE_MEMO_IS = MEMO_IS; @@ -141,9 +118,4 @@ private static HapiSpec recordOfInvalidFileAppendSanityChecks() { getTxnRecord(INVALID_APPEND_TXN) .hasPriority(recordWith().memo(THE_MEMO_IS))); } - - @Override - protected Logger getResultsLogger() { - return log; - } } diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/issues/Issue2051Spec.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/issues/Issue2051Spec.java deleted file mode 100644 index a718c9bc6bbd..000000000000 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/issues/Issue2051Spec.java +++ /dev/null @@ -1,118 +0,0 @@ -/* - * Copyright (C) 2020-2024 Hedera Hashgraph, LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.hedera.services.bdd.suites.issues; - -import static com.hedera.services.bdd.spec.HapiSpec.defaultHapiSpec; -import static com.hedera.services.bdd.spec.assertions.AccountInfoAsserts.approxChangeFromSnapshot; -import static com.hedera.services.bdd.spec.queries.QueryVerbs.getAccountBalance; -import static com.hedera.services.bdd.spec.queries.QueryVerbs.getTxnRecord; -import static com.hedera.services.bdd.spec.transactions.TxnVerbs.contractCreate; -import static com.hedera.services.bdd.spec.transactions.TxnVerbs.contractDelete; -import static com.hedera.services.bdd.spec.transactions.TxnVerbs.cryptoCreate; -import static com.hedera.services.bdd.spec.transactions.TxnVerbs.cryptoDelete; -import static com.hedera.services.bdd.spec.transactions.TxnVerbs.uploadInitCode; -import static com.hedera.services.bdd.spec.utilops.UtilVerbs.balanceSnapshot; -import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.ACCOUNT_DELETED; -import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.INVALID_CONTRACT_ID; -import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.OBTAINER_DOES_NOT_EXIST; - -import com.hedera.services.bdd.junit.HapiTest; -import com.hedera.services.bdd.junit.HapiTestSuite; -import com.hedera.services.bdd.spec.HapiSpec; -import com.hedera.services.bdd.suites.HapiSuite; -import java.util.List; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; - -@HapiTestSuite -public class Issue2051Spec extends HapiSuite { - private static final Logger log = LogManager.getLogger(Issue2051Spec.class); - private static final String TRANSFER_CONTRACT = "transferContract"; - private static final String TRANSFER = "transfer"; - private static final String PAYER = "payer"; - private static final String SNAPSHOT = "snapshot"; - private static final String DELETE_TXN = "deleteTxn"; - - public static void main(String... args) { - new Issue2051Spec().runSuiteSync(); - } - - @Override - public List getSpecsInSuite() { - return List.of(new HapiSpec[] { - transferAccountCannotBeDeletedForContractTarget(), - transferAccountCannotBeDeleted(), - tbdCanPayForItsOwnDeletion(), - }); - } - - @HapiTest - final HapiSpec tbdCanPayForItsOwnDeletion() { - return defaultHapiSpec("TbdCanPayForItsOwnDeletion") - .given(cryptoCreate("tbd"), cryptoCreate(TRANSFER)) - .when() - .then( - cryptoDelete("tbd") - .via("selfFinanced") - .payingWith("tbd") - .transfer(TRANSFER), - getTxnRecord("selfFinanced").logged()); - } - - @HapiTest - final HapiSpec transferAccountCannotBeDeleted() { - return defaultHapiSpec("TransferAccountCannotBeDeleted") - .given(cryptoCreate(PAYER), cryptoCreate(TRANSFER), cryptoCreate("tbd")) - .when(cryptoDelete(TRANSFER)) - .then( - balanceSnapshot(SNAPSHOT, PAYER), - cryptoDelete("tbd") - .via(DELETE_TXN) - .payingWith(PAYER) - .transfer(TRANSFER) - .hasKnownStatus(ACCOUNT_DELETED), - getTxnRecord(DELETE_TXN).logged(), - getAccountBalance(PAYER).hasTinyBars(approxChangeFromSnapshot(SNAPSHOT, -9384399, 10000))); - } - - @HapiTest - final HapiSpec transferAccountCannotBeDeletedForContractTarget() { - return defaultHapiSpec("TransferAccountCannotBeDeletedForContractTarget") - .given( - uploadInitCode("CreateTrivial"), - uploadInitCode("PayReceivable"), - cryptoCreate(TRANSFER), - contractCreate("CreateTrivial"), - contractCreate("PayReceivable")) - .when(cryptoDelete(TRANSFER), contractDelete("PayReceivable")) - .then( - balanceSnapshot(SNAPSHOT, GENESIS), - contractDelete("CreateTrivial") - .via(DELETE_TXN) - .transferAccount(TRANSFER) - .hasKnownStatus(OBTAINER_DOES_NOT_EXIST), - contractDelete("CreateTrivial") - .via(DELETE_TXN) - .transferContract("PayReceivable") - .hasKnownStatus(INVALID_CONTRACT_ID)); - } - - @Override - protected Logger getResultsLogger() { - return log; - } -} diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/issues/Issue2098Spec.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/issues/Issue2098Spec.java index 94d0ec475d74..5ab2cd2e9ad9 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/issues/Issue2098Spec.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/issues/Issue2098Spec.java @@ -16,6 +16,7 @@ package com.hedera.services.bdd.suites.issues; +import static com.hedera.services.bdd.junit.ContextRequirement.PERMISSION_OVERRIDES; import static com.hedera.services.bdd.spec.HapiSpec.defaultHapiSpec; import static com.hedera.services.bdd.spec.queries.QueryVerbs.getTopicInfo; import static com.hedera.services.bdd.spec.transactions.TxnVerbs.createTopic; @@ -23,42 +24,26 @@ import static com.hedera.services.bdd.spec.transactions.TxnVerbs.cryptoTransfer; import static com.hedera.services.bdd.spec.transactions.TxnVerbs.fileUpdate; import static com.hedera.services.bdd.spec.transactions.crypto.HapiCryptoTransfer.tinyBarsFromTo; +import static com.hedera.services.bdd.suites.HapiSuite.ADDRESS_BOOK_CONTROL; +import static com.hedera.services.bdd.suites.HapiSuite.API_PERMISSIONS; +import static com.hedera.services.bdd.suites.HapiSuite.FUNDING; +import static com.hedera.services.bdd.suites.HapiSuite.GENESIS; import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.NOT_SUPPORTED; import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.OK; import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.UNAUTHORIZED; -import com.hedera.services.bdd.junit.HapiTest; -import com.hedera.services.bdd.junit.HapiTestSuite; -import com.hedera.services.bdd.spec.HapiSpec; -import com.hedera.services.bdd.suites.HapiSuite; -import java.util.List; +import com.hedera.services.bdd.junit.LeakyHapiTest; import java.util.Map; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; +import java.util.stream.Stream; +import org.junit.jupiter.api.DynamicTest; -@HapiTestSuite -public class Issue2098Spec extends HapiSuite { - private static final Logger log = LogManager.getLogger(Issue2098Spec.class); +public class Issue2098Spec { private static final String CIVILIAN = "civilian"; private static final String CRYPTO_TRANSFER = "cryptoTransfer"; private static final String GET_TOPIC_INFO = "getTopicInfo"; - public static void main(String... args) { - new Issue2098Spec().runSuiteSync(); - } - - @Override - public List getSpecsInSuite() { - return List.of(new HapiSpec[] { - queryApiPermissionsChangeImmediately(), - txnApiPermissionsChangeImmediately(), - adminsCanQueryNoMatterPermissions(), - adminsCanTransactNoMatterPermissions(), - }); - } - - @HapiTest - final HapiSpec txnApiPermissionsChangeImmediately() { + @LeakyHapiTest(PERMISSION_OVERRIDES) + final Stream txnApiPermissionsChangeImmediately() { return defaultHapiSpec("TxnApiPermissionsChangeImmediately") .given(cryptoCreate(CIVILIAN)) .when(fileUpdate(API_PERMISSIONS) @@ -75,8 +60,8 @@ final HapiSpec txnApiPermissionsChangeImmediately() { cryptoTransfer(tinyBarsFromTo(CIVILIAN, FUNDING, 1L)).payingWith(CIVILIAN)); } - @HapiTest - final HapiSpec queryApiPermissionsChangeImmediately() { + @LeakyHapiTest(PERMISSION_OVERRIDES) + final Stream queryApiPermissionsChangeImmediately() { return defaultHapiSpec("QueryApiPermissionsChangeImmediately") .given(cryptoCreate(CIVILIAN), createTopic("misc")) .when(fileUpdate(API_PERMISSIONS) @@ -90,8 +75,8 @@ final HapiSpec queryApiPermissionsChangeImmediately() { getTopicInfo("misc").payingWith(CIVILIAN)); } - @HapiTest - final HapiSpec adminsCanQueryNoMatterPermissions() { + @LeakyHapiTest(PERMISSION_OVERRIDES) + final Stream adminsCanQueryNoMatterPermissions() { return defaultHapiSpec("AdminsCanQueryNoMatterPermissions") .given(cryptoCreate(CIVILIAN), createTopic("misc")) .when(fileUpdate(API_PERMISSIONS) @@ -105,8 +90,8 @@ final HapiSpec adminsCanQueryNoMatterPermissions() { .overridingProps(Map.of(GET_TOPIC_INFO, "0-*"))); } - @HapiTest - final HapiSpec adminsCanTransactNoMatterPermissions() { + @LeakyHapiTest(PERMISSION_OVERRIDES) + final Stream adminsCanTransactNoMatterPermissions() { return defaultHapiSpec("AdminsCanTransactNoMatterPermissions") .given(cryptoCreate(CIVILIAN)) .when(fileUpdate(API_PERMISSIONS) @@ -122,9 +107,4 @@ final HapiSpec adminsCanTransactNoMatterPermissions() { .payingWith(ADDRESS_BOOK_CONTROL) .overridingProps(Map.of(CRYPTO_TRANSFER, "0-*"))); } - - @Override - protected Logger getResultsLogger() { - return log; - } } diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/issues/Issue2143Spec.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/issues/Issue2143Spec.java index 65a77b56c09f..56de8ebef4c4 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/issues/Issue2143Spec.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/issues/Issue2143Spec.java @@ -16,37 +16,26 @@ package com.hedera.services.bdd.suites.issues; +import static com.hedera.services.bdd.junit.ContextRequirement.PERMISSION_OVERRIDES; +import static com.hedera.services.bdd.junit.ContextRequirement.PROPERTY_OVERRIDES; import static com.hedera.services.bdd.spec.HapiSpec.defaultHapiSpec; import static com.hedera.services.bdd.spec.transactions.TxnVerbs.cryptoTransfer; import static com.hedera.services.bdd.spec.transactions.TxnVerbs.fileUpdate; import static com.hedera.services.bdd.spec.transactions.crypto.HapiCryptoTransfer.tinyBarsFromTo; +import static com.hedera.services.bdd.suites.HapiSuite.ADDRESS_BOOK_CONTROL; +import static com.hedera.services.bdd.suites.HapiSuite.API_PERMISSIONS; +import static com.hedera.services.bdd.suites.HapiSuite.APP_PROPERTIES; +import static com.hedera.services.bdd.suites.HapiSuite.EXCHANGE_RATE_CONTROL; +import static com.hedera.services.bdd.suites.HapiSuite.GENESIS; -import com.hedera.services.bdd.junit.HapiTest; -import com.hedera.services.bdd.junit.HapiTestSuite; -import com.hedera.services.bdd.spec.HapiSpec; -import com.hedera.services.bdd.suites.HapiSuite; -import java.util.List; +import com.hedera.services.bdd.junit.LeakyHapiTest; import java.util.Map; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; +import java.util.stream.Stream; +import org.junit.jupiter.api.DynamicTest; -@HapiTestSuite -public class Issue2143Spec extends HapiSuite { - private static final Logger log = LogManager.getLogger(Issue2143Spec.class); - - public static void main(String... args) { - new Issue2143Spec().runSuiteSync(); - } - - @Override - public List getSpecsInSuite() { - return List.of(new HapiSpec[] { - account55ControlCanUpdatePropertiesAndPermissions(), account57ControlCanUpdatePropertiesAndPermissions(), - }); - } - - @HapiTest - final HapiSpec account55ControlCanUpdatePropertiesAndPermissions() { +public class Issue2143Spec { + @LeakyHapiTest({PERMISSION_OVERRIDES, PROPERTY_OVERRIDES}) + final Stream account55ControlCanUpdatePropertiesAndPermissions() { return defaultHapiSpec("Account55ControlCanUpdatePropertiesAndPermissions") .given(cryptoTransfer(tinyBarsFromTo(GENESIS, ADDRESS_BOOK_CONTROL, 1_000_000_000L))) .when( @@ -65,8 +54,8 @@ final HapiSpec account55ControlCanUpdatePropertiesAndPermissions() { .payingWith(ADDRESS_BOOK_CONTROL)); } - @HapiTest - final HapiSpec account57ControlCanUpdatePropertiesAndPermissions() { + @LeakyHapiTest({PERMISSION_OVERRIDES, PROPERTY_OVERRIDES}) + final Stream account57ControlCanUpdatePropertiesAndPermissions() { return defaultHapiSpec("Account57ControlCanUpdatePropertiesAndPermissions") .given(cryptoTransfer(tinyBarsFromTo(GENESIS, EXCHANGE_RATE_CONTROL, 1_000_000_000L))) .when( @@ -84,9 +73,4 @@ final HapiSpec account57ControlCanUpdatePropertiesAndPermissions() { .overridingProps(Map.of("createFile", "0-*")) .payingWith(EXCHANGE_RATE_CONTROL)); } - - @Override - protected Logger getResultsLogger() { - return log; - } } diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/issues/Issue2150Spec.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/issues/Issue2150Spec.java deleted file mode 100644 index 70a6b4be1508..000000000000 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/issues/Issue2150Spec.java +++ /dev/null @@ -1,76 +0,0 @@ -/* - * Copyright (C) 2020-2024 Hedera Hashgraph, LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.hedera.services.bdd.suites.issues; - -import static com.hedera.services.bdd.spec.HapiSpec.defaultHapiSpec; -import static com.hedera.services.bdd.spec.keys.ControlForKey.forKey; -import static com.hedera.services.bdd.spec.keys.KeyShape.sigs; -import static com.hedera.services.bdd.spec.keys.SigControl.OFF; -import static com.hedera.services.bdd.spec.keys.SigControl.ON; -import static com.hedera.services.bdd.spec.transactions.TxnVerbs.cryptoCreate; -import static com.hedera.services.bdd.spec.transactions.TxnVerbs.cryptoTransfer; -import static com.hedera.services.bdd.spec.transactions.crypto.HapiCryptoTransfer.tinyBarsFromTo; -import static com.hedera.services.bdd.spec.utilops.UtilVerbs.newKeyNamed; - -import com.hedera.services.bdd.junit.HapiTest; -import com.hedera.services.bdd.junit.HapiTestSuite; -import com.hedera.services.bdd.spec.HapiSpec; -import com.hedera.services.bdd.spec.keys.KeyShape; -import com.hedera.services.bdd.spec.keys.SigControl; -import com.hedera.services.bdd.suites.HapiSuite; -import java.util.List; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; - -@HapiTestSuite -public class Issue2150Spec extends HapiSuite { - private static final Logger log = LogManager.getLogger(Issue2150Spec.class); - private static final String PAYER = "payer"; - private static final String RECEIVER = "receiver"; - - public static void main(String... args) { - new Issue2150Spec().runSuiteSync(); - } - - @Override - public List getSpecsInSuite() { - return List.of(new HapiSpec[] { - multiKeyNonPayerEntityVerifiedAsync(), - }); - } - - @HapiTest - final HapiSpec multiKeyNonPayerEntityVerifiedAsync() { - KeyShape LARGE_THRESH_SHAPE = KeyShape.threshOf(1, 10); - SigControl firstOnly = LARGE_THRESH_SHAPE.signedWith(sigs(ON, OFF, OFF, OFF, OFF, OFF, OFF, OFF, OFF, OFF)); - - return defaultHapiSpec("MultiKeyNonPayerEntityVerifiedAsync") - .given( - newKeyNamed("payerKey").shape(LARGE_THRESH_SHAPE), - newKeyNamed("receiverKey").shape(LARGE_THRESH_SHAPE), - cryptoCreate(PAYER).keyShape(LARGE_THRESH_SHAPE), - cryptoCreate(RECEIVER).keyShape(LARGE_THRESH_SHAPE).receiverSigRequired(true)) - .when() - .then(cryptoTransfer(tinyBarsFromTo(PAYER, RECEIVER, 1L)) - .sigControl(forKey(PAYER, firstOnly), forKey(RECEIVER, firstOnly))); - } - - @Override - protected Logger getResultsLogger() { - return log; - } -} diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/issues/Issue2319Spec.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/issues/Issue2319Spec.java index 091dabbba4b0..18f893170fd0 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/issues/Issue2319Spec.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/issues/Issue2319Spec.java @@ -16,6 +16,7 @@ package com.hedera.services.bdd.suites.issues; +import static com.hedera.services.bdd.junit.ContextRequirement.SYSTEM_ACCOUNT_KEYS; import static com.hedera.services.bdd.spec.HapiSpec.defaultHapiSpec; import static com.hedera.services.bdd.spec.queries.QueryVerbs.getFileContents; import static com.hedera.services.bdd.spec.transactions.TxnVerbs.cryptoCreate; @@ -26,43 +27,31 @@ import static com.hedera.services.bdd.spec.utilops.UtilVerbs.newKeyListNamed; import static com.hedera.services.bdd.spec.utilops.UtilVerbs.newKeyNamed; import static com.hedera.services.bdd.spec.utilops.UtilVerbs.withOpContext; +import static com.hedera.services.bdd.suites.HapiSuite.ADDRESS_BOOK_CONTROL; +import static com.hedera.services.bdd.suites.HapiSuite.API_PERMISSIONS; +import static com.hedera.services.bdd.suites.HapiSuite.APP_PROPERTIES; +import static com.hedera.services.bdd.suites.HapiSuite.EXCHANGE_RATES; +import static com.hedera.services.bdd.suites.HapiSuite.EXCHANGE_RATE_CONTROL; +import static com.hedera.services.bdd.suites.HapiSuite.GENESIS; +import static com.hedera.services.bdd.suites.HapiSuite.SYSTEM_ADMIN; import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.AUTHORIZATION_FAILED; import com.google.protobuf.ByteString; -import com.hedera.services.bdd.junit.HapiTest; -import com.hedera.services.bdd.junit.HapiTestSuite; -import com.hedera.services.bdd.spec.HapiSpec; +import com.hedera.services.bdd.junit.LeakyHapiTest; import com.hedera.services.bdd.spec.utilops.CustomSpecAssert; -import com.hedera.services.bdd.suites.HapiSuite; import java.util.List; import java.util.Map; import java.util.concurrent.atomic.AtomicReference; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; +import java.util.stream.Stream; +import org.junit.jupiter.api.DynamicTest; -@HapiTestSuite -public class Issue2319Spec extends HapiSuite { - private static final Logger log = LogManager.getLogger(Issue2319Spec.class); +public class Issue2319Spec { private static final String NON_TREASURY_KEY = "nonTreasuryKey"; private static final String NON_TREASURY_ADMIN_KEY = "nonTreasuryAdminKey"; private static final String DEFAULT_ADMIN_KEY = "defaultAdminKey"; - public static void main(String... args) { - new Issue2319Spec().runSuiteSync(); - } - - @Override - public List getSpecsInSuite() { - return List.of(new HapiSpec[] { - sysFileImmutabilityWaivedForMasterAndTreasury(), - propsPermissionsSigReqsWaivedForAddressBookAdmin(), - sysAccountSigReqsWaivedForMasterAndTreasury(), - sysFileSigReqsWaivedForMasterAndTreasury() - }); - } - - @HapiTest - final HapiSpec propsPermissionsSigReqsWaivedForAddressBookAdmin() { + @LeakyHapiTest(SYSTEM_ACCOUNT_KEYS) + final Stream propsPermissionsSigReqsWaivedForAddressBookAdmin() { return defaultHapiSpec("PropsPermissionsSigReqsWaivedForAddressBookAdmin") .given( newKeyNamed(NON_TREASURY_KEY), @@ -88,8 +77,8 @@ final HapiSpec propsPermissionsSigReqsWaivedForAddressBookAdmin() { fileUpdate(API_PERMISSIONS).wacl(GENESIS)); } - @HapiTest - final HapiSpec sysFileImmutabilityWaivedForMasterAndTreasury() { + @LeakyHapiTest(SYSTEM_ACCOUNT_KEYS) + final Stream sysFileImmutabilityWaivedForMasterAndTreasury() { return defaultHapiSpec("sysFileImmutabilityWaivedForMasterAndTreasury") .given( cryptoCreate("civilian"), @@ -112,8 +101,8 @@ final HapiSpec sysFileImmutabilityWaivedForMasterAndTreasury() { .signedBy(GENESIS)); } - @HapiTest - final HapiSpec sysAccountSigReqsWaivedForMasterAndTreasury() { + @LeakyHapiTest(SYSTEM_ACCOUNT_KEYS) + final Stream sysAccountSigReqsWaivedForMasterAndTreasury() { return defaultHapiSpec("SysAccountSigReqsWaivedForMasterAndTreasury") .given( newKeyNamed(NON_TREASURY_KEY), @@ -146,8 +135,8 @@ final HapiSpec sysAccountSigReqsWaivedForMasterAndTreasury() { .signedBy(GENESIS)); } - @HapiTest - final HapiSpec sysFileSigReqsWaivedForMasterAndTreasury() { + @LeakyHapiTest(SYSTEM_ACCOUNT_KEYS) + final Stream sysFileSigReqsWaivedForMasterAndTreasury() { var validRates = new AtomicReference(); return defaultHapiSpec("SysFileSigReqsWaivedForMasterAndTreasury") @@ -183,9 +172,4 @@ final HapiSpec sysFileSigReqsWaivedForMasterAndTreasury() { .hasPrecheck(AUTHORIZATION_FAILED), fileUpdate(EXCHANGE_RATES).payingWith(GENESIS).wacl(GENESIS)); } - - @Override - protected Logger getResultsLogger() { - return log; - } } diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/issues/Issue305Spec.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/issues/Issue305Spec.java index 9e5d01d78428..ca72e6a46bb9 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/issues/Issue305Spec.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/issues/Issue305Spec.java @@ -16,32 +16,26 @@ package com.hedera.services.bdd.suites.issues; -import static com.hedera.services.bdd.spec.HapiSpec.defaultHapiSpec; +import static com.hedera.services.bdd.junit.ContextRequirement.PROPERTY_OVERRIDES; import static com.hedera.services.bdd.spec.HapiSpec.propertyPreservingHapiSpec; -import static com.hedera.services.bdd.spec.queries.QueryVerbs.getFileInfo; import static com.hedera.services.bdd.spec.queries.QueryVerbs.getTxnRecord; import static com.hedera.services.bdd.spec.transactions.TxnVerbs.contractCall; import static com.hedera.services.bdd.spec.transactions.TxnVerbs.contractCreate; import static com.hedera.services.bdd.spec.transactions.TxnVerbs.cryptoCreate; -import static com.hedera.services.bdd.spec.transactions.TxnVerbs.fileCreate; -import static com.hedera.services.bdd.spec.transactions.TxnVerbs.fileDelete; import static com.hedera.services.bdd.spec.transactions.TxnVerbs.uploadInitCode; import static com.hedera.services.bdd.spec.utilops.CustomSpecAssert.allRunFor; -import static com.hedera.services.bdd.spec.utilops.UtilVerbs.newKeyNamed; import static com.hedera.services.bdd.spec.utilops.UtilVerbs.overridingAllOf; import static com.hedera.services.bdd.spec.utilops.UtilVerbs.withOpContext; +import static com.hedera.services.bdd.suites.HapiSuite.GENESIS; +import static com.hedera.services.bdd.suites.HapiSuite.ONE_HBAR; +import static com.hedera.services.bdd.suites.HapiSuite.ONE_HUNDRED_HBARS; import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.BUSY; import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.OK; import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.RECORD_NOT_FOUND; import com.google.protobuf.InvalidProtocolBufferException; import com.hedera.node.app.hapi.utils.CommonUtils; -import com.hedera.services.bdd.junit.HapiTest; -import com.hedera.services.bdd.junit.HapiTestSuite; -import com.hedera.services.bdd.spec.HapiPropertySource; -import com.hedera.services.bdd.spec.HapiSpec; -import com.hedera.services.bdd.spec.keys.KeyFactory; -import com.hedera.services.bdd.suites.HapiSuite; +import com.hedera.services.bdd.junit.LeakyHapiTest; import com.hederahashgraph.api.proto.java.Transaction; import com.hederahashgraph.api.proto.java.TransactionID; import edu.umd.cs.findbugs.annotations.NonNull; @@ -51,55 +45,13 @@ import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicLong; -import java.util.concurrent.atomic.AtomicReference; -import java.util.stream.IntStream; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; +import java.util.stream.Stream; import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.DynamicTest; -@HapiTestSuite -public class Issue305Spec extends HapiSuite { - private static final Logger log = LogManager.getLogger(Issue305Spec.class); - private static final String KEY = "tbdKey"; - - public static void main(String... args) { - new Issue305Spec().runSuiteSync(); - } - - @Override - public List getSpecsInSuite() { - final var repeatedSpecs = new ArrayList<>(IntStream.range(0, 5) - .mapToObj(ignore -> createDeleteInSameRoundWorks()) - .toList()); - repeatedSpecs.add(congestionMultipliersRefreshOnPropertyUpdate()); - return repeatedSpecs; - } - - @HapiTest - final HapiSpec createDeleteInSameRoundWorks() { - AtomicReference nextFileId = new AtomicReference<>(); - return defaultHapiSpec("CreateDeleteInSameRoundWorks") - .given( - newKeyNamed(KEY).type(KeyFactory.KeyType.LIST), - fileCreate("marker").via("markerTxn")) - .when(withOpContext((spec, opLog) -> { - var lookup = getTxnRecord("markerTxn"); - allRunFor(spec, lookup); - var markerFid = lookup.getResponseRecord().getReceipt().getFileID(); - var nextFid = markerFid.toBuilder() - .setFileNum(markerFid.getFileNum() + 1) - .build(); - nextFileId.set(HapiPropertySource.asFileString(nextFid)); - opLog.info("Next file will be {}", nextFileId.get()); - })) - .then( - fileCreate("tbd").key(KEY).deferStatusResolution(), - fileDelete(nextFileId::get).signedBy(GENESIS, KEY), - getFileInfo(nextFileId::get).hasDeleted(true)); - } - - @HapiTest - final HapiSpec congestionMultipliersRefreshOnPropertyUpdate() { +public class Issue305Spec { + @LeakyHapiTest(PROPERTY_OVERRIDES) + final Stream congestionMultipliersRefreshOnPropertyUpdate() { final var civilian = "civilian"; final var preCongestionTxn = "preCongestionTxn"; final var multipurposeContract = "Multipurpose"; @@ -184,9 +136,4 @@ private TransactionID idOf(@NonNull final Transaction txn) { throw new IllegalArgumentException(e); } } - - @Override - protected Logger getResultsLogger() { - return log; - } } diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/issues/Issue310Suite.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/issues/Issue310Suite.java deleted file mode 100644 index 9d1930533c90..000000000000 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/issues/Issue310Suite.java +++ /dev/null @@ -1,122 +0,0 @@ -/* - * Copyright (C) 2020-2024 Hedera Hashgraph, LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.hedera.services.bdd.suites.issues; - -import static com.hedera.services.bdd.spec.HapiSpec.defaultHapiSpec; -import static com.hedera.services.bdd.spec.queries.QueryVerbs.getTxnRecord; -import static com.hedera.services.bdd.spec.transactions.TxnVerbs.createTopic; -import static com.hedera.services.bdd.spec.transactions.TxnVerbs.cryptoCreate; -import static com.hedera.services.bdd.spec.transactions.TxnVerbs.submitMessageTo; -import static com.hedera.services.bdd.spec.utilops.UtilVerbs.newKeyNamed; -import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.DUPLICATE_TRANSACTION; - -import com.hedera.services.bdd.junit.HapiTest; -import com.hedera.services.bdd.junit.HapiTestSuite; -import com.hedera.services.bdd.spec.HapiSpec; -import com.hedera.services.bdd.spec.utilops.UtilVerbs; -import com.hedera.services.bdd.suites.HapiSuite; -import com.hedera.services.bdd.suites.crypto.CryptoCreateSuite; -import java.util.List; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; - -@HapiTestSuite -public class Issue310Suite extends HapiSuite { - private static final Logger log = LogManager.getLogger(CryptoCreateSuite.class); - - public static void main(String... args) { - new Issue310Suite().runSuiteSync(); - } - - @Override - public List getSpecsInSuite() { - return List.of( - duplicatedTxnsSameTypeDetected(), - duplicatedTxnsDifferentTypesDetected(), - duplicatedTxnsSameTypeDifferentNodesDetected(), - duplicatedTxnsDifferentTypesDifferentNodesDetected()); - } - - @HapiTest - final HapiSpec duplicatedTxnsSameTypeDetected() { - long initialBalance = 10_000L; - - return defaultHapiSpec("duplicatedTxnsSameTypeDetected") - .given( - cryptoCreate("acct1").balance(initialBalance).logged().via("txnId1"), - UtilVerbs.sleepFor(1000), - cryptoCreate("acctWithDuplicateTxnId") - .balance(initialBalance) - .logged() - .txnId("txnId1") - .hasPrecheck(DUPLICATE_TRANSACTION)) - .when() - .then(getTxnRecord("txnId1").logged()); - } - - @HapiTest - final HapiSpec duplicatedTxnsDifferentTypesDetected() { - return defaultHapiSpec("duplicatedTxnsDifferentTypesDetected") - .given( - cryptoCreate("acct2").via("txnId2"), - newKeyNamed("key1"), - createTopic("topic2").submitKeyName("key1")) - .when(submitMessageTo("topic2") - .message("Hello world") - .payingWith("acct2") - .txnId("txnId2") - .hasPrecheck(DUPLICATE_TRANSACTION)) - .then(getTxnRecord("txnId2").logged()); - } - - // This test requires multiple nodes - @HapiTest - final HapiSpec duplicatedTxnsSameTypeDifferentNodesDetected() { - - return defaultHapiSpec("duplicatedTxnsSameTypeDifferentNodesDetected") - .given( - cryptoCreate("acct3").setNode("0.0.3").via("txnId1"), - UtilVerbs.sleepFor(1000), - cryptoCreate("acctWithDuplicateTxnId") - .setNode("0.0.5") - .txnId("txnId1") - .hasPrecheck(DUPLICATE_TRANSACTION)) - .when() - .then(getTxnRecord("txnId1").logged()); - } - - // This test requires multiple nodes - @HapiTest - final HapiSpec duplicatedTxnsDifferentTypesDifferentNodesDetected() { - return defaultHapiSpec("duplicatedTxnsDifferentTypesDifferentNodesDetected") - .given( - cryptoCreate("acct4").via("txnId4").setNode("0.0.3"), - newKeyNamed("key2"), - createTopic("topic2").setNode("0.0.5").submitKeyName("key2")) - .when(submitMessageTo("topic2") - .message("Hello world") - .payingWith("acct4") - .txnId("txnId4") - .hasPrecheck(DUPLICATE_TRANSACTION)) - .then(getTxnRecord("txnId4").logged()); - } - - @Override - protected Logger getResultsLogger() { - return log; - } -} diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/issues/IssueRegressionTests.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/issues/IssueRegressionTests.java new file mode 100644 index 000000000000..3581c7191715 --- /dev/null +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/issues/IssueRegressionTests.java @@ -0,0 +1,358 @@ +/* + * Copyright (C) 2024 Hedera Hashgraph, LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.hedera.services.bdd.suites.issues; + +import static com.hedera.services.bdd.junit.ContextRequirement.NO_CONCURRENT_CREATIONS; +import static com.hedera.services.bdd.junit.TestTags.TOKEN; +import static com.hedera.services.bdd.spec.HapiSpec.defaultHapiSpec; +import static com.hedera.services.bdd.spec.HapiSpec.hapiTest; +import static com.hedera.services.bdd.spec.assertions.AccountInfoAsserts.approxChangeFromSnapshot; +import static com.hedera.services.bdd.spec.keys.ControlForKey.forKey; +import static com.hedera.services.bdd.spec.keys.KeyShape.sigs; +import static com.hedera.services.bdd.spec.keys.SigControl.OFF; +import static com.hedera.services.bdd.spec.keys.SigControl.ON; +import static com.hedera.services.bdd.spec.queries.QueryVerbs.getAccountBalance; +import static com.hedera.services.bdd.spec.queries.QueryVerbs.getAccountInfo; +import static com.hedera.services.bdd.spec.queries.QueryVerbs.getFileInfo; +import static com.hedera.services.bdd.spec.queries.QueryVerbs.getTxnRecord; +import static com.hedera.services.bdd.spec.queries.QueryVerbs.getVersionInfo; +import static com.hedera.services.bdd.spec.transactions.TxnVerbs.contractCreate; +import static com.hedera.services.bdd.spec.transactions.TxnVerbs.contractDelete; +import static com.hedera.services.bdd.spec.transactions.TxnVerbs.createTopic; +import static com.hedera.services.bdd.spec.transactions.TxnVerbs.cryptoCreate; +import static com.hedera.services.bdd.spec.transactions.TxnVerbs.cryptoDelete; +import static com.hedera.services.bdd.spec.transactions.TxnVerbs.cryptoTransfer; +import static com.hedera.services.bdd.spec.transactions.TxnVerbs.fileCreate; +import static com.hedera.services.bdd.spec.transactions.TxnVerbs.fileDelete; +import static com.hedera.services.bdd.spec.transactions.TxnVerbs.submitMessageTo; +import static com.hedera.services.bdd.spec.transactions.TxnVerbs.uploadInitCode; +import static com.hedera.services.bdd.spec.transactions.crypto.HapiCryptoTransfer.tinyBarsFromTo; +import static com.hedera.services.bdd.spec.utilops.CustomSpecAssert.allRunFor; +import static com.hedera.services.bdd.spec.utilops.UtilVerbs.balanceSnapshot; +import static com.hedera.services.bdd.spec.utilops.UtilVerbs.newKeyNamed; +import static com.hedera.services.bdd.spec.utilops.UtilVerbs.sendModified; +import static com.hedera.services.bdd.spec.utilops.UtilVerbs.withOpContext; +import static com.hedera.services.bdd.spec.utilops.mod.ModificationUtils.withSuccessivelyVariedQueryIds; +import static com.hedera.services.bdd.suites.HapiSuite.CIVILIAN_PAYER; +import static com.hedera.services.bdd.suites.HapiSuite.FUNDING; +import static com.hedera.services.bdd.suites.HapiSuite.GENESIS; +import static com.hedera.services.bdd.suites.HapiSuite.ONE_HUNDRED_HBARS; +import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.ACCOUNT_DELETED; +import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.DUPLICATE_TRANSACTION; +import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.INSUFFICIENT_PAYER_BALANCE; +import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.INVALID_CONTRACT_ID; +import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.OBTAINER_DOES_NOT_EXIST; + +import com.hedera.services.bdd.junit.HapiTest; +import com.hedera.services.bdd.junit.LeakyHapiTest; +import com.hedera.services.bdd.spec.HapiPropertySource; +import com.hedera.services.bdd.spec.HapiSpec; +import com.hedera.services.bdd.spec.keys.KeyFactory; +import com.hedera.services.bdd.spec.keys.KeyShape; +import com.hedera.services.bdd.spec.keys.SigControl; +import com.hedera.services.bdd.spec.queries.QueryVerbs; +import com.hedera.services.bdd.spec.queries.meta.HapiGetTxnRecord; +import com.hedera.services.bdd.spec.utilops.UtilVerbs; +import com.hederahashgraph.api.proto.java.TransactionRecord; +import java.util.concurrent.atomic.AtomicReference; +import java.util.stream.Stream; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.DynamicTest; +import org.junit.jupiter.api.Tag; + +@Tag(TOKEN) +public class IssueRegressionTests { + private static final String TRANSFER = "transfer"; + private static final String PAYER = "payer"; + private static final String SNAPSHOT = "snapshot"; + private static final String DELETE_TXN = "deleteTxn"; + private static final String RECEIVER = "receiver"; + + @HapiTest + final Stream allowsCryptoCreatePayerToHaveLessThanTwiceFee() { + return hapiTest( + cryptoCreate(CIVILIAN_PAYER).balance(ONE_HUNDRED_HBARS), + cryptoCreate("payer") + .payingWith(CIVILIAN_PAYER) + .via("referenceTxn") + .balance(0L), + withOpContext((spec, ctxLog) -> { + HapiGetTxnRecord subOp = getTxnRecord("referenceTxn"); + allRunFor(spec, subOp); + TransactionRecord record = subOp.getResponseRecord(); + long fee = record.getTransactionFee(); + spec.registry().saveAmount("balance", fee * 2 - 1); + }), + cryptoTransfer( + tinyBarsFromTo(GENESIS, "payer", spec -> spec.registry().getAmount("balance"))), + cryptoCreate("irrelevant").balance(0L).payingWith("payer")); + } + + @LeakyHapiTest(NO_CONCURRENT_CREATIONS) + final Stream createDeleteInSameRoundWorks() { + final var key = "tbdKey"; + AtomicReference nextFileId = new AtomicReference<>(); + return defaultHapiSpec("CreateDeleteInSameRoundWorks") + .given( + newKeyNamed(key).type(KeyFactory.KeyType.LIST), + fileCreate("marker").via("markerTxn")) + .when(withOpContext((spec, opLog) -> { + var lookup = getTxnRecord("markerTxn"); + allRunFor(spec, lookup); + var markerFid = lookup.getResponseRecord().getReceipt().getFileID(); + var nextFid = markerFid.toBuilder() + .setFileNum(markerFid.getFileNum() + 1) + .build(); + nextFileId.set(HapiPropertySource.asFileString(nextFid)); + opLog.info("Next file will be {}", nextFileId.get()); + })) + .then( + fileCreate("tbd").key(key).deferStatusResolution(), + fileDelete(nextFileId::get).signedBy(GENESIS, key), + getFileInfo(nextFileId::get).hasDeleted(true)); + } + + @HapiTest + final Stream recordStorageFeeIncreasesWithNumTransfers() { + return defaultHapiSpec("RecordStorageFeeIncreasesWithNumTransfers") + .given( + cryptoCreate("civilian").balance(10 * ONE_HUNDRED_HBARS), + cryptoCreate("A"), + cryptoCreate("B"), + cryptoCreate("C"), + cryptoCreate("D"), + cryptoTransfer(tinyBarsFromTo("A", "B", 1L)) + .payingWith("civilian") + .via("txn1"), + cryptoTransfer(tinyBarsFromTo("A", "B", 1L), tinyBarsFromTo("C", "D", 1L)) + .payingWith("civilian") + .via("txn2")) + .when(UtilVerbs.recordFeeAmount("txn1", "feeForOne"), UtilVerbs.recordFeeAmount("txn2", "feeForTwo")) + .then(UtilVerbs.assertionsHold((spec, assertLog) -> { + long feeForOne = spec.registry().getAmount("feeForOne"); + long feeForTwo = spec.registry().getAmount("feeForTwo"); + assertLog.info("[Record storage] fee for one transfer : {}", feeForOne); + assertLog.info("[Record storage] fee for two transfers: {}", feeForTwo); + Assertions.assertEquals(-1, Long.compare(feeForOne, feeForTwo)); + })); + } + + @HapiTest + final Stream queryPaymentTxnMustHavePayerBalanceForBothTransferFeeAndNodePayment() { + final long BALANCE = 1_000_000L; + + return HapiSpec.defaultHapiSpec("QueryPaymentTxnMustHavePayerBalanceForBothTransferFeeAndNodePayment") + .given(cryptoCreate("payer").balance(BALANCE)) + .when() + .then(getAccountInfo("payer") + .nodePayment(BALANCE) + .payingWith("payer") + .hasAnswerOnlyPrecheck(INSUFFICIENT_PAYER_BALANCE)); + } + + @HapiTest + final Stream cryptoTransferListShowsOnlyFeesAfterIAB() { + final long PAYER_BALANCE = 1_000_000L; + + return defaultHapiSpec("CryptoTransferListShowsOnlyFeesAfterIAB") + .given(cryptoCreate("payer").balance(PAYER_BALANCE)) + .when() + .then(cryptoTransfer(tinyBarsFromTo("payer", GENESIS, PAYER_BALANCE)) + .payingWith("payer") + .via("txn") + .hasPrecheck(INSUFFICIENT_PAYER_BALANCE)); + } + + @HapiTest + final Stream duplicatedTxnsSameTypeDetected() { + long initialBalance = 10_000L; + + return defaultHapiSpec("duplicatedTxnsSameTypeDetected") + .given( + cryptoCreate("acct1").balance(initialBalance).logged().via("txnId1"), + UtilVerbs.sleepFor(2000), + cryptoCreate("acctWithDuplicateTxnId") + .balance(initialBalance) + .logged() + .txnId("txnId1") + .hasPrecheck(DUPLICATE_TRANSACTION)) + .when() + .then(getTxnRecord("txnId1").logged()); + } + + @HapiTest + final Stream duplicatedTxnsDifferentTypesDetected() { + return defaultHapiSpec("duplicatedTxnsDifferentTypesDetected") + .given( + cryptoCreate("acct2").via("txnId2"), + newKeyNamed("key1"), + createTopic("topic2").submitKeyName("key1")) + .when(submitMessageTo("topic2") + .message("Hello world") + .payingWith("acct2") + .txnId("txnId2") + .hasPrecheck(DUPLICATE_TRANSACTION)) + .then(getTxnRecord("txnId2").logged()); + } + + @HapiTest + final Stream duplicatedTxnsSameTypeDifferentNodesDetected() { + + return defaultHapiSpec("duplicatedTxnsSameTypeDifferentNodesDetected") + .given( + cryptoCreate("acct3").setNode("0.0.3").via("txnId1"), + UtilVerbs.sleepFor(2000), + cryptoCreate("acctWithDuplicateTxnId") + .setNode("0.0.5") + .txnId("txnId1") + .hasPrecheck(DUPLICATE_TRANSACTION)) + .when() + .then(getTxnRecord("txnId1").logged()); + } + + @HapiTest + final Stream duplicatedTxnsDifferentTypesDifferentNodesDetected() { + return defaultHapiSpec("duplicatedTxnsDifferentTypesDifferentNodesDetected") + .given( + cryptoCreate("acct4").via("txnId4").setNode("0.0.3"), + newKeyNamed("key2"), + createTopic("topic2").setNode("0.0.5").submitKeyName("key2")) + .when(submitMessageTo("topic2") + .message("Hello world") + .payingWith("acct4") + .txnId("txnId4") + .hasPrecheck(DUPLICATE_TRANSACTION)) + .then(getTxnRecord("txnId4").logged()); + } + + @HapiTest + final Stream keepsRecordOfPayerIBE() { + final var payer = "payer"; + return defaultHapiSpec("KeepsRecordOfPayerIBE") + .given( + cryptoCreate(CIVILIAN_PAYER), + cryptoTransfer(tinyBarsFromTo(GENESIS, FUNDING, 1L)) + .payingWith(CIVILIAN_PAYER) + .via("referenceTxn"), + UtilVerbs.withOpContext((spec, ctxLog) -> { + HapiGetTxnRecord subOp = getTxnRecord("referenceTxn"); + allRunFor(spec, subOp); + TransactionRecord record = subOp.getResponseRecord(); + long fee = record.getTransactionFee(); + spec.registry().saveAmount("fee", fee); + spec.registry().saveAmount("balance", fee * 2); + })) + .when(cryptoCreate(payer).balance(spec -> spec.registry().getAmount("balance"))) + .then( + UtilVerbs.inParallel( + cryptoTransfer(tinyBarsFromTo(payer, FUNDING, spec -> spec.registry() + .getAmount("fee"))) + .payingWith(payer) + .via("txnA") + .hasAnyKnownStatus(), + cryptoTransfer(tinyBarsFromTo(payer, FUNDING, spec -> spec.registry() + .getAmount("fee"))) + .payingWith(payer) + .via("txnB") + .hasAnyKnownStatus()), + getTxnRecord("txnA").logged(), + getTxnRecord("txnB").logged()); + } + + @HapiTest + final Stream tbdCanPayForItsOwnDeletion() { + return defaultHapiSpec("TbdCanPayForItsOwnDeletion") + .given(cryptoCreate("tbd"), cryptoCreate(TRANSFER)) + .when() + .then( + cryptoDelete("tbd") + .via("selfFinanced") + .payingWith("tbd") + .transfer(TRANSFER), + getTxnRecord("selfFinanced").logged()); + } + + @HapiTest + final Stream transferAccountCannotBeDeleted() { + return defaultHapiSpec("TransferAccountCannotBeDeleted") + .given(cryptoCreate(PAYER), cryptoCreate(TRANSFER), cryptoCreate("tbd")) + .when(cryptoDelete(TRANSFER)) + .then( + balanceSnapshot(SNAPSHOT, PAYER), + cryptoDelete("tbd") + .via(DELETE_TXN) + .payingWith(PAYER) + .transfer(TRANSFER) + .hasKnownStatus(ACCOUNT_DELETED), + getTxnRecord(DELETE_TXN).logged(), + getAccountBalance(PAYER).hasTinyBars(approxChangeFromSnapshot(SNAPSHOT, -9384399, 10000))); + } + + @HapiTest + final Stream transferAccountCannotBeDeletedForContractTarget() { + return defaultHapiSpec("TransferAccountCannotBeDeletedForContractTarget") + .given( + uploadInitCode("CreateTrivial"), + uploadInitCode("PayReceivable"), + cryptoCreate(TRANSFER), + contractCreate("CreateTrivial"), + contractCreate("PayReceivable")) + .when(cryptoDelete(TRANSFER), contractDelete("PayReceivable")) + .then( + balanceSnapshot(SNAPSHOT, GENESIS), + contractDelete("CreateTrivial") + .via(DELETE_TXN) + .transferAccount(TRANSFER) + .hasKnownStatus(OBTAINER_DOES_NOT_EXIST), + contractDelete("CreateTrivial") + .via(DELETE_TXN) + .transferContract("PayReceivable") + .hasKnownStatus(INVALID_CONTRACT_ID)); + } + + @HapiTest + final Stream multiKeyNonPayerEntityVerifiedAsync() { + KeyShape LARGE_THRESH_SHAPE = KeyShape.threshOf(1, 10); + SigControl firstOnly = LARGE_THRESH_SHAPE.signedWith(sigs(ON, OFF, OFF, OFF, OFF, OFF, OFF, OFF, OFF, OFF)); + + return defaultHapiSpec("MultiKeyNonPayerEntityVerifiedAsync") + .given( + newKeyNamed("payerKey").shape(LARGE_THRESH_SHAPE), + newKeyNamed("receiverKey").shape(LARGE_THRESH_SHAPE), + cryptoCreate(PAYER).keyShape(LARGE_THRESH_SHAPE), + cryptoCreate(RECEIVER).keyShape(LARGE_THRESH_SHAPE).receiverSigRequired(true)) + .when() + .then(cryptoTransfer(tinyBarsFromTo(PAYER, RECEIVER, 1L)) + .sigControl(forKey(PAYER, firstOnly), forKey(RECEIVER, firstOnly))); + } + + @HapiTest + final Stream discoversExpectedVersions() { + return defaultHapiSpec("discoversExpectedVersions") + .given() + .when() + .then(getVersionInfo().logged().hasNoDegenerateSemvers()); + } + + @HapiTest + final Stream idVariantsTreatedAsExpected() { + return defaultHapiSpec("idVariantsTreatedAsExpected") + .given() + .when() + .then(sendModified(withSuccessivelyVariedQueryIds(), QueryVerbs::getVersionInfo)); + } +} diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/leaky/FeatureFlagSuite.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/leaky/FeatureFlagSuite.java index 3ef1da8cf4ce..f68e6454b708 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/leaky/FeatureFlagSuite.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/leaky/FeatureFlagSuite.java @@ -17,7 +17,8 @@ package com.hedera.services.bdd.suites.leaky; import static com.hedera.node.app.service.evm.utils.EthSigsUtils.recoverAddressFromPubKey; -import static com.hedera.services.bdd.spec.HapiSpec.defaultHapiSpec; +import static com.hedera.services.bdd.junit.ContextRequirement.PROPERTY_OVERRIDES; +import static com.hedera.services.bdd.spec.HapiSpec.propertyPreservingHapiSpec; import static com.hedera.services.bdd.spec.infrastructure.providers.ops.crypto.RandomAccount.INITIAL_BALANCE; import static com.hedera.services.bdd.spec.queries.QueryVerbs.getAccountInfo; import static com.hedera.services.bdd.spec.queries.QueryVerbs.getTxnRecord; @@ -34,13 +35,16 @@ import static com.hedera.services.bdd.spec.transactions.token.TokenMovement.moving; import static com.hedera.services.bdd.spec.transactions.token.TokenMovement.movingUnique; import static com.hedera.services.bdd.spec.utilops.CustomSpecAssert.allRunFor; -import static com.hedera.services.bdd.spec.utilops.UtilVerbs.enableAllFeatureFlagsAndDisableContractThrottles; -import static com.hedera.services.bdd.spec.utilops.UtilVerbs.ifHapiTest; -import static com.hedera.services.bdd.spec.utilops.UtilVerbs.ifNotHapiTest; import static com.hedera.services.bdd.spec.utilops.UtilVerbs.inParallel; import static com.hedera.services.bdd.spec.utilops.UtilVerbs.newKeyNamed; import static com.hedera.services.bdd.spec.utilops.UtilVerbs.overridingAllOf; import static com.hedera.services.bdd.spec.utilops.UtilVerbs.withOpContext; +import static com.hedera.services.bdd.suites.HapiSuite.GENESIS; +import static com.hedera.services.bdd.suites.HapiSuite.ONE_HBAR; +import static com.hedera.services.bdd.suites.HapiSuite.ONE_HUNDRED_HBARS; +import static com.hedera.services.bdd.suites.HapiSuite.SECP_256K1_SHAPE; +import static com.hedera.services.bdd.suites.HapiSuite.SECP_256K1_SOURCE_KEY; +import static com.hedera.services.bdd.suites.HapiSuite.TOKEN_TREASURY; import static com.hedera.services.bdd.suites.crypto.AutoAccountCreationSuite.A_TOKEN; import static com.hedera.services.bdd.suites.crypto.AutoAccountCreationSuite.LAZY_CREATE_SPONSOR; import static com.hedera.services.bdd.suites.crypto.AutoAccountCreationSuite.NFT_CREATE; @@ -54,54 +58,37 @@ import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.NOT_SUPPORTED; import com.google.protobuf.ByteString; -import com.hedera.services.bdd.junit.HapiTest; -import com.hedera.services.bdd.junit.HapiTestSuite; -import com.hedera.services.bdd.spec.HapiSpec; +import com.hedera.services.bdd.junit.LeakyHapiTest; import com.hedera.services.bdd.spec.HapiSpecOperation; -import com.hedera.services.bdd.spec.utilops.FeatureFlags; import com.hedera.services.bdd.spec.utilops.UtilVerbs; -import com.hedera.services.bdd.suites.HapiSuite; import com.hederahashgraph.api.proto.java.TokenSupplyType; import com.hederahashgraph.api.proto.java.TokenType; import java.util.List; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; +import java.util.Map; +import java.util.stream.Stream; +import org.junit.jupiter.api.DynamicTest; -@HapiTestSuite -public class FeatureFlagSuite extends HapiSuite { - private static final Logger log = LogManager.getLogger(FeatureFlagSuite.class); - - public static void main(String... args) { - new FeatureFlagSuite().runSuiteSync(); - } - - @Override - public List getSpecsInSuite() { - return List.of( - disableAllFeatureFlagsAndConfirmNotSupported(), - enableAllFeatureFlagsAndDisableThrottlesForFurtherCiTesting()); - } - - @HapiTest - final HapiSpec disableAllFeatureFlagsAndConfirmNotSupported() { - return defaultHapiSpec("disableAllFeatureFlagsAndConfirmNotSupported") - .given(overridingAllOf(FeatureFlags.FEATURE_FLAGS.allDisabled())) +public class FeatureFlagSuite { + @LeakyHapiTest(PROPERTY_OVERRIDES) + final Stream disableAllFeatureFlagsAndConfirmNotSupported() { + return propertyPreservingHapiSpec("disableAllFeatureFlagsAndConfirmNotSupported") + .preserving( + "autoCreation.enabled", + "utilPrng.isEnabled", + "tokens.autoCreations.isEnabled", + "lazyCreation.enabled") + .given(overridingAllOf(Map.of( + "autoCreation.enabled", "false", + "utilPrng.isEnabled", "false", + "tokens.autoCreations.isEnabled", "false", + "lazyCreation.enabled", "false"))) .when() .then(inParallel( - confirmAutoCreationNotSupported(), - confirmUtilPrngNotSupported(), - confirmKeyAliasAutoCreationNotSupported(), - confirmHollowAccountCreationNotSupported())); - } - - @HapiTest - final HapiSpec enableAllFeatureFlagsAndDisableThrottlesForFurtherCiTesting() { - return defaultHapiSpec("enableAllFeatureFlagsAndDisableThrottlesForFurtherCiTesting") - .given() - .when() - .then( - ifNotHapiTest(enableAllFeatureFlagsAndDisableContractThrottles()), - ifHapiTest(enableAllFeatureFlagsAndDisableContractThrottles("scheduling.longTermEnabled"))); + confirmAutoCreationNotSupported(), + confirmUtilPrngNotSupported(), + confirmKeyAliasAutoCreationNotSupported(), + confirmHollowAccountCreationNotSupported()) + .failOnErrors()); } private HapiSpecOperation confirmAutoCreationNotSupported() { @@ -188,9 +175,4 @@ private HapiSpecOperation confirmKeyAliasAutoCreationNotSupported() { .hasKnownStatus(NOT_SUPPORTED), getTxnRecord(TRANSFER_TXN).andAllChildRecords().hasNonStakingChildRecordCount(0)); } - - @Override - protected Logger getResultsLogger() { - return log; - } } diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/leaky/LeakyContractTestsSuite.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/leaky/LeakyContractTestsSuite.java index b3e6c173f2c6..cde2e716ef84 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/leaky/LeakyContractTestsSuite.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/leaky/LeakyContractTestsSuite.java @@ -174,7 +174,6 @@ import static com.hedera.services.bdd.suites.contract.precompile.V1SecurityModelOverrides.CONTRACTS_MAX_NUM_WITH_HAPI_SIGS_ACCESS; import static com.hedera.services.bdd.suites.contract.traceability.EncodingUtils.formattedAssertionValue; import static com.hedera.services.bdd.suites.crypto.AutoAccountCreationSuite.LAZY_MEMO; -import static com.hedera.services.bdd.suites.crypto.AutoAccountCreationSuite.TRUE; import static com.hedera.services.bdd.suites.crypto.AutoCreateUtils.updateSpecFor; import static com.hedera.services.bdd.suites.crypto.CryptoApproveAllowanceSuite.ADMIN_KEY; import static com.hedera.services.bdd.suites.ethereum.EthereumSuite.GAS_LIMIT; @@ -217,9 +216,8 @@ import com.hedera.node.app.hapi.utils.ethereum.EthTxData; import com.hedera.node.app.hapi.utils.fee.FeeBuilder; import com.hedera.services.bdd.junit.HapiTest; -import com.hedera.services.bdd.junit.HapiTestSuite; +import com.hedera.services.bdd.junit.OrderedInIsolation; import com.hedera.services.bdd.spec.HapiPropertySource; -import com.hedera.services.bdd.spec.HapiSpec; import com.hedera.services.bdd.spec.HapiSpecOperation; import com.hedera.services.bdd.spec.assertions.ContractFnResultAsserts; import com.hedera.services.bdd.spec.assertions.ContractInfoAsserts; @@ -259,20 +257,17 @@ import java.util.concurrent.atomic.AtomicReference; import java.util.function.BiConsumer; import java.util.stream.IntStream; +import java.util.stream.Stream; import org.apache.commons.lang3.ArrayUtils; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.apache.tuweni.bytes.Bytes; import org.junit.jupiter.api.Assertions; -import org.junit.jupiter.api.MethodOrderer; +import org.junit.jupiter.api.DynamicTest; import org.junit.jupiter.api.Order; -import org.junit.jupiter.api.TestMethodOrder; -@HapiTestSuite(fuzzyMatch = true) -@TestMethodOrder( - MethodOrderer.OrderAnnotation - .class) // define same running order for mod specs as in getSpecsInSuite() definition used in mono @SuppressWarnings("java:S1192") // "string literal should not be duplicated" - this rule makes test suites worse +@OrderedInIsolation public class LeakyContractTestsSuite extends SidecarAwareHapiSuite { public static final String CONTRACTS_MAX_REFUND_PERCENT_OF_GAS_LIMIT1 = "contracts.maxRefundPercentOfGasLimit"; public static final String CREATE_TX = "createTX"; @@ -339,7 +334,7 @@ public static void main(String... args) { } @Override - public List getSpecsInSuite() { + public List> getSpecsInSuite() { return List.of( transferToCaller(), resultSizeAffectsFees(), @@ -391,7 +386,7 @@ public List getSpecsInSuite() { @SuppressWarnings("java:S5960") @HapiTest @Order(37) - final HapiSpec canMergeCreate2ChildWithHollowAccountAndSelfDestructInConstructor() { + final Stream canMergeCreate2ChildWithHollowAccountAndSelfDestructInConstructor() { final var tcValue = 1_234L; final var contract = "Create2SelfDestructContract"; final var creation = CREATION; @@ -412,9 +407,9 @@ final HapiSpec canMergeCreate2ChildWithHollowAccountAndSelfDestructInConstructor NONDETERMINISTIC_CONTRACT_CALL_RESULTS, NONDETERMINISTIC_TRANSACTION_FEES, NONDETERMINISTIC_NONCE) - .preserving(LAZY_CREATION_ENABLED) + .preserving(EVM_VERSION_PROPERTY) .given( - overriding(LAZY_CREATION_ENABLED, TRUE), + overriding(EVM_VERSION_PROPERTY, "v0.46"), newKeyNamed(adminKey), newKeyNamed(MULTI_KEY), uploadInitCode(contract), @@ -494,7 +489,7 @@ contract, GET_BYTECODE, asHeadlongAddress(factoryEvmAddress.get()), salt) @HapiTest @Order(27) - final HapiSpec transferErc20TokenFromErc721TokenFails() { + final Stream transferErc20TokenFromErc721TokenFails() { return propertyPreservingHapiSpec( "transferErc20TokenFromErc721TokenFails", NONDETERMINISTIC_FUNCTION_PARAMETERS, @@ -540,7 +535,7 @@ final HapiSpec transferErc20TokenFromErc721TokenFails() { @HapiTest @Order(26) - final HapiSpec transferErc20TokenFromContractWithApproval() { + final Stream transferErc20TokenFromContractWithApproval() { final var transferFromOtherContractWithSignaturesTxn = "transferFromOtherContractWithSignaturesTxn"; final var nestedContract = "NestedERC20Contract"; @@ -673,7 +668,7 @@ final HapiSpec transferErc20TokenFromContractWithApproval() { @HapiTest @Order(25) - final HapiSpec transferDontWorkWithoutTopLevelSignatures() { + final Stream transferDontWorkWithoutTopLevelSignatures() { final var transferTokenTxn = "transferTokenTxn"; final var transferTokensTxn = "transferTokensTxn"; final var transferNFTTxn = "transferNFTTxn"; @@ -818,7 +813,7 @@ final HapiSpec transferDontWorkWithoutTopLevelSignatures() { } // Requires legacy security model, cannot be enabled as @HapiTest without refactoring to use contract keys - final HapiSpec transferWorksWithTopLevelSignatures() { + final Stream transferWorksWithTopLevelSignatures() { final var transferTokenTxn = "transferTokenTxn"; final var transferTokensTxn = "transferTokensTxn"; final var transferNFTTxn = "transferNFTTxn"; @@ -985,7 +980,7 @@ final HapiSpec transferWorksWithTopLevelSignatures() { @HapiTest @Order(24) - final HapiSpec transferFailsWithIncorrectAmounts() { + final Stream transferFailsWithIncorrectAmounts() { final var transferTokenWithNegativeAmountTxn = "transferTokenWithNegativeAmountTxn"; final var contract = TOKEN_TRANSFER_CONTRACT; @@ -1043,7 +1038,7 @@ final HapiSpec transferFailsWithIncorrectAmounts() { @HapiTest @Order(35) - final HapiSpec getErc20TokenNameExceedingLimits() { + final Stream getErc20TokenNameExceedingLimits() { final var REDUCED_NETWORK_FEE = 1L; final var REDUCED_NODE_FEE = 1L; final var REDUCED_SERVICE_FEE = 1L; @@ -1106,7 +1101,7 @@ final HapiSpec getErc20TokenNameExceedingLimits() { @HapiTest @Order(2) - HapiSpec payerCannotOverSendValue() { + final Stream payerCannotOverSendValue() { final var payerBalance = 666 * ONE_HBAR; final var overdraftAmount = payerBalance + ONE_HBAR; final var overAmbitiousPayer = "overAmbitiousPayer"; @@ -1138,7 +1133,7 @@ HapiSpec payerCannotOverSendValue() { @HapiTest @Order(9) - final HapiSpec createTokenWithInvalidFeeCollector() { + final Stream createTokenWithInvalidFeeCollector() { // Fully non-deterministic for fuzzy matching because the test uses an absolute account number (i.e. 15252L) // but fuzzy matching compares relative account numbers return propertyPreservingHapiSpec("createTokenWithInvalidFeeCollector", FULLY_NONDETERMINISTIC) @@ -1188,7 +1183,7 @@ final HapiSpec createTokenWithInvalidFeeCollector() { } // Requires legacy security model, cannot be enabled as @HapiTest without refactoring to use contract keys - final HapiSpec createTokenWithInvalidFixedFeeWithERC721Denomination() { + final Stream createTokenWithInvalidFixedFeeWithERC721Denomination() { final String feeCollector = ACCOUNT_2; final String someARAccount = "someARAccount"; return propertyPreservingHapiSpec( @@ -1249,7 +1244,7 @@ final HapiSpec createTokenWithInvalidFixedFeeWithERC721Denomination() { } // Requires legacy security model, cannot be enabled as @HapiTest without refactoring to use contract keys - final HapiSpec createTokenWithInvalidRoyaltyFee() { + final Stream createTokenWithInvalidRoyaltyFee() { final String feeCollector = ACCOUNT_2; AtomicReference existingToken = new AtomicReference<>(); final String treasuryAndFeeCollectorKey = "treasuryAndFeeCollectorKey"; @@ -1314,7 +1309,7 @@ final HapiSpec createTokenWithInvalidRoyaltyFee() { } // Requires legacy security model, cannot be enabled as @HapiTest without refactoring to use contract keys - final HapiSpec nonFungibleTokenCreateWithFeesHappyPath() { + final Stream nonFungibleTokenCreateWithFeesHappyPath() { final var createTokenNum = new AtomicLong(); final var feeCollector = ACCOUNT_2; final var treasuryAndFeeCollectorKey = "treasuryAndFeeCollectorKey"; @@ -1406,7 +1401,7 @@ final HapiSpec nonFungibleTokenCreateWithFeesHappyPath() { } // Requires legacy security model, cannot be enabled as @HapiTest without refactoring to use contract keys - final HapiSpec fungibleTokenCreateWithFeesHappyPath() { + final Stream fungibleTokenCreateWithFeesHappyPath() { final var createdTokenNum = new AtomicLong(); final var feeCollector = "feeCollector"; final var arEd25519Key = "arEd25519Key"; @@ -1496,7 +1491,7 @@ final HapiSpec fungibleTokenCreateWithFeesHappyPath() { @HapiTest @Order(15) - final HapiSpec etx026AccountWithoutAliasCanMakeEthTxnsDueToAutomaticAliasCreation() { + final Stream etx026AccountWithoutAliasCanMakeEthTxnsDueToAutomaticAliasCreation() { final String ACCOUNT = "account"; return propertyPreservingHapiSpec( "etx026AccountWithoutAliasCanMakeEthTxnsDueToAutomaticAliasCreation", NONDETERMINISTIC_NONCE) @@ -1519,7 +1514,7 @@ final HapiSpec etx026AccountWithoutAliasCanMakeEthTxnsDueToAutomaticAliasCreatio @HapiTest @Order(0) - final HapiSpec transferToCaller() { + final Stream transferToCaller() { final var transferTxn = TRANSFER_TXN; final var sender = "sender"; return defaultHapiSpec("transferToCaller", NONDETERMINISTIC_TRANSACTION_FEES, NONDETERMINISTIC_NONCE) @@ -1562,7 +1557,7 @@ final HapiSpec transferToCaller() { @HapiTest @Order(14) - final HapiSpec maxRefundIsMaxGasRefundConfiguredWhenTXGasPriceIsSmaller() { + final Stream maxRefundIsMaxGasRefundConfiguredWhenTXGasPriceIsSmaller() { return defaultHapiSpec( "MaxRefundIsMaxGasRefundConfiguredWhenTXGasPriceIsSmaller", NONDETERMINISTIC_TRANSACTION_FEES, @@ -1592,7 +1587,7 @@ final HapiSpec maxRefundIsMaxGasRefundConfiguredWhenTXGasPriceIsSmaller() { @HapiTest @Order(13) @SuppressWarnings("java:S5960") - final HapiSpec contractCreationStoragePriceMatchesFinalExpiry() { + final Stream contractCreationStoragePriceMatchesFinalExpiry() { final var toyMaker = "ToyMaker"; final var createIndirectly = "CreateIndirectly"; final var normalPayer = "normalPayer"; @@ -1639,7 +1634,7 @@ final HapiSpec contractCreationStoragePriceMatchesFinalExpiry() { @HapiTest @Order(10) - final HapiSpec gasLimitOverMaxGasLimitFailsPrecheck() { + final Stream gasLimitOverMaxGasLimitFailsPrecheck() { return defaultHapiSpec( "GasLimitOverMaxGasLimitFailsPrecheck", NONDETERMINISTIC_TRANSACTION_FEES, @@ -1658,7 +1653,7 @@ final HapiSpec gasLimitOverMaxGasLimitFailsPrecheck() { @HapiTest @Order(12) - final HapiSpec createGasLimitOverMaxGasLimitFailsPrecheck() { + final Stream createGasLimitOverMaxGasLimitFailsPrecheck() { return defaultHapiSpec( "CreateGasLimitOverMaxGasLimitFailsPrecheck", NONDETERMINISTIC_TRANSACTION_FEES, @@ -1672,7 +1667,7 @@ final HapiSpec createGasLimitOverMaxGasLimitFailsPrecheck() { @HapiTest @Order(5) - final HapiSpec transferZeroHbarsToCaller() { + final Stream transferZeroHbarsToCaller() { final var transferTxn = TRANSFER_TXN; return defaultHapiSpec("transferZeroHbarsToCaller", NONDETERMINISTIC_TRANSACTION_FEES, NONDETERMINISTIC_NONCE) .given( @@ -1716,7 +1711,7 @@ final HapiSpec transferZeroHbarsToCaller() { @HapiTest @Order(1) - final HapiSpec resultSizeAffectsFees() { + final Stream resultSizeAffectsFees() { final var contract = "VerboseDeposit"; final var TRANSFER_AMOUNT = 1_000L; BiConsumer resultSizeFormatter = (rcd, txnLog) -> { @@ -1770,7 +1765,7 @@ final HapiSpec resultSizeAffectsFees() { @HapiTest @Order(8) - final HapiSpec autoAssociationSlotsAppearsInInfo() { + final Stream autoAssociationSlotsAppearsInInfo() { final int maxAutoAssociations = 100; final String CONTRACT = "Multipurpose"; @@ -1789,7 +1784,7 @@ final HapiSpec autoAssociationSlotsAppearsInInfo() { @HapiTest @Order(16) - final HapiSpec createMaxRefundIsMaxGasRefundConfiguredWhenTXGasPriceIsSmaller() { + final Stream createMaxRefundIsMaxGasRefundConfiguredWhenTXGasPriceIsSmaller() { return defaultHapiSpec( "CreateMaxRefundIsMaxGasRefundConfiguredWhenTXGasPriceIsSmaller", NONDETERMINISTIC_TRANSACTION_FEES, @@ -1814,7 +1809,7 @@ final HapiSpec createMaxRefundIsMaxGasRefundConfiguredWhenTXGasPriceIsSmaller() @HapiTest @Order(11) - final HapiSpec createMinChargeIsTXGasUsedByContractCreate() { + final Stream createMinChargeIsTXGasUsedByContractCreate() { return defaultHapiSpec( "CreateMinChargeIsTXGasUsedByContractCreate", NONDETERMINISTIC_TRANSACTION_FEES, @@ -1839,7 +1834,7 @@ final HapiSpec createMinChargeIsTXGasUsedByContractCreate() { @HapiTest @Order(3) - HapiSpec propagatesNestedCreations() { + final Stream propagatesNestedCreations() { final var call = "callTxn"; final var creation = "createTxn"; final var contract = "NestedCreations"; @@ -1922,7 +1917,7 @@ HapiSpec propagatesNestedCreations() { @HapiTest @Order(4) - HapiSpec temporarySStoreRefundTest() { + final Stream temporarySStoreRefundTest() { final var contract = "TemporarySStoreRefund"; return defaultHapiSpec("TemporarySStoreRefundTest", NONDETERMINISTIC_TRANSACTION_FEES, NONDETERMINISTIC_NONCE) .given( @@ -1962,7 +1957,7 @@ HapiSpec temporarySStoreRefundTest() { @HapiTest @Order(6) - final HapiSpec canCallPendingContractSafely() { + final Stream canCallPendingContractSafely() { final var numSlots = 64L; final var createBurstSize = 500; final long[] targets = {19, 24}; @@ -2007,7 +2002,7 @@ final HapiSpec canCallPendingContractSafely() { @HapiTest @Order(17) - final HapiSpec lazyCreateThroughPrecompileNotSupportedWhenFlagDisabled() { + final Stream lazyCreateThroughPrecompileNotSupportedWhenFlagDisabled() { final var CONTRACT = CRYPTO_TRANSFER; final var SENDER = "sender"; final var FUNGIBLE_TOKEN = "fungibleToken"; @@ -2077,7 +2072,7 @@ final HapiSpec lazyCreateThroughPrecompileNotSupportedWhenFlagDisabled() { @HapiTest @Order(18) - final HapiSpec evmLazyCreateViaSolidityCall() { + final Stream evmLazyCreateViaSolidityCall() { final var LAZY_CREATE_CONTRACT = "NestedLazyCreateContract"; final var ECDSA_KEY = "ECDSAKey"; final var callLazyCreateFunction = "nestedLazyCreateThenSendMore"; @@ -2200,7 +2195,7 @@ final HapiSpec evmLazyCreateViaSolidityCall() { } // Requires legacy security model, cannot be enabled as @HapiTest without refactoring to use contract keys - final HapiSpec requiresTopLevelSignatureOrApprovalDependingOnControllingProperty() { + final Stream requiresTopLevelSignatureOrApprovalDependingOnControllingProperty() { final var ignoredTopLevelSigTransfer = "ignoredTopLevelSigTransfer"; final var ignoredApprovalTransfer = "ignoredApprovalTransfer"; final var approvedTransfer = "approvedTransfer"; @@ -2328,7 +2323,7 @@ final HapiSpec requiresTopLevelSignatureOrApprovalDependingOnControllingProperty @HapiTest @Order(19) - final HapiSpec evmLazyCreateViaSolidityCallTooManyCreatesFails() { + final Stream evmLazyCreateViaSolidityCallTooManyCreatesFails() { final var LAZY_CREATE_CONTRACT = "NestedLazyCreateContract"; final var ECDSA_KEY = "ECDSAKey"; final var ECDSA_KEY2 = "ECDSAKey2"; @@ -2385,7 +2380,7 @@ final HapiSpec evmLazyCreateViaSolidityCallTooManyCreatesFails() { @HapiTest @Order(21) - final HapiSpec rejectsCreationAndUpdateOfAssociationsWhenFlagDisabled() { + final Stream rejectsCreationAndUpdateOfAssociationsWhenFlagDisabled() { return propertyPreservingHapiSpec( "rejectsCreationAndUpdateOfAssociationsWhenFlagDisabled", NONDETERMINISTIC_TRANSACTION_FEES, @@ -2406,7 +2401,7 @@ final HapiSpec rejectsCreationAndUpdateOfAssociationsWhenFlagDisabled() { @HapiTest @Order(20) - final HapiSpec erc20TransferFromDoesNotWorkIfFlagIsDisabled() { + final Stream erc20TransferFromDoesNotWorkIfFlagIsDisabled() { return defaultHapiSpec( "erc20TransferFromDoesNotWorkIfFlagIsDisabled", NONDETERMINISTIC_TRANSACTION_FEES, @@ -2456,7 +2451,7 @@ final HapiSpec erc20TransferFromDoesNotWorkIfFlagIsDisabled() { @HapiTest @Order(22) - final HapiSpec whitelistPositiveCase() { + final Stream whitelistPositiveCase() { final AtomicLong whitelistedCalleeMirrorNum = new AtomicLong(); final AtomicReference tokenID = new AtomicReference<>(); final AtomicReference attackerMirrorAddr = new AtomicReference<>(); @@ -2513,7 +2508,7 @@ final HapiSpec whitelistPositiveCase() { @HapiTest @Order(23) - final HapiSpec whitelistNegativeCases() { + final Stream whitelistNegativeCases() { final AtomicLong unlistedCalleeMirrorNum = new AtomicLong(); final AtomicLong whitelistedCalleeMirrorNum = new AtomicLong(); final AtomicReference tokenID = new AtomicReference<>(); @@ -2593,7 +2588,7 @@ final HapiSpec whitelistNegativeCases() { @HapiTest @Order(28) - final HapiSpec contractCreateNoncesExternalizationHappyPath() { + final Stream contractCreateNoncesExternalizationHappyPath() { final var contract = "NoncesExternalization"; final var contractCreateTxn = "contractCreateTxn"; @@ -2644,7 +2639,7 @@ final HapiSpec contractCreateNoncesExternalizationHappyPath() { @HapiTest @Order(29) - final HapiSpec contractCreateFollowedByContractCallNoncesExternalization() { + final Stream contractCreateFollowedByContractCallNoncesExternalization() { final var contract = "NoncesExternalization"; final var payer = "payer"; @@ -2750,7 +2745,7 @@ final HapiSpec contractCreateFollowedByContractCallNoncesExternalization() { @HapiTest @Order(30) - final HapiSpec shouldReturnNullWhenContractsNoncesExternalizationFlagIsDisabled() { + final Stream shouldReturnNullWhenContractsNoncesExternalizationFlagIsDisabled() { final var contract = "NoncesExternalization"; final var payer = "payer"; @@ -2779,7 +2774,7 @@ final HapiSpec shouldReturnNullWhenContractsNoncesExternalizationFlagIsDisabled( @HapiTest @Order(31) - HapiSpec someErc721GetApprovedScenariosPass() { + final Stream someErc721GetApprovedScenariosPass() { final AtomicReference tokenMirrorAddr = new AtomicReference<>(); final AtomicReference aCivilianMirrorAddr = new AtomicReference<>(); final AtomicReference zCivilianMirrorAddr = new AtomicReference<>(); @@ -2896,7 +2891,7 @@ HapiSpec someErc721GetApprovedScenariosPass() { @HapiTest @Order(33) - HapiSpec someErc721BalanceOfScenariosPass() { + final Stream someErc721BalanceOfScenariosPass() { final AtomicReference tokenMirrorAddr = new AtomicReference<>(); final AtomicReference aCivilianMirrorAddr = new AtomicReference<>(); final AtomicReference bCivilianMirrorAddr = new AtomicReference<>(); @@ -2984,7 +2979,7 @@ HapiSpec someErc721BalanceOfScenariosPass() { @HapiTest @Order(32) - HapiSpec someErc721OwnerOfScenariosPass() { + final Stream someErc721OwnerOfScenariosPass() { final AtomicReference tokenMirrorAddr = new AtomicReference<>(); final AtomicReference aCivilianMirrorAddr = new AtomicReference<>(); final AtomicReference zCivilianMirrorAddr = new AtomicReference<>(); @@ -3086,7 +3081,7 @@ HapiSpec someErc721OwnerOfScenariosPass() { @HapiTest @Order(34) - HapiSpec callToNonExistingContractFailsGracefully() { + final Stream callToNonExistingContractFailsGracefully() { return propertyPreservingHapiSpec( "callToNonExistingContractFailsGracefully", NONDETERMINISTIC_ETHEREUM_DATA, @@ -3121,7 +3116,7 @@ HapiSpec callToNonExistingContractFailsGracefully() { @Order(36) @HapiTest - HapiSpec relayerFeeAsExpectedIfSenderCoversGas() { + final Stream relayerFeeAsExpectedIfSenderCoversGas() { final var canonicalTxn = "canonical"; final long depositAmount = 20_000L; @@ -3164,7 +3159,7 @@ HapiSpec relayerFeeAsExpectedIfSenderCoversGas() { @HapiTest @Order(38) - HapiSpec invalidContract() { + final Stream invalidContract() { final var function = getABIFor(FUNCTION, "getIndirect", "CreateTrivial"); return propertyPreservingHapiSpec("InvalidContract") @@ -3184,7 +3179,7 @@ HapiSpec invalidContract() { @Order(39) @HapiTest - final HapiSpec htsTransferFromForNFTViaContractCreateLazyCreate() { + final Stream htsTransferFromForNFTViaContractCreateLazyCreate() { final var depositAmount = 1000; return defaultHapiSpec( diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/leaky/LeakyCryptoTestsSuite.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/leaky/LeakyCryptoTestsSuite.java index cbd4d7672455..557be638eb9e 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/leaky/LeakyCryptoTestsSuite.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/leaky/LeakyCryptoTestsSuite.java @@ -19,14 +19,13 @@ import static com.google.protobuf.ByteString.EMPTY; import static com.google.protobuf.ByteString.copyFromUtf8; import static com.hedera.node.app.service.evm.utils.EthSigsUtils.recoverAddressFromPubKey; -import static com.hedera.services.bdd.spec.HapiPropertySource.asAccountString; +import static com.hedera.services.bdd.junit.TestTags.CRYPTO; import static com.hedera.services.bdd.spec.HapiPropertySource.asContractString; import static com.hedera.services.bdd.spec.HapiPropertySource.asSolidityAddress; import static com.hedera.services.bdd.spec.HapiSpec.defaultHapiSpec; import static com.hedera.services.bdd.spec.HapiSpec.propertyPreservingHapiSpec; import static com.hedera.services.bdd.spec.assertions.AccountDetailsAsserts.accountDetailsWith; import static com.hedera.services.bdd.spec.assertions.AccountInfoAsserts.accountWith; -import static com.hedera.services.bdd.spec.assertions.AccountInfoAsserts.changeFromSnapshot; import static com.hedera.services.bdd.spec.assertions.TransactionRecordAsserts.recordWith; import static com.hedera.services.bdd.spec.keys.TrieSigMapGenerator.uniqueWithFullPrefixesFor; import static com.hedera.services.bdd.spec.queries.QueryVerbs.getAccountBalance; @@ -71,7 +70,6 @@ import static com.hedera.services.bdd.spec.transactions.token.TokenMovement.movingUnique; import static com.hedera.services.bdd.spec.utilops.CustomSpecAssert.allRunFor; import static com.hedera.services.bdd.spec.utilops.UtilVerbs.assertionsHold; -import static com.hedera.services.bdd.spec.utilops.UtilVerbs.balanceSnapshot; import static com.hedera.services.bdd.spec.utilops.UtilVerbs.blockingOrder; import static com.hedera.services.bdd.spec.utilops.UtilVerbs.childRecordsCheck; import static com.hedera.services.bdd.spec.utilops.UtilVerbs.emptyChildRecordsCheck; @@ -92,9 +90,31 @@ import static com.hedera.services.bdd.spec.utilops.records.SnapshotMatchMode.NONDETERMINISTIC_ETHEREUM_DATA; import static com.hedera.services.bdd.spec.utilops.records.SnapshotMatchMode.NONDETERMINISTIC_NONCE; import static com.hedera.services.bdd.spec.utilops.records.SnapshotMatchMode.NONDETERMINISTIC_TRANSACTION_FEES; +import static com.hedera.services.bdd.suites.HapiSuite.ADDRESS_BOOK_CONTROL; +import static com.hedera.services.bdd.suites.HapiSuite.APP_PROPERTIES; +import static com.hedera.services.bdd.suites.HapiSuite.CHAIN_ID_PROP; +import static com.hedera.services.bdd.suites.HapiSuite.CRYPTO_CREATE_WITH_ALIAS_ENABLED; +import static com.hedera.services.bdd.suites.HapiSuite.DEFAULT_PAYER; +import static com.hedera.services.bdd.suites.HapiSuite.EMPTY_KEY; +import static com.hedera.services.bdd.suites.HapiSuite.FALSE_VALUE; +import static com.hedera.services.bdd.suites.HapiSuite.FIVE_HBARS; +import static com.hedera.services.bdd.suites.HapiSuite.FUNDING; +import static com.hedera.services.bdd.suites.HapiSuite.GENESIS; +import static com.hedera.services.bdd.suites.HapiSuite.ONE_HBAR; +import static com.hedera.services.bdd.suites.HapiSuite.ONE_HUNDRED_HBARS; +import static com.hedera.services.bdd.suites.HapiSuite.ONE_MILLION_HBARS; +import static com.hedera.services.bdd.suites.HapiSuite.RELAYER; +import static com.hedera.services.bdd.suites.HapiSuite.SECP_256K1_SHAPE; +import static com.hedera.services.bdd.suites.HapiSuite.SECP_256K1_SOURCE_KEY; +import static com.hedera.services.bdd.suites.HapiSuite.THREE_MONTHS_IN_SECONDS; +import static com.hedera.services.bdd.suites.HapiSuite.TOKEN_TREASURY; +import static com.hedera.services.bdd.suites.HapiSuite.TRUE_VALUE; +import static com.hedera.services.bdd.suites.SidecarAwareHapiSuite.assertNoMismatchedSidecars; +import static com.hedera.services.bdd.suites.SidecarAwareHapiSuite.expectContractActionSidecarFor; +import static com.hedera.services.bdd.suites.SidecarAwareHapiSuite.initializeSidecarWatcher; +import static com.hedera.services.bdd.suites.SidecarAwareHapiSuite.tearDownSidecarWatcher; import static com.hedera.services.bdd.suites.contract.Utils.aaWith; import static com.hedera.services.bdd.suites.contract.Utils.getABIFor; -import static com.hedera.services.bdd.suites.contract.Utils.mirrorAddrWith; import static com.hedera.services.bdd.suites.contract.hapi.ContractCreateSuite.EMPTY_CONSTRUCTOR_CONTRACT; import static com.hedera.services.bdd.suites.crypto.AutoAccountCreationSuite.CRYPTO_TRANSFER_RECEIVER; import static com.hedera.services.bdd.suites.crypto.AutoAccountCreationSuite.FALSE; @@ -124,9 +144,7 @@ import static com.hedera.services.bdd.suites.crypto.CryptoCreateSuite.LAZY_CREATION_ENABLED; import static com.hedera.services.bdd.suites.file.FileUpdateSuite.CIVILIAN; import static com.hedera.services.bdd.suites.leaky.LeakyContractTestsSuite.RECEIVER; -import static com.hedera.services.bdd.suites.token.TokenPauseSpecs.DEFAULT_MIN_AUTO_RENEW_PERIOD; import static com.hedera.services.bdd.suites.token.TokenPauseSpecs.LEDGER_AUTO_RENEW_PERIOD_MIN_DURATION; -import static com.hedera.services.bdd.suites.token.TokenPauseSpecs.TokenIdOrderingAsserts.withOrderedTokenIds; import static com.hedera.services.bdd.suites.token.TokenTransactSpecs.SUPPLY_KEY; import static com.hedera.services.bdd.suites.token.TokenTransactSpecs.TRANSFER_TXN; import static com.hedera.services.bdd.suites.token.TokenTransactSpecs.UNIQUE; @@ -152,6 +170,7 @@ import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.OK; import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.REQUESTED_NUM_AUTOMATIC_ASSOCIATIONS_EXCEEDS_ASSOCIATION_LIMIT; import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.SUCCESS; +import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.TRANSACTION_REQUIRES_ZERO_TOKEN_BALANCES; import static com.hederahashgraph.api.proto.java.SubType.DEFAULT; import static com.hederahashgraph.api.proto.java.TokenType.FUNGIBLE_COMMON; import static com.hederahashgraph.api.proto.java.TokenType.NON_FUNGIBLE_UNIQUE; @@ -160,18 +179,15 @@ import com.google.protobuf.ByteString; import com.hedera.node.app.hapi.utils.ethereum.EthTxData; import com.hedera.services.bdd.junit.HapiTest; -import com.hedera.services.bdd.junit.HapiTestSuite; +import com.hedera.services.bdd.junit.OrderedInIsolation; import com.hedera.services.bdd.spec.HapiSpec; import com.hedera.services.bdd.spec.HapiSpecOperation; -import com.hedera.services.bdd.spec.HapiSpecSetup; import com.hedera.services.bdd.spec.assertions.ContractFnResultAsserts; import com.hedera.services.bdd.spec.assertions.ContractInfoAsserts; import com.hedera.services.bdd.spec.keys.KeyShape; import com.hedera.services.bdd.spec.queries.meta.HapiGetTxnRecord; import com.hedera.services.bdd.spec.transactions.TxnVerbs; import com.hedera.services.bdd.spec.transactions.crypto.HapiCryptoTransfer; -import com.hedera.services.bdd.suites.BddMethodIsNotATest; -import com.hedera.services.bdd.suites.SidecarAwareHapiSuite; import com.hedera.services.bdd.suites.contract.Utils; import com.hedera.services.stream.proto.CallOperationType; import com.hedera.services.stream.proto.ContractAction; @@ -184,7 +200,6 @@ import com.hederahashgraph.api.proto.java.TransferList; import java.math.BigInteger; import java.util.Arrays; -import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.OptionalLong; @@ -192,22 +207,19 @@ import java.util.concurrent.atomic.AtomicReference; import java.util.function.IntFunction; import java.util.stream.IntStream; +import java.util.stream.Stream; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.junit.jupiter.api.Assertions; -import org.junit.jupiter.api.MethodOrderer; +import org.junit.jupiter.api.DynamicTest; import org.junit.jupiter.api.Order; -import org.junit.jupiter.api.TestMethodOrder; +import org.junit.jupiter.api.Tag; -@HapiTestSuite(fuzzyMatch = true) -@TestMethodOrder( - MethodOrderer.OrderAnnotation - .class) // define same running order for mod specs as in getSpecsInSuite() definition used in mono -public class LeakyCryptoTestsSuite extends SidecarAwareHapiSuite { +@Tag(CRYPTO) +@OrderedInIsolation +public class LeakyCryptoTestsSuite { private static final Logger log = LogManager.getLogger(LeakyCryptoTestsSuite.class); - private static final String ASSOCIATIONS_LIMIT_PROPERTY = "entities.limitTokenAssociations"; - private static final String DEFAULT_ASSOCIATIONS_LIMIT = - HapiSpecSetup.getDefaultNodeProps().get(ASSOCIATIONS_LIMIT_PROPERTY); + private static final String FACTORY_MIRROR_CONTRACT = "FactoryMirror"; public static final String LAZY_CREATE_PROPERTY_NAME = "lazyCreation.enabled"; public static final String CONTRACTS_EVM_VERSION_PROP = "contracts.evm.version"; @@ -221,40 +233,9 @@ public class LeakyCryptoTestsSuite extends SidecarAwareHapiSuite { private static final String SENDER_TXN = "senderTxn"; private static final long GAS_PRICE = 71L; - public static void main(String... args) { - new LeakyCryptoTestsSuite().runSuiteSync(); - } - - @Override - public List getSpecsInSuite() { - return List.of( - maxAutoAssociationSpec(), - canDissociateFromMultipleExpiredTokens(), - cannotExceedAccountAllowanceLimit(), - cannotExceedAllowancesTransactionLimit(), - createAnAccountWithEVMAddressAliasAndECKey(), - createAnAccountWithEVMAddress(), - scheduledCryptoApproveAllowanceWaitForExpiryTrue(), - txnsUsingHip583FunctionalitiesAreNotAcceptedWhenFlagsAreDisabled(), - getsInsufficientPayerBalanceIfSendingAccountCanPayEverythingButServiceFee(), - hollowAccountCompletionNotAcceptedWhenFlagIsDisabled(), - hollowAccountCompletionWithEthereumTransaction(), - hollowAccountCreationChargesExpectedFees(), - lazyCreateViaEthereumCryptoTransfer(), - hollowAccountCompletionWithSimultaneousPropertiesUpdate(), - contractDeployAfterEthereumTransferLazyCreate(), - contractCallAfterEthereumTransferLazyCreate(), - autoAssociationPropertiesWorkAsExpected(), - autoAssociationWorksForContracts(), - // Interactions between HIP-18 and HIP-542 - customFeesHaveExpectedAutoCreateInteractions(), - callToExpiredContractResultsInSuccess(), - accountDeletionDoesNotReleaseAliasWithDisabledFF()); - } - @HapiTest @Order(16) - final HapiSpec autoAssociationPropertiesWorkAsExpected() { + final Stream autoAssociationPropertiesWorkAsExpected() { final var minAutoRenewPeriodPropertyName = "ledger.autoRenewPeriod.minDuration"; final var maxAssociationsPropertyName = "ledger.maxAutoAssociations"; final var shortLivedAutoAssocUser = "shortLivedAutoAssocUser"; @@ -291,7 +272,7 @@ final HapiSpec autoAssociationPropertiesWorkAsExpected() { @HapiTest @Order(8) - final HapiSpec getsInsufficientPayerBalanceIfSendingAccountCanPayEverythingButServiceFee() { + final Stream getsInsufficientPayerBalanceIfSendingAccountCanPayEverythingButServiceFee() { final var civilian = "civilian"; final var creation = "creation"; final var gasToOffer = 128_000L; @@ -351,9 +332,9 @@ final HapiSpec getsInsufficientPayerBalanceIfSendingAccountCanPayEverythingButSe .hasKnownStatusFrom(INSUFFICIENT_PAYER_BALANCE, INSUFFICIENT_ACCOUNT_BALANCE))); } - @BddMethodIsNotATest @Order(6) - final HapiSpec scheduledCryptoApproveAllowanceWaitForExpiryTrue() { + // (FUTURE) Enable when long-term scheduled transactions are supported + final Stream scheduledCryptoApproveAllowanceWaitForExpiryTrue() { return defaultHapiSpec("ScheduledCryptoApproveAllowanceWaitForExpiryTrue") .given( newKeyNamed(SUPPLY_KEY), @@ -433,7 +414,7 @@ final HapiSpec scheduledCryptoApproveAllowanceWaitForExpiryTrue() { // @HapiTest // will be enabled in next PR @Order(7) - final HapiSpec txnsUsingHip583FunctionalitiesAreNotAcceptedWhenFlagsAreDisabled() { + final Stream txnsUsingHip583FunctionalitiesAreNotAcceptedWhenFlagsAreDisabled() { return propertyPreservingHapiSpec("txnsUsingHip583FunctionalitiesAreNotAcceptedWhenFlagsAreDisabled") .preserving(LAZY_CREATION_ENABLED, CRYPTO_CREATE_WITH_ALIAS_ENABLED) .given( @@ -481,38 +462,12 @@ final HapiSpec txnsUsingHip583FunctionalitiesAreNotAcceptedWhenFlagsAreDisabled( .then(); } - @BddMethodIsNotATest - @Order(0) - final HapiSpec maxAutoAssociationSpec() { - final int MONOGAMOUS_NETWORK = 1; - final int maxAutoAssociations = 100; - final int ADVENTUROUS_NETWORK = 1_000; - final String user1 = "user1"; - - return defaultHapiSpec("MaxAutoAssociationSpec") - .given(overridingTwo( - ASSOCIATIONS_LIMIT_PROPERTY, TRUE_VALUE, "tokens.maxPerAccount", "" + MONOGAMOUS_NETWORK)) - .when() - .then( - cryptoCreate(user1) - .balance(ONE_HBAR) - .maxAutomaticTokenAssociations(maxAutoAssociations) - .hasPrecheck(REQUESTED_NUM_AUTOMATIC_ASSOCIATIONS_EXCEEDS_ASSOCIATION_LIMIT), - // Default is NOT to limit associations - overriding(ASSOCIATIONS_LIMIT_PROPERTY, DEFAULT_ASSOCIATIONS_LIMIT), - cryptoCreate(user1).balance(ONE_HBAR).maxAutomaticTokenAssociations(maxAutoAssociations), - getAccountInfo(user1).hasMaxAutomaticAssociations(maxAutoAssociations), - // Restore default - overriding("tokens.maxPerAccount", "" + ADVENTUROUS_NETWORK)); - } - - @BddMethodIsNotATest @Order(1) - public HapiSpec canDissociateFromMultipleExpiredTokens() { + @HapiTest + final Stream cannotDissociateFromExpiredTokenWithNonZeroBalance() { final var civilian = "civilian"; final long initialSupply = 100L; final long nonZeroXfer = 10L; - final var dissociateTxn = "dissociateTxn"; final var numTokens = 10; final IntFunction tokenNameFn = i -> "fungible" + i; final String[] assocOrder = new String[numTokens]; @@ -520,7 +475,8 @@ public HapiSpec canDissociateFromMultipleExpiredTokens() { final String[] dissocOrder = new String[numTokens]; Arrays.setAll(dissocOrder, i -> tokenNameFn.apply(numTokens - 1 - i)); - return defaultHapiSpec("CanDissociateFromMultipleExpiredTokens") + return propertyPreservingHapiSpec("CanDissociateFromMultipleExpiredTokens") + .preserving(LEDGER_AUTO_RENEW_PERIOD_MIN_DURATION) .given( overriding(LEDGER_AUTO_RENEW_PERIOD_MIN_DURATION, "1"), cryptoCreate(TOKEN_TREASURY), @@ -537,16 +493,13 @@ public HapiSpec canDissociateFromMultipleExpiredTokens() { .mapToObj(i -> cryptoTransfer(moving(nonZeroXfer, tokenNameFn.apply(i)) .between(TOKEN_TREASURY, civilian))) .toArray(HapiSpecOperation[]::new))) - .when(sleepFor(1_000L), tokenDissociate(civilian, dissocOrder).via(dissociateTxn)) - .then( - getTxnRecord(dissociateTxn) - .hasPriority(recordWith().tokenTransfers(withOrderedTokenIds(assocOrder))), - overriding(LEDGER_AUTO_RENEW_PERIOD_MIN_DURATION, DEFAULT_MIN_AUTO_RENEW_PERIOD)); + .when(sleepFor(2_000L)) + .then(tokenDissociate(civilian, dissocOrder).hasKnownStatus(TRANSACTION_REQUIRES_ZERO_TOKEN_BALANCES)); } @HapiTest @Order(2) - final HapiSpec cannotExceedAccountAllowanceLimit() { + final Stream cannotExceedAccountAllowanceLimit() { return defaultHapiSpec("CannotExceedAccountAllowanceLimit", NONDETERMINISTIC_TRANSACTION_FEES) .given( overridingTwo( @@ -615,7 +568,7 @@ final HapiSpec cannotExceedAccountAllowanceLimit() { @HapiTest @Order(4) - final HapiSpec createAnAccountWithEVMAddressAliasAndECKey() { + final Stream createAnAccountWithEVMAddressAliasAndECKey() { return propertyPreservingHapiSpec("CreateAnAccountWithEVMAddressAliasAndECKey") .preserving(LAZY_CREATION_ENABLED, CRYPTO_CREATE_WITH_ALIAS_ENABLED) .given( @@ -675,7 +628,7 @@ final HapiSpec createAnAccountWithEVMAddressAliasAndECKey() { @HapiTest @Order(5) - final HapiSpec createAnAccountWithEVMAddress() { + final Stream createAnAccountWithEVMAddress() { return propertyPreservingHapiSpec("CreateAnAccountWithEVMAddress") .preserving(LAZY_CREATION_ENABLED, CRYPTO_CREATE_WITH_ALIAS_ENABLED) .given( @@ -698,7 +651,7 @@ final HapiSpec createAnAccountWithEVMAddress() { @HapiTest @Order(3) - final HapiSpec cannotExceedAllowancesTransactionLimit() { + final Stream cannotExceedAllowancesTransactionLimit() { return defaultHapiSpec("CannotExceedAllowancesTransactionLimit", NONDETERMINISTIC_TRANSACTION_FEES) .given( newKeyNamed(SUPPLY_KEY), @@ -780,7 +733,7 @@ final HapiSpec cannotExceedAllowancesTransactionLimit() { @HapiTest @Order(9) - final HapiSpec hollowAccountCompletionNotAcceptedWhenFlagIsDisabled() { + final Stream hollowAccountCompletionNotAcceptedWhenFlagIsDisabled() { return propertyPreservingHapiSpec("HollowAccountCompletionNotAcceptedWhenFlagIsDisabled") .preserving(LAZY_CREATION_ENABLED) .given( @@ -829,7 +782,7 @@ final HapiSpec hollowAccountCompletionNotAcceptedWhenFlagIsDisabled() { @HapiTest @Order(11) - final HapiSpec hollowAccountCreationChargesExpectedFees() { + final Stream hollowAccountCreationChargesExpectedFees() { final long REDUCED_NODE_FEE = 2L; final long REDUCED_NETWORK_FEE = 3L; final long REDUCED_SERVICE_FEE = 3L; @@ -911,10 +864,9 @@ final HapiSpec hollowAccountCreationChargesExpectedFees() { .then(uploadDefaultFeeSchedules(GENESIS)); } - // @HapiTest /// will be enabled after EthereumTransaction hollow account finalization is implemented + @HapiTest @Order(10) - final HapiSpec hollowAccountCompletionWithEthereumTransaction() { - final Map startingProps = new HashMap<>(); + final Stream hollowAccountCompletionWithEthereumTransaction() { final String CONTRACT = "Fuse"; return propertyPreservingHapiSpec( "HollowAccountCompletionWithEthereumTransaction", NONDETERMINISTIC_ETHEREUM_DATA) @@ -967,7 +919,7 @@ final HapiSpec hollowAccountCompletionWithEthereumTransaction() { @HapiTest @Order(14) - final HapiSpec contractDeployAfterEthereumTransferLazyCreate() { + final Stream contractDeployAfterEthereumTransferLazyCreate() { final var RECIPIENT_KEY = LAZY_ACCOUNT_RECIPIENT; final var lazyCreateTxn = PAY_TXN; return propertyPreservingHapiSpec( @@ -1021,7 +973,7 @@ final HapiSpec contractDeployAfterEthereumTransferLazyCreate() { @HapiTest @Order(15) - final HapiSpec contractCallAfterEthereumTransferLazyCreate() { + final Stream contractCallAfterEthereumTransferLazyCreate() { final var RECIPIENT_KEY = LAZY_ACCOUNT_RECIPIENT; final var lazyCreateTxn = PAY_TXN; return propertyPreservingHapiSpec( @@ -1077,7 +1029,7 @@ final HapiSpec contractCallAfterEthereumTransferLazyCreate() { @HapiTest @Order(12) - final HapiSpec lazyCreateViaEthereumCryptoTransfer() { + final Stream lazyCreateViaEthereumCryptoTransfer() { final var RECIPIENT_KEY = LAZY_ACCOUNT_RECIPIENT; final var lazyCreateTxn = PAY_TXN; final var failedLazyCreateTxn = "failedLazyCreateTxn"; @@ -1233,7 +1185,7 @@ final HapiSpec lazyCreateViaEthereumCryptoTransfer() { @HapiTest @Order(13) - final HapiSpec hollowAccountCompletionWithSimultaneousPropertiesUpdate() { + final Stream hollowAccountCompletionWithSimultaneousPropertiesUpdate() { return propertyPreservingHapiSpec( "hollowAccountCompletionWithSimultaniousPropertiesUpdate", NONDETERMINISTIC_TRANSACTION_FEES) .preserving(LAZY_CREATION_ENABLED) @@ -1282,7 +1234,7 @@ final HapiSpec hollowAccountCompletionWithSimultaneousPropertiesUpdate() { @HapiTest @Order(17) - public HapiSpec autoAssociationWorksForContracts() { + final Stream autoAssociationWorksForContracts() { final var theContract = "CreateDonor"; final String tokenA = "tokenA"; final String tokenB = "tokenB"; @@ -1350,7 +1302,7 @@ public HapiSpec autoAssociationWorksForContracts() { @HapiTest @Order(18) - final HapiSpec customFeesHaveExpectedAutoCreateInteractions() { + final Stream customFeesHaveExpectedAutoCreateInteractions() { final var nftWithRoyaltyNoFallback = "nftWithRoyaltyNoFallback"; final var nftWithRoyaltyPlusHtsFallback = "nftWithRoyaltyPlusFallback"; final var nftWithRoyaltyPlusHbarFallback = "nftWithRoyaltyPlusHbarFallback"; @@ -1419,38 +1371,8 @@ final HapiSpec customFeesHaveExpectedAutoCreateInteractions() { .via(finalTxn)); } - private HapiSpec callToExpiredContractResultsInSuccess() { - AtomicReference receiverId = new AtomicReference<>(); - final var minAutoRenewPeriodPropertyName = "ledger.autoRenewPeriod.minDuration"; - final String RECEIVER = "receiver"; - final String INTERNAL_CALLER_CONTRACT = "InternalCaller"; - final String INNER_TXN = "innerTx"; - - return propertyPreservingHapiSpec("callToExpiredContractResultsInSuccess") - .preserving(minAutoRenewPeriodPropertyName) - .given( - overriding(minAutoRenewPeriodPropertyName, "1"), - cryptoCreate(RECEIVER).exposingCreatedIdTo(receiverId::set), - uploadInitCode(INTERNAL_CALLER_CONTRACT), - contractCreate(INTERNAL_CALLER_CONTRACT).autoRenewSecs(1L)) - .when( - sleepFor(2_000L), - withOpContext((spec, op) -> allRunFor( - spec, - balanceSnapshot("initialBalance", asAccountString(receiverId.get())), - contractCall( - INTERNAL_CALLER_CONTRACT, - "callWithValueTo", - mirrorAddrWith(receiverId.get().getAccountNum())) - .gas(100_000L) - .via(INNER_TXN)))) - .then( - getTxnRecord(INNER_TXN).hasPriority(recordWith().status(SUCCESS)), - getAccountBalance(RECEIVER).hasTinyBars(changeFromSnapshot("initialBalance", 0))); - } - @HapiTest - final HapiSpec accountDeletionDoesNotReleaseAliasWithDisabledFF() { + final Stream accountDeletionDoesNotReleaseAliasWithDisabledFF() { final AtomicReference partyId = new AtomicReference<>(); final AtomicReference partyAlias = new AtomicReference<>(); @@ -1576,9 +1498,4 @@ private HapiSpecOperation autoCreateWithNonFungible(final String token, final Re .hasKnownStatus(expectedStatus), getTxnRecord(txn).assertingKnownEffectivePayers()); } - - @Override - protected Logger getResultsLogger() { - return log; - } } diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/leaky/LeakyEthereumTestsSuite.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/leaky/LeakyEthereumTestsSuite.java index 678e34d64c6e..3a256cb12589 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/leaky/LeakyEthereumTestsSuite.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/leaky/LeakyEthereumTestsSuite.java @@ -17,6 +17,7 @@ package com.hedera.services.bdd.suites.leaky; import static com.hedera.services.bdd.junit.TestTags.SMART_CONTRACT; +import static com.hedera.services.bdd.spec.HapiPropertySource.asContractIdWithEvmAddress; import static com.hedera.services.bdd.spec.HapiSpec.propertyPreservingHapiSpec; import static com.hedera.services.bdd.spec.assertions.TransactionRecordAsserts.recordWith; import static com.hedera.services.bdd.spec.keys.KeyFactory.KeyType.THRESHOLD; @@ -25,8 +26,13 @@ import static com.hedera.services.bdd.spec.transactions.TxnVerbs.cryptoCreate; import static com.hedera.services.bdd.spec.transactions.TxnVerbs.cryptoTransfer; import static com.hedera.services.bdd.spec.transactions.TxnVerbs.ethereumCall; +import static com.hedera.services.bdd.spec.transactions.TxnVerbs.ethereumCallWithFunctionAbi; +import static com.hedera.services.bdd.spec.transactions.TxnVerbs.tokenAssociate; +import static com.hedera.services.bdd.spec.transactions.TxnVerbs.tokenCreate; import static com.hedera.services.bdd.spec.transactions.TxnVerbs.uploadInitCode; +import static com.hedera.services.bdd.spec.transactions.contract.HapiParserUtil.asHeadlongAddress; import static com.hedera.services.bdd.spec.transactions.crypto.HapiCryptoTransfer.tinyBarsFromAccountToAlias; +import static com.hedera.services.bdd.spec.transactions.token.TokenMovement.moving; import static com.hedera.services.bdd.spec.utilops.CustomSpecAssert.allRunFor; import static com.hedera.services.bdd.spec.utilops.UtilVerbs.newKeyNamed; import static com.hedera.services.bdd.spec.utilops.UtilVerbs.overriding; @@ -34,51 +40,52 @@ import static com.hedera.services.bdd.spec.utilops.UtilVerbs.withOpContext; import static com.hedera.services.bdd.spec.utilops.records.SnapshotMatchMode.NONDETERMINISTIC_CONTRACT_CALL_RESULTS; import static com.hedera.services.bdd.spec.utilops.records.SnapshotMatchMode.NONDETERMINISTIC_ETHEREUM_DATA; +import static com.hedera.services.bdd.suites.HapiSuite.CHAIN_ID_PROP; +import static com.hedera.services.bdd.suites.HapiSuite.GENESIS; +import static com.hedera.services.bdd.suites.HapiSuite.ONE_HUNDRED_HBARS; +import static com.hedera.services.bdd.suites.HapiSuite.ONE_MILLION_HBARS; +import static com.hedera.services.bdd.suites.HapiSuite.RELAYER; +import static com.hedera.services.bdd.suites.HapiSuite.SECP_256K1_SHAPE; +import static com.hedera.services.bdd.suites.HapiSuite.SECP_256K1_SOURCE_KEY; +import static com.hedera.services.bdd.suites.HapiSuite.TOKEN_TREASURY; +import static com.hedera.services.bdd.suites.contract.Utils.FunctionType.FUNCTION; +import static com.hedera.services.bdd.suites.contract.Utils.asAddress; +import static com.hedera.services.bdd.suites.contract.Utils.getABIFor; +import static com.hedera.services.bdd.suites.crypto.CryptoCreateSuite.ACCOUNT; +import static com.hedera.services.bdd.suites.leaky.LeakyContractTestsSuite.RECEIVER; +import static com.hedera.services.bdd.suites.token.TokenAssociationSpecs.VANILLA_TOKEN; +import static com.hedera.services.bdd.suites.token.TokenTransactSpecs.SUPPLY_KEY; import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.SUCCESS; +import static com.hederahashgraph.api.proto.java.TokenType.FUNGIBLE_COMMON; +import static com.swirlds.common.utility.CommonUtils.unhex; +import com.google.protobuf.ByteString; import com.hedera.node.app.hapi.utils.ethereum.EthTxData.EthTransactionType; -import com.hedera.services.bdd.junit.HapiTest; -import com.hedera.services.bdd.junit.HapiTestSuite; -import com.hedera.services.bdd.spec.HapiSpec; -import com.hedera.services.bdd.suites.HapiSuite; +import com.hedera.services.bdd.junit.LeakyHapiTest; +import com.hedera.services.bdd.spec.transactions.contract.HapiParserUtil; import com.hederahashgraph.api.proto.java.ResponseCodeEnum; import java.math.BigInteger; -import java.util.List; import java.util.stream.Stream; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; +import org.junit.jupiter.api.DynamicTest; import org.junit.jupiter.api.Tag; -@HapiTestSuite @Tag(SMART_CONTRACT) @SuppressWarnings("java:S5960") -public class LeakyEthereumTestsSuite extends HapiSuite { - +public class LeakyEthereumTestsSuite { private static final String PAY_RECEIVABLE_CONTRACT = "PayReceivable"; - private static final Logger log = LogManager.getLogger(LeakyEthereumTestsSuite.class); - - public static void main(String... args) { - new LeakyEthereumTestsSuite().runSuiteAsync(); - } - @Override - public boolean canRunConcurrent() { - return true; - } - - @Override - public List getSpecsInSuite() { - return Stream.of(legacyUnprotectedEtxBeforeEIP155(), legacyEtxAfterEIP155()) - .toList(); - } + private static final String EVM_VERSION_PROPERTY = "contracts.evm.version"; + private static final String ALLOW_CALLS_TO_NON_CONTRACT_ACCOUNTS = "contracts.evm.allowCallsToNonContractAccounts"; + private static final String DYNAMIC_EVM_PROPERTY = "contracts.evm.version.dynamic"; + private static final String EVM_VERSION_050 = "v0.50"; // test unprotected legacy ethereum transactions before EIP155 // this tests the behaviour when the `v` field is 27 or 28 // in this case the passed chainId = 0 so ETX is before EIP155 // and so `v` is calculated -> v = {0,1} + 27 // source: https://eips.ethereum.org/EIPS/eip-155 - @HapiTest - HapiSpec legacyUnprotectedEtxBeforeEIP155() { + @LeakyHapiTest + final Stream legacyUnprotectedEtxBeforeEIP155() { final String DEPOSIT = "deposit"; final long depositAmount = 20_000L; final Integer chainId = 0; @@ -125,8 +132,8 @@ HapiSpec legacyUnprotectedEtxBeforeEIP155() { // in this case the passed chainId = 1 so ETX is after EIP155 // and so `v` is calculated -> v = {0,1} + CHAIN_ID * 2 + 35 // source: https://eips.ethereum.org/EIPS/eip-155 - @HapiTest - HapiSpec legacyEtxAfterEIP155() { + @LeakyHapiTest + final Stream legacyEtxAfterEIP155() { final String DEPOSIT = "deposit"; final long depositAmount = 20_000L; final Integer chainId = 1; @@ -166,8 +173,61 @@ HapiSpec legacyEtxAfterEIP155() { resetToDefault(CHAIN_ID_PROP)); } - @Override - protected Logger getResultsLogger() { - return log; + @LeakyHapiTest + final Stream callHtsSystemContractTest() { + final var callHtsSystemContractTxn = "callHtsSystemContractTxn"; + final var function = getABIFor(FUNCTION, "transferToken", "IHederaTokenService"); + final var HTS_SYSTEM_CONTRACT = "hts"; + final var HTS_SYSTEM_CONTRACT_ADDRESS = "0000000000000000000000000000000000000167"; + + return propertyPreservingHapiSpec("callHtsSystemContractTest") + .preserving(EVM_VERSION_PROPERTY, DYNAMIC_EVM_PROPERTY) + .given( + overriding(DYNAMIC_EVM_PROPERTY, "true"), + overriding(EVM_VERSION_PROPERTY, EVM_VERSION_050), + overriding(ALLOW_CALLS_TO_NON_CONTRACT_ACCOUNTS, "true"), + newKeyNamed(SECP_256K1_SOURCE_KEY).shape(SECP_256K1_SHAPE), + newKeyNamed(SUPPLY_KEY), + cryptoTransfer(tinyBarsFromAccountToAlias(GENESIS, SECP_256K1_SOURCE_KEY, ONE_HUNDRED_HBARS)), + cryptoCreate(RELAYER).balance(6 * ONE_MILLION_HBARS), + cryptoCreate(ACCOUNT).balance(6 * ONE_MILLION_HBARS), + cryptoCreate(TOKEN_TREASURY), + cryptoCreate(RECEIVER), + tokenCreate(VANILLA_TOKEN) + .tokenType(FUNGIBLE_COMMON) + .treasury(TOKEN_TREASURY) + .supplyKey(SUPPLY_KEY) + .initialSupply(1_000), + tokenAssociate(ACCOUNT, VANILLA_TOKEN), + tokenAssociate(RECEIVER, VANILLA_TOKEN), + cryptoTransfer(moving(500, VANILLA_TOKEN).between(TOKEN_TREASURY, ACCOUNT))) + .when(withOpContext((spec, opLog) -> { + final var receiver1 = + asHeadlongAddress(asAddress(spec.registry().getAccountID(RECEIVER))); + final var sender = + asHeadlongAddress(asAddress(spec.registry().getAccountID(ACCOUNT))); + + spec.registry() + .saveContractId( + HTS_SYSTEM_CONTRACT, + asContractIdWithEvmAddress( + ByteString.copyFrom(unhex(HTS_SYSTEM_CONTRACT_ADDRESS)))); + allRunFor( + spec, + ethereumCallWithFunctionAbi( + false, + HTS_SYSTEM_CONTRACT, + function, + HapiParserUtil.asHeadlongAddress( + asAddress(spec.registry().getTokenID(VANILLA_TOKEN))), + sender, + receiver1, + 1L) + .payingWith(RELAYER) + .type(EthTransactionType.EIP1559) + .via(callHtsSystemContractTxn) + .hasKnownStatus(SUCCESS)); + })) + .then(); } } diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/leaky/LeakySecurityModelV1Suite.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/leaky/LeakySecurityModelV1Suite.java index 1c3e5856a038..6a64d7fe17e3 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/leaky/LeakySecurityModelV1Suite.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/leaky/LeakySecurityModelV1Suite.java @@ -16,7 +16,6 @@ package com.hedera.services.bdd.suites.leaky; -import com.hedera.services.bdd.spec.HapiSpec; import com.hedera.services.bdd.suites.HapiSuite; import com.hedera.services.bdd.suites.contract.hapi.ContractCallV1SecurityModelSuite; import com.hedera.services.bdd.suites.contract.hapi.ContractCreateV1SecurityModelSuite; @@ -45,8 +44,10 @@ import com.hedera.services.bdd.suites.token.TokenAssociationV1SecurityModelSpecs; import edu.umd.cs.findbugs.annotations.NonNull; import java.util.List; +import java.util.stream.Stream; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; +import org.junit.jupiter.api.DynamicTest; public class LeakySecurityModelV1Suite extends HapiSuite { @@ -89,7 +90,7 @@ public LeakySecurityModelV1Suite() { } @Override - public List getSpecsInSuite() { + public List> getSpecsInSuite() { return suites.stream() .map(HapiSuite::getSpecsInSuite) .flatMap(List::stream) diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/leaky/package-info.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/leaky/package-info.java index 561809ef031a..db16b25a3cde 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/leaky/package-info.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/leaky/package-info.java @@ -7,9 +7,7 @@ * * *

    An example of the first kind is a test like {@link - * com.hedera.services.bdd.suites.file.FileUpdateSuite#chainIdChangesDynamically(), {@link - * com.hedera.services.bdd.suites.leaky.LeakyEthereumTestsSuite#legacyUnprotectedEtxBeforeEIP155() and {@link - * com.hedera.services.bdd.suites.leaky.LeakyEthereumTestsSuite#legacyEtxAfterEIP155()}}} that changes the + * com.hedera.services.bdd.suites.file.FileUpdateSuite#chainIdChangesDynamically()} * network's EVM {@code chainid} to a non-standard value. Any concurrent {@code EthereumCall} that * uses the standard dev {@code 298} chainid will now fail. The updated {@code chainid} "leaked" * out! Any test that updates system files for properties, permissions, fees, or throttles can have diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/meta/VersionInfoSpec.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/meta/VersionInfoSpec.java index 871310e106ba..ad4e5791a627 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/meta/VersionInfoSpec.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/meta/VersionInfoSpec.java @@ -19,21 +19,15 @@ import static com.hedera.services.bdd.spec.HapiSpec.customHapiSpec; import static com.hedera.services.bdd.spec.HapiSpec.defaultHapiSpec; import static com.hedera.services.bdd.spec.queries.QueryVerbs.getVersionInfo; -import static com.hedera.services.bdd.spec.utilops.UtilVerbs.sendModified; -import static com.hedera.services.bdd.spec.utilops.mod.ModificationUtils.withSuccessivelyVariedQueryIds; -import com.hedera.services.bdd.junit.HapiTest; -import com.hedera.services.bdd.junit.HapiTestSuite; -import com.hedera.services.bdd.spec.HapiSpec; -import com.hedera.services.bdd.spec.queries.QueryVerbs; -import com.hedera.services.bdd.suites.BddTestNameDoesNotMatchMethodName; import com.hedera.services.bdd.suites.HapiSuite; import java.util.List; import java.util.Map; +import java.util.stream.Stream; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; +import org.junit.jupiter.api.DynamicTest; -@HapiTestSuite public class VersionInfoSpec extends HapiSuite { private static final Logger log = LogManager.getLogger(VersionInfoSpec.class); private final Map specConfig; @@ -56,13 +50,11 @@ public boolean canRunConcurrent() { } @Override - public List getSpecsInSuite() { + public List> getSpecsInSuite() { return List.of(discoversExpectedVersions()); } - @BddTestNameDoesNotMatchMethodName - @HapiTest - final HapiSpec discoversExpectedVersions() { + final Stream discoversExpectedVersions() { if (specConfig != null) { return customHapiSpec("getVersionInfo") .withProperties(specConfig) @@ -77,14 +69,6 @@ final HapiSpec discoversExpectedVersions() { } } - @HapiTest - public HapiSpec idVariantsTreatedAsExpected() { - return defaultHapiSpec("idVariantsTreatedAsExpected") - .given() - .when() - .then(sendModified(withSuccessivelyVariedQueryIds(), QueryVerbs::getVersionInfo)); - } - @Override protected Logger getResultsLogger() { return log; diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/misc/CannotDeleteSystemEntitiesSuite.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/misc/CannotDeleteSystemEntitiesSuite.java index 02d96507e694..a38749e5db21 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/misc/CannotDeleteSystemEntitiesSuite.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/misc/CannotDeleteSystemEntitiesSuite.java @@ -22,150 +22,101 @@ import static com.hedera.services.bdd.spec.transactions.TxnVerbs.cryptoTransfer; import static com.hedera.services.bdd.spec.transactions.TxnVerbs.fileDelete; import static com.hedera.services.bdd.spec.transactions.TxnVerbs.systemFileDelete; -import static com.hedera.services.bdd.spec.transactions.crypto.HapiCryptoTransfer.tinyBarsFromTo; import static com.hedera.services.bdd.spec.transactions.token.TokenMovement.movingHbar; import static com.hedera.services.bdd.spec.utilops.UtilVerbs.inParallel; import static com.hedera.services.bdd.spec.utilops.UtilVerbs.newKeyNamed; +import static com.hedera.services.bdd.suites.HapiSuite.GENESIS; +import static com.hedera.services.bdd.suites.HapiSuite.ONE_HUNDRED_HBARS; +import static com.hedera.services.bdd.suites.HapiSuite.SYSTEM_ADMIN; +import static com.hedera.services.bdd.suites.HapiSuite.SYSTEM_DELETE_ADMIN; import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.ENTITY_NOT_ALLOWED_TO_DELETE; import com.hedera.services.bdd.junit.HapiTest; -import com.hedera.services.bdd.junit.HapiTestSuite; -import com.hedera.services.bdd.spec.HapiSpec; import com.hedera.services.bdd.spec.HapiSpecOperation; -import com.hedera.services.bdd.suites.BddMethodIsNotATest; -import com.hedera.services.bdd.suites.HapiSuite; import java.util.Arrays; -import java.util.List; import java.util.stream.IntStream; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; - -@HapiTestSuite -public class CannotDeleteSystemEntitiesSuite extends HapiSuite { - private static final Logger log = LogManager.getLogger(CannotDeleteSystemEntitiesSuite.class); +import java.util.stream.Stream; +import org.junit.jupiter.api.DynamicTest; +public class CannotDeleteSystemEntitiesSuite { final int[] sysFileIds = {101, 102, 111, 112, 121, 122, 150}; - public static void main(String... args) { - CannotDeleteSystemEntitiesSuite suite = new CannotDeleteSystemEntitiesSuite(); - suite.runSuiteAsync(); - } - - @Override - public boolean canRunConcurrent() { - return true; - } - - @Override - public List getSpecsInSuite() { - return List.of( - genesisCannotDeleteSystemAccountsFrom1To100(), - genesisCannotDeleteSystemAccountsFrom700To750(), - systemAdminCannotDeleteSystemAccountsFrom1To100(), - systemAdminCannotDeleteSystemAccountsFrom700To750(), - systemDeleteAdminCannotDeleteSystemAccountsFrom1To100(), - systemDeleteAdminCannotDeleteSystemAccountsFrom700To750(), - normalUserCannotDeleteSystemAccountsFrom1To100(), - normalUserCannotDeleteSystemAccountsFrom700To750(), - genesisCannotDeleteSystemFileIds(), - systemAdminCannotDeleteSystemFileIds(), - systemDeleteAdminCannotDeleteSystemFileIds(), - normalUserCannotDeleteSystemFileIds(), - genesisCannotSystemFileDeleteFileIds(), - systemAdminCannotSystemFileDeleteFileIds(), - systemDeleteAdminCannotSystemFileDeleteFileIds()); - } - @HapiTest - final HapiSpec ensureSystemAccountsHaveSomeFunds() { - return defaultHapiSpec("EnsureSystemAccountsHaveSomeFunds") - .given() - .when() - .then( - cryptoTransfer(movingHbar(100 * ONE_HUNDRED_HBARS) - .distributing(GENESIS, SYSTEM_ADMIN, SYSTEM_DELETE_ADMIN)) - .payingWith(GENESIS), - cryptoTransfer(tinyBarsFromTo(GENESIS, SYSTEM_DELETE_ADMIN, 10 * ONE_HUNDRED_HBARS)) - .payingWith(GENESIS)); - } - - @HapiTest - final HapiSpec genesisCannotDeleteSystemAccountsFrom1To100() { + final Stream genesisCannotDeleteSystemAccountsFrom1To100() { return systemUserCannotDeleteSystemAccounts(1, 100, GENESIS); } @HapiTest - final HapiSpec genesisCannotDeleteSystemAccountsFrom700To750() { + final Stream genesisCannotDeleteSystemAccountsFrom700To750() { return systemUserCannotDeleteSystemAccounts(700, 750, GENESIS); } @HapiTest - final HapiSpec systemAdminCannotDeleteSystemAccountsFrom1To100() { + final Stream systemAdminCannotDeleteSystemAccountsFrom1To100() { return systemUserCannotDeleteSystemAccounts(1, 100, SYSTEM_ADMIN); } @HapiTest - final HapiSpec systemAdminCannotDeleteSystemAccountsFrom700To750() { + final Stream systemAdminCannotDeleteSystemAccountsFrom700To750() { return systemUserCannotDeleteSystemAccounts(700, 750, SYSTEM_ADMIN); } @HapiTest - final HapiSpec systemDeleteAdminCannotDeleteSystemAccountsFrom1To100() { + final Stream systemDeleteAdminCannotDeleteSystemAccountsFrom1To100() { return systemUserCannotDeleteSystemAccounts(1, 100, SYSTEM_DELETE_ADMIN); } @HapiTest - final HapiSpec systemDeleteAdminCannotDeleteSystemAccountsFrom700To750() { + final Stream systemDeleteAdminCannotDeleteSystemAccountsFrom700To750() { return systemUserCannotDeleteSystemAccounts(700, 750, SYSTEM_DELETE_ADMIN); } @HapiTest - final HapiSpec normalUserCannotDeleteSystemAccountsFrom1To100() { + final Stream normalUserCannotDeleteSystemAccountsFrom1To100() { return normalUserCannotDeleteSystemAccounts(1, 100); } @HapiTest - final HapiSpec normalUserCannotDeleteSystemAccountsFrom700To750() { + final Stream normalUserCannotDeleteSystemAccountsFrom700To750() { return normalUserCannotDeleteSystemAccounts(700, 750); } @HapiTest - final HapiSpec genesisCannotDeleteSystemFileIds() { + final Stream genesisCannotDeleteSystemFileIds() { return systemUserCannotDeleteSystemFiles(sysFileIds, GENESIS); } @HapiTest - final HapiSpec systemAdminCannotDeleteSystemFileIds() { + final Stream systemAdminCannotDeleteSystemFileIds() { return systemUserCannotDeleteSystemFiles(sysFileIds, SYSTEM_ADMIN); } @HapiTest - final HapiSpec systemDeleteAdminCannotDeleteSystemFileIds() { + final Stream systemDeleteAdminCannotDeleteSystemFileIds() { return systemUserCannotDeleteSystemFiles(sysFileIds, SYSTEM_DELETE_ADMIN); } @HapiTest - final HapiSpec normalUserCannotDeleteSystemFileIds() { + final Stream normalUserCannotDeleteSystemFileIds() { return normalUserCannotDeleteSystemFiles(sysFileIds); } @HapiTest - final HapiSpec genesisCannotSystemFileDeleteFileIds() { + final Stream genesisCannotSystemFileDeleteFileIds() { return systemDeleteCannotDeleteSystemFiles(sysFileIds, GENESIS); } @HapiTest - final HapiSpec systemAdminCannotSystemFileDeleteFileIds() { + final Stream systemAdminCannotSystemFileDeleteFileIds() { return systemDeleteCannotDeleteSystemFiles(sysFileIds, SYSTEM_ADMIN); } @HapiTest - final HapiSpec systemDeleteAdminCannotSystemFileDeleteFileIds() { + final Stream systemDeleteAdminCannotSystemFileDeleteFileIds() { return systemDeleteCannotDeleteSystemFiles(sysFileIds, SYSTEM_DELETE_ADMIN); } - @BddMethodIsNotATest - final HapiSpec systemUserCannotDeleteSystemAccounts(int firstAccount, int lastAccount, String sysUser) { + final Stream systemUserCannotDeleteSystemAccounts(int firstAccount, int lastAccount, String sysUser) { return defaultHapiSpec("systemUserCannotDeleteSystemAccounts") .given( cryptoCreate("unluckyReceiver").balance(0L), @@ -182,8 +133,7 @@ final HapiSpec systemUserCannotDeleteSystemAccounts(int firstAccount, int lastAc .toArray(HapiSpecOperation[]::new))); } - @BddMethodIsNotATest - final HapiSpec normalUserCannotDeleteSystemAccounts(int firstAccount, int lastAccount) { + final Stream normalUserCannotDeleteSystemAccounts(int firstAccount, int lastAccount) { return defaultHapiSpec("normalUserCannotDeleteSystemAccounts") .given(newKeyNamed("normalKey"), cryptoCreate("unluckyReceiver").balance(0L)) .when(cryptoCreate("normalUser").key("normalKey").balance(1_000_000_000L)) @@ -196,10 +146,11 @@ final HapiSpec normalUserCannotDeleteSystemAccounts(int firstAccount, int lastAc .toArray(HapiSpecOperation[]::new))); } - @BddMethodIsNotATest - final HapiSpec systemUserCannotDeleteSystemFiles(int[] fileIds, String sysUser) { + final Stream systemUserCannotDeleteSystemFiles(int[] fileIds, String sysUser) { return defaultHapiSpec("systemUserCannotDeleteSystemFiles") - .given() + .given(cryptoTransfer(movingHbar(100 * ONE_HUNDRED_HBARS) + .distributing(GENESIS, SYSTEM_ADMIN, SYSTEM_DELETE_ADMIN)) + .payingWith(GENESIS)) .when() .then(inParallel(Arrays.stream(fileIds) .mapToObj(id -> cryptoDelete("0.0." + id) @@ -209,8 +160,7 @@ final HapiSpec systemUserCannotDeleteSystemFiles(int[] fileIds, String sysUser) .toArray(HapiSpecOperation[]::new))); } - @BddMethodIsNotATest - final HapiSpec normalUserCannotDeleteSystemFiles(int[] fileIds) { + final Stream normalUserCannotDeleteSystemFiles(int[] fileIds) { return defaultHapiSpec("normalUserCannotDeleteSystemFiles") .given(newKeyNamed("normalKey")) .when(cryptoCreate("normalUser").key("normalKey").balance(1_000_000_000L)) @@ -222,10 +172,11 @@ final HapiSpec normalUserCannotDeleteSystemFiles(int[] fileIds) { .toArray(HapiSpecOperation[]::new))); } - @BddMethodIsNotATest - final HapiSpec systemDeleteCannotDeleteSystemFiles(int[] fileIds, String sysUser) { + final Stream systemDeleteCannotDeleteSystemFiles(int[] fileIds, String sysUser) { return defaultHapiSpec("systemDeleteCannotDeleteSystemFiles") - .given() + .given(cryptoTransfer(movingHbar(100 * ONE_HUNDRED_HBARS) + .distributing(GENESIS, SYSTEM_ADMIN, SYSTEM_DELETE_ADMIN)) + .payingWith(GENESIS)) .when() .then(inParallel(Arrays.stream(fileIds) .mapToObj(id -> systemFileDelete("0.0." + id) @@ -234,9 +185,4 @@ final HapiSpec systemDeleteCannotDeleteSystemFiles(int[] fileIds, String sysUser .hasPrecheck(ENTITY_NOT_ALLOWED_TO_DELETE)) .toArray(HapiSpecOperation[]::new))); } - - @Override - protected Logger getResultsLogger() { - return log; - } } diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/misc/ConsensusQueriesStressTests.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/misc/ConsensusQueriesStressTests.java index a2d2af66df35..b6fba1b85cb0 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/misc/ConsensusQueriesStressTests.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/misc/ConsensusQueriesStressTests.java @@ -24,12 +24,10 @@ import static java.util.concurrent.TimeUnit.SECONDS; import com.hedera.services.bdd.junit.HapiTest; -import com.hedera.services.bdd.junit.HapiTestSuite; import com.hedera.services.bdd.spec.HapiPropertySource; import com.hedera.services.bdd.spec.HapiSpec; import com.hedera.services.bdd.spec.HapiSpecOperation; import com.hedera.services.bdd.spec.infrastructure.OpProvider; -import com.hedera.services.bdd.suites.HapiSuite; import java.util.List; import java.util.Optional; import java.util.concurrent.TimeUnit; @@ -38,30 +36,16 @@ import java.util.concurrent.atomic.AtomicReference; import java.util.function.Consumer; import java.util.function.Function; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; +import java.util.stream.Stream; +import org.junit.jupiter.api.DynamicTest; -@HapiTestSuite -public class ConsensusQueriesStressTests extends HapiSuite { - private static final Logger log = LogManager.getLogger(ConsensusQueriesStressTests.class); - - private AtomicLong duration = new AtomicLong(30); +public class ConsensusQueriesStressTests { + private AtomicLong duration = new AtomicLong(10); private AtomicReference unit = new AtomicReference<>(SECONDS); - private AtomicInteger maxOpsPerSec = new AtomicInteger(100); - - public static void main(String... args) { - new ConsensusQueriesStressTests().runSuiteSync(); - } - - @Override - public List getSpecsInSuite() { - return List.of(new HapiSpec[] { - getTopicInfoStress(), - }); - } + private AtomicInteger maxOpsPerSec = new AtomicInteger(10); @HapiTest - final HapiSpec getTopicInfoStress() { + final Stream getTopicInfoStress() { return defaultHapiSpec("GetTopicInfoStress") .given() .when() @@ -101,9 +85,4 @@ private void configure( configurer.accept(getter.apply(name)); } } - - @Override - protected Logger getResultsLogger() { - return log; - } } diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/misc/ContractQueriesStressTests.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/misc/ContractQueriesStressTests.java deleted file mode 100644 index 02c807dca2c1..000000000000 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/misc/ContractQueriesStressTests.java +++ /dev/null @@ -1,211 +0,0 @@ -/* - * Copyright (C) 2020-2024 Hedera Hashgraph, LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.hedera.services.bdd.suites.misc; - -import static com.hedera.services.bdd.spec.HapiSpec.defaultHapiSpec; -import static com.hedera.services.bdd.spec.assertions.AssertUtils.inOrder; -import static com.hedera.services.bdd.spec.assertions.ContractFnResultAsserts.isLiteralResult; -import static com.hedera.services.bdd.spec.assertions.ContractFnResultAsserts.resultWith; -import static com.hedera.services.bdd.spec.assertions.TransactionRecordAsserts.recordWith; -import static com.hedera.services.bdd.spec.queries.QueryVerbs.contractCallLocal; -import static com.hedera.services.bdd.spec.queries.QueryVerbs.getAccountRecords; -import static com.hedera.services.bdd.spec.queries.QueryVerbs.getContractBytecode; -import static com.hedera.services.bdd.spec.queries.QueryVerbs.getContractInfo; -import static com.hedera.services.bdd.spec.transactions.TxnVerbs.contractCall; -import static com.hedera.services.bdd.spec.transactions.TxnVerbs.contractCreate; -import static com.hedera.services.bdd.spec.transactions.TxnVerbs.uploadInitCode; -import static com.hedera.services.bdd.spec.utilops.UtilVerbs.runWithProvider; -import static com.hedera.services.bdd.spec.utilops.UtilVerbs.withOpContext; -import static com.hedera.services.bdd.suites.contract.Utils.FunctionType.FUNCTION; -import static com.hedera.services.bdd.suites.contract.Utils.getABIFor; -import static java.util.concurrent.TimeUnit.SECONDS; - -import com.hedera.services.bdd.spec.HapiPropertySource; -import com.hedera.services.bdd.spec.HapiSpec; -import com.hedera.services.bdd.spec.HapiSpecOperation; -import com.hedera.services.bdd.spec.infrastructure.OpProvider; -import com.hedera.services.bdd.suites.HapiSuite; -import java.math.BigInteger; -import java.util.List; -import java.util.Optional; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.atomic.AtomicInteger; -import java.util.concurrent.atomic.AtomicLong; -import java.util.concurrent.atomic.AtomicReference; -import java.util.function.Consumer; -import java.util.function.Function; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; - -public class ContractQueriesStressTests extends HapiSuite { - private static final Logger log = LogManager.getLogger(ContractQueriesStressTests.class); - private static final String CHILD_STORAGE = "ChildStorage"; - private static final String SET_ZERO_READ_ONE = "setZeroReadOne"; - private static final String GROW_CHILD = "growChild"; - - private AtomicLong duration = new AtomicLong(30); - private AtomicReference unit = new AtomicReference<>(SECONDS); - private AtomicInteger maxOpsPerSec = new AtomicInteger(100); - - public static void main(String... args) { - new ContractQueriesStressTests().runSuiteSync(); - } - - @Override - public List getSpecsInSuite() { - return List.of(new HapiSpec[] { - contractCallLocalStress(), getContractRecordsStress(), getContractBytecodeStress(), getContractInfoStress(), - }); - } - - final HapiSpec getContractInfoStress() { - return defaultHapiSpec("GetContractInfoStress") - .given() - .when() - .then( - withOpContext((spec, opLog) -> configureFromCi(spec)), - runWithProvider(getContractInfoFactory()) - .lasting(duration::get, unit::get) - .maxOpsPerSec(maxOpsPerSec::get)); - } - - final HapiSpec getContractBytecodeStress() { - return defaultHapiSpec("GetAccountRecordsStress") - .given() - .when() - .then( - withOpContext((spec, opLog) -> configureFromCi(spec)), - runWithProvider(getContractBytecodeFactory()) - .lasting(duration::get, unit::get) - .maxOpsPerSec(maxOpsPerSec::get)); - } - - final HapiSpec contractCallLocalStress() { - return defaultHapiSpec("ContractCallLocalStress") - .given() - .when() - .then( - withOpContext((spec, opLog) -> configureFromCi(spec)), - runWithProvider(contractCallLocalFactory()) - .lasting(duration::get, unit::get) - .maxOpsPerSec(maxOpsPerSec::get)); - } - - final HapiSpec getContractRecordsStress() { - return defaultHapiSpec("GetContractRecordsStress") - .given() - .when() - .then( - withOpContext((spec, opLog) -> configureFromCi(spec)), - runWithProvider(getContractRecordsFactory()) - .lasting(duration::get, unit::get) - .maxOpsPerSec(maxOpsPerSec::get)); - } - - private Function getContractRecordsFactory() { - return spec -> new OpProvider() { - @Override - public List suggestedInitializers() { - return List.of( - uploadInitCode(CHILD_STORAGE), - contractCreate(CHILD_STORAGE), - contractCall(CHILD_STORAGE, GROW_CHILD, 0, 1, 1), - contractCall(CHILD_STORAGE, GROW_CHILD, 1, 1, 3), - contractCall(CHILD_STORAGE, SET_ZERO_READ_ONE, 23).via("first"), - contractCall(CHILD_STORAGE, SET_ZERO_READ_ONE, 23).via("second"), - contractCall(CHILD_STORAGE, SET_ZERO_READ_ONE, 23).via("third")); - } - - @Override - public Optional get() { - return Optional.of(getAccountRecords("somebody") - .has(inOrder( - recordWith().txnId("first"), - recordWith().txnId("second"), - recordWith().txnId("third"))) - .noLogging()); - } - }; - } - - private Function getContractInfoFactory() { - return spec -> new OpProvider() { - @Override - public List suggestedInitializers() { - return List.of(uploadInitCode(CHILD_STORAGE), contractCreate(CHILD_STORAGE)); - } - - @Override - public Optional get() { - return Optional.of(getContractInfo(CHILD_STORAGE).noLogging()); - } - }; - } - - private Function getContractBytecodeFactory() { - return spec -> new OpProvider() { - @Override - public List suggestedInitializers() { - return List.of(uploadInitCode(CHILD_STORAGE), contractCreate(CHILD_STORAGE)); - } - - @Override - public Optional get() { - return Optional.of(getContractBytecode(CHILD_STORAGE).noLogging()); - } - }; - } - - private Function contractCallLocalFactory() { - return spec -> new OpProvider() { - @Override - public List suggestedInitializers() { - return List.of(uploadInitCode(CHILD_STORAGE), contractCreate(CHILD_STORAGE)); - } - - @Override - public Optional get() { - var op = contractCallLocal(CHILD_STORAGE, getABIFor(FUNCTION, "getMyValue", CHILD_STORAGE)) - .noLogging() - .has(resultWith() - .resultThruAbi( - getABIFor(FUNCTION, "getMyValue", CHILD_STORAGE), - isLiteralResult(new Object[] {BigInteger.valueOf(73)}))); - return Optional.of(op); - } - }; - } - - private void configureFromCi(HapiSpec spec) { - HapiPropertySource ciProps = spec.setup().ciPropertiesMap(); - configure("duration", duration::set, ciProps, ciProps::getLong); - configure("unit", unit::set, ciProps, ciProps::getTimeUnit); - configure("maxOpsPerSec", maxOpsPerSec::set, ciProps, ciProps::getInteger); - } - - private void configure( - String name, Consumer configurer, HapiPropertySource ciProps, Function getter) { - if (ciProps.has(name)) { - configurer.accept(getter.apply(name)); - } - } - - @Override - protected Logger getResultsLogger() { - return log; - } -} diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/misc/CryptoQueriesStressTests.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/misc/CryptoQueriesStressTests.java index 9b9af935bd9f..e2a27f694b8c 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/misc/CryptoQueriesStressTests.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/misc/CryptoQueriesStressTests.java @@ -27,15 +27,14 @@ import static com.hedera.services.bdd.spec.transactions.crypto.HapiCryptoTransfer.tinyBarsFromTo; import static com.hedera.services.bdd.spec.utilops.UtilVerbs.runWithProvider; import static com.hedera.services.bdd.spec.utilops.UtilVerbs.withOpContext; +import static com.hedera.services.bdd.suites.HapiSuite.FUNDING; import static java.util.concurrent.TimeUnit.SECONDS; import com.hedera.services.bdd.junit.HapiTest; -import com.hedera.services.bdd.junit.HapiTestSuite; import com.hedera.services.bdd.spec.HapiPropertySource; import com.hedera.services.bdd.spec.HapiSpec; import com.hedera.services.bdd.spec.HapiSpecOperation; import com.hedera.services.bdd.spec.infrastructure.OpProvider; -import com.hedera.services.bdd.suites.HapiSuite; import java.util.List; import java.util.Optional; import java.util.concurrent.TimeUnit; @@ -44,30 +43,16 @@ import java.util.concurrent.atomic.AtomicReference; import java.util.function.Consumer; import java.util.function.Function; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; +import java.util.stream.Stream; +import org.junit.jupiter.api.DynamicTest; -@HapiTestSuite -public class CryptoQueriesStressTests extends HapiSuite { - private static final Logger log = LogManager.getLogger(CryptoQueriesStressTests.class); - - private AtomicLong duration = new AtomicLong(30); +public class CryptoQueriesStressTests { + private AtomicLong duration = new AtomicLong(10); private AtomicReference unit = new AtomicReference<>(SECONDS); - private AtomicInteger maxOpsPerSec = new AtomicInteger(100); - - public static void main(String... args) { - new CryptoQueriesStressTests().runSuiteSync(); - } - - @Override - public List getSpecsInSuite() { - return List.of(new HapiSpec[] { - getAccountInfoStress(), getAccountBalanceStress(), - }); - } + private AtomicInteger maxOpsPerSec = new AtomicInteger(10); @HapiTest - final HapiSpec getAccountBalanceStress() { + final Stream getAccountBalanceStress() { return defaultHapiSpec("getAccountBalanceStress") .given() .when() @@ -79,7 +64,7 @@ final HapiSpec getAccountBalanceStress() { } @HapiTest - final HapiSpec getAccountInfoStress() { + final Stream getAccountInfoStress() { return defaultHapiSpec("getAccountInfoStress") .given() .when() @@ -91,7 +76,7 @@ final HapiSpec getAccountInfoStress() { } @HapiTest - final HapiSpec getAccountRecordsStress() { + final Stream getAccountRecordsStress() { return defaultHapiSpec("getAccountRecordsStress") .given() .when() @@ -166,9 +151,4 @@ private void configure( configurer.accept(getter.apply(name)); } } - - @Override - protected Logger getResultsLogger() { - return log; - } } diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/misc/FileQueriesStressTests.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/misc/FileQueriesStressTests.java index e3ebb399f051..a37623b1df20 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/misc/FileQueriesStressTests.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/misc/FileQueriesStressTests.java @@ -25,12 +25,10 @@ import static java.util.concurrent.TimeUnit.SECONDS; import com.hedera.services.bdd.junit.HapiTest; -import com.hedera.services.bdd.junit.HapiTestSuite; import com.hedera.services.bdd.spec.HapiPropertySource; import com.hedera.services.bdd.spec.HapiSpec; import com.hedera.services.bdd.spec.HapiSpecOperation; import com.hedera.services.bdd.spec.infrastructure.OpProvider; -import com.hedera.services.bdd.suites.HapiSuite; import java.util.List; import java.util.Optional; import java.util.concurrent.TimeUnit; @@ -39,30 +37,16 @@ import java.util.concurrent.atomic.AtomicReference; import java.util.function.Consumer; import java.util.function.Function; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; +import java.util.stream.Stream; +import org.junit.jupiter.api.DynamicTest; -@HapiTestSuite -public class FileQueriesStressTests extends HapiSuite { - private static final Logger log = LogManager.getLogger(FileQueriesStressTests.class); - - private AtomicLong duration = new AtomicLong(30); +public class FileQueriesStressTests { + private AtomicLong duration = new AtomicLong(10); private AtomicReference unit = new AtomicReference<>(SECONDS); - private AtomicInteger maxOpsPerSec = new AtomicInteger(100); - - public static void main(String... args) { - new FileQueriesStressTests().runSuiteSync(); - } - - @Override - public List getSpecsInSuite() { - return List.of(new HapiSpec[] { - getFileInfoStress(), getFileContentsStress(), - }); - } + private AtomicInteger maxOpsPerSec = new AtomicInteger(10); @HapiTest - final HapiSpec getFileContentsStress() { + final Stream getFileContentsStress() { return defaultHapiSpec("getFileContentsStress") .given() .when() @@ -74,7 +58,7 @@ final HapiSpec getFileContentsStress() { } @HapiTest - final HapiSpec getFileInfoStress() { + final Stream getFileInfoStress() { return defaultHapiSpec("getFileInfoStress") .given() .when() @@ -130,9 +114,4 @@ private void configure( configurer.accept(getter.apply(name)); } } - - @Override - protected Logger getResultsLogger() { - return log; - } } diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/misc/FreezeRekeyedState.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/misc/FreezeRekeyedState.java deleted file mode 100644 index ffac51a25c2f..000000000000 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/misc/FreezeRekeyedState.java +++ /dev/null @@ -1,69 +0,0 @@ -/* - * Copyright (C) 2020-2024 Hedera Hashgraph, LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.hedera.services.bdd.suites.misc; - -import static com.hedera.services.bdd.spec.HapiSpec.customHapiSpec; -import static com.hedera.services.bdd.spec.utilops.UtilVerbs.freezeOnly; -import static com.hedera.services.bdd.suites.misc.RekeySavedStateTreasury.newTreasuryPassphrase; -import static com.hedera.services.bdd.suites.misc.RekeySavedStateTreasury.newTreasuryPemLoc; - -import com.hedera.services.bdd.spec.HapiSpec; -import com.hedera.services.bdd.suites.HapiSuite; -import java.util.List; -import java.util.Map; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; - -/** - * Given a network with a "re-keyed" treasury account, we want to use this the new treasury account - * to freeze our network and generate a signed state. - */ -public class FreezeRekeyedState extends HapiSuite { - private static final Logger log = LogManager.getLogger(FreezeRekeyedState.class); - - public static void main(String... args) { - new FreezeRekeyedState().runSuiteSync(); - } - - @Override - public List getSpecsInSuite() { - return List.of(new HapiSpec[] { - freezeWithNewTreasuryKey(), - }); - } - - final HapiSpec freezeWithNewTreasuryKey() { - return customHapiSpec("FreezeWithNewTreasuryKey") - .withProperties(Map.of( - "nodes", - "localhost", - "default.payer", - "0.0.2", - "default.payer.pemKeyLoc", - newTreasuryPemLoc, - "default.payer.pemKeyPassphrase", - newTreasuryPassphrase)) - .given() - .when() - .then(freezeOnly().startingIn(60).seconds()); - } - - @Override - protected Logger getResultsLogger() { - return log; - } -} diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/misc/GuidedTourRemoteSuite.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/misc/GuidedTourRemoteSuite.java deleted file mode 100644 index f3ca17ab302b..000000000000 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/misc/GuidedTourRemoteSuite.java +++ /dev/null @@ -1,235 +0,0 @@ -/* - * Copyright (C) 2020-2024 Hedera Hashgraph, LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.hedera.services.bdd.suites.misc; - -import static com.hedera.services.bdd.spec.HapiSpec.customFailingHapiSpec; -import static com.hedera.services.bdd.spec.HapiSpec.customHapiSpec; -import static com.hedera.services.bdd.spec.assertions.ContractFnResultAsserts.isLiteralResult; -import static com.hedera.services.bdd.spec.assertions.ContractFnResultAsserts.resultWith; -import static com.hedera.services.bdd.spec.keys.KeyShape.OFF; -import static com.hedera.services.bdd.spec.keys.KeyShape.ON; -import static com.hedera.services.bdd.spec.keys.KeyShape.PREDEFINED_SHAPE; -import static com.hedera.services.bdd.spec.keys.KeyShape.SIMPLE; -import static com.hedera.services.bdd.spec.keys.KeyShape.listOf; -import static com.hedera.services.bdd.spec.keys.KeyShape.sigs; -import static com.hedera.services.bdd.spec.keys.KeyShape.threshOf; -import static com.hedera.services.bdd.spec.queries.QueryVerbs.contractCallLocal; -import static com.hedera.services.bdd.spec.queries.QueryVerbs.getAccountBalance; -import static com.hedera.services.bdd.spec.queries.QueryVerbs.getAccountInfo; -import static com.hedera.services.bdd.spec.transactions.TxnVerbs.contractCreate; -import static com.hedera.services.bdd.spec.transactions.TxnVerbs.cryptoCreate; -import static com.hedera.services.bdd.spec.transactions.TxnVerbs.cryptoTransfer; -import static com.hedera.services.bdd.spec.transactions.TxnVerbs.cryptoUpdate; -import static com.hedera.services.bdd.spec.transactions.TxnVerbs.fileCreate; -import static com.hedera.services.bdd.spec.transactions.TxnVerbs.fileDelete; -import static com.hedera.services.bdd.spec.transactions.TxnVerbs.uploadInitCode; -import static com.hedera.services.bdd.spec.transactions.crypto.HapiCryptoTransfer.tinyBarsFromTo; -import static com.hedera.services.bdd.spec.utilops.UtilVerbs.keyFromFile; -import static com.hedera.services.bdd.spec.utilops.UtilVerbs.newKeyNamed; -import static com.hedera.services.bdd.suites.contract.Utils.FunctionType.FUNCTION; -import static com.hedera.services.bdd.suites.contract.Utils.getABIFor; -import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.INVALID_SIGNATURE; -import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.OK; - -import com.hedera.services.bdd.spec.HapiSpec; -import com.hedera.services.bdd.spec.keys.ControlForKey; -import com.hedera.services.bdd.spec.keys.KeyShape; -import com.hedera.services.bdd.spec.keys.SigControl; -import com.hedera.services.bdd.spec.transactions.crypto.HapiCryptoTransfer; -import com.hedera.services.bdd.suites.HapiSuite; -import java.math.BigInteger; -import java.util.Arrays; -import java.util.List; -import java.util.Map; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; - -@SuppressWarnings("java:S1144") -public class GuidedTourRemoteSuite extends HapiSuite { - private static final Logger log = LogManager.getLogger(GuidedTourRemoteSuite.class); - public static final String TODO = ""; - public static final String HOST = ""; - public static final String TARGET_ACCOUNT = "targetAccount"; - public static final String TARGET = "target"; - public static final String OLD_KEY = "oldKey"; - - public static void main(String... args) { - new GuidedTourRemoteSuite().runSuiteSync(); - } - - @Override - public List getSpecsInSuite() { - return allOf(guidedTour()); - } - - private List guidedTour() { - return Arrays.asList( - // transferChangesBalance() - // updateWithInvalidKeyFailsInPrecheck() - // updateWithInvalidatedKeyFailsInHandle() - // topLevelHederaKeyMustBeActive() - // topLevelListBehavesAsRevocationService() - balanceLookupContractWorks()); - } - - /** - * Provides an example of a spec that replaces a target account's key with a 1/2 threshold key, - * where the two top-level keys are the target's existing Ed25519 key and a 2/3 threshold key - * with all these three keys simple Ed25519. - * - *

    All the keys are imported from PEM files. - * - * @return the example spec - */ - final HapiSpec rekeyAccountWith2Of3Choice() { - // The account to re-key - final var target = "0.0.1234"; - // The PEM files with the involved keys - final var targetKeyLoc = "keys/original1234.pem"; - final var oneOfThreeKeysLoc = "keys/oneOfThree.pem"; - final var twoOfThreeKeysLoc = "keys/twoOfThree.pem"; - final var threeOfThreeKeysLoc = "keys/threeOfThree.pem"; - // The registry names of the key we'll use - final var extantKey = "original"; - final var newKey1 = "k1"; - final var newKey2 = "k2"; - final var newKey3 = "k3"; - final var replacementKey = "updated"; - final var newShape = - threshOf(1, PREDEFINED_SHAPE, threshOf(2, PREDEFINED_SHAPE, PREDEFINED_SHAPE, PREDEFINED_SHAPE)); - - return customHapiSpec("RekeyAccountWith2Of3Choice") - .withProperties(Map.of( - "nodes", TODO, - "default.payer", TODO, - "default.payer.pemKeyLoc", TODO, - "default.payer.pemKeyPassphrase", TODO)) - .given( - keyFromFile(extantKey, targetKeyLoc), - keyFromFile(newKey1, oneOfThreeKeysLoc), - keyFromFile(newKey2, twoOfThreeKeysLoc), - keyFromFile(newKey3, threeOfThreeKeysLoc), - newKeyNamed(replacementKey) - .shape(newShape.signedWith(sigs(extantKey, sigs(newKey1, newKey2, newKey3)))), - getAccountInfo(target).logged().loggingHexedKeys()) - .when(cryptoUpdate(target).key(replacementKey).signedBy(DEFAULT_PAYER, extantKey)) - .then( - getAccountInfo(target).loggingHexedKeys().savingProtoTo("targetInfo.bin"), - cryptoTransfer(HapiCryptoTransfer.tinyBarsFromTo(target, FUNDING, 1)) - .signedBy(DEFAULT_PAYER, extantKey)); - } - - final HapiSpec balanceLookupContractWorks() { - final long ACTUAL_BALANCE = 1_234L; - final var contract = "BalanceLookup"; - - return customHapiSpec("BalanceLookupContractWorks") - .withProperties(Map.of("host", HOST)) - .given( - cryptoCreate(TARGET_ACCOUNT).balance(ACTUAL_BALANCE), - uploadInitCode(contract), - contractCreate(contract)) - .when() - .then( - /* This contract (c.f. src/main/resource/contract/contracts/BalanceLookup/BalanceLookup.sol) assumes - a shard and realm of 0; accepts just the sequence number of an account. */ - contractCallLocal(contract, "lookup", spec -> new Object[] { - spec.registry().getAccountID(TARGET_ACCOUNT).getAccountNum() - }) - .has(resultWith() - .resultThruAbi( - getABIFor(FUNCTION, "lookup", contract), - isLiteralResult(new Object[] {BigInteger.valueOf(ACTUAL_BALANCE)})))); - } - - final HapiSpec topLevelHederaKeyMustBeActive() { - KeyShape waclShape = listOf(SIMPLE, threshOf(2, 3)); - SigControl updateSigControl = waclShape.signedWith(sigs(ON, sigs(ON, OFF, OFF))); - - return customHapiSpec("TopLevelListBehavesAsRevocationService") - .withProperties(Map.of("host", HOST)) - .given() - .when() - .then(fileCreate(TARGET) - .waclShape(waclShape) - .sigControl(ControlForKey.forKey(TARGET, updateSigControl)) - .hasKnownStatus(INVALID_SIGNATURE)); - } - - /* Feature is pending; top-level KeyList should allow deletion with an active - signature for any ONE of its child keys active. (I.e. a top-level KeyList - behaves as a revocation service.) - - NOTE: KeyLists lower in the key hierarchy still require all child keys - to have active signatures. - */ - final HapiSpec topLevelListBehavesAsRevocationService() { - KeyShape waclShape = listOf(SIMPLE, threshOf(2, 3)); - SigControl deleteSigControl = waclShape.signedWith(sigs(OFF, sigs(ON, ON, OFF))); - - return customFailingHapiSpec("TopLevelListBehavesAsRevocationService") - .withProperties(Map.of("host", HOST)) - .given(fileCreate(TARGET).waclShape(waclShape)) - .when() - .then(fileDelete(TARGET).sigControl(ControlForKey.forKey(TARGET, deleteSigControl))); - } - - final HapiSpec updateWithInvalidatedKeyFailsInHandle() { - return customHapiSpec("UpdateWithInvalidatedKeyFailsIHandle") - .withProperties(Map.of("host", HOST)) - .given( - newKeyNamed(OLD_KEY), - newKeyNamed("newKey"), - cryptoCreate(TARGET).key(OLD_KEY)) - .when(cryptoUpdate(TARGET).key("newKey").deferStatusResolution()) - .then(cryptoUpdate(TARGET) - .signedBy(GENESIS, OLD_KEY) - .receiverSigRequired(true) - .hasPrecheck(OK) - .hasKnownStatus(INVALID_SIGNATURE)); - } - - final HapiSpec updateWithInvalidKeyFailsInPrecheck() { - KeyShape keyShape = listOf(3); - - return HapiSpec.customHapiSpec("UpdateWithInvalidKeyFailsInPrecheck") - .withProperties(Map.of("host", HOST)) - .given(newKeyNamed("invalidPayerKey").shape(keyShape)) - .when() - .then(cryptoUpdate(SYSTEM_ADMIN) - .receiverSigRequired(true) - .signedBy("invalidPayerKey") - .hasPrecheck(INVALID_SIGNATURE)); - } - - final HapiSpec transferChangesBalance() { - final long AMOUNT = 1_000L; - - return HapiSpec.customHapiSpec("TransferChangesBalance") - .withProperties(Map.of("host", HOST)) - .given(cryptoCreate(TARGET_ACCOUNT).balance(0L)) - .when(cryptoTransfer(tinyBarsFromTo(GENESIS, TARGET_ACCOUNT, AMOUNT))) - .then( - getAccountBalance(TARGET_ACCOUNT).hasTinyBars(AMOUNT), - getAccountInfo(TARGET_ACCOUNT).logged()); - } - - @Override - protected Logger getResultsLogger() { - return log; - } -} diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/misc/InvalidgRPCValuesTest.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/misc/InvalidgRPCValuesTest.java index e558d9771954..79c39fab4904 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/misc/InvalidgRPCValuesTest.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/misc/InvalidgRPCValuesTest.java @@ -21,36 +21,35 @@ import static com.hedera.services.bdd.spec.queries.QueryVerbs.getAccountInfo; import static com.hedera.services.bdd.spec.queries.QueryVerbs.getTokenInfo; import static com.hedera.services.bdd.spec.queries.QueryVerbs.getTopicInfo; +import static com.hedera.services.bdd.spec.queries.QueryVerbs.getTxnRecord; +import static com.hedera.services.bdd.spec.transactions.TxnVerbs.contractCreate; +import static com.hedera.services.bdd.spec.transactions.TxnVerbs.createTopic; +import static com.hedera.services.bdd.spec.transactions.TxnVerbs.cryptoCreate; +import static com.hedera.services.bdd.spec.transactions.TxnVerbs.cryptoTransfer; +import static com.hedera.services.bdd.spec.transactions.TxnVerbs.fileUpdate; import static com.hedera.services.bdd.spec.transactions.TxnVerbs.scheduleDelete; import static com.hedera.services.bdd.spec.transactions.TxnVerbs.scheduleSign; +import static com.hedera.services.bdd.spec.transactions.TxnVerbs.uploadInitCode; +import static com.hedera.services.bdd.spec.transactions.crypto.HapiCryptoTransfer.tinyBarsFromTo; +import static com.hedera.services.bdd.suites.HapiSuite.GENESIS; +import static com.hedera.services.bdd.suites.HapiSuite.SYSTEM_ADMIN; import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.INVALID_ACCOUNT_ID; import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.INVALID_SCHEDULE_ID; import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.INVALID_TOKEN_ID; import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.INVALID_TOPIC_ID; +import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.INVALID_TRANSACTION; +import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.INVALID_TRANSACTION_BODY; +import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.RECORD_NOT_FOUND; import com.hedera.services.bdd.junit.HapiTest; -import com.hedera.services.bdd.junit.HapiTestSuite; -import com.hedera.services.bdd.spec.HapiSpec; -import com.hedera.services.bdd.suites.HapiSuite; -import java.util.List; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; +import java.util.stream.Stream; +import org.junit.jupiter.api.DynamicTest; -@HapiTestSuite -public class InvalidgRPCValuesTest extends HapiSuite { - private static final Logger log = LogManager.getLogger(InvalidgRPCValuesTest.class); - - public static void main(String... args) throws Exception { - new InvalidgRPCValuesTest().runSuiteSync(); - } - - @Override - public List getSpecsInSuite() { - return List.of(new HapiSpec[] {invalidIdCheck()}); - } +public class InvalidgRPCValuesTest { + private static final String FAILED_CRYPTO_TRANSACTION = "failedCryptoTransaction"; @HapiTest - final HapiSpec invalidIdCheck() { + final Stream invalidIdCheck() { final long MAX_NUM_ALLOWED = 0xFFFFFFFFL; final String invalidMaxId = MAX_NUM_ALLOWED + 1 + ".2.3"; return defaultHapiSpec("TransferWithInvalidAccount") @@ -68,8 +67,50 @@ final HapiSpec invalidIdCheck() { scheduleDelete(invalidMaxId).hasKnownStatus(INVALID_SCHEDULE_ID)); } - @Override - protected Logger getResultsLogger() { - return log; + @HapiTest + final Stream transactionsWithOnlySigMap() { + final var contract = "BalanceLookup"; + return defaultHapiSpec("TransactionsWithOnlySigMap") + .given( + cryptoTransfer(tinyBarsFromTo(GENESIS, SYSTEM_ADMIN, 1L)) + .via(FAILED_CRYPTO_TRANSACTION) + .asTxnWithOnlySigMap() + .hasPrecheck(INVALID_TRANSACTION_BODY), + uploadInitCode(contract), + fileUpdate(contract) + .via("failedFileTransaction") + .asTxnWithOnlySigMap() + .hasPrecheck(INVALID_TRANSACTION_BODY)) + .when(contractCreate(contract) + .balance(1_000L) + .via("failedContractTransaction") + .asTxnWithOnlySigMap() + .hasPrecheck(INVALID_TRANSACTION_BODY)) + .then( + getTxnRecord(FAILED_CRYPTO_TRANSACTION).hasCostAnswerPrecheck(INVALID_ACCOUNT_ID), + getTxnRecord("failedFileTransaction").hasCostAnswerPrecheck(INVALID_ACCOUNT_ID), + getTxnRecord("failedContractTransaction").hasCostAnswerPrecheck(INVALID_ACCOUNT_ID)); + } + + @HapiTest + final Stream transactionsWithSignedTxnBytesAndSigMap() { + return defaultHapiSpec("TransactionsWithSignedTxnBytesAndSigMap") + .given() + .when(createTopic("testTopic") + .via("failedConsensusTransaction") + .asTxnWithSignedTxnBytesAndSigMap() + .hasPrecheck(INVALID_TRANSACTION)) + .then(getTxnRecord("failedConsensusTransaction").hasAnswerOnlyPrecheck(RECORD_NOT_FOUND)); + } + + @HapiTest + final Stream transactionsWithSignedTxnBytesAndBodyBytes() { + return defaultHapiSpec("TransactionsWithSignedTxnBytesAndBodyBytes") + .given() + .when(cryptoCreate("testAccount") + .via(FAILED_CRYPTO_TRANSACTION) + .asTxnWithSignedTxnBytesAndBodyBytes() + .hasPrecheck(INVALID_TRANSACTION)) + .then(getTxnRecord(FAILED_CRYPTO_TRANSACTION).hasAnswerOnlyPrecheck(RECORD_NOT_FOUND)); } } diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/misc/MemoValidation.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/misc/MemoValidation.java deleted file mode 100644 index 94553ae18dcb..000000000000 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/misc/MemoValidation.java +++ /dev/null @@ -1,265 +0,0 @@ -/* - * Copyright (C) 2021-2024 Hedera Hashgraph, LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.hedera.services.bdd.suites.misc; - -import static com.hedera.services.bdd.spec.HapiSpec.defaultHapiSpec; -import static com.hedera.services.bdd.spec.transactions.TxnVerbs.contractCall; -import static com.hedera.services.bdd.spec.transactions.TxnVerbs.contractCreate; -import static com.hedera.services.bdd.spec.transactions.TxnVerbs.createTopic; -import static com.hedera.services.bdd.spec.transactions.TxnVerbs.cryptoCreate; -import static com.hedera.services.bdd.spec.transactions.TxnVerbs.cryptoTransfer; -import static com.hedera.services.bdd.spec.transactions.TxnVerbs.cryptoUpdate; -import static com.hedera.services.bdd.spec.transactions.TxnVerbs.scheduleCreate; -import static com.hedera.services.bdd.spec.transactions.TxnVerbs.scheduleSign; -import static com.hedera.services.bdd.spec.transactions.TxnVerbs.tokenAssociate; -import static com.hedera.services.bdd.spec.transactions.TxnVerbs.tokenCreate; -import static com.hedera.services.bdd.spec.transactions.TxnVerbs.tokenUpdate; -import static com.hedera.services.bdd.spec.transactions.TxnVerbs.updateTopic; -import static com.hedera.services.bdd.spec.transactions.TxnVerbs.uploadInitCode; -import static com.hedera.services.bdd.spec.transactions.crypto.HapiCryptoTransfer.tinyBarsFromTo; -import static com.hedera.services.bdd.spec.utilops.UtilVerbs.newKeyNamed; -import static com.hedera.services.bdd.spec.utilops.UtilVerbs.overriding; -import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.INVALID_ZERO_BYTE_IN_STRING; -import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.MEMO_TOO_LONG; - -import com.hedera.services.bdd.spec.HapiSpec; -import com.hedera.services.bdd.spec.HapiSpecSetup; -import com.hedera.services.bdd.spec.transactions.TxnUtils; -import com.hedera.services.bdd.suites.HapiSuite; -import java.nio.charset.StandardCharsets; -import java.util.Arrays; -import java.util.List; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; - -public final class MemoValidation extends HapiSuite { - private static final Logger log = LogManager.getLogger(MemoValidation.class); - - private static final char SINGLE_BYTE_CHAR = 'a'; - private static final char MULTI_BYTE_CHAR = 'Ñ„'; - private static final String primary = "primary"; - private static final String secondary = "secondary"; - private static final String SCHEDULING_WHITELIST = "scheduling.whitelist"; - private static final String CREATE = "create"; - private static final String ADMIN_KEY = "adminKey"; - private static final String IN_VALID_MEMO_WITH_MULTI_BYTE_CHARS = "inValidMemoWithMultiByteChars"; - - private static String longMemo; - private static String validMemoWithMultiByteChars; - private static String inValidMemoWithMultiByteChars; - private static String stringOf49Bytes; - - public static void main(String... args) { - new MemoValidation().runSuiteSync(); - } - - @Override - protected Logger getResultsLogger() { - return log; - } - - @Override - public List getSpecsInSuite() { - setUpByteArrays(); - return List.of( - // cryptoOps(), - // topicOps(), - // scheduleOps(), - // tokenOps(), - contractOps()); - } - - final HapiSpec contractOps() { - final var contract = "CreateTrivial"; - return defaultHapiSpec("MemoValidationsOnContractOps") - .given(uploadInitCode(contract), contractCreate(contract).omitAdminKey()) - .when( - contractCall(contract, CREATE).memo(longMemo).hasPrecheck(MEMO_TOO_LONG), - contractCall(contract, CREATE).memo(ZERO_BYTE_MEMO).hasPrecheck(INVALID_ZERO_BYTE_IN_STRING), - contractCall(contract, CREATE) - .memo(inValidMemoWithMultiByteChars) - .hasPrecheck(MEMO_TOO_LONG), - contractCall(contract, CREATE) - .memo(stringOf49Bytes + SINGLE_BYTE_CHAR + MULTI_BYTE_CHAR + stringOf49Bytes) - .hasPrecheck(MEMO_TOO_LONG)) - .then( - contractCreate(secondary) - .entityMemo(TxnUtils.nAscii(101)) - .hasPrecheck(MEMO_TOO_LONG), - contractCreate(secondary).entityMemo(ZERO_BYTE_MEMO).hasPrecheck(INVALID_ZERO_BYTE_IN_STRING), - contractCreate(secondary) - .entityMemo(inValidMemoWithMultiByteChars) - .hasPrecheck(MEMO_TOO_LONG), - contractCreate(secondary) - .entityMemo(stringOf49Bytes + SINGLE_BYTE_CHAR + MULTI_BYTE_CHAR + stringOf49Bytes) - .hasPrecheck(MEMO_TOO_LONG)); - } - - final HapiSpec tokenOps() { - return defaultHapiSpec("MemoValidationsOnTokenOps") - .given( - cryptoCreate("firstUser"), - newKeyNamed(ADMIN_KEY), - tokenCreate(primary).blankMemo().adminKey(ADMIN_KEY)) - .when( - tokenUpdate(primary) - .memo(longMemo) - .hasPrecheck(MEMO_TOO_LONG) - .signedByPayerAnd(ADMIN_KEY), - tokenUpdate(primary) - .entityMemo(ZERO_BYTE_MEMO) - .hasPrecheck(INVALID_ZERO_BYTE_IN_STRING) - .signedByPayerAnd(ADMIN_KEY), - tokenUpdate(primary) - .entityMemo(inValidMemoWithMultiByteChars) - .signedByPayerAnd(ADMIN_KEY) - .hasPrecheck(MEMO_TOO_LONG), - tokenUpdate(primary) - .entityMemo(stringOf49Bytes + MULTI_BYTE_CHAR + stringOf49Bytes) - .signedByPayerAnd(ADMIN_KEY), - tokenUpdate(primary) - .entityMemo(stringOf49Bytes + SINGLE_BYTE_CHAR + MULTI_BYTE_CHAR + stringOf49Bytes) - .signedByPayerAnd(ADMIN_KEY) - .hasPrecheck(MEMO_TOO_LONG)) - .then( - tokenCreate(secondary).entityMemo(longMemo).hasPrecheck(MEMO_TOO_LONG), - tokenCreate(secondary).entityMemo(ZERO_BYTE_MEMO).hasPrecheck(INVALID_ZERO_BYTE_IN_STRING), - tokenCreate(IN_VALID_MEMO_WITH_MULTI_BYTE_CHARS) - .entityMemo(inValidMemoWithMultiByteChars) - .hasPrecheck(MEMO_TOO_LONG), - tokenCreate(IN_VALID_MEMO_WITH_MULTI_BYTE_CHARS) - .entityMemo(stringOf49Bytes + SINGLE_BYTE_CHAR + MULTI_BYTE_CHAR + stringOf49Bytes) - .hasPrecheck(MEMO_TOO_LONG), - tokenCreate(secondary).entityMemo(stringOf49Bytes + MULTI_BYTE_CHAR + stringOf49Bytes), - tokenAssociate("firstUser", primary) - .memo(inValidMemoWithMultiByteChars) - .hasPrecheck(MEMO_TOO_LONG)); - } - - final HapiSpec scheduleOps() { - final String defaultWhitelist = HapiSpecSetup.getDefaultNodeProps().get(SCHEDULING_WHITELIST); - final var toScheduleOp1 = cryptoCreate("test"); - final var toScheduleOp2 = cryptoCreate("test").balance(1L); - return defaultHapiSpec("MemoValidationsOnScheduleOps") - .given( - overriding(SCHEDULING_WHITELIST, "CryptoCreate"), - scheduleCreate(primary, toScheduleOp1).blankMemo()) - .when( - scheduleSign(primary).memo(longMemo).hasPrecheck(MEMO_TOO_LONG), - scheduleSign(primary).memo(ZERO_BYTE_MEMO).hasPrecheck(INVALID_ZERO_BYTE_IN_STRING), - scheduleSign(primary) - .memo(inValidMemoWithMultiByteChars) - .hasPrecheck(MEMO_TOO_LONG), - scheduleSign(primary) - .memo(stringOf49Bytes + SINGLE_BYTE_CHAR + MULTI_BYTE_CHAR + stringOf49Bytes) - .hasPrecheck(MEMO_TOO_LONG)) - .then( - scheduleCreate(secondary, toScheduleOp2) - .withEntityMemo(longMemo) - .hasPrecheck(MEMO_TOO_LONG), - scheduleCreate(secondary, toScheduleOp2) - .withEntityMemo(ZERO_BYTE_MEMO) - .hasPrecheck(INVALID_ZERO_BYTE_IN_STRING), - scheduleCreate(IN_VALID_MEMO_WITH_MULTI_BYTE_CHARS, toScheduleOp2) - .withEntityMemo(inValidMemoWithMultiByteChars) - .hasPrecheck(MEMO_TOO_LONG), - scheduleCreate(secondary, toScheduleOp2).withEntityMemo(validMemoWithMultiByteChars), - scheduleCreate("validMemo1", toScheduleOp1.balance(100L)) - .withEntityMemo(stringOf49Bytes + MULTI_BYTE_CHAR + stringOf49Bytes), - scheduleCreate("validMemo2", toScheduleOp2.entityMemo(validMemoWithMultiByteChars)) - .withEntityMemo( - stringOf49Bytes + SINGLE_BYTE_CHAR + SINGLE_BYTE_CHAR + stringOf49Bytes), - scheduleCreate("invalidMemo", toScheduleOp2.balance(200L)) - .withEntityMemo(stringOf49Bytes + SINGLE_BYTE_CHAR + MULTI_BYTE_CHAR + stringOf49Bytes) - .hasPrecheck(MEMO_TOO_LONG), - overriding(SCHEDULING_WHITELIST, defaultWhitelist)); - } - - final HapiSpec topicOps() { - return defaultHapiSpec("MemoValidationsOnTopicOps") - .given( - newKeyNamed(ADMIN_KEY), - createTopic(primary).adminKeyName(ADMIN_KEY).blankMemo()) - .when( - updateTopic(primary).topicMemo(longMemo).hasKnownStatus(MEMO_TOO_LONG), - updateTopic(primary).topicMemo(ZERO_BYTE_MEMO).hasKnownStatus(INVALID_ZERO_BYTE_IN_STRING), - updateTopic(primary) - .topicMemo(inValidMemoWithMultiByteChars) - .hasKnownStatus(MEMO_TOO_LONG), - updateTopic(primary).topicMemo(stringOf49Bytes + MULTI_BYTE_CHAR + stringOf49Bytes), - updateTopic(primary) - .topicMemo(stringOf49Bytes + SINGLE_BYTE_CHAR + MULTI_BYTE_CHAR + stringOf49Bytes) - .hasKnownStatus(MEMO_TOO_LONG)) - .then( - createTopic(secondary).topicMemo(longMemo).hasKnownStatus(MEMO_TOO_LONG), - createTopic(secondary).topicMemo(ZERO_BYTE_MEMO).hasKnownStatus(INVALID_ZERO_BYTE_IN_STRING), - createTopic(secondary).topicMemo(validMemoWithMultiByteChars), - createTopic(IN_VALID_MEMO_WITH_MULTI_BYTE_CHARS) - .topicMemo(inValidMemoWithMultiByteChars) - .hasKnownStatus(MEMO_TOO_LONG), - createTopic("validMemo1").topicMemo(stringOf49Bytes + MULTI_BYTE_CHAR + stringOf49Bytes), - createTopic("validMemo2") - .topicMemo(stringOf49Bytes + SINGLE_BYTE_CHAR + SINGLE_BYTE_CHAR + stringOf49Bytes), - createTopic("invalidMemo") - .topicMemo(stringOf49Bytes + SINGLE_BYTE_CHAR + MULTI_BYTE_CHAR + stringOf49Bytes) - .hasKnownStatus(MEMO_TOO_LONG)); - } - - final HapiSpec cryptoOps() { - return defaultHapiSpec("MemoValidationsOnCryptoOps") - .given(cryptoCreate(primary).blankMemo()) - .when( - cryptoUpdate(primary).entityMemo(longMemo).hasPrecheck(MEMO_TOO_LONG), - cryptoUpdate(primary).entityMemo(ZERO_BYTE_MEMO).hasPrecheck(INVALID_ZERO_BYTE_IN_STRING), - cryptoUpdate(primary) - .entityMemo(inValidMemoWithMultiByteChars) - .hasPrecheck(MEMO_TOO_LONG), - cryptoUpdate(primary).entityMemo(stringOf49Bytes + MULTI_BYTE_CHAR + stringOf49Bytes), - cryptoUpdate(primary) - .entityMemo(stringOf49Bytes + SINGLE_BYTE_CHAR + MULTI_BYTE_CHAR + stringOf49Bytes) - .hasPrecheck(MEMO_TOO_LONG), - cryptoTransfer(tinyBarsFromTo(DEFAULT_PAYER, primary, 1000L)) - .memo(stringOf49Bytes + SINGLE_BYTE_CHAR + MULTI_BYTE_CHAR + stringOf49Bytes) - .hasPrecheck(MEMO_TOO_LONG)) - .then( - cryptoCreate(secondary).entityMemo(longMemo).hasPrecheck(MEMO_TOO_LONG), - cryptoCreate(secondary).entityMemo(ZERO_BYTE_MEMO).hasPrecheck(INVALID_ZERO_BYTE_IN_STRING), - cryptoCreate(secondary) - .entityMemo(inValidMemoWithMultiByteChars) - .hasPrecheck(MEMO_TOO_LONG), - cryptoCreate(secondary) - .entityMemo(stringOf49Bytes + SINGLE_BYTE_CHAR + MULTI_BYTE_CHAR + stringOf49Bytes) - .hasPrecheck(MEMO_TOO_LONG), - cryptoCreate(secondary).entityMemo(stringOf49Bytes + MULTI_BYTE_CHAR + stringOf49Bytes)); - } - - private void setUpByteArrays() { - final var LONG_BYTES = new byte[1000]; - final var VALID_BYTES = new byte[100]; - final var INVALID_BYTES = new byte[102]; - final var BYTES_49 = new byte[49]; - - Arrays.fill(LONG_BYTES, (byte) 33); - Arrays.fill(VALID_BYTES, (byte) MULTI_BYTE_CHAR); - Arrays.fill(INVALID_BYTES, (byte) MULTI_BYTE_CHAR); - Arrays.fill(BYTES_49, (byte) SINGLE_BYTE_CHAR); - longMemo = new String(LONG_BYTES, StandardCharsets.UTF_8); - validMemoWithMultiByteChars = new String(VALID_BYTES, StandardCharsets.UTF_8); - inValidMemoWithMultiByteChars = new String(INVALID_BYTES, StandardCharsets.UTF_8); - stringOf49Bytes = new String(BYTES_49, StandardCharsets.UTF_8); - } -} diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/misc/MixedOpsTransactionsSuite.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/misc/MixedOpsTransactionsSuite.java deleted file mode 100644 index b5e18e6380c6..000000000000 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/misc/MixedOpsTransactionsSuite.java +++ /dev/null @@ -1,106 +0,0 @@ -/* - * Copyright (C) 2020-2024 Hedera Hashgraph, LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.hedera.services.bdd.suites.misc; - -import static com.hedera.services.bdd.spec.queries.QueryVerbs.getAccountBalance; -import static com.hedera.services.bdd.spec.transactions.TxnVerbs.createTopic; -import static com.hedera.services.bdd.spec.transactions.TxnVerbs.cryptoCreate; -import static com.hedera.services.bdd.spec.transactions.TxnVerbs.cryptoTransfer; -import static com.hedera.services.bdd.spec.transactions.TxnVerbs.fileUpdate; -import static com.hedera.services.bdd.spec.transactions.TxnVerbs.scheduleCreate; -import static com.hedera.services.bdd.spec.transactions.TxnVerbs.scheduleSign; -import static com.hedera.services.bdd.spec.transactions.TxnVerbs.tokenAssociate; -import static com.hedera.services.bdd.spec.transactions.TxnVerbs.tokenCreate; -import static com.hedera.services.bdd.spec.transactions.crypto.HapiCryptoTransfer.tinyBarsFromTo; -import static com.hedera.services.bdd.spec.utilops.UtilVerbs.freezeOnly; -import static com.hedera.services.bdd.spec.utilops.UtilVerbs.sleepFor; -import static com.hedera.services.bdd.suites.perf.PerfUtilOps.tokenOpsEnablement; - -import com.hedera.services.bdd.spec.HapiSpec; -import com.hedera.services.bdd.spec.HapiSpecOperation; -import com.hedera.services.bdd.suites.HapiSuite; -import com.hedera.services.bdd.suites.perf.PerfUtilOps; -import java.util.List; -import java.util.Map; -import java.util.stream.IntStream; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; - -public class MixedOpsTransactionsSuite extends HapiSuite { - private static final Logger log = LogManager.getLogger(MixedOpsTransactionsSuite.class); - private static final String SENDER = "sender"; - - public static void main(String... args) { - new MixedOpsTransactionsSuite().runSuiteSync(); - } - - @Override - public List getSpecsInSuite() { - return List.of(new HapiSpec[] {createStateWithMixedOps() - // triggerSavedScheduleTxn(), - }); - } - - final HapiSpec triggerSavedScheduleTxn() { - return HapiSpec.defaultHapiSpec("triggerSavedScheduleTxn") - .given(getAccountBalance("0.0.1002").hasTinyBars(0L)) - .when(scheduleSign("0.0.1016").logged().alsoSigningWith(GENESIS)) - .then(getAccountBalance("0.0.1002").hasTinyBars(1L)); - } - // Used to generate state with mixed operations - final HapiSpec createStateWithMixedOps() { - long ONE_YEAR_IN_SECS = 365 * 24 * 60 * 60; - int numScheduledTxns = 10; - return HapiSpec.defaultHapiSpec("createStateWithMixedOps") - .given( - PerfUtilOps.scheduleOpsEnablement(), - tokenOpsEnablement(), - fileUpdate(APP_PROPERTIES) - .payingWith(GENESIS) - .overridingProps(Map.of("ledger.schedule.txExpiryTimeSecs", "" + ONE_YEAR_IN_SECS)), - sleepFor(10000), - cryptoCreate(SENDER).advertisingCreation().balance(ONE_HBAR), - cryptoCreate("receiver") - .key(GENESIS) - .advertisingCreation() - .balance(0L) - .receiverSigRequired(true), - cryptoCreate("tokenTreasury") - .key(GENESIS) - .advertisingCreation() - .balance(ONE_HBAR), - tokenCreate("wellKnown").advertisingCreation().initialSupply(Long.MAX_VALUE), - cryptoCreate("tokenReceiver").advertisingCreation(), - tokenAssociate("tokenReceiver", "wellKnown"), - createTopic("wellKnownTopic").advertisingCreation()) - .when(IntStream.range(0, numScheduledTxns) - .mapToObj(i -> scheduleCreate( - "schedule" + i, cryptoTransfer(tinyBarsFromTo(SENDER, "receiver", 1))) - .advertisingCreation() - .fee(ONE_HUNDRED_HBARS) - .signedBy(DEFAULT_PAYER) - .alsoSigningWith(SENDER) - .withEntityMemo("This is the " + i + "th scheduled txn.")) - .toArray(HapiSpecOperation[]::new)) - .then(freezeOnly().payingWith(GENESIS).startingIn(60).seconds()); - } - - @Override - protected Logger getResultsLogger() { - return log; - } -} diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/misc/OneOfEveryTransaction.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/misc/OneOfEveryTransaction.java deleted file mode 100644 index f97eef837d9e..000000000000 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/misc/OneOfEveryTransaction.java +++ /dev/null @@ -1,149 +0,0 @@ -/* - * Copyright (C) 2020-2024 Hedera Hashgraph, LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.hedera.services.bdd.suites.misc; - -import static com.hedera.services.bdd.spec.HapiSpec.defaultHapiSpec; -import static com.hedera.services.bdd.spec.keys.SigControl.OFF; -import static com.hedera.services.bdd.spec.keys.SigControl.ON; -import static com.hedera.services.bdd.spec.queries.QueryVerbs.contractCallLocal; -import static com.hedera.services.bdd.spec.queries.QueryVerbs.getAccountBalance; -import static com.hedera.services.bdd.spec.queries.QueryVerbs.getFileInfo; -import static com.hedera.services.bdd.spec.transactions.TxnVerbs.contractCall; -import static com.hedera.services.bdd.spec.transactions.TxnVerbs.contractCreate; -import static com.hedera.services.bdd.spec.transactions.TxnVerbs.contractDelete; -import static com.hedera.services.bdd.spec.transactions.TxnVerbs.contractUpdate; -import static com.hedera.services.bdd.spec.transactions.TxnVerbs.createTopic; -import static com.hedera.services.bdd.spec.transactions.TxnVerbs.cryptoCreate; -import static com.hedera.services.bdd.spec.transactions.TxnVerbs.cryptoDelete; -import static com.hedera.services.bdd.spec.transactions.TxnVerbs.cryptoTransfer; -import static com.hedera.services.bdd.spec.transactions.TxnVerbs.cryptoUpdate; -import static com.hedera.services.bdd.spec.transactions.TxnVerbs.deleteTopic; -import static com.hedera.services.bdd.spec.transactions.TxnVerbs.fileAppend; -import static com.hedera.services.bdd.spec.transactions.TxnVerbs.fileCreate; -import static com.hedera.services.bdd.spec.transactions.TxnVerbs.fileDelete; -import static com.hedera.services.bdd.spec.transactions.TxnVerbs.fileUpdate; -import static com.hedera.services.bdd.spec.transactions.TxnVerbs.submitMessageTo; -import static com.hedera.services.bdd.spec.transactions.TxnVerbs.systemFileDelete; -import static com.hedera.services.bdd.spec.transactions.TxnVerbs.systemFileUndelete; -import static com.hedera.services.bdd.spec.transactions.TxnVerbs.updateTopic; -import static com.hedera.services.bdd.spec.transactions.TxnVerbs.uploadInitCode; -import static com.hedera.services.bdd.spec.transactions.crypto.HapiCryptoTransfer.tinyBarsFromTo; -import static com.hedera.services.bdd.spec.utilops.UtilVerbs.newKeyNamed; - -import com.hedera.services.bdd.spec.HapiSpec; -import com.hedera.services.bdd.spec.keys.ControlForKey; -import com.hedera.services.bdd.spec.keys.KeyShape; -import com.hedera.services.bdd.suites.HapiSuite; -import java.time.Instant; -import java.util.List; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; - -public class OneOfEveryTransaction extends HapiSuite { - private static final Logger log = LogManager.getLogger(OneOfEveryTransaction.class); - - public static void main(String... args) throws Exception { - new OneOfEveryTransaction().runSuiteSync(); - } - - @Override - public List getSpecsInSuite() { - return List.of(new HapiSpec[] { - doThings(), - }); - } - - final HapiSpec doThings() { - /* Crypto signing */ - var complex = KeyShape.threshOf(1, KeyShape.listOf(3), KeyShape.threshOf(1, 3)); - /* File signing */ - var complexWacl = KeyShape.listOf(KeyShape.threshOf(2, 4), KeyShape.threshOf(1, 3)); - var secondComplexWacl = KeyShape.listOf(4); - var revocationDeleteSigs = secondComplexWacl.signedWith(KeyShape.sigs(ON, OFF, OFF, OFF)); - /* Topic signing */ - var complexAdmin = KeyShape.threshOf(1, KeyShape.listOf(2), KeyShape.threshOf(1, 3)); - /* Contract signing */ - var complexContract = KeyShape.listOf(KeyShape.threshOf(2, 3), KeyShape.threshOf(1, 3)); - - return defaultHapiSpec("DoThings") - .given( - /* Crypto resources */ - newKeyNamed("firstKey").shape(complex), - newKeyNamed("secondKey"), - /* File resources */ - newKeyNamed("fileFirstKey").shape(complexWacl), - newKeyNamed("fileSecondKey").shape(secondComplexWacl), - /* Topic resources */ - newKeyNamed("topicKey").shape(complexAdmin), - /* Contract resources */ - newKeyNamed("contractFirstKey").shape(complexContract), - newKeyNamed("contractSecondKey"), - uploadInitCode("Multipurpose"), - /* Network resources */ - fileCreate("misc").lifetime(2_000_000)) - .when( - /* Crypto txns */ - cryptoCreate("tbd") - .receiveThreshold(1_000L) - .balance(1_234L) - .key("firstKey"), - getAccountBalance("tbd").logged(), - cryptoUpdate("tbd").key("secondKey"), - cryptoTransfer(tinyBarsFromTo(GENESIS, FUNDING, 1_234L)), - cryptoDelete("tbd").via("deleteTxn").transfer(GENESIS), - /* File txns */ - fileCreate("fileTbd").key("fileFirstKey").contents("abcdefghijklm"), - fileAppend("fileTbd").content("nopqrstuvwxyz"), - fileUpdate("fileTbd").wacl("fileSecondKey"), - getFileInfo("fileTbd"), - fileDelete("fileTbd").sigControl(ControlForKey.forKey("fileTbd", revocationDeleteSigs)), - /* Consensus txns */ - createTopic("topicTbd") - .memo("'Twas brillig, and the slithy toves...") - .adminKeyName("topicKey") - .submitKeyShape(KeyShape.SIMPLE), - submitMessageTo("topicTbd"), - updateTopic("topicTbd").signedBy(GENESIS, "topicKey").submitKey(EMPTY_KEY), - submitMessageTo("topicTbd").signedBy(GENESIS), - deleteTopic("topicTbd"), - /* Contract txns */ - uploadInitCode("Multipurpose"), - contractCreate("Multipurpose") - .adminKey("contractFirstKey") - .balance(1), - contractCall("Multipurpose").sending(1L), - contractCallLocal("Multipurpose", "pick"), - contractUpdate("Multipurpose").newKey("contractSecondKey"), - contractDelete("Multipurpose").transferAccount(GENESIS), - /* Network txns */ - systemFileDelete("misc") - .payingWith(SYSTEM_DELETE_ADMIN) - .fee(0L) - .updatingExpiry(Instant.now().getEpochSecond() + 1_000_000), - systemFileUndelete("misc") - .payingWith(SYSTEM_UNDELETE_ADMIN) - .fee(0L)) - .then( - /* Nothing fails. */ - ); - } - - @Override - protected Logger getResultsLogger() { - return log; - } -} diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/misc/PerpetualTransfers.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/misc/PerpetualTransfers.java index 4c6001d888d0..44decca4c7bf 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/misc/PerpetualTransfers.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/misc/PerpetualTransfers.java @@ -16,6 +16,7 @@ package com.hedera.services.bdd.suites.misc; +import static com.hedera.services.bdd.spec.HapiSpec.defaultHapiSpec; import static com.hedera.services.bdd.spec.transactions.TxnVerbs.cryptoCreate; import static com.hedera.services.bdd.spec.transactions.TxnVerbs.cryptoTransfer; import static com.hedera.services.bdd.spec.transactions.crypto.HapiCryptoTransfer.tinyBarsFromTo; @@ -23,11 +24,9 @@ import static java.util.concurrent.TimeUnit.SECONDS; import com.hedera.services.bdd.junit.HapiTest; -import com.hedera.services.bdd.junit.HapiTestSuite; import com.hedera.services.bdd.spec.HapiSpec; import com.hedera.services.bdd.spec.HapiSpecOperation; import com.hedera.services.bdd.spec.infrastructure.OpProvider; -import com.hedera.services.bdd.suites.HapiSuite; import java.util.List; import java.util.Optional; import java.util.concurrent.TimeUnit; @@ -36,31 +35,17 @@ import java.util.concurrent.atomic.AtomicLong; import java.util.concurrent.atomic.AtomicReference; import java.util.function.Function; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; +import java.util.stream.Stream; +import org.junit.jupiter.api.DynamicTest; -@HapiTestSuite -public class PerpetualTransfers extends HapiSuite { - private static final Logger log = LogManager.getLogger(PerpetualTransfers.class); - - private AtomicLong duration = new AtomicLong(15); +public class PerpetualTransfers { + private AtomicLong duration = new AtomicLong(1); private AtomicReference unit = new AtomicReference<>(SECONDS); private AtomicInteger maxOpsPerSec = new AtomicInteger(10); - public static void main(String... args) { - new PerpetualTransfers().runSuiteSync(); - } - - @Override - public List getSpecsInSuite() { - return List.of(new HapiSpec[] { - canTransferBackAndForthForever(), - }); - } - @HapiTest - final HapiSpec canTransferBackAndForthForever() { - return HapiSpec.defaultHapiSpec("CanTransferBackAndForthForever") + final Stream canTransferBackAndForthForever() { + return defaultHapiSpec("CanTransferBackAndForthForever") .given() .when() .then(runWithProvider(transfersFactory()) @@ -88,9 +73,4 @@ public Optional get() { } }; } - - @Override - protected Logger getResultsLogger() { - return log; - } } diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/misc/PersistenceDevSuite.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/misc/PersistenceDevSuite.java deleted file mode 100644 index 1d478f6e5744..000000000000 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/misc/PersistenceDevSuite.java +++ /dev/null @@ -1,60 +0,0 @@ -/* - * Copyright (C) 2020-2024 Hedera Hashgraph, LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.hedera.services.bdd.suites.misc; - -import static com.hedera.services.bdd.spec.HapiSpec.customHapiSpec; -import static com.hedera.services.bdd.spec.queries.QueryVerbs.getAccountInfo; -import static com.hedera.services.bdd.spec.queries.QueryVerbs.getTokenInfo; -import static com.hedera.services.bdd.spec.queries.QueryVerbs.getTopicInfo; - -import com.hedera.services.bdd.spec.HapiSpec; -import com.hedera.services.bdd.suites.HapiSuite; -import java.util.List; -import java.util.Map; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; - -public class PersistenceDevSuite extends HapiSuite { - private static final Logger log = LogManager.getLogger(PersistenceDevSuite.class); - - public static void main(String... args) throws Exception { - new PersistenceDevSuite().runSuiteSync(); - } - - @Override - public List getSpecsInSuite() { - return List.of(new HapiSpec[] { - testEntityLoading(), - }); - } - - final HapiSpec testEntityLoading() { - return customHapiSpec("TestEntityLoading") - .withProperties(Map.of("persistentEntities.dir.path", "persistent-entities/")) - .given( - getTokenInfo("knownToken").logged(), - getTopicInfo("knownTopic").logged(), - getAccountInfo("knownAccount").logged()) - .when() - .then(); - } - - @Override - protected Logger getResultsLogger() { - return log; - } -} diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/misc/R5BugChecks.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/misc/R5BugChecks.java deleted file mode 100644 index 38b4dd287b3e..000000000000 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/misc/R5BugChecks.java +++ /dev/null @@ -1,270 +0,0 @@ -/* - * Copyright (C) 2020-2024 Hedera Hashgraph, LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.hedera.services.bdd.suites.misc; - -import static com.hedera.services.bdd.spec.HapiSpec.defaultHapiSpec; -import static com.hedera.services.bdd.spec.assertions.AccountInfoAsserts.changeFromSnapshot; -import static com.hedera.services.bdd.spec.assertions.ContractFnResultAsserts.isLiteralResult; -import static com.hedera.services.bdd.spec.assertions.ContractFnResultAsserts.resultWith; -import static com.hedera.services.bdd.spec.keys.KeyShape.SIMPLE; -import static com.hedera.services.bdd.spec.keys.KeyShape.listOf; -import static com.hedera.services.bdd.spec.keys.KeyShape.sigs; -import static com.hedera.services.bdd.spec.keys.KeyShape.threshOf; -import static com.hedera.services.bdd.spec.keys.SigControl.OFF; -import static com.hedera.services.bdd.spec.keys.SigControl.ON; -import static com.hedera.services.bdd.spec.queries.QueryVerbs.contractCallLocal; -import static com.hedera.services.bdd.spec.queries.QueryVerbs.getAccountBalance; -import static com.hedera.services.bdd.spec.queries.QueryVerbs.getAccountInfo; -import static com.hedera.services.bdd.spec.queries.QueryVerbs.getFileContents; -import static com.hedera.services.bdd.spec.queries.QueryVerbs.getFileInfo; -import static com.hedera.services.bdd.spec.transactions.TxnVerbs.contractCall; -import static com.hedera.services.bdd.spec.transactions.TxnVerbs.contractCreate; -import static com.hedera.services.bdd.spec.transactions.TxnVerbs.cryptoCreate; -import static com.hedera.services.bdd.spec.transactions.TxnVerbs.cryptoDelete; -import static com.hedera.services.bdd.spec.transactions.TxnVerbs.cryptoTransfer; -import static com.hedera.services.bdd.spec.transactions.TxnVerbs.fileAppend; -import static com.hedera.services.bdd.spec.transactions.TxnVerbs.fileCreate; -import static com.hedera.services.bdd.spec.transactions.TxnVerbs.fileDelete; -import static com.hedera.services.bdd.spec.transactions.TxnVerbs.fileUpdate; -import static com.hedera.services.bdd.spec.transactions.TxnVerbs.uploadInitCode; -import static com.hedera.services.bdd.spec.transactions.crypto.HapiCryptoTransfer.tinyBarsFromTo; -import static com.hedera.services.bdd.spec.utilops.CustomSpecAssert.allRunFor; -import static com.hedera.services.bdd.spec.utilops.UtilVerbs.balanceSnapshot; -import static com.hedera.services.bdd.spec.utilops.UtilVerbs.freezeOnly; -import static com.hedera.services.bdd.spec.utilops.UtilVerbs.newKeyNamed; -import static com.hedera.services.bdd.spec.utilops.UtilVerbs.withOpContext; -import static com.hedera.services.bdd.suites.contract.Utils.FunctionType.FUNCTION; -import static com.hedera.services.bdd.suites.contract.Utils.getABIFor; -import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.BAD_ENCODING; -import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.FEE_SCHEDULE_FILE_PART_UPLOADED; -import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.INSUFFICIENT_PAYER_BALANCE; -import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.INVALID_ACCOUNT_ID; -import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.INVALID_SIGNATURE; -import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.INVALID_SOLIDITY_ADDRESS; -import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.OK; -import static java.util.stream.Collectors.toList; - -import com.google.protobuf.ByteString; -import com.hedera.services.bdd.spec.HapiPropertySource; -import com.hedera.services.bdd.spec.HapiSpec; -import com.hedera.services.bdd.spec.keys.ControlForKey; -import com.hedera.services.bdd.spec.keys.KeyShape; -import com.hedera.services.bdd.spec.keys.SigControl; -import com.hedera.services.bdd.suites.HapiSuite; -import com.hederahashgraph.api.proto.java.TransferList; -import java.math.BigInteger; -import java.util.List; -import java.util.concurrent.atomic.AtomicReference; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; - -public class R5BugChecks extends HapiSuite { - private static final Logger log = LogManager.getLogger(R5BugChecks.class); - private static final String UNCHECKED_TRANSFER = "uncheckedTransfer"; - private static final String SKETCHY = "sketchy"; - private static final String MULTIPURPOSE = "Multipurpose"; - private static final String HOW_MUCH = "howMuch"; - - public static void main(String... args) { - new R5BugChecks().runSuiteSync(); - } - - @Override - public List getSpecsInSuite() { - return List.of(new HapiSpec[] { - // genesisUpdatesFeesForFree(), - // canGetDeletedFileInfo(), - enforcesSigRequirements(), - // contractCannotTransferToReceiverSigRequired(), - // cannotTransferEntirePayerBalance(), - // costAnswerGetAccountInfoRejectsInvalidId(), - // cannotUseThresholdWithM0(), - - /* --- MISC --- */ - // cannotTransferToDeleted(), - }); - } - - final HapiSpec cannotUseThresholdWithM0() { - KeyShape invalid = listOf(SIMPLE, SIMPLE, threshOf(0, 3)); - - return defaultHapiSpec("CannotUseThresholdWithM0") - .given() - .when() - .then(cryptoCreate(SKETCHY).keyShape(invalid).hasPrecheck(BAD_ENCODING)); - } - - final HapiSpec cannotTransferEntirePayerBalance() { - var balance = 1_234_567L; - return defaultHapiSpec("CannotTransferEntirePayerBalance") - .given(cryptoCreate(SKETCHY).balance(balance)) - .when() - .then(cryptoTransfer(tinyBarsFromTo(SKETCHY, FUNDING, balance)) - .payingWith(SKETCHY) - .hasPrecheck(INSUFFICIENT_PAYER_BALANCE)); - } - - final HapiSpec canGetDeletedFileInfo() { - return defaultHapiSpec("CanGetDeletedFileInfo") - .given(fileCreate("tbd")) - .when(fileDelete("tbd")) - .then(getFileInfo("tbd").hasCostAnswerPrecheck(OK).hasAnswerOnlyPrecheck(OK)); - } - - final HapiSpec costAnswerGetAccountInfoRejectsInvalidId() { - return defaultHapiSpec("CostAnswerGetAccountInfoRejectsInvalidId") - .given() - .when() - .then(getAccountInfo("1.2.3").hasCostAnswerPrecheck(INVALID_ACCOUNT_ID)); - } - - final HapiSpec contractCannotTransferToReceiverSigRequired() { - return defaultHapiSpec("ContractCannotTransferToReceiverSigRequired") - .given( - uploadInitCode(MULTIPURPOSE), - contractCreate(MULTIPURPOSE).balance(1)) - .when(cryptoCreate("sr").receiverSigRequired(true)) - .then(contractCall(MULTIPURPOSE, "donate", spec -> new Object[] { - (int) spec.registry().getAccountID("sr").getAccountNum(), "Hey, Ma!" - }) - .hasKnownStatus(INVALID_SIGNATURE)); - } - - final HapiSpec enforcesSigRequirements() { - final var contract = "LastTrackingSender"; - KeyShape complexSrShape = listOf(SIMPLE, threshOf(1, 3)); - SigControl activeSig = complexSrShape.signedWith(sigs(ON, sigs(OFF, OFF, ON))); - SigControl inactiveSig = complexSrShape.signedWith(sigs(OFF, sigs(ON, ON, ON))); - - return defaultHapiSpec("EnforcesSigRequirements") - .given( - newKeyNamed("srKey").shape(complexSrShape), - uploadInitCode(contract), - contractCreate(contract).balance(10), - cryptoCreate("noSr").balance(0L), - cryptoCreate("sr").key("srKey").balance(0L).receiverSigRequired(true)) - .when( - contractCall(contract, UNCHECKED_TRANSFER, spec -> new Object[] { - (int) spec.registry().getAccountID("sr").getAccountNum(), 5 - }) - .hasKnownStatus(INVALID_SIGNATURE), - contractCall(contract, UNCHECKED_TRANSFER, spec -> new Object[] { - (int) spec.registry().getAccountID("sr").getAccountNum(), 5 - }) - .signedBy(GENESIS, "sr") - .sigControl(ControlForKey.forKey("sr", inactiveSig)) - .hasKnownStatus(INVALID_SIGNATURE), - contractCallLocal(contract, HOW_MUCH) - .has(resultWith() - .resultThruAbi( - getABIFor(FUNCTION, HOW_MUCH, contract), - isLiteralResult(new Object[] {BigInteger.valueOf(0)}))), - getAccountBalance("sr").hasTinyBars(0L)) - .then( - contractCall(contract, UNCHECKED_TRANSFER, spec -> new Object[] { - (int) spec.registry().getAccountID("noSr").getAccountNum(), 1 - }), - contractCall(contract, UNCHECKED_TRANSFER, spec -> new Object[] { - (int) spec.registry().getAccountID("sr").getAccountNum(), 5 - }) - .signedBy(GENESIS, "sr") - .sigControl(ControlForKey.forKey("sr", activeSig)), - contractCallLocal(contract, HOW_MUCH) - .has(resultWith() - .resultThruAbi( - getABIFor(FUNCTION, HOW_MUCH, contract), - isLiteralResult(new Object[] {BigInteger.valueOf(5)}))), - getAccountBalance("sr").hasTinyBars(5L), - getAccountBalance("noSr").hasTinyBars(1L)); - } - - final HapiSpec cannotTransferToDeleted() { - final var contract = "LastTrackingSender"; - return defaultHapiSpec("CannotTransferToDeleted") - .given( - cryptoCreate("tbd"), - uploadInitCode(contract), - contractCreate(contract).balance(10)) - .when( - contractCall(contract, UNCHECKED_TRANSFER, spec -> new Object[] { - (int) spec.registry().getAccountID("tbd").getAccountNum(), 1 - }), - cryptoDelete("tbd")) - .then(contractCall(contract, UNCHECKED_TRANSFER, spec -> new Object[] { - (int) spec.registry().getAccountID("tbd").getAccountNum(), 2 - }) - .hasKnownStatus(INVALID_SOLIDITY_ADDRESS)); - } - - final HapiSpec genesisUpdatesFeesForFree() { - AtomicReference schedulePart1 = new AtomicReference<>(); - AtomicReference schedulePart2 = new AtomicReference<>(); - - return defaultHapiSpec("GenesisUpdatesFeesForFree") - .given( - withOpContext((spec, opLog) -> { - var lookup = getFileContents(FEE_SCHEDULE); - allRunFor(spec, lookup); - var contents = lookup.getResponse() - .getFileGetContents() - .getFileContents() - .getContents(); - var bytes = contents.toByteArray(); - var n = bytes.length; - schedulePart1.set(ByteString.copyFrom(bytes, 0, 4096)); - schedulePart2.set(ByteString.copyFrom(bytes, 4096, n - 4096)); - }), - cryptoCreate("payer")) - .when( - balanceSnapshot("preUpdate", GENESIS), - fileUpdate(FEE_SCHEDULE) - .payingWith(FEE_SCHEDULE_CONTROL) - .fee(5_000_000_000L) - .contents(ignore -> schedulePart1.get()) - .hasKnownStatus(FEE_SCHEDULE_FILE_PART_UPLOADED), - fileAppend(FEE_SCHEDULE) - .fee(5_000_000_000L) - .contentFrom(() -> schedulePart2.get().toByteArray())) - .then(getAccountBalance(GENESIS).hasTinyBars(changeFromSnapshot("preUpdate", 0))); - } - - /* Run from clean local environment to test need for state migration vis-a-vis JContractFunctionResult. */ - final HapiSpec genRecordWithCreations() { - final var contract = "Fuse"; - return defaultHapiSpec("CreateRecordViaExpensiveSubmit") - .given(uploadInitCode(contract), contractCreate(contract)) - .when(contractCreate("fuse").bytecode("bytecode")) - .then(freezeOnly().startingIn(1).minutes()); - } - - public static String readableTransferList(TransferList accountAmounts) { - return accountAmounts.getAccountAmountsList().stream() - .map(aa -> String.format( - "%s %s %s%s", - HapiPropertySource.asAccountString(aa.getAccountID()), - aa.getAmount() < 0 ? "->" : "<-", - aa.getAmount() < 0 ? "-" : "+", - BigInteger.valueOf(aa.getAmount()).abs().toString())) - .collect(toList()) - .toString(); - } - - @Override - protected Logger getResultsLogger() { - return log; - } -} diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/misc/RekeySavedStateTreasury.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/misc/RekeySavedStateTreasury.java deleted file mode 100644 index 53d98f5777a6..000000000000 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/misc/RekeySavedStateTreasury.java +++ /dev/null @@ -1,85 +0,0 @@ -/* - * Copyright (C) 2020-2024 Hedera Hashgraph, LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.hedera.services.bdd.suites.misc; - -import static com.hedera.services.bdd.spec.HapiSpec.customHapiSpec; -import static com.hedera.services.bdd.spec.transactions.TxnVerbs.cryptoUpdate; -import static com.hedera.services.bdd.spec.utilops.UtilVerbs.keyFromPem; -import static com.hedera.services.bdd.spec.utilops.UtilVerbs.withOpContext; - -import com.hedera.services.bdd.spec.HapiSpec; -import com.hedera.services.bdd.suites.HapiSuite; -import java.util.List; -import java.util.Map; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; - -/** - * Given a state loaded from a preprod network (usually stable testnet), we want to "re-key" the - * treasury account for use in dev migration testing. - */ -public class RekeySavedStateTreasury extends HapiSuite { - private static final Logger log = LogManager.getLogger(RekeySavedStateTreasury.class); - - public static void main(String... args) { - new RekeySavedStateTreasury().runSuiteSync(); - } - - @Override - public List getSpecsInSuite() { - return List.of(new HapiSpec[] { - rekeyTreasury(), - }); - } - - static final String newTreasuryPemLoc = "dev-stabletestnet-account2.pem"; - static final String newTreasuryPassphrase = "passphrase"; - static final String devKeyPemLoc = "devGenesisKeypair.pem"; - - final HapiSpec rekeyTreasury() { - final var pemLocForOriginalTreasuryKey = "stabletestnet-account2.pem"; - final var passphraseForOriginalPemLoc = ""; - - // final var hexedNewEd25519PrivateKey = CommonUtils.hex(randomUtf8Bytes(32)); - final var newTreasuryKey = "newTreasuryKey"; - - return customHapiSpec("RekeyTreasury") - .withProperties(Map.of( - "nodes", - "localhost", - "default.payer", - "0.0.2", - "default.payer.pemKeyLoc", - pemLocForOriginalTreasuryKey, - "default.payer.pemKeyPassphrase", - passphraseForOriginalPemLoc)) - .given( - /* Use this for reusing the pem file. */ - keyFromPem(devKeyPemLoc) - .passphrase(newTreasuryPassphrase) - .name(newTreasuryKey), - // keyFromLiteral(newTreasuryKey, hexedNewEd25519PrivateKey), - withOpContext((spec, opLog) -> spec.keys().exportSimpleKey(newTreasuryPemLoc, newTreasuryKey))) - .when() - .then(cryptoUpdate(DEFAULT_PAYER).key(newTreasuryKey)); - } - - @Override - protected Logger getResultsLogger() { - return log; - } -} diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/misc/UtilVerbChecks.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/misc/UtilVerbChecks.java deleted file mode 100644 index 14fd6b4b214d..000000000000 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/misc/UtilVerbChecks.java +++ /dev/null @@ -1,99 +0,0 @@ -/* - * Copyright (C) 2020-2024 Hedera Hashgraph, LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.hedera.services.bdd.suites.misc; - -import static com.hedera.services.bdd.spec.HapiSpec.defaultHapiSpec; -import static com.hedera.services.bdd.spec.queries.QueryVerbs.getAccountInfo; -import static com.hedera.services.bdd.spec.transactions.TxnVerbs.cryptoCreate; -import static com.hedera.services.bdd.spec.transactions.TxnVerbs.cryptoTransfer; -import static com.hedera.services.bdd.spec.transactions.TxnVerbs.tokenAssociate; -import static com.hedera.services.bdd.spec.transactions.TxnVerbs.tokenCreate; -import static com.hedera.services.bdd.spec.transactions.token.TokenMovement.moving; -import static com.hedera.services.bdd.spec.utilops.UtilVerbs.ensureDissociated; -import static com.hedera.services.bdd.spec.utilops.UtilVerbs.makeFree; -import static com.hedera.services.bdd.spec.utilops.UtilVerbs.withLiveNode; -import static com.hederahashgraph.api.proto.java.HederaFunctionality.CryptoGetInfo; -import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.INSUFFICIENT_TX_FEE; -import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.OK; - -import com.hedera.services.bdd.spec.HapiSpec; -import com.hedera.services.bdd.suites.HapiSuite; -import java.util.List; -import java.util.concurrent.TimeUnit; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; - -public class UtilVerbChecks extends HapiSuite { - private static final Logger log = LogManager.getLogger(UtilVerbChecks.class); - - public static void main(String... args) throws Exception { - new UtilVerbChecks().runSuiteSync(); - } - - @Override - public List getSpecsInSuite() { - return List.of(new HapiSpec[] { - // testLivenessTimeout(), - testMakingFree(), - // testDissociation(), - }); - } - - final HapiSpec testMakingFree() { - return defaultHapiSpec("TestMakingFree") - .given( - cryptoCreate("civilian"), - getAccountInfo("0.0.2") - .payingWith("civilian") - .nodePayment(0L) - .hasAnswerOnlyPrecheck(INSUFFICIENT_TX_FEE)) - .when(makeFree(CryptoGetInfo)) - .then(getAccountInfo("0.0.2") - .payingWith("civilian") - .nodePayment(0L) - .hasAnswerOnlyPrecheck(OK)); - } - - final HapiSpec testDissociation() { - return defaultHapiSpec("TestDissociation") - .given( - cryptoCreate("t"), - tokenCreate("a").treasury("t"), - tokenCreate("b").treasury("t"), - cryptoCreate("somebody"), - tokenAssociate("somebody", "a", "b"), - cryptoTransfer(moving(1, "a").between("t", "somebody")), - cryptoTransfer(moving(2, "b").between("t", "somebody"))) - .when(ensureDissociated("somebody", List.of("a", "b"))) - .then(getAccountInfo("somebody").hasNoTokenRelationship("a").hasNoTokenRelationship("b")); - } - - final HapiSpec testLivenessTimeout() { - return defaultHapiSpec("TestLivenessTimeout") - .given() - .when() - .then(withLiveNode("0.0.3") - .within(300, TimeUnit.SECONDS) - .loggingAvailabilityEvery(30) - .sleepingBetweenRetriesFor(10)); - } - - @Override - protected Logger getResultsLogger() { - return log; - } -} diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/misc/ZeroStakeNodeTest.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/misc/ZeroStakeNodeTest.java deleted file mode 100644 index 21c9863f2256..000000000000 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/misc/ZeroStakeNodeTest.java +++ /dev/null @@ -1,142 +0,0 @@ -/* - * Copyright (C) 2020-2024 Hedera Hashgraph, LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.hedera.services.bdd.suites.misc; - -import static com.hedera.services.bdd.spec.HapiSpec.defaultHapiSpec; -import static com.hedera.services.bdd.spec.assertions.AccountInfoAsserts.changeFromSnapshot; -import static com.hedera.services.bdd.spec.assertions.ContractFnResultAsserts.isLiteralResult; -import static com.hedera.services.bdd.spec.assertions.ContractFnResultAsserts.resultWith; -import static com.hedera.services.bdd.spec.queries.QueryVerbs.contractCallLocal; -import static com.hedera.services.bdd.spec.queries.QueryVerbs.getAccountBalance; -import static com.hedera.services.bdd.spec.queries.QueryVerbs.getAccountInfo; -import static com.hedera.services.bdd.spec.queries.QueryVerbs.getContractBytecode; -import static com.hedera.services.bdd.spec.queries.QueryVerbs.getContractInfo; -import static com.hedera.services.bdd.spec.queries.QueryVerbs.getFileInfo; -import static com.hedera.services.bdd.spec.transactions.TxnVerbs.contractCall; -import static com.hedera.services.bdd.spec.transactions.TxnVerbs.contractCreate; -import static com.hedera.services.bdd.spec.transactions.TxnVerbs.contractDelete; -import static com.hedera.services.bdd.spec.transactions.TxnVerbs.contractUpdate; -import static com.hedera.services.bdd.spec.transactions.TxnVerbs.cryptoCreate; -import static com.hedera.services.bdd.spec.transactions.TxnVerbs.cryptoTransfer; -import static com.hedera.services.bdd.spec.transactions.TxnVerbs.uploadInitCode; -import static com.hedera.services.bdd.spec.transactions.crypto.HapiCryptoTransfer.tinyBarsFromTo; -import static com.hedera.services.bdd.spec.utilops.UtilVerbs.balanceSnapshot; -import static com.hedera.services.bdd.suites.contract.Utils.FunctionType.FUNCTION; -import static com.hedera.services.bdd.suites.contract.Utils.getABIFor; -import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.INSUFFICIENT_TX_FEE; -import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.INVALID_NODE_ACCOUNT; - -import com.hedera.services.bdd.spec.HapiSpec; -import com.hedera.services.bdd.suites.HapiSuite; -import java.math.BigInteger; -import java.util.List; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; - -public class ZeroStakeNodeTest extends HapiSuite { - private static final Logger log = LogManager.getLogger(ZeroStakeNodeTest.class); - - public static void main(String... args) throws Exception { - new ZeroStakeNodeTest().runSuiteSync(); - } - - @Override - public List getSpecsInSuite() { - return List.of(new HapiSpec[] { - zeroStakeBehavesAsExpectedJRS(), - }); - } - - /** - * This test has to be run with nodes in spec-defaults set as the full list of ipAddresses and - * node ids of the network with zero stake nodes. Assumes that node 0.0.7 and node 0.0.8 are - * started with zero stake in a 6 node network. - */ - final HapiSpec zeroStakeBehavesAsExpectedJRS() { - return defaultHapiSpec("zeroStakeBehavesAsExpectedJRS") - .given( - cryptoCreate("sponsor"), - cryptoCreate("beneficiary"), - uploadInitCode("Multipurpose"), - contractCreate("Multipurpose"), - contractCreate("impossible") - .setNode("0.0.7") - .bytecode("bytecode") - .hasPrecheck(INVALID_NODE_ACCOUNT), - contractUpdate("Multipurpose") - .setNode("0.0.8") - .newMemo("Oops!") - .hasPrecheck(INVALID_NODE_ACCOUNT), - contractDelete("Multipurpose").setNode("0.0.7").hasPrecheck(INVALID_NODE_ACCOUNT), - contractCall("Multipurpose") - .setNode("0.0.8") - .sending(1L) - .hasPrecheck(INVALID_NODE_ACCOUNT)) - .when( - balanceSnapshot("sponsorBefore", "sponsor"), - balanceSnapshot("beneficiaryBefore", "beneficiary"), - cryptoTransfer(tinyBarsFromTo("sponsor", "beneficiary", 1L)) - .payingWith(GENESIS) - .memo("Hello World!") - .setNode("0.0.5"), - getContractInfo("Multipurpose") - .setNode("0.0.5") - .payingWith("sponsor") - .nodePayment(0L) - .hasAnswerOnlyPrecheck(INSUFFICIENT_TX_FEE)) - .then( - contractCallLocal("Multipurpose", "pick") - .setNode("0.0.7") - .payingWith("sponsor") - .nodePayment(0L) - .has(resultWith() - .resultThruAbi( - getABIFor(FUNCTION, "pick", "Multipurpose"), - isLiteralResult(new Object[] {BigInteger.valueOf(42)}))), - getContractInfo("Multipurpose") - .setNode("0.0.7") - .payingWith("sponsor") - .nodePayment(0L) - .logged(), - getContractBytecode("Multipurpose") - .setNode("0.0.8") - .payingWith("sponsor") - .nodePayment(0L) - .logged(), - getAccountInfo("beneficiary") - .setNode("0.0.7") - .payingWith("sponsor") - .nodePayment(0L) - .logged(), - getFileInfo("bytecode") - .setNode("0.0.8") - .payingWith("sponsor") - .nodePayment(0L) - .logged(), - getAccountBalance("sponsor") - .setNode("0.0.7") - .hasTinyBars(changeFromSnapshot("sponsorBefore", -1L)), - getAccountBalance("beneficiary") - .setNode("0.0.8") - .hasTinyBars(changeFromSnapshot("beneficiaryBefore", +1L))); - } - - @Override - protected Logger getResultsLogger() { - return log; - } -} diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/perf/AccountBalancesClientSaveLoadTest.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/perf/AccountBalancesClientSaveLoadTest.java deleted file mode 100644 index 04da665013d3..000000000000 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/perf/AccountBalancesClientSaveLoadTest.java +++ /dev/null @@ -1,353 +0,0 @@ -/* - * Copyright (C) 2021-2024 Hedera Hashgraph, LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.hedera.services.bdd.suites.perf; - -import static com.hedera.services.bdd.spec.HapiSpec.defaultHapiSpec; -import static com.hedera.services.bdd.spec.queries.QueryVerbs.getAccountBalance; -import static com.hedera.services.bdd.spec.queries.QueryVerbs.getAccountInfo; -import static com.hedera.services.bdd.spec.transactions.TxnUtils.NOISY_RETRY_PRECHECKS; -import static com.hedera.services.bdd.spec.transactions.TxnVerbs.cryptoCreate; -import static com.hedera.services.bdd.spec.transactions.TxnVerbs.cryptoTransfer; -import static com.hedera.services.bdd.spec.transactions.TxnVerbs.fileUpdate; -import static com.hedera.services.bdd.spec.transactions.TxnVerbs.tokenAssociate; -import static com.hedera.services.bdd.spec.transactions.TxnVerbs.tokenCreate; -import static com.hedera.services.bdd.spec.transactions.token.TokenMovement.moving; -import static com.hedera.services.bdd.spec.utilops.CustomSpecAssert.allRunFor; -import static com.hedera.services.bdd.spec.utilops.UtilVerbs.exportAccountBalances; -import static com.hedera.services.bdd.spec.utilops.UtilVerbs.freezeOnly; -import static com.hedera.services.bdd.spec.utilops.UtilVerbs.logIt; -import static com.hedera.services.bdd.spec.utilops.UtilVerbs.runWithProvider; -import static com.hedera.services.bdd.spec.utilops.UtilVerbs.sleepFor; -import static com.hedera.services.bdd.spec.utilops.UtilVerbs.sourcing; -import static com.hedera.services.bdd.spec.utilops.UtilVerbs.withOpContext; -import static com.hedera.services.bdd.suites.perf.PerfUtilOps.tokenOpsEnablement; -import static com.hedera.services.bdd.suites.utils.sysfiles.serdes.ThrottleDefsLoader.protoDefsFromResource; -import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.ACCOUNT_DELETED; -import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.ACCOUNT_ID_DOES_NOT_EXIST; -import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.ACCOUNT_REPEATED_IN_ACCOUNT_AMOUNTS; -import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.BUSY; -import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.DUPLICATE_TRANSACTION; -import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.INSUFFICIENT_TOKEN_BALANCE; -import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.INVALID_SIGNATURE; -import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.OK; -import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.PLATFORM_TRANSACTION_NOT_CREATED; -import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.SUCCESS; -import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.TOKEN_ALREADY_ASSOCIATED_TO_ACCOUNT; -import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.TOKEN_NOT_ASSOCIATED_TO_ACCOUNT; -import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.TRANSACTION_EXPIRED; - -import com.hedera.services.bdd.spec.HapiPropertySource; -import com.hedera.services.bdd.spec.HapiSpec; -import com.hedera.services.bdd.spec.HapiSpecOperation; -import com.hedera.services.bdd.spec.infrastructure.OpProvider; -import com.hedera.services.bdd.spec.infrastructure.RegistryNotFound; -import com.hedera.services.bdd.spec.utilops.LoadTest; -import com.hederahashgraph.api.proto.java.AccountID; -import com.hederahashgraph.api.proto.java.ResponseCodeEnum; -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; -import java.util.Map; -import java.util.Optional; -import java.util.Random; -import java.util.Set; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.atomic.AtomicInteger; -import java.util.function.Function; -import org.apache.commons.lang3.tuple.Pair; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; - -public class AccountBalancesClientSaveLoadTest extends LoadTest { - private static final Logger LOG = LogManager.getLogger(AccountBalancesClientSaveLoadTest.class); - static final int MAX_PENDING_OPS_FOR_SETUP = 1000; - static final int TOTAL_ACCOUNT = 20000; - static final int ESTIMATED_TOKEN_CREATION_RATE = 50; - static final int ESTIMATED_CRYPTO_CREATION_RATE = 500; - static final long MIN_ACCOUNT_BALANCE = 1_000_000_000L; - static final int MIN_TOKEN_SUPPLY = 1000; - static final int MAX_TOKEN_SUPPLY = 1_000_000; - static final int MAX_TOKEN_TRANSFER = 100; - static final int SECOND = 1000; - - @SuppressWarnings("java:S2245") // using java.util.Random in tests is fine - private static final Random RANDOM = new Random(588616L); - - private static final int TOTAL_TEST_TOKENS = 500; - private static final String ACCT_NAME_PREFIX = "acct-"; - private static final String TOKEN_NAME_PREFIX = "token-"; - - private static final String ACCOUNT_FILE_EXPORT_DIR = "src/main/resource/accountBalancesClient.pb"; - - private int totalTestTokens = TOTAL_TEST_TOKENS; - private int totalAccounts = TOTAL_ACCOUNT; - - List> tokenAcctAssociations = new ArrayList<>(); - - private final ResponseCodeEnum[] permissiblePrechecks = new ResponseCodeEnum[] { - OK, BUSY, DUPLICATE_TRANSACTION, PLATFORM_TRANSACTION_NOT_CREATED, ACCOUNT_ID_DOES_NOT_EXIST, ACCOUNT_DELETED - }; - - public static void main(String... args) { - parseArgs(args); - AccountBalancesClientSaveLoadTest suite = new AccountBalancesClientSaveLoadTest(); - suite.runSuiteSync(); - } - - @Override - public List getSpecsInSuite() { - return List.of(runAccountBalancesClientSaveLoadTest()); - } - - final HapiSpec runAccountBalancesClientSaveLoadTest() { - PerfTestLoadSettings settings = new PerfTestLoadSettings(); - var throttlesForJRS = protoDefsFromResource("testSystemFiles/throttles-for-acct-balances-tests.json"); - return defaultHapiSpec("AccountBalancesClientSaveLoadTest") - .given( - tokenOpsEnablement(), - withOpContext( - (spec, ignore) -> settings.setFrom(spec.setup().ciPropertiesMap())), - logIt(ignore -> settings.toString()), - sourcing(() -> fileUpdate(APP_PROPERTIES) - .payingWith(GENESIS) - .overridingProps(Map.of( - "balances.exportPeriodSecs", - String.format("%d", settings.getBalancesExportPeriodSecs()))) - .erasingProps(Set.of("accountBalanceExportPeriodMinutes"))), - fileUpdate(THROTTLE_DEFS).payingWith(GENESIS).contents(throttlesForJRS.toByteArray())) - .when( - sourcing(() -> runWithProvider(accountsCreate(settings)) - .lasting(() -> totalAccounts / settings.getTps() + 30, () -> TimeUnit.SECONDS) - .totalOpsToSumbit(() -> totalAccounts) - .maxOpsPerSec(settings::getTps) - .maxPendingOps(() -> MAX_PENDING_OPS_FOR_SETUP)), - sleepFor(10L * SECOND), - sourcing(() -> runWithProvider(tokensCreate()) - .lasting( - () -> totalTestTokens / ESTIMATED_TOKEN_CREATION_RATE + 10, - () -> TimeUnit.SECONDS) - .totalOpsToSumbit(() -> totalTestTokens) - .maxOpsPerSec(settings::getTps) - .maxPendingOps(() -> MAX_PENDING_OPS_FOR_SETUP)), - sleepFor(10L * SECOND), - sourcing(() -> runWithProvider(randomTokenAssociate()) - .lasting(settings::getDurationCreateTokenAssociation, () -> TimeUnit.SECONDS) - .maxOpsPerSec(settings::getTps) - .maxPendingOps(() -> MAX_PENDING_OPS_FOR_SETUP)), - sleepFor(15L * SECOND), - sourcing(() -> runWithProvider(randomTransfer()) - .lasting(settings::getDurationTokenTransfer, () -> TimeUnit.SECONDS) - .maxOpsPerSec(settings::getTps) - .maxPendingOps(() -> MAX_PENDING_OPS_FOR_SETUP))) - .then( - sleepFor(30L * SECOND), - withOpContext((spec, log) -> { - if (settings.getBooleanProperty("clientToExportBalances", false)) { - log.info("Now get all {} accounts created and save them", totalAccounts); - AccountID acctID; - String lastGoodAcct = null; - int acctProcessed = 0; - int batchSize = 10000; - while (acctProcessed <= totalAccounts) { - List ops = new ArrayList<>(); - String acctName = null; - for (int i = acctProcessed + 1; - i <= acctProcessed + batchSize && i <= totalAccounts; - i++) { - acctName = ACCT_NAME_PREFIX + i; - // Make sure the named account was created before - // query its balances. - try { - acctID = spec.registry().getAccountID(acctName); - } catch (RegistryNotFound e) { - log.info("{} was not created successfully.", acctName); - continue; - } - var op = getAccountBalance(HapiPropertySource.asAccountString(acctID)) - .payingWith(GENESIS) - .hasAnswerOnlyPrecheckFrom(permissiblePrechecks) - .persists(true) - .noLogging(); - ops.add(op); - lastGoodAcct = acctName; - } - ops.add(getAccountInfo(lastGoodAcct) - .payingWith(GENESIS) - .fee(ONE_HBAR)); - allRunFor(spec, ops); - acctProcessed += batchSize; - } - } else { // debug - log.info("Don't dump account balances from client side."); - } - }), - sleepFor(10L * SECOND), - exportAccountBalances(() -> ACCOUNT_FILE_EXPORT_DIR), - freezeOnly().payingWith(GENESIS).startingIn(10).seconds()); - } - - private Function accountsCreate(PerfTestLoadSettings settings) { - totalTestTokens = settings.getTotalTokens() > 10 ? settings.getTotalTokens() : TOTAL_TEST_TOKENS; - totalAccounts = settings.getTotalAccounts() > 100 ? settings.getTotalAccounts() : TOTAL_ACCOUNT; - - LOG.info("Total accounts: {}", totalAccounts); - LOG.info("Total tokens: {}", totalTestTokens); - - AtomicInteger moreToCreate = new AtomicInteger(1); - - return spec -> new OpProvider() { - @Override - public List suggestedInitializers() { - LOG.info("Now running accountsCreate initializer"); - return Collections.emptyList(); - } - - @Override - public Optional get() { - int next; - next = moreToCreate.getAndIncrement(); - if (next > totalAccounts) { - return Optional.empty(); - } - - var op = cryptoCreate(String.format("%s%d", ACCT_NAME_PREFIX, next)) - .balance((RANDOM.nextInt((int) ONE_HBAR) + MIN_ACCOUNT_BALANCE)) - .key(GENESIS) - .fee(ONE_HUNDRED_HBARS) - .rechargeWindow(30) - .hasRetryPrecheckFrom(NOISY_RETRY_PRECHECKS) - .hasPrecheckFrom(DUPLICATE_TRANSACTION, OK) - .hasKnownStatusFrom(SUCCESS, INVALID_SIGNATURE) - .noLogging() - .deferStatusResolution(); - - return Optional.of(op); - } - }; - } - - private Function tokensCreate() { - AtomicInteger createdSofar = new AtomicInteger(0); - - return spec -> new OpProvider() { - @Override - public List suggestedInitializers() { - LOG.info("Now running tokensCreate initializer"); - return Collections.emptyList(); - } - - @Override - public Optional get() { - int next; - next = createdSofar.getAndIncrement(); - if (next >= totalTestTokens) { - return Optional.empty(); - } - var payingTreasury = String.format("%s%s", ACCT_NAME_PREFIX, next); - var op = tokenCreate(TOKEN_NAME_PREFIX + next) - .signedBy(GENESIS) - .fee(ONE_HUNDRED_HBARS) - .initialSupply((long) MIN_TOKEN_SUPPLY + RANDOM.nextInt(MAX_TOKEN_SUPPLY)) - .treasury(payingTreasury) - .hasRetryPrecheckFrom(NOISY_RETRY_PRECHECKS) - .hasPrecheckFrom(DUPLICATE_TRANSACTION, OK) - .hasKnownStatusFrom(SUCCESS, TOKEN_ALREADY_ASSOCIATED_TO_ACCOUNT) - .suppressStats(true) - .noLogging(); - if (next > 0) { - op.deferStatusResolution(); - } - return Optional.of(op); - } - }; - } - - private Function randomTokenAssociate() { - return spec -> new OpProvider() { - @Override - public List suggestedInitializers() { - LOG.info("Now running tokenAssociatesFactory initializer"); - return Collections.emptyList(); - } - - @Override - public Optional get() { - int tokenNum = RANDOM.nextInt(totalTestTokens - 1); - int acctNum = RANDOM.nextInt(totalAccounts - 1); - String tokenName = TOKEN_NAME_PREFIX + tokenNum; - String accountName = ACCT_NAME_PREFIX + acctNum; - tokenAcctAssociations.add(Pair.of(tokenNum, acctNum)); - - var op = tokenAssociate(accountName, tokenName) - .signedBy(GENESIS) - .hasRetryPrecheckFrom(NOISY_RETRY_PRECHECKS) - .hasPrecheckFrom(DUPLICATE_TRANSACTION, OK) - .hasKnownStatusFrom( - SUCCESS, TOKEN_ALREADY_ASSOCIATED_TO_ACCOUNT, INVALID_SIGNATURE, TRANSACTION_EXPIRED) - .fee(ONE_HUNDRED_HBARS) - .noLogging() - .suppressStats(true) - .deferStatusResolution(); - - return Optional.of(op); - } - }; - } - - private Function randomTransfer() { - return spec -> new OpProvider() { - @Override - public List suggestedInitializers() { - LOG.info("Now running tokenTransferFactory initializer"); - return Collections.emptyList(); - } - - @Override - public Optional get() { - int nextTransfer = RANDOM.nextInt(tokenAcctAssociations.size()); - int tokenAndSenderOrd = tokenAcctAssociations.get(nextTransfer).getLeft(); - int receiverOrd = tokenAcctAssociations.get(nextTransfer).getRight(); - String tokenName = TOKEN_NAME_PREFIX + tokenAndSenderOrd; - String senderAcctName = ACCT_NAME_PREFIX + tokenAndSenderOrd; - String receivedAcctName = ACCT_NAME_PREFIX + receiverOrd; - var op = cryptoTransfer(moving(RANDOM.nextInt(MAX_TOKEN_TRANSFER) + 1L, tokenName) - .between(senderAcctName, receivedAcctName)) - .hasKnownStatusFrom( - OK, - SUCCESS, - DUPLICATE_TRANSACTION, - INVALID_SIGNATURE, - TOKEN_NOT_ASSOCIATED_TO_ACCOUNT, - INSUFFICIENT_TOKEN_BALANCE) - .hasRetryPrecheckFrom(NOISY_RETRY_PRECHECKS) - .hasPrecheckFrom(ACCOUNT_REPEATED_IN_ACCOUNT_AMOUNTS, OK) - .noLogging() - .signedBy(GENESIS) - .suppressStats(true) - .fee(ONE_HUNDRED_HBARS) - .deferStatusResolution(); - - return Optional.of(op); - } - }; - } - - @Override - protected Logger getResultsLogger() { - return LOG; - } -} diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/perf/AdjustFeeScheduleSuite.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/perf/AdjustFeeScheduleSuite.java index ce9ea4beccd0..d27597dc5c13 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/perf/AdjustFeeScheduleSuite.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/perf/AdjustFeeScheduleSuite.java @@ -22,12 +22,13 @@ import static com.hederahashgraph.api.proto.java.HederaFunctionality.ConsensusSubmitMessage; import static com.hederahashgraph.api.proto.java.HederaFunctionality.CryptoTransfer; -import com.hedera.services.bdd.spec.HapiSpec; import com.hedera.services.bdd.suites.HapiSuite; import java.util.List; import java.util.Map; +import java.util.stream.Stream; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; +import org.junit.jupiter.api.DynamicTest; /** * This is a suite for any tests with high volume requests of CryptoTransfer and @@ -44,11 +45,11 @@ public static void main(String... args) { } @Override - public List getSpecsInSuite() { + public List> getSpecsInSuite() { return List.of(updateFeesFor()); } - final HapiSpec updateFeesFor() { + final Stream updateFeesFor() { final var fixedFee = ONE_HUNDRED_HBARS; return customHapiSpec("updateFees") .withProperties(Map.of("fees.useFixedOffer", "true", "fees.fixedOffer", "" + fixedFee)) diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/perf/FileContractMemoPerfSuite.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/perf/FileContractMemoPerfSuite.java deleted file mode 100644 index 49a5f7694e60..000000000000 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/perf/FileContractMemoPerfSuite.java +++ /dev/null @@ -1,136 +0,0 @@ -/* - * Copyright (C) 2020-2024 Hedera Hashgraph, LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.hedera.services.bdd.suites.perf; - -import static com.hedera.services.bdd.spec.HapiSpec.defaultHapiSpec; -import static com.hedera.services.bdd.spec.queries.QueryVerbs.getContractInfo; -import static com.hedera.services.bdd.spec.queries.QueryVerbs.getFileInfo; -import static com.hedera.services.bdd.spec.transactions.TxnVerbs.contractCreate; -import static com.hedera.services.bdd.spec.transactions.TxnVerbs.contractUpdate; -import static com.hedera.services.bdd.spec.transactions.TxnVerbs.fileCreate; -import static com.hedera.services.bdd.spec.transactions.TxnVerbs.fileUpdate; -import static com.hedera.services.bdd.spec.utilops.UtilVerbs.logIt; -import static com.hedera.services.bdd.spec.utilops.UtilVerbs.withOpContext; -import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.BUSY; -import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.DUPLICATE_TRANSACTION; -import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.OK; -import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.PLATFORM_TRANSACTION_NOT_CREATED; - -import com.hedera.services.bdd.spec.HapiSpec; -import com.hedera.services.bdd.spec.HapiSpecOperation; -import com.hedera.services.bdd.spec.HapiSpecSetup; -import com.hedera.services.bdd.spec.transactions.TxnUtils; -import com.hedera.services.bdd.spec.utilops.LoadTest; -import com.hederahashgraph.api.proto.java.ResponseCodeEnum; -import java.nio.charset.StandardCharsets; -import java.util.List; -import java.util.concurrent.atomic.AtomicInteger; -import java.util.function.Supplier; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; - -public class FileContractMemoPerfSuite extends LoadTest { - private static final Logger log = LogManager.getLogger(FileContractMemoPerfSuite.class); - - private final ResponseCodeEnum[] permissiblePrechecks = - new ResponseCodeEnum[] {OK, BUSY, DUPLICATE_TRANSACTION, PLATFORM_TRANSACTION_NOT_CREATED}; - - private final String INITIAL_MEMO = "InitialMemo"; - private final String FILE_MEMO = INITIAL_MEMO + " for File Entity"; - private final String CONTRACT_MEMO = INITIAL_MEMO + " for Contract Entity"; - private final String TARGET_FILE = "fileForMemo"; - private final String CONTRACT = "contractForMemo"; - - public static void main(String... args) { - parseArgs(args); - - FileContractMemoPerfSuite suite = new FileContractMemoPerfSuite(); - suite.runSuiteSync(); - } - - @Override - public List getSpecsInSuite() { - return List.of(RunMixedFileContractMemoOps()); - } - - // perform cryptoCreate, cryptoUpdate, TokenCreate, TokenUpdate, FileCreate, FileUpdate txs with - // entity memo set. - protected HapiSpec RunMixedFileContractMemoOps() { - PerfTestLoadSettings settings = new PerfTestLoadSettings(); - final AtomicInteger createdSoFar = new AtomicInteger(0); - Supplier mixedOpsBurst = () -> new HapiSpecOperation[] { - fileCreate("testFile" + createdSoFar.getAndIncrement()) - .payingWith(GENESIS) - .entityMemo(new String(TxnUtils.randomUtf8Bytes(memoLength.getAsInt()), StandardCharsets.UTF_8)) - .noLogging() - .hasPrecheckFrom(OK, BUSY, DUPLICATE_TRANSACTION, PLATFORM_TRANSACTION_NOT_CREATED) - .deferStatusResolution(), - getFileInfo(TARGET_FILE + "Info").hasMemo(FILE_MEMO), - fileUpdate(TARGET_FILE) - .payingWith(GENESIS) - .entityMemo(new String(TxnUtils.randomUtf8Bytes(memoLength.getAsInt()), StandardCharsets.UTF_8)) - .noLogging() - .hasPrecheckFrom(OK, BUSY, DUPLICATE_TRANSACTION, PLATFORM_TRANSACTION_NOT_CREATED) - .deferStatusResolution(), - contractCreate("testContract" + createdSoFar.getAndIncrement()) - .payingWith(GENESIS) - .bytecode(TARGET_FILE) - .entityMemo(new String(TxnUtils.randomUtf8Bytes(memoLength.getAsInt()), StandardCharsets.UTF_8)) - .noLogging() - .hasPrecheckFrom(OK, BUSY, DUPLICATE_TRANSACTION, PLATFORM_TRANSACTION_NOT_CREATED) - .deferStatusResolution(), - getContractInfo(CONTRACT + "Info").hasExpectedInfo(), - contractUpdate(CONTRACT) - .payingWith(GENESIS) - .newMemo(new String(TxnUtils.randomUtf8Bytes(memoLength.getAsInt()), StandardCharsets.UTF_8)) - .noLogging() - .hasPrecheckFrom(OK, BUSY, DUPLICATE_TRANSACTION, PLATFORM_TRANSACTION_NOT_CREATED) - .deferStatusResolution() - }; - return defaultHapiSpec("RunMixedFileContractMemoOps") - .given( - withOpContext( - (spec, ignore) -> settings.setFrom(spec.setup().ciPropertiesMap())), - logIt(ignore -> settings.toString())) - .when( - fileCreate(TARGET_FILE) - .payingWith(GENESIS) - .path(HapiSpecSetup.getDefaultInstance().defaultContractPath()) - .entityMemo(FILE_MEMO) - .logged(), - fileCreate(TARGET_FILE + "Info") - .payingWith(GENESIS) - .entityMemo(FILE_MEMO) - .logged(), - contractCreate(CONTRACT) - .payingWith(GENESIS) - .bytecode(TARGET_FILE) - .entityMemo(CONTRACT_MEMO) - .logged(), - contractCreate(CONTRACT + "Info") - .payingWith(GENESIS) - .bytecode(TARGET_FILE) - .entityMemo(CONTRACT_MEMO) - .logged()) - .then(defaultLoadTest(mixedOpsBurst, settings)); - } - - @Override - protected Logger getResultsLogger() { - return log; - } -} diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/perf/PerfUtilOps.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/perf/PerfUtilOps.java index 9cfd7572d2c7..4a2fed450459 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/perf/PerfUtilOps.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/perf/PerfUtilOps.java @@ -16,16 +16,9 @@ package com.hedera.services.bdd.suites.perf; -import static com.hedera.services.bdd.spec.transactions.TxnVerbs.fileUpdate; import static com.hedera.services.bdd.spec.utilops.UtilVerbs.withOpContext; -import static com.hedera.services.bdd.suites.HapiSuite.API_PERMISSIONS; -import static com.hedera.services.bdd.suites.HapiSuite.GENESIS; -import static com.hedera.services.bdd.suites.HapiSuite.ONE_HUNDRED_HBARS; -import static java.util.Map.entry; import com.hedera.services.bdd.spec.HapiSpecOperation; -import com.hedera.services.bdd.spec.transactions.HapiTxnOp; -import java.util.Map; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicInteger; @@ -83,39 +76,4 @@ public static HapiSpecOperation stdMgmtOf( } }); } - - public static HapiTxnOp tokenOpsEnablement() { - return fileUpdate(API_PERMISSIONS) - .fee(ONE_HUNDRED_HBARS) - .payingWith(GENESIS) - .overridingProps(Map.ofEntries( - entry("tokenCreate", "0-*"), - entry("tokenFreezeAccount", "0-*"), - entry("tokenUnfreezeAccount", "0-*"), - entry("tokenGrantKycToAccount", "0-*"), - entry("tokenRevokeKycFromAccount", "0-*"), - entry("tokenDelete", "0-*"), - entry("tokenMint", "0-*"), - entry("tokenBurn", "0-*"), - entry("tokenAccountWipe", "0-*"), - entry("tokenUpdate", "0-*"), - entry("tokenFeeScheduleUpdate", "0-*"), - entry("tokenGetInfo", "0-*"), - entry("tokenAssociateToAccount", "0-*"), - entry("tokenDissociateFromAccount", "0-*"), - entry("tokenGetNftInfo", "0-*"), - entry("tokenGetNftInfos", "0-*"), - entry("tokenGetAccountNftInfos", "0-*"))); - } - - public static HapiTxnOp scheduleOpsEnablement() { - return fileUpdate(API_PERMISSIONS) - .fee(ONE_HUNDRED_HBARS) - .payingWith(GENESIS) - .overridingProps(Map.ofEntries( - entry("scheduleCreate", "0-*"), - entry("scheduleDelete", "0-*"), - entry("scheduleSign", "0-*"), - entry("scheduleGetInfo", "0-*"))); - } } diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/perf/QueryOnlyLoadTest.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/perf/QueryOnlyLoadTest.java deleted file mode 100644 index 94f456c799c1..000000000000 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/perf/QueryOnlyLoadTest.java +++ /dev/null @@ -1,232 +0,0 @@ -/* - * Copyright (C) 2021-2024 Hedera Hashgraph, LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.hedera.services.bdd.suites.perf; - -import static com.hedera.services.bdd.spec.HapiSpec.defaultHapiSpec; -import static com.hedera.services.bdd.spec.utilops.UtilVerbs.logIt; -import static com.hedera.services.bdd.spec.utilops.UtilVerbs.withOpContext; -import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.ACCOUNT_DELETED; -import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.ACCOUNT_ID_DOES_NOT_EXIST; -import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.DUPLICATE_TRANSACTION; -import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.INSUFFICIENT_PAYER_BALANCE; -import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.INVALID_ACCOUNT_ID; -import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.INVALID_SCHEDULE_ACCOUNT_ID; -import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.INVALID_SCHEDULE_ID; -import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.INVALID_SCHEDULE_PAYER_ID; -import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.INVALID_TOKEN_ID; -import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.INVALID_TOPIC_ID; -import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.OK; -import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.PLATFORM_TRANSACTION_NOT_CREATED; -import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.SCHEDULE_ALREADY_DELETED; -import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.SCHEDULE_ALREADY_EXECUTED; -import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.TOKEN_WAS_DELETED; -import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.TOPIC_EXPIRED; -import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.TRANSACTION_EXPIRED; - -import com.hedera.services.bdd.spec.HapiSpec; -import com.hedera.services.bdd.spec.HapiSpecOperation; -import com.hedera.services.bdd.spec.queries.QueryVerbs; -import com.hedera.services.bdd.spec.utilops.LoadTest; -import java.util.List; -import java.util.Random; -import java.util.function.Supplier; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; - -/** - * This test needs sequential distribution of entity ids for accounts, topics, tokens, and scheduled - * transactions in this order. It also needs roughly the starting and ending point of each entity id - * types. - * - *

    NOTE: Right now, for historical reason, we have good knowledge of the boundaries of account - * and topic chunks in current saved state file, but the token and schedule transactions are not - * clear. - */ -public class QueryOnlyLoadTest extends LoadTest { - private static final Logger log = LogManager.getLogger(QueryOnlyLoadTest.class); - - @SuppressWarnings("java:S2245") // using java.util.Random in tests is fine - private static final Random r = new Random(870333L); - - private static final String ACCOUNT_PATTERN = "0.0.%d"; - - private static int startingScheduleNum = 0; - private static int startingTokenNum = 0; - private static int startingTopicNum = 0; - - public static void main(String... args) { - parseArgs(args); - QueryOnlyLoadTest suite = new QueryOnlyLoadTest(); - suite.runSuiteSync(); - } - - @Override - public List getSpecsInSuite() { - return List.of(new HapiSpec[] {runQueryLoadTest()}); - } - - final HapiSpec runQueryLoadTest() { - PerfTestLoadSettings settings = new PerfTestLoadSettings(); - - Supplier mixedQueries = () -> new HapiSpecOperation[] { - opGetAcctInfo(settings), - opGetTopicInfo(settings), - // TODO: need to generate a new state file for queries of tokens and - // schedule transactions to - // work satisfactorily - (r.nextInt(100) < 10) ? opGetTokenInfo(settings) : opGetAcctInfo(settings), - (r.nextInt(100) < 5) ? opGetScheduleInfo(settings) : opGetTopicInfo(settings) - - // These statement are commented out for now.They may be enabled later - // when we decide - // how to enable and combine all types of queries in the perf test. - // opGetAcctBalance(settings), - // opGetAcctRecords(settings) - }; - - return defaultHapiSpec("runQueryLoadTest") - .given( - withOpContext( - (spec, ignore) -> settings.setFrom(spec.setup().ciPropertiesMap())), - logIt(ignore -> settings.toString())) - .when() - .then(defaultLoadTest(mixedQueries, settings)); - } - - private static HapiSpecOperation opGetAcctBalance(PerfTestLoadSettings settings) { - String acctToQuery = String.format( - ACCOUNT_PATTERN, settings.getTestTreasureStartAccount() + r.nextInt(settings.getTotalAccounts())); - - return QueryVerbs.getAccountBalance(acctToQuery) - .payingWith(GENESIS) - .fee(ONE_HUNDRED_HBARS) - .noLogging() - .hasAnswerOnlyPrecheckFrom(DUPLICATE_TRANSACTION, OK, PLATFORM_TRANSACTION_NOT_CREATED) - .hasCostAnswerPrecheckFrom( - OK, INSUFFICIENT_PAYER_BALANCE, INVALID_ACCOUNT_ID, ACCOUNT_DELETED, ACCOUNT_ID_DOES_NOT_EXIST); - } - - private static HapiSpecOperation opGetAcctRecords(PerfTestLoadSettings settings) { - String acctRecordsQuery = String.format( - ACCOUNT_PATTERN, settings.getTestTreasureStartAccount() + r.nextInt(settings.getTotalAccounts())); - - return QueryVerbs.getAccountRecords(acctRecordsQuery) - .payingWith(GENESIS) - .fee(ONE_HUNDRED_HBARS) - .nodePayment(ONE_HUNDRED_HBARS) - .noLogging() - .hasAnswerOnlyPrecheckFrom(DUPLICATE_TRANSACTION, OK) - .hasCostAnswerPrecheckFrom( - OK, INSUFFICIENT_PAYER_BALANCE, INVALID_ACCOUNT_ID, ACCOUNT_DELETED, ACCOUNT_ID_DOES_NOT_EXIST); - } - - private static HapiSpecOperation opGetAcctInfo(PerfTestLoadSettings settings) { - String acctInfoQuery = String.format( - ACCOUNT_PATTERN, settings.getTestTreasureStartAccount() + r.nextInt(settings.getTotalAccounts())); - - return QueryVerbs.getAccountInfo(acctInfoQuery) - .payingWith(GENESIS) - .fee(ONE_HUNDRED_HBARS) - .nodePayment(ONE_HUNDRED_HBARS) - .noLogging() - .hasAnswerOnlyPrecheckFrom( - DUPLICATE_TRANSACTION, OK, PLATFORM_TRANSACTION_NOT_CREATED, TRANSACTION_EXPIRED) - .hasCostAnswerPrecheckFrom( - OK, INSUFFICIENT_PAYER_BALANCE, INVALID_ACCOUNT_ID, ACCOUNT_DELETED, ACCOUNT_ID_DOES_NOT_EXIST); - } - - private static HapiSpecOperation opGetTopicInfo(PerfTestLoadSettings settings) { - if (startingTopicNum == 0) { - // This and the two assignments should be thread-safe in this context. - startingTopicNum = settings.getTestTreasureStartAccount() + settings.getTotalAccounts(); - } - String topicInfoQuery = String.format(ACCOUNT_PATTERN, startingTopicNum + r.nextInt(settings.getTotalTopics())); - - return QueryVerbs.getTopicInfo(topicInfoQuery) - .payingWith(GENESIS) - .fee(ONE_HUNDRED_HBARS) - .nodePayment(ONE_HUNDRED_HBARS) - .noLogging() - .hasAnswerOnlyPrecheckFrom( - DUPLICATE_TRANSACTION, - OK, - INVALID_TOPIC_ID, - PLATFORM_TRANSACTION_NOT_CREATED, - TRANSACTION_EXPIRED) - .hasCostAnswerPrecheckFrom(OK, INSUFFICIENT_PAYER_BALANCE, INVALID_TOPIC_ID, TOPIC_EXPIRED); - } - - private static HapiSpecOperation opGetTokenInfo(PerfTestLoadSettings settings) { - if (startingTokenNum == 0) { - startingTokenNum = - settings.getTestTreasureStartAccount() + settings.getTotalAccounts() + settings.getTotalTopics(); - } - - String tokenInfoQuery = String.format(ACCOUNT_PATTERN, startingTokenNum + r.nextInt(settings.getTotalTokens())); - - return QueryVerbs.getTokenInfo(tokenInfoQuery) - .payingWith(GENESIS) - .fee(ONE_HUNDRED_HBARS) - .nodePayment(ONE_HUNDRED_HBARS) - .noLogging() - .hasAnswerOnlyPrecheckFrom( - DUPLICATE_TRANSACTION, - OK, - INVALID_TOKEN_ID, - PLATFORM_TRANSACTION_NOT_CREATED, - TRANSACTION_EXPIRED) - .hasCostAnswerPrecheckFrom(OK, INSUFFICIENT_PAYER_BALANCE, INVALID_TOKEN_ID, TOKEN_WAS_DELETED); - } - - private static HapiSpecOperation opGetScheduleInfo(PerfTestLoadSettings settings) { - if (startingScheduleNum == 0) { - startingScheduleNum = settings.getTestTreasureStartAccount() - + settings.getTotalAccounts() - + settings.getTotalTopics() - + settings.getTotalTokens() - + settings.getTotalTokenAssociations(); - } - String scheduleInfoQuery = - String.format(ACCOUNT_PATTERN, startingScheduleNum + r.nextInt(settings.getTotalScheduled())); - - return QueryVerbs.getScheduleInfo(scheduleInfoQuery) - .payingWith(GENESIS) - .fee(ONE_HUNDRED_HBARS) - .nodePayment(ONE_HUNDRED_HBARS) - .noLogging() - .hasAnswerOnlyPrecheckFrom( - DUPLICATE_TRANSACTION, - OK, - INVALID_SCHEDULE_ID, - INVALID_SCHEDULE_ACCOUNT_ID, - PLATFORM_TRANSACTION_NOT_CREATED, - TRANSACTION_EXPIRED) - .hasCostAnswerPrecheckFrom( - OK, - INSUFFICIENT_PAYER_BALANCE, - INVALID_SCHEDULE_ID, - SCHEDULE_ALREADY_DELETED, - SCHEDULE_ALREADY_EXECUTED, - INVALID_SCHEDULE_PAYER_ID, - INVALID_SCHEDULE_ACCOUNT_ID); - } - - @Override - protected Logger getResultsLogger() { - return log; - } -} diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/perf/contract/ContractCallLoadTest.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/perf/contract/ContractCallLoadTest.java deleted file mode 100644 index 913af099fb59..000000000000 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/perf/contract/ContractCallLoadTest.java +++ /dev/null @@ -1,92 +0,0 @@ -/* - * Copyright (C) 2020-2024 Hedera Hashgraph, LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.hedera.services.bdd.suites.perf.contract; - -import static com.hedera.services.bdd.spec.HapiSpec.defaultHapiSpec; -import static com.hedera.services.bdd.spec.queries.QueryVerbs.getContractInfo; -import static com.hedera.services.bdd.spec.transactions.TxnVerbs.contractCall; -import static com.hedera.services.bdd.spec.transactions.TxnVerbs.contractCreate; -import static com.hedera.services.bdd.spec.transactions.TxnVerbs.uploadInitCode; -import static com.hedera.services.bdd.spec.utilops.UtilVerbs.inParallel; -import static com.hedera.services.bdd.spec.utilops.UtilVerbs.logIt; -import static com.hedera.services.bdd.spec.utilops.UtilVerbs.withOpContext; -import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.PLATFORM_TRANSACTION_NOT_CREATED; - -import com.hedera.services.bdd.spec.HapiSpec; -import com.hedera.services.bdd.spec.HapiSpecOperation; -import com.hedera.services.bdd.spec.utilops.LoadTest; -import com.hedera.services.bdd.suites.perf.PerfTestLoadSettings; -import java.util.List; -import java.util.concurrent.atomic.AtomicInteger; -import java.util.function.Supplier; -import java.util.stream.IntStream; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; - -public class ContractCallLoadTest extends LoadTest { - private static final Logger log = LogManager.getLogger(ContractCallLoadTest.class); - private static final String VERBOSE_DEPOSIT = "VerboseDeposit"; - private static final String BALANCE_LOOKUP = "BalanceLookup"; - - public static void main(String... args) { - parseArgs(args); - - ContractCallLoadTest suite = new ContractCallLoadTest(); - suite.runSuiteSync(); - } - - @Override - public List getSpecsInSuite() { - return List.of(runContractCalls()); - } - - final HapiSpec runContractCalls() { - PerfTestLoadSettings settings = new PerfTestLoadSettings(); - final AtomicInteger submittedSoFar = new AtomicInteger(0); - final String DEPOSIT_MEMO = "So we out-danced thought, body perfection brought..."; - - Supplier callBurst = () -> new HapiSpecOperation[] { - inParallel(IntStream.range(0, settings.getBurstSize()) - .mapToObj(i -> contractCall(VERBOSE_DEPOSIT, "deposit", i + 1, 0, DEPOSIT_MEMO) - .sending(i + 1) - .noLogging() - .suppressStats(true) - .hasRetryPrecheckFrom(PLATFORM_TRANSACTION_NOT_CREATED) - .deferStatusResolution()) - .toArray(n -> new HapiSpecOperation[n])), - logIt(ignore -> String.format( - "Now a total of %d transactions submitted.", submittedSoFar.addAndGet(settings.getBurstSize()))), - }; - - return defaultHapiSpec("runContractCalls") - .given( - withOpContext( - (spec, ignore) -> settings.setFrom(spec.setup().ciPropertiesMap())), - logIt(ignore -> settings.toString())) - .when( - uploadInitCode(VERBOSE_DEPOSIT, BALANCE_LOOKUP), - contractCreate(VERBOSE_DEPOSIT), - contractCreate(BALANCE_LOOKUP).balance(1L), - getContractInfo(VERBOSE_DEPOSIT).hasExpectedInfo().logged()) - .then(defaultLoadTest(callBurst, settings)); - } - - @Override - protected Logger getResultsLogger() { - return log; - } -} diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/perf/contract/ContractCallLocalPerfSuite.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/perf/contract/ContractCallLocalPerfSuite.java deleted file mode 100644 index 82a9fede145b..000000000000 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/perf/contract/ContractCallLocalPerfSuite.java +++ /dev/null @@ -1,75 +0,0 @@ -/* - * Copyright (C) 2020-2024 Hedera Hashgraph, LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.hedera.services.bdd.suites.perf.contract; - -import static com.hedera.services.bdd.spec.HapiSpec.defaultHapiSpec; -import static com.hedera.services.bdd.spec.queries.QueryVerbs.contractCallLocal; -import static com.hedera.services.bdd.spec.transactions.TxnVerbs.contractCreate; -import static com.hedera.services.bdd.spec.transactions.TxnVerbs.uploadInitCode; - -import com.hedera.services.bdd.spec.HapiSpec; -import com.hedera.services.bdd.spec.utilops.UtilVerbs; -import com.hedera.services.bdd.suites.HapiSuite; -import java.util.List; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; - -public class ContractCallLocalPerfSuite extends HapiSuite { - private static final Logger log = LogManager.getLogger(ContractCallLocalPerfSuite.class); - - public static void main(String... args) { - new ContractCallLocalPerfSuite().runSuiteSync(); - } - - @Override - public List getSpecsInSuite() { - return List.of(contractCallLocalPerf()); - } - - @Override - public boolean canRunConcurrent() { - return false; - } - - HapiSpec contractCallLocalPerf() { - final int NUM_CALLS = 1_000; - final var contract = "BalanceLookup"; - - return defaultHapiSpec("ContractCallLocalPerf") - .given(uploadInitCode(contract), contractCreate(contract).balance(1_000L)) - .when( - contractCallLocal(contract, "lookup", spec -> new Object[] { - spec.registry().getContractId(contract).getContractNum() - }) - .recordNodePaymentAs("cost"), - UtilVerbs.startThroughputObs("contractCallLocal")) - .then( - UtilVerbs.inParallel(asOpArray( - NUM_CALLS, ignore -> contractCallLocal(contract, "lookup", spec -> new Object[] { - spec.registry() - .getContractId(contract) - .getContractNum() - }) - .nodePayment(spec -> spec.registry().getAmount("cost")))), - UtilVerbs.finishThroughputObs("contractCallLocal")); - } - - @Override - protected Logger getResultsLogger() { - return log; - } -} diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/perf/contract/ContractCallPerfSuite.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/perf/contract/ContractCallPerfSuite.java deleted file mode 100644 index bc907ea6df45..000000000000 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/perf/contract/ContractCallPerfSuite.java +++ /dev/null @@ -1,92 +0,0 @@ -/* - * Copyright (C) 2020-2024 Hedera Hashgraph, LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.hedera.services.bdd.suites.perf.contract; - -import static com.hedera.services.bdd.spec.HapiSpec.defaultHapiSpec; -import static com.hedera.services.bdd.spec.assertions.ContractFnResultAsserts.isLiteralResult; -import static com.hedera.services.bdd.spec.assertions.ContractFnResultAsserts.resultWith; -import static com.hedera.services.bdd.spec.queries.QueryVerbs.contractCallLocal; -import static com.hedera.services.bdd.spec.queries.QueryVerbs.getContractInfo; -import static com.hedera.services.bdd.spec.transactions.TxnVerbs.contractCall; -import static com.hedera.services.bdd.spec.transactions.TxnVerbs.contractCreate; -import static com.hedera.services.bdd.spec.transactions.TxnVerbs.uploadInitCode; -import static com.hedera.services.bdd.suites.contract.Utils.FunctionType.FUNCTION; -import static com.hedera.services.bdd.suites.contract.Utils.getABIFor; - -import com.hedera.services.bdd.spec.HapiSpec; -import com.hedera.services.bdd.spec.utilops.UtilVerbs; -import com.hedera.services.bdd.suites.HapiSuite; -import java.math.BigInteger; -import java.util.List; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; - -public class ContractCallPerfSuite extends HapiSuite { - private static final Logger log = LogManager.getLogger(ContractCallPerfSuite.class); - - public static void main(String... args) { - new ContractCallPerfSuite().runSuiteSync(); - } - - @Override - public List getSpecsInSuite() { - return List.of(contractCallPerf()); - } - - @Override - public boolean canRunConcurrent() { - return false; - } - - HapiSpec contractCallPerf() { - final int NUM_CALLS = 1_000; - final long ENDING_BALANCE = NUM_CALLS * (NUM_CALLS + 1) / 2; - final String DEPOSIT_MEMO = "So we out-danced thought, body perfection brought..."; - final var verboseDeposit = "VerboseDeposit"; - final var balanceLookup = "BalanceLookup"; - - return defaultHapiSpec("ContractCallPerf") - .given( - uploadInitCode(verboseDeposit, balanceLookup), - contractCreate(verboseDeposit), - contractCreate(balanceLookup).balance(1L)) - .when( - getContractInfo(verboseDeposit).hasExpectedInfo().logged(), - UtilVerbs.startThroughputObs("contractCall").msToSaturateQueues(50L)) - .then( - UtilVerbs.inParallel(asOpArray( - NUM_CALLS, i -> contractCall(verboseDeposit, "deposit", i + 1, 0, DEPOSIT_MEMO) - .sending(i + 1) - .deferStatusResolution())), - UtilVerbs.finishThroughputObs("contractCall").gatedByQuery(() -> contractCallLocal( - balanceLookup, "lookup", spec -> new Object[] { - spec.registry() - .getContractId(verboseDeposit) - .getContractNum() - }) - .has(resultWith() - .resultThruAbi( - getABIFor(FUNCTION, "lookup", balanceLookup), - isLiteralResult(new Object[] {BigInteger.valueOf(ENDING_BALANCE)}))) - .noLogging())); - } - - @Override - protected Logger getResultsLogger() { - return log; - } -} diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/perf/contract/ContractPerformanceSuite.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/perf/contract/ContractPerformanceSuite.java deleted file mode 100644 index 07d8f0609050..000000000000 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/perf/contract/ContractPerformanceSuite.java +++ /dev/null @@ -1,161 +0,0 @@ -/* - * Copyright (C) 2021-2024 Hedera Hashgraph, LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.hedera.services.bdd.suites.perf.contract; - -import static com.hedera.services.bdd.spec.HapiSpec.defaultHapiSpec; -import static com.hedera.services.bdd.spec.assertions.ContractFnResultAsserts.resultWith; -import static com.hedera.services.bdd.spec.assertions.TransactionRecordAsserts.recordWith; -import static com.hedera.services.bdd.spec.queries.QueryVerbs.getExecTime; -import static com.hedera.services.bdd.spec.queries.QueryVerbs.getReceipt; -import static com.hedera.services.bdd.spec.queries.QueryVerbs.getTxnRecord; -import static com.hedera.services.bdd.spec.transactions.TxnVerbs.contractCall; -import static com.hedera.services.bdd.spec.transactions.TxnVerbs.contractCreate; -import static com.hedera.services.bdd.spec.transactions.TxnVerbs.fileCreate; -import static com.hedera.services.bdd.spec.utilops.CustomSpecAssert.allRunFor; -import static com.hedera.services.bdd.spec.utilops.UtilVerbs.withOpContext; -import static com.swirlds.common.utility.CommonUtils.hex; - -import com.google.common.io.Files; -import com.hedera.services.bdd.spec.HapiPropertySource; -import com.hedera.services.bdd.spec.HapiSpec; -import com.hedera.services.bdd.spec.HapiSpecOperation; -import com.hedera.services.bdd.spec.transactions.file.HapiFileCreate; -import com.hedera.services.bdd.suites.HapiSuite; -import com.hederahashgraph.api.proto.java.ContractID; -import com.hederahashgraph.api.proto.java.ResponseCodeEnum; -import java.io.File; -import java.io.IOException; -import java.nio.charset.Charset; -import java.nio.charset.StandardCharsets; -import java.time.temporal.ChronoUnit; -import java.util.ArrayList; -import java.util.List; -import java.util.stream.Collectors; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; - -public class ContractPerformanceSuite extends HapiSuite { - private static final Logger LOG = LogManager.getLogger(ContractPerformanceSuite.class); - - private static final String PERF_RESOURCES = "src/main/resource/contract/performance/"; - private static final String LOADER_TEMPLATE = "608060405234801561001057600080fd5b5061%04x806100206000396000f3fe%s"; - private static final String RETURN_PROGRAM = "3360005260206000F3"; - private static final String REVERT_PROGRAM = "6055605555604360A052600160A0FD"; - - private static final String EXTERNAL_CONTRACT_MARKER = "7465737420636f6e7472616374"; - private static final String RETURN_CONTRACT = "returnContract"; - private static final String RETURN_CONTRACT_ADDRESS = "72657475726e207465737420636f6e7472616374"; - private static final String REVERT_CONTRACT = "revertContract"; - private static final String REVERT_CONTRACT_ADDRESS = "726576657274207465737420636f6e7472616374"; - private static final String BYTECODE = "bytecode"; - - public static void main(String... args) { - new ContractPerformanceSuite().runSuiteAsync(); - } - - static HapiFileCreate createProgramFile(String name, String program) { - return fileCreate(name).contents(String.format(LOADER_TEMPLATE, program.length(), program)); - } - - static HapiFileCreate createTestProgram( - String test, ContractID returnAccountAddress, ContractID revertAccountAddress) { - String path = PERF_RESOURCES + test; - try { - var contentString = new String(Files.toByteArray(new File(path)), StandardCharsets.US_ASCII) - .replace(RETURN_CONTRACT_ADDRESS, asSolidityAddress(returnAccountAddress)) - .replace(REVERT_CONTRACT_ADDRESS, asSolidityAddress(revertAccountAddress)); - return fileCreate(test + BYTECODE).contents(contentString.getBytes(StandardCharsets.US_ASCII)); - } catch (Exception e) { - String message = String.format("createTestProgram for %s failed to read bytes from '%s'!", test, path); - LOG.warn(message, e); - return fileCreate(test); - } - } - - @Override - public List getSpecsInSuite() { - List perfTests; - try { - perfTests = - Files.readLines(new File(PERF_RESOURCES + "performanceContracts.csv"), Charset.defaultCharset()) - .stream() - .filter(s -> !s.isEmpty() && !s.startsWith("#")) - .collect(Collectors.toList()); - } catch (IOException e) { - return List.of(); - } - List hapiSpecs = new ArrayList<>(); - for (String line : perfTests) { - String[] values = line.split(",", 2); - String test = values[0]; - long gasCost = Long.parseLong(values[1]); - String path = PERF_RESOURCES + test; - String via = test.substring(0, test.length() - 4); - String contractCode; - try { - contractCode = new String(Files.toByteArray(new File(path)), StandardCharsets.US_ASCII); - } catch (IOException e) { - String message = String.format("createTestProgram for %s failed to read bytes from '%s'!", test, path); - LOG.warn(message, e); - contractCode = "FE"; - } - HapiSpecOperation[] givenBlock; - if (contractCode.contains(EXTERNAL_CONTRACT_MARKER)) { - givenBlock = new HapiSpecOperation[] { - createProgramFile(RETURN_CONTRACT + BYTECODE, RETURN_PROGRAM), - contractCreate(RETURN_CONTRACT).bytecode(RETURN_CONTRACT + BYTECODE), - createProgramFile(REVERT_CONTRACT + BYTECODE, REVERT_PROGRAM), - contractCreate(REVERT_CONTRACT).bytecode(REVERT_CONTRACT + BYTECODE), - withOpContext((spec, opLog) -> allRunFor( - spec, - createTestProgram( - test, - spec.registry().getContractId(RETURN_CONTRACT), - spec.registry().getContractId(REVERT_CONTRACT)))), - contractCreate(test).bytecode(test + BYTECODE) - }; - } else { - givenBlock = new HapiSpecOperation[] { - fileCreate(BYTECODE).path(PERF_RESOURCES + test), - contractCreate(test).bytecode(BYTECODE) - }; - } - hapiSpecs.add(defaultHapiSpec("Perf_" + test) - .given(givenBlock) - .when(contractCall(test, "").gas(35000000).via(via)) - .then( - getExecTime(via) - .payingWith(GENESIS) - .logged() - .assertingNoneLongerThan(20, ChronoUnit.SECONDS), - getReceipt(via).hasPriorityStatus(ResponseCodeEnum.SUCCESS), - getTxnRecord(via) - .hasPriority(recordWith() - .contractCallResult(resultWith().gasUsed(gasCost))))); - } - return hapiSpecs; - } - - @Override - protected Logger getResultsLogger() { - return LOG; - } - - public static String asSolidityAddress(final ContractID id) { - return hex(HapiPropertySource.asSolidityAddress((int) id.getShardNum(), id.getRealmNum(), id.getContractNum())); - } -} diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/perf/contract/FibonacciPlusLoadProvider.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/perf/contract/FibonacciPlusLoadProvider.java index 7c87fa18487e..a670394b6482 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/perf/contract/FibonacciPlusLoadProvider.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/perf/contract/FibonacciPlusLoadProvider.java @@ -66,9 +66,14 @@ import java.util.function.DoubleUnaryOperator; import java.util.function.Function; import java.util.function.IntFunction; +import java.util.stream.Stream; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; +import org.junit.jupiter.api.DynamicTest; +/** + * (FUTURE) Integrate this test to CI in some form. + */ public class FibonacciPlusLoadProvider extends HapiSuite { private static final Logger LOG = LogManager.getLogger(FibonacciPlusLoadProvider.class); @@ -153,11 +158,11 @@ public void logResults() { } @Override - public List getSpecsInSuite() { + public List> getSpecsInSuite() { return List.of(justDoOne(), addFibNums()); } - final HapiSpec addFibNums() { + final Stream addFibNums() { return defaultHapiSpec("AddFibNums") .given( stdMgmtOf(duration, unit, maxOpsPerSec, SUITE_PROPS_PREFIX), @@ -355,7 +360,7 @@ private void observeExposedGas(final long gas) { gasUsed.addAndGet(gas); } - final HapiSpec justDoOne() { + final Stream justDoOne() { final var civilian = "civilian"; final int[] firstTargets = {19, 24}; final int[] secondTargets = {30, 31}; diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/perf/contract/MixedSmartContractOpsLoadTest.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/perf/contract/MixedSmartContractOpsLoadTest.java deleted file mode 100644 index 51854c0848a7..000000000000 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/perf/contract/MixedSmartContractOpsLoadTest.java +++ /dev/null @@ -1,143 +0,0 @@ -/* - * Copyright (C) 2020-2024 Hedera Hashgraph, LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.hedera.services.bdd.suites.perf.contract; - -import static com.hedera.services.bdd.spec.HapiSpec.defaultHapiSpec; -import static com.hedera.services.bdd.spec.keys.KeyFactory.KeyType.THRESHOLD; -import static com.hedera.services.bdd.spec.queries.QueryVerbs.contractCallLocal; -import static com.hedera.services.bdd.spec.queries.QueryVerbs.getContractInfo; -import static com.hedera.services.bdd.spec.transactions.TxnUtils.randomUtf8Bytes; -import static com.hedera.services.bdd.spec.transactions.TxnVerbs.contractCall; -import static com.hedera.services.bdd.spec.transactions.TxnVerbs.contractCreate; -import static com.hedera.services.bdd.spec.transactions.TxnVerbs.contractUpdate; -import static com.hedera.services.bdd.spec.transactions.TxnVerbs.cryptoCreate; -import static com.hedera.services.bdd.spec.transactions.TxnVerbs.fileCreate; -import static com.hedera.services.bdd.spec.transactions.TxnVerbs.uploadInitCode; -import static com.hedera.services.bdd.spec.utilops.UtilVerbs.logIt; -import static com.hedera.services.bdd.spec.utilops.UtilVerbs.withOpContext; - -import com.hedera.services.bdd.spec.HapiSpec; -import com.hedera.services.bdd.spec.HapiSpecOperation; -import com.hedera.services.bdd.spec.HapiSpecSetup; -import com.hedera.services.bdd.spec.utilops.LoadTest; -import com.hedera.services.bdd.suites.perf.PerfTestLoadSettings; -import java.math.BigInteger; -import java.util.List; -import java.util.concurrent.atomic.AtomicInteger; -import java.util.function.Supplier; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; - -/** - * Run mixed operations including ContractCreate, ContractUpdate, ContractCallLocal, ContractCall, - * ContractInfo - */ -public class MixedSmartContractOpsLoadTest extends LoadTest { - private static final Logger log = LogManager.getLogger(MixedSmartContractOpsLoadTest.class); - - public static void main(String... args) { - parseArgs(args); - - MixedSmartContractOpsLoadTest suite = new MixedSmartContractOpsLoadTest(); - suite.runSuiteSync(); - } - - @Override - public List getSpecsInSuite() { - return List.of(runMixedSmartContractOps()); - } - - protected HapiSpec runMixedSmartContractOps() { - PerfTestLoadSettings settings = new PerfTestLoadSettings(); - final AtomicInteger createdSoFar = new AtomicInteger(0); - final String SOME_BYTE_CODE = "contractByteCode"; - final String UPDATABLE_CONTRACT = "updatableContract"; - final String CONTRACT_NAME_PREFIX = "testContract"; - final String PAYABLE_CONTRACT = "PayReceivable"; - final String LOOKUP_CONTRACT = "BalanceLookup"; - final String CIVILIAN_ACCOUNT = "civilian"; - final BigInteger depositAmount = BigInteger.ONE; - - Supplier mixedOpsBurst = () -> new HapiSpecOperation[] { - /* create a contract */ - contractCreate(CONTRACT_NAME_PREFIX + createdSoFar.getAndIncrement()) - .bytecode(SOME_BYTE_CODE) - .hasAnyPrecheck() - .deferStatusResolution(), - - /* update the memo and do get info on the contract that needs to be updated */ - contractUpdate(UPDATABLE_CONTRACT) - .newMemo(new String(randomUtf8Bytes(memoLength.getAsInt()))) - .hasAnyPrecheck() - .deferStatusResolution(), - - /* call balance lookup contract and contract to deposit funds*/ - contractCallLocal(LOOKUP_CONTRACT, "lookup", spec -> new Object[] { - BigInteger.valueOf( - spec.registry().getAccountID(CIVILIAN_ACCOUNT).getAccountNum()) - }) - .payingWith(GENESIS), - contractCall(PAYABLE_CONTRACT, "deposit", depositAmount) - .sending(depositAmount.longValueExact()) - .suppressStats(true) - .deferStatusResolution() - }; - return defaultHapiSpec("RunMixedSmartContractOps") - .given( - withOpContext( - (spec, ignore) -> settings.setFrom(spec.setup().ciPropertiesMap())), - logIt(ignore -> settings.toString())) - .when( - /* create an account */ - cryptoCreate(CIVILIAN_ACCOUNT).balance(ONE_HUNDRED_HBARS), - - /* create a file with some contents and contract with it */ - fileCreate(SOME_BYTE_CODE) - .path(HapiSpecSetup.getDefaultInstance().defaultContractPath()), - contractCreate(UPDATABLE_CONTRACT) - .bytecode(SOME_BYTE_CODE) - .adminKey(THRESHOLD), - - /* create a contract which does a query to look up balance of the civilan account */ - uploadInitCode(LOOKUP_CONTRACT), - contractCreate(LOOKUP_CONTRACT).adminKey(THRESHOLD), - - /* create a contract that does a transaction to deposit funds */ - uploadInitCode(PAYABLE_CONTRACT), - contractCreate(PAYABLE_CONTRACT).adminKey(THRESHOLD), - - /* get contract info on all contracts created */ - getContractInfo(LOOKUP_CONTRACT) - .payingWith(GENESIS) - .hasExpectedInfo() - .logged(), - getContractInfo(PAYABLE_CONTRACT) - .payingWith(GENESIS) - .hasExpectedInfo() - .logged(), - getContractInfo(UPDATABLE_CONTRACT) - .payingWith(GENESIS) - .hasExpectedInfo() - .logged()) - .then(defaultLoadTest(mixedOpsBurst, settings)); - } - - @Override - protected Logger getResultsLogger() { - return log; - } -} diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/perf/contract/opcodes/SStoreOperationLoadTest.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/perf/contract/opcodes/SStoreOperationLoadTest.java deleted file mode 100644 index 61f176a3093e..000000000000 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/perf/contract/opcodes/SStoreOperationLoadTest.java +++ /dev/null @@ -1,107 +0,0 @@ -/* - * Copyright (C) 2020-2024 Hedera Hashgraph, LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.hedera.services.bdd.suites.perf.contract.opcodes; - -import static com.hedera.services.bdd.spec.HapiSpec.defaultHapiSpec; -import static com.hedera.services.bdd.spec.queries.QueryVerbs.getContractInfo; -import static com.hedera.services.bdd.spec.transactions.TxnVerbs.contractCall; -import static com.hedera.services.bdd.spec.transactions.TxnVerbs.contractCreate; -import static com.hedera.services.bdd.spec.transactions.TxnVerbs.cryptoCreate; -import static com.hedera.services.bdd.spec.transactions.TxnVerbs.uploadSingleInitCode; -import static com.hedera.services.bdd.spec.utilops.UtilVerbs.logIt; -import static com.hedera.services.bdd.spec.utilops.UtilVerbs.withOpContext; -import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.BUSY; -import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.DUPLICATE_TRANSACTION; -import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.PLATFORM_TRANSACTION_NOT_CREATED; -import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.SUCCESS; -import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.UNKNOWN; - -import com.hedera.services.bdd.spec.HapiSpec; -import com.hedera.services.bdd.spec.HapiSpecOperation; -import com.hedera.services.bdd.spec.utilops.LoadTest; -import com.hedera.services.bdd.suites.perf.PerfTestLoadSettings; -import java.util.List; -import java.util.concurrent.atomic.AtomicInteger; -import java.util.function.Supplier; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; - -public class SStoreOperationLoadTest extends LoadTest { - private static final Logger log = LogManager.getLogger(SStoreOperationLoadTest.class); - private static int size = 4; - - public static void main(String... args) { - int usedArgs = parseArgs(args); - - // parsing local argument specific to this test - if (args.length > usedArgs) { - size = Integer.parseInt(args[usedArgs]); - log.info("Set sizeInKb as {}", size); - } - - SStoreOperationLoadTest suite = new SStoreOperationLoadTest(); - suite.runSuiteSync(); - } - - @Override - public List getSpecsInSuite() { - return List.of(runContractCalls()); - } - - final HapiSpec runContractCalls() { - final var contract = "BigArray"; - PerfTestLoadSettings settings = new PerfTestLoadSettings(); - final AtomicInteger submittedSoFar = new AtomicInteger(0); - long setValue = 0x1234abdeL; - Supplier callBurst = () -> new HapiSpecOperation[] { - contractCall(contract, "changeArray", setValue) - .noLogging() - .payingWith("sender") - .suppressStats(true) - .hasKnownStatusFrom(UNKNOWN, SUCCESS) - .hasRetryPrecheckFrom(BUSY, DUPLICATE_TRANSACTION, PLATFORM_TRANSACTION_NOT_CREATED) - .deferStatusResolution() - }; - - return defaultHapiSpec("runContractCalls") - .given( - withOpContext( - (spec, ignore) -> settings.setFrom(spec.setup().ciPropertiesMap())), - logIt(ignore -> settings.toString())) - .when( - cryptoCreate("sender") - .balance(initialBalance.getAsLong()) - .withRecharging() - .rechargeWindow(3) - .hasRetryPrecheckFrom(BUSY, DUPLICATE_TRANSACTION, PLATFORM_TRANSACTION_NOT_CREATED), - uploadSingleInitCode(contract, BUSY, DUPLICATE_TRANSACTION, PLATFORM_TRANSACTION_NOT_CREATED), - contractCreate(contract) - .hasRetryPrecheckFrom(BUSY, DUPLICATE_TRANSACTION, PLATFORM_TRANSACTION_NOT_CREATED), - getContractInfo(contract).hasExpectedInfo().logged(), - - // Initialize storage size - contractCall(contract, "setSize", size) - .hasRetryPrecheckFrom(BUSY, DUPLICATE_TRANSACTION, PLATFORM_TRANSACTION_NOT_CREATED) - .gas(300_000)) - .then(defaultLoadTest(callBurst, settings)); - } - - @Override - protected Logger getResultsLogger() { - return log; - } -} diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/perf/crypto/CryptoAllowancePerfSuite.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/perf/crypto/CryptoAllowancePerfSuite.java index aac4eae70653..276bf7d07dd2 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/perf/crypto/CryptoAllowancePerfSuite.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/perf/crypto/CryptoAllowancePerfSuite.java @@ -22,11 +22,12 @@ import static com.hedera.services.bdd.spec.transactions.TxnVerbs.tokenCreate; import static com.hedera.services.bdd.spec.utilops.UtilVerbs.inParallel; -import com.hedera.services.bdd.spec.HapiSpec; import com.hedera.services.bdd.spec.utilops.LoadTest; import java.util.List; +import java.util.stream.Stream; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; +import org.junit.jupiter.api.DynamicTest; public class CryptoAllowancePerfSuite extends LoadTest { @@ -41,11 +42,11 @@ public static void main(String... args) { } @Override - public List getSpecsInSuite() { + public List> getSpecsInSuite() { return List.of(runCryptoCreatesAndTokenCreates(), runCryptoAllowances()); } - final HapiSpec runCryptoCreatesAndTokenCreates() { + final Stream runCryptoCreatesAndTokenCreates() { final int NUM_CREATES = 5000; return defaultHapiSpec("runCryptoCreatesAndTokenCreates") .given() @@ -96,7 +97,7 @@ final HapiSpec runCryptoCreatesAndTokenCreates() { .deferStatusResolution()))); } - final HapiSpec runCryptoAllowances() { + final Stream runCryptoAllowances() { final int NUM_ALLOWANCES = 5000; return defaultHapiSpec("runCryptoAllowances") .given() diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/perf/crypto/CryptoCreatePerfSuite.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/perf/crypto/CryptoCreatePerfSuite.java deleted file mode 100644 index 8c4d1c0b9047..000000000000 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/perf/crypto/CryptoCreatePerfSuite.java +++ /dev/null @@ -1,70 +0,0 @@ -/* - * Copyright (C) 2020-2024 Hedera Hashgraph, LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.hedera.services.bdd.suites.perf.crypto; - -import static com.hedera.services.bdd.spec.HapiSpec.defaultHapiSpec; -import static com.hedera.services.bdd.spec.transactions.TxnVerbs.cryptoCreate; -import static com.hedera.services.bdd.spec.utilops.UtilVerbs.freezeOnly; -import static com.hedera.services.bdd.spec.utilops.UtilVerbs.inParallel; - -import com.hedera.services.bdd.spec.HapiSpec; -import com.hedera.services.bdd.spec.utilops.LoadTest; -import java.util.List; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; - -public class CryptoCreatePerfSuite extends LoadTest { - private static final Logger log = LogManager.getLogger(CryptoCreatePerfSuite.class); - - public static void main(String... args) { - CryptoCreatePerfSuite suite = new CryptoCreatePerfSuite(); - suite.runSuiteSync(); - } - - @Override - public List getSpecsInSuite() { - return List.of(runCryptoCreates()); - } - - final HapiSpec runCryptoCreates() { - final int NUM_CREATES = 1000000; - return defaultHapiSpec("cryptoCreatePerf") - .given() - .when(inParallel(asOpArray( - NUM_CREATES, - i -> (i == (NUM_CREATES - 1)) - ? cryptoCreate("testAccount" + i) - .balance(100_000_000_000L) - .key(GENESIS) - .withRecharging() - .rechargeWindow(30) - .payingWith(GENESIS) - : cryptoCreate("testAccount" + i) - .balance(100_000_000_000L) - .key(GENESIS) - .withRecharging() - .rechargeWindow(30) - .payingWith(GENESIS) - .deferStatusResolution()))) - .then(freezeOnly().payingWith(GENESIS).startingIn(60).seconds()); - } - - @Override - protected Logger getResultsLogger() { - return log; - } -} diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/perf/crypto/CryptoTransferLoadTest.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/perf/crypto/CryptoTransferLoadTest.java deleted file mode 100644 index 3541e3ce5c9c..000000000000 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/perf/crypto/CryptoTransferLoadTest.java +++ /dev/null @@ -1,133 +0,0 @@ -/* - * Copyright (C) 2020-2024 Hedera Hashgraph, LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.hedera.services.bdd.suites.perf.crypto; - -import static com.hedera.services.bdd.spec.HapiSpec.defaultHapiSpec; -import static com.hedera.services.bdd.spec.queries.QueryVerbs.getAccountBalance; -import static com.hedera.services.bdd.spec.transactions.TxnVerbs.cryptoCreate; -import static com.hedera.services.bdd.spec.transactions.TxnVerbs.cryptoTransfer; -import static com.hedera.services.bdd.spec.transactions.crypto.HapiCryptoTransfer.tinyBarsFromTo; -import static com.hedera.services.bdd.spec.utilops.UtilVerbs.logIt; -import static com.hedera.services.bdd.spec.utilops.UtilVerbs.withOpContext; -import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.BUSY; -import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.DUPLICATE_TRANSACTION; -import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.INSUFFICIENT_ACCOUNT_BALANCE; -import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.INSUFFICIENT_PAYER_BALANCE; -import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.OK; -import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.PLATFORM_TRANSACTION_NOT_CREATED; -import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.SUCCESS; -import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.TRANSACTION_EXPIRED; -import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.UNKNOWN; - -import com.hedera.services.bdd.junit.HapiTest; -import com.hedera.services.bdd.spec.HapiSpec; -import com.hedera.services.bdd.spec.HapiSpecOperation; -import com.hedera.services.bdd.spec.utilops.LoadTest; -import com.hedera.services.bdd.suites.perf.PerfTestLoadSettings; -import java.util.List; -import java.util.Random; -import java.util.function.Supplier; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; - -public class CryptoTransferLoadTest extends LoadTest { - private static final Logger log = LogManager.getLogger(CryptoTransferLoadTest.class); - - @SuppressWarnings("java:S2245") // using java.util.Random in tests is fine - private Random r = new Random(528352L); - - private static final long TEST_ACCOUNT_STARTS_FROM = 1001L; - - public static void main(String... args) { - parseArgs(args); - - CryptoTransferLoadTest suite = new CryptoTransferLoadTest(); - suite.runSuiteSync(); - } - - @Override - public List getSpecsInSuite() { - return List.of(runCryptoTransfers()); - } - - @HapiTest - protected HapiSpec runCryptoTransfers() { - PerfTestLoadSettings settings = new PerfTestLoadSettings(); - - Supplier transferBurst = () -> { - String sender = "sender"; - String receiver = "receiver"; - if (settings.getTotalAccounts() > 2) { - int s = r.nextInt(settings.getTotalAccounts()); - int re = 0; - do { - re = r.nextInt(settings.getTotalAccounts()); - } while (re == s); - sender = String.format("0.0.%d", TEST_ACCOUNT_STARTS_FROM + s); - receiver = String.format("0.0.%d", TEST_ACCOUNT_STARTS_FROM + re); - } - - return new HapiSpecOperation[] { - cryptoTransfer(tinyBarsFromTo(sender, receiver, 1L)) - .noLogging() - .payingWith(sender) - .signedBy(GENESIS) - .suppressStats(true) - .fee(100_000_000L) - .hasKnownStatusFrom( - SUCCESS, - OK, - INSUFFICIENT_PAYER_BALANCE, - UNKNOWN, - TRANSACTION_EXPIRED, - INSUFFICIENT_ACCOUNT_BALANCE) - .hasRetryPrecheckFrom(BUSY, PLATFORM_TRANSACTION_NOT_CREATED) - .deferStatusResolution() - }; - }; - - return defaultHapiSpec("RunCryptoTransfers-LoadTest") - .given( - withOpContext( - (spec, ignore) -> settings.setFrom(spec.setup().ciPropertiesMap())), - logIt(ignore -> settings.toString())) - .when( - cryptoCreate("sender") - .balance(ignore -> settings.getInitialBalance()) - .payingWith(GENESIS) - .withRecharging() - .key(GENESIS) - .rechargeWindow(3) - .stakedNodeId(settings.getNodeToStake()) - .logging() - .hasRetryPrecheckFrom(BUSY, DUPLICATE_TRANSACTION, PLATFORM_TRANSACTION_NOT_CREATED), - cryptoCreate("receiver") - .payingWith(GENESIS) - .stakedNodeId(settings.getNodeToStake()) - .hasRetryPrecheckFrom(BUSY, DUPLICATE_TRANSACTION, PLATFORM_TRANSACTION_NOT_CREATED) - .key(GENESIS) - .logging()) - .then( - defaultLoadTest(transferBurst, settings), - getAccountBalance("sender").logged()); - } - - @Override - protected Logger getResultsLogger() { - return log; - } -} diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/perf/crypto/CryptoTransferLoadTestWithAutoAccounts.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/perf/crypto/CryptoTransferLoadTestWithAutoAccounts.java deleted file mode 100644 index 5e65969a842a..000000000000 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/perf/crypto/CryptoTransferLoadTestWithAutoAccounts.java +++ /dev/null @@ -1,164 +0,0 @@ -/* - * Copyright (C) 2020-2024 Hedera Hashgraph, LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.hedera.services.bdd.suites.perf.crypto; - -import static com.hedera.services.bdd.spec.HapiSpec.defaultHapiSpec; -import static com.hedera.services.bdd.spec.queries.QueryVerbs.getAccountBalance; -import static com.hedera.services.bdd.spec.transactions.TxnVerbs.cryptoCreate; -import static com.hedera.services.bdd.spec.transactions.TxnVerbs.cryptoTransfer; -import static com.hedera.services.bdd.spec.transactions.TxnVerbs.fileUpdate; -import static com.hedera.services.bdd.spec.transactions.crypto.HapiCryptoTransfer.tinyBarsFromTo; -import static com.hedera.services.bdd.spec.transactions.crypto.HapiCryptoTransfer.tinyBarsFromToWithAlias; -import static com.hedera.services.bdd.spec.utilops.CustomSpecAssert.allRunFor; -import static com.hedera.services.bdd.spec.utilops.UtilVerbs.logIt; -import static com.hedera.services.bdd.spec.utilops.UtilVerbs.newKeyNamed; -import static com.hedera.services.bdd.spec.utilops.UtilVerbs.withOpContext; -import static com.hedera.services.bdd.suites.utils.sysfiles.serdes.ThrottleDefsLoader.protoDefsFromResource; -import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.BUSY; -import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.DUPLICATE_TRANSACTION; -import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.INSUFFICIENT_ACCOUNT_BALANCE; -import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.INSUFFICIENT_PAYER_BALANCE; -import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.OK; -import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.PLATFORM_TRANSACTION_NOT_CREATED; -import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.SUCCESS; -import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.TRANSACTION_EXPIRED; -import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.UNKNOWN; - -import com.hedera.services.bdd.spec.HapiSpec; -import com.hedera.services.bdd.spec.HapiSpecOperation; -import com.hedera.services.bdd.spec.utilops.LoadTest; -import com.hedera.services.bdd.suites.perf.PerfTestLoadSettings; -import java.util.ArrayList; -import java.util.List; -import java.util.Random; -import java.util.function.Supplier; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; - -public class CryptoTransferLoadTestWithAutoAccounts extends LoadTest { - private static final Logger log = LogManager.getLogger(CryptoTransferLoadTestWithAutoAccounts.class); - - @SuppressWarnings("java:S2245") // using java.util.Random in tests is fine - private Random r = new Random(396330L); - - private static final int AUTO_ACCOUNTS = 20; - - public static void main(String... args) { - parseArgs(args); - - CryptoTransferLoadTestWithAutoAccounts suite = new CryptoTransferLoadTestWithAutoAccounts(); - suite.runSuiteSync(); - } - - @Override - public List getSpecsInSuite() { - return List.of(runCryptoTransfers()); - } - - protected HapiSpec runCryptoTransfers() { - PerfTestLoadSettings settings = new PerfTestLoadSettings(); - - Supplier transferBurst = () -> { - String sender = "sender"; - String receiver; - - if (r.nextInt(10) < 5) { - receiver = "alias" + r.nextInt(AUTO_ACCOUNTS); - } else { - receiver = "receiver"; - } - - if (receiver.startsWith("alias")) { - return new HapiSpecOperation[] { - cryptoTransfer(tinyBarsFromToWithAlias(sender, receiver, 1L)) - .logging() - .payingWith(sender) - .signedBy(GENESIS) - .suppressStats(true) - .fee(100_000_000L) - .hasKnownStatusFrom( - SUCCESS, - OK, - INSUFFICIENT_PAYER_BALANCE, - UNKNOWN, - TRANSACTION_EXPIRED, - INSUFFICIENT_ACCOUNT_BALANCE) - .hasRetryPrecheckFrom(BUSY, PLATFORM_TRANSACTION_NOT_CREATED) - .deferStatusResolution() - }; - } - - return new HapiSpecOperation[] { - cryptoTransfer(tinyBarsFromTo(sender, receiver, 1L)) - .noLogging() - .payingWith(sender) - .signedBy(GENESIS) - .suppressStats(true) - .fee(100_000_000L) - .hasKnownStatusFrom( - SUCCESS, - OK, - INSUFFICIENT_PAYER_BALANCE, - UNKNOWN, - TRANSACTION_EXPIRED, - INSUFFICIENT_ACCOUNT_BALANCE) - .hasRetryPrecheckFrom(BUSY, PLATFORM_TRANSACTION_NOT_CREATED) - .deferStatusResolution() - }; - }; - - var defaultThrottles = protoDefsFromResource("testSystemFiles/throttles-dev.json"); - - return defaultHapiSpec("RunCryptoTransfersWithAutoAccounts") - .given( - withOpContext( - (spec, ignore) -> settings.setFrom(spec.setup().ciPropertiesMap())), - logIt(ignore -> settings.toString())) - .when( - fileUpdate(THROTTLE_DEFS).payingWith(GENESIS).contents(defaultThrottles.toByteArray()), - cryptoCreate("sender") - .balance(ignore -> settings.getInitialBalance()) - .payingWith(GENESIS) - .withRecharging() - .key(GENESIS) - .rechargeWindow(3) - .logging() - .hasRetryPrecheckFrom(BUSY, DUPLICATE_TRANSACTION, PLATFORM_TRANSACTION_NOT_CREATED), - cryptoCreate("receiver") - .payingWith(GENESIS) - .hasRetryPrecheckFrom(BUSY, DUPLICATE_TRANSACTION, PLATFORM_TRANSACTION_NOT_CREATED) - .key(GENESIS) - .logging(), - withOpContext((spec, opLog) -> { - List ops = new ArrayList<>(); - for (int i = 0; i < AUTO_ACCOUNTS; i++) { - var alias = "alias" + i; - ops.add(newKeyNamed(alias)); - ops.add(cryptoTransfer(tinyBarsFromToWithAlias(DEFAULT_PAYER, alias, ONE_HBAR))); - } - allRunFor(spec, ops); - })) - .then( - defaultLoadTest(transferBurst, settings), - getAccountBalance("sender").logged()); - } - - @Override - protected Logger getResultsLogger() { - return log; - } -} diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/perf/crypto/CryptoTransferLoadTestWithInvalidAccounts.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/perf/crypto/CryptoTransferLoadTestWithInvalidAccounts.java deleted file mode 100644 index c457ab529482..000000000000 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/perf/crypto/CryptoTransferLoadTestWithInvalidAccounts.java +++ /dev/null @@ -1,78 +0,0 @@ -/* - * Copyright (C) 2020-2024 Hedera Hashgraph, LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.hedera.services.bdd.suites.perf.crypto; - -import static com.hedera.services.bdd.spec.HapiSpec.defaultHapiSpec; -import static com.hedera.services.bdd.spec.transactions.TxnVerbs.cryptoTransfer; -import static com.hedera.services.bdd.spec.transactions.crypto.HapiCryptoTransfer.tinyBarsFromTo; -import static com.hedera.services.bdd.spec.utilops.UtilVerbs.logIt; -import static com.hedera.services.bdd.spec.utilops.UtilVerbs.withOpContext; -import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.BUSY; -import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.INVALID_ACCOUNT_ID; -import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.PLATFORM_TRANSACTION_NOT_CREATED; - -import com.hedera.services.bdd.spec.HapiSpec; -import com.hedera.services.bdd.spec.HapiSpecOperation; -import com.hedera.services.bdd.spec.utilops.LoadTest; -import com.hedera.services.bdd.suites.perf.PerfTestLoadSettings; -import java.util.List; -import java.util.function.Supplier; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; - -public final class CryptoTransferLoadTestWithInvalidAccounts extends LoadTest { - private static final Logger log = LogManager.getLogger(CryptoTransferLoadTestWithInvalidAccounts.class); - - public static void main(String... args) { - parseArgs(args); - - CryptoTransferLoadTestWithInvalidAccounts suite = new CryptoTransferLoadTestWithInvalidAccounts(); - suite.runSuiteSync(); - } - - @Override - public List getSpecsInSuite() { - return List.of(runCryptoTransfers()); - } - - protected HapiSpec runCryptoTransfers() { - PerfTestLoadSettings settings = new PerfTestLoadSettings(); - - Supplier transferBurst = () -> new HapiSpecOperation[] { - cryptoTransfer(tinyBarsFromTo("0.0.1000000001", "0.0.1000000002", 1L)) - .noLogging() - .signedBy(GENESIS) - .suppressStats(true) - .fee(100_000_000L) - .hasKnownStatusFrom(INVALID_ACCOUNT_ID) - .hasRetryPrecheckFrom(BUSY, PLATFORM_TRANSACTION_NOT_CREATED) - }; - - return defaultHapiSpec("RunCryptoTransfers-InvalidAccount") - .given( - withOpContext( - (spec, ignore) -> settings.setFrom(spec.setup().ciPropertiesMap())), - logIt(ignore -> settings.toString())) - .when() - .then(defaultLoadTest(transferBurst, settings)); - } - - @Override - protected Logger getResultsLogger() { - return log; - } -} diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/perf/crypto/CryptoTransferLoadTestWithStakedAccounts.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/perf/crypto/CryptoTransferLoadTestWithStakedAccounts.java index c42ae2f92536..0a0839a418f2 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/perf/crypto/CryptoTransferLoadTestWithStakedAccounts.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/perf/crypto/CryptoTransferLoadTestWithStakedAccounts.java @@ -26,7 +26,6 @@ import static com.hedera.services.bdd.spec.utilops.UtilVerbs.withOpContext; import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.*; -import com.hedera.services.bdd.spec.HapiSpec; import com.hedera.services.bdd.spec.HapiSpecOperation; import com.hedera.services.bdd.spec.utilops.LoadTest; import com.hedera.services.bdd.suites.perf.PerfTestLoadSettings; @@ -34,8 +33,10 @@ import java.util.List; import java.util.Random; import java.util.function.Supplier; +import java.util.stream.Stream; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; +import org.junit.jupiter.api.DynamicTest; public class CryptoTransferLoadTestWithStakedAccounts extends LoadTest { private static final Logger log = LogManager.getLogger(CryptoTransferLoadTestWithStakedAccounts.class); @@ -54,11 +55,11 @@ public static void main(String... args) { } @Override - public List getSpecsInSuite() { + public List> getSpecsInSuite() { return List.of(runCryptoTransfers()); } - protected HapiSpec runCryptoTransfers() { + final Stream runCryptoTransfers() { PerfTestLoadSettings settings = new PerfTestLoadSettings(); Supplier transferBurst = () -> { @@ -79,7 +80,6 @@ protected HapiSpec runCryptoTransfers() { .noLogging() .payingWith(sender) .signedBy(GENESIS) - .suppressStats(true) .fee(100_000_000L) .hasKnownStatusFrom( SUCCESS, diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/perf/crypto/CryptoTransferPerfSuite.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/perf/crypto/CryptoTransferPerfSuite.java deleted file mode 100644 index 09abc79ddc9a..000000000000 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/perf/crypto/CryptoTransferPerfSuite.java +++ /dev/null @@ -1,88 +0,0 @@ -/* - * Copyright (C) 2020-2024 Hedera Hashgraph, LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.hedera.services.bdd.suites.perf.crypto; - -import static com.hedera.services.bdd.spec.HapiSpec.defaultHapiSpec; -import static com.hedera.services.bdd.spec.queries.QueryVerbs.getAccountBalance; -import static com.hedera.services.bdd.spec.queries.QueryVerbs.getAccountRecords; -import static com.hedera.services.bdd.spec.transactions.TxnVerbs.cryptoCreate; -import static com.hedera.services.bdd.spec.transactions.TxnVerbs.cryptoTransfer; -import static com.hedera.services.bdd.spec.transactions.crypto.HapiCryptoTransfer.tinyBarsFromTo; -import static com.hedera.services.bdd.spec.utilops.UtilVerbs.finishThroughputObs; -import static com.hedera.services.bdd.spec.utilops.UtilVerbs.inParallel; -import static com.hedera.services.bdd.spec.utilops.UtilVerbs.startThroughputObs; - -import com.hedera.services.bdd.spec.HapiSpec; -import com.hedera.services.bdd.suites.HapiSuite; -import java.util.Arrays; -import java.util.List; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; - -public class CryptoTransferPerfSuite extends HapiSuite { - private static final Logger log = LogManager.getLogger(CryptoTransferPerfSuite.class); - - public static void main(String... args) { - CryptoTransferPerfSuite suite = new CryptoTransferPerfSuite(); - suite.runSuiteSync(); - } - - @Override - public List getSpecsInSuite() { - return Arrays.asList(cryptoTransferPerf()); - } - - @Override - public boolean canRunConcurrent() { - return false; - } - - final HapiSpec cryptoTransferPerf() { - final int NUM_ACCOUNTS = 10; - final int NUM_TRANSFERS = 10_000; - final long INIT_BALANCE = 100_000_000_000L; - final long ENDING_ZERO_ACCOUNT_BALANCE = (2L * NUM_TRANSFERS); - - return defaultHapiSpec("CryptoTransferPerf") - .given(inParallel(asOpArray( - NUM_ACCOUNTS + 1, - i -> (i == 0) - ? cryptoCreate("account0").balance(0L) - : cryptoCreate("account" + i).sendThreshold(1L).balance(INIT_BALANCE)))) - .when( - startThroughputObs("transferThroughput").msToSaturateQueues(50L), - inParallel(asOpArray(NUM_TRANSFERS, i -> cryptoTransfer( - tinyBarsFromTo("account" + (i % NUM_ACCOUNTS + 1), "account0", 2L)) - .deferStatusResolution() - .hasAnyStatusAtAll()))) - .then( - finishThroughputObs("transferThroughput") - .gatedByQuery(() -> getAccountBalance("account0") - .hasTinyBars(ENDING_ZERO_ACCOUNT_BALANCE) - .noLogging()) - .sleepMs(1_000L) - .expiryMs(300_000L), - getAccountRecords("account1") - .withLogging((log, records) -> log.info(String.format("%d records!", records.size()))) - .savingTo("record-snapshots")); - } - - @Override - protected Logger getResultsLogger() { - return log; - } -} diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/perf/crypto/CryptoTransferPerfSuiteWOpProvider.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/perf/crypto/CryptoTransferPerfSuiteWOpProvider.java deleted file mode 100644 index 1e588d3becc0..000000000000 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/perf/crypto/CryptoTransferPerfSuiteWOpProvider.java +++ /dev/null @@ -1,112 +0,0 @@ -/* - * Copyright (C) 2020-2024 Hedera Hashgraph, LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.hedera.services.bdd.suites.perf.crypto; - -import static com.hedera.services.bdd.spec.HapiSpec.defaultHapiSpec; -import static com.hedera.services.bdd.spec.transactions.TxnVerbs.cryptoCreate; -import static com.hedera.services.bdd.spec.transactions.TxnVerbs.cryptoTransfer; -import static com.hedera.services.bdd.spec.transactions.crypto.HapiCryptoTransfer.tinyBarsFromTo; -import static com.hedera.services.bdd.spec.utilops.UtilVerbs.logIt; -import static com.hedera.services.bdd.spec.utilops.UtilVerbs.runWithProvider; -import static com.hedera.services.bdd.spec.utilops.UtilVerbs.sleepFor; -import static com.hedera.services.bdd.spec.utilops.UtilVerbs.withOpContext; -import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.BUSY; -import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.DUPLICATE_TRANSACTION; -import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.INSUFFICIENT_ACCOUNT_BALANCE; -import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.INSUFFICIENT_PAYER_BALANCE; -import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.INVALID_SIGNATURE; -import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.OK; -import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.PAYER_ACCOUNT_NOT_FOUND; -import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.PLATFORM_TRANSACTION_NOT_CREATED; -import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.SUCCESS; -import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.TRANSACTION_EXPIRED; -import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.UNKNOWN; -import static java.util.concurrent.TimeUnit.MINUTES; - -import com.hedera.services.bdd.spec.HapiSpec; -import com.hedera.services.bdd.spec.HapiSpecOperation; -import com.hedera.services.bdd.spec.infrastructure.OpProvider; -import com.hedera.services.bdd.suites.HapiSuite; -import com.hedera.services.bdd.suites.perf.PerfTestLoadSettings; -import java.util.List; -import java.util.Optional; -import java.util.function.Function; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; - -public class CryptoTransferPerfSuiteWOpProvider extends HapiSuite { - private static final Logger log = LogManager.getLogger(CryptoTransferPerfSuiteWOpProvider.class); - - public static void main(String... args) { - CryptoTransferPerfSuiteWOpProvider suite = new CryptoTransferPerfSuiteWOpProvider(); - suite.runSuiteSync(); - } - - @Override - public List getSpecsInSuite() { - return List.of(runMixedTransferAndSubmits()); - } - - final HapiSpec runMixedTransferAndSubmits() { - PerfTestLoadSettings settings = new PerfTestLoadSettings(); - return defaultHapiSpec("CryptoTransferPerfSuiteWOpProvider") - .given( - withOpContext( - (spec, ignore) -> settings.setFrom(spec.setup().ciPropertiesMap())), - logIt(ignore -> settings.toString())) - .when() - .then(runWithProvider(XfersFactory()) - .lasting(settings::getMins, () -> MINUTES) - .maxOpsPerSec(settings::getTps)); - } - - private Function XfersFactory() { - return spec -> new OpProvider() { - @Override - public List suggestedInitializers() { - return List.of( - cryptoCreate("sender") - .balance(ONE_HUNDRED_HBARS) - .hasRetryPrecheckFrom(BUSY, PLATFORM_TRANSACTION_NOT_CREATED), - cryptoCreate("receiver").hasRetryPrecheckFrom(BUSY, PLATFORM_TRANSACTION_NOT_CREATED), - sleepFor(10_000L)); - } - - @Override - public Optional get() { - final var op = cryptoTransfer(tinyBarsFromTo("sender", "receiver", 1L)) - .noLogging() - .hasKnownStatusFrom( - SUCCESS, - OK, - INSUFFICIENT_PAYER_BALANCE, - UNKNOWN, - TRANSACTION_EXPIRED, - INSUFFICIENT_ACCOUNT_BALANCE) - .hasPrecheckFrom(DUPLICATE_TRANSACTION, OK, INVALID_SIGNATURE) - .hasRetryPrecheckFrom(BUSY, PLATFORM_TRANSACTION_NOT_CREATED, PAYER_ACCOUNT_NOT_FOUND) - .deferStatusResolution(); - return Optional.of(op); - } - }; - } - - @Override - protected Logger getResultsLogger() { - return log; - } -} diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/perf/crypto/NWayDistNoHotspots.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/perf/crypto/NWayDistNoHotspots.java deleted file mode 100644 index 7e1e31af1a1f..000000000000 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/perf/crypto/NWayDistNoHotspots.java +++ /dev/null @@ -1,156 +0,0 @@ -/* - * Copyright (C) 2020-2024 Hedera Hashgraph, LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.hedera.services.bdd.suites.perf.crypto; - -import static com.hedera.services.bdd.spec.HapiSpec.customHapiSpec; -import static com.hedera.services.bdd.spec.transactions.TxnVerbs.cryptoCreate; -import static com.hedera.services.bdd.spec.transactions.TxnVerbs.cryptoTransfer; -import static com.hedera.services.bdd.spec.transactions.token.TokenMovement.movingHbar; -import static com.hedera.services.bdd.spec.utilops.UtilVerbs.logIt; -import static com.hedera.services.bdd.spec.utilops.UtilVerbs.runWithProvider; -import static com.hedera.services.bdd.suites.perf.crypto.SimpleXfersAvoidingHotspot.uniqueQuietCreation; -import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.INSUFFICIENT_PAYER_BALANCE; -import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.OK; -import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.SUCCESS; -import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.TRANSACTION_EXPIRED; -import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.UNKNOWN; -import static java.util.concurrent.TimeUnit.SECONDS; - -import com.hedera.services.bdd.spec.HapiSpec; -import com.hedera.services.bdd.spec.HapiSpecOperation; -import com.hedera.services.bdd.spec.infrastructure.OpProvider; -import com.hedera.services.bdd.suites.HapiSuite; -import com.hederahashgraph.api.proto.java.ResponseCodeEnum; -import java.util.List; -import java.util.Map; -import java.util.Optional; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.atomic.AtomicInteger; -import java.util.concurrent.atomic.AtomicLong; -import java.util.concurrent.atomic.AtomicReference; -import java.util.function.Function; -import java.util.function.IntFunction; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; - -public class NWayDistNoHotspots extends HapiSuite { - private static final Logger log = LogManager.getLogger(NWayDistNoHotspots.class); - - private static final int XFER_DURATION = 600; - private static final int NUM_BENEFICIARIES = 3; - private static final int DISTRIBUTIONS_PER_SEC = 500; - private static final int CREATIONS_PER_SEC = 50; - private static final double ACCOUNT_BUFFER_PERCENTAGE = 10.0; - - private static final int NUM_ACCOUNTS = (int) - Math.ceil(DISTRIBUTIONS_PER_SEC * (1 + NUM_BENEFICIARIES) * (1.0 + ACCOUNT_BUFFER_PERCENTAGE / 100.0)); - - private final IntFunction nameFn = i -> "account" + i; - private final AtomicReference unit = new AtomicReference<>(SECONDS); - private final AtomicInteger maxOpsPerSec = new AtomicInteger(DISTRIBUTIONS_PER_SEC); - private final AtomicInteger maxCreatesPerSec = new AtomicInteger(CREATIONS_PER_SEC); - private final AtomicLong createDuration = new AtomicLong(NUM_ACCOUNTS / CREATIONS_PER_SEC); - private final AtomicLong duration = new AtomicLong(XFER_DURATION); - - public static void main(String... args) { - new NWayDistNoHotspots().runSuiteSync(); - } - - @Override - public List getSpecsInSuite() { - return List.of(new HapiSpec[] { - runDistributions(), - }); - } - - final HapiSpec runDistributions() { - return customHapiSpec("runCreations") - .withProperties(Map.of("default.keyAlgorithm", "ED25519")) - .given(logIt("Creating at least " - + NUM_ACCOUNTS - + " accounts to avoid hotspots while doing " - + DISTRIBUTIONS_PER_SEC - + (" " + NUM_BENEFICIARIES + "-way distributions/sec"))) - .when() - .then( - runWithProvider(creationsFactory()) - .lasting(createDuration::get, unit::get) - .maxOpsPerSec(maxCreatesPerSec::get), - runWithProvider(distributionsFactory()) - .lasting(duration::get, unit::get) - .maxOpsPerSec(maxOpsPerSec::get)); - } - - private Function creationsFactory() { - final var nextAccount = new AtomicInteger(); - final var payer = "metaPayer"; - - return spec -> new OpProvider() { - @Override - public List suggestedInitializers() { - return List.of(uniqueQuietCreation(payer)); - } - - @Override - public Optional get() { - final var nextI = nextAccount.getAndIncrement(); - return Optional.of(cryptoCreate(nameFn.apply(nextI)) - .balance(ONE_HUNDRED_HBARS) - .payingWith(payer) - .noLogging() - .deferStatusResolution()); - } - }; - } - - private Function distributionsFactory() { - final var nextSender = new AtomicInteger(); - final var n = NUM_ACCOUNTS / 100 * 99; - - return spec -> new OpProvider() { - @Override - public List suggestedInitializers() { - return List.of(); - } - - @Override - public Optional get() { - final int sender = nextSender.getAndUpdate(i -> (i + 1) % n); - final var from = nameFn.apply(sender); - final String[] tos = new String[NUM_BENEFICIARIES]; - for (int i = (sender + 1) % n, j = 0; j < NUM_BENEFICIARIES; i = (i + 1) % n, j++) { - tos[j] = nameFn.apply(i); - } - final var op = cryptoTransfer(movingHbar(NUM_BENEFICIARIES).distributing(from, tos)) - .payingWith(from) - .hasKnownStatusFrom(ACCEPTED_STATUSES) - .noLogging() - .deferStatusResolution(); - return Optional.of(op); - } - }; - } - - private static final ResponseCodeEnum[] ACCEPTED_STATUSES = { - SUCCESS, OK, INSUFFICIENT_PAYER_BALANCE, UNKNOWN, TRANSACTION_EXPIRED - }; - - @Override - protected Logger getResultsLogger() { - return log; - } -} diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/perf/crypto/SimpleXfersAvoidingHotspot.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/perf/crypto/SimpleXfersAvoidingHotspot.java deleted file mode 100644 index 5eadd6079b06..000000000000 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/perf/crypto/SimpleXfersAvoidingHotspot.java +++ /dev/null @@ -1,150 +0,0 @@ -/* - * Copyright (C) 2020-2024 Hedera Hashgraph, LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.hedera.services.bdd.suites.perf.crypto; - -import static com.hedera.services.bdd.spec.transactions.TxnVerbs.cryptoCreate; -import static com.hedera.services.bdd.spec.transactions.TxnVerbs.cryptoTransfer; -import static com.hedera.services.bdd.spec.transactions.crypto.HapiCryptoTransfer.tinyBarsFromTo; -import static com.hedera.services.bdd.spec.utilops.CustomSpecAssert.allRunFor; -import static com.hedera.services.bdd.spec.utilops.UtilVerbs.inParallel; -import static com.hedera.services.bdd.spec.utilops.UtilVerbs.runWithProvider; -import static com.hedera.services.bdd.spec.utilops.UtilVerbs.sleepFor; -import static com.hedera.services.bdd.spec.utilops.UtilVerbs.withOpContext; -import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.INSUFFICIENT_PAYER_BALANCE; -import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.OK; -import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.SUCCESS; -import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.TRANSACTION_EXPIRED; -import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.UNKNOWN; -import static java.util.concurrent.TimeUnit.SECONDS; - -import com.hedera.services.bdd.spec.HapiSpec; -import com.hedera.services.bdd.spec.HapiSpecOperation; -import com.hedera.services.bdd.spec.infrastructure.OpProvider; -import com.hedera.services.bdd.suites.HapiSuite; -import com.hederahashgraph.api.proto.java.ResponseCodeEnum; -import java.util.List; -import java.util.Map; -import java.util.Optional; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.atomic.AtomicInteger; -import java.util.concurrent.atomic.AtomicLong; -import java.util.concurrent.atomic.AtomicReference; -import java.util.function.Function; -import java.util.function.IntFunction; -import java.util.stream.IntStream; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; - -public class SimpleXfersAvoidingHotspot extends HapiSuite { - private static final Logger log = LogManager.getLogger(SimpleXfersAvoidingHotspot.class); - - private static final int NUM_ACCOUNTS = 100; - - private AtomicLong duration = new AtomicLong(600); - private AtomicReference unit = new AtomicReference<>(SECONDS); - private AtomicInteger maxOpsPerSec = new AtomicInteger(70); - - public static void main(String... args) { - new SimpleXfersAvoidingHotspot().runSuiteSync(); - } - - @Override - public List getSpecsInSuite() { - return List.of(new HapiSpec[] { - runSimpleXfers(), - }); - } - - final HapiSpec runSimpleXfers() { - return HapiSpec.customHapiSpec("RunTokenTransfers") - .withProperties(Map.of( - // "default.keyAlgorithm", "SECP256K1" - "default.keyAlgorithm", "ED25519")) - .given() - .when() - .then(runWithProvider(avoidantXfersFactory()) - .lasting(duration::get, unit::get) - .maxOpsPerSec(maxOpsPerSec::get)); - } - - private Function avoidantXfersFactory() { - final var nextSender = new AtomicInteger(); - final IntFunction nameFn = i -> "account" + i; - - return spec -> new OpProvider() { - @Override - public List suggestedInitializers() { - return List.of( - inParallel(IntStream.range(0, NUM_ACCOUNTS) - .mapToObj(i -> uniqueCreation(nameFn.apply(i))) - .toArray(HapiSpecOperation[]::new)), - sleepFor(10_000L)); - } - - @Override - public Optional get() { - final int sender = nextSender.getAndUpdate(i -> (i + 1) % NUM_ACCOUNTS); - final int receiver = (sender + 1) % NUM_ACCOUNTS; - final var from = nameFn.apply(sender); - final var to = nameFn.apply(receiver); - final var op = cryptoTransfer(tinyBarsFromTo(from, to, 1)) - .payingWith(from) - .hasKnownStatusFrom(ACCEPTED_STATUSES) - .deferStatusResolution() - .noLogging(); - return Optional.of(op); - } - }; - } - - static HapiSpecOperation uniqueQuietCreation(final String name) { - return internalUniqueCreation(name, false); - } - - static HapiSpecOperation uniqueCreation(final String name) { - return internalUniqueCreation(name, true); - } - - private static HapiSpecOperation internalUniqueCreation(final String name, final boolean verbose) { - return withOpContext((spec, opLog) -> { - while (true) { - try { - final var attempt = cryptoCreate(name) - .payingWith(GENESIS) - .ensuringResolvedStatusIsntFromDuplicate() - .balance(ONE_HUNDRED_HBARS * 10_000); - if (!verbose) { - attempt.noLogging(); - } - allRunFor(spec, attempt); - return; - } catch (IllegalStateException ignore) { - /* Collision with another client also using the treasury as its payer */ - } - } - }); - } - - private static final ResponseCodeEnum[] ACCEPTED_STATUSES = { - SUCCESS, OK, INSUFFICIENT_PAYER_BALANCE, UNKNOWN, TRANSACTION_EXPIRED - }; - - @Override - protected Logger getResultsLogger() { - return log; - } -} diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/perf/file/FileExpansionLoadProvider.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/perf/file/FileExpansionLoadProvider.java index 8d470c83abe9..017ed9fdc752 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/perf/file/FileExpansionLoadProvider.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/perf/file/FileExpansionLoadProvider.java @@ -33,7 +33,6 @@ import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.SUCCESS; import static java.util.concurrent.TimeUnit.MINUTES; -import com.hedera.services.bdd.junit.HapiTest; import com.hedera.services.bdd.spec.HapiSpec; import com.hedera.services.bdd.spec.HapiSpecOperation; import com.hedera.services.bdd.spec.infrastructure.OpProvider; @@ -52,8 +51,10 @@ import java.util.concurrent.atomic.AtomicReference; import java.util.function.Function; import java.util.function.LongFunction; +import java.util.stream.Stream; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; +import org.junit.jupiter.api.DynamicTest; /** * Load provider that continually appends 5kb chunks to a random choice of one of a set of target @@ -91,12 +92,11 @@ public static void main(String... args) { } @Override - public List getSpecsInSuite() { + public List> getSpecsInSuite() { return List.of(runFileExpansions()); } - @HapiTest - final HapiSpec runFileExpansions() { + final Stream runFileExpansions() { return HapiSpec.defaultHapiSpec("RunFileExpansions") .given( overriding(MAX_FILE_SIZE_KB_PROP, OVERRIDE_MAX_FILE_SIZE_KB), diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/perf/file/FileUpdateLoadTest.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/perf/file/FileUpdateLoadTest.java deleted file mode 100644 index 70fab2404a25..000000000000 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/perf/file/FileUpdateLoadTest.java +++ /dev/null @@ -1,94 +0,0 @@ -/* - * Copyright (C) 2020-2024 Hedera Hashgraph, LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.hedera.services.bdd.suites.perf.file; - -import static com.hedera.services.bdd.spec.HapiSpec.defaultHapiSpec; -import static com.hedera.services.bdd.spec.transactions.TxnVerbs.fileCreate; -import static com.hedera.services.bdd.spec.utilops.UtilVerbs.inParallel; -import static com.hedera.services.bdd.spec.utilops.UtilVerbs.logIt; -import static com.hedera.services.bdd.spec.utilops.UtilVerbs.runLoadTest; -import static com.hedera.services.bdd.spec.utilops.UtilVerbs.withOpContext; -import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.BUSY; -import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.DUPLICATE_TRANSACTION; -import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.OK; -import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.PLATFORM_TRANSACTION_NOT_CREATED; -import static java.util.concurrent.TimeUnit.MINUTES; - -import com.hedera.services.bdd.junit.HapiTest; -import com.hedera.services.bdd.spec.HapiSpec; -import com.hedera.services.bdd.spec.HapiSpecOperation; -import com.hedera.services.bdd.spec.transactions.TxnUtils; -import com.hedera.services.bdd.spec.transactions.TxnVerbs; -import com.hedera.services.bdd.suites.HapiSuite; -import com.hedera.services.bdd.suites.perf.PerfTestLoadSettings; -import java.util.List; -import java.util.concurrent.atomic.AtomicInteger; -import java.util.function.Supplier; -import java.util.stream.IntStream; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; - -public class FileUpdateLoadTest extends HapiSuite { - private static final Logger log = LogManager.getLogger(FileUpdateLoadTest.class); - - public static void main(String... args) { - FileUpdateLoadTest suite = new FileUpdateLoadTest(); - suite.runSuiteSync(); - } - - @Override - public List getSpecsInSuite() { - return List.of(runFileUpdates()); - } - - @HapiTest - final HapiSpec runFileUpdates() { - PerfTestLoadSettings settings = new PerfTestLoadSettings(); - final AtomicInteger submittedSoFar = new AtomicInteger(0); - final byte[] NEW_CONTENTS = TxnUtils.randomUtf8Bytes(TxnUtils.BYTES_4K); - - Supplier fileUpdateBurst = () -> new HapiSpecOperation[] { - inParallel(IntStream.range(0, settings.getBurstSize()) - .mapToObj(i -> TxnVerbs.fileUpdate("target") - .fee(Integer.MAX_VALUE) - .contents(NEW_CONTENTS) - .noLogging() - .hasPrecheckFrom(OK, BUSY, DUPLICATE_TRANSACTION, PLATFORM_TRANSACTION_NOT_CREATED) - .deferStatusResolution()) - .toArray(n -> new HapiSpecOperation[n])), - logIt(ignore -> String.format( - "Now a total of %d file updates submitted.", submittedSoFar.addAndGet(settings.getBurstSize()))), - }; - - return defaultHapiSpec("RunFileUpdates") - .given( - withOpContext( - (spec, ignore) -> settings.setFrom(spec.setup().ciPropertiesMap())), - logIt(ignore -> settings.toString())) - .when(fileCreate("target").contents("The initial contents!")) - .then(runLoadTest(fileUpdateBurst) - .tps(settings::getTps) - .tolerance(settings::getTolerancePercentage) - .allowedSecsBelow(settings::getAllowedSecsBelow) - .lasting(settings::getMins, () -> MINUTES)); - } - - @Override - protected Logger getResultsLogger() { - return log; - } -} diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/perf/file/MixedFileOpsLoadTest.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/perf/file/MixedFileOpsLoadTest.java deleted file mode 100644 index 7f52ffc0c81f..000000000000 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/perf/file/MixedFileOpsLoadTest.java +++ /dev/null @@ -1,103 +0,0 @@ -/* - * Copyright (C) 2020-2024 Hedera Hashgraph, LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.hedera.services.bdd.suites.perf.file; - -import static com.hedera.services.bdd.spec.HapiSpec.defaultHapiSpec; -import static com.hedera.services.bdd.spec.queries.QueryVerbs.getFileInfo; -import static com.hedera.services.bdd.spec.transactions.TxnVerbs.fileAppend; -import static com.hedera.services.bdd.spec.transactions.TxnVerbs.fileCreate; -import static com.hedera.services.bdd.spec.transactions.TxnVerbs.fileUpdate; -import static com.hedera.services.bdd.spec.utilops.UtilVerbs.logIt; -import static com.hedera.services.bdd.spec.utilops.UtilVerbs.withOpContext; -import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.SUCCESS; -import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.UNKNOWN; - -import com.hedera.services.bdd.junit.HapiTest; -import com.hedera.services.bdd.spec.HapiSpec; -import com.hedera.services.bdd.spec.HapiSpecOperation; -import com.hedera.services.bdd.spec.transactions.TxnUtils; -import com.hedera.services.bdd.spec.utilops.LoadTest; -import com.hedera.services.bdd.suites.perf.PerfTestLoadSettings; -import java.util.List; -import java.util.concurrent.atomic.AtomicInteger; -import java.util.function.Supplier; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; - -/** Run mixed operations including FileCreate, FileAppend, FileUpdate */ -public class MixedFileOpsLoadTest extends LoadTest { - private static final Logger log = LogManager.getLogger(MixedFileOpsLoadTest.class); - - public static void main(String... args) { - parseArgs(args); - - MixedFileOpsLoadTest suite = new MixedFileOpsLoadTest(); - suite.runSuiteSync(); - } - - @Override - public List getSpecsInSuite() { - return List.of(runMixedFileOps()); - } - - @HapiTest - protected HapiSpec runMixedFileOps() { - PerfTestLoadSettings settings = new PerfTestLoadSettings(); - final AtomicInteger submittedSoFar = new AtomicInteger(0); - String initialContent = "The initial contents!"; - String targetFile = "targetFile"; - - Supplier mixedFileOpsBurst = () -> new HapiSpecOperation[] { - fileCreate(targetFile + submittedSoFar.getAndIncrement()) - .contents(initialContent) - .hasKnownStatusFrom(SUCCESS, UNKNOWN), - fileUpdate(targetFile) - .fee(ONE_HUNDRED_HBARS) - .contents(TxnUtils.randomUtf8Bytes(TxnUtils.BYTES_4K)) - .noLogging() - .payingWith(GENESIS) - .hasAnyPrecheck() - .hasKnownStatusFrom(SUCCESS, UNKNOWN) - .deferStatusResolution(), - fileAppend(targetFile) - .content("dummy") - .hasAnyPrecheck() - .payingWith(GENESIS) - .fee(ONE_HUNDRED_HBARS) - .hasKnownStatusFrom(SUCCESS, UNKNOWN) - .deferStatusResolution() - }; - - return defaultHapiSpec("runMixedFileOps") - .given( - withOpContext( - (spec, ignore) -> settings.setFrom(spec.setup().ciPropertiesMap())), - logIt(ignore -> settings.toString())) - .when( - fileCreate(targetFile) - .contents(initialContent) - .hasAnyPrecheck() - .payingWith(GENESIS), - getFileInfo(targetFile).logging().payingWith(GENESIS)) - .then(defaultLoadTest(mixedFileOpsBurst, settings)); - } - - @Override - protected Logger getResultsLogger() { - return log; - } -} diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/perf/mixedops/MixedOpsLoadTest.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/perf/mixedops/MixedOpsLoadTest.java deleted file mode 100644 index 3aeb76a90bd2..000000000000 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/perf/mixedops/MixedOpsLoadTest.java +++ /dev/null @@ -1,276 +0,0 @@ -/* - * Copyright (C) 2021-2024 Hedera Hashgraph, LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.hedera.services.bdd.suites.perf.mixedops; - -import static com.hedera.services.bdd.spec.HapiSpec.defaultHapiSpec; -import static com.hedera.services.bdd.spec.infrastructure.OpProvider.STANDARD_PERMISSIBLE_PRECHECKS; -import static com.hedera.services.bdd.spec.transactions.TxnUtils.randomUtf8Bytes; -import static com.hedera.services.bdd.spec.transactions.TxnVerbs.createTopic; -import static com.hedera.services.bdd.spec.transactions.TxnVerbs.cryptoCreate; -import static com.hedera.services.bdd.spec.transactions.TxnVerbs.cryptoTransfer; -import static com.hedera.services.bdd.spec.transactions.TxnVerbs.fileUpdate; -import static com.hedera.services.bdd.spec.transactions.TxnVerbs.scheduleCreate; -import static com.hedera.services.bdd.spec.transactions.TxnVerbs.scheduleSign; -import static com.hedera.services.bdd.spec.transactions.TxnVerbs.submitMessageTo; -import static com.hedera.services.bdd.spec.transactions.TxnVerbs.tokenAssociate; -import static com.hedera.services.bdd.spec.transactions.TxnVerbs.tokenCreate; -import static com.hedera.services.bdd.spec.transactions.crypto.HapiCryptoTransfer.tinyBarsFromTo; -import static com.hedera.services.bdd.spec.transactions.token.TokenMovement.moving; -import static com.hedera.services.bdd.spec.utilops.UtilVerbs.inParallel; -import static com.hedera.services.bdd.spec.utilops.UtilVerbs.logIt; -import static com.hedera.services.bdd.spec.utilops.UtilVerbs.newKeyNamed; -import static com.hedera.services.bdd.spec.utilops.UtilVerbs.sleepFor; -import static com.hedera.services.bdd.spec.utilops.UtilVerbs.withOpContext; -import static com.hedera.services.bdd.suites.perf.PerfUtilOps.scheduleOpsEnablement; -import static com.hedera.services.bdd.suites.perf.PerfUtilOps.tokenOpsEnablement; -import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.BUSY; -import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.DUPLICATE_TRANSACTION; -import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.EMPTY_TOKEN_TRANSFER_ACCOUNT_AMOUNTS; -import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.INSUFFICIENT_PAYER_BALANCE; -import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.INSUFFICIENT_TOKEN_BALANCE; -import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.INVALID_SCHEDULE_ID; -import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.INVALID_TOKEN_ID; -import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.INVALID_TOPIC_ID; -import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.OK; -import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.PAYER_ACCOUNT_NOT_FOUND; -import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.PLATFORM_TRANSACTION_NOT_CREATED; -import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.SCHEDULE_ALREADY_EXECUTED; -import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.SUCCESS; -import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.TOKEN_ALREADY_ASSOCIATED_TO_ACCOUNT; -import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.TOKEN_NOT_ASSOCIATED_TO_ACCOUNT; -import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.TOPIC_EXPIRED; -import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.TRANSACTION_EXPIRED; -import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.UNKNOWN; - -import com.hedera.services.bdd.spec.HapiSpec; -import com.hedera.services.bdd.spec.HapiSpecOperation; -import com.hedera.services.bdd.spec.utilops.LoadTest; -import com.hedera.services.bdd.suites.perf.PerfTestLoadSettings; -import com.hederahashgraph.api.proto.java.ResponseCodeEnum; -import java.net.InetAddress; -import java.nio.ByteBuffer; -import java.time.Instant; -import java.util.List; -import java.util.Map; -import java.util.Random; -import java.util.concurrent.atomic.AtomicInteger; -import java.util.function.Supplier; -import java.util.stream.IntStream; -import org.apache.commons.lang3.ArrayUtils; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; - -public class MixedOpsLoadTest extends LoadTest { - private static final Logger log = LogManager.getLogger(MixedOpsLoadTest.class); - public static final int NUM_SUBMISSIONS = 100; - public static final String SUBMIT_KEY = "submitKey"; - public static final String TOKEN = "token"; - private final ResponseCodeEnum[] permissiblePrechecks = - new ResponseCodeEnum[] {BUSY, DUPLICATE_TRANSACTION, PLATFORM_TRANSACTION_NOT_CREATED, UNKNOWN}; - - private static String sender = "sender"; - private static String receiver = "receiver"; - private static String topic = "topic"; - private static String schedule = "schedule"; - private static int messageSize = 1024; - - public static void main(String... args) { - parseArgs(args); - MixedOpsLoadTest suite = new MixedOpsLoadTest(); - suite.runSuiteSync(); - } - - @Override - public List getSpecsInSuite() { - return List.of(runMixedOps()); - } - - protected HapiSpec runMixedOps() { - PerfTestLoadSettings settings = new PerfTestLoadSettings(); - @SuppressWarnings("java:S2245") // using java.util.Random in tests is fine - Random r = new Random(511523L); - AtomicInteger tokenId = new AtomicInteger(0); - AtomicInteger scheduleId = new AtomicInteger(0); - - Supplier mixedOpsBurst = () -> new HapiSpecOperation[] { - cryptoTransfer(tinyBarsFromTo(sender, receiver, 1L)) - .noLogging() - .payingWith(sender) - .signedBy(GENESIS) - .suppressStats(true) - .fee(ONE_HBAR) - .hasKnownStatusFrom(SUCCESS, OK, INSUFFICIENT_PAYER_BALANCE, UNKNOWN, TRANSACTION_EXPIRED) - .hasRetryPrecheckFrom( - BUSY, DUPLICATE_TRANSACTION, PLATFORM_TRANSACTION_NOT_CREATED, PAYER_ACCOUNT_NOT_FOUND) - .deferStatusResolution(), - submitMessageTo(topic) - .message(ArrayUtils.addAll( - ByteBuffer.allocate(8) - .putLong(Instant.now().toEpochMilli()) - .array(), - randomUtf8Bytes(messageSize - 8))) - .noLogging() - .payingWith(GENESIS) - .signedBy(sender, SUBMIT_KEY) - .fee(ONE_HBAR) - .suppressStats(true) - .hasRetryPrecheckFrom( - BUSY, - DUPLICATE_TRANSACTION, - PLATFORM_TRANSACTION_NOT_CREATED, - TOPIC_EXPIRED, - INVALID_TOPIC_ID, - INSUFFICIENT_PAYER_BALANCE) - .hasKnownStatusFrom( - SUCCESS, OK, INVALID_TOPIC_ID, INSUFFICIENT_PAYER_BALANCE, UNKNOWN, TRANSACTION_EXPIRED) - .deferStatusResolution(), - r.nextInt(100) > 5 - ? cryptoTransfer(moving(1, TOKEN + r.nextInt(NUM_SUBMISSIONS)) - .between(sender, receiver)) - .payingWith(sender) - .signedBy(GENESIS) - .fee(ONE_HUNDRED_HBARS) - .noLogging() - .suppressStats(true) - .hasPrecheckFrom( - OK, - INSUFFICIENT_PAYER_BALANCE, - EMPTY_TOKEN_TRANSFER_ACCOUNT_AMOUNTS, - DUPLICATE_TRANSACTION) - .hasRetryPrecheckFrom(permissiblePrechecks) - .hasKnownStatusFrom( - SUCCESS, - OK, - INSUFFICIENT_TOKEN_BALANCE, - TRANSACTION_EXPIRED, - INVALID_TOKEN_ID, - UNKNOWN, - TOKEN_NOT_ASSOCIATED_TO_ACCOUNT) - .deferStatusResolution() - : scheduleSign(schedule + "-" + getHostName() + "-" + r.nextInt(NUM_SUBMISSIONS)) - .ignoreIfMissing() - .noLogging() - .alsoSigningWith(receiver) - .hasPrecheckFrom(OK, INVALID_SCHEDULE_ID) - .hasKnownStatusFrom( - SUCCESS, - OK, - TRANSACTION_EXPIRED, - INVALID_SCHEDULE_ID, - UNKNOWN, - SCHEDULE_ALREADY_EXECUTED) - .fee(ONE_HBAR) - .deferStatusResolution() - }; - - return defaultHapiSpec("RunMixedOps") - .given( - withOpContext( - (spec, ignore) -> settings.setFrom(spec.setup().ciPropertiesMap())), - logIt(ignore -> settings.toString()), - newKeyNamed(SUBMIT_KEY), - tokenOpsEnablement(), - scheduleOpsEnablement(), - cryptoCreate("treasury") - .hasRetryPrecheckFrom(permissiblePrechecks) - .key(GENESIS)) - .when( - fileUpdate(APP_PROPERTIES) - .payingWith(GENESIS) - .overridingProps(Map.of( - "hapi.throttling.buckets.fastOpBucket.capacity", - "1300000.0", - "hapi.throttling.ops.consensusUpdateTopic.capacityRequired", - "1.0", - "hapi.throttling.ops.consensusGetTopicInfo.capacityRequired", - "1.0", - "hapi.throttling.ops.consensusSubmitMessage.capacityRequired", - "1.0", - "tokens.maxPerAccount", - "10000000")), - cryptoCreate(sender) - .balance(initialBalance.getAsLong()) - .withRecharging() - .key(GENESIS) - .rechargeWindow(3) - .hasRetryPrecheckFrom(permissiblePrechecks), - cryptoCreate(receiver) - .hasRetryPrecheckFrom(permissiblePrechecks) - .key(GENESIS), - createTopic(topic).submitKeyName(SUBMIT_KEY), - inParallel(IntStream.range(0, NUM_SUBMISSIONS) - .mapToObj(ignore -> tokenCreate(TOKEN + tokenId.getAndIncrement()) - .payingWith(GENESIS) - .signedBy(GENESIS) - .fee(ONE_HUNDRED_HBARS) - .initialSupply(ONE_HUNDRED_HBARS) - .treasury("treasury") - .hasRetryPrecheckFrom(permissiblePrechecks) - .hasPrecheckFrom(DUPLICATE_TRANSACTION, OK) - .deferStatusResolution() - .noLogging()) - .toArray(n -> new HapiSpecOperation[n])), - sleepFor(10000), - inParallel(IntStream.range(0, NUM_SUBMISSIONS) - .mapToObj(ignore -> scheduleCreate( - "schedule-" + getHostName() + "-" + scheduleId.getAndIncrement(), - cryptoTransfer(tinyBarsFromTo(sender, receiver, 1))) - .signedBy(DEFAULT_PAYER) - .fee(ONE_HUNDRED_HBARS) - .alsoSigningWith(sender) - .hasPrecheckFrom(STANDARD_PERMISSIBLE_PRECHECKS) - .hasAnyKnownStatus() - .deferStatusResolution() - .adminKey(DEFAULT_PAYER) - .noLogging()) - .toArray(n -> new HapiSpecOperation[n])), - sleepFor(10000), - inParallel(IntStream.range(0, NUM_SUBMISSIONS) - .mapToObj(i -> tokenAssociate(sender, TOKEN + i) - .payingWith(GENESIS) - .signedBy(GENESIS) - .hasRetryPrecheckFrom(permissiblePrechecks) - .hasPrecheckFrom(DUPLICATE_TRANSACTION, OK) - .hasKnownStatusFrom( - SUCCESS, - TOKEN_ALREADY_ASSOCIATED_TO_ACCOUNT, - INVALID_TOKEN_ID, - TRANSACTION_EXPIRED, - OK) - .fee(ONE_HUNDRED_HBARS) - .suppressStats(true) - .deferStatusResolution() - .noLogging()) - .toArray(n -> new HapiSpecOperation[n])), - sleepFor(10000)) - .then(defaultLoadTest(mixedOpsBurst, settings)); - } - - @Override - protected Logger getResultsLogger() { - return log; - } - - private String getHostName() { - try { - return InetAddress.getLocalHost().getHostName(); - } catch (Exception e) { - log.info("Error getting host name"); - return "Hostname-Not-Available"; - } - } -} diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/perf/mixedops/MixedOpsMemoPerfSuite.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/perf/mixedops/MixedOpsMemoPerfSuite.java deleted file mode 100644 index 89d6c1e57fc3..000000000000 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/perf/mixedops/MixedOpsMemoPerfSuite.java +++ /dev/null @@ -1,205 +0,0 @@ -/* - * Copyright (C) 2020-2024 Hedera Hashgraph, LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.hedera.services.bdd.suites.perf.mixedops; - -import static com.hedera.services.bdd.spec.HapiSpec.defaultHapiSpec; -import static com.hedera.services.bdd.spec.assertions.AccountInfoAsserts.accountWith; -import static com.hedera.services.bdd.spec.queries.QueryVerbs.getAccountInfo; -import static com.hedera.services.bdd.spec.queries.QueryVerbs.getTokenInfo; -import static com.hedera.services.bdd.spec.queries.QueryVerbs.getTopicInfo; -import static com.hedera.services.bdd.spec.transactions.TxnVerbs.createTopic; -import static com.hedera.services.bdd.spec.transactions.TxnVerbs.cryptoCreate; -import static com.hedera.services.bdd.spec.transactions.TxnVerbs.cryptoUpdate; -import static com.hedera.services.bdd.spec.transactions.TxnVerbs.fileUpdate; -import static com.hedera.services.bdd.spec.transactions.TxnVerbs.tokenCreate; -import static com.hedera.services.bdd.spec.transactions.TxnVerbs.tokenUpdate; -import static com.hedera.services.bdd.spec.transactions.TxnVerbs.updateTopic; -import static com.hedera.services.bdd.spec.utilops.UtilVerbs.logIt; -import static com.hedera.services.bdd.spec.utilops.UtilVerbs.newKeyNamed; -import static com.hedera.services.bdd.spec.utilops.UtilVerbs.sleepFor; -import static com.hedera.services.bdd.spec.utilops.UtilVerbs.withOpContext; -import static com.hedera.services.bdd.suites.perf.PerfUtilOps.tokenOpsEnablement; -import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.BUSY; -import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.DUPLICATE_TRANSACTION; -import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.OK; -import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.PLATFORM_TRANSACTION_NOT_CREATED; -import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.UNKNOWN; - -import com.hedera.services.bdd.spec.HapiSpec; -import com.hedera.services.bdd.spec.HapiSpecOperation; -import com.hedera.services.bdd.spec.transactions.TxnUtils; -import com.hedera.services.bdd.spec.utilops.LoadTest; -import com.hedera.services.bdd.suites.perf.PerfTestLoadSettings; -import com.hederahashgraph.api.proto.java.ResponseCodeEnum; -import java.nio.charset.StandardCharsets; -import java.util.List; -import java.util.Map; -import java.util.concurrent.atomic.AtomicInteger; -import java.util.function.Supplier; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; - -public class MixedOpsMemoPerfSuite extends LoadTest { - private static final Logger log = LogManager.getLogger(MixedOpsMemoPerfSuite.class); - private final String INITIAL_MEMO = "InitialMemo"; - private final String ACCOUNT_MEMO = INITIAL_MEMO + " for Account Entity"; - private final String TOPIC_MEMO = INITIAL_MEMO + " for Topic Entity"; - private final String TOKEN_MEMO = INITIAL_MEMO + " for Token Entity"; - private final String TARGET_ACCOUNT = "accountForMemo"; - private final String TARGET_TOKEN = "tokenForMemo"; - private final String TARGET_TOPIC = "topicForMemo"; - - private final ResponseCodeEnum[] permissiblePrechecks = - new ResponseCodeEnum[] {OK, BUSY, DUPLICATE_TRANSACTION, PLATFORM_TRANSACTION_NOT_CREATED, UNKNOWN}; - - public static void main(String... args) { - parseArgs(args); - - MixedOpsMemoPerfSuite suite = new MixedOpsMemoPerfSuite(); - suite.runSuiteSync(); - } - - @Override - public List getSpecsInSuite() { - return List.of(runMixedMemoOps()); - } - - // perform cryptoCreate, cryptoUpdate, TokenCreate, TokenUpdate, FileCreate, FileUpdate txs with - // entity memo set. - protected HapiSpec runMixedMemoOps() { - PerfTestLoadSettings settings = new PerfTestLoadSettings(); - final AtomicInteger createdSoFar = new AtomicInteger(0); - Supplier mixedOpsBurst = () -> new HapiSpecOperation[] { - cryptoCreate("testAccount" + createdSoFar.getAndIncrement()) - .balance(1L) - .fee(100_000_000L) - .payingWith(GENESIS) - .entityMemo(new String(TxnUtils.randomUtf8Bytes(memoLength.getAsInt()), StandardCharsets.UTF_8)) - .noLogging() - .hasPrecheckFrom(permissiblePrechecks) - .deferStatusResolution(), - getAccountInfo(TARGET_ACCOUNT + "Info") - .payingWith(GENESIS) - .has(accountWith().memo(ACCOUNT_MEMO)) - .hasAnswerOnlyPrecheckFrom(permissiblePrechecks) - .hasCostAnswerPrecheckFrom(permissiblePrechecks) - .noLogging(), - cryptoUpdate(TARGET_ACCOUNT) - .payingWith(GENESIS) - .entityMemo(new String(TxnUtils.randomUtf8Bytes(memoLength.getAsInt()), StandardCharsets.UTF_8)) - .noLogging() - .hasPrecheckFrom(permissiblePrechecks) - .deferStatusResolution(), - tokenCreate("testToken" + createdSoFar.getAndIncrement()) - .payingWith(GENESIS) - .entityMemo(new String(TxnUtils.randomUtf8Bytes(memoLength.getAsInt()), StandardCharsets.UTF_8)) - .noLogging() - .hasPrecheckFrom(permissiblePrechecks) - .deferStatusResolution(), - getTokenInfo(TARGET_TOKEN + "Info") - .payingWith(GENESIS) - .hasEntityMemo(TOKEN_MEMO) - .hasAnswerOnlyPrecheckFrom(permissiblePrechecks) - .hasCostAnswerPrecheckFrom(permissiblePrechecks) - .noLogging(), - tokenUpdate(TARGET_TOKEN) - .payingWith(GENESIS) - .entityMemo(new String(TxnUtils.randomUtf8Bytes(memoLength.getAsInt()), StandardCharsets.UTF_8)) - .noLogging() - .hasPrecheckFrom(permissiblePrechecks) - .deferStatusResolution(), - createTopic("testTopic" + createdSoFar.getAndIncrement()) - .topicMemo(new String(TxnUtils.randomUtf8Bytes(memoLength.getAsInt()), StandardCharsets.UTF_8)) - .payingWith(GENESIS) - .adminKeyName("adminKey") - .noLogging() - .hasPrecheckFrom(permissiblePrechecks) - .deferStatusResolution(), - getTopicInfo(TARGET_TOPIC + "Info") - .payingWith(GENESIS) - .hasMemo(TOPIC_MEMO) - .hasAnswerOnlyPrecheckFrom(permissiblePrechecks) - .hasCostAnswerPrecheckFrom(permissiblePrechecks) - .noLogging(), - updateTopic(TARGET_TOPIC) - .topicMemo(new String(TxnUtils.randomUtf8Bytes(memoLength.getAsInt()), StandardCharsets.UTF_8)) - .payingWith(GENESIS) - .adminKey("adminKey") - .noLogging() - .hasPrecheckFrom(permissiblePrechecks) - .deferStatusResolution() - }; - return defaultHapiSpec("RunMixedMemoOps") - .given( - withOpContext( - (spec, ignore) -> settings.setFrom(spec.setup().ciPropertiesMap())), - logIt(ignore -> settings.toString()), - tokenOpsEnablement()) - .when( - fileUpdate(APP_PROPERTIES) - .payingWith(GENESIS) - .overridingProps(Map.of( - "hapi.throttling.buckets.fastOpBucket.capacity", - "1300000.0", - "hapi.throttling.ops.consensusUpdateTopic.capacityRequired", - "1.0", - "hapi.throttling.ops.consensusGetTopicInfo.capacityRequired", - "1.0", - "hapi.throttling.ops.consensusSubmitMessage.capacityRequired", - "1.0", - "tokens.maxPerAccount", - "10000000")), - sleepFor(5000), - newKeyNamed("adminKey"), - logIt(ignore -> settings.toString()), - cryptoCreate(TARGET_ACCOUNT) - .fee(100_000_000L) - .payingWith(GENESIS) - .entityMemo("Memo Length :" + settings.getMemoLength()) - .logged(), - getAccountInfo(TARGET_ACCOUNT).logged(), - cryptoCreate(TARGET_ACCOUNT + "Info") - .fee(100_000_000L) - .payingWith(GENESIS) - .entityMemo(ACCOUNT_MEMO) - .logged(), - createTopic(TARGET_TOPIC) - .topicMemo(TOPIC_MEMO) - .adminKeyName("adminKey") - .payingWith(GENESIS) - .logged(), - createTopic(TARGET_TOPIC + "Info") - .payingWith(GENESIS) - .adminKeyName("adminKey") - .topicMemo(TOPIC_MEMO) - .logged(), - tokenCreate(TARGET_TOKEN) - .entityMemo(TOKEN_MEMO) - .payingWith(GENESIS) - .logged(), - tokenCreate(TARGET_TOKEN + "Info") - .entityMemo(TOKEN_MEMO) - .payingWith(GENESIS) - .logged()) - .then(defaultLoadTest(mixedOpsBurst, settings)); - } - - @Override - protected Logger getResultsLogger() { - return log; - } -} diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/perf/mixedops/MixedTransferAndSubmitLoadTest.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/perf/mixedops/MixedTransferAndSubmitLoadTest.java deleted file mode 100644 index 8f4c64487f67..000000000000 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/perf/mixedops/MixedTransferAndSubmitLoadTest.java +++ /dev/null @@ -1,103 +0,0 @@ -/* - * Copyright (C) 2020-2024 Hedera Hashgraph, LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.hedera.services.bdd.suites.perf.mixedops; - -import static com.hedera.services.bdd.spec.HapiSpec.defaultHapiSpec; -import static com.hedera.services.bdd.spec.transactions.TxnVerbs.createTopic; -import static com.hedera.services.bdd.spec.transactions.TxnVerbs.cryptoCreate; -import static com.hedera.services.bdd.spec.transactions.TxnVerbs.cryptoTransfer; -import static com.hedera.services.bdd.spec.transactions.TxnVerbs.submitMessageTo; -import static com.hedera.services.bdd.spec.transactions.crypto.HapiCryptoTransfer.tinyBarsFromTo; -import static com.hedera.services.bdd.spec.utilops.UtilVerbs.inParallel; -import static com.hedera.services.bdd.spec.utilops.UtilVerbs.logIt; -import static com.hedera.services.bdd.spec.utilops.UtilVerbs.runLoadTest; -import static com.hedera.services.bdd.spec.utilops.UtilVerbs.withOpContext; -import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.BUSY; -import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.DUPLICATE_TRANSACTION; -import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.OK; -import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.PLATFORM_TRANSACTION_NOT_CREATED; -import static java.util.concurrent.TimeUnit.MINUTES; - -import com.hedera.services.bdd.spec.HapiSpec; -import com.hedera.services.bdd.spec.HapiSpecOperation; -import com.hedera.services.bdd.suites.HapiSuite; -import com.hedera.services.bdd.suites.perf.PerfTestLoadSettings; -import java.util.List; -import java.util.concurrent.atomic.AtomicInteger; -import java.util.function.Supplier; -import java.util.stream.IntStream; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; - -public class MixedTransferAndSubmitLoadTest extends HapiSuite { - private static final Logger log = LogManager.getLogger(MixedTransferAndSubmitLoadTest.class); - - public static void main(String... args) { - MixedTransferAndSubmitLoadTest suite = new MixedTransferAndSubmitLoadTest(); - suite.runSuiteSync(); - } - - @Override - public List getSpecsInSuite() { - return List.of(runMixedTransferAndSubmits()); - } - - final HapiSpec runMixedTransferAndSubmits() { - PerfTestLoadSettings settings = new PerfTestLoadSettings(); - final AtomicInteger submittedSoFar = new AtomicInteger(0); - - Supplier transferBurst = () -> new HapiSpecOperation[] { - inParallel(flattened( - IntStream.range(0, settings.getBurstSize() / 2) - .mapToObj(ignore -> cryptoTransfer(tinyBarsFromTo("sender", "receiver", 1L)) - .noLogging() - .hasPrecheckFrom(OK, BUSY, DUPLICATE_TRANSACTION, PLATFORM_TRANSACTION_NOT_CREATED) - .deferStatusResolution()) - .toArray(n -> new HapiSpecOperation[n]), - IntStream.range(0, settings.getBurstSize() / 2) - .mapToObj(ignore -> submitMessageTo("topic") - .message("A fascinating" + " item of" + " general" + " interest!") - .noLogging() - .hasPrecheckFrom(OK, BUSY, DUPLICATE_TRANSACTION, PLATFORM_TRANSACTION_NOT_CREATED) - .deferStatusResolution()) - .toArray(n -> new HapiSpecOperation[n]))), - logIt(ignore -> String.format( - "Now a 50/50 mix of %d transfers and messages" + " submitted in total.", - submittedSoFar.addAndGet(settings.getBurstSize()))), - }; - - return defaultHapiSpec("RunMixedTransferAndSubmits") - .given( - withOpContext( - (spec, ignore) -> settings.setFrom(spec.setup().ciPropertiesMap())), - logIt(ignore -> settings.toString())) - .when( - createTopic("topic"), - cryptoCreate("sender").balance(ignore -> settings.getInitialBalance()), - cryptoCreate("receiver")) - .then(runLoadTest(transferBurst) - .tps(settings::getTps) - .tolerance(settings::getTolerancePercentage) - .allowedSecsBelow(settings::getAllowedSecsBelow) - .lasting(settings::getMins, () -> MINUTES)); - } - - @Override - protected Logger getResultsLogger() { - return log; - } -} diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/perf/mixedops/MixedTransferCallAndSubmitLoadTest.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/perf/mixedops/MixedTransferCallAndSubmitLoadTest.java deleted file mode 100644 index dd36e30e843e..000000000000 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/perf/mixedops/MixedTransferCallAndSubmitLoadTest.java +++ /dev/null @@ -1,115 +0,0 @@ -/* - * Copyright (C) 2020-2024 Hedera Hashgraph, LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.hedera.services.bdd.suites.perf.mixedops; - -import static com.hedera.services.bdd.spec.HapiSpec.defaultHapiSpec; -import static com.hedera.services.bdd.spec.transactions.TxnVerbs.contractCall; -import static com.hedera.services.bdd.spec.transactions.TxnVerbs.contractCreate; -import static com.hedera.services.bdd.spec.transactions.TxnVerbs.createTopic; -import static com.hedera.services.bdd.spec.transactions.TxnVerbs.cryptoCreate; -import static com.hedera.services.bdd.spec.transactions.TxnVerbs.cryptoTransfer; -import static com.hedera.services.bdd.spec.transactions.TxnVerbs.submitMessageTo; -import static com.hedera.services.bdd.spec.transactions.TxnVerbs.uploadInitCode; -import static com.hedera.services.bdd.spec.transactions.crypto.HapiCryptoTransfer.tinyBarsFromTo; -import static com.hedera.services.bdd.spec.utilops.UtilVerbs.inParallel; -import static com.hedera.services.bdd.spec.utilops.UtilVerbs.logIt; -import static com.hedera.services.bdd.spec.utilops.UtilVerbs.runLoadTest; -import static com.hedera.services.bdd.spec.utilops.UtilVerbs.withOpContext; -import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.BUSY; -import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.DUPLICATE_TRANSACTION; -import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.OK; -import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.PLATFORM_TRANSACTION_NOT_CREATED; -import static java.util.concurrent.TimeUnit.MINUTES; - -import com.hedera.services.bdd.spec.HapiSpec; -import com.hedera.services.bdd.spec.HapiSpecOperation; -import com.hedera.services.bdd.suites.HapiSuite; -import com.hedera.services.bdd.suites.perf.PerfTestLoadSettings; -import java.util.List; -import java.util.concurrent.atomic.AtomicInteger; -import java.util.function.Supplier; -import java.util.stream.IntStream; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; - -public class MixedTransferCallAndSubmitLoadTest extends HapiSuite { - private static final Logger log = LogManager.getLogger(MixedTransferCallAndSubmitLoadTest.class); - private static final String CONTRACT = "SimpleStorage"; - - public static void main(String... args) { - MixedTransferCallAndSubmitLoadTest suite = new MixedTransferCallAndSubmitLoadTest(); - suite.runSuiteSync(); - } - - @Override - public List getSpecsInSuite() { - return List.of(runMixedTransferCallAndSubmits()); - } - - final HapiSpec runMixedTransferCallAndSubmits() { - PerfTestLoadSettings settings = new PerfTestLoadSettings(); - final AtomicInteger submittedSoFar = new AtomicInteger(0); - - Supplier transferBurst = () -> new HapiSpecOperation[] { - inParallel(flattened( - IntStream.range(0, settings.getBurstSize() / 2) - .mapToObj(ignore -> cryptoTransfer(tinyBarsFromTo("sender", "receiver", 1L)) - .noLogging() - .hasPrecheckFrom(OK, BUSY, DUPLICATE_TRANSACTION, PLATFORM_TRANSACTION_NOT_CREATED) - .deferStatusResolution()) - .toArray(n -> new HapiSpecOperation[n]), - IntStream.range(0, settings.getBurstSize() / 25) - .mapToObj(i -> contractCall(CONTRACT, "set", i) - .noLogging() - .hasPrecheckFrom(OK, BUSY, DUPLICATE_TRANSACTION, PLATFORM_TRANSACTION_NOT_CREATED) - .deferStatusResolution()) - .toArray(n -> new HapiSpecOperation[n]), - IntStream.range(0, settings.getBurstSize() / 2) - .mapToObj(ignore -> submitMessageTo("topic") - .message("A fascinating" + " item of" + " general" + " interest!") - .noLogging() - .hasPrecheckFrom(OK, BUSY, DUPLICATE_TRANSACTION, PLATFORM_TRANSACTION_NOT_CREATED) - .deferStatusResolution()) - .toArray(n -> new HapiSpecOperation[n]))), - logIt(ignore -> String.format( - "Now a 25:1 ratio of %d transfers+messages :" + " calls submitted in total.", - submittedSoFar.addAndGet(settings.getBurstSize() / 25 * 26))), - }; - - return defaultHapiSpec("RunMixedTransferCallAndSubmits") - .given( - withOpContext( - (spec, ignore) -> settings.setFrom(spec.setup().ciPropertiesMap())), - logIt(ignore -> settings.toString())) - .when( - createTopic("topic"), - cryptoCreate("sender").balance(999_999_999_999_999L), - cryptoCreate("receiver"), - uploadInitCode(CONTRACT), - contractCreate(CONTRACT)) - .then(runLoadTest(transferBurst) - .tps(settings::getTps) - .tolerance(settings::getTolerancePercentage) - .allowedSecsBelow(settings::getAllowedSecsBelow) - .lasting(settings::getMins, () -> MINUTES)); - } - - @Override - protected Logger getResultsLogger() { - return log; - } -} diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/perf/schedule/OnePendingSigScheduledXfersLoad.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/perf/schedule/OnePendingSigScheduledXfersLoad.java deleted file mode 100644 index 7d706e466f93..000000000000 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/perf/schedule/OnePendingSigScheduledXfersLoad.java +++ /dev/null @@ -1,202 +0,0 @@ -/* - * Copyright (C) 2020-2024 Hedera Hashgraph, LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.hedera.services.bdd.suites.perf.schedule; - -import static com.hedera.services.bdd.spec.HapiSpec.defaultHapiSpec; -import static com.hedera.services.bdd.spec.queries.QueryVerbs.getAccountBalance; -import static com.hedera.services.bdd.spec.transactions.TxnUtils.NOISY_ALLOWED_STATUSES; -import static com.hedera.services.bdd.spec.transactions.TxnUtils.NOISY_RETRY_PRECHECKS; -import static com.hedera.services.bdd.spec.transactions.TxnVerbs.cryptoTransfer; -import static com.hedera.services.bdd.spec.transactions.TxnVerbs.scheduleCreate; -import static com.hedera.services.bdd.spec.transactions.TxnVerbs.scheduleSign; -import static com.hedera.services.bdd.spec.transactions.crypto.HapiCryptoTransfer.tinyBarsFromTo; -import static com.hedera.services.bdd.spec.utilops.CustomSpecAssert.allRunFor; -import static com.hedera.services.bdd.spec.utilops.UtilVerbs.inParallel; -import static com.hedera.services.bdd.spec.utilops.UtilVerbs.runWithProvider; -import static com.hedera.services.bdd.spec.utilops.UtilVerbs.withOpContext; -import static com.hedera.services.bdd.suites.perf.PerfUtilOps.stdMgmtOf; -import static com.hedera.services.bdd.suites.perf.schedule.ReadyToRunScheduledXfersLoad.inertReceiver; -import static com.hedera.services.bdd.suites.perf.schedule.ReadyToRunScheduledXfersLoad.initializersGiven; -import static com.hedera.services.bdd.suites.perf.schedule.ReadyToRunScheduledXfersLoad.payingSender; -import static java.util.concurrent.TimeUnit.MINUTES; - -import com.google.common.util.concurrent.AtomicDouble; -import com.hedera.services.bdd.spec.HapiSpec; -import com.hedera.services.bdd.spec.HapiSpecOperation; -import com.hedera.services.bdd.spec.infrastructure.OpProvider; -import com.hedera.services.bdd.suites.HapiSuite; -import java.util.Comparator; -import java.util.List; -import java.util.Optional; -import java.util.SplittableRandom; -import java.util.concurrent.BlockingQueue; -import java.util.concurrent.PriorityBlockingQueue; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.atomic.AtomicInteger; -import java.util.concurrent.atomic.AtomicLong; -import java.util.concurrent.atomic.AtomicReference; -import java.util.function.Function; -import java.util.stream.IntStream; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; - -public class OnePendingSigScheduledXfersLoad extends HapiSuite { - private static final Logger log = LogManager.getLogger(OnePendingSigScheduledXfersLoad.class); - - private AtomicDouble probOfSignOp = new AtomicDouble(0.0); - private AtomicInteger numNonDefaultSenders = new AtomicInteger(0); - private AtomicInteger numInertReceivers = new AtomicInteger(0); - - private AtomicLong duration = new AtomicLong(Long.MAX_VALUE); - private AtomicReference unit = new AtomicReference<>(MINUTES); - private AtomicInteger maxOpsPerSec = new AtomicInteger(500); - private SplittableRandom r = new SplittableRandom(); - - BlockingQueue q = - new PriorityBlockingQueue<>(5000, Comparator.comparingDouble(PendingSig::getPriority)); - - public static void main(String... args) { - new OnePendingSigScheduledXfersLoad().runSuiteSync(); - } - - @Override - public List getSpecsInSuite() { - return List.of(new HapiSpec[] { - runOnePendingSigXfers(), - }); - } - - final HapiSpec runOnePendingSigXfers() { - return defaultHapiSpec("RunOnePendingSigXfers") - .given(stdMgmtOf(duration, unit, maxOpsPerSec)) - .when(runWithProvider(pendingSigsFactory()) - .lasting(duration::get, unit::get) - .maxOpsPerSec(maxOpsPerSec::get)) - .then(withOpContext((spec, opLog) -> { - if (numInertReceivers.get() > 0) { - var op = inParallel(IntStream.range(0, numInertReceivers.get()) - .mapToObj( - i -> getAccountBalance(inertReceiver(i)).logged()) - .toArray(HapiSpecOperation[]::new)); - allRunFor(spec, op); - } - })); - } - - private Function pendingSigsFactory() { - return spec -> new OpProvider() { - @Override - public List suggestedInitializers() { - var ciProps = spec.setup().ciPropertiesMap(); - numNonDefaultSenders.set(ciProps.getInteger("numNonDefaultSenders")); - numInertReceivers.set(ciProps.getInteger("numInertReceivers")); - probOfSignOp.set(ciProps.getDouble("probOfSigning")); - return initializersGiven(numNonDefaultSenders.get(), numInertReceivers.get()); - } - - @Override - public Optional get() { - var sample = r.nextDouble(); - if (sample <= probOfSignOp.get()) { - return getScheduleSign(); - } else { - return getScheduleCreate(); - } - } - - private Optional getScheduleSign() { - var nextSig = q.poll(); - if (nextSig == null) { - return Optional.empty(); - } - var op = scheduleSign(nextSig.getScheduleId()) - .alsoSigningWith(nextSig.getSignatory()) - .hasKnownStatusFrom(NOISY_ALLOWED_STATUSES) - .hasRetryPrecheckFrom(NOISY_RETRY_PRECHECKS) - // .noLogging() - .deferStatusResolution(); - return Optional.of(op); - } - - private Optional getScheduleCreate() { - var senderId = -1; - if (numNonDefaultSenders.get() > 0) { - senderId = r.nextInt(numNonDefaultSenders.get()); - } - var payerId = (senderId == -1 || numNonDefaultSenders.get() == 1) - ? -1 - : (senderId + 1) % numNonDefaultSenders.get(); - - var payer = payerId == -1 ? DEFAULT_PAYER : payingSender(payerId); - var sender = senderId == -1 ? DEFAULT_PAYER : payingSender(senderId); - var receiver = FUNDING; - if (numInertReceivers.get() > 0) { - receiver = inertReceiver(r.nextInt(numInertReceivers.get())); - } - var innerOp = - cryptoTransfer(tinyBarsFromTo(sender, receiver, 1L)).fee(ONE_HBAR); - var op = scheduleCreate("wrapper", innerOp) - .exposingSuccessTo( - (createdId, bytes) -> q.offer(new PendingSig(bytes, createdId, sender, r.nextDouble()))) - .rememberingNothing() - .designatingPayer(payer) - .logged() - .alsoSigningWith(payer) - .hasKnownStatusFrom(NOISY_ALLOWED_STATUSES) - .hasRetryPrecheckFrom(NOISY_RETRY_PRECHECKS) - // .noLogging() - .deferStatusResolution(); - return Optional.of(op); - } - }; - } - - @Override - protected Logger getResultsLogger() { - return log; - } - - private static class PendingSig { - private final byte[] scheduledTxnBytes; - private final String scheduleId; - private final String signatory; - private final double priority; - - public PendingSig(byte[] scheduledTxnBytes, String scheduleId, String signatory, double priority) { - this.scheduledTxnBytes = scheduledTxnBytes; - this.scheduleId = scheduleId; - this.signatory = signatory; - this.priority = priority; - } - - public byte[] getScheduledTxnBytes() { - return scheduledTxnBytes; - } - - public String getScheduleId() { - return scheduleId; - } - - public String getSignatory() { - return signatory; - } - - public double getPriority() { - return priority; - } - } -} diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/perf/schedule/ReadyToRunScheduledXfersLoad.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/perf/schedule/ReadyToRunScheduledXfersLoad.java deleted file mode 100644 index 9c27ae21c75d..000000000000 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/perf/schedule/ReadyToRunScheduledXfersLoad.java +++ /dev/null @@ -1,156 +0,0 @@ -/* - * Copyright (C) 2020-2024 Hedera Hashgraph, LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.hedera.services.bdd.suites.perf.schedule; - -import static com.hedera.services.bdd.spec.HapiSpec.defaultHapiSpec; -import static com.hedera.services.bdd.spec.queries.QueryVerbs.getAccountBalance; -import static com.hedera.services.bdd.spec.transactions.TxnUtils.NOISY_ALLOWED_STATUSES; -import static com.hedera.services.bdd.spec.transactions.TxnUtils.NOISY_RETRY_PRECHECKS; -import static com.hedera.services.bdd.spec.transactions.TxnVerbs.cryptoCreate; -import static com.hedera.services.bdd.spec.transactions.TxnVerbs.cryptoTransfer; -import static com.hedera.services.bdd.spec.transactions.TxnVerbs.scheduleCreate; -import static com.hedera.services.bdd.spec.transactions.crypto.HapiCryptoTransfer.tinyBarsFromTo; -import static com.hedera.services.bdd.spec.utilops.UtilVerbs.inParallel; -import static com.hedera.services.bdd.spec.utilops.UtilVerbs.runWithProvider; -import static com.hedera.services.bdd.spec.utilops.UtilVerbs.withOpContext; -import static com.hedera.services.bdd.suites.perf.PerfUtilOps.stdMgmtOf; -import static java.util.concurrent.TimeUnit.MINUTES; - -import com.hedera.services.bdd.spec.HapiSpec; -import com.hedera.services.bdd.spec.HapiSpecOperation; -import com.hedera.services.bdd.spec.infrastructure.OpProvider; -import com.hedera.services.bdd.spec.transactions.HapiTxnOp; -import com.hedera.services.bdd.spec.utilops.CustomSpecAssert; -import com.hedera.services.bdd.suites.HapiSuite; -import java.util.ArrayList; -import java.util.List; -import java.util.Optional; -import java.util.SplittableRandom; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.atomic.AtomicInteger; -import java.util.concurrent.atomic.AtomicLong; -import java.util.concurrent.atomic.AtomicReference; -import java.util.function.Function; -import java.util.stream.IntStream; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; - -public class ReadyToRunScheduledXfersLoad extends HapiSuite { - private static final Logger log = LogManager.getLogger(ReadyToRunScheduledXfersLoad.class); - - private AtomicInteger numNonDefaultSenders = new AtomicInteger(0); - private AtomicInteger numInertReceivers = new AtomicInteger(0); - - private AtomicLong duration = new AtomicLong(Long.MAX_VALUE); - private AtomicReference unit = new AtomicReference<>(MINUTES); - private AtomicInteger maxOpsPerSec = new AtomicInteger(500); - private SplittableRandom r = new SplittableRandom(); - - public static void main(String... args) { - new ReadyToRunScheduledXfersLoad().runSuiteSync(); - } - - @Override - public List getSpecsInSuite() { - return List.of(new HapiSpec[] { - runReadyToRunXfers(), - }); - } - - final HapiSpec runReadyToRunXfers() { - return defaultHapiSpec("RunReadyToRunXfers") - .given(stdMgmtOf(duration, unit, maxOpsPerSec)) - .when(runWithProvider(readyToRunXfersFactory()) - .lasting(duration::get, unit::get) - .maxOpsPerSec(maxOpsPerSec::get)) - .then(withOpContext((spec, opLog) -> { - if (numInertReceivers.get() > 0) { - var op = inParallel(IntStream.range(0, numInertReceivers.get()) - .mapToObj( - i -> getAccountBalance(inertReceiver(i)).logged()) - .toArray(HapiSpecOperation[]::new)); - CustomSpecAssert.allRunFor(spec, op); - } - })); - } - - static String payingSender(int id) { - return "payingSender" + id; - } - - static String inertReceiver(int id) { - return "inertReceiver" + id; - } - - static List initializersGiven(int nonDefaultSenders, int inertReceivers) { - List initializers = new ArrayList<>(); - for (int i = 0; i < nonDefaultSenders; i++) { - initializers.add(cryptoCreate(payingSender(i)).balance(ONE_HUNDRED_HBARS)); - } - for (int i = 0; i < inertReceivers; i++) { - initializers.add(cryptoCreate(inertReceiver(i)).balance(0L)); - } - - for (HapiSpecOperation op : initializers) { - if (op instanceof HapiTxnOp) { - ((HapiTxnOp) op).hasRetryPrecheckFrom(NOISY_RETRY_PRECHECKS); - } - } - - return initializers; - } - - private Function readyToRunXfersFactory() { - return spec -> new OpProvider() { - @Override - public List suggestedInitializers() { - var ciProps = spec.setup().ciPropertiesMap(); - numNonDefaultSenders.set(ciProps.getInteger("numNonDefaultSenders")); - numInertReceivers.set(ciProps.getInteger("numInertReceivers")); - return initializersGiven(numNonDefaultSenders.get(), numInertReceivers.get()); - } - - @Override - public Optional get() { - var sendingPayer = DEFAULT_PAYER; - if (numNonDefaultSenders.get() > 0) { - sendingPayer = payingSender(r.nextInt(numNonDefaultSenders.get())); - } - var receiver = FUNDING; - if (numInertReceivers.get() > 0) { - receiver = inertReceiver(r.nextInt(numInertReceivers.get())); - } - var innerOp = cryptoTransfer(tinyBarsFromTo(sendingPayer, receiver, 1L)) - .payingWith(sendingPayer) - .noLogging(); - var op = scheduleCreate("wrapper", innerOp) - .rememberingNothing() - .alsoSigningWith(sendingPayer) - .hasKnownStatusFrom(NOISY_ALLOWED_STATUSES) - .hasRetryPrecheckFrom(NOISY_RETRY_PRECHECKS) - .noLogging() - .deferStatusResolution(); - return Optional.of(op); - } - }; - } - - @Override - protected Logger getResultsLogger() { - return log; - } -} diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/perf/token/TokenAssociationLoadTest.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/perf/token/TokenAssociationLoadTest.java index 7e8ccc2dd6c5..670214c88590 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/perf/token/TokenAssociationLoadTest.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/perf/token/TokenAssociationLoadTest.java @@ -22,8 +22,10 @@ import com.hedera.services.bdd.suites.HapiSuite; import java.util.List; import java.util.concurrent.atomic.AtomicInteger; +import java.util.stream.Stream; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; +import org.junit.jupiter.api.DynamicTest; public class TokenAssociationLoadTest extends HapiSuite { private static final Logger log = LogManager.getLogger(TokenAssociationLoadTest.class); @@ -36,13 +38,11 @@ protected Logger getResultsLogger() { } @Override - public List getSpecsInSuite() { - return List.of(new HapiSpec[] { - runTokenAssociationLoadTest(), - }); + public List> getSpecsInSuite() { + return List.of(runTokenAssociationLoadTest()); } - final HapiSpec runTokenAssociationLoadTest() { + final Stream runTokenAssociationLoadTest() { return HapiSpec.defaultHapiSpec("RunTokenAssociationLoadTest") .given(overridingTwo( "tokens.maxPerAccount", "10", diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/perf/token/TokenCreatePerfSuite.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/perf/token/TokenCreatePerfSuite.java index 35b1973d2f1b..cf28a04a6c40 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/perf/token/TokenCreatePerfSuite.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/perf/token/TokenCreatePerfSuite.java @@ -21,12 +21,13 @@ import static com.hedera.services.bdd.spec.utilops.UtilVerbs.freezeOnly; import static com.hedera.services.bdd.spec.utilops.UtilVerbs.inParallel; -import com.hedera.services.bdd.spec.HapiSpec; import com.hedera.services.bdd.spec.utilops.LoadTest; import com.hedera.services.bdd.spec.utilops.UtilVerbs; import java.util.List; +import java.util.stream.Stream; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; +import org.junit.jupiter.api.DynamicTest; public class TokenCreatePerfSuite extends LoadTest { private static final Logger log = LogManager.getLogger(TokenCreatePerfSuite.class); @@ -38,11 +39,11 @@ public static void main(String... args) { } @Override - public List getSpecsInSuite() { + public List> getSpecsInSuite() { return List.of(runTokenCreates()); } - final HapiSpec runTokenCreates() { + final Stream runTokenCreates() { final int NUM_CREATES = 100000; return defaultHapiSpec("tokenCreatePerf") .given() diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/perf/token/TokenRelStatusChanges.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/perf/token/TokenRelStatusChanges.java deleted file mode 100644 index ef5bff24d4fe..000000000000 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/perf/token/TokenRelStatusChanges.java +++ /dev/null @@ -1,154 +0,0 @@ -/* - * Copyright (C) 2020-2024 Hedera Hashgraph, LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.hedera.services.bdd.suites.perf.token; - -import static com.hedera.services.bdd.spec.transactions.TxnUtils.NOISY_RETRY_PRECHECKS; -import static com.hedera.services.bdd.spec.transactions.TxnVerbs.cryptoCreate; -import static com.hedera.services.bdd.spec.transactions.TxnVerbs.tokenAssociate; -import static com.hedera.services.bdd.spec.transactions.TxnVerbs.tokenCreate; -import static com.hedera.services.bdd.spec.transactions.TxnVerbs.tokenDissociate; -import static com.hedera.services.bdd.spec.utilops.UtilVerbs.inParallel; -import static com.hedera.services.bdd.spec.utilops.UtilVerbs.runWithProvider; -import static com.hedera.services.bdd.suites.perf.PerfUtilOps.stdMgmtOf; -import static com.hedera.services.bdd.suites.perf.PerfUtilOps.tokenOpsEnablement; -import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.DUPLICATE_TRANSACTION; -import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.OK; -import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.SUCCESS; -import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.TOKEN_ALREADY_ASSOCIATED_TO_ACCOUNT; -import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.TOKEN_NOT_ASSOCIATED_TO_ACCOUNT; -import static java.util.concurrent.TimeUnit.MINUTES; - -import com.hedera.services.bdd.spec.HapiSpec; -import com.hedera.services.bdd.spec.HapiSpecOperation; -import com.hedera.services.bdd.spec.infrastructure.OpProvider; -import com.hedera.services.bdd.suites.HapiSuite; -import java.util.ArrayList; -import java.util.List; -import java.util.Optional; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.atomic.AtomicBoolean; -import java.util.concurrent.atomic.AtomicInteger; -import java.util.concurrent.atomic.AtomicLong; -import java.util.concurrent.atomic.AtomicReference; -import java.util.function.Function; -import java.util.stream.IntStream; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; - -public class TokenRelStatusChanges extends HapiSuite { - private static final Logger log = LogManager.getLogger(TokenRelStatusChanges.class); - - private AtomicLong duration = new AtomicLong(5); - private AtomicReference unit = new AtomicReference<>(MINUTES); - private AtomicInteger maxOpsPerSec = new AtomicInteger(500); - - public static void main(String... args) { - new TokenRelStatusChanges().runSuiteSync(); - } - - @Override - public List getSpecsInSuite() { - return List.of(new HapiSpec[] { - runTokenRelStatusChanges(), - }); - } - - final HapiSpec runTokenRelStatusChanges() { - return HapiSpec.defaultHapiSpec("RunTokenRelStatusChanges") - .given(stdMgmtOf(duration, unit, maxOpsPerSec)) - .when() - .then(runWithProvider(statusChangesFactory()) - .lasting(duration::get, unit::get) - .maxOpsPerSec(maxOpsPerSec::get)); - } - - private Function statusChangesFactory() { - var nowAssociating = new AtomicBoolean(Boolean.FALSE); - var relatableTokens = new AtomicInteger(); - var relatableAccounts = new AtomicInteger(); - List tokens = new ArrayList<>(); - List accounts = new ArrayList<>(); - var nextToken = new AtomicInteger(-1); - var nextAccount = new AtomicInteger(-1); - - return spec -> new OpProvider() { - @Override - public List suggestedInitializers() { - var ciProps = spec.setup().ciPropertiesMap(); - relatableTokens.set(ciProps.getInteger("numTokens")); - relatableAccounts.set(ciProps.getInteger("numAccounts")); - - List initializers = new ArrayList<>(); - initializers.add(tokenOpsEnablement().hasRetryPrecheckFrom(NOISY_RETRY_PRECHECKS)); - IntStream.range(0, relatableTokens.get()) - .mapToObj(i -> "token" + i) - .forEach(tokens::add); - initializers.add(inParallel(tokens.stream() - .map(token -> tokenCreate(token).hasRetryPrecheckFrom(NOISY_RETRY_PRECHECKS)) - .toArray(HapiSpecOperation[]::new))); - IntStream.range(0, relatableAccounts.get()) - .mapToObj(i -> "account" + i) - .forEach(accounts::add); - initializers.add(inParallel(accounts.stream() - .map(account -> cryptoCreate(account).hasRetryPrecheckFrom(NOISY_RETRY_PRECHECKS)) - .toArray(HapiSpecOperation[]::new))); - - return initializers; - } - - @Override - public Optional get() { - HapiSpecOperation op; - final int numTokens = relatableTokens.get(); - final int numAccounts = relatableAccounts.get(); - - int token = nextToken.get() % numTokens; - int account = nextAccount.incrementAndGet() % numAccounts; - if (account == 0) { - token = nextToken.incrementAndGet() % numTokens; - if (token == 0) { - var current = nowAssociating.get(); - nowAssociating.compareAndSet(current, !current); - } - } - - if (nowAssociating.get()) { - op = tokenAssociate(accounts.get(account), tokens.get(token)) - .fee(ONE_HUNDRED_HBARS) - .hasRetryPrecheckFrom(NOISY_RETRY_PRECHECKS) - .hasKnownStatusFrom(OK, SUCCESS, DUPLICATE_TRANSACTION, TOKEN_ALREADY_ASSOCIATED_TO_ACCOUNT) - .noLogging() - .deferStatusResolution(); - } else { - op = tokenDissociate(accounts.get(account), tokens.get(token)) - .fee(ONE_HUNDRED_HBARS) - .hasRetryPrecheckFrom(NOISY_RETRY_PRECHECKS) - .hasKnownStatusFrom(OK, SUCCESS, DUPLICATE_TRANSACTION, TOKEN_NOT_ASSOCIATED_TO_ACCOUNT) - .noLogging() - .deferStatusResolution(); - } - - return Optional.of(op); - } - }; - } - - @Override - protected Logger getResultsLogger() { - return log; - } -} diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/perf/token/TokenTransferBasicLoadTest.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/perf/token/TokenTransferBasicLoadTest.java deleted file mode 100644 index 6d6d0651afce..000000000000 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/perf/token/TokenTransferBasicLoadTest.java +++ /dev/null @@ -1,270 +0,0 @@ -/* - * Copyright (C) 2021-2024 Hedera Hashgraph, LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.hedera.services.bdd.suites.perf.token; - -import static com.hedera.services.bdd.spec.HapiSpec.defaultHapiSpec; -import static com.hedera.services.bdd.spec.transactions.TxnVerbs.cryptoTransfer; -import static com.hedera.services.bdd.spec.transactions.TxnVerbs.tokenAssociate; -import static com.hedera.services.bdd.spec.transactions.TxnVerbs.tokenCreate; -import static com.hedera.services.bdd.spec.transactions.token.TokenMovement.moving; -import static com.hedera.services.bdd.spec.utilops.UtilVerbs.runWithProvider; -import static com.hedera.services.bdd.spec.utilops.UtilVerbs.sleepFor; -import static com.hedera.services.bdd.spec.utilops.UtilVerbs.sourcing; -import static com.hedera.services.bdd.spec.utilops.UtilVerbs.withOpContext; -import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.BUSY; -import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.DUPLICATE_TRANSACTION; -import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.EMPTY_TOKEN_TRANSFER_ACCOUNT_AMOUNTS; -import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.FAIL_INVALID; -import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.INSUFFICIENT_PAYER_BALANCE; -import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.INSUFFICIENT_TOKEN_BALANCE; -import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.INVALID_TOKEN_ID; -import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.OK; -import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.PLATFORM_TRANSACTION_NOT_CREATED; -import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.SUCCESS; -import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.TOKEN_ALREADY_ASSOCIATED_TO_ACCOUNT; -import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.TOKEN_NOT_ASSOCIATED_TO_ACCOUNT; -import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.TRANSACTION_EXPIRED; -import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.UNKNOWN; - -import com.hedera.services.bdd.spec.HapiSpec; -import com.hedera.services.bdd.spec.HapiSpecOperation; -import com.hedera.services.bdd.spec.infrastructure.OpProvider; -import com.hedera.services.bdd.spec.utilops.LoadTest; -import com.hedera.services.bdd.suites.perf.PerfTestLoadSettings; -import java.util.Collections; -import java.util.List; -import java.util.Optional; -import java.util.Random; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.atomic.AtomicInteger; -import java.util.concurrent.atomic.AtomicLong; -import java.util.function.Function; -import java.util.function.Supplier; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; - -public class TokenTransferBasicLoadTest extends LoadTest { - - private static final org.apache.logging.log4j.Logger LOG = LogManager.getLogger(TokenTransferBasicLoadTest.class); - private static final int EXPECTED_MAX_OPS_PER_SEC = 5_000; - private static final int MAX_PENDING_OPS_FOR_SETUP = 10_000; - private static final int ESTIMATED_TOKEN_CREATION_RATE = 50; - - @SuppressWarnings("java:S2245") // using java.util.Random in tests is fine - private static final Random r = new Random(858639L); - - public static final String ACCOUNT_FORMAT = "0.0.%d"; - - public static void main(String... args) { - parseArgs(args); - TokenTransferBasicLoadTest suite = new TokenTransferBasicLoadTest(); - suite.runSuiteSync(); - } - - @Override - public List getSpecsInSuite() { - return List.of(runTokenTransferBasicLoadTest()); - } - - private static String tokenRegistryName(int id) { - return "TestToken" + id; - } - - private Function tokenCreatesFactory(PerfTestLoadSettings settings) { - int numTotalTokens = settings.getTotalTokens(); - int totalClients = settings.getTotalClients(); - int numActiveTokens = (totalClients >= 1) ? numTotalTokens / totalClients : numTotalTokens; - AtomicInteger remaining = new AtomicInteger(numActiveTokens - 1); - - return spec -> new OpProvider() { - @Override - public List suggestedInitializers() { - return Collections.emptyList(); - } - - @Override - public Optional get() { - int next; - if ((next = remaining.getAndDecrement()) < 0) { - return Optional.empty(); - } - var payingTreasury = String.format(ACCOUNT_FORMAT, settings.getTestTreasureStartAccount() + next); - var op = tokenCreate(tokenRegistryName(next)) - .payingWith(DEFAULT_PAYER) - .signedBy(DEFAULT_PAYER) - .fee(ONE_HUNDRED_HBARS) - .initialSupply(100_000_000_000L) - .treasury(payingTreasury) - .hasRetryPrecheckFrom( - BUSY, - PLATFORM_TRANSACTION_NOT_CREATED, - DUPLICATE_TRANSACTION, - INSUFFICIENT_PAYER_BALANCE) - .hasPrecheckFrom(DUPLICATE_TRANSACTION, OK) - .hasKnownStatusFrom(SUCCESS, TOKEN_ALREADY_ASSOCIATED_TO_ACCOUNT, FAIL_INVALID) - .suppressStats(true) - .noLogging(); - return Optional.of(op); - } - }; - } - - private Function activeTokenAssociatesFactory(PerfTestLoadSettings settings) { - int numTotalTokens = settings.getTotalTokens(); - int numActiveTokenAccounts = settings.getTotalTestTokenAccounts(); - int totalClients = settings.getTotalClients(); - int numActiveTokens = (totalClients >= 1) ? numTotalTokens / totalClients : numTotalTokens; - AtomicLong remainingAssociations = new AtomicLong(numActiveTokens * numActiveTokenAccounts - 1L); - - if (LOG.isDebugEnabled()) { - LOG.debug( - "Total active token accounts {}, total test tokens {}, my portion of tokens {}", - numActiveTokenAccounts, - numTotalTokens, - numActiveTokens); - } - - long startAccountId = settings.getTestTreasureStartAccount(); - /* We are useing first portion, 10K, 100K or any other number of total existing accounts to test - token performance. Thus the total account number (currently 1M+) won't impact the algorithm - here to build the associations of active testing accounts and newly created active tokens. - Given n accounts, the association between the i-th token and the j-th account has id - assocId = i * numActiveTokenAccounts + j - Where: - - i is in the range [0, numActiveTokens) - - j is in the range [0, numActiveTokenAccounts) - */ - - return spec -> new OpProvider() { - @Override - public List suggestedInitializers() { - return Collections.emptyList(); - } - - @Override - public Optional get() { - long nextAssocId; - if ((nextAssocId = remainingAssociations.getAndDecrement()) < 0) { - return Optional.empty(); - } - int curToken = (int) nextAssocId / numActiveTokenAccounts; - long curAccount = nextAssocId % numActiveTokenAccounts; - var accountId = "0.0." + (startAccountId + curAccount); - var op = tokenAssociate(accountId, tokenRegistryName(curToken)) - .payingWith(DEFAULT_PAYER) - .signedBy(DEFAULT_PAYER) - .hasRetryPrecheckFrom( - BUSY, - PLATFORM_TRANSACTION_NOT_CREATED, - DUPLICATE_TRANSACTION, - INSUFFICIENT_PAYER_BALANCE) - .hasPrecheckFrom(DUPLICATE_TRANSACTION, OK) - .hasKnownStatusFrom( - SUCCESS, - TOKEN_ALREADY_ASSOCIATED_TO_ACCOUNT, - INVALID_TOKEN_ID, - TRANSACTION_EXPIRED, - FAIL_INVALID, - OK) - .fee(ONE_HUNDRED_HBARS) - .noLogging() - .suppressStats(true) - .deferStatusResolution(); - return Optional.of(op); - } - }; - } - - final HapiSpec runTokenTransferBasicLoadTest() { - PerfTestLoadSettings settings = new PerfTestLoadSettings(); - Supplier tokenTransferBurst = - () -> new HapiSpecOperation[] {opSupplier(settings).get()}; - return defaultHapiSpec("TokenTransferBasicLoadTest") - .given(withOpContext( - (spec, ignore) -> settings.setFrom(spec.setup().ciPropertiesMap()))) - .when( - sourcing(() -> runWithProvider(tokenCreatesFactory(settings)) - .lasting( - () -> settings.getTotalTokens() / ESTIMATED_TOKEN_CREATION_RATE - + 10, // 10s as buffering - // time - () -> TimeUnit.SECONDS) - .totalOpsToSumbit(() -> (int) - Math.ceil((double) (settings.getTotalTokens()) / settings.getTotalClients())) - .maxOpsPerSec(() -> (EXPECTED_MAX_OPS_PER_SEC / settings.getTotalClients())) - .maxPendingOps(() -> MAX_PENDING_OPS_FOR_SETUP)), - sourcing(() -> runWithProvider(activeTokenAssociatesFactory(settings)) - .lasting( - () -> (settings.getTotalTokens() - * settings.getTotalTestTokenAccounts() - / EXPECTED_MAX_OPS_PER_SEC) - + 30, // 30s as buffering - // time - () -> TimeUnit.SECONDS) - .totalOpsToSumbit(() -> (int) Math.ceil( - (double) (settings.getTotalTokens()) / settings.getTotalClients()) - * settings.getTotalTestTokenAccounts()) - .maxOpsPerSec(() -> (EXPECTED_MAX_OPS_PER_SEC / settings.getTotalClients())) - .maxPendingOps(() -> MAX_PENDING_OPS_FOR_SETUP)), - sleepFor(2000)) - .then(defaultLoadTest(tokenTransferBurst, settings)); - } - - private static Supplier opSupplier(PerfTestLoadSettings settings) { - int tokenNum = r.nextInt(settings.getTotalTokens() / settings.getTotalClients()); - int sender = r.nextInt(settings.getTotalTestTokenAccounts()); - int receiver = r.nextInt(settings.getTotalTestTokenAccounts()); - while (receiver == sender) { - receiver = r.nextInt(settings.getTotalTestTokenAccounts()); - } - String senderAcct = String.format(ACCOUNT_FORMAT, settings.getTestTreasureStartAccount() + sender); - String receiverAcct = String.format(ACCOUNT_FORMAT, settings.getTestTreasureStartAccount() + receiver); - - if (LOG.isDebugEnabled()) { - LOG.debug( - "Account 0.0.{} will send 1 token of testToken{} to account 0.0.{}", - senderAcct, - tokenNum, - receiverAcct); - } - - var op = cryptoTransfer(moving(1, tokenRegistryName(tokenNum)).between(senderAcct, receiverAcct)) - .payingWith(senderAcct) - .signedBy(GENESIS) - .fee(ONE_HUNDRED_HBARS) - .noLogging() - .suppressStats(true) - .hasPrecheckFrom( - OK, INSUFFICIENT_PAYER_BALANCE, EMPTY_TOKEN_TRANSFER_ACCOUNT_AMOUNTS, DUPLICATE_TRANSACTION) - .hasRetryPrecheckFrom(BUSY, PLATFORM_TRANSACTION_NOT_CREATED) - .hasKnownStatusFrom( - SUCCESS, - OK, - INSUFFICIENT_TOKEN_BALANCE, - TRANSACTION_EXPIRED, - INVALID_TOKEN_ID, - UNKNOWN, - TOKEN_NOT_ASSOCIATED_TO_ACCOUNT) - .deferStatusResolution(); - return () -> op; - } - - @Override - protected Logger getResultsLogger() { - return LOG; - } -} diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/perf/token/TokenTransfersLoadProvider.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/perf/token/TokenTransfersLoadProvider.java deleted file mode 100644 index 9e1ae6e73706..000000000000 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/perf/token/TokenTransfersLoadProvider.java +++ /dev/null @@ -1,226 +0,0 @@ -/* - * Copyright (C) 2020-2024 Hedera Hashgraph, LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.hedera.services.bdd.suites.perf.token; - -import static com.hedera.services.bdd.spec.queries.QueryVerbs.getAccountBalance; -import static com.hedera.services.bdd.spec.transactions.TxnUtils.NOISY_RETRY_PRECHECKS; -import static com.hedera.services.bdd.spec.transactions.TxnVerbs.cryptoCreate; -import static com.hedera.services.bdd.spec.transactions.TxnVerbs.cryptoTransfer; -import static com.hedera.services.bdd.spec.transactions.TxnVerbs.fileUpdate; -import static com.hedera.services.bdd.spec.transactions.TxnVerbs.tokenAssociate; -import static com.hedera.services.bdd.spec.transactions.TxnVerbs.tokenCreate; -import static com.hedera.services.bdd.spec.transactions.token.TokenMovement.moving; -import static com.hedera.services.bdd.spec.utilops.UtilVerbs.freezeOnly; -import static com.hedera.services.bdd.spec.utilops.UtilVerbs.runWithProvider; -import static com.hedera.services.bdd.spec.utilops.UtilVerbs.sleepFor; -import static com.hedera.services.bdd.suites.perf.PerfUtilOps.stdMgmtOf; -import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.DUPLICATE_TRANSACTION; -import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.FREEZE_ALREADY_SCHEDULED; -import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.INSUFFICIENT_PAYER_BALANCE; -import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.OK; -import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.PLATFORM_NOT_ACTIVE; -import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.SUCCESS; -import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.UNKNOWN; -import static java.util.concurrent.TimeUnit.MINUTES; - -import com.hedera.services.bdd.spec.HapiSpec; -import com.hedera.services.bdd.spec.HapiSpecOperation; -import com.hedera.services.bdd.spec.infrastructure.OpProvider; -import com.hedera.services.bdd.spec.transactions.HapiTxnOp; -import com.hedera.services.bdd.spec.transactions.token.TokenMovement; -import com.hedera.services.bdd.suites.HapiSuite; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Optional; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.atomic.AtomicBoolean; -import java.util.concurrent.atomic.AtomicInteger; -import java.util.concurrent.atomic.AtomicLong; -import java.util.concurrent.atomic.AtomicReference; -import java.util.function.Function; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; - -public class TokenTransfersLoadProvider extends HapiSuite { - private static final Logger log = LogManager.getLogger(TokenTransfersLoadProvider.class); - private static final String TOKEN = "token"; - private static final String SENDER = "sender"; - private static final String RECEIVER = "receiver"; - - private AtomicLong duration = new AtomicLong(Long.MAX_VALUE); - private AtomicReference unit = new AtomicReference<>(MINUTES); - private AtomicInteger maxOpsPerSec = new AtomicInteger(500); - - public static void main(String... args) { - new TokenTransfersLoadProvider().runSuiteSync(); - } - - @Override - public List getSpecsInSuite() { - return List.of(new HapiSpec[] { - runTokenTransfers(), - }); - } - - final HapiSpec runTokenTransfers() { - return HapiSpec.defaultHapiSpec("RunTokenTransfers") - .given( - getAccountBalance(DEFAULT_PAYER).logged(), - stdMgmtOf(duration, unit, maxOpsPerSec), - fileUpdate(APP_PROPERTIES) - .payingWith(GENESIS) - .overridingProps(Map.of( - "balances.exportPeriodSecs", - "300", - "balances.exportDir.path", - "data/accountBalances/"))) - .when(runWithProvider(tokenTransfersFactory()) - .lasting(duration::get, unit::get) - .maxOpsPerSec(maxOpsPerSec::get)) - .then( - getAccountBalance(DEFAULT_PAYER).logged(), - // The freeze and long wait after freeze means to keep the server in - // MAINTENANCE state till test - // end to prevent it from making new export files that may cause account - // balances validator to - // be inconsistent. The freeze shouldn't cause normal perf test any issue. - freezeOnly() - .payingWith(GENESIS) - .startingIn(30) - .seconds() - .hasKnownStatusFrom(SUCCESS, UNKNOWN, FREEZE_ALREADY_SCHEDULED) - .hasAnyPrecheck(), - sleepFor(60_000)); - } - - private Function tokenTransfersFactory() { - var firstDir = new AtomicBoolean(Boolean.TRUE); - var balanceInit = new AtomicLong(); - var tokensPerTxn = new AtomicInteger(); - var sendingAccountsPerToken = new AtomicInteger(); - var receivingAccountsPerToken = new AtomicInteger(); - List treasuries = new ArrayList<>(); - Map> senders = new HashMap<>(); - Map> receivers = new HashMap<>(); - - return spec -> new OpProvider() { - @Override - public List suggestedInitializers() { - var ciProps = spec.setup().ciPropertiesMap(); - balanceInit.set(ciProps.getLong("balanceInit")); - tokensPerTxn.set(ciProps.getInteger("tokensPerTxn")); - sendingAccountsPerToken.set(ciProps.getInteger("sendingAccountsPerToken")); - receivingAccountsPerToken.set(ciProps.getInteger("receivingAccountsPerToken")); - - var initialSupply = - (sendingAccountsPerToken.get() + receivingAccountsPerToken.get()) * balanceInit.get(); - List initializers = new ArrayList<>(); - for (int i = 0; i < tokensPerTxn.get(); i++) { - var token = TOKEN + i; - var treasury = "treasury" + i; - initializers.add(cryptoCreate(treasury)); - initializers.add(tokenCreate(token).treasury(treasury).initialSupply(initialSupply)); - treasuries.add(treasury); - for (int j = 0; j < sendingAccountsPerToken.get(); j++) { - var sender = token + SENDER + j; - senders.computeIfAbsent(token, ignore -> new ArrayList<>()) - .add(sender); - initializers.add(cryptoCreate(sender)); - initializers.add(tokenAssociate(sender, token)); - initializers.add( - cryptoTransfer(moving(balanceInit.get(), token).between(treasury, sender))); - } - for (int j = 0; j < receivingAccountsPerToken.get(); j++) { - var receiver = token + RECEIVER + j; - receivers - .computeIfAbsent(token, ignore -> new ArrayList<>()) - .add(receiver); - initializers.add(cryptoCreate(receiver)); - initializers.add(tokenAssociate(receiver, token)); - initializers.add( - cryptoTransfer(moving(balanceInit.get(), token).between(treasury, receiver))); - } - } - - for (HapiSpecOperation op : initializers) { - if (op instanceof HapiTxnOp) { - ((HapiTxnOp) op).hasRetryPrecheckFrom(NOISY_RETRY_PRECHECKS); - } - } - - return initializers; - } - - @Override - public Optional get() { - HapiSpecOperation op; - var numTokens = tokensPerTxn.get(); - var numSenders = sendingAccountsPerToken.get(); - var numReceivers = receivingAccountsPerToken.get(); - if (firstDir.get()) { - var xfers = new TokenMovement[numTokens * numSenders]; - for (int i = 0; i < numTokens; i++) { - var token = TOKEN + i; - for (int j = 0; j < numSenders; j++) { - var receivers = new String[numReceivers]; - for (int k = 0; k < numReceivers; k++) { - receivers[k] = token + RECEIVER + k; - } - xfers[i * numSenders + j] = - moving(numReceivers, token).distributing(token + SENDER + j, receivers); - } - } - op = cryptoTransfer(xfers) - .hasKnownStatusFrom(OK, DUPLICATE_TRANSACTION, SUCCESS, UNKNOWN, INSUFFICIENT_PAYER_BALANCE) - .hasRetryPrecheckFrom(NOISY_RETRY_PRECHECKS) - .hasPrecheckFrom(OK, PLATFORM_NOT_ACTIVE) - .noLogging() - .deferStatusResolution(); - firstDir.set(Boolean.FALSE); - } else { - var xfers = new TokenMovement[numTokens * numReceivers]; - for (int i = 0; i < numTokens; i++) { - var token = TOKEN + i; - for (int j = 0; j < numReceivers; j++) { - var senders = new String[numSenders]; - for (int k = 0; k < numSenders; k++) { - senders[k] = token + SENDER + k; - } - xfers[i * numReceivers + j] = - moving(numSenders, token).distributing(token + RECEIVER + j, senders); - } - } - op = cryptoTransfer(xfers) - .hasKnownStatusFrom(OK, DUPLICATE_TRANSACTION, SUCCESS, UNKNOWN, INSUFFICIENT_PAYER_BALANCE) - .hasRetryPrecheckFrom(NOISY_RETRY_PRECHECKS) - .hasPrecheckFrom(OK, PLATFORM_NOT_ACTIVE) - .noLogging() - .deferStatusResolution(); - firstDir.set(Boolean.TRUE); - } - return Optional.of(op); - } - }; - } - - @Override - protected Logger getResultsLogger() { - return log; - } -} diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/perf/token/UniqueTokenStateSetup.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/perf/token/UniqueTokenStateSetup.java deleted file mode 100644 index 02da053abde4..000000000000 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/perf/token/UniqueTokenStateSetup.java +++ /dev/null @@ -1,312 +0,0 @@ -/* - * Copyright (C) 2021-2024 Hedera Hashgraph, LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.hedera.services.bdd.suites.perf.token; - -import static com.hedera.services.bdd.spec.HapiSpec.defaultHapiSpec; -import static com.hedera.services.bdd.spec.transactions.TxnVerbs.cryptoCreate; -import static com.hedera.services.bdd.spec.transactions.TxnVerbs.mintToken; -import static com.hedera.services.bdd.spec.transactions.TxnVerbs.tokenCreate; -import static com.hedera.services.bdd.spec.utilops.UtilVerbs.inParallel; -import static com.hedera.services.bdd.spec.utilops.UtilVerbs.runWithProvider; -import static com.hedera.services.bdd.spec.utilops.UtilVerbs.sleepFor; -import static com.hedera.services.bdd.spec.utilops.UtilVerbs.withOpContext; -import static com.hedera.services.bdd.suites.perf.PerfUtilOps.mgmtOfIntProp; -import static com.hedera.services.bdd.suites.perf.PerfUtilOps.mgmtOfLongProp; -import static com.hedera.services.bdd.suites.perf.PerfUtilOps.stdMgmtOf; -import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.DUPLICATE_TRANSACTION; -import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.INVALID_TOKEN_ID; -import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.OK; -import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.SUCCESS; -import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.TRANSACTION_EXPIRED; -import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.UNKNOWN; -import static com.hederahashgraph.api.proto.java.TokenSupplyType.INFINITE; -import static com.hederahashgraph.api.proto.java.TokenType.NON_FUNGIBLE_UNIQUE; -import static java.util.concurrent.TimeUnit.SECONDS; - -import com.google.protobuf.ByteString; -import com.hedera.services.bdd.spec.HapiSpec; -import com.hedera.services.bdd.spec.HapiSpecOperation; -import com.hedera.services.bdd.spec.infrastructure.OpProvider; -import com.hedera.services.bdd.suites.HapiSuite; -import java.util.ArrayList; -import java.util.List; -import java.util.Optional; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.atomic.AtomicBoolean; -import java.util.concurrent.atomic.AtomicInteger; -import java.util.concurrent.atomic.AtomicLong; -import java.util.concurrent.atomic.AtomicReference; -import java.util.function.Function; -import java.util.function.IntFunction; -import java.util.stream.IntStream; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; - -/** - * A client that creates some number of NON_FUNGIBLE_UNIQUE tokens, and then for each token mints - * some number of NFTs, each w/ the requested number of bytes of metadata. All tokens are created - * using the dev treasury key as the token supply key. - * - *

    The exact number of entities to create can be configured using the constants at the top of the - * class definition. - * - *

    IMPORTANT: Please note the following two items: - * - *

      - *
    1. If creating a large number of NFTs, e.g. 1M+, it is essential to comment out the body of - * the {@link - * com.hedera.services.bdd.spec.transactions.token.HapiTokenMint#updateStateOf(HapiSpec)} - * method, since it adds the minted token's creation time to the registry and the client will - * run OOM fairly quickly with a 1GB heap. - *
    2. There is evidence of slower memory leaks hidden elsewhere in the EET infrastructure, so you - * should probably not try to create more than 10M NFTs using a single run of this client; if - * more NFTs are needed, then please run several instances in sequence. - *

      Also for creating even more NFTs, it will be easier to run the JRS workflow {@code - * GCP-Create-Large-Volume-NFTs-SavedState.json} scheduled with CircleCi and modify the - * parameters here and the parameters in the JRS workflow and its referenced test config as - * well. - *

    - */ -public class UniqueTokenStateSetup extends HapiSuite { - private static final Logger log = LogManager.getLogger(UniqueTokenStateSetup.class); - - private final IntFunction treasuryNameFn = i -> "treasury" + i; - private final IntFunction uniqueTokenNameFn = i -> "uniqueToken" + i; - - private final AtomicLong duration = new AtomicLong(SECS_TO_RUN); - private final AtomicLong prepDuration = new AtomicLong(PREP_SECS_TO_RUN); - private final AtomicInteger maxOpsPerSec = new AtomicInteger(MINT_TPS); - private final AtomicInteger maxPrepOpsPerSecs = new AtomicInteger(CRYPTO_CREATE_TPS); - private final AtomicInteger numXferPrepAccounts = new AtomicInteger(NUM_PREPPED_XFER_ACCOUNTS); - private final AtomicInteger numTokens = new AtomicInteger(NUM_UNIQ_TOKENS); - private final AtomicInteger numNftsPerToken = new AtomicInteger(NFTS_PER_UNIQ_TOKEN); - private final AtomicInteger numNftsPerMintOp = new AtomicInteger(NEW_NFTS_PER_MINT_OP); - private final AtomicReference unit = new AtomicReference<>(SECONDS); - - public static void main(String... args) { - new UniqueTokenStateSetup().runSuiteSync(); - System.out.println("Created unique tokens from " + firstCreatedId + " + to " + lastCreatedId); - } - - @Override - public List getSpecsInSuite() { - return List.of(new HapiSpec[] { - createNfts(), - }); - } - - final HapiSpec createNfts() { - return defaultHapiSpec("CreateNfts") - .given( - stdMgmtOf(duration, unit, maxOpsPerSec, "mint_"), - mgmtOfIntProp(numTokens, "mint_numTokens"), - mgmtOfIntProp(numNftsPerToken, "mint_numNftsPerToken"), - mgmtOfIntProp(numNftsPerMintOp, "mint_numNftsPerMintOp"), - mgmtOfIntProp(maxPrepOpsPerSecs, "mint_maxPrepOpsPerSecs"), - mgmtOfIntProp(numXferPrepAccounts, "mint_numXferPrepAccounts"), - mgmtOfLongProp(prepDuration, "mint_prepDuration"), - withOpContext((spec, opLog) -> opLog.info( - "Resolved configuration:\n " - + "mint_prepDuration={}\n " - + "mint_numXferPrepAccounts={}\n " - + "mint_maxPrepOpsPerSecs={}\n " - + "mint_duration={}\n " - + "mint_maxOpsPerSec={}\n " - + "mint_numTokens={}\n " - + "mint_numNftsPerToken={}\n " - + "mint_numNftsPerMintOp={}", - prepDuration.get(), - numXferPrepAccounts.get(), - maxPrepOpsPerSecs.get(), - duration.get(), - maxPrepOpsPerSecs.get(), - numTokens.get(), - numNftsPerToken.get(), - numNftsPerMintOp.get()))) - .when( - runWithProvider(xferPrepAccountFactory()) - .lasting(prepDuration::get, unit::get) - .maxOpsPerSec(maxPrepOpsPerSecs::get), - sleepFor(20_000L)) - .then(runWithProvider(nftFactory()) - .lasting(duration::get, unit::get) - .maxOpsPerSec(maxOpsPerSec::get)); - } - - private Function xferPrepAccountFactory() { - final AtomicInteger xferAccountsCreated = new AtomicInteger(0); - - return spec -> (OpProvider) () -> { - if (xferAccountsCreated.get() >= numXferPrepAccounts.get()) { - return Optional.empty(); - } - final var op = cryptoCreate("xferPrep" + xferAccountsCreated.getAndIncrement()) - .payingWith(GENESIS) - .key(GENESIS) - .balance(10 * ONE_HUNDRED_HBARS) - .hasPrecheckFrom(OK, DUPLICATE_TRANSACTION) - .hasKnownStatusFrom(SUCCESS, UNKNOWN, TRANSACTION_EXPIRED) - .noLogging() - .rememberingNothing() - .deferStatusResolution(); - return Optional.of(op); - }; - } - - private Function nftFactory() { - final AtomicInteger uniqueTokensCreated = new AtomicInteger(0); - final AtomicInteger nftsMintedForCurrentUniqueToken = new AtomicInteger(0); - final AtomicBoolean done = new AtomicBoolean(false); - final AtomicReference currentUniqueToken = new AtomicReference<>(uniqueTokenNameFn.apply(0)); - - return spec -> new OpProvider() { - @Override - public List suggestedInitializers() { - final var numTreasuries = numTokens.get() / UNIQ_TOKENS_PER_TREASURY - + Math.min(1, numTokens.get() % UNIQ_TOKENS_PER_TREASURY); - final List inits = new ArrayList<>(); - inits.add(inParallel(IntStream.range(0, numTreasuries) - .mapToObj(i -> cryptoCreate(treasuryNameFn.apply(i)) - .payingWith(GENESIS) - .balance(0L) - .noLogging() - .key(GENESIS) - .hasRetryPrecheckFrom(DUPLICATE_TRANSACTION) - .hasKnownStatusFrom(SUCCESS, UNKNOWN, TRANSACTION_EXPIRED) - .deferStatusResolution()) - .toArray(HapiSpecOperation[]::new))); - inits.add(sleepFor(5_000L)); - inits.addAll( - burstedUniqCreations(UNIQ_TOKENS_BURST_SIZE, numTreasuries, UNIQ_TOKENS_POST_BURST_PAUSE_MS)); - return inits; - } - - @Override - public Optional get() { - if (done.get()) { - return Optional.empty(); - } - - final var currentToken = currentUniqueToken.get(); - if (nftsMintedForCurrentUniqueToken.get() < numNftsPerToken.get()) { - final List allMeta = new ArrayList<>(); - final int noMoreThan = numNftsPerToken.get() - nftsMintedForCurrentUniqueToken.get(); - for (int i = 0, n = Math.min(noMoreThan, numNftsPerMintOp.get()); i < n; i++) { - final var nextSerialNo = nftsMintedForCurrentUniqueToken.incrementAndGet(); - allMeta.add(metadataFor(currentToken, nextSerialNo)); - } - final var op = mintToken(currentToken, allMeta) - .payingWith(GENESIS) - .rememberingNothing() - .deferStatusResolution() - .fee(ONE_HBAR) - .hasPrecheckFrom(OK, DUPLICATE_TRANSACTION) - .hasKnownStatusFrom(SUCCESS, UNKNOWN, OK, TRANSACTION_EXPIRED, INVALID_TOKEN_ID) - .noLogging(); - return Optional.of(op); - } else { - nftsMintedForCurrentUniqueToken.set(0); - final var nextUniqTokenNo = uniqueTokensCreated.incrementAndGet(); - currentUniqueToken.set(uniqueTokenNameFn.apply(nextUniqTokenNo)); - if (nextUniqTokenNo >= numTokens.get()) { - System.out.println("Done creating " - + nextUniqTokenNo - + " unique tokens w/ approximately " - + (numNftsPerToken.get() * nextUniqTokenNo) - + " NFTs"); - done.set(true); - } - return Optional.empty(); - } - } - }; - } - - private List burstedUniqCreations(int perBurst, int numTreasuries, long pauseMs) { - final var createdSoFar = new AtomicInteger(0); - List ans = new ArrayList<>(); - while (createdSoFar.get() < numTokens.get()) { - var thisBurst = Math.min(numTokens.get() - createdSoFar.get(), perBurst); - final var burst = inParallel(IntStream.range(0, thisBurst) - .mapToObj(i -> tokenCreate(uniqueTokenNameFn.apply(i + createdSoFar.get())) - .payingWith(GENESIS) - .tokenType(NON_FUNGIBLE_UNIQUE) - .deferStatusResolution() - .noLogging() - .supplyType(INFINITE) - .initialSupply(0) - .supplyKey(GENESIS) - .hasPrecheckFrom(OK, DUPLICATE_TRANSACTION) - .hasKnownStatusFrom(SUCCESS, UNKNOWN, TRANSACTION_EXPIRED) - .treasury(treasuryNameFn.apply((i + createdSoFar.get()) % numTreasuries)) - .exposingCreatedIdTo(newId -> { - final var newN = numFrom(newId); - if (newN < numFrom(firstCreatedId.get())) { - firstCreatedId.set(newId); - lastCreatedId.set(newId); - } else if (lastCreatedId.get() == null || newN > numFrom(lastCreatedId.get())) { - lastCreatedId.set(newId); - } - if (newN % 100 == 0) { - System.out.println("Resolved" + " creation" + " for " + newId); - } - })) - .toArray(HapiSpecOperation[]::new)); - ans.add(burst); - ans.add(sleepFor(pauseMs)); - createdSoFar.addAndGet(thisBurst); - } - return ans; - } - - private long numFrom(String id) { - if (id == null) { - return Long.MAX_VALUE; - } - return Long.parseLong(id.substring(id.lastIndexOf('.') + 1)); - } - - private ByteString metadataFor(String uniqToken, int nftNo) { - final var base = new StringBuilder(uniqToken).append("-SN").append(nftNo); - var padding = METADATA_SIZE - base.length(); - while (padding-- > 0) { - base.append("_"); - } - return ByteString.copyFromUtf8(base.toString()); - } - - @Override - protected Logger getResultsLogger() { - return log; - } - - private static final AtomicReference firstCreatedId = new AtomicReference<>(null); - private static final AtomicReference lastCreatedId = new AtomicReference<>(null); - - private static final long SECS_TO_RUN = 4050; - private static final long PREP_SECS_TO_RUN = 25; - private static final int MINT_TPS = 250; - private static final int CRYPTO_CREATE_TPS = 1000; - private static final int NUM_PREPPED_XFER_ACCOUNTS = 10_000; - private static final int NUM_UNIQ_TOKENS = 10_000; - private static final int UNIQ_TOKENS_BURST_SIZE = 1000; - private static final int UNIQ_TOKENS_POST_BURST_PAUSE_MS = 10_000; - private static final int NFTS_PER_UNIQ_TOKEN = 1000; - private static final int NEW_NFTS_PER_MINT_OP = 10; - private static final int UNIQ_TOKENS_PER_TREASURY = 500; - private static final int METADATA_SIZE = 100; -} diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/perf/topic/CreateTopicPerfSuite.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/perf/topic/CreateTopicPerfSuite.java deleted file mode 100644 index a103a0aa4cf2..000000000000 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/perf/topic/CreateTopicPerfSuite.java +++ /dev/null @@ -1,86 +0,0 @@ -/* - * Copyright (C) 2020-2024 Hedera Hashgraph, LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.hedera.services.bdd.suites.perf.topic; - -import static com.hedera.services.bdd.spec.HapiSpec.defaultHapiSpec; -import static com.hedera.services.bdd.spec.keys.KeyShape.SIMPLE; -import static com.hedera.services.bdd.spec.keys.KeyShape.listOf; -import static com.hedera.services.bdd.spec.keys.KeyShape.threshOf; -import static com.hedera.services.bdd.spec.queries.QueryVerbs.getTopicInfo; -import static com.hedera.services.bdd.spec.transactions.TxnVerbs.createTopic; -import static com.hedera.services.bdd.spec.utilops.UtilVerbs.finishThroughputObs; -import static com.hedera.services.bdd.spec.utilops.UtilVerbs.inParallel; -import static com.hedera.services.bdd.spec.utilops.UtilVerbs.startThroughputObs; - -import com.hedera.services.bdd.spec.HapiSpec; -import com.hedera.services.bdd.spec.keys.KeyShape; -import com.hedera.services.bdd.suites.HapiSuite; -import java.util.Arrays; -import java.util.List; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; - -public class CreateTopicPerfSuite extends HapiSuite { - private static final Logger log = LogManager.getLogger(CreateTopicPerfSuite.class); - private static final String TEST_TOPIC = "testTopic"; - - public static void main(String... args) { - CreateTopicPerfSuite suite = new CreateTopicPerfSuite(); - suite.runSuiteSync(); - } - - @Override - public List getSpecsInSuite() { - return Arrays.asList(createTopicPerf()); - } - - @Override - public boolean canRunConcurrent() { - return false; - } - - final HapiSpec createTopicPerf() { - final int NUM_TOPICS = 100000; - - KeyShape submitKeyShape = threshOf(2, SIMPLE, SIMPLE, listOf(2)); - - return defaultHapiSpec("createTopicPerf") - .given() - .when( - startThroughputObs("createTopicThroughput").msToSaturateQueues(50L), - inParallel( - // only ask for record for the last transaction - asOpArray( - NUM_TOPICS, - i -> (i == (NUM_TOPICS - 1)) - ? createTopic(TEST_TOPIC + i).submitKeyShape(submitKeyShape) - : createTopic(TEST_TOPIC + i) - .submitKeyShape(submitKeyShape) - .deferStatusResolution()))) - .then( - // wait until the record of the last transaction are ready - finishThroughputObs("createTopicThroughput") - .gatedByQuery(() -> getTopicInfo(TEST_TOPIC + (NUM_TOPICS - 1))) - .sleepMs(1_000L) - .expiryMs(300_000L)); - } - - @Override - protected Logger getResultsLogger() { - return log; - } -} diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/perf/topic/HCSChunkingRealisticPerfSuite.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/perf/topic/HCSChunkingRealisticPerfSuite.java deleted file mode 100644 index fc50fb0a9e92..000000000000 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/perf/topic/HCSChunkingRealisticPerfSuite.java +++ /dev/null @@ -1,104 +0,0 @@ -/* - * Copyright (C) 2020-2024 Hedera Hashgraph, LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.hedera.services.bdd.suites.perf.topic; - -import static com.hedera.services.bdd.spec.HapiSpec.defaultHapiSpec; -import static com.hedera.services.bdd.spec.transactions.TxnVerbs.createTopic; -import static com.hedera.services.bdd.spec.transactions.TxnVerbs.cryptoCreate; -import static com.hedera.services.bdd.spec.utilops.UtilVerbs.chunkAFile; -import static com.hedera.services.bdd.spec.utilops.UtilVerbs.inParallel; -import static com.hedera.services.bdd.spec.utilops.UtilVerbs.logIt; -import static com.hedera.services.bdd.spec.utilops.UtilVerbs.newKeyNamed; -import static com.hedera.services.bdd.spec.utilops.UtilVerbs.sleepFor; -import static com.hedera.services.bdd.spec.utilops.UtilVerbs.withOpContext; -import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.BUSY; -import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.DUPLICATE_TRANSACTION; -import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.PLATFORM_TRANSACTION_NOT_CREATED; - -import com.hedera.services.bdd.spec.HapiSpec; -import com.hedera.services.bdd.spec.HapiSpecOperation; -import com.hedera.services.bdd.spec.utilops.CustomSpecAssert; -import com.hedera.services.bdd.spec.utilops.LoadTest; -import com.hedera.services.bdd.suites.perf.PerfTestLoadSettings; -import java.util.ArrayList; -import java.util.List; -import java.util.concurrent.atomic.AtomicLong; -import java.util.function.Supplier; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; - -public class HCSChunkingRealisticPerfSuite extends LoadTest { - - private static final Logger log = LogManager.getLogger(HCSChunkingRealisticPerfSuite.class); - private static final int CHUNK_SIZE = 150; - private static final String LARGE_FILE = "src/main/resource/contract/contracts/EmitEvent/EmitEvent.bin"; - private static final String PAYER = "payer"; - private static final String TOPIC = "topic"; - public static final int DEFAULT_COLLISION_AVOIDANCE_FACTOR = 2; - private static AtomicLong totalMsgSubmitted = new AtomicLong(0); - - public static void main(String... args) { - HCSChunkingRealisticPerfSuite suite = new HCSChunkingRealisticPerfSuite(); - suite.runSuiteSync(); - } - - @Override - public List getSpecsInSuite() { - return List.of(fragmentLongMessageIntoChunks()); - } - - private static HapiSpec fragmentLongMessageIntoChunks() { - PerfTestLoadSettings settings = new PerfTestLoadSettings(); - - Supplier submitBurst = - () -> new HapiSpecOperation[] {chunkAFile(LARGE_FILE, CHUNK_SIZE, PAYER, TOPIC, totalMsgSubmitted)}; - - return defaultHapiSpec("fragmentLongMessageIntoChunks") - .given( - withOpContext( - (spec, ignore) -> settings.setFrom(spec.setup().ciPropertiesMap())), - newKeyNamed("submitKey"), - logIt(ignore -> settings.toString())) - .when( - cryptoCreate(PAYER) - .balance(initialBalance.getAsLong()) - .withRecharging() - .rechargeWindow(30) - .hasRetryPrecheckFrom(BUSY, DUPLICATE_TRANSACTION, PLATFORM_TRANSACTION_NOT_CREATED), - withOpContext((spec, ignore) -> { - int factor = settings.getIntProperty( - "collisionAvoidanceFactor", DEFAULT_COLLISION_AVOIDANCE_FACTOR); - List opsList = new ArrayList(); - for (int i = 0; i < settings.getThreads() * factor; i++) { - var op = createTopic(TOPIC + i) - .submitKeyName("submitKey") - .hasRetryPrecheckFrom( - BUSY, DUPLICATE_TRANSACTION, PLATFORM_TRANSACTION_NOT_CREATED); - opsList.add(op); - } - CustomSpecAssert.allRunFor(spec, inParallel(flattened(opsList))); - }), - sleepFor(5000) // wait all other thread ready - ) - .then(defaultLoadTest(submitBurst, settings)); - } - - @Override - protected Logger getResultsLogger() { - return log; - } -} diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/perf/topic/SubmitMessageLoadTest.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/perf/topic/SubmitMessageLoadTest.java index 7c76b42fb1a6..feed5ac93f8e 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/perf/topic/SubmitMessageLoadTest.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/perf/topic/SubmitMessageLoadTest.java @@ -21,7 +21,6 @@ import static com.hedera.services.bdd.spec.transactions.TxnUtils.randomUtf8Bytes; import static com.hedera.services.bdd.spec.transactions.TxnVerbs.createTopic; import static com.hedera.services.bdd.spec.transactions.TxnVerbs.cryptoCreate; -import static com.hedera.services.bdd.spec.transactions.TxnVerbs.fileUpdate; import static com.hedera.services.bdd.spec.transactions.TxnVerbs.submitMessageTo; import static com.hedera.services.bdd.spec.utilops.UtilVerbs.keyFromPem; import static com.hedera.services.bdd.spec.utilops.UtilVerbs.logIt; @@ -40,7 +39,6 @@ import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.TRANSACTION_EXPIRED; import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.UNKNOWN; -import com.hedera.services.bdd.spec.HapiSpec; import com.hedera.services.bdd.spec.HapiSpecOperation; import com.hedera.services.bdd.spec.keys.KeyFactory; import com.hedera.services.bdd.spec.utilops.LoadTest; @@ -48,13 +46,14 @@ import java.nio.ByteBuffer; import java.time.Instant; import java.util.List; -import java.util.Map; import java.util.Random; import java.util.concurrent.atomic.AtomicInteger; import java.util.function.Supplier; +import java.util.stream.Stream; import org.apache.commons.lang3.ArrayUtils; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; +import org.junit.jupiter.api.DynamicTest; public class SubmitMessageLoadTest extends LoadTest { @@ -109,11 +108,11 @@ public static void main(String... args) { } @Override - public List getSpecsInSuite() { + public List> getSpecsInSuite() { return List.of(runSubmitMessages()); } - private static HapiSpec runSubmitMessages() { + final Stream runSubmitMessages() { PerfTestLoadSettings settings = new PerfTestLoadSettings(); final AtomicInteger submittedSoFar = new AtomicInteger(0); Supplier submitBurst = @@ -137,13 +136,6 @@ private static HapiSpec runSubmitMessages() { : sleepFor(100), logIt(ignore -> settings.toString())) .when( - fileUpdate(APP_PROPERTIES) - .payingWith(GENESIS) - .overridingProps(Map.of( - "hapi.throttling.buckets.fastOpBucket.capacity", - "4000", - "hapi.throttling.ops.consensusSubmitMessage.capacityRequired", - "1.0")), cryptoCreate(SENDER) .balance(ignore -> settings.getInitialBalance()) .withRecharging() @@ -200,7 +192,6 @@ private static Supplier opSupplier(PerfTestLoadSettings setti .payingWith(senderId) .signedBy(senderKey, submitKey) .fee(100_000_000) - .suppressStats(true) .hasRetryPrecheckFrom( BUSY, DUPLICATE_TRANSACTION, diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/perf/topic/SubmitMessagePerfSuite.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/perf/topic/SubmitMessagePerfSuite.java deleted file mode 100644 index 1738b85b5bc6..000000000000 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/perf/topic/SubmitMessagePerfSuite.java +++ /dev/null @@ -1,82 +0,0 @@ -/* - * Copyright (C) 2020-2024 Hedera Hashgraph, LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.hedera.services.bdd.suites.perf.topic; - -import static com.hedera.services.bdd.spec.HapiSpec.defaultHapiSpec; -import static com.hedera.services.bdd.spec.queries.QueryVerbs.getTopicInfo; -import static com.hedera.services.bdd.spec.transactions.TxnVerbs.createTopic; -import static com.hedera.services.bdd.spec.transactions.TxnVerbs.submitMessageTo; -import static com.hedera.services.bdd.spec.utilops.UtilVerbs.finishThroughputObs; -import static com.hedera.services.bdd.spec.utilops.UtilVerbs.inParallel; -import static com.hedera.services.bdd.spec.utilops.UtilVerbs.newKeyNamed; -import static com.hedera.services.bdd.spec.utilops.UtilVerbs.startThroughputObs; - -import com.hedera.services.bdd.spec.HapiSpec; -import com.hedera.services.bdd.suites.HapiSuite; -import java.util.Arrays; -import java.util.List; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; - -public class SubmitMessagePerfSuite extends HapiSuite { - private static final Logger log = LogManager.getLogger(SubmitMessagePerfSuite.class); - private static final String TEST_TOPIC = "testTopic"; - - public static void main(String... args) { - SubmitMessagePerfSuite suite = new SubmitMessagePerfSuite(); - suite.runSuiteSync(); - } - - @Override - public List getSpecsInSuite() { - return Arrays.asList(submitMessagePerf()); - } - - @Override - public boolean canRunConcurrent() { - return false; - } - - final HapiSpec submitMessagePerf() { - final int NUM_SUBMISSIONS = 100_000; - - return defaultHapiSpec("submitMessagePerf") - .given(newKeyNamed("submitKey"), createTopic(TEST_TOPIC).submitKeyName("submitKey")) - .when( - startThroughputObs("submitMessageThroughput").msToSaturateQueues(50L), - inParallel( - // only ask for record for the last transaction - asOpArray( - NUM_SUBMISSIONS, - i -> (i == (NUM_SUBMISSIONS - 1)) - ? submitMessageTo(TEST_TOPIC).message("testMessage" + i) - : submitMessageTo(TEST_TOPIC) - .message("testMessage" + i) - .deferStatusResolution()))) - .then(finishThroughputObs("submitMessageThroughput") - .gatedByQuery(() -> getTopicInfo(TEST_TOPIC) - .hasSeqNo(NUM_SUBMISSIONS) - .logged()) - .sleepMs(1_000L) - .expiryMs(300_000L)); - } - - @Override - protected Logger getResultsLogger() { - return log; - } -} diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/perf/topic/createTopicLoadTest.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/perf/topic/createTopicLoadTest.java deleted file mode 100644 index 8d50f09ec3f8..000000000000 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/perf/topic/createTopicLoadTest.java +++ /dev/null @@ -1,83 +0,0 @@ -/* - * Copyright (C) 2020-2024 Hedera Hashgraph, LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.hedera.services.bdd.suites.perf.topic; - -import static com.hedera.services.bdd.spec.HapiSpec.defaultHapiSpec; -import static com.hedera.services.bdd.spec.keys.KeyShape.SIMPLE; -import static com.hedera.services.bdd.spec.keys.KeyShape.listOf; -import static com.hedera.services.bdd.spec.keys.KeyShape.threshOf; -import static com.hedera.services.bdd.spec.transactions.TxnVerbs.createTopic; -import static com.hedera.services.bdd.spec.utilops.UtilVerbs.logIt; -import static com.hedera.services.bdd.spec.utilops.UtilVerbs.withOpContext; -import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.BUSY; -import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.DUPLICATE_TRANSACTION; -import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.PLATFORM_TRANSACTION_NOT_CREATED; - -import com.hedera.services.bdd.spec.HapiSpec; -import com.hedera.services.bdd.spec.HapiSpecOperation; -import com.hedera.services.bdd.spec.keys.KeyShape; -import com.hedera.services.bdd.spec.utilops.LoadTest; -import com.hedera.services.bdd.suites.perf.PerfTestLoadSettings; -import java.util.List; -import java.util.concurrent.atomic.AtomicInteger; -import java.util.function.Supplier; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; - -public class createTopicLoadTest extends LoadTest { - - private static final Logger log = LogManager.getLogger(createTopicLoadTest.class); - - public static void main(String... args) { - parseArgs(args); - - createTopicLoadTest suite = new createTopicLoadTest(); - suite.runSuiteSync(); - } - - @Override - public List getSpecsInSuite() { - return List.of(runCreateTopics()); - } - - private static HapiSpec runCreateTopics() { - PerfTestLoadSettings settings = new PerfTestLoadSettings(); - final AtomicInteger submittedSoFar = new AtomicInteger(0); - KeyShape submitKeyShape = threshOf(2, SIMPLE, SIMPLE, listOf(2)); - - Supplier submitBurst = () -> new HapiSpecOperation[] { - createTopic("testTopic" + submittedSoFar.addAndGet(1)) - .submitKeyShape(submitKeyShape) - .noLogging() - .hasRetryPrecheckFrom(BUSY, DUPLICATE_TRANSACTION, PLATFORM_TRANSACTION_NOT_CREATED) - .deferStatusResolution() - }; - - return defaultHapiSpec("runCreateTopics") - .given( - withOpContext( - (spec, ignore) -> settings.setFrom(spec.setup().ciPropertiesMap())), - logIt(ignore -> settings.toString())) - .when() - .then(defaultLoadTest(submitBurst, settings)); - } - - @Override - protected Logger getResultsLogger() { - return log; - } -} diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/reconnect/AutoAccountCreationValidationsAfterReconnect.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/reconnect/AutoAccountCreationValidationsAfterReconnect.java deleted file mode 100644 index 99c4c21bfd68..000000000000 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/reconnect/AutoAccountCreationValidationsAfterReconnect.java +++ /dev/null @@ -1,60 +0,0 @@ -/* - * Copyright (C) 2020-2024 Hedera Hashgraph, LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.hedera.services.bdd.suites.reconnect; - -import static com.hedera.services.bdd.spec.HapiSpec.defaultHapiSpec; -import static com.hedera.services.bdd.spec.queries.QueryVerbs.getAccountInfo; -import static com.hedera.services.bdd.spec.utilops.UtilVerbs.inParallel; -import static com.hedera.services.bdd.suites.reconnect.AutoAccountCreationsBeforeReconnect.TOTAL_ACCOUNTS; - -import com.hedera.services.bdd.junit.HapiTest; -import com.hedera.services.bdd.spec.HapiSpec; -import com.hedera.services.bdd.spec.assertions.AccountInfoAsserts; -import com.hedera.services.bdd.suites.HapiSuite; -import java.util.List; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; - -public class AutoAccountCreationValidationsAfterReconnect extends HapiSuite { - private static final Logger log = LogManager.getLogger(AutoAccountCreationValidationsAfterReconnect.class); - - @Override - protected Logger getResultsLogger() { - return log; - } - - @Override - public List getSpecsInSuite() { - return List.of(getAccountInfoOfAutomaticallyCreatedAccounts()); - } - - public static void main(String... args) { - new AutoAccountCreationValidationsAfterReconnect().runSuiteSync(); - } - /* These validations are assuming the state is from a 6N-1C test in which a client generates 10 autoAccounts in the - * beginning of the test */ - @HapiTest - final HapiSpec getAccountInfoOfAutomaticallyCreatedAccounts() { - return defaultHapiSpec("GetAccountInfoOfAutomaticallyCreatedAccounts") - .given() - .when() - .then(inParallel(asOpArray(TOTAL_ACCOUNTS, i -> getAccountInfo("0.0." + (i + 1054)) - .has(AccountInfoAsserts.accountWith().hasAlias()) - .setNode("0.0.8") - .logged()))); - } -} diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/reconnect/AutoAccountCreationsBeforeReconnect.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/reconnect/AutoAccountCreationsBeforeReconnect.java deleted file mode 100644 index 88293a0ced68..000000000000 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/reconnect/AutoAccountCreationsBeforeReconnect.java +++ /dev/null @@ -1,64 +0,0 @@ -/* - * Copyright (C) 2020-2024 Hedera Hashgraph, LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.hedera.services.bdd.suites.reconnect; - -import static com.hedera.services.bdd.spec.HapiSpec.defaultHapiSpec; -import static com.hedera.services.bdd.spec.transactions.TxnVerbs.cryptoTransfer; -import static com.hedera.services.bdd.spec.transactions.crypto.HapiCryptoTransfer.tinyBarsFromToWithAlias; -import static com.hedera.services.bdd.spec.utilops.CustomSpecAssert.allRunFor; -import static com.hedera.services.bdd.spec.utilops.UtilVerbs.newKeyNamed; -import static com.hedera.services.bdd.spec.utilops.UtilVerbs.withOpContext; - -import com.hedera.services.bdd.spec.HapiSpec; -import com.hedera.services.bdd.spec.HapiSpecOperation; -import com.hedera.services.bdd.suites.HapiSuite; -import java.util.ArrayList; -import java.util.List; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; - -public class AutoAccountCreationsBeforeReconnect extends HapiSuite { - private static final Logger log = LogManager.getLogger(AutoAccountCreationsBeforeReconnect.class); - - public static final int TOTAL_ACCOUNTS = 10; - - @Override - protected Logger getResultsLogger() { - return log; - } - - @Override - public List getSpecsInSuite() { - return List.of(createAccountsUsingAlias()); - } - - public static void main(String... args) { - new AutoAccountCreationsBeforeReconnect().runSuiteSync(); - } - - final HapiSpec createAccountsUsingAlias() { - return defaultHapiSpec("createAccountsUsingAlias").given().when().then(withOpContext((spec, opLog) -> { - List ops = new ArrayList<>(); - for (int i = 0; i < TOTAL_ACCOUNTS; i++) { - var alias = "alias" + i; - ops.add(newKeyNamed(alias)); - ops.add(cryptoTransfer(tinyBarsFromToWithAlias(DEFAULT_PAYER, alias, ONE_HBAR))); - } - allRunFor(spec, ops); - })); - } -} diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/reconnect/AutoRenewEntitiesForReconnect.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/reconnect/AutoRenewEntitiesForReconnect.java deleted file mode 100644 index a6ad4725bd81..000000000000 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/reconnect/AutoRenewEntitiesForReconnect.java +++ /dev/null @@ -1,121 +0,0 @@ -/* - * Copyright (C) 2021-2024 Hedera Hashgraph, LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.hedera.services.bdd.suites.reconnect; - -import static com.hedera.services.bdd.spec.HapiSpec.defaultHapiSpec; -import static com.hedera.services.bdd.spec.queries.QueryVerbs.getAccountBalance; -import static com.hedera.services.bdd.spec.transactions.TxnVerbs.cryptoCreate; -import static com.hedera.services.bdd.spec.transactions.TxnVerbs.cryptoTransfer; -import static com.hedera.services.bdd.spec.transactions.TxnVerbs.fileUpdate; -import static com.hedera.services.bdd.spec.transactions.crypto.HapiCryptoTransfer.tinyBarsFromTo; -import static com.hedera.services.bdd.spec.utilops.UtilVerbs.withLiveNode; -import static com.hedera.services.bdd.spec.utilops.UtilVerbs.withOpContext; -import static com.hedera.services.bdd.suites.autorenew.AutoRenewConfigChoices.disablingAutoRenewWithDefaults; -import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.INVALID_ACCOUNT_ID; - -import com.hedera.services.bdd.spec.HapiSpec; -import com.hedera.services.bdd.spec.HapiSpecOperation; -import com.hedera.services.bdd.spec.utilops.CustomSpecAssert; -import com.hedera.services.bdd.suites.HapiSuite; -import com.hedera.services.bdd.suites.autorenew.AutoRenewConfigChoices; -import java.util.ArrayList; -import java.util.List; -import java.util.Set; -import java.util.concurrent.TimeUnit; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; - -public class AutoRenewEntitiesForReconnect extends HapiSuite { - private static final Logger log = LogManager.getLogger(AutoRenewEntitiesForReconnect.class); - - public static void main(String... args) { - new AutoRenewEntitiesForReconnect().runSuiteSync(); - } - - @Override - public List getSpecsInSuite() { - return List.of( - runTransfersBeforeReconnect(), - autoRenewAccountGetsDeletedOnReconnectingNodeAsWell(), - accountAutoRenewalSuiteCleanup()); - } - - final HapiSpec autoRenewAccountGetsDeletedOnReconnectingNodeAsWell() { - String autoDeleteAccount = "autoDeleteAccount"; - int autoRenewSecs = 1; - return defaultHapiSpec("AutoRenewAccountGetsDeletedOnReconnectingNodeAsWell") - .given( - fileUpdate(APP_PROPERTIES) - .payingWith(GENESIS) - .overridingProps( - AutoRenewConfigChoices.propsForAccountAutoRenewOnWith(autoRenewSecs, 0, 100, 2)) - .erasingProps(Set.of("minimumAutoRenewDuration")), - cryptoCreate(autoDeleteAccount) - .autoRenewSecs(autoRenewSecs) - .balance(0L)) - .when( - // do some transfers so that we pass autoRenewSecs - withOpContext((spec, ctxLog) -> { - List opsList = new ArrayList(); - for (int i = 0; i < 50; i++) { - opsList.add(cryptoTransfer(tinyBarsFromTo(GENESIS, NODE, 1L)) - .logged()); - } - CustomSpecAssert.allRunFor(spec, opsList); - }), - withLiveNode("0.0.8") - .within(120, TimeUnit.SECONDS) - .loggingAvailabilityEvery(10) - .sleepingBetweenRetriesFor(10)) - .then(getAccountBalance(autoDeleteAccount) - .setNode("0.0.8") - .hasAnswerOnlyPrecheckFrom(INVALID_ACCOUNT_ID)); - } - - final HapiSpec accountAutoRenewalSuiteCleanup() { - return defaultHapiSpec("accountAutoRenewalSuiteCleanup") - .given() - .when() - .then(fileUpdate(APP_PROPERTIES).payingWith(GENESIS).overridingProps(disablingAutoRenewWithDefaults())); - } - - @Override - protected Logger getResultsLogger() { - return log; - } - - /** - * Since reconnect is not supported when node starts from genesis, run some transactions before - * running correctness tests so that a state is saved before reconnect. - * - * @return a {@link HapiSpec} to do some crypto transfer transactions before reconnect - */ - public static HapiSpec runTransfersBeforeReconnect() { - return defaultHapiSpec("runTransfersBeforeReconnect") - .given() - .when() - .then( - // do some transfers to save a state before reconnect - withOpContext((spec, ctxLog) -> { - List opsList = new ArrayList(); - for (int i = 0; i < 125; i++) { - opsList.add(cryptoTransfer(tinyBarsFromTo(GENESIS, NODE, 1L))); - } - CustomSpecAssert.allRunFor(spec, opsList); - })); - } -} diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/reconnect/CheckUnavailableNode.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/reconnect/CheckUnavailableNode.java deleted file mode 100644 index 5ae68da4f101..000000000000 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/reconnect/CheckUnavailableNode.java +++ /dev/null @@ -1,51 +0,0 @@ -/* - * Copyright (C) 2020-2024 Hedera Hashgraph, LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.hedera.services.bdd.suites.reconnect; - -import static com.hedera.services.bdd.spec.HapiSpec.defaultHapiSpec; -import static com.hedera.services.bdd.spec.queries.QueryVerbs.getAccountBalance; - -import com.hedera.services.bdd.spec.HapiSpec; -import com.hedera.services.bdd.suites.HapiSuite; -import java.util.List; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; - -public class CheckUnavailableNode extends HapiSuite { - private static final Logger log = LogManager.getLogger(CheckUnavailableNode.class); - - public static void main(String... args) { - new CheckUnavailableNode().runSuiteSync(); - } - - @Override - public List getSpecsInSuite() { - return List.of(checkUnavailableNode()); - } - - final HapiSpec checkUnavailableNode() { - return defaultHapiSpec("CheckUnavailableNode") - .given() - .when() - .then(getAccountBalance(GENESIS).setNode("0.0.6").unavailableNode()); - } - - @Override - protected Logger getResultsLogger() { - return log; - } -} diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/reconnect/CreateAccountsBeforeReconnect.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/reconnect/CreateAccountsBeforeReconnect.java index 63e793b27ca1..1f74f5953926 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/reconnect/CreateAccountsBeforeReconnect.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/reconnect/CreateAccountsBeforeReconnect.java @@ -25,15 +25,16 @@ import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.DUPLICATE_TRANSACTION; import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.PLATFORM_TRANSACTION_NOT_CREATED; -import com.hedera.services.bdd.spec.HapiSpec; import com.hedera.services.bdd.spec.HapiSpecOperation; import com.hedera.services.bdd.suites.HapiSuite; import com.hedera.services.bdd.suites.perf.PerfTestLoadSettings; import java.util.List; import java.util.concurrent.atomic.AtomicInteger; import java.util.function.Supplier; +import java.util.stream.Stream; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; +import org.junit.jupiter.api.DynamicTest; public class CreateAccountsBeforeReconnect extends HapiSuite { private static final Logger log = LogManager.getLogger(CreateAccountsBeforeReconnect.class); @@ -51,7 +52,7 @@ public static void main(String... args) { private static final AtomicInteger accountNumber = new AtomicInteger(0); @Override - public List getSpecsInSuite() { + public List> getSpecsInSuite() { return List.of(runCreateAccounts()); } @@ -68,7 +69,7 @@ private synchronized HapiSpecOperation generateCreateAccountOperation() { .deferStatusResolution(); } - final HapiSpec runCreateAccounts() { + final Stream runCreateAccounts() { PerfTestLoadSettings settings = new PerfTestLoadSettings( ACCOUNT_CREATION_RECONNECT_TPS, DEFAULT_MINS_FOR_RECONNECT_TESTS, DEFAULT_THREADS_FOR_RECONNECT_TESTS); diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/reconnect/CreateFilesBeforeReconnect.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/reconnect/CreateFilesBeforeReconnect.java index 3f8b99a44c3b..d0d4dde4c2b8 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/reconnect/CreateFilesBeforeReconnect.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/reconnect/CreateFilesBeforeReconnect.java @@ -27,15 +27,16 @@ import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.DUPLICATE_TRANSACTION; import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.PLATFORM_TRANSACTION_NOT_CREATED; -import com.hedera.services.bdd.spec.HapiSpec; import com.hedera.services.bdd.spec.HapiSpecOperation; import com.hedera.services.bdd.suites.HapiSuite; import com.hedera.services.bdd.suites.perf.PerfTestLoadSettings; import java.util.List; import java.util.concurrent.atomic.AtomicInteger; import java.util.function.Supplier; +import java.util.stream.Stream; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; +import org.junit.jupiter.api.DynamicTest; public class CreateFilesBeforeReconnect extends HapiSuite { private static final Logger log = LogManager.getLogger(CreateFilesBeforeReconnect.class); @@ -50,7 +51,7 @@ public static void main(String... args) { private static final AtomicInteger fileNumber = new AtomicInteger(0); @Override - public List getSpecsInSuite() { + public List> getSpecsInSuite() { return List.of(runCreateFiles()); } @@ -67,7 +68,7 @@ private synchronized HapiSpecOperation generateFileCreateOperation() { .deferStatusResolution(); } - final HapiSpec runCreateFiles() { + final Stream runCreateFiles() { PerfTestLoadSettings settings = new PerfTestLoadSettings( FILE_CREATION_RECONNECT_TPS, DEFAULT_MINS_FOR_RECONNECT_TESTS, DEFAULT_THREADS_FOR_RECONNECT_TESTS); diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/reconnect/CreateSchedulesBeforeReconnect.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/reconnect/CreateSchedulesBeforeReconnect.java deleted file mode 100644 index 978fc2c0340e..000000000000 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/reconnect/CreateSchedulesBeforeReconnect.java +++ /dev/null @@ -1,146 +0,0 @@ -/* - * Copyright (C) 2020-2024 Hedera Hashgraph, LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.hedera.services.bdd.suites.reconnect; - -import static com.hedera.services.bdd.spec.HapiSpec.defaultHapiSpec; -import static com.hedera.services.bdd.spec.infrastructure.OpProvider.STANDARD_PERMISSIBLE_PRECHECKS; -import static com.hedera.services.bdd.spec.transactions.TxnVerbs.cryptoCreate; -import static com.hedera.services.bdd.spec.transactions.TxnVerbs.cryptoTransfer; -import static com.hedera.services.bdd.spec.transactions.TxnVerbs.fileUpdate; -import static com.hedera.services.bdd.spec.transactions.TxnVerbs.scheduleCreate; -import static com.hedera.services.bdd.spec.transactions.crypto.HapiCryptoTransfer.tinyBarsFromTo; -import static com.hedera.services.bdd.spec.utilops.LoadTest.defaultLoadTest; -import static com.hedera.services.bdd.spec.utilops.LoadTest.initialBalance; -import static com.hedera.services.bdd.spec.utilops.UtilVerbs.logIt; -import static com.hedera.services.bdd.spec.utilops.UtilVerbs.noOp; -import static com.hedera.services.bdd.spec.utilops.UtilVerbs.sleepFor; -import static com.hedera.services.bdd.suites.perf.PerfUtilOps.scheduleOpsEnablement; -import static com.hedera.services.bdd.suites.reconnect.CreateAccountsBeforeReconnect.DEFAULT_MINS_FOR_RECONNECT_TESTS; -import static com.hedera.services.bdd.suites.reconnect.CreateAccountsBeforeReconnect.DEFAULT_THREADS_FOR_RECONNECT_TESTS; -import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.BUSY; -import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.DUPLICATE_TRANSACTION; -import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.PLATFORM_TRANSACTION_NOT_CREATED; -import static org.apache.commons.lang3.SystemUtils.getHostName; - -import com.hedera.services.bdd.spec.HapiSpec; -import com.hedera.services.bdd.spec.HapiSpecOperation; -import com.hedera.services.bdd.suites.HapiSuite; -import com.hedera.services.bdd.suites.perf.PerfTestLoadSettings; -import java.util.List; -import java.util.Map; -import java.util.concurrent.atomic.AtomicInteger; -import java.util.function.Supplier; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; - -public class CreateSchedulesBeforeReconnect extends HapiSuite { - private static final Logger log = LogManager.getLogger(CreateSchedulesBeforeReconnect.class); - - private static final int SCHEDULE_CREATION_LIMIT = 20000; - private static final int SCHEDULE_CREATION_RECONNECT_TPS = 120; - private static final String SCHEDULE = "schedule-"; - private static final String SCHEDULE_SENDER = "scheduleSender"; - private static final String SCHEDULE_RECEIVER = "scheduleReceiver"; - - public static void main(String... args) { - new CreateSchedulesBeforeReconnect().runSuiteSync(); - } - - private static final AtomicInteger scheduleNumber = new AtomicInteger(0); - - @Override - public List getSpecsInSuite() { - return List.of(runCreateSchedules()); - } - - private HapiSpecOperation generateScheduleCreateOperation() { - if (scheduleNumber.getAndIncrement() > SCHEDULE_CREATION_LIMIT) { - return noOp(); - } - - return scheduleCreate( - SCHEDULE + getHostName() + "-" + scheduleNumber.getAndIncrement(), - cryptoTransfer(tinyBarsFromTo(SCHEDULE_SENDER, SCHEDULE_RECEIVER, 1))) - .signedBy(DEFAULT_PAYER) - .fee(ONE_HUNDRED_HBARS) - .alsoSigningWith(SCHEDULE_SENDER) - .hasPrecheckFrom(STANDARD_PERMISSIBLE_PRECHECKS) - .hasAnyKnownStatus() - .deferStatusResolution() - .adminKey(DEFAULT_PAYER) - .noLogging() - .advertisingCreation(); - } - - final HapiSpec runCreateSchedules() { - PerfTestLoadSettings settings = new PerfTestLoadSettings( - SCHEDULE_CREATION_RECONNECT_TPS, DEFAULT_MINS_FOR_RECONNECT_TESTS, DEFAULT_THREADS_FOR_RECONNECT_TESTS); - - Supplier createBurst = () -> new HapiSpecOperation[] {generateScheduleCreateOperation()}; - - return defaultHapiSpec("RunCreateSchedules") - .given( - scheduleOpsEnablement(), - logIt(ignore -> settings.toString()), - cryptoCreate(SCHEDULE_SENDER) - .balance(initialBalance.getAsLong()) - .key(GENESIS) - .hasRetryPrecheckFrom(BUSY, DUPLICATE_TRANSACTION, PLATFORM_TRANSACTION_NOT_CREATED) - .deferStatusResolution(), - cryptoCreate(SCHEDULE_RECEIVER) - .key(GENESIS) - .hasRetryPrecheckFrom(BUSY, DUPLICATE_TRANSACTION, PLATFORM_TRANSACTION_NOT_CREATED) - .deferStatusResolution(), - sleepFor(10000), - scheduleCreate( - SCHEDULE + getHostName() + "-" + scheduleNumber.getAndIncrement(), - cryptoTransfer(tinyBarsFromTo(SCHEDULE_SENDER, SCHEDULE_RECEIVER, 1))) - .signedBy(DEFAULT_PAYER) - .fee(ONE_HUNDRED_HBARS) - .alsoSigningWith(SCHEDULE_SENDER) - .hasPrecheckFrom(STANDARD_PERMISSIBLE_PRECHECKS) - .hasAnyKnownStatus() - .deferStatusResolution() - .adminKey(DEFAULT_PAYER) - .noLogging() - .advertisingCreation()) - .when( - fileUpdate(APP_PROPERTIES) - .payingWith(GENESIS) - .overridingProps(Map.of("ledger.schedule.txExpiryTimeSecs", "" + 60)), - sleepFor(10000)) - .then( - scheduleCreate( - SCHEDULE + getHostName() + "-" + scheduleNumber.getAndIncrement(), - cryptoTransfer(tinyBarsFromTo(SCHEDULE_SENDER, SCHEDULE_RECEIVER, 1))) - .signedBy(DEFAULT_PAYER) - .fee(ONE_HUNDRED_HBARS) - .alsoSigningWith(SCHEDULE_SENDER) - .hasPrecheckFrom(STANDARD_PERMISSIBLE_PRECHECKS) - .hasAnyKnownStatus() - .deferStatusResolution() - .adminKey(DEFAULT_PAYER) - .noLogging() - .advertisingCreation(), - defaultLoadTest(createBurst, settings)); - } - - @Override - protected Logger getResultsLogger() { - return log; - } -} diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/reconnect/CreateTokensBeforeReconnect.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/reconnect/CreateTokensBeforeReconnect.java deleted file mode 100644 index f571cee82a2c..000000000000 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/reconnect/CreateTokensBeforeReconnect.java +++ /dev/null @@ -1,90 +0,0 @@ -/* - * Copyright (C) 2020-2024 Hedera Hashgraph, LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.hedera.services.bdd.suites.reconnect; - -import static com.hedera.services.bdd.spec.HapiSpec.defaultHapiSpec; -import static com.hedera.services.bdd.spec.transactions.TxnVerbs.tokenCreate; -import static com.hedera.services.bdd.spec.utilops.LoadTest.defaultLoadTest; -import static com.hedera.services.bdd.spec.utilops.UtilVerbs.logIt; -import static com.hedera.services.bdd.spec.utilops.UtilVerbs.noOp; -import static com.hedera.services.bdd.suites.reconnect.CreateAccountsBeforeReconnect.DEFAULT_MINS_FOR_RECONNECT_TESTS; -import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.BUSY; -import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.PLATFORM_TRANSACTION_NOT_CREATED; -import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.SUCCESS; -import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.TOKEN_ALREADY_ASSOCIATED_TO_ACCOUNT; -import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.UNKNOWN; - -import com.hedera.services.bdd.spec.HapiSpec; -import com.hedera.services.bdd.spec.HapiSpecOperation; -import com.hedera.services.bdd.suites.HapiSuite; -import com.hedera.services.bdd.suites.perf.PerfTestLoadSettings; -import java.util.List; -import java.util.concurrent.atomic.AtomicInteger; -import java.util.function.Supplier; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; - -public class CreateTokensBeforeReconnect extends HapiSuite { - private static final Logger log = LogManager.getLogger(CreateTokensBeforeReconnect.class); - - private static final int TOKEN_CREATION_LIMIT = 300000; - private static final int TOKEN_CREATION_RECONNECT_TPS = 120; - private static final int DEFAULT_TOKEN_THREADS_FOR_RECONNECT_TESTS = 10; - - public static void main(String... args) { - new CreateTokensBeforeReconnect().runSuiteSync(); - } - - private static final AtomicInteger tokenNumber = new AtomicInteger(0); - - @Override - public List getSpecsInSuite() { - return List.of(runCreateTopics()); - } - - private HapiSpecOperation generateTopicCreateOperation() { - final long token = tokenNumber.getAndIncrement(); - if (token >= TOKEN_CREATION_LIMIT) { - return noOp(); - } - - return tokenCreate("token" + token) - .noLogging() - .hasRetryPrecheckFrom(BUSY, PLATFORM_TRANSACTION_NOT_CREATED) - .hasKnownStatusFrom(SUCCESS, TOKEN_ALREADY_ASSOCIATED_TO_ACCOUNT, UNKNOWN) - .deferStatusResolution(); - } - - final HapiSpec runCreateTopics() { - PerfTestLoadSettings settings = new PerfTestLoadSettings( - TOKEN_CREATION_RECONNECT_TPS, - DEFAULT_MINS_FOR_RECONNECT_TESTS, - DEFAULT_TOKEN_THREADS_FOR_RECONNECT_TESTS); - - Supplier createBurst = () -> new HapiSpecOperation[] {generateTopicCreateOperation()}; - - return defaultHapiSpec("RunCreateTokens") - .given(logIt(ignore -> settings.toString())) - .when() - .then(defaultLoadTest(createBurst, settings)); - } - - @Override - protected Logger getResultsLogger() { - return log; - } -} diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/reconnect/CreateTopicsBeforeReconnect.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/reconnect/CreateTopicsBeforeReconnect.java index 524a741799b3..7d5691afd765 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/reconnect/CreateTopicsBeforeReconnect.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/reconnect/CreateTopicsBeforeReconnect.java @@ -27,15 +27,16 @@ import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.DUPLICATE_TRANSACTION; import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.PLATFORM_TRANSACTION_NOT_CREATED; -import com.hedera.services.bdd.spec.HapiSpec; import com.hedera.services.bdd.spec.HapiSpecOperation; import com.hedera.services.bdd.suites.HapiSuite; import com.hedera.services.bdd.suites.perf.PerfTestLoadSettings; import java.util.List; import java.util.concurrent.atomic.AtomicInteger; import java.util.function.Supplier; +import java.util.stream.Stream; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; +import org.junit.jupiter.api.DynamicTest; public class CreateTopicsBeforeReconnect extends HapiSuite { private static final Logger log = LogManager.getLogger(CreateTopicsBeforeReconnect.class); @@ -50,7 +51,7 @@ public static void main(String... args) { private static final AtomicInteger topicNumber = new AtomicInteger(0); @Override - public List getSpecsInSuite() { + public List> getSpecsInSuite() { return List.of(runCreateTopics()); } @@ -66,7 +67,7 @@ private synchronized HapiSpecOperation generateTopicCreateOperation() { .deferStatusResolution(); } - final HapiSpec runCreateTopics() { + final Stream runCreateTopics() { PerfTestLoadSettings settings = new PerfTestLoadSettings( TOPIC_CREATION_RECONNECT_TPS, DEFAULT_MINS_FOR_RECONNECT_TESTS, DEFAULT_THREADS_FOR_RECONNECT_TESTS); diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/reconnect/MixedValidationsAfterReconnect.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/reconnect/MixedValidationsAfterReconnect.java deleted file mode 100644 index 3206169f6942..000000000000 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/reconnect/MixedValidationsAfterReconnect.java +++ /dev/null @@ -1,134 +0,0 @@ -/* - * Copyright (C) 2020-2024 Hedera Hashgraph, LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.hedera.services.bdd.suites.reconnect; - -import static com.hedera.services.bdd.spec.HapiSpec.defaultHapiSpec; -import static com.hedera.services.bdd.spec.assertions.AccountInfoAsserts.changeFromSnapshot; -import static com.hedera.services.bdd.spec.queries.QueryVerbs.getAccountBalance; -import static com.hedera.services.bdd.spec.queries.QueryVerbs.getFileInfo; -import static com.hedera.services.bdd.spec.queries.QueryVerbs.getTopicInfo; -import static com.hedera.services.bdd.spec.utilops.UtilVerbs.balanceSnapshot; - -import com.hedera.services.bdd.spec.HapiSpec; -import com.hedera.services.bdd.suites.HapiSuite; -import com.hederahashgraph.api.proto.java.ResponseCodeEnum; -import java.util.List; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; - -public class MixedValidationsAfterReconnect extends HapiSuite { - private static final Logger log = LogManager.getLogger(MixedValidationsAfterReconnect.class); - - private static final String SENDER = "0.0.1301"; - private static final String RECEIVER = "0.0.1302"; - private static final String LAST_CREATED_ACCOUNT = "0.0.3500"; - private static final String FIRST_CREATED_TOPIC = "0.0.3600"; - private static final String LAST_CREATED_TOPIC = "0.0.5900"; - private static final String INVALID_TOPIC_ID = "0.0.41064"; - private static final String TOPIC_ID_WITH_MESSAGE_SUBMITTED_TO = "0.0.5900"; - - private static final String FIRST_CREATED_FILE = "0.0.6100"; - private static final String LAST_CREATED_FILE = "0.0.6900"; - private static final String INVALID_FILE_ID = "0.0.7064"; - - public static void main(String... args) { - new MixedValidationsAfterReconnect().runSuiteSync(); - } - - @Override - public List getSpecsInSuite() { - return List.of(getAccountBalanceFromAllNodes(), validateTopicInfo(), validateFileInfo()); - } - - final HapiSpec getAccountBalanceFromAllNodes() { - // Since https://github.com/hashgraph/hedera-services/pull/5799, the nodes will create - // 299 "blocklist" accounts with EVM addresses commonly used in HardHat test environments, - // to protect developers from accidentally sending hbar to those addresses - // NOTE: blacklisted accounts are NOT created for use when blacklisted accounts are disabled - return defaultHapiSpec("GetAccountBalanceFromAllNodes") - .given() - .when() - .then( - balanceSnapshot("senderBalance", SENDER), // from default node 0.0.3 - balanceSnapshot("receiverBalance", RECEIVER), // from default node 0.0.3 - balanceSnapshot("lastlyCreatedAccountBalance", LAST_CREATED_ACCOUNT), // from default node 0.0.3 - getAccountBalance(SENDER) - .logged() - .setNode("0.0.4") - .hasTinyBars(changeFromSnapshot("senderBalance", 0)), - getAccountBalance(RECEIVER) - .logged() - .setNode("0.0.4") - .hasTinyBars(changeFromSnapshot("receiverBalance", 0)), - getAccountBalance(SENDER) - .logged() - .setNode("0.0.5") - .hasTinyBars(changeFromSnapshot("senderBalance", 0)), - getAccountBalance(RECEIVER) - .logged() - .setNode("0.0.5") - .hasTinyBars(changeFromSnapshot("receiverBalance", 0)), - getAccountBalance(SENDER) - .logged() - .setNode("0.0.8") - .hasTinyBars(changeFromSnapshot("senderBalance", 0)), - getAccountBalance(RECEIVER) - .logged() - .setNode("0.0.8") - .hasTinyBars(changeFromSnapshot("receiverBalance", 0)), - getAccountBalance(LAST_CREATED_ACCOUNT) - .logged() - .setNode("0.0.8") - .hasTinyBars(changeFromSnapshot("lastlyCreatedAccountBalance", 0))); - } - - final HapiSpec validateTopicInfo() { - final byte[] emptyRunningHash = new byte[48]; - return defaultHapiSpec("ValidateTopicInfo") - .given(getTopicInfo(TOPIC_ID_WITH_MESSAGE_SUBMITTED_TO).logged().saveRunningHash()) - .when() - .then( - getTopicInfo(FIRST_CREATED_TOPIC) - .logged() - .setNode("0.0.8") - .hasRunningHash(emptyRunningHash), - getTopicInfo(LAST_CREATED_TOPIC) - .logged() - .setNode("0.0.8") - .hasRunningHash(emptyRunningHash), - getTopicInfo(INVALID_TOPIC_ID).hasCostAnswerPrecheck(ResponseCodeEnum.INVALID_TOPIC_ID), - getTopicInfo(TOPIC_ID_WITH_MESSAGE_SUBMITTED_TO) - .logged() - .setNode("0.0.8") - .hasRunningHash(TOPIC_ID_WITH_MESSAGE_SUBMITTED_TO)); - } - - final HapiSpec validateFileInfo() { - return defaultHapiSpec("ValidateFileInfo") - .given() - .when() - .then( - getFileInfo(FIRST_CREATED_FILE).logged().setNode("0.0.8"), - getFileInfo(LAST_CREATED_FILE).logged().setNode("0.0.8"), - getFileInfo(INVALID_FILE_ID).hasCostAnswerPrecheck(ResponseCodeEnum.INVALID_FILE_ID)); - } - - @Override - protected Logger getResultsLogger() { - return log; - } -} diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/reconnect/SchedulesExpiryDuringReconnect.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/reconnect/SchedulesExpiryDuringReconnect.java deleted file mode 100644 index 2e06c54e00b5..000000000000 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/reconnect/SchedulesExpiryDuringReconnect.java +++ /dev/null @@ -1,183 +0,0 @@ -/* - * Copyright (C) 2021-2024 Hedera Hashgraph, LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.hedera.services.bdd.suites.reconnect; - -import static com.hedera.services.bdd.spec.HapiSpec.customHapiSpec; -import static com.hedera.services.bdd.spec.HapiSpec.defaultHapiSpec; -import static com.hedera.services.bdd.spec.infrastructure.OpProvider.STANDARD_PERMISSIBLE_PRECHECKS; -import static com.hedera.services.bdd.spec.queries.QueryVerbs.getAccountBalance; -import static com.hedera.services.bdd.spec.queries.QueryVerbs.getScheduleInfo; -import static com.hedera.services.bdd.spec.transactions.TxnVerbs.cryptoCreate; -import static com.hedera.services.bdd.spec.transactions.TxnVerbs.cryptoTransfer; -import static com.hedera.services.bdd.spec.transactions.TxnVerbs.fileUpdate; -import static com.hedera.services.bdd.spec.transactions.TxnVerbs.scheduleCreate; -import static com.hedera.services.bdd.spec.transactions.crypto.HapiCryptoTransfer.tinyBarsFromTo; -import static com.hedera.services.bdd.spec.utilops.UtilVerbs.overriding; -import static com.hedera.services.bdd.spec.utilops.UtilVerbs.sleepFor; -import static com.hedera.services.bdd.spec.utilops.UtilVerbs.withLiveNode; -import static com.hedera.services.bdd.suites.perf.PerfUtilOps.scheduleOpsEnablement; -import static com.hedera.services.bdd.suites.reconnect.AutoRenewEntitiesForReconnect.runTransfersBeforeReconnect; -import static com.hedera.services.bdd.suites.reconnect.ValidateTokensStateAfterReconnect.nonReconnectingNode; -import static com.hedera.services.bdd.suites.reconnect.ValidateTokensStateAfterReconnect.reconnectingNode; -import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.IDENTICAL_SCHEDULE_ALREADY_CREATED; -import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.INVALID_SCHEDULE_ID; -import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.OK; - -import com.hedera.services.bdd.spec.HapiSpec; -import com.hedera.services.bdd.suites.HapiSuite; -import java.time.Duration; -import java.util.List; -import java.util.Map; -import java.util.concurrent.TimeUnit; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; - -/** - * A reconnect test in which a few schedule transactions are created while the node 0.0.8 is - * disconnected from the network. Once the node is reconnected the state of the schedules are - * verified on reconnected node - */ -public class SchedulesExpiryDuringReconnect extends HapiSuite { - private static final String SCHEDULE_EXPIRY_TIME_SECS = "10"; - - private static final Logger log = LogManager.getLogger(SchedulesExpiryDuringReconnect.class); - - public static void main(String... args) { - new ValidateAppPropertiesStateAfterReconnect().runSuiteSync(); - } - - @Override - public List getSpecsInSuite() { - return List.of(runTransfersBeforeReconnect(), suiteSetup(), expireSchedulesDuringReconnect()); - } - - final HapiSpec expireSchedulesDuringReconnect() { - String soonToBeExpiredSchedule = "schedule-1"; - String longLastingSchedule = "schedule-2"; - String oneOtherSchedule = "schedule-3"; - String duplicateSchedule = "schedule-4"; - return customHapiSpec("SchedulesExpiryDuringReconnect") - .withProperties(Map.of("txn.start.offset.secs", "-5")) - .given( - scheduleOpsEnablement(), - sleepFor(Duration.ofSeconds(25).toMillis()), - fileUpdate(APP_PROPERTIES) - .payingWith(GENESIS) - .overridingProps(Map.of("ledger.schedule.txExpiryTimeSecs", "10")), - scheduleCreate( - soonToBeExpiredSchedule, - cryptoTransfer(tinyBarsFromTo(GENESIS, FUNDING, 1)) - .fee(ONE_HBAR)) - .signedBy(DEFAULT_PAYER) - .fee(ONE_HUNDRED_HBARS) - .hasPrecheckFrom(STANDARD_PERMISSIBLE_PRECHECKS) - .hasAnyKnownStatus() - .deferStatusResolution() - .adminKey(DEFAULT_PAYER) - .logging() - .advertisingCreation() - .savingExpectedScheduledTxnId()) - .when( - fileUpdate(APP_PROPERTIES) - .payingWith(GENESIS) - .overridingProps(Map.of("ledger.schedule.txExpiryTimeSecs", "1800")), - scheduleCreate( - longLastingSchedule, - cryptoTransfer(tinyBarsFromTo(GENESIS, FUNDING, 2)) - .fee(ONE_HBAR)) - .fee(ONE_HUNDRED_HBARS) - .hasPrecheckFrom(STANDARD_PERMISSIBLE_PRECHECKS) - .hasAnyKnownStatus() - .deferStatusResolution() - .adminKey(DEFAULT_PAYER) - .logging() - .advertisingCreation() - .savingExpectedScheduledTxnId(), - scheduleCreate( - oneOtherSchedule, - cryptoTransfer(tinyBarsFromTo(GENESIS, FUNDING, 3)) - .fee(ONE_HBAR)) - .signedBy(DEFAULT_PAYER) - .fee(ONE_HUNDRED_HBARS) - .hasPrecheckFrom(STANDARD_PERMISSIBLE_PRECHECKS) - .hasAnyKnownStatus() - .deferStatusResolution() - .adminKey(DEFAULT_PAYER) - .logging() - .advertisingCreation() - .savingExpectedScheduledTxnId(), - getAccountBalance(GENESIS).setNode(reconnectingNode).unavailableNode(), - cryptoCreate("civilian1").setNode(nonReconnectingNode), - cryptoCreate("civilian2").setNode(nonReconnectingNode), - cryptoCreate("civilian3").setNode(nonReconnectingNode)) - .then( - cryptoTransfer(tinyBarsFromTo(GENESIS, FUNDING, 3)) - .fee(ONE_HBAR) - .setNode(nonReconnectingNode), - cryptoTransfer(tinyBarsFromTo(GENESIS, FUNDING, 3)) - .fee(ONE_HBAR) - .setNode(nonReconnectingNode), - cryptoTransfer(tinyBarsFromTo(GENESIS, FUNDING, 3)) - .fee(ONE_HBAR) - .setNode(nonReconnectingNode), - withLiveNode(reconnectingNode) - .within(5 * 60, TimeUnit.SECONDS) - .loggingAvailabilityEvery(30) - .sleepingBetweenRetriesFor(10), - fileUpdate(APP_PROPERTIES) - .payingWith(GENESIS) - .overridingProps(Map.of("ledger.schedule.txExpiryTimeSecs", "1000")), - scheduleCreate( - duplicateSchedule, - cryptoTransfer(tinyBarsFromTo(GENESIS, FUNDING, 2)) - .fee(ONE_HBAR)) - .fee(ONE_HUNDRED_HBARS) - .hasPrecheckFrom(STANDARD_PERMISSIBLE_PRECHECKS) - .hasKnownStatus(IDENTICAL_SCHEDULE_ALREADY_CREATED) - .deferStatusResolution() - .adminKey(DEFAULT_PAYER) - .logging() - .advertisingCreation(), - sleepFor(Duration.ofSeconds(60).toMillis()), - getScheduleInfo(longLastingSchedule) - .setNode(reconnectingNode) - .logging() - .hasScheduledTxnIdSavedBy(longLastingSchedule) - .hasCostAnswerPrecheck(OK), - getScheduleInfo(oneOtherSchedule) - .setNode(reconnectingNode) - .hasScheduledTxnIdSavedBy(oneOtherSchedule) - .hasCostAnswerPrecheck(OK), - getScheduleInfo(soonToBeExpiredSchedule) - .setNode(reconnectingNode) - .hasScheduledTxnIdSavedBy(soonToBeExpiredSchedule) - .logging() - .hasCostAnswerPrecheck(INVALID_SCHEDULE_ID)); - } - - final HapiSpec suiteSetup() { - return defaultHapiSpec("suiteSetup") - .given() - .when() - .then(overriding("ledger.schedule.txExpiryTimeSecs", "" + SCHEDULE_EXPIRY_TIME_SECS)); - } - - @Override - protected Logger getResultsLogger() { - return log; - } -} diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/reconnect/SubmitMessagesForReconnect.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/reconnect/SubmitMessagesForReconnect.java deleted file mode 100644 index cd89670be424..000000000000 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/reconnect/SubmitMessagesForReconnect.java +++ /dev/null @@ -1,77 +0,0 @@ -/* - * Copyright (C) 2020-2024 Hedera Hashgraph, LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.hedera.services.bdd.suites.reconnect; - -import static com.hedera.services.bdd.spec.HapiSpec.defaultHapiSpec; -import static com.hedera.services.bdd.spec.transactions.TxnUtils.randomUtf8Bytes; -import static com.hedera.services.bdd.spec.transactions.TxnVerbs.submitMessageTo; -import static com.hedera.services.bdd.spec.utilops.LoadTest.defaultLoadTest; -import static com.hedera.services.bdd.spec.utilops.UtilVerbs.logIt; -import static com.hedera.services.bdd.spec.utilops.UtilVerbs.withOpContext; -import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.BUSY; -import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.PLATFORM_TRANSACTION_NOT_CREATED; - -import com.hedera.services.bdd.spec.HapiSpec; -import com.hedera.services.bdd.spec.HapiSpecOperation; -import com.hedera.services.bdd.suites.HapiSuite; -import com.hedera.services.bdd.suites.perf.PerfTestLoadSettings; -import java.util.List; -import java.util.function.Supplier; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; - -public class SubmitMessagesForReconnect extends HapiSuite { - private static final Logger log = LogManager.getLogger(SubmitMessagesForReconnect.class); - - public static void main(String... args) { - new SubmitMessagesForReconnect().runSuiteSync(); - } - - @Override - public List getSpecsInSuite() { - return List.of(runSubmitMessages()); - } - - private static HapiSpecOperation submitToTestTopic(PerfTestLoadSettings settings) { - String topicToSubmit = String.format("0.0.%d", settings.getTestTopicId()); - return submitMessageTo(topicToSubmit) - .message(randomUtf8Bytes(100)) - .noLogging() - .fee(ONE_HBAR) - .hasRetryPrecheckFrom(BUSY, PLATFORM_TRANSACTION_NOT_CREATED) - .deferStatusResolution(); - } - - final HapiSpec runSubmitMessages() { - PerfTestLoadSettings settings = new PerfTestLoadSettings(); - - Supplier submitBurst = () -> new HapiSpecOperation[] {submitToTestTopic(settings)}; - - return defaultHapiSpec("RunSubmitMessages") - .given( - withOpContext( - (spec, ignore) -> settings.setFrom(spec.setup().ciPropertiesMap())), - logIt(ignore -> settings.toString())) - .when() - .then(defaultLoadTest(submitBurst, settings)); - } - - @Override - protected Logger getResultsLogger() { - return log; - } -} diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/reconnect/UpdateAllProtectedFilesDuringReconnect.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/reconnect/UpdateAllProtectedFilesDuringReconnect.java deleted file mode 100644 index f276aaaaaa8e..000000000000 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/reconnect/UpdateAllProtectedFilesDuringReconnect.java +++ /dev/null @@ -1,154 +0,0 @@ -/* - * Copyright (C) 2020-2024 Hedera Hashgraph, LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.hedera.services.bdd.suites.reconnect; - -import static com.hedera.services.bdd.spec.HapiSpec.customHapiSpec; -import static com.hedera.services.bdd.spec.queries.QueryVerbs.getAccountBalance; -import static com.hedera.services.bdd.spec.queries.QueryVerbs.getAccountInfo; -import static com.hedera.services.bdd.spec.queries.QueryVerbs.getFileContents; -import static com.hedera.services.bdd.spec.transactions.TxnVerbs.contractCreate; -import static com.hedera.services.bdd.spec.transactions.TxnVerbs.cryptoCreate; -import static com.hedera.services.bdd.spec.transactions.TxnVerbs.fileCreate; -import static com.hedera.services.bdd.spec.transactions.TxnVerbs.fileUpdate; -import static com.hedera.services.bdd.spec.utilops.UtilVerbs.makeFree; -import static com.hedera.services.bdd.spec.utilops.UtilVerbs.restoreFileFromRegistry; -import static com.hedera.services.bdd.spec.utilops.UtilVerbs.saveFileToRegistry; -import static com.hedera.services.bdd.spec.utilops.UtilVerbs.sleepFor; -import static com.hedera.services.bdd.spec.utilops.UtilVerbs.withLiveNode; -import static com.hedera.services.bdd.suites.reconnect.AutoRenewEntitiesForReconnect.runTransfersBeforeReconnect; -import static com.hederahashgraph.api.proto.java.HederaFunctionality.CryptoGetInfo; -import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.AUTORENEW_DURATION_NOT_IN_RANGE; -import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.INSUFFICIENT_TX_FEE; -import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.NOT_SUPPORTED; -import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.OK; - -import com.google.protobuf.ByteString; -import com.hedera.services.bdd.spec.HapiSpec; -import com.hedera.services.bdd.spec.HapiSpecSetup; -import com.hedera.services.bdd.spec.utilops.UtilVerbs; -import com.hedera.services.bdd.suites.HapiSuite; -import java.time.Duration; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.concurrent.TimeUnit; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; - -public class UpdateAllProtectedFilesDuringReconnect extends HapiSuite { - - private static final Logger log = LogManager.getLogger(UpdateAllProtectedFilesDuringReconnect.class); - - public static void main(String... args) { - new ValidateAppPropertiesStateAfterReconnect().runSuiteSync(); - } - - private static final String APP_FILE_REGISTRY = "AppPropertiesInRegistry"; - private static final String API_FILE_REGISTRY = "ApiPropertiesInRegistry"; - private static final String RATES_FILE_REGISTRY = "ExchangeRatesInRegistry"; - private static final String FEES_FILE_REGISTRY = "FeeSchedulesInRegistry"; - - @Override - public List getSpecsInSuite() { - return List.of(runTransfersBeforeReconnect(), updateAllProtectedFilesDuringReconnect()); - } - - final HapiSpec updateAllProtectedFilesDuringReconnect() { - final String fileInfoRegistry = "apiPermissionsReconnect"; - final String nonUpdatableFile = "nonUpdatableFile"; - - return customHapiSpec("UpdateAllProtectedFilesDuringReconnect") - .withProperties(Map.of("txn.start.offset.secs", "-5")) - .given( - saveFileToRegistry(APP_PROPERTIES, APP_FILE_REGISTRY), - saveFileToRegistry(API_PERMISSIONS, API_FILE_REGISTRY), - saveFileToRegistry(EXCHANGE_RATES, RATES_FILE_REGISTRY), - saveFileToRegistry(FEE_SCHEDULE, FEES_FILE_REGISTRY), - sleepFor(Duration.ofSeconds(50).toMillis()), - getAccountBalance(GENESIS).setNode("0.0.8").unavailableNode()) - .when( - fileUpdate(APP_PROPERTIES) - .payingWith(GENESIS) - .overridingProps(Map.of("ledger.autoRenewPeriod.minDuration", "20")) - .erasingProps(Set.of("minimumAutoRenewDuration")), - fileUpdate(API_PERMISSIONS).payingWith(GENESIS).overridingProps(Map.of("updateFile", "2-50")), - fileCreate(nonUpdatableFile).contents("Cannot update file because of the new api permissions"), - fileUpdate(EXCHANGE_RATES) - .contents(spec -> { - ByteString newRates = spec.ratesProvider() - .rateSetWith(100, 1) - .toByteString(); - spec.registry().saveBytes("newRates", newRates); - return newRates; - }) - .payingWith(GENESIS), - makeFree(CryptoGetInfo), - getAccountBalance(GENESIS).setNode("0.0.8").unavailableNode()) - .then( - withLiveNode("0.0.8") - .within(5 * 60, TimeUnit.SECONDS) - .loggingAvailabilityEvery(30) - .sleepingBetweenRetriesFor(10), - UtilVerbs.sleepFor(30 * 1000), - withLiveNode("0.0.8") - .within(5 * 60, TimeUnit.SECONDS) - .loggingAvailabilityEvery(30) - .sleepingBetweenRetriesFor(10), - getFileContents(API_PERMISSIONS) - .logged() - .setNode("0.0.3") - .payingWith(GENESIS) - .saveToRegistry(fileInfoRegistry), - getFileContents(API_PERMISSIONS) - .logged() - .setNode("0.0.8") - .payingWith(GENESIS) - .hasContents(fileInfoRegistry), - fileUpdate(nonUpdatableFile) - .setNode("0.0.8") - .fee(ONE_MILLION_HBARS) - .hasPrecheck(NOT_SUPPORTED), - fileCreate("contractFile") - .setNode("0.0.8") - .payingWith(GENESIS) - .path(HapiSpecSetup.getDefaultInstance().defaultContractPath()), - contractCreate("testContract") - .bytecode("contractFile") - .autoRenewSecs(15) - .setNode("0.0.8") - .hasPrecheck(AUTORENEW_DURATION_NOT_IN_RANGE), - cryptoCreate("civilian") - .setNode("0.0.8") - .fee(ONE_HUNDRED_HBARS) - .hasPrecheck(INSUFFICIENT_TX_FEE), - cryptoCreate("civilian").setNode("0.0.8"), - getAccountInfo("0.0.2") - .payingWith("civilian") - .nodePayment(0L) - .setNode("0.0.8") - .hasAnswerOnlyPrecheck(OK), - restoreFileFromRegistry(APP_PROPERTIES, APP_FILE_REGISTRY), - restoreFileFromRegistry(API_PERMISSIONS, API_FILE_REGISTRY), - restoreFileFromRegistry(EXCHANGE_RATES, RATES_FILE_REGISTRY), - restoreFileFromRegistry(FEE_SCHEDULE, FEES_FILE_REGISTRY)); - } - - @Override - protected Logger getResultsLogger() { - return log; - } -} diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/reconnect/UpdatePermissionsDuringReconnect.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/reconnect/UpdatePermissionsDuringReconnect.java deleted file mode 100644 index dc199eaacd85..000000000000 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/reconnect/UpdatePermissionsDuringReconnect.java +++ /dev/null @@ -1,87 +0,0 @@ -/* - * Copyright (C) 2020-2024 Hedera Hashgraph, LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.hedera.services.bdd.suites.reconnect; - -import static com.hedera.services.bdd.spec.HapiSpec.defaultHapiSpec; -import static com.hedera.services.bdd.spec.queries.QueryVerbs.getAccountBalance; -import static com.hedera.services.bdd.spec.queries.QueryVerbs.getFileContents; -import static com.hedera.services.bdd.spec.transactions.TxnVerbs.fileUpdate; -import static com.hedera.services.bdd.spec.utilops.UtilVerbs.sleepFor; -import static com.hedera.services.bdd.spec.utilops.UtilVerbs.withLiveNode; - -import com.hedera.services.bdd.spec.HapiSpec; -import com.hedera.services.bdd.spec.utilops.UtilVerbs; -import com.hedera.services.bdd.suites.HapiSuite; -import java.time.Duration; -import java.util.List; -import java.util.Map; -import java.util.concurrent.TimeUnit; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; - -public class UpdatePermissionsDuringReconnect extends HapiSuite { - private static final Logger log = LogManager.getLogger(UpdatePermissionsDuringReconnect.class); - public static final String NODE_ACCOUNT = "0.0.6"; - - public static void main(String... args) { - new UpdatePermissionsDuringReconnect().runSuiteSync(); - } - - @Override - public List getSpecsInSuite() { - return List.of(updateApiPermissionsDuringReconnect()); - } - - final HapiSpec updateApiPermissionsDuringReconnect() { - final String fileInfoRegistry = "apiPermissionsReconnect"; - return defaultHapiSpec("updateApiPermissionsDuringReconnect") - .given( - sleepFor(Duration.ofSeconds(25).toMillis()), - getAccountBalance(GENESIS).setNode(NODE_ACCOUNT).unavailableNode()) - .when( - fileUpdate(API_PERMISSIONS) - .overridingProps(Map.of("updateFile", "1-1011")) - .payingWith(SYSTEM_ADMIN) - .logged(), - getAccountBalance(GENESIS).setNode(NODE_ACCOUNT).unavailableNode()) - .then( - withLiveNode(NODE_ACCOUNT) - .within(5 * 60, TimeUnit.SECONDS) - .loggingAvailabilityEvery(30) - .sleepingBetweenRetriesFor(10), - UtilVerbs.sleepFor(30 * 1000L), - withLiveNode(NODE_ACCOUNT) - .within(5 * 60, TimeUnit.SECONDS) - .loggingAvailabilityEvery(30) - .sleepingBetweenRetriesFor(10), - getFileContents(API_PERMISSIONS) - .logged() - .setNode("0.0.3") - .payingWith(SYSTEM_ADMIN) - .saveToRegistry(fileInfoRegistry), - getFileContents(API_PERMISSIONS) - .logged() - .setNode(NODE_ACCOUNT) - .payingWith(SYSTEM_ADMIN) - .hasContents(fileInfoRegistry)); - } - - @Override - protected Logger getResultsLogger() { - return log; - } -} diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/reconnect/ValidateAppPropertiesStateAfterReconnect.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/reconnect/ValidateAppPropertiesStateAfterReconnect.java deleted file mode 100644 index 829e174315ca..000000000000 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/reconnect/ValidateAppPropertiesStateAfterReconnect.java +++ /dev/null @@ -1,84 +0,0 @@ -/* - * Copyright (C) 2020-2024 Hedera Hashgraph, LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.hedera.services.bdd.suites.reconnect; - -import static com.hedera.services.bdd.spec.HapiSpec.customHapiSpec; -import static com.hedera.services.bdd.spec.queries.QueryVerbs.getAccountBalance; -import static com.hedera.services.bdd.spec.transactions.TxnVerbs.contractCreate; -import static com.hedera.services.bdd.spec.transactions.TxnVerbs.fileCreate; -import static com.hedera.services.bdd.spec.transactions.TxnVerbs.fileUpdate; -import static com.hedera.services.bdd.spec.utilops.UtilVerbs.sleepFor; -import static com.hedera.services.bdd.spec.utilops.UtilVerbs.withLiveNode; -import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.AUTORENEW_DURATION_NOT_IN_RANGE; - -import com.hedera.services.bdd.spec.HapiSpec; -import com.hedera.services.bdd.spec.HapiSpecSetup; -import com.hedera.services.bdd.suites.HapiSuite; -import java.time.Duration; -import java.util.List; -import java.util.Map; -import java.util.concurrent.TimeUnit; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; - -public class ValidateAppPropertiesStateAfterReconnect extends HapiSuite { - private static final Logger log = LogManager.getLogger(ValidateAppPropertiesStateAfterReconnect.class); - - final String PATH_TO_VALID_BYTECODE = HapiSpecSetup.getDefaultInstance().defaultContractPath(); - - public static void main(String... args) { - new ValidateAppPropertiesStateAfterReconnect().runSuiteSync(); - } - - @Override - public List getSpecsInSuite() { - return List.of(validateAppPropertiesStateAfterReconnect()); - } - - final HapiSpec validateAppPropertiesStateAfterReconnect() { - return customHapiSpec("validateAppPropertiesStateAfterReconnect") - .withProperties(Map.of("txn.start.offset.secs", "-5")) - .given( - sleepFor(Duration.ofSeconds(25).toMillis()), - getAccountBalance(GENESIS).setNode("0.0.6").unavailableNode()) - .when( - getAccountBalance(GENESIS).setNode("0.0.6").unavailableNode(), - fileUpdate(APP_PROPERTIES) - .payingWith(ADDRESS_BOOK_CONTROL) - .overridingProps(Map.of("minimumAutoRenewDuration", "20")), - getAccountBalance(GENESIS).setNode("0.0.6").unavailableNode()) - .then( - withLiveNode("0.0.6") - .within(180, TimeUnit.SECONDS) - .loggingAvailabilityEvery(30) - .sleepingBetweenRetriesFor(10), - fileCreate("contractFile") - .setNode("0.0.6") - .payingWith(ADDRESS_BOOK_CONTROL) - .path(PATH_TO_VALID_BYTECODE), - contractCreate("testContract") - .bytecode("contractFile") - .autoRenewSecs(15) - .setNode("0.0.6") - .hasPrecheck(AUTORENEW_DURATION_NOT_IN_RANGE)); - } - - @Override - protected Logger getResultsLogger() { - return log; - } -} diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/reconnect/ValidateCongestionPricingAfterReconnect.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/reconnect/ValidateCongestionPricingAfterReconnect.java deleted file mode 100644 index 2d0e55f3c42b..000000000000 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/reconnect/ValidateCongestionPricingAfterReconnect.java +++ /dev/null @@ -1,194 +0,0 @@ -/* - * Copyright (C) 2020-2024 Hedera Hashgraph, LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.hedera.services.bdd.suites.reconnect; - -import static com.hedera.services.bdd.spec.HapiSpec.customHapiSpec; -import static com.hedera.services.bdd.spec.queries.QueryVerbs.getAccountBalance; -import static com.hedera.services.bdd.spec.queries.QueryVerbs.getTxnRecord; -import static com.hedera.services.bdd.spec.transactions.TxnVerbs.contractCall; -import static com.hedera.services.bdd.spec.transactions.TxnVerbs.contractCreate; -import static com.hedera.services.bdd.spec.transactions.TxnVerbs.cryptoCreate; -import static com.hedera.services.bdd.spec.transactions.TxnVerbs.cryptoTransfer; -import static com.hedera.services.bdd.spec.transactions.TxnVerbs.fileUpdate; -import static com.hedera.services.bdd.spec.transactions.TxnVerbs.uncheckedSubmit; -import static com.hedera.services.bdd.spec.transactions.TxnVerbs.uploadInitCode; -import static com.hedera.services.bdd.spec.utilops.UtilVerbs.blockingOrder; -import static com.hedera.services.bdd.spec.utilops.UtilVerbs.sleepFor; -import static com.hedera.services.bdd.spec.utilops.UtilVerbs.usableTxnIdNamed; -import static com.hedera.services.bdd.spec.utilops.UtilVerbs.withLiveNode; -import static com.hedera.services.bdd.spec.utilops.UtilVerbs.withOpContext; -import static com.hedera.services.bdd.suites.reconnect.AutoRenewEntitiesForReconnect.runTransfersBeforeReconnect; -import static com.hedera.services.bdd.suites.reconnect.ValidateTokensStateAfterReconnect.reconnectingNode; -import static com.hedera.services.bdd.suites.utils.sysfiles.serdes.ThrottleDefsLoader.protoDefsFromResource; - -import com.hedera.services.bdd.spec.HapiSpec; -import com.hedera.services.bdd.spec.HapiSpecOperation; -import com.hedera.services.bdd.spec.HapiSpecSetup; -import com.hedera.services.bdd.spec.transactions.crypto.HapiCryptoTransfer; -import com.hedera.services.bdd.suites.HapiSuite; -import java.util.Arrays; -import java.util.List; -import java.util.Map; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.atomic.AtomicLong; -import java.util.stream.IntStream; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; -import org.junit.jupiter.api.Assertions; - -/** - * A reconnect test in which a congestion pricing multiplier is updated and triggered while the node 0.0.8 is - * disconnected from the network. Once the node is reconnected validate that the congestion pricing is in affect on - * reconnected node - */ -public class ValidateCongestionPricingAfterReconnect extends HapiSuite { - private static final Logger log = LogManager.getLogger(ValidateCongestionPricingAfterReconnect.class); - private static final String FEES_PERCENT_CONGESTION_MULTIPLIERS = "fees.percentCongestionMultipliers"; - private static final String defaultCongestionMultipliers = - HapiSpecSetup.getDefaultNodeProps().get(FEES_PERCENT_CONGESTION_MULTIPLIERS); - private static final String FEES_MIN_CONGESTION_PERIOD = "fees.minCongestionPeriod"; - private static final String defaultMinCongestionPeriod = - HapiSpecSetup.getDefaultNodeProps().get(FEES_MIN_CONGESTION_PERIOD); - - public static void main(String... args) { - new ValidateAppPropertiesStateAfterReconnect().runSuiteSync(); - } - - @Override - public List getSpecsInSuite() { - return List.of(new HapiSpec[] { - runTransfersBeforeReconnect(), validateCongestionPricing(), - }); - } - - final HapiSpec validateCongestionPricing() { - var artificialLimits = protoDefsFromResource("testSystemFiles/artificial-limits-6N.json"); - var defaultThrottles = protoDefsFromResource("testSystemFiles/throttles-dev.json"); - String tmpMinCongestionPeriodInSecs = "5"; - String civilianAccount = "civilian"; - String oneContract = "Multipurpose"; - - AtomicLong normalPrice = new AtomicLong(); - AtomicLong tenXPrice = new AtomicLong(); - - return customHapiSpec("ValidateCongestionPricing") - .withProperties(Map.of("txn.start.offset.secs", "-5")) - .given( - cryptoCreate(civilianAccount).payingWith(GENESIS).balance(ONE_MILLION_HBARS), - uploadInitCode(oneContract), - contractCreate(oneContract).payingWith(GENESIS).logging(), - contractCall(oneContract) - .payingWith(civilianAccount) - .fee(ONE_HUNDRED_HBARS) - .sending(ONE_HBAR) - .via("cheapCallBeforeCongestionPricing"), - getTxnRecord("cheapCallBeforeCongestionPricing").providingFeeTo(normalPrice::set), - sleepFor(30000), - getAccountBalance(GENESIS).setNode(reconnectingNode).unavailableNode()) - .when( - /* update the multiplier to 10x with a 1% congestion for tmpMinCongestionPeriodInSecs */ - fileUpdate(APP_PROPERTIES) - .fee(ONE_HUNDRED_HBARS) - .payingWith(EXCHANGE_RATE_CONTROL) - .overridingProps(Map.of( - FEES_PERCENT_CONGESTION_MULTIPLIERS, - "1,10x", - FEES_MIN_CONGESTION_PERIOD, - tmpMinCongestionPeriodInSecs)), - fileUpdate(THROTTLE_DEFS) - .fee(ONE_HUNDRED_HBARS) - .payingWith(EXCHANGE_RATE_CONTROL) - .contents(artificialLimits.toByteArray()), - blockingOrder(IntStream.range(0, 110) - .mapToObj(i -> new HapiSpecOperation[] { - usableTxnIdNamed("uncheckedTxn1" + i).payerId(civilianAccount), - uncheckedSubmit(contractCall(oneContract) - .signedBy(civilianAccount) - .fee(ONE_HUNDRED_HBARS) - .sending(ONE_HBAR) - .txnId("uncheckedTxn1" + i)) - .payingWith(GENESIS), - sleepFor(50) - }) - .flatMap(Arrays::stream) - .toArray(HapiSpecOperation[]::new))) - .then( - withLiveNode(reconnectingNode) - .within(5 * 60, TimeUnit.SECONDS) - .loggingAvailabilityEvery(30) - .sleepingBetweenRetriesFor(10), - // So although currently disconnected node is in ACTIVE mode, it might - // immediately enter BEHIND mode and start a reconnection session. - // We need to wait for the reconnection session to finish and the platform - // becomes ACTIVE again, - // then we can send more transactions. Otherwise, transactions may be - // pending for too long - // and we will get UNKNOWN status - sleepFor(80000), - blockingOrder(IntStream.range(0, 110) - .mapToObj(i -> new HapiSpecOperation[] { - usableTxnIdNamed("uncheckedTxn2" + i).payerId(civilianAccount), - uncheckedSubmit(contractCall(oneContract) - .signedBy(civilianAccount) - .fee(ONE_HUNDRED_HBARS) - .sending(ONE_HBAR) - .txnId("uncheckedTxn2" + i)) - .payingWith(GENESIS) - .setNode(reconnectingNode), - sleepFor(50) - }) - .flatMap(Arrays::stream) - .toArray(HapiSpecOperation[]::new)), - contractCall(oneContract) - .payingWith(civilianAccount) - .fee(ONE_HUNDRED_HBARS) - .sending(ONE_HBAR) - .via("pricyCallAfterReconnect") - .setNode(reconnectingNode), - getTxnRecord("pricyCallAfterReconnect") - .payingWith(GENESIS) - .providingFeeTo(tenXPrice::set) - .setNode(reconnectingNode), - - /* check if the multiplier took effect in the contract call operation */ - withOpContext((spec, opLog) -> Assertions.assertEquals( - 10.0, - (1.0 * tenXPrice.get()) / normalPrice.get(), - 0.1, - "~10x multiplier should be in affect!")), - - /* revert the multiplier before test ends */ - fileUpdate(THROTTLE_DEFS) - .fee(ONE_HUNDRED_HBARS) - .payingWith(EXCHANGE_RATE_CONTROL) - .contents(defaultThrottles.toByteArray()) - .setNode(reconnectingNode), - fileUpdate(APP_PROPERTIES) - .fee(ONE_HUNDRED_HBARS) - .payingWith(EXCHANGE_RATE_CONTROL) - .overridingProps(Map.of( - FEES_PERCENT_CONGESTION_MULTIPLIERS, defaultCongestionMultipliers, - FEES_MIN_CONGESTION_PERIOD, defaultMinCongestionPeriod)), - cryptoTransfer(HapiCryptoTransfer.tinyBarsFromTo(GENESIS, FUNDING, 1)) - .payingWith(GENESIS)); - } - - @Override - protected Logger getResultsLogger() { - return log; - } -} diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/reconnect/ValidateDuplicateTransactionAfterReconnect.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/reconnect/ValidateDuplicateTransactionAfterReconnect.java deleted file mode 100644 index 590584672949..000000000000 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/reconnect/ValidateDuplicateTransactionAfterReconnect.java +++ /dev/null @@ -1,76 +0,0 @@ -/* - * Copyright (C) 2020-2024 Hedera Hashgraph, LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.hedera.services.bdd.suites.reconnect; - -import static com.hedera.services.bdd.spec.HapiSpec.customHapiSpec; -import static com.hedera.services.bdd.spec.queries.QueryVerbs.getAccountBalance; -import static com.hedera.services.bdd.spec.transactions.TxnVerbs.cryptoCreate; -import static com.hedera.services.bdd.spec.utilops.UtilVerbs.sleepFor; -import static com.hedera.services.bdd.spec.utilops.UtilVerbs.withLiveNode; -import static com.hedera.services.bdd.suites.reconnect.AutoRenewEntitiesForReconnect.runTransfersBeforeReconnect; -import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.DUPLICATE_TRANSACTION; - -import com.hedera.services.bdd.spec.HapiSpec; -import com.hedera.services.bdd.suites.HapiSuite; -import java.time.Duration; -import java.util.List; -import java.util.Map; -import java.util.concurrent.TimeUnit; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; - -public class ValidateDuplicateTransactionAfterReconnect extends HapiSuite { - private static final Logger log = LogManager.getLogger(ValidateDuplicateTransactionAfterReconnect.class); - - public static void main(String... args) { - new ValidateDuplicateTransactionAfterReconnect().runSuiteSync(); - } - - @Override - public List getSpecsInSuite() { - return List.of(runTransfersBeforeReconnect(), validateDuplicateTransactionAfterReconnect()); - } - - final HapiSpec validateDuplicateTransactionAfterReconnect() { - final String transactionId = "specialTransactionId"; - return customHapiSpec("validateDuplicateTransactionAfterReconnect") - .withProperties(Map.of("txn.start.offset.secs", "-5")) - .given( - sleepFor(Duration.ofSeconds(20).toMillis()), - getAccountBalance(GENESIS).setNode("0.0.8").unavailableNode()) - .when( - cryptoCreate("repeatedTransaction").via(transactionId), - getAccountBalance(GENESIS).setNode("0.0.8").unavailableNode()) - .then( - withLiveNode("0.0.8") - .within(150, TimeUnit.SECONDS) - .loggingAvailabilityEvery(10) - .sleepingBetweenRetriesFor(5), - // the target node is back online, but may enter reconnect session soon, - // add some delay to wait for it to finish - sleepFor(Duration.ofSeconds(60).toMillis()), - cryptoCreate("repeatedTransaction") - .txnId(transactionId) - .hasPrecheck(DUPLICATE_TRANSACTION) - .setNode("0.0.8")); - } - - @Override - protected Logger getResultsLogger() { - return log; - } -} diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/reconnect/ValidateExchangeRateStateAfterReconnect.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/reconnect/ValidateExchangeRateStateAfterReconnect.java deleted file mode 100644 index f2c95bb20ae0..000000000000 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/reconnect/ValidateExchangeRateStateAfterReconnect.java +++ /dev/null @@ -1,86 +0,0 @@ -/* - * Copyright (C) 2020-2024 Hedera Hashgraph, LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.hedera.services.bdd.suites.reconnect; - -import static com.hedera.services.bdd.spec.HapiSpec.customHapiSpec; -import static com.hedera.services.bdd.spec.assertions.TransactionRecordAsserts.recordWith; -import static com.hedera.services.bdd.spec.queries.QueryVerbs.getAccountBalance; -import static com.hedera.services.bdd.spec.queries.QueryVerbs.getTxnRecord; -import static com.hedera.services.bdd.spec.transactions.TxnVerbs.cryptoCreate; -import static com.hedera.services.bdd.spec.transactions.TxnVerbs.fileUpdate; -import static com.hedera.services.bdd.spec.utilops.UtilVerbs.sleepFor; -import static com.hedera.services.bdd.spec.utilops.UtilVerbs.withLiveNode; - -import com.google.protobuf.ByteString; -import com.hedera.services.bdd.spec.HapiSpec; -import com.hedera.services.bdd.suites.HapiSuite; -import java.time.Duration; -import java.util.List; -import java.util.Map; -import java.util.concurrent.TimeUnit; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; - -public class ValidateExchangeRateStateAfterReconnect extends HapiSuite { - private static final Logger log = LogManager.getLogger(ValidateExchangeRateStateAfterReconnect.class); - - public static void main(String... args) { - new ValidateExchangeRateStateAfterReconnect().runSuiteSync(); - } - - @Override - public List getSpecsInSuite() { - return List.of(validateExchangeRateStateAfterReconnect()); - } - - final HapiSpec validateExchangeRateStateAfterReconnect() { - final String transactionid = "authorizedTxn"; - final long oldFee = 13_299_075L; - final long newFee = 159_588_904; - return customHapiSpec("validateExchangeRateStateAfterReconnect") - .withProperties(Map.of("txn.start.offset.secs", "-5")) - .given( - sleepFor(Duration.ofSeconds(25).toMillis()), - getAccountBalance(GENESIS).setNode("0.0.6").unavailableNode()) - .when( - getAccountBalance(GENESIS).setNode("0.0.6").unavailableNode(), - fileUpdate(EXCHANGE_RATES) - .contents(spec -> { - ByteString newRates = spec.ratesProvider() - .rateSetWith(1, 1) - .toByteString(); - spec.registry().saveBytes("newRates", newRates); - return newRates; - }) - .payingWith(SYSTEM_ADMIN), - getAccountBalance(GENESIS).setNode("0.0.6").unavailableNode()) - .then( - withLiveNode("0.0.6") - .within(180, TimeUnit.SECONDS) - .loggingAvailabilityEvery(30) - .sleepingBetweenRetriesFor(10), - cryptoCreate("civilian").via(transactionid).setNode("0.0.6"), - getTxnRecord(transactionid) - .setNode("0.0.6") - .hasPriority(recordWith().fee(newFee))); - } - - @Override - protected Logger getResultsLogger() { - return log; - } -} diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/reconnect/ValidateFeeScheduleStateAfterReconnect.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/reconnect/ValidateFeeScheduleStateAfterReconnect.java deleted file mode 100644 index 276d866680d0..000000000000 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/reconnect/ValidateFeeScheduleStateAfterReconnect.java +++ /dev/null @@ -1,77 +0,0 @@ -/* - * Copyright (C) 2020-2024 Hedera Hashgraph, LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.hedera.services.bdd.suites.reconnect; - -import static com.hedera.services.bdd.spec.HapiSpec.customHapiSpec; -import static com.hedera.services.bdd.spec.queries.QueryVerbs.getAccountBalance; -import static com.hedera.services.bdd.spec.queries.QueryVerbs.getAccountInfo; -import static com.hedera.services.bdd.spec.transactions.TxnVerbs.cryptoCreate; -import static com.hedera.services.bdd.spec.utilops.UtilVerbs.makeFree; -import static com.hedera.services.bdd.spec.utilops.UtilVerbs.sleepFor; -import static com.hedera.services.bdd.spec.utilops.UtilVerbs.withLiveNode; -import static com.hederahashgraph.api.proto.java.HederaFunctionality.CryptoGetInfo; -import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.OK; - -import com.hedera.services.bdd.spec.HapiSpec; -import com.hedera.services.bdd.suites.HapiSuite; -import java.time.Duration; -import java.util.List; -import java.util.Map; -import java.util.concurrent.TimeUnit; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; - -public class ValidateFeeScheduleStateAfterReconnect extends HapiSuite { - private static final Logger log = LogManager.getLogger(ValidateFeeScheduleStateAfterReconnect.class); - - public static void main(String... args) { - new ValidateFeeScheduleStateAfterReconnect().runSuiteSync(); - } - - @Override - public List getSpecsInSuite() { - return List.of(validateFeeScheduleStateAfterReconnect()); - } - - final HapiSpec validateFeeScheduleStateAfterReconnect() { - return customHapiSpec("validateFeeScheduleStateAfterReconnect") - .withProperties(Map.of("txn.start.offset.secs", "-5")) - .given( - sleepFor(Duration.ofSeconds(25).toMillis()), - getAccountBalance(GENESIS).setNode("0.0.6").unavailableNode()) - .when( - getAccountBalance(GENESIS).setNode("0.0.6").unavailableNode(), - makeFree(CryptoGetInfo), - getAccountBalance(GENESIS).setNode("0.0.6").unavailableNode()) - .then( - withLiveNode("0.0.6") - .within(180, TimeUnit.SECONDS) - .loggingAvailabilityEvery(30) - .sleepingBetweenRetriesFor(10), - cryptoCreate("civilian").setNode("0.0.6"), - getAccountInfo("0.0.2") - .payingWith("civilian") - .nodePayment(0L) - .setNode("0.0.6") - .hasAnswerOnlyPrecheck(OK)); - } - - @Override - protected Logger getResultsLogger() { - return log; - } -} diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/reconnect/ValidatePermissionStateAfterReconnect.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/reconnect/ValidatePermissionStateAfterReconnect.java deleted file mode 100644 index 69a17173d4ef..000000000000 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/reconnect/ValidatePermissionStateAfterReconnect.java +++ /dev/null @@ -1,74 +0,0 @@ -/* - * Copyright (C) 2020-2024 Hedera Hashgraph, LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.hedera.services.bdd.suites.reconnect; - -import static com.hedera.services.bdd.spec.HapiSpec.customHapiSpec; -import static com.hedera.services.bdd.spec.queries.QueryVerbs.getAccountBalance; -import static com.hedera.services.bdd.spec.transactions.TxnVerbs.fileCreate; -import static com.hedera.services.bdd.spec.transactions.TxnVerbs.fileUpdate; -import static com.hedera.services.bdd.spec.utilops.UtilVerbs.sleepFor; -import static com.hedera.services.bdd.spec.utilops.UtilVerbs.withLiveNode; -import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.NOT_SUPPORTED; - -import com.hedera.services.bdd.spec.HapiSpec; -import com.hedera.services.bdd.suites.HapiSuite; -import java.time.Duration; -import java.util.List; -import java.util.Map; -import java.util.concurrent.TimeUnit; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; - -public class ValidatePermissionStateAfterReconnect extends HapiSuite { - private static final Logger log = LogManager.getLogger(ValidatePermissionStateAfterReconnect.class); - public static final String NODE_ACCOUNT = "0.0.6"; - - public static void main(String... args) { - new ValidatePermissionStateAfterReconnect().runSuiteSync(); - } - - @Override - public List getSpecsInSuite() { - return List.of(validateApiPermissionStateAfterReconnect()); - } - - final HapiSpec validateApiPermissionStateAfterReconnect() { - return customHapiSpec("validateApiPermissionStateAfterReconnect") - .withProperties(Map.of("txn.start.offset.secs", "-5")) - .given( - sleepFor(Duration.ofSeconds(25).toMillis()), - getAccountBalance(GENESIS).setNode(NODE_ACCOUNT).unavailableNode()) - .when( - getAccountBalance(GENESIS).setNode(NODE_ACCOUNT).unavailableNode(), - fileCreate("effectivelyImmutable").contents("Can't touch me!"), - fileUpdate(API_PERMISSIONS) - .payingWith(ADDRESS_BOOK_CONTROL) - .overridingProps(Map.of("updateFile", "2-50")), - getAccountBalance(GENESIS).setNode(NODE_ACCOUNT).unavailableNode()) - .then( - withLiveNode(NODE_ACCOUNT) - .within(180, TimeUnit.SECONDS) - .loggingAvailabilityEvery(30) - .sleepingBetweenRetriesFor(10), - fileUpdate("effectivelyImmutable").setNode(NODE_ACCOUNT).hasPrecheck(NOT_SUPPORTED)); - } - - @Override - protected Logger getResultsLogger() { - return log; - } -} diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/reconnect/ValidateTokensDeleteAfterReconnect.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/reconnect/ValidateTokensDeleteAfterReconnect.java deleted file mode 100644 index e25ebf2de7fc..000000000000 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/reconnect/ValidateTokensDeleteAfterReconnect.java +++ /dev/null @@ -1,111 +0,0 @@ -/* - * Copyright (C) 2020-2024 Hedera Hashgraph, LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.hedera.services.bdd.suites.reconnect; - -import static com.hedera.services.bdd.spec.HapiSpec.customHapiSpec; -import static com.hedera.services.bdd.spec.queries.QueryVerbs.getAccountBalance; -import static com.hedera.services.bdd.spec.queries.QueryVerbs.getTokenInfo; -import static com.hedera.services.bdd.spec.transactions.TxnVerbs.cryptoCreate; -import static com.hedera.services.bdd.spec.transactions.TxnVerbs.tokenCreate; -import static com.hedera.services.bdd.spec.transactions.TxnVerbs.tokenDelete; -import static com.hedera.services.bdd.spec.transactions.TxnVerbs.tokenDissociate; -import static com.hedera.services.bdd.spec.utilops.UtilVerbs.blockingOrder; -import static com.hedera.services.bdd.spec.utilops.UtilVerbs.newKeyNamed; -import static com.hedera.services.bdd.spec.utilops.UtilVerbs.sleepFor; -import static com.hedera.services.bdd.spec.utilops.UtilVerbs.withLiveNode; -import static com.hedera.services.bdd.suites.reconnect.AutoRenewEntitiesForReconnect.runTransfersBeforeReconnect; - -import com.hedera.services.bdd.spec.HapiSpec; -import com.hedera.services.bdd.spec.HapiSpecOperation; -import com.hedera.services.bdd.suites.HapiSuite; -import java.time.Duration; -import java.util.List; -import java.util.Map; -import java.util.concurrent.TimeUnit; -import java.util.stream.IntStream; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; - -/** - * A reconnect test in which a few tokens are created while the node 0.0.8 is disconnected from the - * network. Once the node is reconnected the state of tokens is verified on reconnected node and - * other node - */ -public class ValidateTokensDeleteAfterReconnect extends HapiSuite { - private static final Logger log = LogManager.getLogger(ValidateTokensDeleteAfterReconnect.class); - public static final String reconnectingNode = "0.0.8"; - public static final String nonReconnectingNode = "0.0.3"; - private static final long TOKEN_INITIAL_SUPPLY = 500; - - public static void main(String... args) { - new ValidateAppPropertiesStateAfterReconnect().runSuiteSync(); - } - - @Override - public List getSpecsInSuite() { - return List.of(runTransfersBeforeReconnect(), validateTokensAfterReconnect()); - } - - final HapiSpec validateTokensAfterReconnect() { - String token = "token"; - String account = "account"; - String adminKey = "admin"; - - return customHapiSpec("ValidateTokensAfterReconnect") - .withProperties(Map.of("txn.start.offset.secs", "-5")) - .given( - sleepFor(Duration.ofSeconds(25).toMillis()), - cryptoCreate(TOKEN_TREASURY).balance(ONE_MILLION_HBARS).logging(), - cryptoCreate(account).balance(ONE_HUNDRED_HBARS).logging(), - newKeyNamed(adminKey), - tokenCreate(token) - .initialSupply(TOKEN_INITIAL_SUPPLY) - .treasury(TOKEN_TREASURY) - .adminKey(adminKey) - .logging()) - .when( - sleepFor(30000), - getAccountBalance(GENESIS).setNode(reconnectingNode).unavailableNode(), - tokenDelete(token).logging(), - blockingOrder(IntStream.range(0, 500) - .mapToObj(i -> getTokenInfo(token)) - .toArray(HapiSpecOperation[]::new))) - .then( - withLiveNode(reconnectingNode) - .within(5 * 60, TimeUnit.SECONDS) - .loggingAvailabilityEvery(30) - .sleepingBetweenRetriesFor(10), - // So although currently disconnected node is in ACTIVE mode, it might - // immediately enter BEHIND mode and start a reconnection session. - // We need to wait for the reconnection session to finish and the platform - // becomes ACTIVE again, - // then we can send more transactions. Otherwise, transactions may be - // pending for too long - // and we will get UNKNOWN status - sleepFor(30000), - /* - Check that the reconnected node knows it's ok to dissociate the - treasury from a deleted token. -> https://github.com/hashgraph/hedera-services/issues/1678 - */ - tokenDissociate(TOKEN_TREASURY, token).setNode(reconnectingNode)); - } - - @Override - protected Logger getResultsLogger() { - return log; - } -} diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/reconnect/ValidateTokensStateAfterReconnect.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/reconnect/ValidateTokensStateAfterReconnect.java deleted file mode 100644 index fc23ea9754c9..000000000000 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/reconnect/ValidateTokensStateAfterReconnect.java +++ /dev/null @@ -1,200 +0,0 @@ -/* - * Copyright (C) 2021-2024 Hedera Hashgraph, LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.hedera.services.bdd.suites.reconnect; - -import static com.hedera.services.bdd.spec.HapiSpec.customHapiSpec; -import static com.hedera.services.bdd.spec.queries.QueryVerbs.getAccountBalance; -import static com.hedera.services.bdd.spec.queries.QueryVerbs.getTokenInfo; -import static com.hedera.services.bdd.spec.transactions.TxnVerbs.burnToken; -import static com.hedera.services.bdd.spec.transactions.TxnVerbs.cryptoCreate; -import static com.hedera.services.bdd.spec.transactions.TxnVerbs.cryptoDelete; -import static com.hedera.services.bdd.spec.transactions.TxnVerbs.cryptoTransfer; -import static com.hedera.services.bdd.spec.transactions.TxnVerbs.mintToken; -import static com.hedera.services.bdd.spec.transactions.TxnVerbs.tokenAssociate; -import static com.hedera.services.bdd.spec.transactions.TxnVerbs.tokenCreate; -import static com.hedera.services.bdd.spec.transactions.TxnVerbs.tokenDelete; -import static com.hedera.services.bdd.spec.transactions.TxnVerbs.tokenDissociate; -import static com.hedera.services.bdd.spec.transactions.TxnVerbs.tokenUpdate; -import static com.hedera.services.bdd.spec.transactions.token.TokenMovement.moving; -import static com.hedera.services.bdd.spec.utilops.UtilVerbs.blockingOrder; -import static com.hedera.services.bdd.spec.utilops.UtilVerbs.newKeyNamed; -import static com.hedera.services.bdd.spec.utilops.UtilVerbs.sleepFor; -import static com.hedera.services.bdd.spec.utilops.UtilVerbs.withLiveNode; -import static com.hedera.services.bdd.suites.perf.PerfUtilOps.tokenOpsEnablement; -import static com.hedera.services.bdd.suites.reconnect.AutoRenewEntitiesForReconnect.runTransfersBeforeReconnect; -import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.ACCOUNT_IS_TREASURY; -import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.TRANSACTION_REQUIRES_ZERO_TOKEN_BALANCES; - -import com.hedera.services.bdd.spec.HapiSpec; -import com.hedera.services.bdd.spec.HapiSpecOperation; -import com.hedera.services.bdd.suites.HapiSuite; -import java.time.Duration; -import java.util.List; -import java.util.Map; -import java.util.concurrent.TimeUnit; -import java.util.stream.IntStream; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; - -/** - * A reconnect test in which a few tokens are created while the node 0.0.8 is disconnected from the - * network. Once the node is reconnected the state of tokens is verified on reconnected node and - * other node - */ -public class ValidateTokensStateAfterReconnect extends HapiSuite { - private static final Logger log = LogManager.getLogger(ValidateTokensStateAfterReconnect.class); - public static final String reconnectingNode = "0.0.8"; - public static final String nonReconnectingNode = "0.0.3"; - private static final long TOKEN_INITIAL_SUPPLY = 500; - - public static void main(String... args) { - new ValidateAppPropertiesStateAfterReconnect().runSuiteSync(); - } - - @Override - public List getSpecsInSuite() { - return List.of(runTransfersBeforeReconnect(), validateTokensAfterReconnect()); - } - - final HapiSpec validateTokensAfterReconnect() { - String tokenToBeQueried = "token-1"; - String anotherToken = "token-2"; - String anotherAccount = "account"; - String supplyKey = "supplyKey"; - String freezeKey = "freezeKey"; - String adminKey = "adminKey"; - String newAdminKey = "newAdminKey"; - - return customHapiSpec("ValidateTokensAfterReconnect") - .withProperties(Map.of("txn.start.offset.secs", "-5")) - .given( - sleepFor(Duration.ofSeconds(25).toMillis()), - tokenOpsEnablement(), - newKeyNamed(supplyKey), - newKeyNamed(freezeKey), - newKeyNamed(adminKey), - newKeyNamed(newAdminKey), - cryptoCreate(TOKEN_TREASURY).balance(ONE_MILLION_HBARS).logging(), - cryptoCreate(anotherAccount).balance(ONE_HUNDRED_HBARS).logging()) - .when( - sleepFor(Duration.ofSeconds(26).toMillis()), - getAccountBalance(GENESIS).setNode(reconnectingNode).unavailableNode(), - tokenCreate(tokenToBeQueried) - .freezeKey(freezeKey) - .supplyKey(supplyKey) - .initialSupply(TOKEN_INITIAL_SUPPLY) - .treasury(TOKEN_TREASURY) - .adminKey(adminKey) - .logging(), - tokenCreate(anotherToken) - .freezeKey(freezeKey) - .supplyKey(supplyKey) - .initialSupply(TOKEN_INITIAL_SUPPLY) - .adminKey(adminKey) - .treasury(TOKEN_TREASURY) - .logging(), - - /* Some token operations*/ - getTokenInfo(tokenToBeQueried), - getTokenInfo(anotherToken), - tokenUpdate(tokenToBeQueried) - .fee(ONE_HUNDRED_HBARS) - .payingWith(TOKEN_TREASURY) - .adminKey(newAdminKey), - tokenAssociate(anotherAccount, tokenToBeQueried, anotherToken) - .logging(), - blockingOrder(IntStream.range(0, 10) - .mapToObj(i -> cryptoTransfer( - moving(1, tokenToBeQueried).between(TOKEN_TREASURY, anotherAccount))) - .toArray(HapiSpecOperation[]::new)), - blockingOrder(IntStream.range(0, 5) - .mapToObj(i -> mintToken(tokenToBeQueried, 100)) - .toArray(HapiSpecOperation[]::new)), - blockingOrder(IntStream.range(0, 5) - .mapToObj(i -> mintToken(anotherToken, 100)) - .toArray(HapiSpecOperation[]::new)), - burnToken(anotherToken, 1), - burnToken(tokenToBeQueried, 1), - cryptoDelete(TOKEN_TREASURY).hasKnownStatus(ACCOUNT_IS_TREASURY), - getTokenInfo(tokenToBeQueried), - getTokenInfo(anotherToken), - /* end token operations */ - - getAccountBalance(GENESIS).setNode(reconnectingNode).unavailableNode()) - .then( - withLiveNode(reconnectingNode) - .within(5 * 60, TimeUnit.SECONDS) - .loggingAvailabilityEvery(30) - .sleepingBetweenRetriesFor(10), - - // wait reconnect node to finish receiving new state, otherwise it may - // response - // with incorrect answer from old state - sleepFor(3000), - /* validate tokenInfo between reconnecting node and other nodes match*/ - getTokenInfo(tokenToBeQueried) - .setNode(reconnectingNode) - .hasAdminKey(tokenToBeQueried) - .hasFreezeKey(tokenToBeQueried) - .hasSupplyKey(tokenToBeQueried) - .hasTotalSupply(999) - .logging(), - getTokenInfo(tokenToBeQueried) - .setNode(nonReconnectingNode) - .hasAdminKey(tokenToBeQueried) - .hasFreezeKey(tokenToBeQueried) - .hasSupplyKey(tokenToBeQueried) - .hasTotalSupply(999) - .logging(), - getTokenInfo(anotherToken) - .setNode(reconnectingNode) - .hasFreezeKey(anotherToken) - .hasAdminKey(anotherToken) - .hasSupplyKey(anotherToken) - .hasTotalSupply(999) - .logging(), - getTokenInfo(anotherToken) - .setNode(nonReconnectingNode) - .hasFreezeKey(anotherToken) - .hasAdminKey(anotherToken) - .hasSupplyKey(anotherToken) - .hasTotalSupply(999) - .logging(), - cryptoDelete(TOKEN_TREASURY) - .hasKnownStatus(ACCOUNT_IS_TREASURY) - .setNode(reconnectingNode), - - /* Should be able to delete treasury only after dissociating the tokens */ - tokenDelete(tokenToBeQueried).setNode(reconnectingNode), - tokenDissociate(TOKEN_TREASURY, tokenToBeQueried).setNode(reconnectingNode), - cryptoDelete(TOKEN_TREASURY) - .hasKnownStatus(ACCOUNT_IS_TREASURY) - .setNode(reconnectingNode), - tokenDelete(anotherToken).setNode(reconnectingNode), - /* Should dissociate with any tokens[even deleted ones] to be able to delete the treasury */ - cryptoDelete(TOKEN_TREASURY) - .hasKnownStatus(TRANSACTION_REQUIRES_ZERO_TOKEN_BALANCES) - .setNode(reconnectingNode), - tokenDissociate(TOKEN_TREASURY, anotherToken), - cryptoDelete(TOKEN_TREASURY)); - } - - @Override - protected Logger getResultsLogger() { - return log; - } -} diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/records/BalanceValidation.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/records/BalanceValidation.java index 1a6d1c17e561..b8876dcc1b66 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/records/BalanceValidation.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/records/BalanceValidation.java @@ -22,15 +22,16 @@ import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.CONTRACT_DELETED; import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.OK; -import com.hedera.services.bdd.junit.utils.AccountClassifier; -import com.hedera.services.bdd.spec.HapiSpec; +import com.hedera.services.bdd.junit.support.validators.utils.AccountClassifier; import com.hedera.services.bdd.spec.HapiSpecOperation; import com.hedera.services.bdd.spec.queries.QueryVerbs; import com.hedera.services.bdd.suites.HapiSuite; import java.util.List; import java.util.Map; +import java.util.stream.Stream; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; +import org.junit.jupiter.api.DynamicTest; public class BalanceValidation extends HapiSuite { private static final Logger log = LogManager.getLogger(BalanceValidation.class); @@ -55,11 +56,11 @@ public boolean canRunConcurrent() { } @Override - public List getSpecsInSuite() { + public List> getSpecsInSuite() { return List.of(validateBalances()); } - final HapiSpec validateBalances() { + final Stream validateBalances() { return customHapiSpec("ValidateBalances") .withProperties(Map.of( "fees.useFixedOffer", "true", diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/records/ClosingTime.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/records/ClosingTime.java deleted file mode 100644 index cddab7a328c2..000000000000 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/records/ClosingTime.java +++ /dev/null @@ -1,65 +0,0 @@ -/* - * Copyright (C) 2020-2024 Hedera Hashgraph, LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.hedera.services.bdd.suites.records; - -import static com.hedera.services.bdd.spec.HapiSpec.customHapiSpec; -import static com.hedera.services.bdd.spec.transactions.TxnVerbs.cryptoTransfer; -import static com.hedera.services.bdd.spec.utilops.UtilVerbs.sleepFor; - -import com.hedera.services.bdd.junit.HapiTest; -import com.hedera.services.bdd.junit.HapiTestSuite; -import com.hedera.services.bdd.spec.HapiSpec; -import com.hedera.services.bdd.suites.HapiSuite; -import java.util.List; -import java.util.Map; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; - -@HapiTestSuite -public class ClosingTime extends HapiSuite { - private static final Logger log = LogManager.getLogger(ClosingTime.class); - - public static void main(String... args) { - new ClosingTime().runSuiteSync(); - } - - @Override - public boolean canRunConcurrent() { - return true; - } - - @Override - public List getSpecsInSuite() { - return List.of(closeLastStreamFileWithNoBalanceImpact()); - } - - @HapiTest - final HapiSpec closeLastStreamFileWithNoBalanceImpact() { - return customHapiSpec("CloseLastStreamFileWithNoBalanceImpact") - .withProperties(Map.of( - "fees.useFixedOffer", "true", - "fees.fixedOffer", "100000000")) - .given() - .when() - .then(sleepFor(2500), cryptoTransfer((spec, b) -> {}).payingWith(GENESIS), sleepFor(500)); - } - - @Override - protected Logger getResultsLogger() { - return log; - } -} diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/records/ContractRecordsSanityCheckSuite.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/records/ContractRecordsSanityCheckSuite.java index 260f650d0b72..99f93de4c11f 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/records/ContractRecordsSanityCheckSuite.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/records/ContractRecordsSanityCheckSuite.java @@ -16,6 +16,8 @@ package com.hedera.services.bdd.suites.records; +import static com.hedera.services.bdd.junit.ContextRequirement.SYSTEM_ACCOUNT_BALANCES; +import static com.hedera.services.bdd.junit.TestTags.SMART_CONTRACT; import static com.hedera.services.bdd.spec.HapiSpec.defaultHapiSpec; import static com.hedera.services.bdd.spec.transactions.TxnVerbs.contractCall; import static com.hedera.services.bdd.spec.transactions.TxnVerbs.contractCallWithFunctionAbi; @@ -30,48 +32,36 @@ import static com.hedera.services.bdd.spec.utilops.UtilVerbs.takeBalanceSnapshots; import static com.hedera.services.bdd.spec.utilops.UtilVerbs.validateRecordTransactionFees; import static com.hedera.services.bdd.spec.utilops.UtilVerbs.validateTransferListForBalances; +import static com.hedera.services.bdd.suites.HapiSuite.DEFAULT_PAYER; +import static com.hedera.services.bdd.suites.HapiSuite.FUNDING; +import static com.hedera.services.bdd.suites.HapiSuite.NODE; +import static com.hedera.services.bdd.suites.HapiSuite.NODE_REWARD; +import static com.hedera.services.bdd.suites.HapiSuite.STAKING_REWARD; +import static com.hedera.services.bdd.suites.HapiSuite.flattened; import static java.util.function.Function.identity; import com.esaulpaugh.headlong.abi.Tuple; -import com.hedera.services.bdd.junit.HapiTest; -import com.hedera.services.bdd.junit.HapiTestSuite; -import com.hedera.services.bdd.spec.HapiSpec; +import com.hedera.services.bdd.junit.LeakyHapiTest; import com.hedera.services.bdd.spec.HapiSpecOperation; import com.hedera.services.bdd.spec.keys.KeyFactory; import com.hedera.services.bdd.spec.utilops.UtilVerbs; -import com.hedera.services.bdd.suites.HapiSuite; import java.math.BigInteger; import java.util.List; import java.util.Set; import java.util.function.ToLongFunction; import java.util.stream.IntStream; import java.util.stream.Stream; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; +import org.junit.jupiter.api.DynamicTest; +import org.junit.jupiter.api.Tag; -@HapiTestSuite -public class ContractRecordsSanityCheckSuite extends HapiSuite { - private static final Logger log = LogManager.getLogger(ContractRecordsSanityCheckSuite.class); +@Tag(SMART_CONTRACT) +public class ContractRecordsSanityCheckSuite { private static final String BALANCE_LOOKUP = "BalanceLookup"; public static final String PAYABLE_CONTRACT = "PayReceivable"; public static final String ALTRUISTIC_TXN = "altruisticTxn"; - public static void main(String... args) { - new ContractRecordsSanityCheckSuite().runSuiteSync(); - } - - @Override - public List getSpecsInSuite() { - return List.of( - contractCallWithSendRecordSanityChecks(), - circularTransfersRecordSanityChecks(), - contractCreateRecordSanityChecks(), - contractUpdateRecordSanityChecks(), - contractDeleteRecordSanityChecks()); - } - - @HapiTest - final HapiSpec contractDeleteRecordSanityChecks() { + @LeakyHapiTest(SYSTEM_ACCOUNT_BALANCES) + final Stream contractDeleteRecordSanityChecks() { return defaultHapiSpec("ContractDeleteRecordSanityChecks") .given(flattened( uploadInitCode(BALANCE_LOOKUP), @@ -87,8 +77,8 @@ final HapiSpec contractDeleteRecordSanityChecks() { validateRecordTransactionFees("txn")); } - @HapiTest - final HapiSpec contractCreateRecordSanityChecks() { + @LeakyHapiTest(SYSTEM_ACCOUNT_BALANCES) + final Stream contractCreateRecordSanityChecks() { return defaultHapiSpec("ContractCreateRecordSanityChecks") .given(flattened( uploadInitCode(BALANCE_LOOKUP), @@ -101,8 +91,8 @@ final HapiSpec contractCreateRecordSanityChecks() { validateRecordTransactionFees("txn")); } - @HapiTest - final HapiSpec contractCallWithSendRecordSanityChecks() { + @LeakyHapiTest(SYSTEM_ACCOUNT_BALANCES) + final Stream contractCallWithSendRecordSanityChecks() { return defaultHapiSpec("ContractCallWithSendRecordSanityChecks") .given(flattened( uploadInitCode(PAYABLE_CONTRACT), @@ -119,8 +109,8 @@ final HapiSpec contractCallWithSendRecordSanityChecks() { validateRecordTransactionFees("txn")); } - @HapiTest - final HapiSpec circularTransfersRecordSanityChecks() { + @LeakyHapiTest(SYSTEM_ACCOUNT_BALANCES) + final Stream circularTransfersRecordSanityChecks() { final var contractName = "CircularTransfers"; int numAltruists = 3; ToLongFunction initBalanceFn = ignore -> 1_000_000L; @@ -194,8 +184,8 @@ final HapiSpec circularTransfersRecordSanityChecks() { })); } - @HapiTest - final HapiSpec contractUpdateRecordSanityChecks() { + @LeakyHapiTest(SYSTEM_ACCOUNT_BALANCES) + final Stream contractUpdateRecordSanityChecks() { return defaultHapiSpec("ContractUpdateRecordSanityChecks") .given(flattened( newKeyNamed("newKey").type(KeyFactory.KeyType.SIMPLE), @@ -209,11 +199,6 @@ final HapiSpec contractUpdateRecordSanityChecks() { validateRecordTransactionFees("txn")); } - @Override - protected Logger getResultsLogger() { - return log; - } - private static final String SET_NODES_ABI = "{ \"constant\": false, \"inputs\": [ { \"internalType\": \"uint64[]\", \"name\":" + " \"accounts\", \"type\": \"uint64[]\" } ], \"name\": \"setNodes\"," diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/records/CryptoRecordsSanityCheckSuite.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/records/CryptoRecordsSanityCheckSuite.java index dc7dd03403cb..cf0c3abb58c4 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/records/CryptoRecordsSanityCheckSuite.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/records/CryptoRecordsSanityCheckSuite.java @@ -16,6 +16,8 @@ package com.hedera.services.bdd.suites.records; +import static com.hedera.services.bdd.junit.ContextRequirement.SYSTEM_ACCOUNT_BALANCES; +import static com.hedera.services.bdd.junit.TestTags.CRYPTO; import static com.hedera.services.bdd.spec.HapiSpec.defaultHapiSpec; import static com.hedera.services.bdd.spec.queries.QueryVerbs.getAccountBalance; import static com.hedera.services.bdd.spec.queries.QueryVerbs.getTxnRecord; @@ -34,48 +36,35 @@ import static com.hedera.services.bdd.spec.utilops.UtilVerbs.takeBalanceSnapshots; import static com.hedera.services.bdd.spec.utilops.UtilVerbs.validateRecordTransactionFees; import static com.hedera.services.bdd.spec.utilops.UtilVerbs.validateTransferListForBalances; +import static com.hedera.services.bdd.suites.HapiSuite.DEFAULT_PAYER; +import static com.hedera.services.bdd.suites.HapiSuite.FUNDING; +import static com.hedera.services.bdd.suites.HapiSuite.NODE; +import static com.hedera.services.bdd.suites.HapiSuite.NODE_REWARD; +import static com.hedera.services.bdd.suites.HapiSuite.ONE_HUNDRED_HBARS; +import static com.hedera.services.bdd.suites.HapiSuite.STAKING_REWARD; +import static com.hedera.services.bdd.suites.HapiSuite.flattened; import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.INSUFFICIENT_ACCOUNT_BALANCE; import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.INVALID_PAYER_SIGNATURE; import static com.hederahashgraph.api.proto.java.TokenType.NON_FUNGIBLE_UNIQUE; import com.google.protobuf.ByteString; -import com.hedera.services.bdd.junit.HapiTest; -import com.hedera.services.bdd.junit.HapiTestSuite; -import com.hedera.services.bdd.spec.HapiSpec; +import com.hedera.services.bdd.junit.LeakyHapiTest; import com.hedera.services.bdd.spec.keys.KeyFactory; -import com.hedera.services.bdd.suites.HapiSuite; import java.util.List; import java.util.Set; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; +import java.util.stream.Stream; +import org.junit.jupiter.api.DynamicTest; +import org.junit.jupiter.api.Tag; -@HapiTestSuite -public class CryptoRecordsSanityCheckSuite extends HapiSuite { - private static final Logger log = LogManager.getLogger(CryptoRecordsSanityCheckSuite.class); +@Tag(CRYPTO) +public class CryptoRecordsSanityCheckSuite { private static final String PAYER = "payer"; private static final String RECEIVER = "receiver"; private static final String NEW_KEY = "newKey"; private static final String ORIG_KEY = "origKey"; - public static void main(String... args) { - new CryptoRecordsSanityCheckSuite().runSuiteSync(); - } - - @Override - public List getSpecsInSuite() { - return List.of(new HapiSpec[] { - cryptoCreateRecordSanityChecks(), - cryptoDeleteRecordSanityChecks(), - cryptoTransferRecordSanityChecks(), - cryptoUpdateRecordSanityChecks(), - insufficientAccountBalanceRecordSanityChecks(), - invalidPayerSigCryptoTransferRecordSanityChecks(), - ownershipChangeShowsInRecord(), - }); - } - - @HapiTest - final HapiSpec ownershipChangeShowsInRecord() { + @LeakyHapiTest(SYSTEM_ACCOUNT_BALANCES) + final Stream ownershipChangeShowsInRecord() { final var firstOwner = "A"; final var secondOwner = "B"; final var uniqueToken = "DoubleVision"; @@ -107,8 +96,8 @@ final HapiSpec ownershipChangeShowsInRecord() { getTxnRecord(xferRecord).logged()); } - @HapiTest - final HapiSpec cryptoCreateRecordSanityChecks() { + @LeakyHapiTest(SYSTEM_ACCOUNT_BALANCES) + final Stream cryptoCreateRecordSanityChecks() { return defaultHapiSpec("CryptoCreateRecordSanityChecks") .given(takeBalanceSnapshots(FUNDING, NODE, STAKING_REWARD, NODE_REWARD, DEFAULT_PAYER)) .when(cryptoCreate("test").via("txn")) @@ -118,8 +107,8 @@ final HapiSpec cryptoCreateRecordSanityChecks() { validateRecordTransactionFees("txn")); } - @HapiTest - final HapiSpec cryptoDeleteRecordSanityChecks() { + @LeakyHapiTest(SYSTEM_ACCOUNT_BALANCES) + final Stream cryptoDeleteRecordSanityChecks() { return defaultHapiSpec("CryptoDeleteRecordSanityChecks") .given(flattened( cryptoCreate("test"), @@ -133,8 +122,8 @@ final HapiSpec cryptoDeleteRecordSanityChecks() { validateRecordTransactionFees("txn")); } - @HapiTest - final HapiSpec cryptoTransferRecordSanityChecks() { + @LeakyHapiTest(SYSTEM_ACCOUNT_BALANCES) + final Stream cryptoTransferRecordSanityChecks() { return defaultHapiSpec("CryptoTransferRecordSanityChecks") .given(flattened( cryptoCreate("a").balance(100_000L), @@ -146,8 +135,8 @@ final HapiSpec cryptoTransferRecordSanityChecks() { validateRecordTransactionFees("txn")); } - @HapiTest - final HapiSpec cryptoUpdateRecordSanityChecks() { + @LeakyHapiTest(SYSTEM_ACCOUNT_BALANCES) + final Stream cryptoUpdateRecordSanityChecks() { return defaultHapiSpec("CryptoUpdateRecordSanityChecks") .given(flattened( cryptoCreate("test"), @@ -160,8 +149,8 @@ final HapiSpec cryptoUpdateRecordSanityChecks() { validateRecordTransactionFees("txn")); } - @HapiTest - final HapiSpec insufficientAccountBalanceRecordSanityChecks() { + @LeakyHapiTest(SYSTEM_ACCOUNT_BALANCES) + final Stream insufficientAccountBalanceRecordSanityChecks() { final long BALANCE = 500_000_000L; return defaultHapiSpec("InsufficientAccountBalanceRecordSanityChecks") .given(flattened( @@ -184,8 +173,8 @@ final HapiSpec insufficientAccountBalanceRecordSanityChecks() { List.of("txn1", "txn2"), List.of(FUNDING, NODE, STAKING_REWARD, NODE_REWARD, PAYER, RECEIVER))); } - @HapiTest - final HapiSpec invalidPayerSigCryptoTransferRecordSanityChecks() { + @LeakyHapiTest(SYSTEM_ACCOUNT_BALANCES) + final Stream invalidPayerSigCryptoTransferRecordSanityChecks() { final long BALANCE = 10_000_000L; return defaultHapiSpec("InvalidPayerSigCryptoTransferSanityChecks") @@ -206,9 +195,4 @@ final HapiSpec invalidPayerSigCryptoTransferRecordSanityChecks() { .signedBy(ORIG_KEY, RECEIVER) .hasKnownStatus(INVALID_PAYER_SIGNATURE)); } - - @Override - protected Logger getResultsLogger() { - return log; - } } diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/records/DuplicateManagementTest.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/records/DuplicateManagementTest.java index fabdd9be21e5..f2c7044f62ad 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/records/DuplicateManagementTest.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/records/DuplicateManagementTest.java @@ -32,6 +32,9 @@ import static com.hedera.services.bdd.spec.utilops.UtilVerbs.sleepFor; import static com.hedera.services.bdd.spec.utilops.UtilVerbs.usableTxnIdNamed; import static com.hedera.services.bdd.spec.utilops.UtilVerbs.withOpContext; +import static com.hedera.services.bdd.suites.HapiSuite.GENESIS; +import static com.hedera.services.bdd.suites.HapiSuite.ONE_HBAR; +import static com.hedera.services.bdd.suites.HapiSuite.ONE_HUNDRED_HBARS; import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.BUSY; import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.DUPLICATE_TRANSACTION; import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.INVALID_NODE_ACCOUNT; @@ -41,37 +44,19 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import com.hedera.services.bdd.junit.HapiTest; -import com.hedera.services.bdd.junit.HapiTestSuite; -import com.hedera.services.bdd.spec.HapiSpec; -import com.hedera.services.bdd.suites.HapiSuite; -import java.util.List; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; +import java.util.stream.Stream; +import org.junit.jupiter.api.DynamicTest; -@HapiTestSuite -public class DuplicateManagementTest extends HapiSuite { - private static final Logger log = LogManager.getLogger(DuplicateManagementTest.class); +public class DuplicateManagementTest { private static final String REPEATED = "repeated"; private static final String TXN_ID = "txnId"; private static final String TO = "0.0.3"; private static final String CIVILIAN = "civilian"; private static final long MS_TO_WAIT_FOR_CONSENSUS = 6_000L; - public static void main(String... args) { - new DuplicateManagementTest().runSuiteSync(); - } - - @Override - public List getSpecsInSuite() { - return List.of( - usesUnclassifiableIfNoClassifiableAvailable(), - hasExpectedDuplicates(), - classifiableTakesPriorityOverUnclassifiable()); - } - @HapiTest @SuppressWarnings("java:S5960") - final HapiSpec hasExpectedDuplicates() { + final Stream hasExpectedDuplicates() { return defaultHapiSpec("HasExpectedDuplicates") .given( cryptoCreate(CIVILIAN).balance(ONE_HUNDRED_HBARS), @@ -131,7 +116,7 @@ final HapiSpec hasExpectedDuplicates() { } @HapiTest - final HapiSpec usesUnclassifiableIfNoClassifiableAvailable() { + final Stream usesUnclassifiableIfNoClassifiableAvailable() { return defaultHapiSpec("UsesUnclassifiableIfNoClassifiableAvailable") .given( newKeyNamed("wrongKey"), @@ -154,7 +139,7 @@ final HapiSpec usesUnclassifiableIfNoClassifiableAvailable() { } @HapiTest - final HapiSpec classifiableTakesPriorityOverUnclassifiable() { + final Stream classifiableTakesPriorityOverUnclassifiable() { return defaultHapiSpec("ClassifiableTakesPriorityOverUnclassifiable") .given( cryptoCreate(CIVILIAN).balance(100 * 100_000_000L), @@ -183,9 +168,4 @@ final HapiSpec classifiableTakesPriorityOverUnclassifiable() { .hasPriority(recordWith().status(SUCCESS)) .hasDuplicates(inOrder(recordWith().status(INVALID_NODE_ACCOUNT)))); } - - @Override - protected Logger getResultsLogger() { - return log; - } } diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/records/FileRecordsSanityCheckSuite.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/records/FileRecordsSanityCheckSuite.java index ebcd0af32069..839aea47dc2a 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/records/FileRecordsSanityCheckSuite.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/records/FileRecordsSanityCheckSuite.java @@ -25,35 +25,22 @@ import static com.hedera.services.bdd.spec.utilops.UtilVerbs.takeBalanceSnapshots; import static com.hedera.services.bdd.spec.utilops.UtilVerbs.validateRecordTransactionFees; import static com.hedera.services.bdd.spec.utilops.UtilVerbs.validateTransferListForBalances; +import static com.hedera.services.bdd.suites.HapiSuite.DEFAULT_PAYER; +import static com.hedera.services.bdd.suites.HapiSuite.EXCHANGE_RATE_CONTROL; +import static com.hedera.services.bdd.suites.HapiSuite.FUNDING; +import static com.hedera.services.bdd.suites.HapiSuite.NODE; +import static com.hedera.services.bdd.suites.HapiSuite.NODE_REWARD; +import static com.hedera.services.bdd.suites.HapiSuite.STAKING_REWARD; +import static com.hedera.services.bdd.suites.HapiSuite.flattened; import com.hedera.services.bdd.junit.HapiTest; -import com.hedera.services.bdd.junit.HapiTestSuite; -import com.hedera.services.bdd.spec.HapiSpec; -import com.hedera.services.bdd.suites.HapiSuite; import java.util.List; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; - -@HapiTestSuite -public class FileRecordsSanityCheckSuite extends HapiSuite { - private static final Logger log = LogManager.getLogger(FileRecordsSanityCheckSuite.class); - - public static void main(String... args) { - new FileRecordsSanityCheckSuite().runSuiteSync(); - } - - @Override - public List getSpecsInSuite() { - return List.of(new HapiSpec[] { - fileCreateRecordSanityChecks(), - fileDeleteRecordSanityChecks(), - fileAppendRecordSanityChecks(), - fileUpdateRecordSanityChecks() - }); - } +import java.util.stream.Stream; +import org.junit.jupiter.api.DynamicTest; +public class FileRecordsSanityCheckSuite { @HapiTest - final HapiSpec fileAppendRecordSanityChecks() { + final Stream fileAppendRecordSanityChecks() { return defaultHapiSpec("FileAppendRecordSanityChecks") .given(flattened( fileCreate("test"), @@ -66,7 +53,7 @@ final HapiSpec fileAppendRecordSanityChecks() { } @HapiTest - final HapiSpec fileCreateRecordSanityChecks() { + final Stream fileCreateRecordSanityChecks() { return defaultHapiSpec("FileCreateRecordSanityChecks") .given(takeBalanceSnapshots(FUNDING, NODE, STAKING_REWARD, NODE_REWARD, DEFAULT_PAYER)) .when(fileCreate("test").via("txn")) @@ -77,7 +64,7 @@ final HapiSpec fileCreateRecordSanityChecks() { } @HapiTest - final HapiSpec fileDeleteRecordSanityChecks() { + final Stream fileDeleteRecordSanityChecks() { return defaultHapiSpec("FileDeleteRecordSanityChecks") .given(flattened( fileCreate("test"), @@ -90,7 +77,7 @@ final HapiSpec fileDeleteRecordSanityChecks() { } @HapiTest - final HapiSpec fileUpdateRecordSanityChecks() { + final Stream fileUpdateRecordSanityChecks() { return defaultHapiSpec("FileUpdateRecordSanityChecks") .given(flattened( fileCreate("test"), @@ -105,9 +92,4 @@ final HapiSpec fileUpdateRecordSanityChecks() { "txn", List.of(FUNDING, NODE, STAKING_REWARD, NODE_REWARD, DEFAULT_PAYER)), validateRecordTransactionFees("txn")); } - - @Override - protected Logger getResultsLogger() { - return log; - } } diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/records/RecordCreationSuite.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/records/RecordCreationSuite.java index fe5a9592d4ef..5a183b895f5b 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/records/RecordCreationSuite.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/records/RecordCreationSuite.java @@ -16,6 +16,7 @@ package com.hedera.services.bdd.suites.records; +import static com.hedera.services.bdd.junit.ContextRequirement.PROPERTY_OVERRIDES; import static com.hedera.services.bdd.spec.HapiSpec.defaultHapiSpec; import static com.hedera.services.bdd.spec.HapiSpec.propertyPreservingHapiSpec; import static com.hedera.services.bdd.spec.assertions.AccountInfoAsserts.changeFromSnapshot; @@ -25,41 +26,29 @@ import static com.hedera.services.bdd.spec.queries.QueryVerbs.getAccountBalance; import static com.hedera.services.bdd.spec.queries.QueryVerbs.getAccountRecords; import static com.hedera.services.bdd.spec.queries.QueryVerbs.getTxnRecord; -import static com.hedera.services.bdd.spec.transactions.TxnVerbs.createTopic; import static com.hedera.services.bdd.spec.transactions.TxnVerbs.cryptoCreate; import static com.hedera.services.bdd.spec.transactions.TxnVerbs.cryptoTransfer; -import static com.hedera.services.bdd.spec.transactions.TxnVerbs.submitMessageTo; import static com.hedera.services.bdd.spec.transactions.TxnVerbs.uncheckedSubmit; import static com.hedera.services.bdd.spec.transactions.crypto.HapiCryptoTransfer.tinyBarsFromTo; -import static com.hedera.services.bdd.spec.utilops.CustomSpecAssert.allRunFor; -import static com.hedera.services.bdd.spec.utilops.UtilVerbs.assertionsHold; import static com.hedera.services.bdd.spec.utilops.UtilVerbs.balanceSnapshot; import static com.hedera.services.bdd.spec.utilops.UtilVerbs.overridingTwo; import static com.hedera.services.bdd.spec.utilops.UtilVerbs.sleepFor; import static com.hedera.services.bdd.spec.utilops.UtilVerbs.sourcing; import static com.hedera.services.bdd.spec.utilops.UtilVerbs.usableTxnIdNamed; +import static com.hedera.services.bdd.suites.HapiSuite.FUNDING; +import static com.hedera.services.bdd.suites.HapiSuite.GENESIS; +import static com.hedera.services.bdd.suites.HapiSuite.ONE_HBAR; import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.INSUFFICIENT_TX_FEE; import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.INVALID_ZERO_BYTE_IN_STRING; -import static org.junit.jupiter.api.Assertions.assertEquals; import com.hedera.node.app.hapi.utils.fee.FeeObject; import com.hedera.services.bdd.junit.HapiTest; -import com.hedera.services.bdd.junit.HapiTestSuite; -import com.hedera.services.bdd.spec.HapiSpec; -import com.hedera.services.bdd.spec.transactions.TxnUtils; -import com.hedera.services.bdd.suites.HapiSuite; -import com.hederahashgraph.api.proto.java.AccountAmount; -import com.hederahashgraph.api.proto.java.AccountID; -import com.hederahashgraph.api.proto.java.TransactionRecord; -import java.util.List; +import com.hedera.services.bdd.junit.LeakyHapiTest; import java.util.concurrent.atomic.AtomicReference; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; - -@HapiTestSuite -public class RecordCreationSuite extends HapiSuite { - private static final Logger log = LogManager.getLogger(RecordCreationSuite.class); +import java.util.stream.Stream; +import org.junit.jupiter.api.DynamicTest; +public class RecordCreationSuite { private static final long SLEEP_MS = 1_000L; private static final String BEFORE = "before"; private static final String FUNDING_BEFORE = "fundingBefore"; @@ -75,22 +64,8 @@ public class RecordCreationSuite extends HapiSuite { public static final String STAKING_FEES_NODE_REWARD_PERCENTAGE = "staking.fees.nodeRewardPercentage"; public static final String STAKING_FEES_STAKING_REWARD_PERCENTAGE = "staking.fees.stakingRewardPercentage"; - public static void main(String... args) { - new RecordCreationSuite().runSuiteSync(); - } - - @Override - public List getSpecsInSuite() { - return List.of( - payerRecordCreationSanityChecks(), - accountsGetPayerRecordsIfSoConfigured(), - submittingNodeChargedNetworkFeeForLackOfDueDiligence(), - submittingNodeChargedNetworkFeeForIgnoringPayerUnwillingness(), - submittingNodeStillPaidIfServiceFeesOmitted()); - } - - @HapiTest - final HapiSpec submittingNodeStillPaidIfServiceFeesOmitted() { + @LeakyHapiTest(PROPERTY_OVERRIDES) + final Stream submittingNodeStillPaidIfServiceFeesOmitted() { final String comfortingMemo = THIS_IS_OK_IT_S_FINE_IT_S_WHATEVER; final AtomicReference feeObs = new AtomicReference<>(); @@ -147,8 +122,8 @@ final HapiSpec submittingNodeStillPaidIfServiceFeesOmitted() { .logged())); } - @HapiTest - final HapiSpec submittingNodeChargedNetworkFeeForLackOfDueDiligence() { + @LeakyHapiTest(PROPERTY_OVERRIDES) + final Stream submittingNodeChargedNetworkFeeForLackOfDueDiligence() { final String comfortingMemo = THIS_IS_OK_IT_S_FINE_IT_S_WHATEVER; final String disquietingMemo = "\u0000his is ok, it's fine, it's whatever."; final AtomicReference feeObs = new AtomicReference<>(); @@ -203,8 +178,8 @@ final HapiSpec submittingNodeChargedNetworkFeeForLackOfDueDiligence() { .logged())); } - @HapiTest - final HapiSpec submittingNodeChargedNetworkFeeForIgnoringPayerUnwillingness() { + @LeakyHapiTest(PROPERTY_OVERRIDES) + final Stream submittingNodeChargedNetworkFeeForIgnoringPayerUnwillingness() { final String comfortingMemo = THIS_IS_OK_IT_S_FINE_IT_S_WHATEVER; final AtomicReference feeObs = new AtomicReference<>(); @@ -260,36 +235,7 @@ final HapiSpec submittingNodeChargedNetworkFeeForIgnoringPayerUnwillingness() { } @HapiTest - final HapiSpec payerRecordCreationSanityChecks() { - return defaultHapiSpec("PayerRecordCreationSanityChecks") - .given(cryptoCreate(PAYER)) - .when( - createTopic("ofGeneralInterest").payingWith(PAYER), - cryptoTransfer(tinyBarsFromTo(GENESIS, FUNDING, 1_000L)).payingWith(PAYER), - submitMessageTo("ofGeneralInterest").message("I say!").payingWith(PAYER)) - .then(assertionsHold((spec, opLog) -> { - final var payerId = spec.registry().getAccountID(PAYER); - final var subOp = getAccountRecords(PAYER).logged(); - allRunFor(spec, subOp); - final var records = subOp.getResponse().getCryptoGetAccountRecords().getRecordsList().stream() - .filter(TxnUtils::isNotEndOfStakingPeriodRecord) - .toList(); - assertEquals(3, records.size()); - for (var record : records) { - assertEquals(record.getTransactionFee(), -netChangeIn(record, payerId)); - } - })); - } - - private long netChangeIn(TransactionRecord record, AccountID id) { - return record.getTransferList().getAccountAmountsList().stream() - .filter(aa -> id.equals(aa.getAccountID())) - .mapToLong(AccountAmount::getAmount) - .sum(); - } - - @HapiTest - final HapiSpec accountsGetPayerRecordsIfSoConfigured() { + final Stream accountsGetPayerRecordsIfSoConfigured() { final var txn = "ofRecord"; return defaultHapiSpec("AccountsGetPayerRecordsIfSoConfigured") @@ -299,9 +245,4 @@ final HapiSpec accountsGetPayerRecordsIfSoConfigured() { .via(txn)) .then(getAccountRecords(PAYER).has(inOrder(recordWith().txnId(txn)))); } - - @Override - protected Logger getResultsLogger() { - return log; - } } diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/records/SignedTransactionBytesRecordsSuite.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/records/SignedTransactionBytesRecordsSuite.java deleted file mode 100644 index 8ef952975843..000000000000 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/records/SignedTransactionBytesRecordsSuite.java +++ /dev/null @@ -1,115 +0,0 @@ -/* - * Copyright (C) 2020-2024 Hedera Hashgraph, LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.hedera.services.bdd.suites.records; - -import static com.hedera.services.bdd.spec.HapiSpec.defaultHapiSpec; -import static com.hedera.services.bdd.spec.queries.QueryVerbs.getTxnRecord; -import static com.hedera.services.bdd.spec.transactions.TxnVerbs.contractCreate; -import static com.hedera.services.bdd.spec.transactions.TxnVerbs.createTopic; -import static com.hedera.services.bdd.spec.transactions.TxnVerbs.cryptoCreate; -import static com.hedera.services.bdd.spec.transactions.TxnVerbs.cryptoTransfer; -import static com.hedera.services.bdd.spec.transactions.TxnVerbs.fileUpdate; -import static com.hedera.services.bdd.spec.transactions.TxnVerbs.uploadInitCode; -import static com.hedera.services.bdd.spec.transactions.crypto.HapiCryptoTransfer.tinyBarsFromTo; -import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.INVALID_ACCOUNT_ID; -import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.INVALID_TRANSACTION; -import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.INVALID_TRANSACTION_BODY; -import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.RECORD_NOT_FOUND; - -import com.hedera.services.bdd.junit.HapiTest; -import com.hedera.services.bdd.junit.HapiTestSuite; -import com.hedera.services.bdd.spec.HapiSpec; -import com.hedera.services.bdd.suites.HapiSuite; -import java.util.List; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; - -@HapiTestSuite -public class SignedTransactionBytesRecordsSuite extends HapiSuite { - private static final Logger log = LogManager.getLogger(SignedTransactionBytesRecordsSuite.class); - private static final String FAILED_CRYPTO_TRANSACTION = "failedCryptoTransaction"; - - public static void main(final String... args) { - new SignedTransactionBytesRecordsSuite().runSuiteSync(); - } - - @Override - public List getSpecsInSuite() { - return List.of(new HapiSpec[] { - transactionsWithOnlySigMap(), - transactionsWithSignedTxnBytesAndSigMap(), - transactionsWithSignedTxnBytesAndBodyBytes() - }); - } - - @Override - public boolean canRunConcurrent() { - return true; - } - - @HapiTest - final HapiSpec transactionsWithOnlySigMap() { - final var contract = "BalanceLookup"; - return defaultHapiSpec("TransactionsWithOnlySigMap") - .given( - cryptoTransfer(tinyBarsFromTo(GENESIS, SYSTEM_ADMIN, 1L)) - .via(FAILED_CRYPTO_TRANSACTION) - .asTxnWithOnlySigMap() - .hasPrecheck(INVALID_TRANSACTION_BODY), - uploadInitCode(contract), - fileUpdate(contract) - .via("failedFileTransaction") - .asTxnWithOnlySigMap() - .hasPrecheck(INVALID_TRANSACTION_BODY)) - .when(contractCreate(contract) - .balance(1_000L) - .via("failedContractTransaction") - .asTxnWithOnlySigMap() - .hasPrecheck(INVALID_TRANSACTION_BODY)) - .then( - getTxnRecord(FAILED_CRYPTO_TRANSACTION).hasCostAnswerPrecheck(INVALID_ACCOUNT_ID), - getTxnRecord("failedFileTransaction").hasCostAnswerPrecheck(INVALID_ACCOUNT_ID), - getTxnRecord("failedContractTransaction").hasCostAnswerPrecheck(INVALID_ACCOUNT_ID)); - } - - @HapiTest - final HapiSpec transactionsWithSignedTxnBytesAndSigMap() { - return defaultHapiSpec("TransactionsWithSignedTxnBytesAndSigMap") - .given() - .when(createTopic("testTopic") - .via("failedConsensusTransaction") - .asTxnWithSignedTxnBytesAndSigMap() - .hasPrecheck(INVALID_TRANSACTION)) - .then(getTxnRecord("failedConsensusTransaction").hasAnswerOnlyPrecheck(RECORD_NOT_FOUND)); - } - - @HapiTest - final HapiSpec transactionsWithSignedTxnBytesAndBodyBytes() { - return defaultHapiSpec("TransactionsWithSignedTxnBytesAndBodyBytes") - .given() - .when(cryptoCreate("testAccount") - .via(FAILED_CRYPTO_TRANSACTION) - .asTxnWithSignedTxnBytesAndBodyBytes() - .hasPrecheck(INVALID_TRANSACTION)) - .then(getTxnRecord(FAILED_CRYPTO_TRANSACTION).hasAnswerOnlyPrecheck(RECORD_NOT_FOUND)); - } - - @Override - protected Logger getResultsLogger() { - return log; - } -} diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/records/TokenBalanceValidation.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/records/TokenBalanceValidation.java index 340993710439..ab408af3a11f 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/records/TokenBalanceValidation.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/records/TokenBalanceValidation.java @@ -29,9 +29,8 @@ import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.INVALID_CONTRACT_ID; import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.OK; -import com.hedera.services.bdd.junit.utils.AccountClassifier; -import com.hedera.services.bdd.junit.validators.AccountNumTokenNum; -import com.hedera.services.bdd.spec.HapiSpec; +import com.hedera.services.bdd.junit.support.validators.AccountNumTokenNum; +import com.hedera.services.bdd.junit.support.validators.utils.AccountClassifier; import com.hedera.services.bdd.spec.HapiSpecOperation; import com.hedera.services.bdd.spec.queries.QueryVerbs; import com.hedera.services.bdd.suites.HapiSuite; @@ -39,8 +38,10 @@ import java.util.Arrays; import java.util.List; import java.util.Map; +import java.util.stream.Stream; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; +import org.junit.jupiter.api.DynamicTest; /** * Tests to validate that token balances are correct after token transfers occur. @@ -116,7 +117,7 @@ public boolean canRunConcurrent() { } @Override - public List getSpecsInSuite() { + public List> getSpecsInSuite() { return List.of(validateTokenBalances()); } @@ -164,7 +165,7 @@ private HapiSpecOperation[] getHapiSpecsForTransferTxs() { * Create HAPI queries to check whether token balances match what's given in expectedTokenBalances * @return HAPI queries to execute */ - final HapiSpec validateTokenBalances() { + final Stream validateTokenBalances() { return defaultHapiSpec("ValidateTokenBalances") .given(getHapiSpecsForTransferTxs()) // set up transfers if needed .when() diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/records/TransactionBodyValidation.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/records/TransactionBodyValidation.java index f6dd0f7029d7..f1185ee3f63e 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/records/TransactionBodyValidation.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/records/TransactionBodyValidation.java @@ -20,13 +20,14 @@ import static com.hedera.services.bdd.spec.utilops.UtilVerbs.assertEventuallyPasses; import static com.hedera.services.bdd.spec.utilops.UtilVerbs.sourcing; -import com.hedera.services.bdd.junit.TransactionBodyValidator; -import com.hedera.services.bdd.spec.HapiSpec; +import com.hedera.services.bdd.junit.support.validators.TransactionBodyValidator; import com.hedera.services.bdd.suites.HapiSuite; import java.time.Duration; import java.util.List; +import java.util.stream.Stream; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; +import org.junit.jupiter.api.DynamicTest; public class TransactionBodyValidation extends HapiSuite { private static final Logger log = LogManager.getLogger(TransactionBodyValidation.class); @@ -36,11 +37,11 @@ public static void main(String... args) { } @Override - public List getSpecsInSuite() { + public List> getSpecsInSuite() { return List.of(validateTransactionBodySet()); } - final HapiSpec validateTransactionBodySet() { + final Stream validateTransactionBodySet() { return defaultHapiSpec("TransactionBodyValidation") .given() .when() diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/regression/AddWellKnownEntities.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/regression/AddWellKnownEntities.java deleted file mode 100644 index 331831539001..000000000000 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/regression/AddWellKnownEntities.java +++ /dev/null @@ -1,61 +0,0 @@ -/* - * Copyright (C) 2020-2024 Hedera Hashgraph, LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.hedera.services.bdd.suites.regression; - -import static com.hedera.services.bdd.spec.utilops.UtilVerbs.expectedEntitiesExist; -import static com.hedera.services.bdd.spec.utilops.UtilVerbs.freezeOnly; -import static com.hedera.services.bdd.spec.utilops.UtilVerbs.sleepFor; - -import com.hedera.services.bdd.spec.HapiSpec; -import com.hedera.services.bdd.suites.HapiSuite; -import java.util.List; -import java.util.Map; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; - -public class AddWellKnownEntities extends HapiSuite { - private static final Logger log = LogManager.getLogger(AddWellKnownEntities.class); - - public static void main(String... args) { - var hero = new AddWellKnownEntities(); - - hero.runSuiteSync(); - } - - @Override - public List getSpecsInSuite() { - return List.of(new HapiSpec[] { - instantiateEntities(), - }); - } - - final HapiSpec instantiateEntities() { - return HapiSpec.customHapiSpec("AddWellKnownEntities") - .withProperties(Map.of( - "fees.useFixedOffer", "true", - "fees.fixedOffer", "" + ONE_HUNDRED_HBARS, - "persistentEntities.dir.path", "src/main/resource/jrs-creations")) - .given(expectedEntitiesExist()) - .when() - .then(sleepFor(10_000L), freezeOnly().startingIn(60).seconds().payingWith(GENESIS)); - } - - @Override - protected Logger getResultsLogger() { - return log; - } -} diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/regression/AddressAliasIdFuzzing.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/regression/AddressAliasIdFuzzing.java index 37c42b1d66f2..5d81031cc665 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/regression/AddressAliasIdFuzzing.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/regression/AddressAliasIdFuzzing.java @@ -23,21 +23,21 @@ import static com.hedera.services.bdd.spec.queries.QueryVerbs.getFileContents; import static com.hedera.services.bdd.spec.transactions.TxnVerbs.cryptoCreate; import static com.hedera.services.bdd.spec.utilops.UtilVerbs.*; +import static com.hedera.services.bdd.suites.HapiSuite.APP_PROPERTIES; +import static com.hedera.services.bdd.suites.HapiSuite.CHAIN_ID_PROP; import static com.hedera.services.bdd.suites.leaky.LeakyCryptoTestsSuite.*; import static com.hedera.services.bdd.suites.regression.factories.IdFuzzingProviderFactory.*; import static java.util.stream.Collectors.joining; import com.hedera.services.bdd.junit.HapiTest; -import com.hedera.services.bdd.junit.HapiTestSuite; -import com.hedera.services.bdd.spec.HapiSpec; -import com.hedera.services.bdd.suites.HapiSuite; import java.util.LinkedHashMap; -import java.util.List; import java.util.Map; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; +import java.util.stream.Stream; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; +import org.junit.jupiter.api.DynamicTest; /** * We want to make this suite exercise all forms of identity a Hedera account may have, under all @@ -45,27 +45,17 @@ * *

    See #4565 for details. */ -@HapiTestSuite -public class AddressAliasIdFuzzing extends HapiSuite { +public class AddressAliasIdFuzzing { private static final Logger log = LogManager.getLogger(AddressAliasIdFuzzing.class); private static final String PROPERTIES = "id-fuzzing.properties"; public static final String ATOMIC_CRYPTO_TRANSFER = "contracts.precompile.atomicCryptoTransfer.enabled"; - private final AtomicInteger maxOpsPerSec = new AtomicInteger(1); + private final AtomicInteger maxOpsPerSec = new AtomicInteger(10); private final AtomicInteger maxPendingOps = new AtomicInteger(Integer.MAX_VALUE); private final AtomicInteger backoffSleepSecs = new AtomicInteger(Integer.MAX_VALUE); - public static void main(String... args) { - new AddressAliasIdFuzzing().runSuiteSync(); - } - - @Override - public List getSpecsInSuite() { - return List.of(addressAliasIdFuzzing(), transferToKeyFuzzing()); - } - @HapiTest - final HapiSpec addressAliasIdFuzzing() { + final Stream addressAliasIdFuzzing() { final Map existingProps = new LinkedHashMap<>(); return propertyPreservingHapiSpec("AddressAliasIdFuzzing") .preserving( @@ -86,7 +76,7 @@ final HapiSpec addressAliasIdFuzzing() { } @HapiTest - final HapiSpec transferToKeyFuzzing() { + final Stream transferToKeyFuzzing() { return defaultHapiSpec("TransferToKeyFuzzing") .given(cryptoCreate(UNIQUE_PAYER_ACCOUNT) .balance(UNIQUE_PAYER_ACCOUNT_INITIAL_BALANCE) @@ -94,9 +84,4 @@ final HapiSpec transferToKeyFuzzing() { .when() .then(runWithProvider(idTransferToRandomKeyWith(PROPERTIES)).lasting(10L, TimeUnit.SECONDS)); } - - @Override - protected Logger getResultsLogger() { - return log; - } } diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/regression/CompletedHollowAccountOperationsFuzzing.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/regression/CompletedHollowAccountOperationsFuzzing.java index 07a49972d3d6..da1a9f785a00 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/regression/CompletedHollowAccountOperationsFuzzing.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/regression/CompletedHollowAccountOperationsFuzzing.java @@ -22,42 +22,24 @@ import static com.hedera.services.bdd.suites.regression.factories.HollowAccountCompletedFuzzingFactory.initOperations; import com.hedera.services.bdd.junit.HapiTest; -import com.hedera.services.bdd.junit.HapiTestSuite; -import com.hedera.services.bdd.spec.HapiSpec; -import com.hedera.services.bdd.suites.HapiSuite; -import java.util.List; import java.util.concurrent.TimeUnit; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; +import java.util.stream.Stream; +import org.junit.jupiter.api.DynamicTest; /** * Fuzz test, testing different operations on completed hollow account */ -@HapiTestSuite -public class CompletedHollowAccountOperationsFuzzing extends HapiSuite { - private static final Logger log = LogManager.getLogger(CompletedHollowAccountOperationsFuzzing.class); - +public class CompletedHollowAccountOperationsFuzzing { private static final String PROPERTIES = "completed-hollow-account-fuzzing.properties"; - public static void main(String... args) { - new CompletedHollowAccountOperationsFuzzing().runSuiteSync(); - } - - @Override - public List getSpecsInSuite() { - return List.of(completedHollowAccountOperationsFuzzing()); - } - @HapiTest - final HapiSpec completedHollowAccountOperationsFuzzing() { + final Stream completedHollowAccountOperationsFuzzing() { return defaultHapiSpec("CompletedHollowAccountOperationsFuzzing") .given(initOperations()) .when() - .then(runWithProvider(hollowAccountFuzzingWith(PROPERTIES)).lasting(60L, TimeUnit.SECONDS)); - } - - @Override - protected Logger getResultsLogger() { - return log; + .then(runWithProvider(hollowAccountFuzzingWith(PROPERTIES)) + .maxOpsPerSec(10) + .loggingOff() + .lasting(10L, TimeUnit.SECONDS)); } } diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/regression/HollowAccountCompletionFuzzing.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/regression/HollowAccountCompletionFuzzing.java index 5cec090a0dfb..c43c98eaf5f0 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/regression/HollowAccountCompletionFuzzing.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/regression/HollowAccountCompletionFuzzing.java @@ -22,42 +22,23 @@ import static com.hedera.services.bdd.suites.regression.factories.AccountCompletionFuzzingFactory.initOperations; import com.hedera.services.bdd.junit.HapiTest; -import com.hedera.services.bdd.junit.HapiTestSuite; -import com.hedera.services.bdd.spec.HapiSpec; -import com.hedera.services.bdd.suites.HapiSuite; -import java.util.List; import java.util.concurrent.TimeUnit; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; +import java.util.stream.Stream; +import org.junit.jupiter.api.DynamicTest; /** * Fuzz test, testing the completion of hollow accounts, by running a set of random operations for a period of time */ -@HapiTestSuite -public class HollowAccountCompletionFuzzing extends HapiSuite { - private static final Logger log = LogManager.getLogger(HollowAccountCompletionFuzzing.class); - +public class HollowAccountCompletionFuzzing { private static final String PROPERTIES = "hollow-account-completion-fuzzing.properties"; - public static void main(String... args) { - new HollowAccountCompletionFuzzing().runSuiteSync(); - } - - @Override - public List getSpecsInSuite() { - return List.of(hollowAccountCompletionFuzzing()); - } - @HapiTest - final HapiSpec hollowAccountCompletionFuzzing() { + final Stream hollowAccountCompletionFuzzing() { return defaultHapiSpec("HollowAccountCompletionFuzzing") .given(initOperations()) .when() - .then(runWithProvider(hollowAccountFuzzingWith(PROPERTIES)).lasting(10L, TimeUnit.SECONDS)); - } - - @Override - protected Logger getResultsLogger() { - return log; + .then(runWithProvider(hollowAccountFuzzingWith(PROPERTIES)) + .maxOpsPerSec(10) + .lasting(10L, TimeUnit.SECONDS)); } } diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/regression/HollowAccountFuzzing.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/regression/HollowAccountFuzzing.java index 738ed015d7d4..4244a2b27584 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/regression/HollowAccountFuzzing.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/regression/HollowAccountFuzzing.java @@ -22,40 +22,20 @@ import static com.hedera.services.bdd.suites.regression.factories.HollowAccountFuzzingFactory.initOperations; import com.hedera.services.bdd.junit.HapiTest; -import com.hedera.services.bdd.junit.HapiTestSuite; -import com.hedera.services.bdd.spec.HapiSpec; -import com.hedera.services.bdd.suites.HapiSuite; -import java.util.List; import java.util.concurrent.TimeUnit; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; - -@HapiTestSuite -public class HollowAccountFuzzing extends HapiSuite { - - private static final Logger log = LogManager.getLogger(HollowAccountFuzzing.class); +import java.util.stream.Stream; +import org.junit.jupiter.api.DynamicTest; +public class HollowAccountFuzzing { private static final String PROPERTIES = "hollow-account-fuzzing.properties"; - public static void main(String... args) { - new HollowAccountFuzzing().runSuiteSync(); - } - @HapiTest - final HapiSpec hollowAccountFuzzing() { + final Stream hollowAccountFuzzing() { return defaultHapiSpec("HollowAccountFuzzing") .given(initOperations()) .when() - .then(runWithProvider(hollowAccountFuzzingTest(PROPERTIES)).lasting(10L, TimeUnit.SECONDS)); - } - - @Override - protected Logger getResultsLogger() { - return log; - } - - @Override - public List getSpecsInSuite() { - return List.of(hollowAccountFuzzing()); + .then(runWithProvider(hollowAccountFuzzingTest(PROPERTIES)) + .maxOpsPerSec(10) + .lasting(10L, TimeUnit.SECONDS)); } } diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/regression/JrsRestartTestTemplate.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/regression/JrsRestartTestTemplate.java index c7de69337d90..2da7e250aa08 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/regression/JrsRestartTestTemplate.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/regression/JrsRestartTestTemplate.java @@ -17,7 +17,6 @@ package com.hedera.services.bdd.suites.regression; import static com.hedera.services.bdd.spec.HapiSpec.customHapiSpec; -import static com.hedera.services.bdd.spec.HapiSpec.defaultHapiSpec; import static com.hedera.services.bdd.spec.assertions.AccountInfoAsserts.accountWith; import static com.hedera.services.bdd.spec.assertions.ContractFnResultAsserts.isLiteralResult; import static com.hedera.services.bdd.spec.assertions.ContractFnResultAsserts.resultWith; @@ -40,19 +39,19 @@ import static com.hedera.services.bdd.spec.utilops.UtilVerbs.withOpContext; import static com.hedera.services.bdd.suites.contract.Utils.FunctionType.FUNCTION; import static com.hedera.services.bdd.suites.contract.Utils.getABIFor; -import static com.hedera.services.bdd.suites.perf.PerfUtilOps.scheduleOpsEnablement; import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.ACCOUNT_FROZEN_FOR_TOKEN; import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.ACCOUNT_KYC_NOT_GRANTED_FOR_TOKEN; -import com.hedera.services.bdd.spec.HapiSpec; import com.hedera.services.bdd.spec.HapiSpecOperation; import com.hedera.services.bdd.suites.HapiSuite; import java.math.BigInteger; import java.util.Arrays; import java.util.List; import java.util.Map; +import java.util.stream.Stream; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; +import org.junit.jupiter.api.DynamicTest; /** * This restart test uses the named entities under @@ -71,6 +70,8 @@ *

    SCHEDULES - pendingXfer (1tâ„ from sender to receiver; has sender sig only) * *

    CONTRACTS - multipurpose + * + *

    (ATROPHIED) */ public class JrsRestartTestTemplate extends HapiSuite { private static final Logger log = LogManager.getLogger(JrsRestartTestTemplate.class); @@ -93,23 +94,11 @@ public static void main(String... args) { } @Override - public List getSpecsInSuite() { - return List.of(new HapiSpec[] { - enableHSS(), jrsRestartTemplate(), - }); - } - - final HapiSpec enableHSS() { - return defaultHapiSpec("enableHSS") - .given( - // Directly puting this request in the customHapiSpec before - // expectedEntitiesExist() doesn't work - scheduleOpsEnablement()) - .when() - .then(); + public List> getSpecsInSuite() { + return List.of(jrsRestartTemplate()); } - final HapiSpec jrsRestartTemplate() { + final Stream jrsRestartTemplate() { return customHapiSpec("JrsRestartTemplate") .withProperties(Map.of("persistentEntities.dir.path", ENTITIES_DIR)) .given(expectedEntitiesExist()) diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/regression/PrecompileMintThrottlingCheck.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/regression/PrecompileMintThrottlingCheck.java index d7cfcb616ea7..04c60b971d6a 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/regression/PrecompileMintThrottlingCheck.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/regression/PrecompileMintThrottlingCheck.java @@ -49,9 +49,11 @@ import java.util.concurrent.atomic.AtomicReference; import java.util.function.Function; import java.util.stream.IntStream; +import java.util.stream.Stream; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.DynamicTest; public class PrecompileMintThrottlingCheck extends HapiSuite { @@ -69,12 +71,12 @@ public static void main(String... args) { } @Override - public List getSpecsInSuite() { + public List> getSpecsInSuite() { return List.of(precompileNftMintsAreLimitedByConsThrottle()); } @SuppressWarnings("java:S5960") - final HapiSpec precompileNftMintsAreLimitedByConsThrottle() { + final Stream precompileNftMintsAreLimitedByConsThrottle() { var mainnetLimits = protoDefsFromResource("testSystemFiles/mainnet-throttles.json"); return propertyPreservingHapiSpec("PrecompileNftMintsAreLimitedByConsThrottle") .preserving("contracts.throttle.throttleByGas") diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/regression/SplittingThrottlesWorks.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/regression/SplittingThrottlesWorks.java index e6180d92b38c..7c0cb612c3a6 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/regression/SplittingThrottlesWorks.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/regression/SplittingThrottlesWorks.java @@ -41,8 +41,10 @@ import java.util.concurrent.atomic.AtomicLong; import java.util.concurrent.atomic.AtomicReference; import java.util.function.Function; +import java.util.stream.Stream; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; +import org.junit.jupiter.api.DynamicTest; public class SplittingThrottlesWorks extends HapiSuite { private static final Logger log = LogManager.getLogger(SplittingThrottlesWorks.class); @@ -58,13 +60,11 @@ public static void main(String... args) { } @Override - public List getSpecsInSuite() { - return List.of(new HapiSpec[] { - setNewLimits(), tryCreations(), - }); + public List> getSpecsInSuite() { + return List.of(setNewLimits(), tryCreations()); } - final HapiSpec setNewLimits() { + final Stream setNewLimits() { var artificialLimits = protoDefsFromResource("testSystemFiles/split-throttles.json"); return defaultHapiSpec("SetNewLimits") @@ -75,7 +75,7 @@ final HapiSpec setNewLimits() { .contents(artificialLimits.toByteArray())); } - final HapiSpec tryCreations() { + final Stream tryCreations() { return defaultHapiSpec("TryCreations") .given() .when(runWithProvider(cryptoCreateOps()) diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/regression/SteadyStateThrottlingCheck.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/regression/SteadyStateThrottlingCheck.java index e5640ba57891..2e04018c45d8 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/regression/SteadyStateThrottlingCheck.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/regression/SteadyStateThrottlingCheck.java @@ -16,7 +16,7 @@ package com.hedera.services.bdd.suites.regression; -import static com.hedera.services.bdd.junit.TestTags.TIME_CONSUMING; +import static com.hedera.services.bdd.junit.TestTags.LONG_RUNNING; import static com.hedera.services.bdd.spec.HapiSpec.customHapiSpec; import static com.hedera.services.bdd.spec.HapiSpec.defaultHapiSpec; import static com.hedera.services.bdd.spec.queries.QueryVerbs.getAccountBalance; @@ -34,6 +34,14 @@ import static com.hedera.services.bdd.spec.utilops.UtilVerbs.newKeyNamed; import static com.hedera.services.bdd.spec.utilops.UtilVerbs.runWithProvider; import static com.hedera.services.bdd.spec.utilops.UtilVerbs.withOpContext; +import static com.hedera.services.bdd.suites.HapiSuite.ADDRESS_BOOK_CONTROL; +import static com.hedera.services.bdd.suites.HapiSuite.APP_PROPERTIES; +import static com.hedera.services.bdd.suites.HapiSuite.EXCHANGE_RATE_CONTROL; +import static com.hedera.services.bdd.suites.HapiSuite.GENESIS; +import static com.hedera.services.bdd.suites.HapiSuite.ONE_HBAR; +import static com.hedera.services.bdd.suites.HapiSuite.ONE_MILLION_HBARS; +import static com.hedera.services.bdd.suites.HapiSuite.THROTTLE_DEFS; +import static com.hedera.services.bdd.suites.HapiSuite.TOKEN_TREASURY; import static com.hedera.services.bdd.suites.utils.sysfiles.serdes.ThrottleDefsLoader.protoDefsFromResource; import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.BUSY; import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.OK; @@ -43,14 +51,12 @@ import com.google.common.base.Stopwatch; import com.google.protobuf.ByteString; import com.hedera.services.bdd.junit.HapiTest; -import com.hedera.services.bdd.junit.HapiTestSuite; +import com.hedera.services.bdd.junit.OrderedInIsolation; import com.hedera.services.bdd.spec.HapiSpec; import com.hedera.services.bdd.spec.HapiSpecOperation; import com.hedera.services.bdd.spec.HapiSpecSetup; import com.hedera.services.bdd.spec.infrastructure.OpProvider; import com.hedera.services.bdd.spec.queries.crypto.HapiGetAccountBalance; -import com.hedera.services.bdd.suites.BddMethodIsNotATest; -import com.hedera.services.bdd.suites.HapiSuite; import java.util.Collections; import java.util.List; import java.util.Map; @@ -61,21 +67,15 @@ import java.util.concurrent.atomic.AtomicReference; import java.util.function.Function; import java.util.stream.IntStream; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; +import java.util.stream.Stream; import org.junit.jupiter.api.Assertions; -import org.junit.jupiter.api.MethodOrderer.OrderAnnotation; +import org.junit.jupiter.api.DynamicTest; import org.junit.jupiter.api.Order; import org.junit.jupiter.api.Tag; -import org.junit.jupiter.api.TestMethodOrder; - -@HapiTestSuite -@TestMethodOrder(OrderAnnotation.class) -@Tag(TIME_CONSUMING) -public class SteadyStateThrottlingCheck extends HapiSuite { - - private static final Logger LOG = LogManager.getLogger(SteadyStateThrottlingCheck.class); +@Tag(LONG_RUNNING) +@OrderedInIsolation +public class SteadyStateThrottlingCheck { private static final String TOKENS_NFTS_MINT_THROTTLE_SCALE_FACTOR = "tokens.nfts.mintThrottleScaleFactor"; private static final String DEFAULT_NFT_SCALING = HapiSpecSetup.getDefaultNodeProps().get(TOKENS_NFTS_MINT_THROTTLE_SCALE_FACTOR); @@ -106,25 +106,9 @@ public class SteadyStateThrottlingCheck extends HapiSuite { private final AtomicReference unit = new AtomicReference<>(SECONDS); private final AtomicInteger maxOpsPerSec = new AtomicInteger(500); - public static void main(String... args) { - new SteadyStateThrottlingCheck().runSuiteSync(); - } - - @Override - public List getSpecsInSuite() { - return List.of( - setArtificialLimits(), - checkTps("Xfers", EXPECTED_XFER_TPS, xferOps()), - checkTps("FungibleMints", EXPECTED_FUNGIBLE_MINT_TPS, fungibleMintOps()), - checkTps("ContractCalls", EXPECTED_CONTRACT_CALL_TPS, scCallOps()), - checkTps("CryptoCreates", EXPECTED_CRYPTO_CREATE_TPS, cryptoCreateOps()), - checkBalanceQps(1000, EXPECTED_GET_BALANCE_QPS), - restoreDevLimits()); - } - @HapiTest @Order(1) - final HapiSpec setArtificialLimits() { + final Stream setArtificialLimits() { var artificialLimits = protoDefsFromResource("testSystemFiles/artificial-limits.json"); return defaultHapiSpec("SetArtificialLimits") @@ -137,37 +121,37 @@ final HapiSpec setArtificialLimits() { @HapiTest @Order(2) - final HapiSpec checkXfersTps() { + final Stream checkXfersTps() { return checkTps("Xfers", EXPECTED_XFER_TPS, xferOps()); } @HapiTest @Order(3) - final HapiSpec checkFungibleMintsTps() { + final Stream checkFungibleMintsTps() { return checkTps("FungibleMints", EXPECTED_FUNGIBLE_MINT_TPS, fungibleMintOps()); } @HapiTest @Order(4) - final HapiSpec checkContractCallsTps() { + final Stream checkContractCallsTps() { return checkTps("ContractCalls", EXPECTED_CONTRACT_CALL_TPS, scCallOps()); } @HapiTest @Order(5) - final HapiSpec checkCryptoCreatesTps() { + final Stream checkCryptoCreatesTps() { return checkTps("CryptoCreates", EXPECTED_CRYPTO_CREATE_TPS, cryptoCreateOps()); } @HapiTest @Order(6) - final HapiSpec checkBalanceQps() { + final Stream checkBalanceQps() { return checkBalanceQps(50, EXPECTED_GET_BALANCE_QPS); } @HapiTest @Order(7) - final HapiSpec restoreDevLimits() { + final Stream restoreDevLimits() { var defaultThrottles = protoDefsFromResource("testSystemFiles/throttles-dev.json"); return defaultHapiSpec("RestoreDevLimits") .given() @@ -181,8 +165,7 @@ final HapiSpec restoreDevLimits() { .payingWith(ADDRESS_BOOK_CONTROL)); } - @BddMethodIsNotATest - final HapiSpec checkTps(String txn, double expectedTps, Function provider) { + final Stream checkTps(String txn, double expectedTps, Function provider) { return checkCustomNetworkTps(txn, expectedTps, provider, Collections.emptyMap()); } @@ -199,9 +182,8 @@ final HapiSpec checkTps(String txn, double expectedTps, Function */ - @BddMethodIsNotATest @SuppressWarnings("java:S5960") - final HapiSpec checkCustomNetworkTps( + final Stream checkCustomNetworkTps( String txn, double expectedTps, Function provider, Map custom) { final var name = "Throttles" + txn + "AsExpected"; final var baseSpec = @@ -225,8 +207,7 @@ final HapiSpec checkCustomNetworkTps( })); } - @BddMethodIsNotATest - final HapiSpec checkBalanceQps(int burstSize, double expectedQps) { + final Stream checkBalanceQps(int burstSize, double expectedQps) { return defaultHapiSpec("CheckBalanceQps") .given(cryptoCreate("curious").payingWith(GENESIS)) .when() @@ -409,9 +390,4 @@ public Optional get() { } }; } - - @Override - protected Logger getResultsLogger() { - return LOG; - } } diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/regression/TargetNetworkPrep.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/regression/TargetNetworkPrep.java index a7053763649d..2f98b7b717ac 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/regression/TargetNetworkPrep.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/regression/TargetNetworkPrep.java @@ -17,7 +17,8 @@ package com.hedera.services.bdd.suites.regression; import static com.hedera.node.app.hapi.utils.keys.Ed25519Utils.relocatedIfNotPresentInWorkingDir; -import static com.hedera.services.bdd.spec.HapiSpec.defaultHapiSpec; +import static com.hedera.services.bdd.junit.ContextRequirement.PROPERTY_OVERRIDES; +import static com.hedera.services.bdd.spec.HapiSpec.propertyPreservingHapiSpec; import static com.hedera.services.bdd.spec.assertions.AccountDetailsAsserts.accountDetailsWith; import static com.hedera.services.bdd.spec.assertions.AccountInfoAsserts.changeFromSnapshot; import static com.hedera.services.bdd.spec.queries.QueryVerbs.getAccountBalance; @@ -30,20 +31,22 @@ import static com.hedera.services.bdd.spec.utilops.CustomSpecAssert.allRunFor; import static com.hedera.services.bdd.spec.utilops.UtilVerbs.balanceSnapshot; import static com.hedera.services.bdd.spec.utilops.UtilVerbs.inParallel; -import static com.hedera.services.bdd.spec.utilops.UtilVerbs.overridingAllOf; +import static com.hedera.services.bdd.spec.utilops.UtilVerbs.overridingTwo; import static com.hedera.services.bdd.spec.utilops.UtilVerbs.sourcing; import static com.hedera.services.bdd.spec.utilops.UtilVerbs.uploadDefaultFeeSchedules; import static com.hedera.services.bdd.spec.utilops.UtilVerbs.withOpContext; +import static com.hedera.services.bdd.suites.HapiSuite.API_PERMISSIONS; +import static com.hedera.services.bdd.suites.HapiSuite.GENESIS; +import static com.hedera.services.bdd.suites.HapiSuite.NODE_REWARD; +import static com.hedera.services.bdd.suites.HapiSuite.ONE_HBAR; +import static com.hedera.services.bdd.suites.HapiSuite.STAKING_REWARD; import static com.hedera.services.bdd.suites.records.RecordCreationSuite.STAKING_FEES_NODE_REWARD_PERCENTAGE; import static com.hedera.services.bdd.suites.records.RecordCreationSuite.STAKING_FEES_STAKING_REWARD_PERCENTAGE; import com.hedera.node.app.hapi.utils.fee.FeeObject; -import com.hedera.services.bdd.junit.HapiTest; -import com.hedera.services.bdd.junit.HapiTestSuite; -import com.hedera.services.bdd.spec.HapiSpec; +import com.hedera.services.bdd.junit.LeakyHapiTest; import com.hedera.services.bdd.spec.HapiSpecOperation; import com.hedera.services.bdd.spec.assertions.AccountInfoAsserts; -import com.hedera.services.bdd.suites.HapiSuite; import com.hedera.services.bdd.suites.utils.sysfiles.serdes.StandardSerdes; import com.hederahashgraph.api.proto.java.Key; import com.hederahashgraph.api.proto.java.KeyList; @@ -51,31 +54,14 @@ import java.io.UncheckedIOException; import java.nio.file.Files; import java.nio.file.Paths; -import java.util.List; -import java.util.Map; import java.util.concurrent.atomic.AtomicReference; import java.util.stream.IntStream; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; +import java.util.stream.Stream; +import org.junit.jupiter.api.DynamicTest; -@HapiTestSuite -public class TargetNetworkPrep extends HapiSuite { - private static final Logger log = LogManager.getLogger(TargetNetworkPrep.class); - public static final int SYSTEM_ENTITY_EXPIRY = 1812637686; - - public static void main(String... args) { - var hero = new TargetNetworkPrep(); - - hero.runSuiteSync(); - } - - @Override - public List getSpecsInSuite() { - return List.of(ensureSystemStateAsExpectedWithSystemDefaultFiles()); - } - - @HapiTest - final HapiSpec ensureSystemStateAsExpectedWithSystemDefaultFiles() { +public class TargetNetworkPrep { + @LeakyHapiTest(PROPERTY_OVERRIDES) + final Stream ensureSystemStateAsExpectedWithSystemDefaultFiles() { final var emptyKey = Key.newBuilder().setKeyList(KeyList.getDefaultInstance()).build(); final var snapshot800 = "800startBalance"; @@ -88,27 +74,18 @@ final HapiSpec ensureSystemStateAsExpectedWithSystemDefaultFiles() { Files.readString(relocatedIfNotPresentInWorkingDir(Paths.get(defaultPermissionsLoc))); final var serde = StandardSerdes.SYS_FILE_SERDES.get(122L); - return defaultHapiSpec("ensureSystemStateAsExpectedWithSystemDefaultFiles") + return propertyPreservingHapiSpec("ensureSystemStateAsExpectedWithSystemDefaultFiles") + .preserving(STAKING_FEES_NODE_REWARD_PERCENTAGE, STAKING_FEES_STAKING_REWARD_PERCENTAGE) .given( uploadDefaultFeeSchedules(GENESIS), fileUpdate(API_PERMISSIONS) .payingWith(GENESIS) .contents(serde.toValidatedRawFile(stylized121, null)), - overridingAllOf(Map.of( - "scheduling.whitelist", - "ConsensusSubmitMessage,CryptoTransfer,TokenMint,TokenBurn,CryptoApproveAllowance,CryptoUpdate", - CHAIN_ID_PROP, - "298", + overridingTwo( STAKING_FEES_NODE_REWARD_PERCENTAGE, "10", STAKING_FEES_STAKING_REWARD_PERCENTAGE, - "10", - "staking.isEnabled", - "true", - "staking.perHbarRewardRate", - "100_000_000_000", - "staking.startThreshold", - "100_000_000"))) + "10")) .when( cryptoCreate(civilian), balanceSnapshot(snapshot800, STAKING_REWARD), @@ -171,9 +148,4 @@ final HapiSpec ensureSystemStateAsExpectedWithSystemDefaultFiles() { throw new UncheckedIOException(e); } } - - @Override - protected Logger getResultsLogger() { - return log; - } } diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/regression/UmbrellaRedux.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/regression/UmbrellaRedux.java index 75a7c690c44b..d2a965b5ed38 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/regression/UmbrellaRedux.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/regression/UmbrellaRedux.java @@ -17,37 +17,29 @@ package com.hedera.services.bdd.suites.regression; import static com.hedera.services.bdd.spec.HapiSpec.defaultHapiSpec; -import static com.hedera.services.bdd.spec.infrastructure.OpProvider.UNIQUE_PAYER_ACCOUNT; -import static com.hedera.services.bdd.spec.infrastructure.OpProvider.UNIQUE_PAYER_ACCOUNT_INITIAL_BALANCE; -import static com.hedera.services.bdd.spec.queries.QueryVerbs.getTxnRecord; -import static com.hedera.services.bdd.spec.transactions.TxnVerbs.cryptoCreate; import static com.hedera.services.bdd.spec.transactions.TxnVerbs.fileUpdate; import static com.hedera.services.bdd.spec.utilops.UtilVerbs.runWithProvider; import static com.hedera.services.bdd.spec.utilops.UtilVerbs.sleepFor; import static com.hedera.services.bdd.spec.utilops.UtilVerbs.sourcing; import static com.hedera.services.bdd.spec.utilops.UtilVerbs.withOpContext; +import static com.hedera.services.bdd.suites.HapiSuite.GENESIS; +import static com.hedera.services.bdd.suites.HapiSuite.THROTTLE_DEFS; import static com.hedera.services.bdd.suites.regression.factories.RegressionProviderFactory.factoryFrom; import static com.hedera.services.bdd.suites.utils.sysfiles.serdes.ThrottleDefsLoader.protoDefsFromResource; import static java.util.concurrent.TimeUnit.SECONDS; import com.hedera.services.bdd.junit.HapiTest; -import com.hedera.services.bdd.junit.HapiTestSuite; import com.hedera.services.bdd.spec.HapiPropertySource; import com.hedera.services.bdd.spec.HapiSpec; -import com.hedera.services.bdd.suites.HapiSuite; -import java.util.List; import java.util.Map; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicLong; import java.util.concurrent.atomic.AtomicReference; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; - -@HapiTestSuite -public class UmbrellaRedux extends HapiSuite { - private static final Logger log = LogManager.getLogger(UmbrellaRedux.class); +import java.util.stream.Stream; +import org.junit.jupiter.api.DynamicTest; +public class UmbrellaRedux { public static final String DEFAULT_PROPERTIES = "regression-mixed_ops.properties"; private AtomicLong duration = new AtomicLong(10); @@ -58,20 +50,8 @@ public class UmbrellaRedux extends HapiSuite { private AtomicReference props = new AtomicReference<>(DEFAULT_PROPERTIES); private AtomicReference unit = new AtomicReference<>(SECONDS); - public static void main(String... args) { - UmbrellaRedux umbrellaRedux = new UmbrellaRedux(); - umbrellaRedux.runSuiteSync(); - } - - @Override - public List getSpecsInSuite() { - return List.of(new HapiSpec[] { - umbrellaRedux(), - }); - } - @HapiTest - final HapiSpec umbrellaRedux() { + final Stream umbrellaRedux() { var defaultThrottles = protoDefsFromResource("testSystemFiles/throttles-dev.json"); return defaultHapiSpec("UmbrellaRedux") .given( @@ -82,13 +62,8 @@ final HapiSpec umbrellaRedux() { spec.addOverrideProperties(Map.of( "status.wait.timeout.ms", Integer.toString(1_000 * statusTimeoutSecs.get()))); }), - fileUpdate(THROTTLE_DEFS).payingWith(GENESIS).contents(defaultThrottles.toByteArray()), - cryptoCreate(UNIQUE_PAYER_ACCOUNT) - .balance(UNIQUE_PAYER_ACCOUNT_INITIAL_BALANCE) - .withRecharging() - .via("createUniquePayer"), - sleepFor(2000)) - .when(getTxnRecord("createUniquePayer").logged()) + fileUpdate(THROTTLE_DEFS).payingWith(GENESIS).contents(defaultThrottles.toByteArray())) + .when(sleepFor(2000)) .then(sourcing(() -> runWithProvider(factoryFrom(props::get)) .lasting(duration::get, unit::get) .maxOpsPerSec(maxOpsPerSec::get) @@ -123,9 +98,4 @@ private void configureFromCi(HapiSpec spec) { statusTimeoutSecs.set(ciProps.getInteger("secondsWaitingServerUp")); } } - - @Override - protected Logger getResultsLogger() { - return log; - } } diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/regression/UmbrellaReduxWithCustomNodes.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/regression/UmbrellaReduxWithCustomNodes.java deleted file mode 100644 index 8a4ec6a48830..000000000000 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/regression/UmbrellaReduxWithCustomNodes.java +++ /dev/null @@ -1,164 +0,0 @@ -/* - * Copyright (C) 2020-2024 Hedera Hashgraph, LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.hedera.services.bdd.suites.regression; - -import static com.hedera.services.bdd.spec.transactions.TxnVerbs.createTopic; -import static com.hedera.services.bdd.spec.transactions.TxnVerbs.cryptoCreate; -import static com.hedera.services.bdd.spec.transactions.TxnVerbs.submitMessageTo; -import static com.hedera.services.bdd.spec.utilops.UtilVerbs.newKeyNamed; -import static com.hedera.services.bdd.spec.utilops.UtilVerbs.runWithProvider; -import static com.hedera.services.bdd.spec.utilops.UtilVerbs.withOpContext; -import static com.hedera.services.bdd.suites.regression.factories.RegressionProviderFactory.factoryFrom; -import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.SUCCESS; -import static java.util.concurrent.TimeUnit.MILLISECONDS; - -import com.hedera.services.bdd.spec.HapiPropertySource; -import com.hedera.services.bdd.spec.HapiSpec; -import com.hedera.services.bdd.spec.HapiSpecSetup; -import com.hedera.services.bdd.spec.assertions.TransactionRecordAsserts; -import com.hedera.services.bdd.spec.props.NodeConnectInfo; -import com.hedera.services.bdd.spec.queries.QueryVerbs; -import com.hedera.services.bdd.suites.HapiSuite; -import java.util.List; -import java.util.Map; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.atomic.AtomicInteger; -import java.util.concurrent.atomic.AtomicLong; -import java.util.concurrent.atomic.AtomicReference; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; - -public class UmbrellaReduxWithCustomNodes extends HapiSuite { - private static final Logger log = LogManager.getLogger(UmbrellaRedux.class); - - public static final String DEFAULT_PROPERTIES = "regression-file_ops.properties"; - public static final String MESSAGE_SUBMISSION_SIMPLE = "messageSubmissionSimple"; - - public static String nodeId = "0.0."; - public static String nodeAddress = ""; - public static String payer = "0.0."; - public static int topic_running_hash_version = 0; - - private AtomicLong duration = new AtomicLong(1); - private AtomicInteger maxOpsPerSec = new AtomicInteger(Integer.MAX_VALUE); - private AtomicInteger maxPendingOps = new AtomicInteger(Integer.MAX_VALUE); - private AtomicInteger backoffSleepSecs = new AtomicInteger(1); - private AtomicInteger statusTimeoutSecs = new AtomicInteger(5); - private AtomicReference props = new AtomicReference<>(DEFAULT_PROPERTIES); - private AtomicReference unit = new AtomicReference<>(MILLISECONDS); - - public static void main(String... args) { - if (args.length < 4) { - HapiSpecSetup defaultSetup = HapiSpecSetup.getDefaultInstance(); - NodeConnectInfo nodeInfo = defaultSetup.nodes().get(0); - nodeId += nodeInfo.getAccount().getAccountNum(); - nodeAddress = nodeInfo.getHost(); - log.info( - "using default nodeId {} and node address {} -- RUNNING CUSTOM NET MIGRATION ", - nodeId, - nodeAddress); - payer += defaultSetup.defaultPayer().getAccountNum(); - topic_running_hash_version = defaultSetup.defaultTopicRunningHashVersion(); - } else { - nodeId += args[0]; - nodeAddress = args[1]; - payer += args[2]; - topic_running_hash_version = Integer.parseInt(args[3]); - } - - UmbrellaReduxWithCustomNodes umbrellaReduxWithCustomNodes = new UmbrellaReduxWithCustomNodes(); - umbrellaReduxWithCustomNodes.runSuiteSync(); - } - - @Override - public List getSpecsInSuite() { - return List.of(runUmbrellaReduxWithCustomNodes(), messageSubmissionSimple()); - } - - final HapiSpec messageSubmissionSimple() { - return HapiSpec.customHapiSpec(MESSAGE_SUBMISSION_SIMPLE) - .withProperties(Map.of( - "default.topic.runningHash.version", topic_running_hash_version, - "default.node", nodeId, - "default.payer", payer, - "nodes", nodeAddress + ":" + nodeId)) - .given(newKeyNamed("submitKey"), createTopic("testTopic").submitKeyName("submitKey")) - .when(cryptoCreate("civilian").sendThreshold(1L)) - .then( - submitMessageTo("testTopic") - .message("testmessage") - .payingWith("civilian") - .hasKnownStatus(SUCCESS) - .via(MESSAGE_SUBMISSION_SIMPLE), - QueryVerbs.getTxnRecord(MESSAGE_SUBMISSION_SIMPLE) - .logged() - .hasPriority(TransactionRecordAsserts.recordWith() - .checkTopicRunningHashVersion(topic_running_hash_version))); - } - - final HapiSpec runUmbrellaReduxWithCustomNodes() { - return HapiSpec.customHapiSpec("RunUmbrellaReduxWithCustomNodes") - .withProperties(Map.of( - "status.wait.timeout.ms", - Integer.toString(1_000 * statusTimeoutSecs.get()), - "default.node", - nodeId, - "default.payer", - payer, - "nodes", - nodeAddress + ":" + nodeId)) - .given() - .when() - .then( - withOpContext((spec, opLog) -> configureFromCi(spec)), - runWithProvider(factoryFrom(props::get)) - .lasting(duration::get, unit::get) - .maxOpsPerSec(maxOpsPerSec::get) - .maxPendingOps(maxPendingOps::get) - .backoffSleepSecs(backoffSleepSecs::get)); - } - - private void configureFromCi(HapiSpec spec) { - HapiPropertySource ciProps = spec.setup().ciPropertiesMap(); - if (ciProps.has("duration")) { - duration.set(ciProps.getLong("duration")); - } - if (ciProps.has("unit")) { - unit.set(ciProps.getTimeUnit("unit")); - } - if (ciProps.has("maxOpsPerSec")) { - maxOpsPerSec.set(ciProps.getInteger("maxOpsPerSec")); - } - if (ciProps.has("props")) { - props.set(ciProps.get("props")); - } - if (ciProps.has("maxPendingOps")) { - maxPendingOps.set(ciProps.getInteger("maxPendingOps")); - } - if (ciProps.has("backoffSleepSecs")) { - backoffSleepSecs.set(ciProps.getInteger("backoffSleepSecs")); - } - if (ciProps.has("statusTimeoutSecs")) { - statusTimeoutSecs.set(ciProps.getInteger("statusTimeoutSecs")); - } - } - - @Override - protected Logger getResultsLogger() { - return log; - } -} diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/regression/UtilScalePricingCheck.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/regression/UtilScalePricingCheck.java index 6f1d671a11f0..7ddf2a00649e 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/regression/UtilScalePricingCheck.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/regression/UtilScalePricingCheck.java @@ -23,45 +23,38 @@ import static com.hedera.services.bdd.spec.transactions.TxnVerbs.tokenCreate; import static com.hedera.services.bdd.spec.utilops.UtilVerbs.blockingOrder; import static com.hedera.services.bdd.spec.utilops.UtilVerbs.overridingTwo; +import static com.hedera.services.bdd.suites.HapiSuite.ONE_HUNDRED_HBARS; +import static com.hedera.services.bdd.suites.HapiSuite.ONE_MILLION_HBARS; +import static org.junit.jupiter.api.Assertions.assertEquals; import com.google.protobuf.ByteString; import com.hedera.services.bdd.junit.HapiTest; -import com.hedera.services.bdd.junit.HapiTestSuite; -import com.hedera.services.bdd.spec.HapiSpec; import com.hedera.services.bdd.spec.HapiSpecOperation; -import com.hedera.services.bdd.suites.HapiSuite; import com.hederahashgraph.api.proto.java.TokenType; import java.util.List; +import java.util.concurrent.atomic.AtomicLong; import java.util.function.IntFunction; import java.util.stream.IntStream; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; +import java.util.stream.Stream; +import org.junit.jupiter.api.DynamicTest; +import org.junit.jupiter.api.Order; // Run this suite first in CI, since it assumes there are no NFTs in state -@HapiTestSuite(order = Integer.MIN_VALUE) -public class UtilScalePricingCheck extends HapiSuite { - - private static final Logger LOG = LogManager.getLogger(UtilScalePricingCheck.class); +// at the beginning of the test +@Order(Integer.MIN_VALUE) +public class UtilScalePricingCheck { private static final String NON_FUNGIBLE_TOKEN = "NON_FUNGIBLE_TOKEN"; private static final String MAX_MINTS_PROP = "tokens.nfts.maxAllowedMints"; private static final String ENTITY_UTILIZATION_SCALE_FACTOR_PROP = "fees.percentUtilizationScaleFactors"; - public static void main(String... args) { - new UtilScalePricingCheck().runSuiteSync(); - } - - @Override - public List getSpecsInSuite() { - return List.of(nftPriceScalesWithUtilization()); - } - @HapiTest - final HapiSpec nftPriceScalesWithUtilization() { + final Stream nftPriceScalesWithUtilization() { final var civilian = "civilian"; - final var maxAllowed = 100; + final var maxAllowed = 10; final IntFunction mintOp = i -> "mint" + i; final var standard100ByteMetadata = ByteString.copyFromUtf8( "0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789"); + final AtomicLong baseFee = new AtomicLong(); return propertyPreservingHapiSpec("PrecompileNftMintsAreLimitedByConsThrottle") .preserving(MAX_MINTS_PROP, ENTITY_UTILIZATION_SCALE_FACTOR_PROP) .given( @@ -69,7 +62,7 @@ final HapiSpec nftPriceScalesWithUtilization() { MAX_MINTS_PROP, "" + maxAllowed, ENTITY_UTILIZATION_SCALE_FACTOR_PROP, - "NFT(0,1:1,25,2:1,50,5:1,75,10:1,90,50:1)"), + "NFT(0,1:1,50,5:1,90,50:1)"), cryptoCreate(civilian).balance(ONE_MILLION_HBARS), tokenCreate(NON_FUNGIBLE_TOKEN) .supplyKey(civilian) @@ -83,12 +76,31 @@ final HapiSpec nftPriceScalesWithUtilization() { .via(mintOp.apply(i))) .toArray(HapiSpecOperation[]::new))) .then(blockingOrder(IntStream.range(1, maxAllowed + 1) - .mapToObj(i -> getTxnRecord(mintOp.apply(i)).noLogging().loggingOnlyFee()) + .mapToObj(i -> getTxnRecord(mintOp.apply(i)) + .noLogging() + .loggingOnlyFee() + .exposingTo(mintRecord -> { + if (i == 1) { + baseFee.set(mintRecord.getTransactionFee()); + } else { + final var multiplier = expectedMultiplier(i); + final var expected = multiplier * baseFee.get(); + assertEquals( + expected, + mintRecord.getTransactionFee(), + multiplier + "x multiplier should be in effect at " + i + " mints"); + } + })) .toArray(HapiSpecOperation[]::new))); } - @Override - protected Logger getResultsLogger() { - return LOG; + private long expectedMultiplier(final int mintNo) { + if (mintNo <= 5) { + return 1; + } else if (mintNo <= 9) { + return 5; + } else { + return 50; + } } } diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/regression/factories/IdFuzzingProviderFactory.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/regression/factories/IdFuzzingProviderFactory.java index a8a0528697a4..bf2adcc6d197 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/regression/factories/IdFuzzingProviderFactory.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/regression/factories/IdFuzzingProviderFactory.java @@ -25,9 +25,13 @@ import static com.hedera.services.bdd.spec.transactions.token.TokenMovement.movingUnique; import static com.hedera.services.bdd.spec.utilops.UtilVerbs.*; import static com.hedera.services.bdd.suites.HapiSuite.CHAIN_ID_PROP; +import static com.hedera.services.bdd.suites.HapiSuite.GENESIS; import static com.hedera.services.bdd.suites.HapiSuite.ONE_HBAR; import static com.hedera.services.bdd.suites.HapiSuite.ONE_HUNDRED_HBARS; +import static com.hedera.services.bdd.suites.HapiSuite.ONE_MILLION_HBARS; +import static com.hedera.services.bdd.suites.HapiSuite.RELAYER; import static com.hedera.services.bdd.suites.HapiSuite.SECP_256K1_SHAPE; +import static com.hedera.services.bdd.suites.HapiSuite.SECP_256K1_SOURCE_KEY; import static com.hedera.services.bdd.suites.leaky.LeakyCryptoTestsSuite.*; import static com.hedera.services.bdd.suites.regression.AddressAliasIdFuzzing.ATOMIC_CRYPTO_TRANSFER; import static com.hedera.services.bdd.suites.regression.factories.RegressionProviderFactory.intPropOrElse; diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/regression/factories/RegressionProviderFactory.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/regression/factories/RegressionProviderFactory.java index 48df4d473e00..dc46312227f5 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/regression/factories/RegressionProviderFactory.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/regression/factories/RegressionProviderFactory.java @@ -16,6 +16,10 @@ package com.hedera.services.bdd.suites.regression.factories; +import static com.hedera.services.bdd.spec.infrastructure.OpProvider.UNIQUE_PAYER_ACCOUNT; +import static com.hedera.services.bdd.spec.infrastructure.OpProvider.UNIQUE_PAYER_ACCOUNT_INITIAL_BALANCE; +import static com.hedera.services.bdd.spec.transactions.TxnVerbs.cryptoCreate; +import static com.hedera.services.bdd.suites.HapiSuite.flattened; import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.INVALID_SIGNATURE; import com.hedera.services.bdd.spec.HapiPropertySource; @@ -117,7 +121,11 @@ public static Function factoryFrom(Supplier resour return new BiasedDelegatingProvider() /* --- --- */ - .withInitialization(keyInventory.creationOps()) + .withInitialization(flattened( + cryptoCreate(UNIQUE_PAYER_ACCOUNT) + .balance(UNIQUE_PAYER_ACCOUNT_INITIAL_BALANCE) + .withRecharging(), + keyInventory.creationOps())) /* ----- META ----- */ .withOp(new RandomRecord(spec.txns()), intPropOrElse("randomRecord.bias", 0, props)) .withOp(new RandomReceipt(spec.txns()), intPropOrElse("randomReceipt.bias", 0, props)) diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/regression/system/MixedOperations.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/regression/system/MixedOperations.java index a65e38c54ec3..8ad4339a6e24 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/regression/system/MixedOperations.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/regression/system/MixedOperations.java @@ -16,34 +16,14 @@ package com.hedera.services.bdd.suites.regression.system; -import static com.hedera.services.bdd.spec.transactions.TxnUtils.randomUtf8Bytes; -import static com.hedera.services.bdd.spec.transactions.TxnVerbs.contractCreate; -import static com.hedera.services.bdd.spec.transactions.TxnVerbs.createTopic; -import static com.hedera.services.bdd.spec.transactions.TxnVerbs.cryptoTransfer; -import static com.hedera.services.bdd.spec.transactions.TxnVerbs.fileUpdate; -import static com.hedera.services.bdd.spec.transactions.TxnVerbs.scheduleCreate; -import static com.hedera.services.bdd.spec.transactions.TxnVerbs.submitMessageTo; -import static com.hedera.services.bdd.spec.transactions.TxnVerbs.tokenAssociate; -import static com.hedera.services.bdd.spec.transactions.TxnVerbs.tokenCreate; -import static com.hedera.services.bdd.spec.transactions.crypto.HapiCryptoTransfer.tinyBarsFromTo; -import static com.hedera.services.bdd.spec.utilops.UtilVerbs.inParallel; -import static com.hedera.services.bdd.spec.utilops.UtilVerbs.sleepFor; -import static com.hedera.services.bdd.suites.HapiSuite.APP_PROPERTIES; -import static com.hedera.services.bdd.suites.HapiSuite.GENESIS; -import static com.hedera.services.bdd.suites.HapiSuite.THREE_MONTHS_IN_SECONDS; -import static com.hedera.services.bdd.suites.token.TokenTransactSpecs.SUPPLY_KEY; +import static com.hedera.services.bdd.spec.utilops.UtilVerbs.runWithProvider; +import static com.hedera.services.bdd.suites.regression.factories.RegressionProviderFactory.factoryFrom; import com.hedera.services.bdd.spec.HapiSpecOperation; -import com.hederahashgraph.api.proto.java.TokenSupplyType; -import com.hederahashgraph.api.proto.java.TokenType; -import java.nio.ByteBuffer; -import java.time.Instant; -import java.util.Map; -import java.util.Random; -import java.util.concurrent.atomic.AtomicInteger; -import java.util.function.Supplier; -import java.util.stream.IntStream; -import org.apache.commons.lang3.ArrayUtils; +import com.hedera.services.bdd.suites.regression.UmbrellaRedux; +import edu.umd.cs.findbugs.annotations.NonNull; +import java.time.Duration; +import java.util.concurrent.TimeUnit; /** * Returns mixed operations that can be used for restart and reconnect tests. @@ -62,92 +42,15 @@ public class MixedOperations { static final String PAYER = "payer"; final int numSubmissions; static final String SOME_BYTE_CODE = "contractByteCode"; - static final String CONTRACT_NAME_PREFIX = "testContract"; public MixedOperations(int numSubmissions) { this.numSubmissions = numSubmissions; } - Supplier mixedOps( - final AtomicInteger tokenId, - final AtomicInteger nftId, - final AtomicInteger scheduleId, - final AtomicInteger contractId, - final Random r) { - return () -> new HapiSpecOperation[] { - // Submit some mixed operations - fileUpdate(APP_PROPERTIES).payingWith(GENESIS).overridingProps(Map.of("tokens.maxPerAccount", "10000000")), - inParallel(IntStream.range(0, 2 * numSubmissions) - .mapToObj(ignore -> cryptoTransfer(tinyBarsFromTo(SENDER, RECEIVER, 1L)) - .payingWith(PAYER) - .logging() - .signedBy(SENDER, PAYER)) - .toArray(HapiSpecOperation[]::new)), - sleepFor(10000), - inParallel(IntStream.range(0, numSubmissions) - .mapToObj(ignore -> tokenCreate(TOKEN + tokenId.getAndIncrement()) - .supplyType(TokenSupplyType.FINITE) - .treasury(TREASURY) - .autoRenewPeriod(THREE_MONTHS_IN_SECONDS) - .maxSupply(1000) - .initialSupply(500) - .decimals(1) - .adminKey(ADMIN_KEY) - .supplyKey(SUPPLY_KEY) - .payingWith(PAYER) - .logging()) - .toArray(HapiSpecOperation[]::new)), - sleepFor(10000), - inParallel(IntStream.range(0, numSubmissions) - .mapToObj(ignore -> tokenCreate(NFT + nftId.getAndIncrement()) - .tokenType(TokenType.NON_FUNGIBLE_UNIQUE) - .treasury(TREASURY) - .autoRenewPeriod(THREE_MONTHS_IN_SECONDS) - .initialSupply(0) - .adminKey(ADMIN_KEY) - .supplyKey(SUPPLY_KEY) - .payingWith(PAYER) - .logging()) - .toArray(HapiSpecOperation[]::new)), - sleepFor(10000), - inParallel(IntStream.range(0, numSubmissions) - .mapToObj(i -> tokenAssociate(SENDER, TOKEN + i) - .payingWith(PAYER) - .logging() - .signedBy(SENDER, PAYER)) - .toArray(HapiSpecOperation[]::new)), - sleepFor(10000), - inParallel(IntStream.range(0, numSubmissions) - .mapToObj(i -> - createTopic(TOPIC + i).submitKeyName(SUBMIT_KEY).payingWith(PAYER)) - .toArray(HapiSpecOperation[]::new)), - sleepFor(10000), - inParallel(IntStream.range(0, numSubmissions) - .mapToObj(i -> submitMessageTo(TOPIC + i) - .message(ArrayUtils.addAll( - ByteBuffer.allocate(8) - .putLong(Instant.now().toEpochMilli()) - .array(), - randomUtf8Bytes(1000))) - .payingWith(SENDER) - .signedBy(SENDER, SUBMIT_KEY)) - .toArray(HapiSpecOperation[]::new)), - sleepFor(10000), - inParallel(IntStream.range(0, 100) - .mapToObj(ignore -> scheduleCreate( - "schedule" + scheduleId.incrementAndGet(), - cryptoTransfer(tinyBarsFromTo(SENDER, RECEIVER, r.nextInt(100000000)))) - .payingWith(PAYER) - .signedBy(SENDER, PAYER) - .adminKey(SENDER) - .logging()) - .toArray(HapiSpecOperation[]::new)), - sleepFor(10000), - inParallel(IntStream.range(0, 100) - .mapToObj(ignore -> contractCreate(CONTRACT_NAME_PREFIX + contractId.getAndIncrement()) - .bytecode(SOME_BYTE_CODE) - .logging()) - .toArray(HapiSpecOperation[]::new)) - }; + public static HapiSpecOperation burstOfTps(final int tps, @NonNull final Duration duration) { + return runWithProvider(factoryFrom(() -> UmbrellaRedux.DEFAULT_PROPERTIES)) + .lasting(duration.toMillis(), TimeUnit.MILLISECONDS) + .maxOpsPerSec(tps) + .loggingOff(); } } diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/regression/system/MixedOpsNodeDeathReconnectTest.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/regression/system/MixedOpsNodeDeathReconnectTest.java index 8d0faabfa629..88437b43149b 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/regression/system/MixedOpsNodeDeathReconnectTest.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/regression/system/MixedOpsNodeDeathReconnectTest.java @@ -18,46 +18,17 @@ import static com.hedera.services.bdd.junit.TestTags.ND_RECONNECT; import static com.hedera.services.bdd.spec.HapiSpec.defaultHapiSpec; -import static com.hedera.services.bdd.spec.transactions.TxnVerbs.createTopic; import static com.hedera.services.bdd.spec.transactions.TxnVerbs.cryptoCreate; -import static com.hedera.services.bdd.spec.transactions.TxnVerbs.fileCreate; -import static com.hedera.services.bdd.spec.transactions.TxnVerbs.mintToken; -import static com.hedera.services.bdd.spec.utilops.UtilVerbs.inParallel; -import static com.hedera.services.bdd.spec.utilops.UtilVerbs.newKeyNamed; -import static com.hedera.services.bdd.spec.utilops.UtilVerbs.shutDownNode; +import static com.hedera.services.bdd.spec.utilops.UtilVerbs.logIt; +import static com.hedera.services.bdd.spec.utilops.UtilVerbs.restartNode; +import static com.hedera.services.bdd.spec.utilops.UtilVerbs.shutdownWithin; import static com.hedera.services.bdd.spec.utilops.UtilVerbs.sleepFor; -import static com.hedera.services.bdd.spec.utilops.UtilVerbs.startNode; -import static com.hedera.services.bdd.spec.utilops.UtilVerbs.waitForNodeToBeBehind; -import static com.hedera.services.bdd.spec.utilops.UtilVerbs.waitForNodeToBecomeActive; -import static com.hedera.services.bdd.spec.utilops.UtilVerbs.waitForNodeToFinishReconnect; -import static com.hedera.services.bdd.spec.utilops.UtilVerbs.waitForNodeToShutDown; -import static com.hedera.services.bdd.suites.perf.PerfUtilOps.scheduleOpsEnablement; -import static com.hedera.services.bdd.suites.perf.PerfUtilOps.tokenOpsEnablement; -import static com.hedera.services.bdd.suites.regression.system.MixedOperations.ADMIN_KEY; -import static com.hedera.services.bdd.suites.regression.system.MixedOperations.NFT; -import static com.hedera.services.bdd.suites.regression.system.MixedOperations.PAYER; -import static com.hedera.services.bdd.suites.regression.system.MixedOperations.RECEIVER; -import static com.hedera.services.bdd.suites.regression.system.MixedOperations.SENDER; -import static com.hedera.services.bdd.suites.regression.system.MixedOperations.SOME_BYTE_CODE; -import static com.hedera.services.bdd.suites.regression.system.MixedOperations.SUBMIT_KEY; -import static com.hedera.services.bdd.suites.regression.system.MixedOperations.TOPIC; -import static com.hedera.services.bdd.suites.regression.system.MixedOperations.TREASURY; -import static com.hedera.services.bdd.suites.token.TokenTransactSpecs.SUPPLY_KEY; +import static com.hedera.services.bdd.spec.utilops.UtilVerbs.waitForActive; -import com.google.protobuf.ByteString; import com.hedera.services.bdd.junit.HapiTest; -import com.hedera.services.bdd.junit.HapiTestSuite; -import com.hedera.services.bdd.spec.HapiSpec; -import com.hedera.services.bdd.spec.HapiSpecOperation; -import com.hedera.services.bdd.spec.HapiSpecSetup; -import com.hedera.services.bdd.suites.HapiSuite; -import java.util.List; -import java.util.Random; -import java.util.concurrent.atomic.AtomicInteger; -import java.util.function.Supplier; -import java.util.stream.IntStream; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; +import java.time.Duration; +import java.util.stream.Stream; +import org.junit.jupiter.api.DynamicTest; import org.junit.jupiter.api.Tag; /** @@ -65,86 +36,41 @@ * shuts one node,and starts it back after some time. Node will reconnect, and once reconnect is completed * submits the same burst of mixed operations again. */ -@HapiTestSuite @Tag(ND_RECONNECT) -public class MixedOpsNodeDeathReconnectTest extends HapiSuite { - private static final Logger log = LogManager.getLogger(MixedOpsNodeDeathReconnectTest.class); - - private static final int NUM_SUBMISSIONS = 700; - - public static void main(String... args) { - new MixedOpsNodeDeathReconnectTest().runSuiteSync(); - } - - @Override - protected Logger getResultsLogger() { - return log; - } - - @Override - public List getSpecsInSuite() { - return List.of(reconnectMixedOps()); - } +public class MixedOpsNodeDeathReconnectTest { + private static final int MIXED_OPS_BURST_TPS = 50; + private static final Duration MIXED_OPS_BURST_DURATION = Duration.ofSeconds(10); + private static final Duration SHUTDOWN_TIMEOUT = Duration.ofSeconds(30); + private static final Duration RESTART_TO_ACTIVE_TIMEOUT = Duration.ofSeconds(120); @HapiTest - private HapiSpec reconnectMixedOps() { - final AtomicInteger tokenId = new AtomicInteger(0); - final AtomicInteger scheduleId = new AtomicInteger(0); - final AtomicInteger contractId = new AtomicInteger(0); - AtomicInteger nftId = new AtomicInteger(0); - Random r = new Random(38582L); - Supplier mixedOpsBurst = - new MixedOperations(NUM_SUBMISSIONS).mixedOps(tokenId, nftId, scheduleId, contractId, r); + final Stream reconnectMixedOps() { return defaultHapiSpec("RestartMixedOps") .given( - newKeyNamed(SUBMIT_KEY), - newKeyNamed(SUPPLY_KEY), - newKeyNamed(ADMIN_KEY), - tokenOpsEnablement(), - scheduleOpsEnablement(), - cryptoCreate(PAYER).balance(100 * ONE_MILLION_HBARS), - cryptoCreate(TREASURY), - cryptoCreate(SENDER).balance(ONE_MILLION_HBARS).payingWith(PAYER), - cryptoCreate(RECEIVER).balance(ONE_MILLION_HBARS).payingWith(PAYER), - createTopic(TOPIC).submitKeyName(SUBMIT_KEY).payingWith(PAYER), - fileCreate(SOME_BYTE_CODE) - .path(HapiSpecSetup.getDefaultInstance().defaultContractPath()), - // Kill node 2 - shutDownNode("Carol").logged(), - // Wait for it to shut down - waitForNodeToShutDown("Carol", 75).logged(), + // Validate we can initially submit transactions to node2 + cryptoCreate("nobody").setNode("0.0.5"), + // Run some mixed transactions + MixedOperations.burstOfTps(MIXED_OPS_BURST_TPS, MIXED_OPS_BURST_DURATION), + // Stop node 2 + shutdownWithin("Carol", SHUTDOWN_TIMEOUT), + logIt("Node 2 is supposedly down"), // This sleep is needed, since the ports of shutdown node may still be in time_wait status, // which will cause an error that address is already in use when restarting nodes. // Sleep long enough (120s or 180 secs for TIME_WAIT status to be finished based on // kernel settings), so restarting node succeeds. - sleepFor(180_000L).logged()) + sleepFor(180_000)) .when( // Submit operations when node 2 is down - inParallel(mixedOpsBurst.get()), - sleepFor(10000), - inParallel(IntStream.range(0, NUM_SUBMISSIONS) - .mapToObj(ignore -> mintToken( - NFT + nftId.getAndDecrement(), - List.of(ByteString.copyFromUtf8("a"), ByteString.copyFromUtf8("b"))) - .logging()) - .toArray(HapiSpecOperation[]::new)), - // start all nodes - startNode("Carol").logged(), - // wait for node 2 to go BEHIND - waitForNodeToBeBehind("Carol", 60).logged(), - // Node 2 will try to reconnect and comes to RECONNECT_COMPLETE - waitForNodeToFinishReconnect("Carol", 60).logged(), - // Node 2 successfully reconnects and becomes ACTIVE - waitForNodeToBecomeActive("Carol", 60).logged()) + MixedOperations.burstOfTps(MIXED_OPS_BURST_TPS, MIXED_OPS_BURST_DURATION), + // Restart node2 + restartNode("Carol"), + logIt("Node 2 is supposedly restarted"), + // Wait for node2 ACTIVE (BUSY and RECONNECT_COMPLETE are too transient to reliably poll for) + waitForActive("Carol", RESTART_TO_ACTIVE_TIMEOUT)) .then( - // Once node 2 come back ACTIVE, submit some operations again - cryptoCreate(PAYER).balance(100 * ONE_MILLION_HBARS), - cryptoCreate(TREASURY).balance(ONE_MILLION_HBARS).payingWith(PAYER), - cryptoCreate(SENDER).balance(ONE_MILLION_HBARS).payingWith(PAYER), - cryptoCreate(RECEIVER).balance(ONE_MILLION_HBARS).payingWith(PAYER), - createTopic(TOPIC).submitKeyName(SUBMIT_KEY).payingWith(PAYER), - fileCreate(SOME_BYTE_CODE) - .path(HapiSpecSetup.getDefaultInstance().defaultContractPath()), - inParallel(mixedOpsBurst.get())); + // Run some more transactions + MixedOperations.burstOfTps(MIXED_OPS_BURST_TPS, MIXED_OPS_BURST_DURATION), + // And validate we can still submit transactions to node2 + cryptoCreate("somebody").setNode("0.0.5")); } } diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/regression/system/MixedOpsRestartTest.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/regression/system/MixedOpsRestartTest.java index ea7b6b030ee1..e021ee00d41d 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/regression/system/MixedOpsRestartTest.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/regression/system/MixedOpsRestartTest.java @@ -18,46 +18,18 @@ import static com.hedera.services.bdd.junit.TestTags.RESTART; import static com.hedera.services.bdd.spec.HapiSpec.defaultHapiSpec; -import static com.hedera.services.bdd.spec.transactions.TxnVerbs.createTopic; -import static com.hedera.services.bdd.spec.transactions.TxnVerbs.cryptoCreate; -import static com.hedera.services.bdd.spec.transactions.TxnVerbs.fileCreate; -import static com.hedera.services.bdd.spec.transactions.TxnVerbs.mintToken; import static com.hedera.services.bdd.spec.utilops.UtilVerbs.freezeOnly; -import static com.hedera.services.bdd.spec.utilops.UtilVerbs.inParallel; -import static com.hedera.services.bdd.spec.utilops.UtilVerbs.newKeyNamed; -import static com.hedera.services.bdd.spec.utilops.UtilVerbs.shutDownAllNodes; +import static com.hedera.services.bdd.spec.utilops.UtilVerbs.restartNetwork; +import static com.hedera.services.bdd.spec.utilops.UtilVerbs.shutdownNetworkWithin; import static com.hedera.services.bdd.spec.utilops.UtilVerbs.sleepFor; -import static com.hedera.services.bdd.spec.utilops.UtilVerbs.startAllNodes; -import static com.hedera.services.bdd.spec.utilops.UtilVerbs.waitForNodesToBecomeActive; -import static com.hedera.services.bdd.spec.utilops.UtilVerbs.waitForNodesToFreeze; -import static com.hedera.services.bdd.spec.utilops.UtilVerbs.waitForNodesToShutDown; -import static com.hedera.services.bdd.suites.perf.PerfUtilOps.scheduleOpsEnablement; -import static com.hedera.services.bdd.suites.perf.PerfUtilOps.tokenOpsEnablement; -import static com.hedera.services.bdd.suites.regression.system.MixedOperations.ADMIN_KEY; -import static com.hedera.services.bdd.suites.regression.system.MixedOperations.NFT; -import static com.hedera.services.bdd.suites.regression.system.MixedOperations.PAYER; -import static com.hedera.services.bdd.suites.regression.system.MixedOperations.RECEIVER; -import static com.hedera.services.bdd.suites.regression.system.MixedOperations.SENDER; -import static com.hedera.services.bdd.suites.regression.system.MixedOperations.SOME_BYTE_CODE; -import static com.hedera.services.bdd.suites.regression.system.MixedOperations.SUBMIT_KEY; -import static com.hedera.services.bdd.suites.regression.system.MixedOperations.TOPIC; -import static com.hedera.services.bdd.suites.regression.system.MixedOperations.TREASURY; -import static com.hedera.services.bdd.suites.token.TokenTransactSpecs.SUPPLY_KEY; +import static com.hedera.services.bdd.spec.utilops.UtilVerbs.waitForActiveNetwork; +import static com.hedera.services.bdd.spec.utilops.UtilVerbs.waitForFrozenNetwork; +import static com.hedera.services.bdd.suites.HapiSuite.GENESIS; -import com.google.protobuf.ByteString; import com.hedera.services.bdd.junit.HapiTest; -import com.hedera.services.bdd.junit.HapiTestSuite; -import com.hedera.services.bdd.spec.HapiSpec; -import com.hedera.services.bdd.spec.HapiSpecOperation; -import com.hedera.services.bdd.spec.HapiSpecSetup; -import com.hedera.services.bdd.suites.HapiSuite; -import java.util.List; -import java.util.Random; -import java.util.concurrent.atomic.AtomicInteger; -import java.util.function.Supplier; -import java.util.stream.IntStream; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; +import java.time.Duration; +import java.util.stream.Stream; +import org.junit.jupiter.api.DynamicTest; import org.junit.jupiter.api.Tag; /** @@ -65,85 +37,39 @@ * freezes all nodes, shuts them down, restarts them, and submits the same burst of mixed operations * again. */ -@HapiTestSuite @Tag(RESTART) -public class MixedOpsRestartTest extends HapiSuite { - private static final Logger log = LogManager.getLogger(MixedOpsRestartTest.class); - - private static final int NUM_SUBMISSIONS = 20; - - public static void main(String... args) { - new MixedOpsRestartTest().runSuiteSync(); - } - - @Override - protected Logger getResultsLogger() { - return log; - } - - @Override - public List getSpecsInSuite() { - return List.of(restartMixedOps()); - } +public class MixedOpsRestartTest { + private static final int MIXED_OPS_BURST_TPS = 50; + private static final long PORT_UNBINDING_TIMEOUT_MS = 180_000L; + private static final Duration MIXED_OPS_BURST_DURATION = Duration.ofSeconds(10); + private static final Duration FREEZE_TIMEOUT = Duration.ofSeconds(75); + private static final Duration RESTART_TIMEOUT = Duration.ofSeconds(120); + private static final Duration SHUTDOWN_TIMEOUT = Duration.ofSeconds(60); @HapiTest - final HapiSpec restartMixedOps() { - AtomicInteger tokenId = new AtomicInteger(0); - AtomicInteger scheduleId = new AtomicInteger(0); - AtomicInteger contractId = new AtomicInteger(0); - AtomicInteger nftId = new AtomicInteger(0); - Random r = new Random(38582L); - Supplier mixedOpsBurst = - new MixedOperations(NUM_SUBMISSIONS).mixedOps(tokenId, nftId, scheduleId, contractId, r); + final Stream restartMixedOps() { return defaultHapiSpec("RestartMixedOps") .given( - newKeyNamed(SUBMIT_KEY), - newKeyNamed(SUPPLY_KEY), - newKeyNamed(ADMIN_KEY), - tokenOpsEnablement(), - scheduleOpsEnablement(), - cryptoCreate(PAYER).balance(100 * ONE_MILLION_HBARS), - cryptoCreate(TREASURY).payingWith(PAYER), - cryptoCreate(SENDER).payingWith(PAYER), - cryptoCreate(RECEIVER).payingWith(PAYER), - createTopic(TOPIC).submitKeyName(SUBMIT_KEY).payingWith(PAYER), - fileCreate(SOME_BYTE_CODE) - .path(HapiSpecSetup.getDefaultInstance().defaultContractPath()), - inParallel(mixedOpsBurst.get()), - sleepFor(10000), - inParallel(IntStream.range(0, NUM_SUBMISSIONS) - .mapToObj(ignore -> mintToken( - NFT + nftId.getAndDecrement(), - List.of(ByteString.copyFromUtf8("a"), ByteString.copyFromUtf8("b"))) - .logging()) - .toArray(HapiSpecOperation[]::new))) + // Run some mixed transactions + MixedOperations.burstOfTps(MIXED_OPS_BURST_TPS, MIXED_OPS_BURST_DURATION)) .when( - // freeze nodes - freezeOnly().startingIn(10).payingWith(GENESIS), - // wait for all nodes to be in FREEZE status - waitForNodesToFreeze(75).logged(), - // shut down all nodes, since the platform doesn't automatically go back to ACTIVE status - shutDownAllNodes().logged(), - // wait for all nodes to be shut down - waitForNodesToShutDown(60).logged(), + // Freeze the network + freezeOnly().startingIn(10).seconds().payingWith(GENESIS), + // Wait for all nodes to be in FREEZE status + waitForFrozenNetwork(FREEZE_TIMEOUT), + // Shut down all nodes, since the platform doesn't automatically go back to ACTIVE status + shutdownNetworkWithin(SHUTDOWN_TIMEOUT), // This sleep is needed, since the ports of shutdown nodes may still be in time_wait status, // which will cause an error that address is already in use when restarting nodes. // Sleep long enough (120s or 180 secs for TIME_WAIT status to be finished based on // kernel settings), so restarting nodes succeeds. - sleepFor(180_000L).logged(), - // start all nodes - startAllNodes().logged(), - // wait for all nodes to be ACTIVE - waitForNodesToBecomeActive(100).logged()) + sleepFor(PORT_UNBINDING_TIMEOUT_MS), + // (Re)start all nodes + restartNetwork(), + // Wait for all nodes to be ACTIVE + waitForActiveNetwork(RESTART_TIMEOUT)) .then( // Once nodes come back ACTIVE, submit some operations again - cryptoCreate(PAYER).balance(100 * ONE_MILLION_HBARS), - cryptoCreate(TREASURY).balance(ONE_MILLION_HBARS).payingWith(PAYER), - cryptoCreate(SENDER).balance(ONE_MILLION_HBARS).payingWith(PAYER), - cryptoCreate(RECEIVER).balance(ONE_MILLION_HBARS).payingWith(PAYER), - createTopic(TOPIC).submitKeyName(SUBMIT_KEY).payingWith(PAYER), - fileCreate(SOME_BYTE_CODE) - .path(HapiSpecSetup.getDefaultInstance().defaultContractPath()), - inParallel(mixedOpsBurst.get())); + MixedOperations.burstOfTps(MIXED_OPS_BURST_TPS, MIXED_OPS_BURST_DURATION)); } } diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/regression/system/MixedOpsStateCreation.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/regression/system/MixedOpsStateCreation.java deleted file mode 100644 index 625b75af9cab..000000000000 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/regression/system/MixedOpsStateCreation.java +++ /dev/null @@ -1,154 +0,0 @@ -/* - * Copyright (C) 2023-2024 Hedera Hashgraph, LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.hedera.services.bdd.suites.regression.system; - -import static com.hedera.services.bdd.spec.HapiPropertySource.asHexedSolidityAddress; -import static com.hedera.services.bdd.spec.HapiSpec.defaultHapiSpec; -import static com.hedera.services.bdd.spec.queries.QueryVerbs.contractCallLocal; -import static com.hedera.services.bdd.spec.transactions.TxnVerbs.contractCall; -import static com.hedera.services.bdd.spec.transactions.TxnVerbs.contractCreate; -import static com.hedera.services.bdd.spec.transactions.TxnVerbs.createTopic; -import static com.hedera.services.bdd.spec.transactions.TxnVerbs.cryptoCreate; -import static com.hedera.services.bdd.spec.transactions.TxnVerbs.fileCreate; -import static com.hedera.services.bdd.spec.transactions.TxnVerbs.mintToken; -import static com.hedera.services.bdd.spec.transactions.TxnVerbs.uploadInitCode; -import static com.hedera.services.bdd.spec.transactions.contract.HapiParserUtil.asHeadlongAddress; -import static com.hedera.services.bdd.spec.utilops.UtilVerbs.freezeOnly; -import static com.hedera.services.bdd.spec.utilops.UtilVerbs.inParallel; -import static com.hedera.services.bdd.spec.utilops.UtilVerbs.newKeyNamed; -import static com.hedera.services.bdd.spec.utilops.UtilVerbs.sleepFor; -import static com.hedera.services.bdd.spec.utilops.UtilVerbs.sourcing; -import static com.hedera.services.bdd.spec.utilops.UtilVerbs.waitForNodesToFreeze; -import static com.hedera.services.bdd.suites.perf.PerfUtilOps.scheduleOpsEnablement; -import static com.hedera.services.bdd.suites.perf.PerfUtilOps.tokenOpsEnablement; -import static com.hedera.services.bdd.suites.regression.system.MixedOperations.ADMIN_KEY; -import static com.hedera.services.bdd.suites.regression.system.MixedOperations.NFT; -import static com.hedera.services.bdd.suites.regression.system.MixedOperations.PAYER; -import static com.hedera.services.bdd.suites.regression.system.MixedOperations.RECEIVER; -import static com.hedera.services.bdd.suites.regression.system.MixedOperations.SENDER; -import static com.hedera.services.bdd.suites.regression.system.MixedOperations.SOME_BYTE_CODE; -import static com.hedera.services.bdd.suites.regression.system.MixedOperations.SUBMIT_KEY; -import static com.hedera.services.bdd.suites.regression.system.MixedOperations.TOPIC; -import static com.hedera.services.bdd.suites.regression.system.MixedOperations.TREASURY; -import static com.hedera.services.bdd.suites.token.TokenTransactSpecs.SUPPLY_KEY; - -import com.google.protobuf.ByteString; -import com.hedera.services.bdd.junit.HapiTest; -import com.hedera.services.bdd.spec.HapiSpec; -import com.hedera.services.bdd.spec.HapiSpecOperation; -import com.hedera.services.bdd.spec.HapiSpecSetup; -import com.hedera.services.bdd.suites.HapiSuite; -import java.math.BigInteger; -import java.util.List; -import java.util.Random; -import java.util.concurrent.atomic.AtomicInteger; -import java.util.concurrent.atomic.AtomicReference; -import java.util.function.Supplier; -import java.util.stream.IntStream; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; - -public class MixedOpsStateCreation extends HapiSuite { - private static final Logger log = LogManager.getLogger(MixedOpsStateCreation.class); - - private static final int NUM_SUBMISSIONS = 20; - private static final String KV_CONTRACT = "Create2MultipleCreates"; - private static final String CONTRACT_ADMIN_KEY = "Create2MultipleCreates"; - public static final String GET_BYTECODE = "getBytecode"; - public static final String DEPLOY = "deploy"; - - public static void main(String... args) { - new MixedOpsStateCreation().runSuiteSync(); - } - - @Override - protected Logger getResultsLogger() { - return log; - } - - @Override - public List getSpecsInSuite() { - return List.of(createState()); - } - - @HapiTest - final HapiSpec createState() { - AtomicInteger tokenId = new AtomicInteger(0); - AtomicInteger nftId = new AtomicInteger(0); - AtomicInteger scheduleId = new AtomicInteger(0); - AtomicInteger contractId = new AtomicInteger(0); - final AtomicReference factoryEvmAddress = new AtomicReference<>(); - final AtomicReference testContractInitcode = new AtomicReference<>(); - Random r = new Random(38582L); - Supplier mixedOpsBurst = - new MixedOperations(NUM_SUBMISSIONS).mixedOps(tokenId, nftId, scheduleId, contractId, r); - return defaultHapiSpec("RestartMixedOps") - .given( - newKeyNamed(CONTRACT_ADMIN_KEY), - newKeyNamed(SUBMIT_KEY), - newKeyNamed(SUPPLY_KEY), - newKeyNamed(ADMIN_KEY), - tokenOpsEnablement(), - scheduleOpsEnablement(), - cryptoCreate(PAYER).balance(100 * ONE_MILLION_HBARS), - cryptoCreate(TREASURY).payingWith(PAYER), - cryptoCreate(SENDER).payingWith(PAYER), - cryptoCreate(RECEIVER).payingWith(PAYER), - createTopic(TOPIC).submitKeyName(SUBMIT_KEY).payingWith(PAYER), - fileCreate(SOME_BYTE_CODE) - .path(HapiSpecSetup.getDefaultInstance().defaultContractPath()), - // Create a contract with some KV Pairs - uploadInitCode(KV_CONTRACT), - contractCreate(KV_CONTRACT) - .payingWith(GENESIS) - .adminKey(CONTRACT_ADMIN_KEY) - .entityMemo("Test contract") - .gas(10_000_000L) - .exposingNumTo(num -> factoryEvmAddress.set(asHexedSolidityAddress(0, 0, num))), - sourcing(() -> contractCallLocal( - KV_CONTRACT, - GET_BYTECODE, - asHeadlongAddress(factoryEvmAddress.get()), - BigInteger.valueOf(42)) - .exposingTypedResultsTo(results -> { - final var tcInitcode = (byte[]) results[0]; - testContractInitcode.set(tcInitcode); - }) - .payingWith(GENESIS) - .nodePayment(ONE_HBAR)), - sourcing(() -> contractCall( - KV_CONTRACT, DEPLOY, testContractInitcode.get(), BigInteger.valueOf(42)) - .payingWith(GENESIS) - .gas(10_000_000L) - .sending(1_234L)), - inParallel(mixedOpsBurst.get()), - sleepFor(10000), - inParallel(IntStream.range(0, NUM_SUBMISSIONS) - .mapToObj(ignore -> mintToken( - NFT + nftId.getAndDecrement(), - List.of(ByteString.copyFromUtf8("a"), ByteString.copyFromUtf8("b"))) - .logging()) - .toArray(HapiSpecOperation[]::new))) - .when( - sleepFor(60000), - // freeze nodes - freezeOnly().startingIn(10).payingWith(GENESIS), - // wait for all nodes to be in FREEZE status - waitForNodesToFreeze(75).logged()) - .then(); - } -} diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/schedule/ScheduleCreateSpecs.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/schedule/ScheduleCreateSpecs.java index 617e317be0e3..a40ecd9eaef1 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/schedule/ScheduleCreateSpecs.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/schedule/ScheduleCreateSpecs.java @@ -16,8 +16,10 @@ package com.hedera.services.bdd.suites.schedule; +import static com.hedera.services.bdd.junit.ContextRequirement.PROPERTY_OVERRIDES; import static com.hedera.services.bdd.spec.HapiSpec.customHapiSpec; import static com.hedera.services.bdd.spec.HapiSpec.defaultHapiSpec; +import static com.hedera.services.bdd.spec.HapiSpec.propertyPreservingHapiSpec; import static com.hedera.services.bdd.spec.assertions.AccountInfoAsserts.accountWith; import static com.hedera.services.bdd.spec.keys.ControlForKey.forKey; import static com.hedera.services.bdd.spec.keys.KeyShape.SIMPLE; @@ -50,6 +52,12 @@ import static com.hedera.services.bdd.spec.utilops.UtilVerbs.submitModified; import static com.hedera.services.bdd.spec.utilops.UtilVerbs.withOpContext; import static com.hedera.services.bdd.spec.utilops.mod.ModificationUtils.withSuccessivelyVariedBodyIds; +import static com.hedera.services.bdd.suites.HapiSuite.DEFAULT_PAYER; +import static com.hedera.services.bdd.suites.HapiSuite.FUNDING; +import static com.hedera.services.bdd.suites.HapiSuite.GENESIS; +import static com.hedera.services.bdd.suites.HapiSuite.ONE_HBAR; +import static com.hedera.services.bdd.suites.HapiSuite.ONE_HUNDRED_HBARS; +import static com.hedera.services.bdd.suites.HapiSuite.ZERO_BYTE_MEMO; import static com.hedera.services.bdd.suites.crypto.AutoAccountUpdateSuite.ALIAS; import static com.hedera.services.bdd.suites.crypto.AutoAccountUpdateSuite.INITIAL_BALANCE; import static com.hedera.services.bdd.suites.crypto.AutoCreateUtils.updateSpecFor; @@ -58,7 +66,6 @@ import static com.hedera.services.bdd.suites.schedule.ScheduleUtils.CONTINUE; import static com.hedera.services.bdd.suites.schedule.ScheduleUtils.COPYCAT; import static com.hedera.services.bdd.suites.schedule.ScheduleUtils.CREATION; -import static com.hedera.services.bdd.suites.schedule.ScheduleUtils.DEFAULT_TX_EXPIRY; import static com.hedera.services.bdd.suites.schedule.ScheduleUtils.DESIGNATING_PAYER; import static com.hedera.services.bdd.suites.schedule.ScheduleUtils.ENTITY_MEMO; import static com.hedera.services.bdd.suites.schedule.ScheduleUtils.FIRST_PAYER; @@ -74,8 +81,6 @@ import static com.hedera.services.bdd.suites.schedule.ScheduleUtils.SECOND_PAYER; import static com.hedera.services.bdd.suites.schedule.ScheduleUtils.SENDER; import static com.hedera.services.bdd.suites.schedule.ScheduleUtils.VALID_SCHEDULE; -import static com.hedera.services.bdd.suites.schedule.ScheduleUtils.WHITELIST_DEFAULT; -import static com.hedera.services.bdd.suites.schedule.ScheduleUtils.withAndWithoutLongTermEnabled; import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.ACCOUNT_ID_DOES_NOT_EXIST; import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.BUSY; import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.IDENTICAL_SCHEDULE_ALREADY_CREATED; @@ -90,62 +95,19 @@ import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.UNRESOLVABLE_REQUIRED_SIGNERS; import com.hedera.services.bdd.junit.HapiTest; -import com.hedera.services.bdd.junit.HapiTestSuite; -import com.hedera.services.bdd.spec.HapiSpec; +import com.hedera.services.bdd.junit.LeakyHapiTest; import com.hedera.services.bdd.spec.HapiSpecSetup; import com.hedera.services.bdd.spec.keys.KeyShape; import com.hedera.services.bdd.spec.keys.OverlappingKeyGenerator; import com.hedera.services.bdd.spec.keys.SigControl; -import com.hedera.services.bdd.suites.HapiSuite; import java.security.SecureRandom; -import java.util.List; import java.util.Map; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; - -@HapiTestSuite -public class ScheduleCreateSpecs extends HapiSuite { - private static final Logger log = LogManager.getLogger(ScheduleCreateSpecs.class); - - public static void main(String... args) { - new ScheduleCreateSpecs().runSuiteSync(); - } - - @Override - public List getSpecsInSuite() { - return withAndWithoutLongTermEnabled(() -> List.of( - notIdenticalScheduleIfScheduledTxnChanges(), - notIdenticalScheduleIfAdminKeyChanges(), - notIdenticalScheduleIfMemoChanges(), - recognizesIdenticalScheduleEvenWithDifferentDesignatedPayer(), - rejectsSentinelKeyListAsAdminKey(), - rejectsMalformedScheduledTxnMemo(), - bodyOnlyCreation(), - onlyBodyAndAdminCreation(), - onlyBodyAndMemoCreation(), - bodyAndSignatoriesCreation(), - bodyAndPayerCreation(), - rejectsUnresolvableReqSigners(), - triggersImmediatelyWithBothReqSimpleSigs(), - onlySchedulesWithMissingReqSimpleSigs(), - failsWithNonExistingPayerAccountId(), - failsWithTooLongMemo(), - detectsKeysChangedBetweenExpandSigsAndHandleTxn(), - doesntTriggerUntilPayerSigns(), - requiresExtantPayer(), - rejectsFunctionlessTxn(), - functionlessTxnBusyWithNonExemptPayer(), - whitelistWorks(), - preservesRevocationServiceSemanticsForFileDelete(), - worksAsExpectedWithDefaultScheduleId(), - infoIncludesTxnIdFromCreationReceipt(), - suiteCleanup(), - validateSignersInInfo(), - aliasNotAllowedAsPayer())); - } +import java.util.stream.Stream; +import org.junit.jupiter.api.DynamicTest; +public class ScheduleCreateSpecs { @HapiTest - private HapiSpec aliasNotAllowedAsPayer() { + final Stream aliasNotAllowedAsPayer() { return defaultHapiSpec("BodyAndPayerCreation") .given( newKeyNamed(ALIAS), @@ -181,15 +143,7 @@ private HapiSpec aliasNotAllowedAsPayer() { } @HapiTest - final HapiSpec suiteCleanup() { - return defaultHapiSpec("suiteCleanup") - .given() - .when() - .then(overriding("ledger.schedule.txExpiryTimeSecs", DEFAULT_TX_EXPIRY)); - } - - @HapiTest - final HapiSpec worksAsExpectedWithDefaultScheduleId() { + final Stream worksAsExpectedWithDefaultScheduleId() { return defaultHapiSpec("WorksAsExpectedWithDefaultScheduleId") .given() .when() @@ -197,10 +151,10 @@ final HapiSpec worksAsExpectedWithDefaultScheduleId() { } @HapiTest - final HapiSpec bodyOnlyCreation() { + final Stream bodyOnlyCreation() { return customHapiSpec("bodyOnlyCreation") .withProperties(Map.of("default.keyAlgorithm", "SECP256K1")) - .given(overriding(SCHEDULING_WHITELIST, "CryptoTransfer,CryptoCreate"), cryptoCreate(SENDER)) + .given(cryptoCreate(SENDER)) .when( scheduleCreate(ONLY_BODY, cryptoTransfer(tinyBarsFromTo(SENDER, GENESIS, 1))) .recordingScheduledTxn(), @@ -212,7 +166,7 @@ final HapiSpec bodyOnlyCreation() { } @HapiTest - final HapiSpec validateSignersInInfo() { + final Stream validateSignersInInfo() { return customHapiSpec(VALID_SCHEDULE) .withProperties(Map.of("default.keyAlgorithm", "SECP256K1")) .given(cryptoCreate(SENDER)) @@ -227,7 +181,7 @@ final HapiSpec validateSignersInInfo() { } @HapiTest - final HapiSpec onlyBodyAndAdminCreation() { + final Stream onlyBodyAndAdminCreation() { return defaultHapiSpec("OnlyBodyAndAdminCreation") .given(newKeyNamed(ADMIN), cryptoCreate(SENDER)) .when(scheduleCreate(ONLY_BODY_AND_ADMIN_KEY, cryptoTransfer(tinyBarsFromTo(SENDER, GENESIS, 1))) @@ -240,9 +194,9 @@ final HapiSpec onlyBodyAndAdminCreation() { } @HapiTest - final HapiSpec onlyBodyAndMemoCreation() { + final Stream onlyBodyAndMemoCreation() { return defaultHapiSpec("OnlyBodyAndMemoCreation") - .given(overriding(SCHEDULING_WHITELIST, "CryptoTransfer,CryptoCreate"), cryptoCreate(SENDER)) + .given(cryptoCreate(SENDER)) .when(scheduleCreate(ONLY_BODY_AND_MEMO, cryptoTransfer(tinyBarsFromTo(SENDER, GENESIS, 1))) .recordingScheduledTxn() .withEntityMemo("sample memo")) @@ -253,7 +207,7 @@ final HapiSpec onlyBodyAndMemoCreation() { } @HapiTest - final HapiSpec idVariantsTreatedAsExpected() { + final Stream idVariantsTreatedAsExpected() { return defaultHapiSpec("idVariantsTreatedAsExpected") .given(cryptoCreate(PAYER)) .when() @@ -264,7 +218,7 @@ ONLY_BODY_AND_PAYER, cryptoTransfer(tinyBarsFromTo(PAYER, GENESIS, 1))) } @HapiTest - final HapiSpec bodyAndPayerCreation() { + final Stream bodyAndPayerCreation() { return defaultHapiSpec("BodyAndPayerCreation") .given(cryptoCreate(PAYER)) .when(scheduleCreate( @@ -282,13 +236,12 @@ final HapiSpec bodyAndPayerCreation() { } @HapiTest - final HapiSpec bodyAndSignatoriesCreation() { + final Stream bodyAndSignatoriesCreation() { var scheduleName = "onlyBodyAndSignatories"; var scheduledTxn = cryptoTransfer(tinyBarsFromTo(SENDER, RECEIVER, 1)); return defaultHapiSpec("BodyAndSignatoriesCreation") .given( - overriding(SCHEDULING_WHITELIST, "CryptoTransfer,CryptoCreate"), cryptoCreate("payingAccount"), newKeyNamed("adminKey"), cryptoCreate(SENDER), @@ -305,9 +258,9 @@ final HapiSpec bodyAndSignatoriesCreation() { } @HapiTest - final HapiSpec failsWithNonExistingPayerAccountId() { + final Stream failsWithNonExistingPayerAccountId() { return defaultHapiSpec("FailsWithNonExistingPayerAccountId") - .given(overriding(SCHEDULING_WHITELIST, "CryptoTransfer,CryptoCreate")) + .given() .when(scheduleCreate("invalidPayer", cryptoCreate("secondary")) .designatingPayer(DESIGNATING_PAYER) .hasKnownStatus(ACCOUNT_ID_DOES_NOT_EXIST)) @@ -315,7 +268,7 @@ final HapiSpec failsWithNonExistingPayerAccountId() { } @HapiTest - final HapiSpec failsWithTooLongMemo() { + final Stream failsWithTooLongMemo() { return defaultHapiSpec("FailsWithTooLongMemo") .given() .when(scheduleCreate("invalidMemo", cryptoCreate("secondary")) @@ -325,10 +278,9 @@ final HapiSpec failsWithTooLongMemo() { } @HapiTest - final HapiSpec notIdenticalScheduleIfScheduledTxnChanges() { + final Stream notIdenticalScheduleIfScheduledTxnChanges() { return defaultHapiSpec("NotIdenticalScheduleIfScheduledTxnChanges") .given( - overriding(SCHEDULING_WHITELIST, "CryptoTransfer,CryptoCreate"), cryptoCreate(SENDER).balance(1L), cryptoCreate(FIRST_PAYER), scheduleCreate( @@ -347,10 +299,9 @@ final HapiSpec notIdenticalScheduleIfScheduledTxnChanges() { } @HapiTest - final HapiSpec notIdenticalScheduleIfMemoChanges() { + final Stream notIdenticalScheduleIfMemoChanges() { return defaultHapiSpec("NotIdenticalScheduleIfMemoChanges") .given( - overriding(SCHEDULING_WHITELIST, "CryptoTransfer,CryptoCreate"), cryptoCreate(SENDER).balance(1L), scheduleCreate( ORIGINAL, @@ -366,7 +317,7 @@ final HapiSpec notIdenticalScheduleIfMemoChanges() { } @HapiTest - final HapiSpec notIdenticalScheduleIfAdminKeyChanges() { + final Stream notIdenticalScheduleIfAdminKeyChanges() { return defaultHapiSpec("notIdenticalScheduleIfAdminKeyChanges") .given( newKeyNamed("adminA"), @@ -393,10 +344,9 @@ final HapiSpec notIdenticalScheduleIfAdminKeyChanges() { } @HapiTest - final HapiSpec recognizesIdenticalScheduleEvenWithDifferentDesignatedPayer() { + final Stream recognizesIdenticalScheduleEvenWithDifferentDesignatedPayer() { return defaultHapiSpec("recognizesIdenticalScheduleEvenWithDifferentDesignatedPayer") .given( - overriding(SCHEDULING_WHITELIST, "CryptoTransfer,CryptoCreate"), cryptoCreate(SENDER).balance(1L), cryptoCreate(FIRST_PAYER), scheduleCreate( @@ -421,7 +371,7 @@ final HapiSpec recognizesIdenticalScheduleEvenWithDifferentDesignatedPayer() { } @HapiTest - final HapiSpec rejectsSentinelKeyListAsAdminKey() { + final Stream rejectsSentinelKeyListAsAdminKey() { return defaultHapiSpec("RejectsSentinelKeyListAsAdminKey") .given() .when() @@ -431,7 +381,7 @@ final HapiSpec rejectsSentinelKeyListAsAdminKey() { } @HapiTest - final HapiSpec rejectsMalformedScheduledTxnMemo() { + final Stream rejectsMalformedScheduledTxnMemo() { return defaultHapiSpec("RejectsMalformedScheduledTxnMemo") .given( cryptoCreate("ntb").memo(ZERO_BYTE_MEMO).hasPrecheck(INVALID_ZERO_BYTE_IN_STRING), @@ -451,10 +401,9 @@ final HapiSpec rejectsMalformedScheduledTxnMemo() { } @HapiTest - final HapiSpec infoIncludesTxnIdFromCreationReceipt() { + final Stream infoIncludesTxnIdFromCreationReceipt() { return defaultHapiSpec("InfoIncludesTxnIdFromCreationReceipt") .given( - overriding(SCHEDULING_WHITELIST, "CryptoTransfer,CryptoCreate"), cryptoCreate(SENDER), scheduleCreate(CREATION, cryptoTransfer(tinyBarsFromTo(SENDER, FUNDING, 1))) .savingExpectedScheduledTxnId()) @@ -465,8 +414,8 @@ final HapiSpec infoIncludesTxnIdFromCreationReceipt() { .logged()); } - @HapiTest - final HapiSpec preservesRevocationServiceSemanticsForFileDelete() { + @LeakyHapiTest(PROPERTY_OVERRIDES) + final Stream preservesRevocationServiceSemanticsForFileDelete() { KeyShape waclShape = listOf(SIMPLE, threshOf(2, 3)); SigControl adequateSigs = waclShape.signedWith(sigs(OFF, sigs(ON, ON, OFF))); SigControl inadequateSigs = waclShape.signedWith(sigs(OFF, sigs(ON, OFF, OFF))); @@ -475,7 +424,8 @@ final HapiSpec preservesRevocationServiceSemanticsForFileDelete() { String shouldBeInstaDeleted = "tbd"; String shouldBeDeletedEventually = "tbdl"; - return defaultHapiSpec("PreservesRevocationServiceSemanticsForFileDelete") + return propertyPreservingHapiSpec("PreservesRevocationServiceSemanticsForFileDelete") + .preserving(SCHEDULING_WHITELIST) .given( overriding(SCHEDULING_WHITELIST, "FileDelete"), fileCreate(shouldBeInstaDeleted).waclShape(waclShape), @@ -495,12 +445,11 @@ final HapiSpec preservesRevocationServiceSemanticsForFileDelete() { .alsoSigningWith(shouldBeDeletedEventually) .sigControl(forKey(shouldBeDeletedEventually, compensatorySigs)), sleepFor(1_000L), - getFileInfo(shouldBeDeletedEventually).hasDeleted(true), - overriding(SCHEDULING_WHITELIST, WHITELIST_DEFAULT)); + getFileInfo(shouldBeDeletedEventually).hasDeleted(true)); } @HapiTest - public HapiSpec detectsKeysChangedBetweenExpandSigsAndHandleTxn() { + final Stream detectsKeysChangedBetweenExpandSigsAndHandleTxn() { var keyGen = OverlappingKeyGenerator.withAtLeastOneOverlappingByte(2); String aKey = "a", bKey = "b"; @@ -526,10 +475,9 @@ public HapiSpec detectsKeysChangedBetweenExpandSigsAndHandleTxn() { } @HapiTest - public HapiSpec onlySchedulesWithMissingReqSimpleSigs() { + final Stream onlySchedulesWithMissingReqSimpleSigs() { return defaultHapiSpec("OnlySchedulesWithMissingReqSimpleSigs") .given( - overriding(SCHEDULING_WHITELIST, "CryptoTransfer,CryptoCreate"), cryptoCreate(SENDER).balance(1L), cryptoCreate(RECEIVER).balance(0L).receiverSigRequired(true)) .when(scheduleCreate(BASIC_XFER, cryptoTransfer(tinyBarsFromTo(SENDER, RECEIVER, 1))) @@ -538,7 +486,7 @@ public HapiSpec onlySchedulesWithMissingReqSimpleSigs() { } @HapiTest - public HapiSpec requiresExtantPayer() { + final Stream requiresExtantPayer() { return defaultHapiSpec("RequiresExtantPayer") .given() .when() @@ -549,7 +497,7 @@ NEVER_TO_BE, cryptoCreate("nope").key(GENESIS).receiverSigRequired(true)) } @HapiTest - public HapiSpec doesntTriggerUntilPayerSigns() { + final Stream doesntTriggerUntilPayerSigns() { return defaultHapiSpec("DoesntTriggerUntilPayerSigns") .given( cryptoCreate(PAYER).balance(ONE_HBAR * 5), @@ -577,7 +525,7 @@ public HapiSpec doesntTriggerUntilPayerSigns() { } @HapiTest - public HapiSpec triggersImmediatelyWithBothReqSimpleSigs() { + final Stream triggersImmediatelyWithBothReqSimpleSigs() { long initialBalance = HapiSpecSetup.getDefaultInstance().defaultBalance(); long transferAmount = 1; @@ -598,7 +546,7 @@ public HapiSpec triggersImmediatelyWithBothReqSimpleSigs() { } @HapiTest - public HapiSpec rejectsUnresolvableReqSigners() { + final Stream rejectsUnresolvableReqSigners() { return defaultHapiSpec("RejectsUnresolvableReqSigners") .given() .when() @@ -611,7 +559,7 @@ public HapiSpec rejectsUnresolvableReqSigners() { } @HapiTest - public HapiSpec rejectsFunctionlessTxn() { + final Stream rejectsFunctionlessTxn() { return defaultHapiSpec("RejectsFunctionlessTxn") .given() .when() @@ -621,7 +569,7 @@ public HapiSpec rejectsFunctionlessTxn() { } @HapiTest - public HapiSpec functionlessTxnBusyWithNonExemptPayer() { + final Stream functionlessTxnBusyWithNonExemptPayer() { return defaultHapiSpec("FunctionlessTxnBusyWithNonExemptPayer") .given() .when() @@ -630,21 +578,15 @@ public HapiSpec functionlessTxnBusyWithNonExemptPayer() { scheduleCreateFunctionless("unknown").hasPrecheck(BUSY).payingWith(SENDER)); } - @HapiTest - public HapiSpec whitelistWorks() { - return defaultHapiSpec("whitelistWorks") + @LeakyHapiTest(PROPERTY_OVERRIDES) + final Stream whitelistWorks() { + return propertyPreservingHapiSpec("whitelistWorks") + .preserving(SCHEDULING_WHITELIST) .given(scheduleCreate("nope", createTopic(NEVER_TO_BE)) .hasKnownStatus(SCHEDULED_TRANSACTION_NOT_IN_WHITELIST)) - .when( - overriding(SCHEDULING_WHITELIST, "ConsensusCreateTopic"), - scheduleCreate("ok", createTopic(NEVER_TO_BE)) - // prevent multiple runs of this test causing duplicates - .withEntityMemo("" + new SecureRandom().nextLong())) - .then(overriding(SCHEDULING_WHITELIST, WHITELIST_DEFAULT)); - } - - @Override - protected Logger getResultsLogger() { - return log; + .when(overriding(SCHEDULING_WHITELIST, "ConsensusCreateTopic")) + .then(scheduleCreate("ok", createTopic(NEVER_TO_BE)) + // prevent multiple runs of this test causing duplicates + .withEntityMemo("" + new SecureRandom().nextLong())); } } diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/schedule/ScheduleDeleteSpecs.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/schedule/ScheduleDeleteSpecs.java index 0b1bad9d000f..560bdaabbc4f 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/schedule/ScheduleDeleteSpecs.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/schedule/ScheduleDeleteSpecs.java @@ -26,17 +26,17 @@ import static com.hedera.services.bdd.spec.transactions.TxnVerbs.submitMessageTo; import static com.hedera.services.bdd.spec.transactions.crypto.HapiCryptoTransfer.tinyBarsFromTo; import static com.hedera.services.bdd.spec.utilops.UtilVerbs.newKeyNamed; -import static com.hedera.services.bdd.spec.utilops.UtilVerbs.overriding; import static com.hedera.services.bdd.spec.utilops.UtilVerbs.sendModified; import static com.hedera.services.bdd.spec.utilops.UtilVerbs.submitModified; import static com.hedera.services.bdd.spec.utilops.mod.ModificationUtils.withSuccessivelyVariedBodyIds; import static com.hedera.services.bdd.spec.utilops.mod.ModificationUtils.withSuccessivelyVariedQueryIds; +import static com.hedera.services.bdd.suites.HapiSuite.DEFAULT_PAYER; +import static com.hedera.services.bdd.suites.HapiSuite.FUNDING; +import static com.hedera.services.bdd.suites.HapiSuite.ONE_HBAR; import static com.hedera.services.bdd.suites.schedule.ScheduleUtils.ADMIN; import static com.hedera.services.bdd.suites.schedule.ScheduleUtils.RECEIVER; -import static com.hedera.services.bdd.suites.schedule.ScheduleUtils.SCHEDULING_WHITELIST; import static com.hedera.services.bdd.suites.schedule.ScheduleUtils.SENDER; import static com.hedera.services.bdd.suites.schedule.ScheduleUtils.VALID_SCHEDULED_TXN; -import static com.hedera.services.bdd.suites.schedule.ScheduleUtils.withAndWithoutLongTermEnabled; import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.INVALID_SCHEDULE_ID; import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.INVALID_SIGNATURE; import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.SCHEDULE_ALREADY_DELETED; @@ -44,41 +44,14 @@ import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.SCHEDULE_IS_IMMUTABLE; import com.hedera.services.bdd.junit.HapiTest; -import com.hedera.services.bdd.junit.HapiTestSuite; -import com.hedera.services.bdd.spec.HapiSpec; -import com.hedera.services.bdd.suites.HapiSuite; -import java.util.List; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; - -@HapiTestSuite -public class ScheduleDeleteSpecs extends HapiSuite { - private static final Logger log = LogManager.getLogger(ScheduleDeleteSpecs.class); - - public static void main(String... args) { - new ScheduleDeleteSpecs().runSuiteAsync(); - } - - @Override - protected Logger getResultsLogger() { - return log; - } - - @Override - public List getSpecsInSuite() { - return withAndWithoutLongTermEnabled(() -> List.of( - deleteWithNoAdminKeyFails(), - unauthorizedDeletionFails(), - deletingAlreadyDeletedIsObvious(), - deletingNonExistingFails(), - deletingExecutedIsPointless())); - } +import java.util.stream.Stream; +import org.junit.jupiter.api.DynamicTest; +public class ScheduleDeleteSpecs { @HapiTest - final HapiSpec deleteWithNoAdminKeyFails() { + final Stream deleteWithNoAdminKeyFails() { return defaultHapiSpec("DeleteWithNoAdminKeyFails") .given( - overriding(SCHEDULING_WHITELIST, "CryptoTransfer,CryptoCreate"), cryptoCreate(SENDER), cryptoCreate(RECEIVER), scheduleCreate(VALID_SCHEDULED_TXN, cryptoTransfer(tinyBarsFromTo(SENDER, RECEIVER, 1)))) @@ -87,10 +60,9 @@ final HapiSpec deleteWithNoAdminKeyFails() { } @HapiTest - final HapiSpec unauthorizedDeletionFails() { + final Stream unauthorizedDeletionFails() { return defaultHapiSpec("UnauthorizedDeletionFails") .given( - overriding(SCHEDULING_WHITELIST, "CryptoTransfer,CryptoCreate"), newKeyNamed(ADMIN), newKeyNamed("non-admin-key"), cryptoCreate(SENDER), @@ -104,10 +76,9 @@ final HapiSpec unauthorizedDeletionFails() { } @HapiTest - final HapiSpec deletingAlreadyDeletedIsObvious() { + final Stream deletingAlreadyDeletedIsObvious() { return defaultHapiSpec("DeletingAlreadyDeletedIsObvious") .given( - overriding(SCHEDULING_WHITELIST, "CryptoTransfer,CryptoCreate"), cryptoCreate(SENDER), cryptoCreate(RECEIVER), newKeyNamed(ADMIN), @@ -122,7 +93,7 @@ final HapiSpec deletingAlreadyDeletedIsObvious() { } @HapiTest - final HapiSpec idVariantsTreatedAsExpected() { + final Stream idVariantsTreatedAsExpected() { return defaultHapiSpec("idVariantsTreatedAsExpected") .given( newKeyNamed(ADMIN), @@ -135,7 +106,7 @@ final HapiSpec idVariantsTreatedAsExpected() { } @HapiTest - public HapiSpec getScheduleInfoIdVariantsTreatedAsExpected() { + final Stream getScheduleInfoIdVariantsTreatedAsExpected() { return defaultHapiSpec("getScheduleInfoIdVariantsTreatedAsExpected") .given( newKeyNamed(ADMIN), @@ -147,7 +118,7 @@ public HapiSpec getScheduleInfoIdVariantsTreatedAsExpected() { } @HapiTest - final HapiSpec deletingNonExistingFails() { + final Stream deletingNonExistingFails() { return defaultHapiSpec("DeletingNonExistingFails") .given() .when() @@ -157,10 +128,9 @@ final HapiSpec deletingNonExistingFails() { } @HapiTest - final HapiSpec deletingExecutedIsPointless() { + final Stream deletingExecutedIsPointless() { return defaultHapiSpec("DeletingExecutedIsPointless") .given( - overriding(SCHEDULING_WHITELIST, "CryptoTransfer,CryptoCreate,ConsensusSubmitMessage"), createTopic("ofGreatInterest"), newKeyNamed(ADMIN), scheduleCreate(VALID_SCHEDULED_TXN, submitMessageTo("ofGreatInterest")) diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/schedule/ScheduleExecutionSpecStateful.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/schedule/ScheduleExecutionSpecStateful.java index 64d208975c87..f13d02192732 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/schedule/ScheduleExecutionSpecStateful.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/schedule/ScheduleExecutionSpecStateful.java @@ -37,6 +37,8 @@ import static com.hedera.services.bdd.spec.utilops.UtilVerbs.newKeyNamed; import static com.hedera.services.bdd.spec.utilops.UtilVerbs.overriding; import static com.hedera.services.bdd.spec.utilops.UtilVerbs.withOpContext; +import static com.hedera.services.bdd.suites.HapiSuite.ADDRESS_BOOK_CONTROL; +import static com.hedera.services.bdd.suites.HapiSuite.APP_PROPERTIES; import static com.hedera.services.bdd.suites.schedule.ScheduleUtils.A_TOKEN; import static com.hedera.services.bdd.suites.schedule.ScheduleUtils.DEFAULT_MAX_TOKEN_TRANSFER_LEN; import static com.hedera.services.bdd.suites.schedule.ScheduleUtils.DEFAULT_MAX_TRANSFER_LEN; @@ -56,7 +58,6 @@ import static com.hedera.services.bdd.suites.schedule.ScheduleUtils.VALID_SCHEDULE; import static com.hedera.services.bdd.suites.schedule.ScheduleUtils.WHITELIST_DEFAULT; import static com.hedera.services.bdd.suites.schedule.ScheduleUtils.addAllToWhitelist; -import static com.hedera.services.bdd.suites.schedule.ScheduleUtils.withAndWithoutLongTermEnabled; import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.NOT_SUPPORTED; import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.SUCCESS; import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.TOKEN_TRANSFER_LIST_SIZE_LIMIT_EXCEEDED; @@ -65,54 +66,31 @@ import com.google.protobuf.ByteString; import com.hedera.services.bdd.junit.HapiTest; -import com.hedera.services.bdd.junit.HapiTestSuite; -import com.hedera.services.bdd.spec.HapiSpec; -import com.hedera.services.bdd.suites.HapiSuite; import com.hederahashgraph.api.proto.java.TokenType; import java.util.List; import java.util.Map; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; +import java.util.stream.Stream; import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.DynamicTest; import org.junit.jupiter.api.MethodOrderer; import org.junit.jupiter.api.Order; import org.junit.jupiter.api.TestMethodOrder; -@HapiTestSuite @TestMethodOrder(MethodOrderer.OrderAnnotation.class) -public class ScheduleExecutionSpecStateful extends HapiSuite { - private static final Logger log = LogManager.getLogger(ScheduleExecutionSpecStateful.class); - +public class ScheduleExecutionSpecStateful { private static final int TMP_MAX_TRANSFER_LENGTH = 2; private static final int TMP_MAX_TOKEN_TRANSFER_LENGTH = 2; - public static void main(String... args) { - new ScheduleExecutionSpecStateful().runSuiteSync(); - } - - @Override - public List getSpecsInSuite() { - return withAndWithoutLongTermEnabled(() -> List.of( - /* Stateful specs from ScheduleExecutionSpecs */ - suiteSetup(), - scheduledUniqueMintFailsWithNftsDisabled(), - scheduledUniqueBurnFailsWithNftsDisabled(), - scheduledBurnWithInvalidTokenThrowsUnresolvableSigners(), - executionWithTransferListWrongSizedFails(), - executionWithTokenTransferListSizeExceedFails(), - suiteCleanup())); - } - @HapiTest @Order(1) - final HapiSpec suiteSetup() { + final Stream suiteSetup() { // Managing whitelist for these is error-prone, so just whitelist everything by default. return defaultHapiSpec("suiteSetup").given().when().then(addAllToWhitelist()); } @HapiTest @Order(4) - final HapiSpec scheduledBurnWithInvalidTokenThrowsUnresolvableSigners() { + final Stream scheduledBurnWithInvalidTokenThrowsUnresolvableSigners() { return defaultHapiSpec("ScheduledBurnWithInvalidTokenThrowsUnresolvableSigners") .given(cryptoCreate(SCHEDULE_PAYER)) .when(scheduleCreate(VALID_SCHEDULE, burnToken("0.0.123231", List.of(1L, 2L))) @@ -123,7 +101,7 @@ final HapiSpec scheduledBurnWithInvalidTokenThrowsUnresolvableSigners() { @HapiTest @Order(2) - final HapiSpec scheduledUniqueMintFailsWithNftsDisabled() { + final Stream scheduledUniqueMintFailsWithNftsDisabled() { return defaultHapiSpec("ScheduledUniqueMintFailsWithNftsDisabled") .given( cryptoCreate(TREASURY), @@ -155,7 +133,7 @@ final HapiSpec scheduledUniqueMintFailsWithNftsDisabled() { @HapiTest @Order(3) - final HapiSpec scheduledUniqueBurnFailsWithNftsDisabled() { + final Stream scheduledUniqueBurnFailsWithNftsDisabled() { return defaultHapiSpec("ScheduledUniqueBurnFailsWithNftsDisabled") .given( cryptoCreate(TREASURY), @@ -187,7 +165,7 @@ final HapiSpec scheduledUniqueBurnFailsWithNftsDisabled() { @HapiTest @Order(5) - public HapiSpec executionWithTransferListWrongSizedFails() { + final Stream executionWithTransferListWrongSizedFails() { long transferAmount = 1L; long senderBalance = 1000L; long payingAccountBalance = 1_000_000L; @@ -235,7 +213,7 @@ public HapiSpec executionWithTransferListWrongSizedFails() { @HapiTest @Order(6) - final HapiSpec executionWithTokenTransferListSizeExceedFails() { + final Stream executionWithTokenTransferListSizeExceedFails() { String xToken = "XXX"; String invalidSchedule = "withMaxTokenTransfer"; String schedulePayer = "somebody", xTreasury = "xt", civilianA = "xa", civilianB = "xb"; @@ -272,7 +250,7 @@ final HapiSpec executionWithTokenTransferListSizeExceedFails() { @HapiTest @Order(7) - final HapiSpec suiteCleanup() { + final Stream suiteCleanup() { return defaultHapiSpec("suiteCleanup") .given() .when() @@ -283,9 +261,4 @@ final HapiSpec suiteCleanup() { .payingWith(ADDRESS_BOOK_CONTROL) .overridingProps(Map.of(TOKENS_NFTS_ARE_ENABLED, "true"))); } - - @Override - protected Logger getResultsLogger() { - return log; - } } diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/schedule/ScheduleExecutionSpecs.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/schedule/ScheduleExecutionSpecs.java index 30b1527f22f5..8e8840834a1d 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/schedule/ScheduleExecutionSpecs.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/schedule/ScheduleExecutionSpecs.java @@ -71,6 +71,20 @@ import static com.hedera.services.bdd.spec.utilops.UtilVerbs.sourcing; import static com.hedera.services.bdd.spec.utilops.UtilVerbs.usableTxnIdNamed; import static com.hedera.services.bdd.spec.utilops.UtilVerbs.withOpContext; +import static com.hedera.services.bdd.suites.HapiSuite.ADDRESS_BOOK_CONTROL; +import static com.hedera.services.bdd.suites.HapiSuite.APP_PROPERTIES; +import static com.hedera.services.bdd.suites.HapiSuite.DEFAULT_PAYER; +import static com.hedera.services.bdd.suites.HapiSuite.EXCHANGE_RATE_CONTROL; +import static com.hedera.services.bdd.suites.HapiSuite.FREEZE_ADMIN; +import static com.hedera.services.bdd.suites.HapiSuite.FUNDING; +import static com.hedera.services.bdd.suites.HapiSuite.GENESIS; +import static com.hedera.services.bdd.suites.HapiSuite.ONE_HBAR; +import static com.hedera.services.bdd.suites.HapiSuite.ONE_HUNDRED_HBARS; +import static com.hedera.services.bdd.suites.HapiSuite.ONE_MILLION_HBARS; +import static com.hedera.services.bdd.suites.HapiSuite.SOFTWARE_UPDATE_ADMIN; +import static com.hedera.services.bdd.suites.HapiSuite.SYSTEM_DELETE_ADMIN; +import static com.hedera.services.bdd.suites.HapiSuite.THREE_MONTHS_IN_SECONDS; +import static com.hedera.services.bdd.suites.HapiSuite.THROTTLE_DEFS; import static com.hedera.services.bdd.suites.freeze.UpgradeSuite.poeticUpgradeLoc; import static com.hedera.services.bdd.suites.freeze.UpgradeSuite.standardUpdateFile; import static com.hedera.services.bdd.suites.schedule.ScheduleUtils.ACCOUNT; @@ -118,7 +132,6 @@ import static com.hedera.services.bdd.suites.schedule.ScheduleUtils.getPoeticUpgradeHash; import static com.hedera.services.bdd.suites.schedule.ScheduleUtils.scheduledVersionOf; import static com.hedera.services.bdd.suites.schedule.ScheduleUtils.transferListCheck; -import static com.hedera.services.bdd.suites.schedule.ScheduleUtils.withAndWithoutLongTermEnabled; import static com.hedera.services.bdd.suites.utils.sysfiles.serdes.ThrottleDefsLoader.protoDefsFromResource; import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.ACCOUNT_DELETED; import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.ACCOUNT_FROZEN_FOR_TOKEN; @@ -156,13 +169,9 @@ import com.google.protobuf.ByteString; import com.hedera.node.config.data.ConsensusConfig; import com.hedera.services.bdd.junit.HapiTest; -import com.hedera.services.bdd.junit.HapiTestSuite; -import com.hedera.services.bdd.spec.HapiSpec; import com.hedera.services.bdd.spec.HapiSpecOperation; import com.hedera.services.bdd.spec.HapiSpecSetup; import com.hedera.services.bdd.spec.transactions.crypto.HapiCryptoTransfer; -import com.hedera.services.bdd.suites.BddMethodIsNotATest; -import com.hedera.services.bdd.suites.HapiSuite; import com.hederahashgraph.api.proto.java.AccountID; import com.hederahashgraph.api.proto.java.TokenType; import com.hederahashgraph.api.proto.java.TransactionID; @@ -175,16 +184,17 @@ import java.util.concurrent.atomic.AtomicLong; import java.util.concurrent.atomic.AtomicReference; import java.util.stream.IntStream; +import java.util.stream.Stream; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.DynamicTest; import org.junit.jupiter.api.MethodOrderer; import org.junit.jupiter.api.Order; import org.junit.jupiter.api.TestMethodOrder; -@HapiTestSuite @TestMethodOrder(MethodOrderer.OrderAnnotation.class) -public class ScheduleExecutionSpecs extends HapiSuite { +public class ScheduleExecutionSpecs { private static final Logger log = LogManager.getLogger(ScheduleExecutionSpecs.class); /** @@ -199,83 +209,16 @@ public class ScheduleExecutionSpecs extends HapiSuite { @SuppressWarnings("java:S2245") // using java.util.Random in tests is fine private final Random r = new Random(882654L); - public static void main(String... args) { - new ScheduleExecutionSpecs().runSuiteAsync(); - } - - @Override - protected Logger getResultsLogger() { - return log; - } - - @Override - public List getSpecsInSuite() { - return withAndWithoutLongTermEnabled(isLongTermEnabled -> List.of( - suiteSetup(), - // Note: Order matters here, e2e tests may fail if reordered. - // There are stateful assumptions throughout these tests. - executionTriggersOnceTopicHasSatisfiedSubmitKey(), - executionTriggersWithWeirdlyRepeatedKey(), - executionWithCryptoInsufficientAccountBalanceFails(), - executionWithCryptoSenderDeletedFails(), - executionWithCustomPayerAndAdminKeyWorks(), - executionWithCustomPayerButAccountDeletedFails(), - executionWithCustomPayerButNoFundsFails(), - executionWithCustomPayerWhoSignsAtCreationAsPayerWorks(), - executionWithCustomPayerWorks(), - executionWithCustomPayerWorksWithLastSigBeingCustomPayer(), - executionWithDefaultPayerButAccountDeletedFails(), - executionWithDefaultPayerButNoFundsFails(), - executionWithDefaultPayerWorks(), - executionWithInvalidAccountAmountsFails(), - executionWithTokenInsufficientAccountBalanceFails(), - scheduledBurnExecutesProperly(), - scheduledBurnFailsWithInvalidTxBody(), - scheduledBurnForUniqueFailsWithInvalidAmount(), - scheduledBurnForUniqueSucceedsWithExistingAmount(), - scheduledFreezeWithUnauthorizedPayerFails(isLongTermEnabled), - scheduledFreezeWorksAsExpected(), - scheduledMintExecutesProperly(), - scheduledMintFailsWithInvalidAmount(), - scheduledMintFailsWithInvalidTxBody(), - scheduledMintWithInvalidTokenThrowsUnresolvableSigners(), - scheduledPermissionedFileUpdateUnauthorizedPayerFails(), - scheduledPermissionedFileUpdateWorksAsExpected(), - scheduledSubmitFailedWithInvalidChunkNumberStillPaysServiceFeeButHasNoImpact(), - scheduledSubmitFailedWithInvalidChunkTxnIdStillPaysServiceFeeButHasNoImpact(), - scheduledSubmitFailedWithMsgSizeTooLargeStillPaysServiceFeeButHasNoImpact(), - scheduledSubmitThatWouldFailWithInvalidTopicIdCannotBeScheduled(), - scheduledSubmitThatWouldFailWithTopicDeletedCannotBeSigned(), - scheduledSystemDeleteUnauthorizedPayerFails(isLongTermEnabled), - scheduledSystemDeleteWorksAsExpected(), - scheduledUniqueBurnExecutesProperly(), - scheduledUniqueBurnFailsWithInvalidBatchSize(), - scheduledUniqueBurnFailsWithInvalidNftId(), - scheduledUniqueMintExecutesProperly(), - scheduledUniqueMintFailsWithInvalidBatchSize(), - scheduledUniqueMintFailsWithInvalidMetadata(), - scheduledXferFailingWithDeletedAccountPaysServiceFeeButNoImpact(), - scheduledXferFailingWithDeletedTokenPaysServiceFeeButNoImpact(), - scheduledXferFailingWithEmptyTokenTransferAccountAmountsPaysServiceFeeButNoImpact(), - scheduledXferFailingWithFrozenAccountTransferPaysServiceFeeButNoImpact(), - scheduledXferFailingWithNonKycedAccountTransferPaysServiceFeeButNoImpact(), - scheduledXferFailingWithNonNetZeroTokenTransferPaysServiceFeeButNoImpact(), - scheduledXferFailingWithRepeatedTokenIdPaysServiceFeeButNoImpact(), - scheduledXferFailingWithUnassociatedAccountTransferPaysServiceFeeButNoImpact(), - // congestionPricingAffectsImmediateScheduleExecution(), - suiteCleanup())); - } - @HapiTest @Order(1) - final HapiSpec suiteSetup() { + final Stream suiteSetup() { // Managing whitelist for these is error-prone, so just whitelist everything by default. return defaultHapiSpec("suiteSetup").given().when().then(addAllToWhitelist()); } @HapiTest @Order(18) - final HapiSpec scheduledBurnFailsWithInvalidTxBody() { + final Stream scheduledBurnFailsWithInvalidTxBody() { return defaultHapiSpec("ScheduledBurnFailsWithInvalidTxBody") .given( overriding(SCHEDULING_WHITELIST, WHITELIST_MINIMUM), @@ -304,7 +247,7 @@ final HapiSpec scheduledBurnFailsWithInvalidTxBody() { @HapiTest @Order(23) - final HapiSpec scheduledMintFailsWithInvalidTxBody() { + final Stream scheduledMintFailsWithInvalidTxBody() { return defaultHapiSpec("ScheduledMintFailsWithInvalidTxBody") .given( overriding(SCHEDULING_WHITELIST, WHITELIST_MINIMUM), @@ -339,7 +282,7 @@ final HapiSpec scheduledMintFailsWithInvalidTxBody() { @HapiTest @Order(24) - final HapiSpec scheduledMintWithInvalidTokenThrowsUnresolvableSigners() { + final Stream scheduledMintWithInvalidTokenThrowsUnresolvableSigners() { return defaultHapiSpec("ScheduledMintWithInvalidTokenThrowsUnresolvableSigners") .given(overriding(SCHEDULING_WHITELIST, WHITELIST_MINIMUM), cryptoCreate(SCHEDULE_PAYER)) .when(scheduleCreate( @@ -353,7 +296,7 @@ final HapiSpec scheduledMintWithInvalidTokenThrowsUnresolvableSigners() { @HapiTest @Order(35) - final HapiSpec scheduledUniqueBurnFailsWithInvalidBatchSize() { + final Stream scheduledUniqueBurnFailsWithInvalidBatchSize() { return defaultHapiSpec("ScheduledUniqueBurnFailsWithInvalidBatchSize") .given( overriding(SCHEDULING_WHITELIST, WHITELIST_MINIMUM), @@ -387,7 +330,7 @@ final HapiSpec scheduledUniqueBurnFailsWithInvalidBatchSize() { @HapiTest @Order(34) - final HapiSpec scheduledUniqueBurnExecutesProperly() { + final Stream scheduledUniqueBurnExecutesProperly() { return defaultHapiSpec("ScheduledUniqueBurnExecutesProperly") .given( overriding(SCHEDULING_WHITELIST, WHITELIST_MINIMUM), @@ -464,7 +407,7 @@ final HapiSpec scheduledUniqueBurnExecutesProperly() { @HapiTest @Order(39) - final HapiSpec scheduledUniqueMintFailsWithInvalidMetadata() { + final Stream scheduledUniqueMintFailsWithInvalidMetadata() { return defaultHapiSpec("ScheduledUniqueMintFailsWithInvalidMetadata") .given( overriding(SCHEDULING_WHITELIST, WHITELIST_MINIMUM), @@ -493,7 +436,7 @@ final HapiSpec scheduledUniqueMintFailsWithInvalidMetadata() { @HapiTest @Order(36) - final HapiSpec scheduledUniqueBurnFailsWithInvalidNftId() { + final Stream scheduledUniqueBurnFailsWithInvalidNftId() { return defaultHapiSpec("ScheduledUniqueBurnFailsWithInvalidNftId") .given( overriding(SCHEDULING_WHITELIST, WHITELIST_MINIMUM), @@ -518,7 +461,7 @@ final HapiSpec scheduledUniqueBurnFailsWithInvalidNftId() { @HapiTest @Order(20) - final HapiSpec scheduledBurnForUniqueSucceedsWithExistingAmount() { + final Stream scheduledBurnForUniqueSucceedsWithExistingAmount() { return defaultHapiSpec("scheduledBurnForUniqueSucceedsWithExistingAmount") .given( overriding(SCHEDULING_WHITELIST, WHITELIST_MINIMUM), @@ -545,7 +488,7 @@ final HapiSpec scheduledBurnForUniqueSucceedsWithExistingAmount() { @HapiTest @Order(19) - final HapiSpec scheduledBurnForUniqueFailsWithInvalidAmount() { + final Stream scheduledBurnForUniqueFailsWithInvalidAmount() { return defaultHapiSpec("ScheduledBurnForUniqueFailsWithInvalidAmount") .given( overriding(SCHEDULING_WHITELIST, WHITELIST_MINIMUM), @@ -586,7 +529,7 @@ private byte[] genRandomBytes(int numBytes) { @HapiTest @Order(38) - final HapiSpec scheduledUniqueMintFailsWithInvalidBatchSize() { + final Stream scheduledUniqueMintFailsWithInvalidBatchSize() { return defaultHapiSpec("ScheduledUniqueMintFailsWithInvalidBatchSize") .given( overriding(TOKENS_NFTS_MAX_BATCH_SIZE_MINT, "5"), @@ -625,7 +568,7 @@ final HapiSpec scheduledUniqueMintFailsWithInvalidBatchSize() { @HapiTest @Order(22) - final HapiSpec scheduledMintFailsWithInvalidAmount() { + final Stream scheduledMintFailsWithInvalidAmount() { final var zeroAmountTxn = "zeroAmountTxn"; return defaultHapiSpec("ScheduledMintFailsWithInvalidAmount") .given( @@ -659,7 +602,7 @@ final HapiSpec scheduledMintFailsWithInvalidAmount() { @HapiTest @Order(37) - final HapiSpec scheduledUniqueMintExecutesProperly() { + final Stream scheduledUniqueMintExecutesProperly() { return defaultHapiSpec("ScheduledUniqueMintExecutesProperly") .given( overriding(SCHEDULING_WHITELIST, WHITELIST_MINIMUM), @@ -739,7 +682,7 @@ final HapiSpec scheduledUniqueMintExecutesProperly() { @HapiTest @Order(21) - final HapiSpec scheduledMintExecutesProperly() { + final Stream scheduledMintExecutesProperly() { return defaultHapiSpec("ScheduledMintExecutesProperly") .given( overriding(SCHEDULING_WHITELIST, WHITELIST_MINIMUM), @@ -812,7 +755,7 @@ final HapiSpec scheduledMintExecutesProperly() { @HapiTest @Order(17) - final HapiSpec scheduledBurnExecutesProperly() { + final Stream scheduledBurnExecutesProperly() { return defaultHapiSpec("ScheduledBurnExecutesProperly") .given( overriding(SCHEDULING_WHITELIST, WHITELIST_MINIMUM), @@ -886,7 +829,7 @@ final HapiSpec scheduledBurnExecutesProperly() { @HapiTest @Order(40) - final HapiSpec scheduledXferFailingWithDeletedAccountPaysServiceFeeButNoImpact() { + final Stream scheduledXferFailingWithDeletedAccountPaysServiceFeeButNoImpact() { final String xToken = "XXX"; final String validSchedule = "withLiveAccount"; final String invalidSchedule = "withDeletedAccount"; @@ -938,7 +881,7 @@ final HapiSpec scheduledXferFailingWithDeletedAccountPaysServiceFeeButNoImpact() @HapiTest @Order(41) - final HapiSpec scheduledXferFailingWithDeletedTokenPaysServiceFeeButNoImpact() { + final Stream scheduledXferFailingWithDeletedTokenPaysServiceFeeButNoImpact() { String xToken = "XXX"; String validSchedule = "withLiveToken"; String invalidSchedule = "withDeletedToken"; @@ -993,7 +936,7 @@ final HapiSpec scheduledXferFailingWithDeletedTokenPaysServiceFeeButNoImpact() { @HapiTest @Order(43) - final HapiSpec scheduledXferFailingWithFrozenAccountTransferPaysServiceFeeButNoImpact() { + final Stream scheduledXferFailingWithFrozenAccountTransferPaysServiceFeeButNoImpact() { String xToken = "XXX"; String validSchedule = "withUnfrozenAccount"; String invalidSchedule = "withFrozenAccount"; @@ -1050,7 +993,7 @@ final HapiSpec scheduledXferFailingWithFrozenAccountTransferPaysServiceFeeButNoI @HapiTest @Order(44) - final HapiSpec scheduledXferFailingWithNonKycedAccountTransferPaysServiceFeeButNoImpact() { + final Stream scheduledXferFailingWithNonKycedAccountTransferPaysServiceFeeButNoImpact() { String xToken = "XXX"; String validSchedule = "withKycedToken"; String invalidSchedule = "withNonKycedToken"; @@ -1106,7 +1049,7 @@ final HapiSpec scheduledXferFailingWithNonKycedAccountTransferPaysServiceFeeButN @HapiTest @Order(47) - final HapiSpec scheduledXferFailingWithUnassociatedAccountTransferPaysServiceFeeButNoImpact() { + final Stream scheduledXferFailingWithUnassociatedAccountTransferPaysServiceFeeButNoImpact() { String xToken = "XXX"; String validSchedule = "withAssociatedToken"; String invalidSchedule = "withUnassociatedToken"; @@ -1157,7 +1100,7 @@ final HapiSpec scheduledXferFailingWithUnassociatedAccountTransferPaysServiceFee @HapiTest @Order(45) - final HapiSpec scheduledXferFailingWithNonNetZeroTokenTransferPaysServiceFeeButNoImpact() { + final Stream scheduledXferFailingWithNonNetZeroTokenTransferPaysServiceFeeButNoImpact() { String xToken = "XXX"; String validSchedule = "withZeroNetTokenChange"; String invalidSchedule = "withNonZeroNetTokenChange"; @@ -1215,7 +1158,7 @@ final HapiSpec scheduledXferFailingWithNonNetZeroTokenTransferPaysServiceFeeButN @HapiTest @Order(46) - final HapiSpec scheduledXferFailingWithRepeatedTokenIdPaysServiceFeeButNoImpact() { + final Stream scheduledXferFailingWithRepeatedTokenIdPaysServiceFeeButNoImpact() { String xToken = "XXX"; String yToken = "YYY"; String validSchedule = "withNoRepeats"; @@ -1282,7 +1225,7 @@ final HapiSpec scheduledXferFailingWithRepeatedTokenIdPaysServiceFeeButNoImpact( @HapiTest @Order(42) - final HapiSpec scheduledXferFailingWithEmptyTokenTransferAccountAmountsPaysServiceFeeButNoImpact() { + final Stream scheduledXferFailingWithEmptyTokenTransferAccountAmountsPaysServiceFeeButNoImpact() { String xToken = "XXX"; String yToken = "YYY"; String validSchedule = "withNonEmptyTransfers"; @@ -1352,7 +1295,7 @@ final HapiSpec scheduledXferFailingWithEmptyTokenTransferAccountAmountsPaysServi @HapiTest @Order(29) - final HapiSpec scheduledSubmitFailedWithMsgSizeTooLargeStillPaysServiceFeeButHasNoImpact() { + final Stream scheduledSubmitFailedWithMsgSizeTooLargeStillPaysServiceFeeButHasNoImpact() { String immutableTopic = "XXX"; String validSchedule = "withValidSize"; String invalidSchedule = "withInvalidSize"; @@ -1395,7 +1338,7 @@ final HapiSpec scheduledSubmitFailedWithMsgSizeTooLargeStillPaysServiceFeeButHas @HapiTest @Order(28) - final HapiSpec scheduledSubmitFailedWithInvalidChunkTxnIdStillPaysServiceFeeButHasNoImpact() { + final Stream scheduledSubmitFailedWithInvalidChunkTxnIdStillPaysServiceFeeButHasNoImpact() { String immutableTopic = "XXX"; String validSchedule = "withValidChunkTxnId"; String invalidSchedule = "withInvalidChunkTxnId"; @@ -1449,7 +1392,7 @@ final HapiSpec scheduledSubmitFailedWithInvalidChunkTxnIdStillPaysServiceFeeButH @HapiTest @Order(27) - final HapiSpec scheduledSubmitFailedWithInvalidChunkNumberStillPaysServiceFeeButHasNoImpact() { + final Stream scheduledSubmitFailedWithInvalidChunkNumberStillPaysServiceFeeButHasNoImpact() { String immutableTopic = "XXX"; String validSchedule = "withValidChunkNumber"; String invalidSchedule = "withInvalidChunkNumber"; @@ -1498,7 +1441,7 @@ final HapiSpec scheduledSubmitFailedWithInvalidChunkNumberStillPaysServiceFeeBut @HapiTest @Order(30) - final HapiSpec scheduledSubmitThatWouldFailWithInvalidTopicIdCannotBeScheduled() { + final Stream scheduledSubmitThatWouldFailWithInvalidTopicIdCannotBeScheduled() { String civilianPayer = PAYER; AtomicReference> successFeesObs = new AtomicReference<>(); AtomicReference> failureFeesObs = new AtomicReference<>(); @@ -1537,7 +1480,7 @@ private void assertBasicallyIdentical( @HapiTest @Order(31) - final HapiSpec scheduledSubmitThatWouldFailWithTopicDeletedCannotBeSigned() { + final Stream scheduledSubmitThatWouldFailWithTopicDeletedCannotBeSigned() { String adminKey = ADMIN; String mutableTopic = "XXX"; String postDeleteSchedule = "deferredTooLongSubmitMsg"; @@ -1564,7 +1507,7 @@ final HapiSpec scheduledSubmitThatWouldFailWithTopicDeletedCannotBeSigned() { @HapiTest @Order(2) - final HapiSpec executionTriggersOnceTopicHasSatisfiedSubmitKey() { + final Stream executionTriggersOnceTopicHasSatisfiedSubmitKey() { String adminKey = ADMIN; String submitKey = "submit"; String mutableTopic = "XXX"; @@ -1603,7 +1546,7 @@ final HapiSpec executionTriggersOnceTopicHasSatisfiedSubmitKey() { @HapiTest @Order(3) - final HapiSpec executionTriggersWithWeirdlyRepeatedKey() { + final Stream executionTriggersWithWeirdlyRepeatedKey() { String schedule = "dupKeyXfer"; return defaultHapiSpec("ExecutionTriggersWithWeirdlyRepeatedKey") @@ -1638,7 +1581,7 @@ final HapiSpec executionTriggersWithWeirdlyRepeatedKey() { @HapiTest @Order(14) - final HapiSpec executionWithDefaultPayerWorks() { + final Stream executionWithDefaultPayerWorks() { long transferAmount = 1; return defaultHapiSpec("ExecutionWithDefaultPayerWorks") .given( @@ -1697,7 +1640,7 @@ final HapiSpec executionWithDefaultPayerWorks() { @HapiTest @Order(13) - final HapiSpec executionWithDefaultPayerButNoFundsFails() { + final Stream executionWithDefaultPayerButNoFundsFails() { long balance = 10_000_000L; long noBalance = 0L; long transferAmount = 1L; @@ -1736,7 +1679,7 @@ final HapiSpec executionWithDefaultPayerButNoFundsFails() { @HapiTest @Order(11) - final HapiSpec executionWithCustomPayerWorksWithLastSigBeingCustomPayer() { + final Stream executionWithCustomPayerWorksWithLastSigBeingCustomPayer() { long noBalance = 0L; long transferAmount = 1; return defaultHapiSpec("ExecutionWithCustomPayerWorksWithLastSigBeingCustomPayer") @@ -1775,7 +1718,7 @@ final HapiSpec executionWithCustomPayerWorksWithLastSigBeingCustomPayer() { @HapiTest @Order(8) - final HapiSpec executionWithCustomPayerButNoFundsFails() { + final Stream executionWithCustomPayerButNoFundsFails() { long balance = 0L; long noBalance = 0L; long transferAmount = 1; @@ -1809,7 +1752,7 @@ final HapiSpec executionWithCustomPayerButNoFundsFails() { @HapiTest @Order(12) - final HapiSpec executionWithDefaultPayerButAccountDeletedFails() { + final Stream executionWithDefaultPayerButAccountDeletedFails() { long balance = 10_000_000L; long noBalance = 0L; long transferAmount = 1L; @@ -1837,7 +1780,7 @@ final HapiSpec executionWithDefaultPayerButAccountDeletedFails() { @Order(7) @HapiTest - final HapiSpec executionWithCustomPayerButAccountDeletedFails() { + final Stream executionWithCustomPayerButAccountDeletedFails() { long balance = 10_000_000L; long noBalance = 0L; long transferAmount = 1; @@ -1878,7 +1821,7 @@ final HapiSpec executionWithCustomPayerButAccountDeletedFails() { @HapiTest @Order(4) - final HapiSpec executionWithCryptoInsufficientAccountBalanceFails() { + final Stream executionWithCryptoInsufficientAccountBalanceFails() { long noBalance = 0L; long senderBalance = 100L; long transferAmount = 101L; @@ -1913,7 +1856,7 @@ final HapiSpec executionWithCryptoInsufficientAccountBalanceFails() { @HapiTest @Order(5) - final HapiSpec executionWithCryptoSenderDeletedFails() { + final Stream executionWithCryptoSenderDeletedFails() { long noBalance = 0L; long senderBalance = 100L; long transferAmount = 101L; @@ -1950,7 +1893,7 @@ final HapiSpec executionWithCryptoSenderDeletedFails() { @HapiTest @Order(16) - final HapiSpec executionWithTokenInsufficientAccountBalanceFails() { + final Stream executionWithTokenInsufficientAccountBalanceFails() { String xToken = "XXX"; String invalidSchedule = "withInsufficientTokenTransfer"; String schedulePayer = PAYER; @@ -1985,7 +1928,7 @@ final HapiSpec executionWithTokenInsufficientAccountBalanceFails() { @HapiTest @Order(15) - final HapiSpec executionWithInvalidAccountAmountsFails() { + final Stream executionWithInvalidAccountAmountsFails() { long transferAmount = 100; long senderBalance = 1000L; long payingAccountBalance = 1_000_000L; @@ -2030,7 +1973,7 @@ final HapiSpec executionWithInvalidAccountAmountsFails() { @HapiTest @Order(10) - final HapiSpec executionWithCustomPayerWorks() { + final Stream executionWithCustomPayerWorks() { long transferAmount = 1; return defaultHapiSpec("ExecutionWithCustomPayerWorks") .given( @@ -2097,7 +2040,7 @@ final HapiSpec executionWithCustomPayerWorks() { @HapiTest @Order(6) - final HapiSpec executionWithCustomPayerAndAdminKeyWorks() { + final Stream executionWithCustomPayerAndAdminKeyWorks() { long transferAmount = 1; return defaultHapiSpec("ExecutionWithCustomPayerAndAdminKeyWorks") .given( @@ -2166,7 +2109,7 @@ final HapiSpec executionWithCustomPayerAndAdminKeyWorks() { @HapiTest @Order(9) - final HapiSpec executionWithCustomPayerWhoSignsAtCreationAsPayerWorks() { + final Stream executionWithCustomPayerWhoSignsAtCreationAsPayerWorks() { long transferAmount = 1; return defaultHapiSpec("ExecutionWithCustomPayerWhoSignsAtCreationAsPayerWorks") .given( @@ -2232,9 +2175,8 @@ final HapiSpec executionWithCustomPayerWhoSignsAtCreationAsPayerWorks() { })); } - // Currently this cannot be run as HapiTest because it stops the captive nodes. - @BddMethodIsNotATest - final HapiSpec scheduledFreezeWorksAsExpected() { + // (FUTURE) Consider this as a test against a standalone network? + final Stream scheduledFreezeWorksAsExpected() { final byte[] poeticUpgradeHash = getPoeticUpgradeHash(); return defaultHapiSpec("ScheduledFreezeWorksAsExpected") @@ -2275,9 +2217,8 @@ final HapiSpec scheduledFreezeWorksAsExpected() { })); } - // Currently this cannot be run as HapiTest because it stops the captive nodes. - @BddMethodIsNotATest - final HapiSpec scheduledFreezeWithUnauthorizedPayerFails(boolean isLongTermEnabled) { + // (FUTURE) Consider this as a test against a standalone network? + final Stream scheduledFreezeWithUnauthorizedPayerFails(boolean isLongTermEnabled) { final byte[] poeticUpgradeHash = getPoeticUpgradeHash(); if (isLongTermEnabled) { @@ -2349,7 +2290,7 @@ final HapiSpec scheduledFreezeWithUnauthorizedPayerFails(boolean isLongTermEnabl @HapiTest @Order(26) - final HapiSpec scheduledPermissionedFileUpdateWorksAsExpected() { + final Stream scheduledPermissionedFileUpdateWorksAsExpected() { return defaultHapiSpec("ScheduledPermissionedFileUpdateWorksAsExpected") .given( overriding(SCHEDULING_WHITELIST, WHITELIST_MINIMUM), @@ -2382,7 +2323,7 @@ final HapiSpec scheduledPermissionedFileUpdateWorksAsExpected() { @HapiTest @Order(25) - final HapiSpec scheduledPermissionedFileUpdateUnauthorizedPayerFails() { + final Stream scheduledPermissionedFileUpdateUnauthorizedPayerFails() { return defaultHapiSpec("ScheduledPermissionedFileUpdateUnauthorizedPayerFails") .given( @@ -2417,7 +2358,7 @@ final HapiSpec scheduledPermissionedFileUpdateUnauthorizedPayerFails() { @HapiTest @Order(33) - final HapiSpec scheduledSystemDeleteWorksAsExpected() { + final Stream scheduledSystemDeleteWorksAsExpected() { return defaultHapiSpec("ScheduledSystemDeleteWorksAsExpected") .given( @@ -2451,7 +2392,7 @@ final HapiSpec scheduledSystemDeleteWorksAsExpected() { @HapiTest @Order(32) - final HapiSpec hapiTestScheduledSystemDeleteUnauthorizedPayerFails() { + final Stream hapiTestScheduledSystemDeleteUnauthorizedPayerFails() { return defaultHapiSpec("ScheduledSystemDeleteUnauthorizedPayerFails") .given( overriding(SCHEDULING_WHITELIST, WHITELIST_MINIMUM), @@ -2484,8 +2425,7 @@ final HapiSpec hapiTestScheduledSystemDeleteUnauthorizedPayerFails() { })); } - @BddMethodIsNotATest - final HapiSpec scheduledSystemDeleteUnauthorizedPayerFails(boolean isLongTermEnabled) { + final Stream scheduledSystemDeleteUnauthorizedPayerFails(boolean isLongTermEnabled) { if (isLongTermEnabled) { return defaultHapiSpec("ScheduledSystemDeleteUnauthorizedPayerFails") @@ -2541,7 +2481,7 @@ final HapiSpec scheduledSystemDeleteUnauthorizedPayerFails(boolean isLongTermEna // @todo('FUTURE') Work out why we get `PLATFORM_TRANSACTION_NOT_CREATED` instead of `OK` // for UncheckedSubmit... This appears to come from `blockingOrder` call in `when` clause - final HapiSpec congestionPricingAffectsImmediateScheduleExecution() { + final Stream congestionPricingAffectsImmediateScheduleExecution() { var artificialLimits = protoDefsFromResource("testSystemFiles/artificial-limits-congestion.json"); var defaultThrottles = protoDefsFromResource("testSystemFiles/throttles-dev.json"); var contract = "Multipurpose"; @@ -2645,7 +2585,7 @@ final HapiSpec congestionPricingAffectsImmediateScheduleExecution() { @HapiTest @Order(48) - final HapiSpec suiteCleanup() { + final Stream suiteCleanup() { return defaultHapiSpec("suiteCleanup") .given() .when() diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/schedule/ScheduleLongTermExecutionSpecs.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/schedule/ScheduleLongTermExecutionSpecs.java index 306e9befe37e..84898ff86826 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/schedule/ScheduleLongTermExecutionSpecs.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/schedule/ScheduleLongTermExecutionSpecs.java @@ -102,7 +102,6 @@ import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.SCHEDULE_FUTURE_THROTTLE_EXCEEDED; import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.SUCCESS; -import com.hedera.services.bdd.spec.HapiSpec; import com.hedera.services.bdd.spec.HapiSpecOperation; import com.hedera.services.bdd.spec.HapiSpecSetup; import com.hedera.services.bdd.spec.transactions.crypto.HapiCryptoTransfer; @@ -112,10 +111,15 @@ import java.util.List; import java.util.Optional; import java.util.stream.IntStream; +import java.util.stream.Stream; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.DynamicTest; +/** + * (FUTURE) - Re-enable after long-term scheduled transactions are restored. + */ public class ScheduleLongTermExecutionSpecs extends HapiSuite { private static final Logger LOG = LogManager.getLogger(ScheduleLongTermExecutionSpecs.class); @@ -125,7 +129,7 @@ public static void main(String... args) { } @Override - public List getSpecsInSuite() { + public List> getSpecsInSuite() { return List.of( enableLongTermScheduledTransactions(), executionWithDefaultPayerWorks(), @@ -159,7 +163,7 @@ public List getSpecsInSuite() { } @SuppressWarnings("java:S5960") - final HapiSpec executionWithCustomPayerWorks() { + final Stream executionWithCustomPayerWorks() { return defaultHapiSpec("ExecutionAtExpiryWithCustomPayerWorks") .given( cryptoCreate(PAYING_ACCOUNT), @@ -264,7 +268,7 @@ final HapiSpec executionWithCustomPayerWorks() { })); } - final HapiSpec executionWithCustomPayerAndAdminKeyWorks() { + final Stream executionWithCustomPayerAndAdminKeyWorks() { return defaultHapiSpec("ExecutionAtExpiryWithCustomPayerAndAdminKeyWorks") .given( newKeyNamed("adminKey"), @@ -371,7 +375,7 @@ final HapiSpec executionWithCustomPayerAndAdminKeyWorks() { })); } - final HapiSpec executionWithCustomPayerWhoSignsAtCreationAsPayerWorks() { + final Stream executionWithCustomPayerWhoSignsAtCreationAsPayerWorks() { return defaultHapiSpec("ExecutionAtExpiryWithCustomPayerWhoSignsAtCreationAsPayerWorks") .given( cryptoCreate(PAYING_ACCOUNT), @@ -477,7 +481,7 @@ final HapiSpec executionWithCustomPayerWhoSignsAtCreationAsPayerWorks() { })); } - public HapiSpec executionWithDefaultPayerWorks() { + final Stream executionWithDefaultPayerWorks() { long transferAmount = 1; return defaultHapiSpec("ExecutionAtExpiryWithDefaultPayerWorks") .given( @@ -579,7 +583,7 @@ public HapiSpec executionWithDefaultPayerWorks() { })); } - public HapiSpec executionWithContractCallWorksAtExpiry() { + final Stream executionWithContractCallWorksAtExpiry() { return defaultHapiSpec("ExecutionWithContractCallWorksAtExpiry") .given( overriding(SCHEDULING_WHITELIST, "ContractCall"), @@ -630,7 +634,7 @@ public HapiSpec executionWithContractCallWorksAtExpiry() { })); } - public HapiSpec executionWithContractCreateWorksAtExpiry() { + final Stream executionWithContractCreateWorksAtExpiry() { return defaultHapiSpec("ExecutionWithContractCreateWorksAtExpiry") .given( overriding(SCHEDULING_WHITELIST, "ContractCreate"), @@ -683,7 +687,7 @@ public HapiSpec executionWithContractCreateWorksAtExpiry() { })); } - public HapiSpec executionWithDefaultPayerButNoFundsFails() { + final Stream executionWithDefaultPayerButNoFundsFails() { long balance = 10_000_000L; long noBalance = 0L; long transferAmount = 1L; @@ -732,7 +736,7 @@ public HapiSpec executionWithDefaultPayerButNoFundsFails() { })); } - public HapiSpec executionWithCustomPayerThatNeverSignsFails() { + final Stream executionWithCustomPayerThatNeverSignsFails() { long transferAmount = 1; return defaultHapiSpec("ExecutionWithCustomPayerThatNeverSignsFails") .given( @@ -763,7 +767,7 @@ public HapiSpec executionWithCustomPayerThatNeverSignsFails() { getTxnRecord(CREATE_TX).scheduled().hasAnswerOnlyPrecheck(RECORD_NOT_FOUND)); } - public HapiSpec executionWithCustomPayerButNoFundsFails() { + final Stream executionWithCustomPayerButNoFundsFails() { long balance = 0L; long transferAmount = 1; return defaultHapiSpec("ExecutionAtExpiryWithCustomPayerButNoFundsFails") @@ -804,7 +808,7 @@ public HapiSpec executionWithCustomPayerButNoFundsFails() { })); } - public HapiSpec executionWithDefaultPayerButAccountDeletedFails() { + final Stream executionWithDefaultPayerButAccountDeletedFails() { long balance = 10_000_000L; long noBalance = 0L; long transferAmount = 1L; @@ -842,7 +846,7 @@ public HapiSpec executionWithDefaultPayerButAccountDeletedFails() { getTxnRecord(CREATE_TX).scheduled().hasCostAnswerPrecheck(ACCOUNT_DELETED)); } - public HapiSpec executionWithCustomPayerButAccountDeletedFails() { + final Stream executionWithCustomPayerButAccountDeletedFails() { long balance = 10_000_000L; long noBalance = 0L; long transferAmount = 1; @@ -891,7 +895,7 @@ public HapiSpec executionWithCustomPayerButAccountDeletedFails() { })); } - public HapiSpec executionWithInvalidAccountAmountsFails() { + final Stream executionWithInvalidAccountAmountsFails() { long transferAmount = 100; long senderBalance = 1000L; long payingAccountBalance = 1_000_000L; @@ -939,7 +943,7 @@ public HapiSpec executionWithInvalidAccountAmountsFails() { })); } - public HapiSpec executionWithCryptoInsufficientAccountBalanceFails() { + final Stream executionWithCryptoInsufficientAccountBalanceFails() { long noBalance = 0L; long senderBalance = 100L; long transferAmount = 101L; @@ -984,7 +988,7 @@ public HapiSpec executionWithCryptoInsufficientAccountBalanceFails() { })); } - public HapiSpec executionWithCryptoSenderDeletedFails() { + final Stream executionWithCryptoSenderDeletedFails() { long noBalance = 0L; long senderBalance = 100L; long transferAmount = 101L; @@ -1031,7 +1035,7 @@ public HapiSpec executionWithCryptoSenderDeletedFails() { })); } - public HapiSpec executionTriggersWithWeirdlyRepeatedKey() { + final Stream executionTriggersWithWeirdlyRepeatedKey() { String schedule = "dupKeyXfer"; return defaultHapiSpec("ExecutionAtExpiryTriggersWithWeirdlyRepeatedKey") @@ -1073,7 +1077,7 @@ public HapiSpec executionTriggersWithWeirdlyRepeatedKey() { .hasPrecheck(INVALID_SCHEDULE_ID)); } - final HapiSpec scheduledFreezeWorksAsExpected() { + final Stream scheduledFreezeWorksAsExpected() { final byte[] poeticUpgradeHash = getPoeticUpgradeHash(); @@ -1127,7 +1131,7 @@ final HapiSpec scheduledFreezeWorksAsExpected() { })); } - final HapiSpec scheduledFreezeWithUnauthorizedPayerFails() { + final Stream scheduledFreezeWithUnauthorizedPayerFails() { final byte[] poeticUpgradeHash = getPoeticUpgradeHash(); @@ -1162,7 +1166,7 @@ final HapiSpec scheduledFreezeWithUnauthorizedPayerFails() { HapiSpecSetup.getDefaultNodeProps().get(SCHEDULING_WHITELIST))); } - final HapiSpec scheduledPermissionedFileUpdateWorksAsExpected() { + final Stream scheduledPermissionedFileUpdateWorksAsExpected() { return defaultHapiSpec("ScheduledPermissionedFileUpdateWorksAsExpectedAtExpiry") .given( @@ -1207,7 +1211,7 @@ final HapiSpec scheduledPermissionedFileUpdateWorksAsExpected() { })); } - final HapiSpec scheduledPermissionedFileUpdateUnauthorizedPayerFails() { + final Stream scheduledPermissionedFileUpdateUnauthorizedPayerFails() { return defaultHapiSpec("ScheduledPermissionedFileUpdateUnauthorizedPayerFailsAtExpiry") .given( @@ -1253,7 +1257,7 @@ final HapiSpec scheduledPermissionedFileUpdateUnauthorizedPayerFails() { })); } - final HapiSpec scheduledSystemDeleteWorksAsExpected() { + final Stream scheduledSystemDeleteWorksAsExpected() { return defaultHapiSpec("ScheduledSystemDeleteWorksAsExpectedAtExpiry") .given( @@ -1298,7 +1302,7 @@ final HapiSpec scheduledSystemDeleteWorksAsExpected() { })); } - final HapiSpec scheduledSystemDeleteUnauthorizedPayerFails() { + final Stream scheduledSystemDeleteUnauthorizedPayerFails() { return defaultHapiSpec("ScheduledSystemDeleteUnauthorizedPayerFailsAtExpiry") .given( @@ -1324,7 +1328,7 @@ final HapiSpec scheduledSystemDeleteUnauthorizedPayerFails() { HapiSpecSetup.getDefaultNodeProps().get(SCHEDULING_WHITELIST))); } - public HapiSpec waitForExpiryIgnoredWhenLongTermDisabled() { + final Stream waitForExpiryIgnoredWhenLongTermDisabled() { return defaultHapiSpec("WaitForExpiryIgnoredWhenLongTermDisabled") .given( @@ -1349,7 +1353,7 @@ public HapiSpec waitForExpiryIgnoredWhenLongTermDisabled() { .isExecuted()); } - public HapiSpec expiryIgnoredWhenLongTermDisabled() { + final Stream expiryIgnoredWhenLongTermDisabled() { return defaultHapiSpec("ExpiryIgnoredWhenLongTermDisabled") .given( @@ -1377,7 +1381,7 @@ public HapiSpec expiryIgnoredWhenLongTermDisabled() { getAccountBalance(RECEIVER).hasTinyBars(0L)); } - public HapiSpec waitForExpiryIgnoredWhenLongTermDisabledThenEnabled() { + final Stream waitForExpiryIgnoredWhenLongTermDisabledThenEnabled() { return defaultHapiSpec("WaitForExpiryIgnoredWhenLongTermDisabledThenEnabled") .given( @@ -1404,7 +1408,7 @@ public HapiSpec waitForExpiryIgnoredWhenLongTermDisabledThenEnabled() { overriding(SCHEDULING_LONG_TERM_ENABLED, FALSE)); } - public HapiSpec expiryIgnoredWhenLongTermDisabledThenEnabled() { + final Stream expiryIgnoredWhenLongTermDisabledThenEnabled() { return defaultHapiSpec("ExpiryIgnoredWhenLongTermDisabledThenEnabled") .given( @@ -1434,7 +1438,7 @@ public HapiSpec expiryIgnoredWhenLongTermDisabledThenEnabled() { getAccountBalance(RECEIVER).hasTinyBars(0L)); } - final HapiSpec futureThrottlesAreRespected() { + final Stream futureThrottlesAreRespected() { var artificialLimits = protoDefsFromResource("testSystemFiles/artificial-limits-schedule.json"); var defaultThrottles = protoDefsFromResource("testSystemFiles/throttles-dev.json"); diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/schedule/ScheduleLongTermSignSpecs.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/schedule/ScheduleLongTermSignSpecs.java index 42d34cf788d1..5a85bcc78be6 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/schedule/ScheduleLongTermSignSpecs.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/schedule/ScheduleLongTermSignSpecs.java @@ -70,16 +70,20 @@ import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.RECORD_NOT_FOUND; import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.SOME_SIGNATURES_WERE_INVALID; -import com.hedera.services.bdd.spec.HapiSpec; import com.hedera.services.bdd.spec.keys.ControlForKey; import com.hedera.services.bdd.spec.keys.OverlappingKeyGenerator; import com.hedera.services.bdd.suites.HapiSuite; import com.hederahashgraph.api.proto.java.Key; import java.util.List; import java.util.Map; +import java.util.stream.Stream; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; +import org.junit.jupiter.api.DynamicTest; +/** + * (FUTURE) - Re-enable after long-term scheduled transactions are restored. + */ public class ScheduleLongTermSignSpecs extends HapiSuite { private static final Logger log = LogManager.getLogger(ScheduleLongTermSignSpecs.class); @@ -90,7 +94,7 @@ public static void main(String... args) { } @Override - public List getSpecsInSuite() { + public List> getSpecsInSuite() { return List.of( enableLongTermScheduledTransactions(), suiteSetup(), @@ -114,7 +118,7 @@ public List getSpecsInSuite() { setLongTermScheduledTransactionsToDefault()); } - final HapiSpec suiteCleanup() { + final Stream suiteCleanup() { return defaultHapiSpec("suiteCleanup") .given() .when() @@ -123,7 +127,7 @@ final HapiSpec suiteCleanup() { .overridingProps(Map.of(SCHEDULING_WHITELIST, WHITELIST_DEFAULT))); } - final HapiSpec suiteSetup() { + final Stream suiteSetup() { return defaultHapiSpec("suiteSetup") .given() .when() @@ -132,7 +136,7 @@ final HapiSpec suiteSetup() { .overridingProps(Map.of(SCHEDULING_WHITELIST, suiteWhitelist))); } - final HapiSpec changeInNestedSigningReqsRespected() { + final Stream changeInNestedSigningReqsRespected() { var senderShape = threshOf(2, threshOf(1, 3), threshOf(1, 3), threshOf(1, 3)); var sigOne = senderShape.signedWith(sigs(sigs(OFF, OFF, ON), sigs(OFF, OFF, OFF), sigs(OFF, OFF, OFF))); var firstSigThree = senderShape.signedWith(sigs(sigs(OFF, OFF, OFF), sigs(OFF, OFF, OFF), sigs(ON, OFF, OFF))); @@ -191,7 +195,7 @@ private Key bumpThirdNestedThresholdSigningReq(Key source) { return mutation; } - final HapiSpec reductionInSigningReqsAllowsTxnToGoThrough() { + final Stream reductionInSigningReqsAllowsTxnToGoThrough() { var senderShape = threshOf(2, threshOf(1, 3), threshOf(1, 3), threshOf(2, 3)); var sigOne = senderShape.signedWith(sigs(sigs(OFF, OFF, ON), sigs(OFF, OFF, OFF), sigs(OFF, OFF, OFF))); var sigTwo = senderShape.signedWith(sigs(sigs(OFF, OFF, OFF), sigs(ON, ON, ON), sigs(OFF, OFF, OFF))); @@ -241,7 +245,7 @@ final HapiSpec reductionInSigningReqsAllowsTxnToGoThrough() { getAccountBalance(receiver).hasTinyBars(1L)); } - final HapiSpec reductionInSigningReqsAllowsTxnToGoThroughAtExpiryWithNoWaitForExpiry() { + final Stream reductionInSigningReqsAllowsTxnToGoThroughAtExpiryWithNoWaitForExpiry() { var senderShape = threshOf(2, threshOf(1, 3), threshOf(1, 3), threshOf(2, 3)); var sigOne = senderShape.signedWith(sigs(sigs(OFF, OFF, ON), sigs(OFF, OFF, OFF), sigs(OFF, OFF, OFF))); var sigTwo = senderShape.signedWith(sigs(sigs(OFF, OFF, OFF), sigs(ON, ON, ON), sigs(OFF, OFF, OFF))); @@ -299,7 +303,7 @@ private Key lowerThirdNestedThresholdSigningReq(Key source) { return mutation; } - final HapiSpec nestedSigningReqsWorkAsExpected() { + final Stream nestedSigningReqsWorkAsExpected() { var senderShape = threshOf(2, threshOf(1, 3), threshOf(1, 3), threshOf(1, 3)); var sigOne = senderShape.signedWith(sigs(sigs(OFF, OFF, ON), sigs(OFF, OFF, OFF), sigs(OFF, OFF, OFF))); var sigTwo = senderShape.signedWith(sigs(sigs(OFF, OFF, OFF), sigs(OFF, ON, OFF), sigs(OFF, OFF, OFF))); @@ -338,7 +342,7 @@ final HapiSpec nestedSigningReqsWorkAsExpected() { getAccountBalance(receiver).hasTinyBars(1L)); } - final HapiSpec receiverSigRequiredNotConfusedByOrder() { + final Stream receiverSigRequiredNotConfusedByOrder() { var senderShape = threshOf(1, 3); var sigOne = senderShape.signedWith(sigs(ON, OFF, OFF)); String sender = "X"; @@ -375,7 +379,7 @@ final HapiSpec receiverSigRequiredNotConfusedByOrder() { getAccountBalance(receiver).hasTinyBars(1L)); } - final HapiSpec extraSigsDontMatterAtExpiry() { + final Stream extraSigsDontMatterAtExpiry() { var senderShape = threshOf(1, 3); var sigOne = senderShape.signedWith(sigs(ON, OFF, OFF)); var sigTwo = senderShape.signedWith(sigs(OFF, ON, OFF)); @@ -468,7 +472,7 @@ final HapiSpec extraSigsDontMatterAtExpiry() { getAccountBalance(receiver).hasTinyBars(1L)); } - final HapiSpec receiverSigRequiredNotConfusedByMultiSigSender() { + final Stream receiverSigRequiredNotConfusedByMultiSigSender() { var senderShape = threshOf(1, 3); var sigOne = senderShape.signedWith(sigs(ON, OFF, OFF)); var sigTwo = senderShape.signedWith(sigs(OFF, ON, OFF)); @@ -509,7 +513,7 @@ final HapiSpec receiverSigRequiredNotConfusedByMultiSigSender() { getAccountBalance(receiver).hasTinyBars(1L)); } - final HapiSpec receiverSigRequiredUpdateIsRecognized() { + final Stream receiverSigRequiredUpdateIsRecognized() { var senderShape = threshOf(2, 3); var sigOne = senderShape.signedWith(sigs(ON, OFF, OFF)); var sigTwo = senderShape.signedWith(sigs(OFF, ON, OFF)); @@ -555,7 +559,7 @@ final HapiSpec receiverSigRequiredUpdateIsRecognized() { getAccountBalance(receiver).hasTinyBars(1)); } - final HapiSpec basicSignatureCollectionWorks() { + final Stream basicSignatureCollectionWorks() { var txnBody = cryptoTransfer(tinyBarsFromTo(SENDER, RECEIVER, 1)); return defaultHapiSpec("BasicSignatureCollectionWorksWithExpiryAndWait") @@ -567,7 +571,7 @@ final HapiSpec basicSignatureCollectionWorks() { .then(getScheduleInfo(BASIC_XFER).hasSignatories(RECEIVER)); } - final HapiSpec signalsIrrelevantSig() { + final Stream signalsIrrelevantSig() { var txnBody = cryptoTransfer(tinyBarsFromTo(SENDER, RECEIVER, 1)); return defaultHapiSpec("SignalsIrrelevantSigWithExpiryAndWait") @@ -582,7 +586,7 @@ final HapiSpec signalsIrrelevantSig() { .hasKnownStatusFrom(NO_NEW_VALID_SIGNATURES, SOME_SIGNATURES_WERE_INVALID)); } - final HapiSpec signalsIrrelevantSigEvenAfterLinkedEntityUpdate() { + final Stream signalsIrrelevantSigEvenAfterLinkedEntityUpdate() { var txnBody = mintToken(TOKEN_A, 50000000L); return defaultHapiSpec("SignalsIrrelevantSigEvenAfterLinkedEntityUpdateWithExpiryAndWait") @@ -610,7 +614,7 @@ final HapiSpec signalsIrrelevantSigEvenAfterLinkedEntityUpdate() { overriding(SCHEDULING_WHITELIST, suiteWhitelist)); } - public HapiSpec triggersUponFinishingPayerSig() { + final Stream triggersUponFinishingPayerSig() { return defaultHapiSpec("TriggersUponFinishingPayerSigAtExpiry") .given( cryptoCreate(PAYER).balance(ONE_HBAR), @@ -643,7 +647,7 @@ public HapiSpec triggersUponFinishingPayerSig() { getAccountBalance(RECEIVER).hasTinyBars(1L)); } - public HapiSpec triggersUponAdditionalNeededSig() { + final Stream triggersUponAdditionalNeededSig() { return defaultHapiSpec("TriggersUponAdditionalNeededSigAtExpiry") .given( cryptoCreate(SENDER).balance(1L).via(SENDER_TXN), @@ -674,7 +678,7 @@ public HapiSpec triggersUponAdditionalNeededSig() { getAccountBalance(RECEIVER).hasTinyBars(1L)); } - public HapiSpec sharedKeyWorksAsExpected() { + final Stream sharedKeyWorksAsExpected() { return defaultHapiSpec("RequiresSharedKeyToSignBothSchedulingAndScheduledTxnsAtExpiry") .given( newKeyNamed(SHARED_KEY), @@ -707,7 +711,7 @@ public HapiSpec sharedKeyWorksAsExpected() { getTxnRecord(CREATION).scheduled()); } - public HapiSpec overlappingKeysTreatedAsExpected() { + final Stream overlappingKeysTreatedAsExpected() { var keyGen = OverlappingKeyGenerator.withAtLeastOneOverlappingByte(2); return defaultHapiSpec("OverlappingKeysTreatedAsExpectedAtExpiry") @@ -758,7 +762,7 @@ public HapiSpec overlappingKeysTreatedAsExpected() { getAccountBalance(ADDRESS_BOOK_CONTROL).hasTinyBars(changeFromSnapshot(BEFORE, +2))); } - public HapiSpec retestsActivationOnSignWithEmptySigMap() { + final Stream retestsActivationOnSignWithEmptySigMap() { return defaultHapiSpec("RetestsActivationOnCreateWithEmptySigMapAtExpiry") .given(newKeyNamed("a"), newKeyNamed("b"), newKeyListNamed("ab", List.of("a", "b")), newKeyNamed(ADMIN)) .when( diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/schedule/ScheduleRecordSpecs.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/schedule/ScheduleRecordSpecs.java index 4f12fc959eb5..7a15b7dbe863 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/schedule/ScheduleRecordSpecs.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/schedule/ScheduleRecordSpecs.java @@ -43,6 +43,11 @@ import static com.hedera.services.bdd.spec.utilops.UtilVerbs.usableTxnIdNamed; import static com.hedera.services.bdd.spec.utilops.UtilVerbs.validateChargedUsdWithin; import static com.hedera.services.bdd.spec.utilops.UtilVerbs.withOpContext; +import static com.hedera.services.bdd.suites.HapiSuite.DEFAULT_PAYER; +import static com.hedera.services.bdd.suites.HapiSuite.FUNDING; +import static com.hedera.services.bdd.suites.HapiSuite.GENESIS; +import static com.hedera.services.bdd.suites.HapiSuite.ONE_HBAR; +import static com.hedera.services.bdd.suites.HapiSuite.ONE_HUNDRED_HBARS; import static com.hedera.services.bdd.suites.schedule.ScheduleUtils.ADMIN; import static com.hedera.services.bdd.suites.schedule.ScheduleUtils.BEGIN; import static com.hedera.services.bdd.suites.schedule.ScheduleUtils.CREATION; @@ -62,17 +67,13 @@ import static com.hedera.services.bdd.suites.schedule.ScheduleUtils.UNWILLING_PAYER; import static com.hedera.services.bdd.suites.schedule.ScheduleUtils.WHITELIST_MINIMUM; import static com.hedera.services.bdd.suites.schedule.ScheduleUtils.scheduledVersionOf; -import static com.hedera.services.bdd.suites.schedule.ScheduleUtils.withAndWithoutLongTermEnabled; import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.INSUFFICIENT_PAYER_BALANCE; import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.INSUFFICIENT_TX_FEE; import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.SUCCESS; import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.TRANSACTION_ID_FIELD_NOT_ALLOWED; import com.hedera.services.bdd.junit.HapiTest; -import com.hedera.services.bdd.junit.HapiTestSuite; -import com.hedera.services.bdd.spec.HapiSpec; import com.hedera.services.bdd.spec.HapiSpecSetup; -import com.hedera.services.bdd.suites.HapiSuite; import com.hederahashgraph.api.proto.java.TransactionID; import java.math.BigInteger; import java.security.SecureRandom; @@ -80,32 +81,12 @@ import java.util.List; import java.util.Map; import java.util.concurrent.atomic.AtomicReference; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; - -@HapiTestSuite -public class ScheduleRecordSpecs extends HapiSuite { - private static final Logger log = LogManager.getLogger(ScheduleRecordSpecs.class); - - public static void main(String... args) { - new ScheduleRecordSpecs().runSuiteAsync(); - } - - @Override - public List getSpecsInSuite() { - return withAndWithoutLongTermEnabled(() -> List.of( - allRecordsAreQueryable(), - canonicalScheduleOpsHaveExpectedUsdFees(), - canScheduleChunkedMessages(), - deletionTimeIsAvailable(), - executionTimeIsAvailable(), - noFeesChargedIfTriggeredPayerIsInsolvent(), - noFeesChargedIfTriggeredPayerIsUnwilling(), - schedulingTxnIdFieldsNotAllowed())); - } +import java.util.stream.Stream; +import org.junit.jupiter.api.DynamicTest; +public class ScheduleRecordSpecs { @HapiTest - HapiSpec canonicalScheduleOpsHaveExpectedUsdFees() { + final Stream canonicalScheduleOpsHaveExpectedUsdFees() { return defaultHapiSpec("CanonicalScheduleOpsHaveExpectedUsdFees") .given( overriding(SCHEDULING_WHITELIST, "CryptoTransfer,ContractCall"), @@ -161,7 +142,7 @@ HapiSpec canonicalScheduleOpsHaveExpectedUsdFees() { } @HapiTest - public HapiSpec noFeesChargedIfTriggeredPayerIsUnwilling() { + final Stream noFeesChargedIfTriggeredPayerIsUnwilling() { return defaultHapiSpec("NoFeesChargedIfTriggeredPayerIsUnwilling") .given(cryptoCreate(UNWILLING_PAYER)) .when(scheduleCreate( @@ -182,7 +163,7 @@ public HapiSpec noFeesChargedIfTriggeredPayerIsUnwilling() { } @HapiTest - public HapiSpec noFeesChargedIfTriggeredPayerIsInsolvent() { + final Stream noFeesChargedIfTriggeredPayerIsInsolvent() { return defaultHapiSpec("NoFeesChargedIfTriggeredPayerIsInsolvent") .given(cryptoCreate(INSOLVENT_PAYER).balance(0L)) .when(scheduleCreate(SCHEDULE, cryptoTransfer(tinyBarsFromTo(GENESIS, FUNDING, 1))) @@ -200,7 +181,7 @@ public HapiSpec noFeesChargedIfTriggeredPayerIsInsolvent() { } @HapiTest - public HapiSpec canScheduleChunkedMessages() { + final Stream canScheduleChunkedMessages() { String ofGeneralInterest = "Scotch"; AtomicReference initialTxnId = new AtomicReference<>(); @@ -273,7 +254,7 @@ public HapiSpec canScheduleChunkedMessages() { } @HapiTest - public HapiSpec schedulingTxnIdFieldsNotAllowed() { + final Stream schedulingTxnIdFieldsNotAllowed() { return defaultHapiSpec("SchedulingTxnIdFieldsNotAllowed") .given(usableTxnIdNamed("withScheduled").settingScheduledInappropriately()) .when() @@ -281,7 +262,7 @@ public HapiSpec schedulingTxnIdFieldsNotAllowed() { } @HapiTest - public HapiSpec executionTimeIsAvailable() { + final Stream executionTimeIsAvailable() { return defaultHapiSpec("ExecutionTimeIsAvailable") .given( overriding(SCHEDULING_WHITELIST, "CryptoTransfer,ContractCall"), @@ -300,7 +281,7 @@ public HapiSpec executionTimeIsAvailable() { } @HapiTest - public HapiSpec deletionTimeIsAvailable() { + final Stream deletionTimeIsAvailable() { return defaultHapiSpec("DeletionTimeIsAvailable") .given( overriding(SCHEDULING_WHITELIST, "CryptoTransfer,ContractCall"), @@ -320,7 +301,7 @@ public HapiSpec deletionTimeIsAvailable() { } @HapiTest - public HapiSpec allRecordsAreQueryable() { + final Stream allRecordsAreQueryable() { return defaultHapiSpec("AllRecordsAreQueryable") .given( overriding(SCHEDULING_WHITELIST, "CryptoTransfer,ContractCall"), @@ -344,9 +325,4 @@ public HapiSpec allRecordsAreQueryable() { getTxnRecord(CREATION).scheduled(), getTxnRecord(CREATION).scheduledBy(TWO_SIG_XFER)); } - - @Override - protected Logger getResultsLogger() { - return log; - } } diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/schedule/ScheduleSignSpecs.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/schedule/ScheduleSignSpecs.java index 46fc2079b6a4..4ae96431512f 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/schedule/ScheduleSignSpecs.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/schedule/ScheduleSignSpecs.java @@ -46,6 +46,12 @@ import static com.hedera.services.bdd.spec.utilops.UtilVerbs.sleepFor; import static com.hedera.services.bdd.spec.utilops.UtilVerbs.submitModified; import static com.hedera.services.bdd.spec.utilops.mod.ModificationUtils.withSuccessivelyVariedBodyIds; +import static com.hedera.services.bdd.suites.HapiSuite.ADDRESS_BOOK_CONTROL; +import static com.hedera.services.bdd.suites.HapiSuite.APP_PROPERTIES; +import static com.hedera.services.bdd.suites.HapiSuite.DEFAULT_PAYER; +import static com.hedera.services.bdd.suites.HapiSuite.FUNDING; +import static com.hedera.services.bdd.suites.HapiSuite.ONE_HBAR; +import static com.hedera.services.bdd.suites.HapiSuite.ONE_HUNDRED_HBARS; import static com.hedera.services.bdd.suites.schedule.ScheduleUtils.ADMIN; import static com.hedera.services.bdd.suites.schedule.ScheduleUtils.BASIC_XFER; import static com.hedera.services.bdd.suites.schedule.ScheduleUtils.DEFAULT_TX_EXPIRY; @@ -64,7 +70,6 @@ import static com.hedera.services.bdd.suites.schedule.ScheduleUtils.VALID_SCHEDULED_TXN; import static com.hedera.services.bdd.suites.schedule.ScheduleUtils.WHITELIST_DEFAULT; import static com.hedera.services.bdd.suites.schedule.ScheduleUtils.WHITELIST_MINIMUM; -import static com.hedera.services.bdd.suites.schedule.ScheduleUtils.withAndWithoutLongTermEnabled; import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.INVALID_SCHEDULE_ID; import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.NO_NEW_VALID_SIGNATURES; import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.OK; @@ -74,62 +79,24 @@ import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.SOME_SIGNATURES_WERE_INVALID; import com.hedera.services.bdd.junit.HapiTest; -import com.hedera.services.bdd.junit.HapiTestSuite; -import com.hedera.services.bdd.spec.HapiSpec; import com.hedera.services.bdd.spec.keys.ControlForKey; import com.hedera.services.bdd.spec.keys.OverlappingKeyGenerator; -import com.hedera.services.bdd.suites.HapiSuite; import com.hederahashgraph.api.proto.java.Key; import java.util.List; import java.util.Map; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; +import java.util.stream.Stream; +import org.junit.jupiter.api.DynamicTest; import org.junit.jupiter.api.MethodOrderer; import org.junit.jupiter.api.Order; import org.junit.jupiter.api.TestMethodOrder; -@HapiTestSuite @TestMethodOrder(MethodOrderer.OrderAnnotation.class) -public class ScheduleSignSpecs extends HapiSuite { - private static final Logger log = LogManager.getLogger(ScheduleSignSpecs.class); +public class ScheduleSignSpecs { private static final int SCHEDULE_EXPIRY_TIME_SECS = 10; private static final int SCHEDULE_EXPIRY_TIME_MS = SCHEDULE_EXPIRY_TIME_SECS * 1000; - public static void main(String... args) { - new ScheduleSignSpecs().runSuiteSync(); - } - - @Override - public List getSpecsInSuite() { - return withAndWithoutLongTermEnabled(() -> List.of( - suiteSetup(), - addingSignaturesToExecutedTxFails(), - addingSignaturesToNonExistingTxFails(), - basicSignatureCollectionWorks(), - changeInNestedSigningReqsRespected(), - nestedSigningReqsWorkAsExpected(), - okIfAdminKeyOverlapsWithActiveScheduleKey(), - overlappingKeysTreatedAsExpected(), - receiverSigRequiredNotConfusedByMultiSigSender(), - receiverSigRequiredNotConfusedByOrder(), - receiverSigRequiredUpdateIsRecognized(), - reductionInSigningReqsAllowsTxnToGoThrough(), - reductionInSigningReqsAllowsTxnToGoThroughWithRandomKey(), - retestsActivationOnSignWithEmptySigMap(), - scheduleAlreadyExecutedDoesntRepeatTransaction(), - scheduleAlreadyExecutedOnCreateDoesntRepeatTransaction(), - sharedKeyWorksAsExpected(), - signFailsDueToDeletedExpiration(), - signalsIrrelevantSig(), - signalsIrrelevantSigEvenAfterLinkedEntityUpdate(), - signingDeletedSchedulesHasNoEffect(), - triggersUponAdditionalNeededSig(), - triggersUponFinishingPayerSig(), - suiteCleanup())); - } - @HapiTest - final HapiSpec idVariantsTreatedAsExpected() { + final Stream idVariantsTreatedAsExpected() { return defaultHapiSpec("idVariantsTreatedAsExpected") .given( newKeyNamed(ADMIN), @@ -143,7 +110,7 @@ final HapiSpec idVariantsTreatedAsExpected() { @HapiTest @Order(24) - private HapiSpec suiteCleanup() { + final Stream suiteCleanup() { return defaultHapiSpec("suiteCleanup") .given() .when() @@ -156,7 +123,7 @@ private HapiSpec suiteCleanup() { @HapiTest @Order(1) - private HapiSpec suiteSetup() { + final Stream suiteSetup() { return defaultHapiSpec("suiteSetup") .given() .when() @@ -167,7 +134,7 @@ private HapiSpec suiteSetup() { @HapiTest @Order(21) - final HapiSpec signingDeletedSchedulesHasNoEffect() { + final Stream signingDeletedSchedulesHasNoEffect() { String sender = "X"; String receiver = "Y"; String schedule = "Z"; @@ -191,7 +158,7 @@ final HapiSpec signingDeletedSchedulesHasNoEffect() { @HapiTest @Order(5) - final HapiSpec changeInNestedSigningReqsRespected() { + final Stream changeInNestedSigningReqsRespected() { var senderShape = threshOf(2, threshOf(1, 3), threshOf(1, 3), threshOf(1, 3)); var sigOne = senderShape.signedWith(sigs(sigs(OFF, OFF, ON), sigs(OFF, OFF, OFF), sigs(OFF, OFF, OFF))); var sigTwo = senderShape.signedWith(sigs(sigs(OFF, OFF, OFF), sigs(ON, ON, ON), sigs(OFF, OFF, OFF))); @@ -244,7 +211,7 @@ private Key bumpThirdNestedThresholdSigningReq(Key source) { @HapiTest @Order(12) - final HapiSpec reductionInSigningReqsAllowsTxnToGoThrough() { + final Stream reductionInSigningReqsAllowsTxnToGoThrough() { var senderShape = threshOf(2, threshOf(1, 3), threshOf(1, 3), threshOf(2, 3)); var sigOne = senderShape.signedWith(sigs(sigs(OFF, OFF, ON), sigs(OFF, OFF, OFF), sigs(OFF, OFF, OFF))); var sigTwo = senderShape.signedWith(sigs(sigs(OFF, OFF, OFF), sigs(ON, ON, ON), sigs(OFF, OFF, OFF))); @@ -285,7 +252,7 @@ final HapiSpec reductionInSigningReqsAllowsTxnToGoThrough() { @HapiTest @Order(13) - final HapiSpec reductionInSigningReqsAllowsTxnToGoThroughWithRandomKey() { + final Stream reductionInSigningReqsAllowsTxnToGoThroughWithRandomKey() { var senderShape = threshOf(2, threshOf(1, 3), threshOf(1, 3), threshOf(2, 3)); var sigOne = senderShape.signedWith(sigs(sigs(OFF, OFF, ON), sigs(OFF, OFF, OFF), sigs(OFF, OFF, OFF))); var sigTwo = senderShape.signedWith(sigs(sigs(OFF, OFF, OFF), sigs(ON, ON, ON), sigs(OFF, OFF, OFF))); @@ -338,7 +305,7 @@ private Key lowerThirdNestedThresholdSigningReq(Key source) { @HapiTest @Order(6) - final HapiSpec nestedSigningReqsWorkAsExpected() { + final Stream nestedSigningReqsWorkAsExpected() { var senderShape = threshOf(2, threshOf(1, 3), threshOf(1, 3), threshOf(1, 3)); var sigOne = senderShape.signedWith(sigs(sigs(OFF, OFF, ON), sigs(OFF, OFF, OFF), sigs(OFF, OFF, OFF))); var sigTwo = senderShape.signedWith(sigs(sigs(OFF, OFF, OFF), sigs(OFF, ON, OFF), sigs(OFF, OFF, OFF))); @@ -372,7 +339,7 @@ final HapiSpec nestedSigningReqsWorkAsExpected() { @HapiTest @Order(10) - final HapiSpec receiverSigRequiredNotConfusedByOrder() { + final Stream receiverSigRequiredNotConfusedByOrder() { var senderShape = threshOf(1, 3); var sigOne = senderShape.signedWith(sigs(ON, OFF, OFF)); var sigTwo = senderShape.signedWith(sigs(OFF, ON, OFF)); @@ -410,7 +377,7 @@ final HapiSpec receiverSigRequiredNotConfusedByOrder() { @HapiTest @Order(9) - final HapiSpec receiverSigRequiredNotConfusedByMultiSigSender() { + final Stream receiverSigRequiredNotConfusedByMultiSigSender() { var senderShape = threshOf(1, 3); var sigOne = senderShape.signedWith(sigs(ON, OFF, OFF)); var sigTwo = senderShape.signedWith(sigs(OFF, ON, OFF)); @@ -448,7 +415,7 @@ final HapiSpec receiverSigRequiredNotConfusedByMultiSigSender() { @HapiTest @Order(11) - final HapiSpec receiverSigRequiredUpdateIsRecognized() { + final Stream receiverSigRequiredUpdateIsRecognized() { var senderShape = threshOf(2, 3); var sigOne = senderShape.signedWith(sigs(ON, OFF, OFF)); var sigTwo = senderShape.signedWith(sigs(OFF, ON, OFF)); @@ -490,7 +457,7 @@ final HapiSpec receiverSigRequiredUpdateIsRecognized() { @HapiTest @Order(16) - final HapiSpec scheduleAlreadyExecutedOnCreateDoesntRepeatTransaction() { + final Stream scheduleAlreadyExecutedOnCreateDoesntRepeatTransaction() { var senderShape = threshOf(1, 3); var sigOne = senderShape.signedWith(sigs(ON, OFF, OFF)); var sigTwo = senderShape.signedWith(sigs(OFF, ON, OFF)); @@ -527,7 +494,7 @@ final HapiSpec scheduleAlreadyExecutedOnCreateDoesntRepeatTransaction() { @HapiTest @Order(15) - final HapiSpec scheduleAlreadyExecutedDoesntRepeatTransaction() { + final Stream scheduleAlreadyExecutedDoesntRepeatTransaction() { var senderShape = threshOf(2, 3); var sigOne = senderShape.signedWith(sigs(ON, OFF, OFF)); var sigTwo = senderShape.signedWith(sigs(OFF, ON, OFF)); @@ -562,7 +529,7 @@ final HapiSpec scheduleAlreadyExecutedDoesntRepeatTransaction() { @HapiTest @Order(4) - final HapiSpec basicSignatureCollectionWorks() { + final Stream basicSignatureCollectionWorks() { var txnBody = cryptoTransfer(tinyBarsFromTo(SENDER, RECEIVER, 1)); return defaultHapiSpec("BasicSignatureCollectionWorks") @@ -577,7 +544,7 @@ final HapiSpec basicSignatureCollectionWorks() { @HapiTest @Order(19) - final HapiSpec signalsIrrelevantSig() { + final Stream signalsIrrelevantSig() { var txnBody = cryptoTransfer(tinyBarsFromTo(SENDER, RECEIVER, 1)); return defaultHapiSpec("SignalsIrrelevantSig") @@ -595,7 +562,7 @@ final HapiSpec signalsIrrelevantSig() { @HapiTest @Order(20) - final HapiSpec signalsIrrelevantSigEvenAfterLinkedEntityUpdate() { + final Stream signalsIrrelevantSigEvenAfterLinkedEntityUpdate() { var txnBody = mintToken(TOKEN_A, 50000000L); return defaultHapiSpec("SignalsIrrelevantSigEvenAfterLinkedEntityUpdate") @@ -623,7 +590,7 @@ final HapiSpec signalsIrrelevantSigEvenAfterLinkedEntityUpdate() { @HapiTest @Order(3) - final HapiSpec addingSignaturesToNonExistingTxFails() { + final Stream addingSignaturesToNonExistingTxFails() { return defaultHapiSpec("AddingSignaturesToNonExistingTxFails") .given(cryptoCreate(SENDER), newKeyNamed(SOMEBODY)) .when() @@ -636,7 +603,7 @@ final HapiSpec addingSignaturesToNonExistingTxFails() { @HapiTest @Order(2) - final HapiSpec addingSignaturesToExecutedTxFails() { + final Stream addingSignaturesToExecutedTxFails() { var txnBody = cryptoCreate(SOMEBODY); var creation = "basicCryptoCreate"; @@ -651,7 +618,7 @@ final HapiSpec addingSignaturesToExecutedTxFails() { @HapiTest @Order(23) - public HapiSpec triggersUponFinishingPayerSig() { + final Stream triggersUponFinishingPayerSig() { return defaultHapiSpec("TriggersUponFinishingPayerSig") .given( overriding(SCHEDULING_WHITELIST, WHITELIST_MINIMUM), @@ -672,7 +639,7 @@ public HapiSpec triggersUponFinishingPayerSig() { @HapiTest @Order(22) - public HapiSpec triggersUponAdditionalNeededSig() { + final Stream triggersUponAdditionalNeededSig() { return defaultHapiSpec("TriggersUponAdditionalNeededSig") .given( overriding(SCHEDULING_WHITELIST, WHITELIST_MINIMUM), @@ -691,7 +658,7 @@ public HapiSpec triggersUponAdditionalNeededSig() { @HapiTest @Order(17) - public HapiSpec sharedKeyWorksAsExpected() { + final Stream sharedKeyWorksAsExpected() { return defaultHapiSpec("RequiresSharedKeyToSignBothSchedulingAndScheduledTxns") .given( overriding(SCHEDULING_WHITELIST, WHITELIST_MINIMUM), @@ -712,7 +679,7 @@ public HapiSpec sharedKeyWorksAsExpected() { @HapiTest @Order(7) - public HapiSpec okIfAdminKeyOverlapsWithActiveScheduleKey() { + final Stream okIfAdminKeyOverlapsWithActiveScheduleKey() { var keyGen = OverlappingKeyGenerator.withAtLeastOneOverlappingByte(2); var adminKey = "adminKey"; var scheduledTxnKey = "scheduledTxnKey"; @@ -730,7 +697,7 @@ public HapiSpec okIfAdminKeyOverlapsWithActiveScheduleKey() { @HapiTest @Order(8) - public HapiSpec overlappingKeysTreatedAsExpected() { + final Stream overlappingKeysTreatedAsExpected() { var keyGen = OverlappingKeyGenerator.withAtLeastOneOverlappingByte(2); return defaultHapiSpec("OverlappingKeysTreatedAsExpected") @@ -770,7 +737,7 @@ public HapiSpec overlappingKeysTreatedAsExpected() { @HapiTest @Order(14) - public HapiSpec retestsActivationOnSignWithEmptySigMap() { + final Stream retestsActivationOnSignWithEmptySigMap() { return defaultHapiSpec("RetestsActivationOnCreateWithEmptySigMap") .given(newKeyNamed("a"), newKeyNamed("b"), newKeyListNamed("ab", List.of("a", "b")), newKeyNamed(ADMIN)) .when( @@ -789,7 +756,7 @@ public HapiSpec retestsActivationOnSignWithEmptySigMap() { @HapiTest @Order(18) - public HapiSpec signFailsDueToDeletedExpiration() { + final Stream signFailsDueToDeletedExpiration() { final int FAST_EXPIRATION = 0; return defaultHapiSpec("SignFailsDueToDeletedExpiration") .given( @@ -823,9 +790,4 @@ public HapiSpec signFailsDueToDeletedExpiration() { getScheduleInfo(TWO_SIG_XFER).hasCostAnswerPrecheck(INVALID_SCHEDULE_ID), overriding(LEDGER_SCHEDULE_TX_EXPIRY_TIME_SECS, "" + DEFAULT_TX_EXPIRY)); } - - @Override - protected Logger getResultsLogger() { - return log; - } } diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/schedule/ScheduleUtils.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/schedule/ScheduleUtils.java index d40f24e5c10a..a348e84d9ab5 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/schedule/ScheduleUtils.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/schedule/ScheduleUtils.java @@ -45,6 +45,8 @@ import java.util.Map; import java.util.function.Function; import java.util.function.Supplier; +import java.util.stream.Stream; +import org.junit.jupiter.api.DynamicTest; public final class ScheduleUtils { static final String ACCOUNT = "civilian"; @@ -285,7 +287,7 @@ static HapiSpecOperation addAllToWhitelist() { return overriding(SCHEDULING_WHITELIST, fullWhitelist); } - static HapiSpec enableLongTermScheduledTransactions() { + static Stream enableLongTermScheduledTransactions() { return defaultHapiSpec("EnableLongTermScheduledTransactions") .given() .when() @@ -294,7 +296,7 @@ static HapiSpec enableLongTermScheduledTransactions() { .overridingProps(Map.of(SCHEDULING_LONG_TERM_ENABLED, "true"))); } - static HapiSpec disableLongTermScheduledTransactions() { + static Stream disableLongTermScheduledTransactions() { return defaultHapiSpec("DisableLongTermScheduledTransactions") .given() .when() @@ -303,7 +305,7 @@ static HapiSpec disableLongTermScheduledTransactions() { .overridingProps(Map.of(SCHEDULING_LONG_TERM_ENABLED, FALSE))); } - static HapiSpec setLongTermScheduledTransactionsToDefault() { + static Stream setLongTermScheduledTransactionsToDefault() { return defaultHapiSpec("SetLongTermScheduledTransactionsToDefault") .given() .when() @@ -312,26 +314,27 @@ static HapiSpec setLongTermScheduledTransactionsToDefault() { .overridingProps(Map.of(SCHEDULING_LONG_TERM_ENABLED, DEFAULT_LONG_TERM_ENABLED))); } - static List withAndWithoutLongTermEnabled(Supplier> getSpecs) { - List list = new ArrayList<>(); + static List> withAndWithoutLongTermEnabled(Supplier>> getSpecs) { + List> list = new ArrayList<>(); list.add(disableLongTermScheduledTransactions()); list.addAll(getSpecs.get()); list.add(enableLongTermScheduledTransactions()); var withEnabled = getSpecs.get(); - withEnabled.forEach(s -> s.appendToName("WithLongTermEnabled")); - list.addAll(withEnabled); + list.add(withEnabled.stream().flatMap(Function.identity()).peek(s -> ((HapiSpec) s.getExecutable()) + .appendToName("WithLongTermEnabled"))); list.add(setLongTermScheduledTransactionsToDefault()); return list; } - static List withAndWithoutLongTermEnabled(Function> getSpecs) { - List list = new ArrayList<>(); + static List> withAndWithoutLongTermEnabled( + Function>> getSpecs) { + List> list = new ArrayList<>(); list.add(disableLongTermScheduledTransactions()); list.addAll(getSpecs.apply(false)); list.add(enableLongTermScheduledTransactions()); var withEnabled = getSpecs.apply(true); - withEnabled.forEach(s -> s.appendToName("WithLongTermEnabled")); - list.addAll(withEnabled); + list.add(withEnabled.stream().flatMap(Function.identity()).peek(s -> ((HapiSpec) s.getExecutable()) + .appendToName("WithLongTermEnabled"))); list.add(setLongTermScheduledTransactionsToDefault()); return list; } diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/streaming/RecordStreamValidation.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/streaming/RecordStreamValidation.java deleted file mode 100644 index 89593817848c..000000000000 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/streaming/RecordStreamValidation.java +++ /dev/null @@ -1,65 +0,0 @@ -/* - * Copyright (C) 2020-2024 Hedera Hashgraph, LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.hedera.services.bdd.suites.streaming; - -import static com.hedera.services.bdd.spec.HapiSpec.defaultHapiSpec; -import static com.hedera.services.bdd.spec.utilops.UtilVerbs.verifyRecordStreams; -import static com.hedera.services.bdd.spec.utilops.UtilVerbs.withOpContext; - -import com.hedera.services.bdd.spec.HapiPropertySource; -import com.hedera.services.bdd.spec.HapiSpec; -import com.hedera.services.bdd.suites.HapiSuite; -import java.util.List; -import java.util.concurrent.atomic.AtomicReference; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; - -public class RecordStreamValidation extends HapiSuite { - private static final Logger log = LogManager.getLogger(RecordStreamValidation.class); - - private static final String PATH_TO_LOCAL_STREAMS = "../data/recordstreams"; - - public static void main(String... args) { - new RecordStreamValidation().runSuiteSync(); - } - - @Override - public List getSpecsInSuite() { - return List.of(new HapiSpec[] { - recordStreamSanityChecks(), - }); - } - - final HapiSpec recordStreamSanityChecks() { - AtomicReference pathToStreams = new AtomicReference<>(PATH_TO_LOCAL_STREAMS); - - return defaultHapiSpec("RecordStreamSanityChecks") - .given(withOpContext((spec, opLog) -> { - HapiPropertySource ciProps = spec.setup().ciPropertiesMap(); - if (ciProps.has("recordStreamsDir")) { - pathToStreams.set(ciProps.get("recordStreamsDir")); - } - })) - .when() - .then(verifyRecordStreams(pathToStreams::get)); - } - - @Override - protected Logger getResultsLogger() { - return log; - } -} diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/streaming/RunTransfers.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/streaming/RunTransfers.java index e327b3a0dbf1..1398eb13bf6f 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/streaming/RunTransfers.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/streaming/RunTransfers.java @@ -36,8 +36,10 @@ import java.util.concurrent.atomic.AtomicReference; import java.util.function.Function; import java.util.stream.IntStream; +import java.util.stream.Stream; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; +import org.junit.jupiter.api.DynamicTest; public class RunTransfers extends HapiSuite { private static final Logger log = LogManager.getLogger(RunTransfers.class); @@ -54,13 +56,11 @@ public static void main(String... args) { } @Override - public List getSpecsInSuite() { - return List.of(new HapiSpec[] { - runTransfers(), - }); + public List> getSpecsInSuite() { + return List.of(runTransfers()); } - final HapiSpec runTransfers() { + final Stream runTransfers() { return defaultHapiSpec("RunTransfers") .given() .when() diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/throttling/GasLimitThrottlingSuite.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/throttling/GasLimitThrottlingSuite.java index 48c78ae065a0..289a78bc6314 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/throttling/GasLimitThrottlingSuite.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/throttling/GasLimitThrottlingSuite.java @@ -24,81 +24,26 @@ import static com.hedera.services.bdd.spec.utilops.UtilVerbs.overridingAllOfDeferred; import static com.hedera.services.bdd.spec.utilops.UtilVerbs.overridingTwo; import static com.hedera.services.bdd.spec.utilops.UtilVerbs.remembering; +import static com.hedera.services.bdd.suites.HapiSuite.ONE_MILLION_HBARS; import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.BUSY; import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.MAX_GAS_LIMIT_EXCEEDED; -import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.OK; -import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.SUCCESS; -import com.hedera.services.bdd.junit.HapiTest; -import com.hedera.services.bdd.junit.HapiTestSuite; -import com.hedera.services.bdd.spec.HapiSpec; -import com.hedera.services.bdd.spec.utilops.UtilVerbs; -import com.hedera.services.bdd.suites.HapiSuite; +import com.hedera.services.bdd.junit.ContextRequirement; +import com.hedera.services.bdd.junit.LeakyHapiTest; import java.util.HashMap; -import java.util.List; import java.util.Map; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; +import java.util.stream.Stream; import org.apache.tuweni.bytes.Bytes; +import org.junit.jupiter.api.DynamicTest; -@HapiTestSuite -public class GasLimitThrottlingSuite extends HapiSuite { - - private static final Logger log = LogManager.getLogger(GasLimitThrottlingSuite.class); +public class GasLimitThrottlingSuite { private static final String CONTRACT = "Benchmark"; private static final String USE_GAS_THROTTLE_PROP = "contracts.throttle.throttleByGas"; private static final String CONS_MAX_GAS_PROP = "contracts.maxGasPerSec"; public static final String PAYER_ACCOUNT = "payerAccount"; - @Override - public List getSpecsInSuite() { - return List.of(txsUnderGasLimitAllowed(), txOverGasLimitThrottled()); - } - - public static void main(String... args) { - new GasLimitThrottlingSuite().runSuiteSync(); - } - - @HapiTest - final HapiSpec txsUnderGasLimitAllowed() { - final var NUM_CALLS = 10; - final Map startingProps = new HashMap<>(); - return defaultHapiSpec("TXsUnderGasLimitAllowed") - .given( - remembering(startingProps, USE_GAS_THROTTLE_PROP, CONS_MAX_GAS_PROP), - overridingTwo( - USE_GAS_THROTTLE_PROP, "true", - CONS_MAX_GAS_PROP, "10000000")) - .when( - /* we need the payer account, see SystemPrecheck IS_THROTTLE_EXEMPT */ - cryptoCreate(PAYER_ACCOUNT).balance(ONE_MILLION_HBARS), - uploadInitCode(CONTRACT), - contractCreate(CONTRACT).payingWith(PAYER_ACCOUNT)) - .then( - UtilVerbs.inParallel(asOpArray(NUM_CALLS, i -> contractCall( - CONTRACT, - "twoSSTOREs", - Bytes.fromHexString( - "0x0000000000000000000000000000000000000000000000000000000000000005") - .toArray()) - .gas(100_000) - .payingWith(PAYER_ACCOUNT) - .hasKnownStatusFrom(SUCCESS, OK))), - UtilVerbs.sleepFor(1000), - contractCall( - CONTRACT, - "twoSSTOREs", - Bytes.fromHexString( - "0x0000000000000000000000000000000000000000000000000000000000000006") - .toArray()) - .gas(1_000_000L) - .payingWith(PAYER_ACCOUNT) - .hasKnownStatusFrom(SUCCESS, OK), - overridingAllOfDeferred(() -> startingProps)); - } - - @HapiTest - final HapiSpec txOverGasLimitThrottled() { + @LeakyHapiTest(ContextRequirement.PROPERTY_OVERRIDES) + final Stream txOverGasLimitThrottled() { final Map startingProps = new HashMap<>(); final var MAX_GAS_PER_SECOND = 1_000_001L; return defaultHapiSpec("TXOverGasLimitThrottled") @@ -122,9 +67,4 @@ final HapiSpec txOverGasLimitThrottled() { .hasPrecheckFrom(MAX_GAS_LIMIT_EXCEEDED, BUSY), overridingAllOfDeferred(() -> startingProps)); } - - @Override - protected Logger getResultsLogger() { - return log; - } } diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/throttling/PrivilegedOpsSuite.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/throttling/PrivilegedOpsSuite.java index 9743da7e0c0e..49ae75876c72 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/throttling/PrivilegedOpsSuite.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/throttling/PrivilegedOpsSuite.java @@ -17,6 +17,7 @@ package com.hedera.services.bdd.suites.throttling; import static com.hedera.services.bdd.spec.HapiSpec.defaultHapiSpec; +import static com.hedera.services.bdd.spec.HapiSpec.hapiTest; import static com.hedera.services.bdd.spec.queries.QueryVerbs.getAccountInfo; import static com.hedera.services.bdd.spec.queries.QueryVerbs.getTopicInfo; import static com.hedera.services.bdd.spec.queries.QueryVerbs.getTxnRecord; @@ -30,26 +31,33 @@ import static com.hedera.services.bdd.spec.utilops.UtilVerbs.inParallel; import static com.hedera.services.bdd.spec.utilops.UtilVerbs.newKeyNamed; import static com.hedera.services.bdd.spec.utilops.UtilVerbs.sleepFor; +import static com.hedera.services.bdd.suites.HapiSuite.ADDRESS_BOOK_CONTROL; +import static com.hedera.services.bdd.suites.HapiSuite.EXCHANGE_RATE_CONTROL; +import static com.hedera.services.bdd.suites.HapiSuite.FEE_SCHEDULE_CONTROL; +import static com.hedera.services.bdd.suites.HapiSuite.FREEZE_ADMIN; +import static com.hedera.services.bdd.suites.HapiSuite.FUNDING; +import static com.hedera.services.bdd.suites.HapiSuite.GENESIS; +import static com.hedera.services.bdd.suites.HapiSuite.ONE_HUNDRED_HBARS; +import static com.hedera.services.bdd.suites.HapiSuite.ONE_MILLION_HBARS; +import static com.hedera.services.bdd.suites.HapiSuite.SOFTWARE_UPDATE_ADMIN; +import static com.hedera.services.bdd.suites.HapiSuite.SYSTEM_ADMIN; +import static com.hedera.services.bdd.suites.HapiSuite.THROTTLE_DEFS; +import static com.hedera.services.bdd.suites.HapiSuite.UPDATE_ZIP_FILE; +import static com.hedera.services.bdd.suites.HapiSuite.flattened; import static com.hedera.services.bdd.suites.utils.sysfiles.serdes.ThrottleDefsLoader.protoDefsFromResource; import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.AUTHORIZATION_FAILED; import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.INVALID_SIGNATURE; import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.SUCCESS_BUT_MISSING_EXPECTED_OPERATION; import com.hedera.services.bdd.junit.HapiTest; -import com.hedera.services.bdd.junit.HapiTestSuite; -import com.hedera.services.bdd.spec.HapiSpec; +import com.hedera.services.bdd.junit.LeakyHapiTest; import com.hedera.services.bdd.spec.HapiSpecOperation; -import com.hedera.services.bdd.suites.HapiSuite; -import java.util.List; import java.util.function.Function; import java.util.stream.IntStream; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; - -@HapiTestSuite -public class PrivilegedOpsSuite extends HapiSuite { - private static final Logger log = LogManager.getLogger(PrivilegedOpsSuite.class); +import java.util.stream.Stream; +import org.junit.jupiter.api.DynamicTest; +public class PrivilegedOpsSuite { private static final byte[] totalLimits = protoDefsFromResource("testSystemFiles/only-mint-allowed.json").toByteArray(); private static final byte[] defaultThrottles = @@ -60,22 +68,6 @@ public class PrivilegedOpsSuite extends HapiSuite { private static final String NEW_88 = "new88"; private static final int BURST_SIZE = 10; - public static void main(String... args) { - new PrivilegedOpsSuite().runSuiteSync(); - } - - @Override - public List getSpecsInSuite() { - return List.of( - superusersAreNeverThrottledOnTransfers(), - superusersAreNeverThrottledOnMiscTxns(), - superusersAreNeverThrottledOnHcsTxns(), - superusersAreNeverThrottledOnMiscQueries(), - superusersAreNeverThrottledOnHcsQueries(), - systemAccountUpdatePrivilegesAsExpected(), - freezeAdminPrivilegesAsExpected()); - } - Function transferBurstFn = payer -> IntStream.range(0, BURST_SIZE) .mapToObj(ignore -> cryptoTransfer(tinyBarsFromTo(GENESIS, FUNDING, 1L)) .payingWith(payer) @@ -100,7 +92,7 @@ public List getSpecsInSuite() { .toArray(HapiSpecOperation[]::new); @HapiTest - final HapiSpec freezeAdminPrivilegesAsExpected() { + final Stream freezeAdminPrivilegesAsExpected() { return defaultHapiSpec("freezeAdminPrivilegesAsExpected") .given( cryptoCreate(CIVILIAN), @@ -146,8 +138,9 @@ final HapiSpec freezeAdminPrivilegesAsExpected() { fileAppend(UPDATE_ZIP_FILE).fee(0L).payingWith(GENESIS).content(new byte[0])); } - @HapiTest - final HapiSpec systemAccountUpdatePrivilegesAsExpected() { + // Temporarily changes system account keys + @LeakyHapiTest + final Stream systemAccountUpdatePrivilegesAsExpected() { final var tmpTreasury = "tmpTreasury"; return defaultHapiSpec("systemAccountUpdatePrivilegesAsExpected") .given(newKeyNamed(tmpTreasury), newKeyNamed(NEW_88), cryptoCreate(CIVILIAN)) @@ -200,105 +193,29 @@ final HapiSpec systemAccountUpdatePrivilegesAsExpected() { .signedBy(SYSTEM_ADMIN, GENESIS)); } - @HapiTest - final HapiSpec superusersAreNeverThrottledOnTransfers() { - return defaultHapiSpec("superusersAreNeverThrottledOnTransfers") - .given( - cryptoTransfer(tinyBarsFromTo(GENESIS, ADDRESS_BOOK_CONTROL, 1_000_000_000_000L)) - .fee(ONE_HUNDRED_HBARS), - cryptoTransfer(tinyBarsFromTo(GENESIS, SYSTEM_ADMIN, 1_000_000_000_000L)) - .fee(ONE_HUNDRED_HBARS)) - .when(fileUpdate(THROTTLE_DEFS) - .payingWith(EXCHANGE_RATE_CONTROL) - .contents(totalLimits) - .hasKnownStatus(SUCCESS_BUT_MISSING_EXPECTED_OPERATION)) - .then(flattened( - transferBurstFn.apply(SYSTEM_ADMIN), - transferBurstFn.apply(ADDRESS_BOOK_CONTROL), - sleepFor(5_000L), - fileUpdate(THROTTLE_DEFS) - .payingWith(EXCHANGE_RATE_CONTROL) - .contents(defaultThrottles))); - } - - @HapiTest - final HapiSpec superusersAreNeverThrottledOnMiscTxns() { - return defaultHapiSpec("superusersAreNeverThrottledOnMiscTxns") - .given( - cryptoTransfer(tinyBarsFromTo(GENESIS, ADDRESS_BOOK_CONTROL, 1_000_000_000_000L)) - .fee(ONE_HUNDRED_HBARS), - cryptoTransfer(tinyBarsFromTo(GENESIS, SYSTEM_ADMIN, 1_000_000_000_000L)) - .fee(ONE_HUNDRED_HBARS)) - .when(fileUpdate(THROTTLE_DEFS) + @LeakyHapiTest + final Stream systemAccountsAreNeverThrottled() { + return hapiTest(flattened( + cryptoTransfer(tinyBarsFromTo(GENESIS, ADDRESS_BOOK_CONTROL, 1_000_000_000_000L)) + .fee(ONE_HUNDRED_HBARS), + cryptoTransfer(tinyBarsFromTo(GENESIS, SYSTEM_ADMIN, 1_000_000_000_000L)) + .fee(ONE_HUNDRED_HBARS), + fileUpdate(THROTTLE_DEFS) .payingWith(EXCHANGE_RATE_CONTROL) .contents(totalLimits) - .hasKnownStatus(SUCCESS_BUT_MISSING_EXPECTED_OPERATION)) - .then(flattened( - miscTxnBurstFn.apply(SYSTEM_ADMIN), - miscTxnBurstFn.apply(ADDRESS_BOOK_CONTROL), - sleepFor(5_000L), - fileUpdate(THROTTLE_DEFS) - .payingWith(EXCHANGE_RATE_CONTROL) - .contents(defaultThrottles))); - } - - @HapiTest - final HapiSpec superusersAreNeverThrottledOnHcsTxns() { - return defaultHapiSpec("superusersAreNeverThrottledOnHcsTxns") - .given( - cryptoTransfer(tinyBarsFromTo(GENESIS, ADDRESS_BOOK_CONTROL, 1_000_000_000_000L)), - cryptoTransfer(tinyBarsFromTo(GENESIS, SYSTEM_ADMIN, 1_000_000_000_000L))) - .when(fileUpdate(THROTTLE_DEFS) - .payingWith(EXCHANGE_RATE_CONTROL) - .contents(totalLimits) - .hasKnownStatus(SUCCESS_BUT_MISSING_EXPECTED_OPERATION)) - .then(flattened( - hcsTxnBurstFn.apply(SYSTEM_ADMIN), - hcsTxnBurstFn.apply(ADDRESS_BOOK_CONTROL), - fileUpdate(THROTTLE_DEFS) - .payingWith(EXCHANGE_RATE_CONTROL) - .contents(defaultThrottles))); - } - - @HapiTest - final HapiSpec superusersAreNeverThrottledOnMiscQueries() { - return defaultHapiSpec("superusersAreNeverThrottledOnMiscQueries") - .given( - cryptoTransfer(tinyBarsFromTo(GENESIS, ADDRESS_BOOK_CONTROL, 1_000_000_000_000L)), - cryptoTransfer(tinyBarsFromTo(GENESIS, SYSTEM_ADMIN, 1_000_000_000_000L))) - .when(fileUpdate(THROTTLE_DEFS) - .payingWith(EXCHANGE_RATE_CONTROL) - .contents(totalLimits) - .hasKnownStatus(SUCCESS_BUT_MISSING_EXPECTED_OPERATION)) - .then(flattened( - inParallel(miscQueryBurstFn.apply(SYSTEM_ADMIN)), - inParallel(miscQueryBurstFn.apply(ADDRESS_BOOK_CONTROL)), - fileUpdate(THROTTLE_DEFS) - .payingWith(EXCHANGE_RATE_CONTROL) - .contents(defaultThrottles))); - } - - @HapiTest - final HapiSpec superusersAreNeverThrottledOnHcsQueries() { - return defaultHapiSpec("superusersAreNeverThrottledOnHcsQueries") - .given( - cryptoTransfer(tinyBarsFromTo(GENESIS, ADDRESS_BOOK_CONTROL, 1_000_000_000_000L)), - cryptoTransfer(tinyBarsFromTo(GENESIS, SYSTEM_ADMIN, 1_000_000_000_000L)), - createTopic("misc")) - .when(fileUpdate(THROTTLE_DEFS) - .payingWith(EXCHANGE_RATE_CONTROL) - .contents(totalLimits) - .hasKnownStatus(SUCCESS_BUT_MISSING_EXPECTED_OPERATION)) - .then(flattened( - inParallel(hcsQueryBurstFn.apply(SYSTEM_ADMIN)), - inParallel(hcsQueryBurstFn.apply(ADDRESS_BOOK_CONTROL)), - fileUpdate(THROTTLE_DEFS) - .payingWith(EXCHANGE_RATE_CONTROL) - .contents(defaultThrottles))); - } - - @Override - protected Logger getResultsLogger() { - return log; + .hasKnownStatus(SUCCESS_BUT_MISSING_EXPECTED_OPERATION), + transferBurstFn.apply(SYSTEM_ADMIN), + transferBurstFn.apply(ADDRESS_BOOK_CONTROL), + miscTxnBurstFn.apply(SYSTEM_ADMIN), + miscTxnBurstFn.apply(ADDRESS_BOOK_CONTROL), + hcsTxnBurstFn.apply(SYSTEM_ADMIN), + hcsTxnBurstFn.apply(ADDRESS_BOOK_CONTROL), + inParallel(miscQueryBurstFn.apply(SYSTEM_ADMIN)), + inParallel(miscQueryBurstFn.apply(ADDRESS_BOOK_CONTROL)), + createTopic("misc"), + inParallel(hcsQueryBurstFn.apply(SYSTEM_ADMIN)), + inParallel(hcsQueryBurstFn.apply(ADDRESS_BOOK_CONTROL)), + sleepFor(5_000L), + fileUpdate(THROTTLE_DEFS).payingWith(EXCHANGE_RATE_CONTROL).contents(defaultThrottles))); } } diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/throttling/ThrottleDefValidationSuite.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/throttling/ThrottleDefValidationSuite.java index 8cbddaabf6e2..1ca2e4f6d7ba 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/throttling/ThrottleDefValidationSuite.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/throttling/ThrottleDefValidationSuite.java @@ -21,6 +21,12 @@ import static com.hedera.services.bdd.spec.transactions.TxnVerbs.cryptoTransfer; import static com.hedera.services.bdd.spec.transactions.TxnVerbs.fileUpdate; import static com.hedera.services.bdd.spec.transactions.token.TokenMovement.movingHbar; +import static com.hedera.services.bdd.suites.HapiSuite.APP_PROPERTIES; +import static com.hedera.services.bdd.suites.HapiSuite.EXCHANGE_RATE_CONTROL; +import static com.hedera.services.bdd.suites.HapiSuite.FEE_SCHEDULE_CONTROL; +import static com.hedera.services.bdd.suites.HapiSuite.GENESIS; +import static com.hedera.services.bdd.suites.HapiSuite.ONE_HUNDRED_HBARS; +import static com.hedera.services.bdd.suites.HapiSuite.THROTTLE_DEFS; import static com.hedera.services.bdd.suites.utils.sysfiles.serdes.ThrottleDefsLoader.protoDefsFromResource; import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.AUTHORIZATION_FAILED; import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.INVALID_TRANSACTION; @@ -29,44 +35,21 @@ import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.THROTTLE_GROUP_HAS_ZERO_OPS_PER_SEC; import com.hedera.services.bdd.junit.HapiTest; -import com.hedera.services.bdd.junit.HapiTestSuite; -import com.hedera.services.bdd.spec.HapiSpec; import com.hedera.services.bdd.spec.HapiSpecSetup; -import com.hedera.services.bdd.suites.HapiSuite; -import java.util.List; import java.util.Map; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; +import java.util.stream.Stream; +import org.junit.jupiter.api.DynamicTest; import org.junit.jupiter.api.MethodOrderer.OrderAnnotation; import org.junit.jupiter.api.Order; import org.junit.jupiter.api.TestMethodOrder; -@HapiTestSuite @TestMethodOrder(OrderAnnotation.class) -public class ThrottleDefValidationSuite extends HapiSuite { - - private static final Logger log = LogManager.getLogger(ThrottleDefValidationSuite.class); - +public class ThrottleDefValidationSuite { private static final String DEFAULT_CONGESTION_MULTIPLIERS = HapiSpecSetup.getDefaultNodeProps().get("fees.percentCongestionMultipliers"); - public static void main(String... args) { - new ThrottleDefValidationSuite().runSuiteSync(); - } - - @Override - public List getSpecsInSuite() { - return List.of(new HapiSpec[] { - throttleDefsRejectUnauthorizedPayers(), - throttleUpdateRejectsMultiGroupAssignment(), - throttleUpdateWithZeroGroupOpsPerSecFails(), - updateWithMissingTokenMintFails(), - ensureDefaultsRestored() - }); - } - @HapiTest - final HapiSpec updateWithMissingTokenMintFails() { + final Stream updateWithMissingTokenMintFails() { var missingMintThrottles = protoDefsFromResource("testSystemFiles/throttles-sans-mint.json"); return defaultHapiSpec("updateWithMissingTokenMintFails") @@ -80,7 +63,7 @@ final HapiSpec updateWithMissingTokenMintFails() { @HapiTest @Order(100) // this needs to be executed after all other tests - final HapiSpec ensureDefaultsRestored() { + final Stream ensureDefaultsRestored() { var defaultThrottles = protoDefsFromResource("testSystemFiles/throttles-dev.json"); return defaultHapiSpec("EnsureDefaultsRestored") @@ -97,7 +80,7 @@ final HapiSpec ensureDefaultsRestored() { } @HapiTest - final HapiSpec throttleUpdateWithZeroGroupOpsPerSecFails() { + final Stream throttleUpdateWithZeroGroupOpsPerSecFails() { var zeroOpsPerSecThrottles = protoDefsFromResource("testSystemFiles/zero-ops-group.json"); return defaultHapiSpec("ThrottleUpdateWithZeroGroupOpsPerSecFails") @@ -110,7 +93,7 @@ final HapiSpec throttleUpdateWithZeroGroupOpsPerSecFails() { } @HapiTest - final HapiSpec throttleUpdateRejectsMultiGroupAssignment() { + final Stream throttleUpdateRejectsMultiGroupAssignment() { var multiGroupThrottles = protoDefsFromResource("testSystemFiles/duplicated-operation.json"); return defaultHapiSpec("ThrottleUpdateRejectsMultiGroupAssignment") @@ -123,7 +106,7 @@ final HapiSpec throttleUpdateRejectsMultiGroupAssignment() { } @HapiTest - final HapiSpec throttleDefsRejectUnauthorizedPayers() { + final Stream throttleDefsRejectUnauthorizedPayers() { return defaultHapiSpec("ThrottleDefsRejectUnauthorizedPayers") .given( cryptoCreate("civilian"), @@ -139,9 +122,4 @@ final HapiSpec throttleDefsRejectUnauthorizedPayers() { .payingWith(FEE_SCHEDULE_CONTROL) .hasPrecheck(AUTHORIZATION_FAILED)); } - - @Override - protected Logger getResultsLogger() { - return log; - } } diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/token/Hip17UnhappyAccountsSuite.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/token/Hip17UnhappyAccountsSuite.java index 53085bdbf717..b7908cd97ee4 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/token/Hip17UnhappyAccountsSuite.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/token/Hip17UnhappyAccountsSuite.java @@ -38,20 +38,14 @@ import com.google.protobuf.ByteString; import com.hedera.services.bdd.junit.HapiTest; -import com.hedera.services.bdd.junit.HapiTestSuite; -import com.hedera.services.bdd.spec.HapiSpec; -import com.hedera.services.bdd.suites.HapiSuite; import com.hederahashgraph.api.proto.java.TokenType; import java.util.List; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; +import java.util.stream.Stream; +import org.junit.jupiter.api.DynamicTest; import org.junit.jupiter.api.Tag; -@HapiTestSuite @Tag(TOKEN) -public class Hip17UnhappyAccountsSuite extends HapiSuite { - - private static final Logger log = LogManager.getLogger(Hip17UnhappyAccountsSuite.class); +public class Hip17UnhappyAccountsSuite { private static final String MEMO_1 = "memo1"; private static final String MEMO_2 = "memo2"; @@ -64,26 +58,8 @@ public class Hip17UnhappyAccountsSuite extends HapiSuite { private static final String TREASURY = "treasury"; private static final String UNIQUE_TOKEN_A = "TokenA"; - public static void main(String... args) { - new Hip17UnhappyAccountsSuite().runSuiteSync(); - } - - @Override - public List getSpecsInSuite() { - return List.of(new HapiSpec[] { - /* Dissociated Account */ - uniqueTokenOperationsFailForDissociatedAccount(), - /* Frozen Account */ - uniqueTokenOperationsFailForFrozenAccount(), - /* Account Without KYC */ - uniqueTokenOperationsFailForKycRevokedAccount(), - /* Deleted Account */ - uniqueTokenOperationsFailForDeletedAccount(), - }); - } - @HapiTest - final HapiSpec uniqueTokenOperationsFailForDeletedAccount() { + final Stream uniqueTokenOperationsFailForDeletedAccount() { return defaultHapiSpec("UniqueTokenOperationsFailForDeletedAccount") .given( newKeyNamed(SUPPLY_KEY), @@ -117,7 +93,7 @@ final HapiSpec uniqueTokenOperationsFailForDeletedAccount() { } @HapiTest - final HapiSpec uniqueTokenOperationsFailForKycRevokedAccount() { + final Stream uniqueTokenOperationsFailForKycRevokedAccount() { return defaultHapiSpec("UniqueTokenOperationsFailForKycRevokedAccount") .given( newKeyNamed(SUPPLY_KEY), @@ -154,7 +130,7 @@ final HapiSpec uniqueTokenOperationsFailForKycRevokedAccount() { } @HapiTest - final HapiSpec uniqueTokenOperationsFailForFrozenAccount() { + final Stream uniqueTokenOperationsFailForFrozenAccount() { return defaultHapiSpec("UniqueTokenOperationsFailForFrozenAccount") .given( newKeyNamed(SUPPLY_KEY), @@ -191,7 +167,7 @@ final HapiSpec uniqueTokenOperationsFailForFrozenAccount() { } @HapiTest - final HapiSpec uniqueTokenOperationsFailForDissociatedAccount() { + final Stream uniqueTokenOperationsFailForDissociatedAccount() { return defaultHapiSpec("UniqueTokenOperationsFailForDissociatedAccount") .given( newKeyNamed(SUPPLY_KEY), @@ -220,9 +196,4 @@ final HapiSpec uniqueTokenOperationsFailForDissociatedAccount() { cryptoTransfer(movingUnique(UNIQUE_TOKEN_A, 1L).between(TREASURY, CLIENT_1)) .hasKnownStatus(TOKEN_NOT_ASSOCIATED_TO_ACCOUNT)); } - - @Override - protected Logger getResultsLogger() { - return log; - } } diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/token/Hip17UnhappyTokensSuite.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/token/Hip17UnhappyTokensSuite.java index 38996df674e9..2e788cb40d09 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/token/Hip17UnhappyTokensSuite.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/token/Hip17UnhappyTokensSuite.java @@ -42,8 +42,13 @@ import static com.hedera.services.bdd.spec.utilops.UtilVerbs.newKeyNamed; import static com.hedera.services.bdd.spec.utilops.UtilVerbs.sleepFor; import static com.hedera.services.bdd.spec.utilops.UtilVerbs.sourcing; +import static com.hedera.services.bdd.suites.HapiSuite.APP_PROPERTIES; +import static com.hedera.services.bdd.suites.HapiSuite.GENESIS; +import static com.hedera.services.bdd.suites.HapiSuite.ONE_HUNDRED_HBARS; +import static com.hedera.services.bdd.suites.HapiSuite.THREE_MONTHS_IN_SECONDS; +import static com.hedera.services.bdd.suites.HapiSuite.ZERO_BYTE_MEMO; +import static com.hedera.services.bdd.suites.HapiSuite.salted; import static com.hedera.services.bdd.suites.autorenew.AutoRenewConfigChoices.disablingAutoRenewWithDefaults; -import static com.hedera.services.bdd.suites.perf.PerfUtilOps.tokenOpsEnablement; import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.INVALID_ZERO_BYTE_IN_STRING; import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.OK; import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.SUCCESS; @@ -52,26 +57,19 @@ import com.google.protobuf.ByteString; import com.hedera.services.bdd.junit.HapiTest; -import com.hedera.services.bdd.junit.HapiTestSuite; -import com.hedera.services.bdd.spec.HapiSpec; import com.hedera.services.bdd.spec.transactions.token.TokenMovement; -import com.hedera.services.bdd.suites.HapiSuite; import com.hedera.services.bdd.suites.autorenew.AutoRenewConfigChoices; import com.hederahashgraph.api.proto.java.TokenSupplyType; import com.hederahashgraph.api.proto.java.TokenType; import java.time.Instant; import java.util.List; import java.util.Set; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; +import java.util.stream.Stream; +import org.junit.jupiter.api.DynamicTest; import org.junit.jupiter.api.Tag; -@HapiTestSuite @Tag(TOKEN) -public class Hip17UnhappyTokensSuite extends HapiSuite { - - private static final Logger log = LogManager.getLogger(Hip17UnhappyTokensSuite.class); - +public class Hip17UnhappyTokensSuite { private static final String ANOTHER_USER = "AnotherUser"; private static final String ANOTHER_KEY = "AnotherKey"; @@ -100,35 +98,8 @@ public class Hip17UnhappyTokensSuite extends HapiSuite { private static String SALTED_NAME = salted("primary"); private static String NEW_SALTED_NAME = salted("primary"); - public static void main(String... args) { - new Hip17UnhappyTokensSuite().runSuiteSync(); - } - - @Override - public List getSpecsInSuite() { - return List.of( - canStillGetNftInfoWhenDeleted(), - cannotWipeNftWhenDeleted(), - cannotBurnNftWhenDeleted(), - cannotMintNftWhenDeleted(), - cannotDissociateNftWhenDeleted(), - cannotAssociateNftWhenDeleted(), - cannotUpdateNftWhenDeleted(), - cannotUpdateNftFeeScheduleWhenDeleted(), - cannotTransferNftWhenDeleted(), - cannotFreezeNftWhenDeleted(), - cannotUnfreezeNftWhenDeleted() - - // TODO: when auto removal and expiry implemented, enable the following and - // also add all those scenarios like above to complete the matrix. - // cannotGetNftInfoWhenExpired() - // cannotGetNftInfoWhenAutoRemoved(), - // autoRemovalCasesSuiteCleanup() - ); - } - @HapiTest - final HapiSpec canStillGetNftInfoWhenDeleted() { + final Stream canStillGetNftInfoWhenDeleted() { return defaultHapiSpec("canStillGetNftInfoWhenDeleted") .given( newKeyNamed(SUPPLY_KEY), @@ -147,7 +118,7 @@ final HapiSpec canStillGetNftInfoWhenDeleted() { } @HapiTest - final HapiSpec cannotTransferNftWhenDeleted() { + final Stream cannotTransferNftWhenDeleted() { return defaultHapiSpec("cannotTransferNftWhenDeleted") .given( newKeyNamed(SUPPLY_KEY), @@ -172,7 +143,7 @@ final HapiSpec cannotTransferNftWhenDeleted() { } @HapiTest - final HapiSpec cannotUnfreezeNftWhenDeleted() { + final Stream cannotUnfreezeNftWhenDeleted() { return defaultHapiSpec("cannotUnfreezeNftWhenDeleted") .given( newKeyNamed(SUPPLY_KEY), @@ -192,7 +163,7 @@ final HapiSpec cannotUnfreezeNftWhenDeleted() { } @HapiTest - final HapiSpec cannotFreezeNftWhenDeleted() { + final Stream cannotFreezeNftWhenDeleted() { return defaultHapiSpec("cannotFreezeNftWhenDeleted") .given( newKeyNamed(SUPPLY_KEY), @@ -211,7 +182,7 @@ final HapiSpec cannotFreezeNftWhenDeleted() { } @HapiTest - final HapiSpec cannotDissociateNftWhenDeleted() { + final Stream cannotDissociateNftWhenDeleted() { return defaultHapiSpec("cannotDissociateNftWhenDeleted") .given( newKeyNamed(ADMIN_KEY), @@ -231,7 +202,7 @@ final HapiSpec cannotDissociateNftWhenDeleted() { } @HapiTest - final HapiSpec cannotAssociateNftWhenDeleted() { + final Stream cannotAssociateNftWhenDeleted() { return defaultHapiSpec("cannotAssociateNftWhenDeleted") .given( newKeyNamed(ADMIN_KEY), @@ -250,7 +221,7 @@ final HapiSpec cannotAssociateNftWhenDeleted() { } @HapiTest // transferList differ - public HapiSpec cannotUpdateNftWhenDeleted() { + final Stream cannotUpdateNftWhenDeleted() { return defaultHapiSpec("cannotUpdateNftWhenDeleted") .given( fileUpdate(APP_PROPERTIES) @@ -327,7 +298,7 @@ public HapiSpec cannotUpdateNftWhenDeleted() { } @HapiTest - final HapiSpec cannotUpdateNftFeeScheduleWhenDeleted() { + final Stream cannotUpdateNftFeeScheduleWhenDeleted() { final var origHbarFee = 1_234L; final var newHbarFee = 4_321L; final var hbarCollector = "hbarFee"; @@ -354,7 +325,7 @@ final HapiSpec cannotUpdateNftFeeScheduleWhenDeleted() { } @HapiTest - final HapiSpec cannotMintNftWhenDeleted() { + final Stream cannotMintNftWhenDeleted() { return defaultHapiSpec("cannotMintNftWhenDeleted") .given( newKeyNamed(SUPPLY_KEY), @@ -375,7 +346,7 @@ final HapiSpec cannotMintNftWhenDeleted() { } @HapiTest - final HapiSpec cannotBurnNftWhenDeleted() { + final Stream cannotBurnNftWhenDeleted() { return defaultHapiSpec("cannotBurnNftWhenDeleted") .given( newKeyNamed(SUPPLY_KEY), @@ -406,7 +377,7 @@ final HapiSpec cannotBurnNftWhenDeleted() { } @HapiTest - final HapiSpec cannotWipeNftWhenDeleted() { + final Stream cannotWipeNftWhenDeleted() { return defaultHapiSpec("cannotWipeNftWhenDeleted") .given( newKeyNamed(SUPPLY_KEY), @@ -438,10 +409,9 @@ final HapiSpec cannotWipeNftWhenDeleted() { } @HapiTest - final HapiSpec cannotGetNftInfoWhenExpired() { + final Stream cannotGetNftInfoWhenExpired() { return defaultHapiSpec("cannotGetNftInfoWhenExpired") .given( - tokenOpsEnablement(), fileUpdate(APP_PROPERTIES) .payingWith(GENESIS) .overridingProps(AutoRenewConfigChoices.propsForAccountAutoRenewOnWith(1, 0L, 2, 2)), @@ -460,10 +430,9 @@ final HapiSpec cannotGetNftInfoWhenExpired() { } @HapiTest - final HapiSpec cannotGetNftInfoWhenAutoRemoved() { + final Stream cannotGetNftInfoWhenAutoRemoved() { return defaultHapiSpec("cannotGetNftInfoWhenAutoRemoved") .given( - tokenOpsEnablement(), fileUpdate(APP_PROPERTIES) .payingWith(GENESIS) .overridingProps(AutoRenewConfigChoices.propsForAccountAutoRenewOnWith(1, 0L, 100, 100)) @@ -500,7 +469,7 @@ final HapiSpec cannotGetNftInfoWhenAutoRemoved() { } @HapiTest - final HapiSpec autoRemovalCasesSuiteCleanup() { + final Stream autoRemovalCasesSuiteCleanup() { return defaultHapiSpec("AutoRemovalCasesSuiteCleanup") .given() .when() @@ -510,9 +479,4 @@ final HapiSpec autoRemovalCasesSuiteCleanup() { private ByteString metadata(String contents) { return ByteString.copyFromUtf8(contents); } - - @Override - protected Logger getResultsLogger() { - return log; - } } diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/token/TokenAssociationSpecs.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/token/TokenAssociationSpecs.java index d9524d41c454..2b08949f4dc0 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/token/TokenAssociationSpecs.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/token/TokenAssociationSpecs.java @@ -16,6 +16,7 @@ package com.hedera.services.bdd.suites.token; +import static com.hedera.services.bdd.junit.ContextRequirement.PROPERTY_OVERRIDES; import static com.hedera.services.bdd.junit.TestTags.TOKEN; import static com.hedera.services.bdd.spec.HapiSpec.defaultHapiSpec; import static com.hedera.services.bdd.spec.HapiSpec.propertyPreservingHapiSpec; @@ -52,6 +53,8 @@ import static com.hedera.services.bdd.spec.utilops.UtilVerbs.submitModified; import static com.hedera.services.bdd.spec.utilops.UtilVerbs.withOpContext; import static com.hedera.services.bdd.spec.utilops.mod.ModificationUtils.withSuccessivelyVariedBodyIds; +import static com.hedera.services.bdd.suites.HapiSuite.DEFAULT_PAYER; +import static com.hedera.services.bdd.suites.HapiSuite.TOKEN_TREASURY; import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.ACCOUNT_DELETED; import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.ACCOUNT_FROZEN_FOR_TOKEN; import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.ACCOUNT_IS_TREASURY; @@ -73,25 +76,19 @@ import com.google.protobuf.ByteString; import com.hedera.services.bdd.junit.HapiTest; -import com.hedera.services.bdd.junit.HapiTestSuite; -import com.hedera.services.bdd.spec.HapiSpec; +import com.hedera.services.bdd.junit.LeakyHapiTest; import com.hedera.services.bdd.spec.HapiSpecOperation; import com.hedera.services.bdd.spec.transactions.token.TokenMovement; import com.hedera.services.bdd.spec.utilops.records.SnapshotMatchMode; -import com.hedera.services.bdd.suites.HapiSuite; import java.nio.charset.StandardCharsets; import java.util.List; import java.util.concurrent.atomic.AtomicLong; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; +import java.util.stream.Stream; +import org.junit.jupiter.api.DynamicTest; import org.junit.jupiter.api.Tag; -@HapiTestSuite(fuzzyMatch = true) @Tag(TOKEN) -public class TokenAssociationSpecs extends HapiSuite { - - private static final Logger log = LogManager.getLogger(TokenAssociationSpecs.class); - +public class TokenAssociationSpecs { public static final String FREEZABLE_TOKEN_ON_BY_DEFAULT = "TokenA"; public static final String FREEZABLE_TOKEN_OFF_BY_DEFAULT = "TokenB"; public static final String KNOWABLE_TOKEN = "TokenC"; @@ -103,37 +100,8 @@ public class TokenAssociationSpecs extends HapiSuite { public static final String FREEZE_KEY = "freezeKey"; public static final String KYC_KEY = "kycKey"; - public static void main(String... args) { - final var spec = new TokenAssociationSpecs(); - - spec.deferResultsSummary(); - spec.runSuiteAsync(); - spec.summarizeDeferredResults(); - } - - @Override - public List getSpecsInSuite() { - return List.of( - treasuryAssociationIsAutomatic(), - dissociateHasExpectedSemantics(), - associatedContractsMustHaveAdminKeys(), - expiredAndDeletedTokensStillAppearInContractInfo(), - accountInfoQueriesAsExpected(), - handlesUseOfDefaultTokenId(), - contractInfoQueriesAsExpected(), - dissociateHasExpectedSemanticsForDeletedTokens(), - dissociateHasExpectedSemanticsForDissociatedContracts(), - canDissociateFromDeletedTokenWithAlreadyDissociatedTreasury(), - associateAndDissociateNeedsValidAccountAndToken()); - } - - @Override - public boolean canRunConcurrent() { - return true; - } - @HapiTest - public HapiSpec canHandleInvalidAssociateTransactions() { + final Stream canHandleInvalidAssociateTransactions() { final String alice = "ALICE"; final String bob = "BOB"; final String unknownID = "0.0." + Long.MAX_VALUE; @@ -168,7 +136,7 @@ public HapiSpec canHandleInvalidAssociateTransactions() { } @HapiTest - public HapiSpec idVariantsTreatedAsExpected() { + final Stream idVariantsTreatedAsExpected() { return defaultHapiSpec("idVariantsTreatedAsExpected") .given( cryptoCreate(TOKEN_TREASURY), @@ -181,8 +149,8 @@ public HapiSpec idVariantsTreatedAsExpected() { withSuccessivelyVariedBodyIds(), () -> tokenDissociate(DEFAULT_PAYER, "a", "b"))); } - @HapiTest - public HapiSpec canLimitMaxTokensPerAccountTransactions() { + @LeakyHapiTest(PROPERTY_OVERRIDES) + final Stream canLimitMaxTokensPerAccountTransactions() { final String alice = "ALICE"; final String treasury2 = "TREASURY_2"; return propertyPreservingHapiSpec("CanHandleInvalidAssociateTransactions") @@ -200,7 +168,7 @@ public HapiSpec canLimitMaxTokensPerAccountTransactions() { } @HapiTest - public HapiSpec handlesUseOfDefaultTokenId() { + final Stream handlesUseOfDefaultTokenId() { return defaultHapiSpec("HandlesUseOfDefaultTokenId", SnapshotMatchMode.NONDETERMINISTIC_TRANSACTION_FEES) .given() .when() @@ -208,7 +176,7 @@ public HapiSpec handlesUseOfDefaultTokenId() { } @HapiTest - public HapiSpec canDeleteNonFungibleTokenTreasuryAfterUpdate() { + final Stream canDeleteNonFungibleTokenTreasuryAfterUpdate() { return defaultHapiSpec("canDeleteNonFungibleTokenTreasuryAfterUpdate") .given( newKeyNamed(MULTI_KEY), @@ -234,7 +202,7 @@ public HapiSpec canDeleteNonFungibleTokenTreasuryAfterUpdate() { } @HapiTest - public HapiSpec canDeleteNonFungibleTokenTreasuryBurnsAndTokenDeletion() { + final Stream canDeleteNonFungibleTokenTreasuryBurnsAndTokenDeletion() { final var firstTbdToken = "firstTbdToken"; final var secondTbdToken = "secondTbdToken"; final var treasuryWithoutAllPiecesBurned = "treasuryWithoutAllPiecesBurned"; @@ -277,7 +245,7 @@ public HapiSpec canDeleteNonFungibleTokenTreasuryBurnsAndTokenDeletion() { } @HapiTest - public HapiSpec associatedContractsMustHaveAdminKeys() { + final Stream associatedContractsMustHaveAdminKeys() { String misc = "someToken"; String contract = "defaultContract"; @@ -288,7 +256,7 @@ public HapiSpec associatedContractsMustHaveAdminKeys() { } @HapiTest - public HapiSpec contractInfoQueriesAsExpected() { + final Stream contractInfoQueriesAsExpected() { final var contract = "contract"; return defaultHapiSpec("ContractInfoQueriesAsExpected") .given( @@ -316,7 +284,7 @@ public HapiSpec contractInfoQueriesAsExpected() { } @HapiTest - public HapiSpec accountInfoQueriesAsExpected() { + final Stream accountInfoQueriesAsExpected() { final var account = "account"; return defaultHapiSpec("accountInfoQueriesAsExpected") .given( @@ -344,7 +312,7 @@ public HapiSpec accountInfoQueriesAsExpected() { } @HapiTest - public HapiSpec expiredAndDeletedTokensStillAppearInContractInfo() { + final Stream expiredAndDeletedTokensStillAppearInContractInfo() { final String contract = "Fuse"; final String treasury = "something"; final String expiringToken = "expiringToken"; @@ -388,7 +356,7 @@ var record = subOp.getResponseRecord(); } @HapiTest - public HapiSpec canDissociateFromDeletedTokenWithAlreadyDissociatedTreasury() { + final Stream canDissociateFromDeletedTokenWithAlreadyDissociatedTreasury() { final String aNonTreasuryAcquaintance = "aNonTreasuryAcquaintance"; final String bNonTreasuryAcquaintance = "bNonTreasuryAcquaintance"; final long initialSupply = 100L; @@ -434,7 +402,7 @@ public HapiSpec canDissociateFromDeletedTokenWithAlreadyDissociatedTreasury() { } @HapiTest - public HapiSpec dissociateHasExpectedSemanticsForDeletedTokens() { + final Stream dissociateHasExpectedSemanticsForDeletedTokens() { final String tbdUniqToken = "UniqToBeDeleted"; final String zeroBalanceFrozen = "0bFrozen"; final String zeroBalanceUnfrozen = "0bUnfrozen"; @@ -505,7 +473,7 @@ public HapiSpec dissociateHasExpectedSemanticsForDeletedTokens() { } @HapiTest - public HapiSpec dissociateHasExpectedSemantics() { + final Stream dissociateHasExpectedSemantics() { return defaultHapiSpec("DissociateHasExpectedSemantics") .given(basicKeysAndTokens()) .when( @@ -529,7 +497,7 @@ public HapiSpec dissociateHasExpectedSemantics() { } @HapiTest - public HapiSpec dissociateHasExpectedSemanticsForDissociatedContracts() { + final Stream dissociateHasExpectedSemanticsForDissociatedContracts() { final var uniqToken = "UniqToken"; final var contract = "Fuse"; final var firstMeta = ByteString.copyFrom("FIRST".getBytes(StandardCharsets.UTF_8)); @@ -555,7 +523,7 @@ public HapiSpec dissociateHasExpectedSemanticsForDissociatedContracts() { } @HapiTest - public HapiSpec treasuryAssociationIsAutomatic() { + final Stream treasuryAssociationIsAutomatic() { return defaultHapiSpec("TreasuryAssociationIsAutomatic") .given(basicKeysAndTokens()) .when() @@ -587,7 +555,7 @@ public HapiSpec treasuryAssociationIsAutomatic() { } @HapiTest - private HapiSpec associateAndDissociateNeedsValidAccountAndToken() { + final Stream associateAndDissociateNeedsValidAccountAndToken() { final var account = "account"; return defaultHapiSpec("dissociationNeedsAccount") .given( @@ -619,9 +587,4 @@ public static HapiSpecOperation[] basicKeysAndTokens() { tokenCreate(VANILLA_TOKEN).treasury(TOKEN_TREASURY) }; } - - @Override - protected Logger getResultsLogger() { - return log; - } } diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/token/TokenAssociationV1SecurityModelSpecs.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/token/TokenAssociationV1SecurityModelSpecs.java index 6d83be3f4842..c022e858f6cb 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/token/TokenAssociationV1SecurityModelSpecs.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/token/TokenAssociationV1SecurityModelSpecs.java @@ -38,12 +38,13 @@ import com.esaulpaugh.headlong.abi.Address; import com.hedera.services.bdd.spec.HapiPropertySource; -import com.hedera.services.bdd.spec.HapiSpec; import com.hedera.services.bdd.suites.HapiSuite; import java.util.List; import java.util.concurrent.atomic.AtomicReference; +import java.util.stream.Stream; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; +import org.junit.jupiter.api.DynamicTest; public class TokenAssociationV1SecurityModelSpecs extends HapiSuite { @@ -63,7 +64,7 @@ public static void main(String... args) { } @Override - public List getSpecsInSuite() { + public List> getSpecsInSuite() { return List.of(multiAssociationWithSameRepeatedTokenAsExpected()); } @@ -72,7 +73,7 @@ public boolean canRunConcurrent() { return false; } - final HapiSpec multiAssociationWithSameRepeatedTokenAsExpected() { + final Stream multiAssociationWithSameRepeatedTokenAsExpected() { final var nfToken = "nfToken"; final var civilian = "civilian"; final var multiAssociate = "multiAssociate"; diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/token/TokenCreateSpecs.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/token/TokenCreateSpecs.java index 869a5ebd8407..43268f610eff 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/token/TokenCreateSpecs.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/token/TokenCreateSpecs.java @@ -63,6 +63,12 @@ import static com.hedera.services.bdd.spec.utilops.records.SnapshotMatchMode.HIGHLY_NON_DETERMINISTIC_FEES; import static com.hedera.services.bdd.spec.utilops.records.SnapshotMatchMode.NONDETERMINISTIC_TOKEN_NAMES; import static com.hedera.services.bdd.spec.utilops.records.SnapshotMatchMode.NONDETERMINISTIC_TRANSACTION_FEES; +import static com.hedera.services.bdd.suites.HapiSuite.DEFAULT_PAYER; +import static com.hedera.services.bdd.suites.HapiSuite.GENESIS; +import static com.hedera.services.bdd.suites.HapiSuite.ONE_HBAR; +import static com.hedera.services.bdd.suites.HapiSuite.ONE_HUNDRED_HBARS; +import static com.hedera.services.bdd.suites.HapiSuite.THREE_MONTHS_IN_SECONDS; +import static com.hedera.services.bdd.suites.HapiSuite.salted; import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.ACCOUNT_REPEATED_IN_ACCOUNT_AMOUNTS; import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.CUSTOM_FEE_MUST_BE_POSITIVE; import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.CUSTOM_FEE_NOT_FULLY_SPECIFIED; @@ -97,11 +103,8 @@ import static com.hederahashgraph.api.proto.java.TokenType.NON_FUNGIBLE_UNIQUE; import com.hedera.services.bdd.junit.HapiTest; -import com.hedera.services.bdd.junit.HapiTestSuite; -import com.hedera.services.bdd.spec.HapiSpec; import com.hedera.services.bdd.spec.HapiSpecSetup; import com.hedera.services.bdd.spec.transactions.TxnUtils; -import com.hedera.services.bdd.suites.HapiSuite; import com.hederahashgraph.api.proto.java.TokenFreezeStatus; import com.hederahashgraph.api.proto.java.TokenKycStatus; import com.hederahashgraph.api.proto.java.TokenPauseStatus; @@ -113,9 +116,9 @@ import java.util.concurrent.atomic.AtomicInteger; import java.util.stream.Collectors; import java.util.stream.IntStream; +import java.util.stream.Stream; import org.apache.commons.lang3.tuple.Pair; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; +import org.junit.jupiter.api.DynamicTest; import org.junit.jupiter.api.Tag; /** @@ -125,10 +128,8 @@ *

  • Default values.
  • * */ -@HapiTestSuite(fuzzyMatch = true) @Tag(TOKEN) -public class TokenCreateSpecs extends HapiSuite { - private static final Logger log = LogManager.getLogger(TokenCreateSpecs.class); +public class TokenCreateSpecs { private static final String NON_FUNGIBLE_UNIQUE_FINITE = "non-fungible-unique-finite"; private static final String PRIMARY = "primary"; private static final String AUTO_RENEW_ACCOUNT = "autoRenewAccount"; @@ -149,51 +150,8 @@ public class TokenCreateSpecs extends HapiSuite { private static final long defaultMaxLifetime = Long.parseLong(HapiSpecSetup.getDefaultNodeProps().get("entities.maxLifetime")); - public static void main(String... args) { - new TokenCreateSpecs().runSuiteSync(); - } - - @Override - public boolean canRunConcurrent() { - return true; - } - - @Override - public List getSpecsInSuite() { - return List.of( - creationValidatesNonFungiblePrechecks(), - creationValidatesMaxSupply(), - creationValidatesMemo(), - creationValidatesName(), - creationValidatesSymbol(), - treasuryHasCorrectBalance(), - creationRequiresAppropriateSigs(), - creationRequiresAppropriateSigsHappyPath(), - initialSupplyMustBeSane(), - creationYieldsExpectedToken(), - creationSetsExpectedName(), - creationValidatesTreasuryAccount(), - autoRenewValidationWorks(), - creationWithoutKYCSetsCorrectStatus(), - creationValidatesExpiry(), - creationValidatesFreezeDefaultWithNoFreezeKey(), - creationSetsCorrectExpiry(), - creationHappyPath(), - worksAsExpectedWithDefaultTokenId(), - cannotCreateWithExcessiveLifetime(), - prechecksWork(), - /* HIP-18 */ - onlyValidCustomFeeScheduleCanBeCreated(), - feeCollectorSigningReqsWorkForTokenCreate(), - createsFungibleInfiniteByDefault(), - baseCreationsHaveExpectedPrices(), - /* HIP-23 */ - validateNewTokenAssociations(), - missingTreasurySignatureFails()); - } - @HapiTest - public HapiSpec getInfoIdVariantsTreatedAsExpected() { + final Stream getInfoIdVariantsTreatedAsExpected() { return defaultHapiSpec("getInfoIdVariantsTreatedAsExpected") .given(tokenCreate("something")) .when() @@ -201,7 +159,7 @@ public HapiSpec getInfoIdVariantsTreatedAsExpected() { } @HapiTest - public HapiSpec idVariantsTreatedAsExpected() { + final Stream idVariantsTreatedAsExpected() { return defaultHapiSpec("idVariantsTreatedAsExpected") .given( newKeyNamed("supplyKey"), @@ -231,7 +189,7 @@ public HapiSpec idVariantsTreatedAsExpected() { } @HapiTest - public HapiSpec canHandleInvalidTokenCreateTransactions() { + final Stream canHandleInvalidTokenCreateTransactions() { final String alice = "ALICE"; return defaultHapiSpec("canHandleInvalidTokenCreateTransactions") .given(cryptoCreate(alice)) @@ -260,7 +218,7 @@ public HapiSpec canHandleInvalidTokenCreateTransactions() { * automatic associations limit defined by https://hips.hedera.com/hip/hip-23. */ @HapiTest - final HapiSpec validateNewTokenAssociations() { + final Stream validateNewTokenAssociations() { final String notToBeToken = "notToBeToken"; final String hbarCollector = "hbarCollector"; final String fractionalCollector = "fractionalCollector"; @@ -344,7 +302,7 @@ final HapiSpec validateNewTokenAssociations() { * Validates the default values for a {@code TokenCreate}'s token type (fungible) and supply type (infinite). */ @HapiTest - final HapiSpec createsFungibleInfiniteByDefault() { + final Stream createsFungibleInfiniteByDefault() { return defaultHapiSpec("CreatesFungibleInfiniteByDefault") .given() .when(tokenCreate("DefaultFungible")) @@ -354,7 +312,7 @@ final HapiSpec createsFungibleInfiniteByDefault() { } @HapiTest - final HapiSpec worksAsExpectedWithDefaultTokenId() { + final Stream worksAsExpectedWithDefaultTokenId() { return defaultHapiSpec("WorksAsExpectedWithDefaultTokenId") .given() .when() @@ -362,7 +320,7 @@ final HapiSpec worksAsExpectedWithDefaultTokenId() { } @HapiTest - public HapiSpec cannotCreateWithExcessiveLifetime() { + final Stream cannotCreateWithExcessiveLifetime() { final var smallBuffer = 12_345L; final var okExpiry = defaultMaxLifetime + Instant.now().getEpochSecond() - smallBuffer; final var excessiveExpiry = defaultMaxLifetime + Instant.now().getEpochSecond() + smallBuffer; @@ -375,7 +333,7 @@ public HapiSpec cannotCreateWithExcessiveLifetime() { } @HapiTest - public HapiSpec autoRenewValidationWorks() { + final Stream autoRenewValidationWorks() { final var deletingAccount = "deletingAccount"; return defaultHapiSpec("AutoRenewValidationWorks") .given( @@ -403,7 +361,7 @@ public HapiSpec autoRenewValidationWorks() { } @HapiTest - public HapiSpec creationYieldsExpectedToken() { + final Stream creationYieldsExpectedToken() { return defaultHapiSpec("CreationYieldsExpectedToken") .given(cryptoCreate(TOKEN_TREASURY).balance(0L), newKeyNamed("freeze")) .when(tokenCreate(PRIMARY) @@ -416,7 +374,7 @@ public HapiSpec creationYieldsExpectedToken() { } @HapiTest - public HapiSpec creationSetsExpectedName() { + final Stream creationSetsExpectedName() { String saltedName = salted(PRIMARY); return defaultHapiSpec("CreationSetsExpectedName", NONDETERMINISTIC_TOKEN_NAMES) .given(cryptoCreate(TOKEN_TREASURY).balance(0L)) @@ -425,7 +383,7 @@ public HapiSpec creationSetsExpectedName() { } @HapiTest - public HapiSpec creationWithoutKYCSetsCorrectStatus() { + final Stream creationWithoutKYCSetsCorrectStatus() { String saltedName = salted(PRIMARY); return defaultHapiSpec( "CreationWithoutKYCSetsCorrectStatus", @@ -439,7 +397,7 @@ public HapiSpec creationWithoutKYCSetsCorrectStatus() { } @HapiTest - public HapiSpec baseCreationsHaveExpectedPrices() { + final Stream baseCreationsHaveExpectedPrices() { final var civilian = "NonExemptPayer"; final var expectedCommonNoCustomFeesPriceUsd = 1.00; @@ -525,7 +483,7 @@ private String txnFor(String tokenSubType) { } @HapiTest - public HapiSpec creationHappyPath() { + final Stream creationHappyPath() { String memo = "JUMP"; String saltedName = salted(PRIMARY); final var secondCreation = "secondCreation"; @@ -627,7 +585,7 @@ public HapiSpec creationHappyPath() { } @HapiTest - public HapiSpec missingTreasurySignatureFails() { + final Stream missingTreasurySignatureFails() { String memo = "JUMP"; String saltedName = salted(PRIMARY); final var pauseKey = "pauseKey"; @@ -716,7 +674,7 @@ public HapiSpec missingTreasurySignatureFails() { } @HapiTest - public HapiSpec creationSetsCorrectExpiry() { + final Stream creationSetsCorrectExpiry() { return defaultHapiSpec("CreationSetsCorrectExpiry") .given( cryptoCreate(TOKEN_TREASURY).balance(0L), @@ -740,7 +698,7 @@ public HapiSpec creationSetsCorrectExpiry() { } @HapiTest - public HapiSpec creationValidatesExpiry() { + final Stream creationValidatesExpiry() { return defaultHapiSpec("CreationValidatesExpiry") .given() .when() @@ -748,7 +706,7 @@ public HapiSpec creationValidatesExpiry() { } @HapiTest - public HapiSpec creationValidatesFreezeDefaultWithNoFreezeKey() { + final Stream creationValidatesFreezeDefaultWithNoFreezeKey() { return defaultHapiSpec("CreationValidatesFreezeDefaultWithNoFreezeKey") .given() .when() @@ -756,7 +714,7 @@ public HapiSpec creationValidatesFreezeDefaultWithNoFreezeKey() { } @HapiTest - public HapiSpec creationValidatesMemo() { + final Stream creationValidatesMemo() { return defaultHapiSpec("CreationValidatesMemo") .given() .when() @@ -764,7 +722,7 @@ public HapiSpec creationValidatesMemo() { } @HapiTest - public HapiSpec creationValidatesNonFungiblePrechecks() { + final Stream creationValidatesNonFungiblePrechecks() { return defaultHapiSpec("CreationValidatesNonFungiblePrechecks") .given() .when() @@ -787,7 +745,7 @@ public HapiSpec creationValidatesNonFungiblePrechecks() { } @HapiTest - public HapiSpec creationValidatesMaxSupply() { + final Stream creationValidatesMaxSupply() { return defaultHapiSpec("CreationValidatesMaxSupply") .given() .when() @@ -810,7 +768,7 @@ public HapiSpec creationValidatesMaxSupply() { } @HapiTest - public HapiSpec onlyValidCustomFeeScheduleCanBeCreated() { + final Stream onlyValidCustomFeeScheduleCanBeCreated() { return defaultHapiSpec("OnlyValidCustomFeeScheduleCanBeCreated") .given( newKeyNamed(customFeesKey), @@ -984,7 +942,7 @@ public HapiSpec onlyValidCustomFeeScheduleCanBeCreated() { } @HapiTest - final HapiSpec feeCollectorSigningReqsWorkForTokenCreate() { + final Stream feeCollectorSigningReqsWorkForTokenCreate() { return defaultHapiSpec("feeCollectorSigningReqsWorkForTokenCreate", NONDETERMINISTIC_TRANSACTION_FEES) .given( newKeyNamed(customFeesKey), @@ -1044,7 +1002,7 @@ final HapiSpec feeCollectorSigningReqsWorkForTokenCreate() { } @HapiTest - public HapiSpec creationValidatesName() { + final Stream creationValidatesName() { AtomicInteger maxUtf8Bytes = new AtomicInteger(); return defaultHapiSpec("CreationValidatesName", NONDETERMINISTIC_TRANSACTION_FEES) @@ -1064,7 +1022,7 @@ public HapiSpec creationValidatesName() { } @HapiTest - public HapiSpec creationValidatesSymbol() { + final Stream creationValidatesSymbol() { AtomicInteger maxUtf8Bytes = new AtomicInteger(); return defaultHapiSpec("CreationValidatesSymbol") @@ -1088,7 +1046,7 @@ private String nCurrencySymbols(int n) { } @HapiTest - public HapiSpec creationRequiresAppropriateSigs() { + final Stream creationRequiresAppropriateSigs() { return defaultHapiSpec("CreationRequiresAppropriateSigs") .given( cryptoCreate(PAYER).balance(ONE_HUNDRED_HBARS), @@ -1112,7 +1070,7 @@ public HapiSpec creationRequiresAppropriateSigs() { } @HapiTest - public HapiSpec creationRequiresAppropriateSigsHappyPath() { + final Stream creationRequiresAppropriateSigsHappyPath() { return defaultHapiSpec("CreationRequiresAppropriateSigsHappyPath", NONDETERMINISTIC_TRANSACTION_FEES) .given(cryptoCreate(PAYER), cryptoCreate(TOKEN_TREASURY).balance(0L), newKeyNamed(ADMIN_KEY)) .when() @@ -1125,7 +1083,7 @@ public HapiSpec creationRequiresAppropriateSigsHappyPath() { } @HapiTest - public HapiSpec creationValidatesTreasuryAccount() { + final Stream creationValidatesTreasuryAccount() { return defaultHapiSpec("CreationValidatesTreasuryAccount") .given(cryptoCreate(TOKEN_TREASURY).balance(0L)) .when(cryptoDelete(TOKEN_TREASURY)) @@ -1135,7 +1093,7 @@ public HapiSpec creationValidatesTreasuryAccount() { } @HapiTest - public HapiSpec initialSupplyMustBeSane() { + final Stream initialSupplyMustBeSane() { return defaultHapiSpec("InitialSupplyMustBeSane") .given() .when() @@ -1149,7 +1107,7 @@ public HapiSpec initialSupplyMustBeSane() { } @HapiTest - public HapiSpec treasuryHasCorrectBalance() { + final Stream treasuryHasCorrectBalance() { String token = salted("myToken"); int decimals = 1; @@ -1166,7 +1124,7 @@ public HapiSpec treasuryHasCorrectBalance() { // FULLY_NONDETERMINISTIC because in mono-service zero amount token transfers will create a tokenTransferLists // with a just tokenNum, in mono-service the tokenTransferLists will be empty @HapiTest - final HapiSpec prechecksWork() { + final Stream prechecksWork() { return defaultHapiSpec("PrechecksWork", HIGHLY_NON_DETERMINISTIC_FEES, FULLY_NONDETERMINISTIC) .given( cryptoCreate(TOKEN_TREASURY) @@ -1212,11 +1170,6 @@ final HapiSpec prechecksWork() { cryptoTransfer(moving(10, A_TOKEN).empty()).hasPrecheck(EMPTY_TOKEN_TRANSFER_ACCOUNT_AMOUNTS)); } - @Override - protected Logger getResultsLogger() { - return log; - } - private final long hbarAmount = 1_234L; private final long htsAmount = 2_345L; private final long numerator = 1; diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/token/TokenDeleteSpecs.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/token/TokenDeleteSpecs.java index 76033c5b63bf..56749953a030 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/token/TokenDeleteSpecs.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/token/TokenDeleteSpecs.java @@ -38,53 +38,28 @@ import static com.hedera.services.bdd.spec.utilops.UtilVerbs.submitModified; import static com.hedera.services.bdd.spec.utilops.mod.ModificationUtils.withSuccessivelyVariedBodyIds; import static com.hedera.services.bdd.spec.utilops.records.SnapshotMatchMode.HIGHLY_NON_DETERMINISTIC_FEES; +import static com.hedera.services.bdd.suites.HapiSuite.GENESIS; +import static com.hedera.services.bdd.suites.HapiSuite.TOKEN_TREASURY; import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.ACCOUNT_IS_TREASURY; import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.INVALID_TOKEN_ID; import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.TOKEN_IS_IMMUTABLE; import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.TOKEN_WAS_DELETED; import com.hedera.services.bdd.junit.HapiTest; -import com.hedera.services.bdd.junit.HapiTestSuite; -import com.hedera.services.bdd.spec.HapiSpec; -import com.hedera.services.bdd.suites.HapiSuite; -import java.util.List; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; +import java.util.stream.Stream; +import org.junit.jupiter.api.DynamicTest; import org.junit.jupiter.api.Tag; -@HapiTestSuite(fuzzyMatch = true) @Tag(TOKEN) -public class TokenDeleteSpecs extends HapiSuite { - - private static final Logger log = LogManager.getLogger(TokenDeleteSpecs.class); - +public class TokenDeleteSpecs { private static final String FIRST_TBD = "firstTbd"; private static final String SECOND_TBD = "secondTbd"; private static final String TOKEN_ADMIN = "tokenAdmin"; private static final String PAYER = "payer"; private static final String MULTI_KEY = "multiKey"; - public static void main(String... args) { - new TokenDeleteSpecs().runSuiteAsync(); - } - - @Override - public boolean canRunConcurrent() { - return true; - } - - @Override - public List getSpecsInSuite() { - return List.of( - deletionValidatesMissingAdminKey(), - deletionWorksAsExpected(), - deletionValidatesAlreadyDeletedToken(), - treasuryBecomesDeletableAfterTokenDelete(), - deletionValidatesRef()); - } - @HapiTest - final HapiSpec treasuryBecomesDeletableAfterTokenDelete() { + final Stream treasuryBecomesDeletableAfterTokenDelete() { return defaultHapiSpec("TreasuryBecomesDeletableAfterTokenDelete") .given( newKeyNamed(TOKEN_ADMIN), @@ -102,7 +77,7 @@ final HapiSpec treasuryBecomesDeletableAfterTokenDelete() { } @HapiTest - final HapiSpec deletionValidatesAlreadyDeletedToken() { + final Stream deletionValidatesAlreadyDeletedToken() { return defaultHapiSpec("DeletionValidatesAlreadyDeletedToken") .given( newKeyNamed(MULTI_KEY), @@ -114,7 +89,7 @@ final HapiSpec deletionValidatesAlreadyDeletedToken() { } @HapiTest - public HapiSpec idVariantsTreatedAsExpected() { + final Stream idVariantsTreatedAsExpected() { return defaultHapiSpec("idVariantsTreatedAsExpected") .given(newKeyNamed("adminKey"), tokenCreate("t").adminKey("adminKey")) .when() @@ -122,7 +97,7 @@ public HapiSpec idVariantsTreatedAsExpected() { } @HapiTest - final HapiSpec deletionValidatesMissingAdminKey() { + final Stream deletionValidatesMissingAdminKey() { return defaultHapiSpec("DeletionValidatesMissingAdminKey") .given( newKeyNamed(MULTI_KEY), @@ -137,7 +112,7 @@ final HapiSpec deletionValidatesMissingAdminKey() { } @HapiTest - public HapiSpec deletionWorksAsExpected() { + final Stream deletionWorksAsExpected() { return defaultHapiSpec("DeletionWorksAsExpected", HIGHLY_NON_DETERMINISTIC_FEES) .given( newKeyNamed(MULTI_KEY), @@ -177,7 +152,7 @@ public HapiSpec deletionWorksAsExpected() { } @HapiTest - public HapiSpec deletionValidatesRef() { + final Stream deletionValidatesRef() { return defaultHapiSpec("DeletionValidatesRef") .given(cryptoCreate(PAYER)) .when() @@ -185,9 +160,4 @@ public HapiSpec deletionValidatesRef() { tokenDelete("0.0.0").payingWith(PAYER).signedBy(PAYER).hasKnownStatus(INVALID_TOKEN_ID), tokenDelete("1.2.3").payingWith(PAYER).signedBy(PAYER).hasKnownStatus(INVALID_TOKEN_ID)); } - - @Override - protected Logger getResultsLogger() { - return log; - } } diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/token/TokenFeeScheduleUpdateSpecs.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/token/TokenFeeScheduleUpdateSpecs.java index e63e45ff26b2..39c5cb6cee4c 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/token/TokenFeeScheduleUpdateSpecs.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/token/TokenFeeScheduleUpdateSpecs.java @@ -35,6 +35,10 @@ import static com.hedera.services.bdd.spec.utilops.UtilVerbs.submitModified; import static com.hedera.services.bdd.spec.utilops.UtilVerbs.validateChargedUsdWithin; import static com.hedera.services.bdd.spec.utilops.mod.ModificationUtils.withSuccessivelyVariedBodyIds; +import static com.hedera.services.bdd.suites.HapiSuite.APP_PROPERTIES; +import static com.hedera.services.bdd.suites.HapiSuite.GENESIS; +import static com.hedera.services.bdd.suites.HapiSuite.ONE_HBAR; +import static com.hedera.services.bdd.suites.HapiSuite.THREE_MONTHS_IN_SECONDS; import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.CUSTOM_FEES_LIST_TOO_LONG; import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.CUSTOM_FEE_MUST_BE_POSITIVE; import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.CUSTOM_FEE_NOT_FULLY_SPECIFIED; @@ -45,37 +49,18 @@ import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.TOKEN_NOT_ASSOCIATED_TO_FEE_COLLECTOR; import com.hedera.services.bdd.junit.HapiTest; -import com.hedera.services.bdd.junit.HapiTestSuite; -import com.hedera.services.bdd.spec.HapiSpec; import com.hedera.services.bdd.spec.utilops.records.SnapshotMatchMode; -import com.hedera.services.bdd.suites.HapiSuite; import java.time.Instant; -import java.util.List; import java.util.Map; import java.util.OptionalLong; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; +import java.util.stream.Stream; +import org.junit.jupiter.api.DynamicTest; import org.junit.jupiter.api.Tag; -@HapiTestSuite(fuzzyMatch = true) @Tag(TOKEN) -public class TokenFeeScheduleUpdateSpecs extends HapiSuite { - - private static final Logger log = LogManager.getLogger(TokenFeeScheduleUpdateSpecs.class); - - public static void main(String... args) { - new TokenFeeScheduleUpdateSpecs().runSuiteSync(); - } - - @Override - public List getSpecsInSuite() { - return List.of(new HapiSpec[] { - onlyValidCustomFeeScheduleCanBeUpdated(), baseOperationIsChargedExpectedFee(), - }); - } - +public class TokenFeeScheduleUpdateSpecs { @HapiTest - final HapiSpec baseOperationIsChargedExpectedFee() { + final Stream baseOperationIsChargedExpectedFee() { final var htsAmount = 2_345L; final var targetToken = "immutableToken"; final var feeDenom = "denom"; @@ -102,7 +87,7 @@ final HapiSpec baseOperationIsChargedExpectedFee() { } @HapiTest - public HapiSpec idVariantsTreatedAsExpected() { + final Stream idVariantsTreatedAsExpected() { return defaultHapiSpec("idVariantsTreatedAsExpected") .given( newKeyNamed("feeScheduleKey"), @@ -111,11 +96,12 @@ public HapiSpec idVariantsTreatedAsExpected() { tokenAssociate("feeCollector", "t")) .when() .then(submitModified(withSuccessivelyVariedBodyIds(), () -> tokenFeeScheduleUpdate("t") - .withCustom(fixedHbarFee(1, "feeCollector")))); + .withCustom(fixedHbarFee(1, "feeCollector")) + .fee(ONE_HBAR))); } @HapiTest - final HapiSpec onlyValidCustomFeeScheduleCanBeUpdated() { + final Stream onlyValidCustomFeeScheduleCanBeUpdated() { final var hbarAmount = 1_234L; final var htsAmount = 2_345L; final var numerator = 1; @@ -291,9 +277,4 @@ final HapiSpec onlyValidCustomFeeScheduleCanBeUpdated() { false, newTokenCollector))); } - - @Override - protected Logger getResultsLogger() { - return log; - } } diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/token/TokenManagementSpecs.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/token/TokenManagementSpecs.java index 054cacacae3a..1f86b89cf63c 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/token/TokenManagementSpecs.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/token/TokenManagementSpecs.java @@ -65,6 +65,11 @@ import static com.hedera.services.bdd.spec.utilops.mod.ModificationUtils.withSuccessivelyVariedQueryIds; import static com.hedera.services.bdd.spec.utilops.records.SnapshotMatchMode.HIGHLY_NON_DETERMINISTIC_FEES; import static com.hedera.services.bdd.spec.utilops.records.SnapshotMatchMode.NONDETERMINISTIC_TRANSACTION_FEES; +import static com.hedera.services.bdd.suites.HapiSuite.DEFAULT_PAYER; +import static com.hedera.services.bdd.suites.HapiSuite.GENESIS; +import static com.hedera.services.bdd.suites.HapiSuite.ONE_HBAR; +import static com.hedera.services.bdd.suites.HapiSuite.ONE_HUNDRED_HBARS; +import static com.hedera.services.bdd.suites.HapiSuite.TOKEN_TREASURY; import static com.hedera.services.bdd.suites.crypto.AutoAccountCreationSuite.A_TOKEN; import static com.hedera.services.bdd.suites.crypto.AutoAccountCreationSuite.TOKEN_A_CREATE; import static com.hedera.services.bdd.suites.crypto.AutoAccountCreationSuite.VALID_ALIAS; @@ -97,24 +102,18 @@ import com.google.protobuf.ByteString; import com.hedera.services.bdd.junit.HapiTest; -import com.hedera.services.bdd.junit.HapiTestSuite; -import com.hedera.services.bdd.spec.HapiSpec; import com.hedera.services.bdd.spec.keys.KeyFactory; import com.hedera.services.bdd.spec.utilops.records.SnapshotMatchMode; -import com.hedera.services.bdd.suites.HapiSuite; import com.hederahashgraph.api.proto.java.TokenSupplyType; import com.hederahashgraph.api.proto.java.TokenType; import java.util.List; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; +import java.util.stream.Stream; import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.DynamicTest; import org.junit.jupiter.api.Tag; -@HapiTestSuite(fuzzyMatch = true) @Tag(TOKEN) -public class TokenManagementSpecs extends HapiSuite { - - private static final Logger log = LogManager.getLogger(TokenManagementSpecs.class); +public class TokenManagementSpecs { private static final String SUPPLE = "supple"; private static final String SHOULD_NOT_APPEAR = "should-not-appear"; private static final String FUNGIBLE_TOKEN = "fungibleToken"; @@ -124,35 +123,8 @@ public class TokenManagementSpecs extends HapiSuite { private static final String ONE_KYC = "oneKyc"; private static final String RIGID = "rigid"; - public static void main(String... args) { - new TokenManagementSpecs().runSuiteAsync(); - } - - @Override - public List getSpecsInSuite() { - return List.of( - freezeMgmtSuccessCasesWork(), - kycMgmtFailureCasesWork(), - kycMgmtSuccessCasesWork(), - supplyMgmtSuccessCasesWork(), - wipeAccountFailureCasesWork(), - wipeAccountSuccessCasesWork(), - wipeAccountWithAliasesWork(), - supplyMgmtFailureCasesWork(), - burnTokenFailsDueToInsufficientTreasuryBalance(), - frozenTreasuryCannotBeMintedOrBurned(), - revokedKYCTreasuryCannotBeMintedOrBurned(), - fungibleCommonMaxSupplyReachWork(), - mintingMaxLongValueWorks(), - nftMintProvidesMintedNftsAndNewTotalSupply(), - zeroUnitTokenOperationsWorkAsExpected(), - aliasFormFailsForAllTokenOps() - // aliasFormWorksForAllTokenOps(), // this will be enabled when alias forms are allowed in all token ops - ); - } - @HapiTest - private HapiSpec aliasFormFailsForAllTokenOps() { + final Stream aliasFormFailsForAllTokenOps() { final var CIVILIAN = "civilian"; final var PAUSE_KEY = "pauseKey"; final var KYC_KEY = "kycKey"; @@ -217,14 +189,9 @@ private HapiSpec aliasFormFailsForAllTokenOps() { .then(); } - @Override - public boolean canRunConcurrent() { - return true; - } - // @HapiTest // This test should be enabled when aliases are supported in all transaction bodies - private HapiSpec aliasFormWorksForAllTokenOps() { + final Stream aliasFormWorksForAllTokenOps() { final var CIVILIAN = "civilian"; final var PAUSE_KEY = "pauseKey"; final var KYC_KEY = "kycKey"; @@ -318,7 +285,7 @@ private HapiSpec aliasFormWorksForAllTokenOps() { } @HapiTest - final HapiSpec getNftInfoIdVariantsTreatedAsExpected() { + final Stream getNftInfoIdVariantsTreatedAsExpected() { return defaultHapiSpec("getNftInfoIdVariantsTreatedAsExpected") .given(newKeyNamed("supplyKey"), cryptoCreate(TOKEN_TREASURY).balance(0L)) .when( @@ -334,7 +301,7 @@ final HapiSpec getNftInfoIdVariantsTreatedAsExpected() { // FULLY_NONDETERMINISTIC because in mono-service zero amount token transfers will create a tokenTransferLists // with a just tokenNum, in mono-service the tokenTransferLists will be empty @HapiTest - final HapiSpec zeroUnitTokenOperationsWorkAsExpected() { + final Stream zeroUnitTokenOperationsWorkAsExpected() { final var civilian = "civilian"; final var adminKey = "adminKey"; final var fungible = "fungible"; @@ -392,7 +359,7 @@ final HapiSpec zeroUnitTokenOperationsWorkAsExpected() { } @HapiTest - final HapiSpec frozenTreasuryCannotBeMintedOrBurned() { + final Stream frozenTreasuryCannotBeMintedOrBurned() { return defaultHapiSpec("FrozenTreasuryCannotBeMintedOrBurned", NONDETERMINISTIC_TRANSACTION_FEES) .given( newKeyNamed(SUPPLY_KEY), @@ -413,7 +380,7 @@ final HapiSpec frozenTreasuryCannotBeMintedOrBurned() { } @HapiTest - final HapiSpec revokedKYCTreasuryCannotBeMintedOrBurned() { + final Stream revokedKYCTreasuryCannotBeMintedOrBurned() { return defaultHapiSpec( "RevokedKYCTreasuryCannotBeMintedOrBurned", SnapshotMatchMode.EXPECT_STREAMLINED_INGEST_RECORDS) .given( @@ -435,7 +402,7 @@ final HapiSpec revokedKYCTreasuryCannotBeMintedOrBurned() { } @HapiTest - final HapiSpec burnTokenFailsDueToInsufficientTreasuryBalance() { + final Stream burnTokenFailsDueToInsufficientTreasuryBalance() { final String BURN_TOKEN = "burn"; final int TOTAL_SUPPLY = 100; final int TRANSFER_AMOUNT = 50; @@ -468,7 +435,7 @@ final HapiSpec burnTokenFailsDueToInsufficientTreasuryBalance() { } @HapiTest - public HapiSpec wipeAccountSuccessCasesWork() { + final Stream wipeAccountSuccessCasesWork() { var wipeableToken = "with"; return defaultHapiSpec("WipeAccountSuccessCasesWork") @@ -499,7 +466,7 @@ public HapiSpec wipeAccountSuccessCasesWork() { } @HapiTest - public HapiSpec wipeAccountWithAliasesWork() { + final Stream wipeAccountWithAliasesWork() { final var initialTokenSupply = 1000; return defaultHapiSpec("wipeAccountWithAliasesWork") .given( @@ -545,7 +512,7 @@ public HapiSpec wipeAccountWithAliasesWork() { } @HapiTest - public HapiSpec wipeAccountFailureCasesWork() { + final Stream wipeAccountFailureCasesWork() { var unwipeableToken = "without"; var wipeableToken = "with"; var wipeableUniqueToken = "uniqueWith"; @@ -601,7 +568,7 @@ public HapiSpec wipeAccountFailureCasesWork() { } @HapiTest - public HapiSpec kycMgmtFailureCasesWork() { + final Stream kycMgmtFailureCasesWork() { var withoutKycKey = "withoutKycKey"; var withKycKey = "withKycKey"; @@ -630,7 +597,7 @@ public HapiSpec kycMgmtFailureCasesWork() { } @HapiTest - public HapiSpec updateIdVariantsTreatedAsExpected() { + final Stream updateIdVariantsTreatedAsExpected() { return defaultHapiSpec("updateIdVariantsTreatedAsExpected") .given( newKeyNamed("adminKey"), @@ -644,7 +611,7 @@ public HapiSpec updateIdVariantsTreatedAsExpected() { } @HapiTest - public HapiSpec wipeIdVariantsTreatedAsExpected() { + final Stream wipeIdVariantsTreatedAsExpected() { return defaultHapiSpec("wipeIdVariantsTreatedAsExpected") .given( newKeyNamed("wipeKey"), @@ -655,7 +622,7 @@ public HapiSpec wipeIdVariantsTreatedAsExpected() { } @HapiTest - public HapiSpec grantRevokeIdVariantsTreatedAsExpected() { + final Stream grantRevokeIdVariantsTreatedAsExpected() { return defaultHapiSpec("grantRevokeIdVariantsTreatedAsExpected") .given( newKeyNamed("kycKey"), @@ -670,7 +637,7 @@ public HapiSpec grantRevokeIdVariantsTreatedAsExpected() { } @HapiTest - public HapiSpec pauseUnpauseIdVariantsTreatedAsExpected() { + final Stream pauseUnpauseIdVariantsTreatedAsExpected() { return defaultHapiSpec("pauseUnpauseIdVariantsTreatedAsExpected") .given(newKeyNamed("pauseKey"), tokenCreate("t").pauseKey("pauseKey")) .when() @@ -680,7 +647,7 @@ public HapiSpec pauseUnpauseIdVariantsTreatedAsExpected() { } @HapiTest - public HapiSpec freezeUnfreezeIdVariantsTreatedAsExpected() { + final Stream freezeUnfreezeIdVariantsTreatedAsExpected() { return defaultHapiSpec("freezeUnfreezeIdVariantsTreatedAsExpected") .given( newKeyNamed("freezeKey"), @@ -694,7 +661,7 @@ public HapiSpec freezeUnfreezeIdVariantsTreatedAsExpected() { } @HapiTest - public HapiSpec mintBurnIdVariantsTreatedAsExpected() { + final Stream mintBurnIdVariantsTreatedAsExpected() { return defaultHapiSpec("mintBurnIdVariantsTreatedAsExpected") .given(newKeyNamed("supplyKey"), tokenCreate("t").supplyKey("supplyKey")) .when() @@ -704,7 +671,7 @@ public HapiSpec mintBurnIdVariantsTreatedAsExpected() { } @HapiTest - public HapiSpec freezeMgmtSuccessCasesWork() { + final Stream freezeMgmtSuccessCasesWork() { var withPlusDefaultFalse = "withPlusDefaultFalse"; return defaultHapiSpec("FreezeMgmtSuccessCasesWork") @@ -730,7 +697,7 @@ public HapiSpec freezeMgmtSuccessCasesWork() { } @HapiTest - public HapiSpec kycMgmtSuccessCasesWork() { + final Stream kycMgmtSuccessCasesWork() { var withKycKey = "withKycKey"; var withoutKycKey = "withoutKycKey"; @@ -757,7 +724,7 @@ public HapiSpec kycMgmtSuccessCasesWork() { } @HapiTest - public HapiSpec supplyMgmtSuccessCasesWork() { + final Stream supplyMgmtSuccessCasesWork() { return defaultHapiSpec("SupplyMgmtSuccessCasesWork") .given( cryptoCreate(TOKEN_TREASURY).balance(0L), @@ -780,7 +747,7 @@ public HapiSpec supplyMgmtSuccessCasesWork() { } @HapiTest - final HapiSpec fungibleCommonMaxSupplyReachWork() { + final Stream fungibleCommonMaxSupplyReachWork() { return defaultHapiSpec("FungibleCommonMaxSupplyReachWork") .given( newKeyNamed(SUPPLY_KEY), @@ -807,7 +774,7 @@ final HapiSpec fungibleCommonMaxSupplyReachWork() { } @HapiTest - final HapiSpec mintingMaxLongValueWorks() { + final Stream mintingMaxLongValueWorks() { return defaultHapiSpec("MintingMaxLongValueWorks") .given( newKeyNamed(SUPPLY_KEY), @@ -823,7 +790,7 @@ final HapiSpec mintingMaxLongValueWorks() { } @HapiTest - final HapiSpec nftMintProvidesMintedNftsAndNewTotalSupply() { + final Stream nftMintProvidesMintedNftsAndNewTotalSupply() { final var multiKey = "multi"; final var token = "non-fungible"; final var txn = "mint"; @@ -850,7 +817,7 @@ final HapiSpec nftMintProvidesMintedNftsAndNewTotalSupply() { } @HapiTest - public HapiSpec supplyMgmtFailureCasesWork() { + final Stream supplyMgmtFailureCasesWork() { return defaultHapiSpec("SupplyMgmtFailureCasesWork") .given(newKeyNamed(SUPPLY_KEY)) .when( @@ -866,9 +833,4 @@ public HapiSpec supplyMgmtFailureCasesWork() { burnToken(SUPPLE, 0).hasPrecheck(OK), burnToken(SUPPLE, -1).hasPrecheck(INVALID_TOKEN_BURN_AMOUNT)); } - - @Override - protected Logger getResultsLogger() { - return log; - } } diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/token/TokenManagementSpecsStateful.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/token/TokenManagementSpecsStateful.java index 7591e36a9313..c15c5ac55da7 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/token/TokenManagementSpecsStateful.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/token/TokenManagementSpecsStateful.java @@ -26,6 +26,10 @@ import static com.hedera.services.bdd.spec.transactions.TxnVerbs.tokenFreeze; import static com.hedera.services.bdd.spec.transactions.TxnVerbs.tokenUnfreeze; import static com.hedera.services.bdd.spec.utilops.UtilVerbs.newKeyNamed; +import static com.hedera.services.bdd.suites.HapiSuite.ADDRESS_BOOK_CONTROL; +import static com.hedera.services.bdd.suites.HapiSuite.APP_PROPERTIES; +import static com.hedera.services.bdd.suites.HapiSuite.GENESIS; +import static com.hedera.services.bdd.suites.HapiSuite.TOKEN_TREASURY; import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.INVALID_ACCOUNT_ID; import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.INVALID_SIGNATURE; import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.MAX_NFTS_IN_PRICE_REGIME_HAVE_BEEN_MINTED; @@ -35,42 +39,23 @@ import com.google.protobuf.ByteString; import com.hedera.services.bdd.junit.HapiTest; -import com.hedera.services.bdd.junit.HapiTestSuite; -import com.hedera.services.bdd.spec.HapiSpec; import com.hedera.services.bdd.spec.HapiSpecSetup; -import com.hedera.services.bdd.suites.HapiSuite; import com.hederahashgraph.api.proto.java.TokenSupplyType; import java.util.List; import java.util.Map; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; +import java.util.stream.Stream; +import org.junit.jupiter.api.DynamicTest; import org.junit.jupiter.api.Tag; -@HapiTestSuite(fuzzyMatch = true) @Tag(TOKEN) -public class TokenManagementSpecsStateful extends HapiSuite { - - private static final Logger log = LogManager.getLogger(TokenManagementSpecsStateful.class); - +public class TokenManagementSpecsStateful { private static final String TOKENS_NFTS_MAX_ALLOWED_MINTS = "tokens.nfts.maxAllowedMints"; private static final String defaultMaxNftMints = HapiSpecSetup.getDefaultNodeProps().get(TOKENS_NFTS_MAX_ALLOWED_MINTS); private static final String FUNGIBLE_TOKEN = "fungibleToken"; - public static void main(String... args) { - new TokenManagementSpecsStateful().runSuiteSync(); - } - - @Override - public List getSpecsInSuite() { - return List.of(new HapiSpec[] { - /* Stateful specs from TokenManagementSpecs */ - freezeMgmtFailureCasesWork(), - }); - } - @HapiTest - public HapiSpec freezeMgmtFailureCasesWork() { + final Stream freezeMgmtFailureCasesWork() { var unfreezableToken = "without"; var freezableToken = "withPlusDefaultTrue"; @@ -110,7 +95,7 @@ public HapiSpec freezeMgmtFailureCasesWork() { } @HapiTest - final HapiSpec nftMintingCapIsEnforced() { + final Stream nftMintingCapIsEnforced() { return defaultHapiSpec("NftMintingCapIsEnforced") .given( newKeyNamed("supplyKey"), @@ -131,9 +116,4 @@ final HapiSpec nftMintingCapIsEnforced() { .overridingProps(Map.of(TOKENS_NFTS_MAX_ALLOWED_MINTS, "" + defaultMaxNftMints)), mintToken(FUNGIBLE_TOKEN, List.of(ByteString.copyFromUtf8("Again, why not?")))); } - - @Override - protected Logger getResultsLogger() { - return log; - } } diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/token/TokenMetadataSpecs.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/token/TokenMetadataSpecs.java index 9bb5e9d83bca..7d5b309d62a6 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/token/TokenMetadataSpecs.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/token/TokenMetadataSpecs.java @@ -32,6 +32,11 @@ import static com.hedera.services.bdd.spec.utilops.UtilVerbs.withOpContext; import static com.hedera.services.bdd.spec.utilops.records.SnapshotMatchMode.NONDETERMINISTIC_TOKEN_NAMES; import static com.hedera.services.bdd.spec.utilops.records.SnapshotMatchMode.NONDETERMINISTIC_TRANSACTION_FEES; +import static com.hedera.services.bdd.suites.HapiSuite.DEFAULT_PAYER; +import static com.hedera.services.bdd.suites.HapiSuite.GENESIS; +import static com.hedera.services.bdd.suites.HapiSuite.ONE_HUNDRED_HBARS; +import static com.hedera.services.bdd.suites.HapiSuite.THREE_MONTHS_IN_SECONDS; +import static com.hedera.services.bdd.suites.HapiSuite.salted; import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.INVALID_SIGNATURE; import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.METADATA_TOO_LONG; import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.SUCCESS; @@ -39,18 +44,15 @@ import static com.hederahashgraph.api.proto.java.TokenType.NON_FUNGIBLE_UNIQUE; import com.hedera.services.bdd.junit.HapiTest; -import com.hedera.services.bdd.junit.HapiTestSuite; -import com.hedera.services.bdd.spec.HapiSpec; import com.hedera.services.bdd.spec.transactions.TxnUtils; -import com.hedera.services.bdd.suites.HapiSuite; import com.hederahashgraph.api.proto.java.TokenFreezeStatus; import com.hederahashgraph.api.proto.java.TokenKycStatus; import com.hederahashgraph.api.proto.java.TokenSupplyType; import com.hederahashgraph.api.proto.java.TokenType; import java.util.List; +import java.util.stream.Stream; import org.apache.commons.lang3.tuple.Pair; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; +import org.junit.jupiter.api.DynamicTest; import org.junit.jupiter.api.Tag; /** @@ -59,10 +61,8 @@ *
  • Metadata and MetadataKey values and behaviours.
  • * */ -@HapiTestSuite @Tag(TOKEN) -public class TokenMetadataSpecs extends HapiSuite { - private static final Logger log = LogManager.getLogger(TokenMetadataSpecs.class); +public class TokenMetadataSpecs { private static final String PRIMARY = "primary"; private static final String NON_FUNGIBLE_UNIQUE_FINITE = "non-fungible-unique-finite"; private static final String AUTO_RENEW_ACCOUNT = "autoRenewAccount"; @@ -71,42 +71,12 @@ public class TokenMetadataSpecs extends HapiSuite { private static final String CREATE_TXN = "createTxn"; private static final String PAYER = "payer"; private static final String METADATA_KEY = "metadataKey"; - private static final String PAUSE_KEY = "pauseKey"; private static final String FREEZE_KEY = "freezeKey"; private static final String KYC_KEY = "kycKey"; - private static final String FEE_SCHEDULE_KEY = "feeScheduleKey"; - private static String TOKEN_TREASURY = "treasury"; - public static void main(String... args) { - new TokenMetadataSpecs().runSuiteSync(); - } - - @Override - public boolean canRunConcurrent() { - return true; - } - - @Override - public List getSpecsInSuite() { - return List.of( - rejectsMetadataTooLong(), - creationRequiresAppropriateSigsHappyPath(), - creationDoesNotHaveRequiredSigs(), - fungibleCreationHappyPath(), - updatingMetadataWorksWithMetadataKey(), - updatingMetadataWorksWithAdminKey(), - cannotUpdateMetadataOnImmutableToken(), - cannotUpdateMetadataWithoutAdminOrMetadataKeySignature()); - } - - @Override - protected Logger getResultsLogger() { - return log; - } - @HapiTest - public HapiSpec rejectsMetadataTooLong() { + final Stream rejectsMetadataTooLong() { String metadataStringTooLong = TxnUtils.nAscii(101); return defaultHapiSpec("validatesMetadataLength") .given() @@ -115,7 +85,7 @@ public HapiSpec rejectsMetadataTooLong() { } @HapiTest - public HapiSpec creationDoesNotHaveRequiredSigs() { + final Stream creationDoesNotHaveRequiredSigs() { return defaultHapiSpec("CreationRequiresAppropriateSigs") .given( cryptoCreate(PAYER).balance(ONE_HUNDRED_HBARS), @@ -139,7 +109,7 @@ public HapiSpec creationDoesNotHaveRequiredSigs() { } @HapiTest - public HapiSpec creationRequiresAppropriateSigsHappyPath() { + final Stream creationRequiresAppropriateSigsHappyPath() { return defaultHapiSpec("CreationRequiresAppropriateSigsHappyPath", NONDETERMINISTIC_TRANSACTION_FEES) .given(cryptoCreate(PAYER), cryptoCreate(TOKEN_TREASURY).balance(0L), newKeyNamed(ADMIN_KEY)) .when() @@ -152,7 +122,7 @@ public HapiSpec creationRequiresAppropriateSigsHappyPath() { } @HapiTest - public HapiSpec fungibleCreationHappyPath() { + final Stream fungibleCreationHappyPath() { String memo = "JUMP"; String metadata = "metadata"; String saltedName = salted(PRIMARY); @@ -219,7 +189,7 @@ public HapiSpec fungibleCreationHappyPath() { } @HapiTest - public HapiSpec nonFungibleCreationHappyPath() { + final Stream nonFungibleCreationHappyPath() { String metadata = "metadata"; return defaultHapiSpec("NonFungibleCreationHappyPath", NONDETERMINISTIC_TOKEN_NAMES) .given( @@ -270,17 +240,13 @@ public HapiSpec nonFungibleCreationHappyPath() { } @HapiTest - private HapiSpec updatingMetadataWorksWithMetadataKey() { + final Stream updatingMetadataWorksWithMetadataKey() { String memo = "JUMP"; String metadata = "metadata"; String saltedName = salted(PRIMARY); return defaultHapiSpec("updatingMetadataWorksWithMetadataKey", NONDETERMINISTIC_TOKEN_NAMES) - .given( - cryptoCreate(TOKEN_TREASURY).balance(100L), - newKeyNamed(ADMIN_KEY), - newKeyNamed(SUPPLY_KEY), - newKeyNamed(METADATA_KEY)) + .given(cryptoCreate(TOKEN_TREASURY).balance(100L), newKeyNamed(SUPPLY_KEY), newKeyNamed(METADATA_KEY)) .when( tokenCreate(PRIMARY) .supplyType(TokenSupplyType.FINITE) @@ -300,7 +266,7 @@ private HapiSpec updatingMetadataWorksWithMetadataKey() { } @HapiTest - private HapiSpec updatingMetadataWorksWithAdminKey() { + final Stream updatingMetadataWorksWithAdminKey() { String memo = "JUMP"; String metadata = "metadata"; String saltedName = salted(PRIMARY); @@ -330,7 +296,7 @@ private HapiSpec updatingMetadataWorksWithAdminKey() { } @HapiTest - private HapiSpec cannotUpdateMetadataWithoutAdminOrMetadataKeySignature() { + final Stream cannotUpdateMetadataWithoutAdminOrMetadataKeySignature() { String memo = "JUMP"; String metadata = "metadata"; String saltedName = salted(PRIMARY); @@ -362,7 +328,7 @@ private HapiSpec cannotUpdateMetadataWithoutAdminOrMetadataKeySignature() { } @HapiTest - private HapiSpec cannotUpdateMetadataOnImmutableToken() { + final Stream cannotUpdateMetadataOnImmutableToken() { String memo = "JUMP"; String metadata = "metadata"; String saltedName = salted(PRIMARY); diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/token/TokenMiscOps.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/token/TokenMiscOps.java deleted file mode 100644 index 3416f35ff525..000000000000 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/token/TokenMiscOps.java +++ /dev/null @@ -1,113 +0,0 @@ -/* - * Copyright (C) 2020-2024 Hedera Hashgraph, LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.hedera.services.bdd.suites.token; - -import static com.hedera.services.bdd.spec.HapiSpec.defaultHapiSpec; -import static com.hedera.services.bdd.spec.queries.QueryVerbs.getAccountInfo; -import static com.hedera.services.bdd.spec.queries.QueryVerbs.getTokenInfo; -import static com.hedera.services.bdd.spec.transactions.TxnVerbs.cryptoCreate; -import static com.hedera.services.bdd.spec.transactions.TxnVerbs.cryptoTransfer; -import static com.hedera.services.bdd.spec.transactions.TxnVerbs.tokenAssociate; -import static com.hedera.services.bdd.spec.transactions.TxnVerbs.tokenCreate; -import static com.hedera.services.bdd.spec.transactions.token.TokenMovement.moving; -import static com.hedera.services.bdd.spec.utilops.UtilVerbs.newKeyNamed; - -import com.hedera.services.bdd.spec.HapiSpec; -import com.hedera.services.bdd.suites.HapiSuite; -import java.util.List; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; - -public class TokenMiscOps extends HapiSuite { - - private static final Logger log = LogManager.getLogger(TokenMiscOps.class); - - public static void main(String... args) { - new TokenMiscOps().runSuiteSync(); - } - - @Override - public List getSpecsInSuite() { - return allOf(List.of(new HapiSpec[] { - // wellKnownAccountsHaveTokens(), - // someLowNumAccountsHaveTokens(), - // someInfoQueries(), - theCreation(), - })); - } - - public HapiSpec someLowNumAccountsHaveTokens() { - long aSupply = 666L, bSupply = 777L; - - return defaultHapiSpec("SomeLowNumAccountsHaveTokens") - .given( - tokenCreate("first").treasury(GENESIS).initialSupply(aSupply), - tokenCreate("second").treasury(GENESIS).initialSupply(bSupply)) - .when( - tokenAssociate("0.0.3", "second").signedBy(GENESIS), - cryptoTransfer(moving(aSupply / 2, "second").between(GENESIS, "0.0.3")) - .signedBy(GENESIS)) - .then(getAccountInfo(GENESIS).logged(), getAccountInfo("0.0.3").logged()); - } - - public HapiSpec theCreation() { - return defaultHapiSpec("TheCreation").given().when().then(cryptoCreate("adam")); - } - - public HapiSpec someInfoQueries() { - return defaultHapiSpec("SomeInfoQueries") - .given() - .when() - .then( - getAccountInfo("0.0.1001").logged(), - getAccountInfo(SYSTEM_ADMIN).logged(), - getTokenInfo("0.0.1002").logged(), - getTokenInfo("0.0.1003").logged()); - } - - public HapiSpec wellKnownAccountsHaveTokens() { - return defaultHapiSpec("WellKnownAccountsHaveTokens") - .given( - cryptoCreate(TOKEN_TREASURY).balance(0L), - newKeyNamed("supplyKey"), - newKeyNamed("freezeKey"), - newKeyNamed("kycKey"), - tokenCreate("supple") - .kycKey("kycKey") - .freezeKey("freezeKey") - .supplyKey("supplyKey") - .decimals(1) - .treasury(TOKEN_TREASURY), - tokenCreate("another") - .kycKey("kycKey") - .freezeDefault(true) - .freezeKey("freezeKey") - .supplyKey("supplyKey") - .treasury(SYSTEM_ADMIN)) - .when(tokenAssociate(TOKEN_TREASURY, "another")) - .then( - getAccountInfo(TOKEN_TREASURY).logged(), - getAccountInfo(SYSTEM_ADMIN).logged(), - getTokenInfo("supple").logged(), - getTokenInfo("another").logged()); - } - - @Override - protected Logger getResultsLogger() { - return log; - } -} diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/token/TokenPauseSpecs.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/token/TokenPauseSpecs.java index 0bec2852f426..0e3f78c79530 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/token/TokenPauseSpecs.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/token/TokenPauseSpecs.java @@ -44,6 +44,11 @@ import static com.hedera.services.bdd.spec.transactions.token.TokenMovement.movingUnique; import static com.hedera.services.bdd.spec.utilops.UtilVerbs.newKeyNamed; import static com.hedera.services.bdd.spec.utilops.UtilVerbs.validateChargedUsd; +import static com.hedera.services.bdd.suites.HapiSuite.DEFAULT_PAYER; +import static com.hedera.services.bdd.suites.HapiSuite.GENESIS; +import static com.hedera.services.bdd.suites.HapiSuite.ONE_HBAR; +import static com.hedera.services.bdd.suites.HapiSuite.ONE_HUNDRED_HBARS; +import static com.hedera.services.bdd.suites.HapiSuite.TOKEN_TREASURY; import static com.hedera.services.bdd.suites.utils.MiscEETUtils.metadata; import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.INVALID_SIGNATURE; import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.INVALID_TOKEN_ID_IN_CUSTOM_FEES; @@ -57,30 +62,22 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import com.hedera.services.bdd.junit.HapiTest; -import com.hedera.services.bdd.junit.HapiTestSuite; import com.hedera.services.bdd.spec.HapiSpec; -import com.hedera.services.bdd.spec.HapiSpecSetup; import com.hedera.services.bdd.spec.assertions.BaseErroringAssertsProvider; import com.hedera.services.bdd.spec.assertions.ErroringAsserts; import com.hedera.services.bdd.spec.utilops.records.SnapshotMatchMode; -import com.hedera.services.bdd.suites.HapiSuite; import com.hederahashgraph.api.proto.java.ResponseCodeEnum; import com.hederahashgraph.api.proto.java.TokenSupplyType; import com.hederahashgraph.api.proto.java.TokenTransferList; import java.util.Collections; import java.util.List; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; +import java.util.stream.Stream; +import org.junit.jupiter.api.DynamicTest; import org.junit.jupiter.api.Tag; -@HapiTestSuite(fuzzyMatch = true) @Tag(TOKEN) -public final class TokenPauseSpecs extends HapiSuite { - - private static final Logger LOG = LogManager.getLogger(TokenPauseSpecs.class); +public class TokenPauseSpecs { public static final String LEDGER_AUTO_RENEW_PERIOD_MIN_DURATION = "ledger.autoRenewPeriod.minDuration"; - public static final String DEFAULT_MIN_AUTO_RENEW_PERIOD = - HapiSpecSetup.getDefaultNodeProps().get(LEDGER_AUTO_RENEW_PERIOD_MIN_DURATION); private static final String PAUSE_KEY = "pauseKey"; private static final String SUPPLY_KEY = "supplyKey"; @@ -97,35 +94,8 @@ public final class TokenPauseSpecs extends HapiSuite { private static final String THIRD_USER = "thirdUser"; private static final String NON_FUNGIBLE_UNIQUE_PRIMARY = "non-fungible-unique-primary"; - public static void main(String... args) { - new TokenPauseSpecs().runSuiteAsync(); - } - - @Override - protected Logger getResultsLogger() { - return LOG; - } - - @Override - public List getSpecsInSuite() { - return List.of( - cannotPauseWithInvalidPauseKey(), - cannotChangePauseStatusIfMissingPauseKey(), - pausedFungibleTokenCannotBeUsed(), - pausedNonFungibleUniqueCannotBeUsed(), - unpauseWorks(), - basePauseAndUnpauseHaveExpectedPrices(), - pausedTokenInCustomFeeCaseStudy(), - cannotAddPauseKeyViaTokenUpdate()); - } - - @Override - public boolean canRunConcurrent() { - return true; - } - @HapiTest - final HapiSpec cannotAddPauseKeyViaTokenUpdate() { + final Stream cannotAddPauseKeyViaTokenUpdate() { return defaultHapiSpec("CannotAddPauseKeyViaTokenUpdate") .given(newKeyNamed(PAUSE_KEY), newKeyNamed(ADMIN_KEY)) .when(tokenCreate(PRIMARY), tokenCreate(SECONDARY).adminKey(ADMIN_KEY)) @@ -138,7 +108,7 @@ final HapiSpec cannotAddPauseKeyViaTokenUpdate() { } @HapiTest - final HapiSpec cannotPauseWithInvalidPauseKey() { + final Stream cannotPauseWithInvalidPauseKey() { return defaultHapiSpec("cannotPauseWithInvalidPauseKey") .given(newKeyNamed(PAUSE_KEY), newKeyNamed(OTHER_KEY)) .when(tokenCreate(PRIMARY).pauseKey(PAUSE_KEY)) @@ -146,7 +116,7 @@ final HapiSpec cannotPauseWithInvalidPauseKey() { } @HapiTest - HapiSpec pausedTokenInCustomFeeCaseStudy() { + final Stream pausedTokenInCustomFeeCaseStudy() { return defaultHapiSpec("PausedTokenInCustomFeeCaseStudy", SnapshotMatchMode.NONDETERMINISTIC_TRANSACTION_FEES) .given( cryptoCreate(TOKEN_TREASURY), @@ -191,7 +161,7 @@ HapiSpec pausedTokenInCustomFeeCaseStudy() { } @HapiTest - final HapiSpec unpauseWorks() { + final Stream unpauseWorks() { final String firstUser = FIRST_USER; final String token = PRIMARY; @@ -226,7 +196,7 @@ final HapiSpec unpauseWorks() { } @HapiTest - final HapiSpec pausedNonFungibleUniqueCannotBeUsed() { + final Stream pausedNonFungibleUniqueCannotBeUsed() { final String uniqueToken = "nonFungibleUnique"; final String firstUser = FIRST_USER; final String secondUser = SECOND_USER; @@ -311,7 +281,7 @@ final HapiSpec pausedNonFungibleUniqueCannotBeUsed() { } @HapiTest - final HapiSpec pausedFungibleTokenCannotBeUsed() { + final Stream pausedFungibleTokenCannotBeUsed() { final String token = PRIMARY; final String otherToken = SECONDARY; final String firstUser = FIRST_USER; @@ -394,7 +364,7 @@ final HapiSpec pausedFungibleTokenCannotBeUsed() { } @HapiTest - final HapiSpec cannotChangePauseStatusIfMissingPauseKey() { + final Stream cannotChangePauseStatusIfMissingPauseKey() { return defaultHapiSpec("CannotChangePauseStatusIfMissingPauseKey") .given(cryptoCreate(TOKEN_TREASURY)) .when( @@ -424,7 +394,7 @@ final HapiSpec cannotChangePauseStatusIfMissingPauseKey() { } @HapiTest - final HapiSpec basePauseAndUnpauseHaveExpectedPrices() { + final Stream basePauseAndUnpauseHaveExpectedPrices() { final var expectedBaseFee = 0.001; final var token = "token"; final var tokenPauseTransaction = "tokenPauseTxn"; diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/token/TokenTotalSupplyAfterMintBurnWipeSuite.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/token/TokenTotalSupplyAfterMintBurnWipeSuite.java index 0a1e4e874e18..5c19a064183a 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/token/TokenTotalSupplyAfterMintBurnWipeSuite.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/token/TokenTotalSupplyAfterMintBurnWipeSuite.java @@ -33,35 +33,18 @@ import static com.hedera.services.bdd.spec.utilops.UtilVerbs.newKeyNamed; import com.hedera.services.bdd.junit.HapiTest; -import com.hedera.services.bdd.junit.HapiTestSuite; -import com.hedera.services.bdd.spec.HapiSpec; import com.hedera.services.bdd.spec.utilops.records.SnapshotMatchMode; -import com.hedera.services.bdd.suites.HapiSuite; import com.hederahashgraph.api.proto.java.TokenType; -import java.util.List; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; +import java.util.stream.Stream; +import org.junit.jupiter.api.DynamicTest; import org.junit.jupiter.api.Tag; -@HapiTestSuite(fuzzyMatch = true) @Tag(TOKEN) -public class TokenTotalSupplyAfterMintBurnWipeSuite extends HapiSuite { - - private static final Logger log = LogManager.getLogger(TokenTotalSupplyAfterMintBurnWipeSuite.class); - +public class TokenTotalSupplyAfterMintBurnWipeSuite { private static String TOKEN_TREASURY = "treasury"; - public static void main(String... args) { - new TokenTotalSupplyAfterMintBurnWipeSuite().runSuiteSync(); - } - - @Override - public List getSpecsInSuite() { - return List.of(new HapiSpec[] {checkTokenTotalSupplyAfterMintAndBurn(), totalSupplyAfterWipe()}); - } - @HapiTest - public HapiSpec checkTokenTotalSupplyAfterMintAndBurn() { + final Stream checkTokenTotalSupplyAfterMintAndBurn() { String tokenName = "tokenToTest"; return defaultHapiSpec( "checkTokenTotalSupplyAfterMintAndBurn", SnapshotMatchMode.NONDETERMINISTIC_TRANSACTION_FEES) @@ -91,7 +74,7 @@ public HapiSpec checkTokenTotalSupplyAfterMintAndBurn() { } @HapiTest - public HapiSpec totalSupplyAfterWipe() { + final Stream totalSupplyAfterWipe() { var tokenToWipe = "tokenToWipe"; return defaultHapiSpec("totalSupplyAfterWipe") @@ -127,9 +110,4 @@ public HapiSpec totalSupplyAfterWipe() { .logged(), getAccountBalance(TOKEN_TREASURY).hasTokenBalance(tokenToWipe, 300)); } - - @Override - protected Logger getResultsLogger() { - return log; - } } diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/token/TokenTransactSpecs.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/token/TokenTransactSpecs.java index d3c7dec7d712..84adb0c1474a 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/token/TokenTransactSpecs.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/token/TokenTransactSpecs.java @@ -61,6 +61,13 @@ import static com.hedera.services.bdd.spec.utilops.UtilVerbs.withTargetLedgerId; import static com.hedera.services.bdd.spec.utilops.records.SnapshotMatchMode.HIGHLY_NON_DETERMINISTIC_FEES; import static com.hedera.services.bdd.spec.utilops.records.SnapshotMatchMode.NONDETERMINISTIC_TRANSACTION_FEES; +import static com.hedera.services.bdd.suites.HapiSuite.DEFAULT_PAYER; +import static com.hedera.services.bdd.suites.HapiSuite.FUNDING; +import static com.hedera.services.bdd.suites.HapiSuite.GENESIS; +import static com.hedera.services.bdd.suites.HapiSuite.HBAR_TOKEN_SENTINEL; +import static com.hedera.services.bdd.suites.HapiSuite.ONE_HBAR; +import static com.hedera.services.bdd.suites.HapiSuite.ONE_HUNDRED_HBARS; +import static com.hedera.services.bdd.suites.HapiSuite.TOKEN_TREASURY; import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.ACCOUNT_AMOUNT_TRANSFERS_ONLY_ALLOWED_FOR_FUNGIBLE_COMMON; import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.ACCOUNT_DELETED; import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.ACCOUNT_FROZEN_FOR_TOKEN; @@ -85,26 +92,21 @@ import com.google.protobuf.ByteString; import com.hedera.services.bdd.junit.HapiTest; -import com.hedera.services.bdd.junit.HapiTestSuite; import com.hedera.services.bdd.spec.HapiSpec; import com.hedera.services.bdd.spec.HapiSpecOperation; import com.hedera.services.bdd.spec.utilops.records.SnapshotMatchMode; -import com.hedera.services.bdd.suites.HapiSuite; import com.hederahashgraph.api.proto.java.CustomFee; import com.hederahashgraph.api.proto.java.TokenType; import java.util.List; import java.util.OptionalLong; import java.util.function.Function; +import java.util.stream.Stream; import org.apache.commons.lang3.tuple.Pair; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; +import org.junit.jupiter.api.DynamicTest; import org.junit.jupiter.api.Tag; -@HapiTestSuite(fuzzyMatch = true) @Tag(TOKEN) -public class TokenTransactSpecs extends HapiSuite { - private static final Logger log = LogManager.getLogger(TokenTransactSpecs.class); - +public class TokenTransactSpecs { public static final String PAYER = "payer"; private static final long TOTAL_SUPPLY = 1_000; private static final String A_TOKEN = "TokenA"; @@ -141,74 +143,8 @@ public class TokenTransactSpecs extends HapiSuite { public static final String FUNGIBLE = "fungible"; public static final String TRANSFER_TXN = "transferTxn"; - public static void main(String... args) { - new TokenTransactSpecs().runSuiteAsync(); - } - - @Override - public boolean canRunConcurrent() { - return true; - } - - @Override - @SuppressWarnings("java:S3878") - public List getSpecsInSuite() { - return List.of( - balancesChangeOnTokenTransfer(), - accountsMustBeExplicitlyUnfrozenOnlyIfDefaultFreezeIsTrue(), - senderSigsAreValid(), - balancesAreChecked(), - duplicateAccountsInTokenTransferRejected(), - tokenOnlyTxnsAreAtomic(), - tokenPlusHbarTxnsAreAtomic(), - nonZeroTransfersRejected(), - missingEntitiesRejected(), - allRequiredSigsAreChecked(), - uniqueTokenTxnAccountBalance(), - uniqueTokenTxnAccountBalancesForTreasury(), - uniqueTokenTxnWithNoAssociation(), - uniqueTokenTxnWithFrozenAccount(), - uniqueTokenTxnWithSenderNotSigned(), - uniqueTokenTxnWithReceiverNotSigned(), - uniqueTokenTxnsAreAtomic(), - uniqueTokenDeletedTxn(), - cannotSendFungibleToDissociatedContractsOrAccounts(), - cannotGiveNftsToDissociatedContractsOrAccounts(), - recordsIncludeBothFungibleTokenChangesAndOwnershipChange(), - transferListsEnforceTokenTypeRestrictions(), - // HIP-18 charging case studies - fixedHbarCaseStudy(), - fractionalCaseStudy(), - simpleHtsFeeCaseStudy(), - nestedHbarCaseStudy(), - nestedFractionalCaseStudy(), - nestedHtsCaseStudy(), - treasuriesAreExemptFromAllCustomFees(), - collectorsAreExemptFromTheirOwnFeesButNotOthers(), - multipleRoyaltyFallbackCaseStudy(), - normalRoyaltyCaseStudy(), - canTransactInTokenWithSelfDenominatedFixedFee(), - nftOwnersChangeAtomically(), - fractionalNetOfTransfersCaseStudy(), - royaltyAndFractionalTogetherCaseStudy(), - respondsCorrectlyWhenNonFungibleTokenWithRoyaltyUsedInTransferList(), - // HIP-573 charging case studies - collectorIsChargedFixedFeeUnlessExempt(), - collectorIsChargedFractionalFeeUnlessExempt(), - collectorIsChargedNetOfTransferFractionalFeeUnlessExempt(), - collectorIsChargedRoyaltyFeeUnlessExempt(), - collectorIsChargedRoyaltyFallbackFeeUnlessExempt(), - // HIP-23 - happyPathAutoAssociationsWorkForBothTokenTypes(), - failedAutoAssociationHasNoSideEffectsOrHistoryForUnrelatedProblem(), - newSlotsCanBeOpenedViaUpdate(), - newSlotsCanBeOpenedViaDissociate(), - autoAssociationWithKycTokenHasNoSideEffectsOrHistory(), - autoAssociationWithFrozenByDefaultTokenHasNoSideEffectsOrHistory()); - } - @HapiTest - public HapiSpec autoAssociationWithFrozenByDefaultTokenHasNoSideEffectsOrHistory() { + final Stream autoAssociationWithFrozenByDefaultTokenHasNoSideEffectsOrHistory() { final var beneficiary = BENEFICIARY; final var uniqueToken = UNIQUE; final var fungibleToken = FUNGIBLE; @@ -259,7 +195,7 @@ public HapiSpec autoAssociationWithFrozenByDefaultTokenHasNoSideEffectsOrHistory } @HapiTest - public HapiSpec autoAssociationWithKycTokenHasNoSideEffectsOrHistory() { + final Stream autoAssociationWithKycTokenHasNoSideEffectsOrHistory() { final var beneficiary = BENEFICIARY; final var uniqueToken = UNIQUE; final var fungibleToken = FUNGIBLE; @@ -308,7 +244,7 @@ public HapiSpec autoAssociationWithKycTokenHasNoSideEffectsOrHistory() { } @HapiTest - public HapiSpec failedAutoAssociationHasNoSideEffectsOrHistoryForUnrelatedProblem() { + final Stream failedAutoAssociationHasNoSideEffectsOrHistoryForUnrelatedProblem() { final var beneficiary = BENEFICIARY; final var unluckyBeneficiary = "unluckyBeneficiary"; final var thirdParty = "thirdParty"; @@ -359,7 +295,7 @@ public HapiSpec failedAutoAssociationHasNoSideEffectsOrHistoryForUnrelatedProble } @HapiTest - public HapiSpec newSlotsCanBeOpenedViaUpdate() { + final Stream newSlotsCanBeOpenedViaUpdate() { final var beneficiary = BENEFICIARY; final var uniqueToken = UNIQUE; final var firstFungibleToken = "firstFungibleToken"; @@ -431,7 +367,7 @@ public HapiSpec newSlotsCanBeOpenedViaUpdate() { } @HapiTest - public HapiSpec newSlotsCanBeOpenedViaDissociate() { + final Stream newSlotsCanBeOpenedViaDissociate() { final var beneficiary = BENEFICIARY; final var uniqueToken = UNIQUE; final var firstFungibleToken = "firstFungibleToken"; @@ -482,7 +418,7 @@ public HapiSpec newSlotsCanBeOpenedViaDissociate() { } @HapiTest - public HapiSpec happyPathAutoAssociationsWorkForBothTokenTypes() { + final Stream happyPathAutoAssociationsWorkForBothTokenTypes() { final var beneficiary = BENEFICIARY; final var uniqueToken = UNIQUE; final var fungibleToken = FUNGIBLE; @@ -528,7 +464,7 @@ public HapiSpec happyPathAutoAssociationsWorkForBothTokenTypes() { } @HapiTest - public HapiSpec transferListsEnforceTokenTypeRestrictions() { + final Stream transferListsEnforceTokenTypeRestrictions() { final var theAccount = "anybody"; final var nonFungibleToken = "non-fungible"; final var theKey = MULTIPURPOSE; @@ -557,7 +493,7 @@ public HapiSpec transferListsEnforceTokenTypeRestrictions() { } @HapiTest - public HapiSpec recordsIncludeBothFungibleTokenChangesAndOwnershipChange() { + final Stream recordsIncludeBothFungibleTokenChangesAndOwnershipChange() { final var theUniqueToken = "special"; final var theCommonToken = "quotidian"; final var theAccount = "lucky"; @@ -589,7 +525,7 @@ public HapiSpec recordsIncludeBothFungibleTokenChangesAndOwnershipChange() { } @HapiTest - public HapiSpec cannotGiveNftsToDissociatedContractsOrAccounts() { + final Stream cannotGiveNftsToDissociatedContractsOrAccounts() { final var theContract = "tbd"; final var theAccount = "alsoTbd"; final var theKey = MULTIPURPOSE; @@ -628,7 +564,7 @@ public HapiSpec cannotGiveNftsToDissociatedContractsOrAccounts() { } @HapiTest - public HapiSpec cannotSendFungibleToDissociatedContractsOrAccounts() { + final Stream cannotSendFungibleToDissociatedContractsOrAccounts() { final var theContract = "tbd"; final var theAccount = "alsoTbd"; return defaultHapiSpec("CannotSendFungibleToDissociatedContractsOrAccounts") @@ -663,7 +599,7 @@ public HapiSpec cannotSendFungibleToDissociatedContractsOrAccounts() { } @HapiTest - public HapiSpec missingEntitiesRejected() { + final Stream missingEntitiesRejected() { return defaultHapiSpec("missingEntitiesRejected") .given(tokenCreate("some").treasury(DEFAULT_PAYER)) .when() @@ -678,7 +614,7 @@ public HapiSpec missingEntitiesRejected() { } @HapiTest - public HapiSpec balancesAreChecked() { + final Stream balancesAreChecked() { return defaultHapiSpec("BalancesAreChecked") .given( cryptoCreate(PAYER), @@ -704,7 +640,7 @@ public HapiSpec balancesAreChecked() { } @HapiTest - public HapiSpec accountsMustBeExplicitlyUnfrozenOnlyIfDefaultFreezeIsTrue() { + final Stream accountsMustBeExplicitlyUnfrozenOnlyIfDefaultFreezeIsTrue() { return defaultHapiSpec("AccountsMustBeExplicitlyUnfrozenOnlyIfDefaultFreezeIsTrue") .given( cryptoCreate(RANDOM_BENEFICIARY).balance(0L), @@ -731,7 +667,7 @@ public HapiSpec accountsMustBeExplicitlyUnfrozenOnlyIfDefaultFreezeIsTrue() { } @HapiTest - public HapiSpec allRequiredSigsAreChecked() { + final Stream allRequiredSigsAreChecked() { return defaultHapiSpec("AllRequiredSigsAreChecked") .given( cryptoCreate(PAYER), @@ -777,7 +713,7 @@ public HapiSpec allRequiredSigsAreChecked() { } @HapiTest - public HapiSpec senderSigsAreValid() { + final Stream senderSigsAreValid() { return defaultHapiSpec("SenderSigsAreValid", NONDETERMINISTIC_TRANSACTION_FEES) .given( cryptoCreate(PAYER), @@ -808,7 +744,7 @@ public HapiSpec senderSigsAreValid() { } @HapiTest - public HapiSpec tokenPlusHbarTxnsAreAtomic() { + final Stream tokenPlusHbarTxnsAreAtomic() { return defaultHapiSpec("TokenPlusHbarTxnsAreAtomic") .given( cryptoCreate(PAYER), @@ -835,7 +771,7 @@ public HapiSpec tokenPlusHbarTxnsAreAtomic() { } @HapiTest - public HapiSpec tokenOnlyTxnsAreAtomic() { + final Stream tokenOnlyTxnsAreAtomic() { return defaultHapiSpec("TokenOnlyTxnsAreAtomic") .given( cryptoCreate(PAYER), @@ -857,7 +793,7 @@ public HapiSpec tokenOnlyTxnsAreAtomic() { } @HapiTest - public HapiSpec duplicateAccountsInTokenTransferRejected() { + final Stream duplicateAccountsInTokenTransferRejected() { return defaultHapiSpec("DuplicateAccountsInTokenTransferRejected") .given(cryptoCreate(FIRST_TREASURY), cryptoCreate(FIRST_USER), cryptoCreate(SECOND_USER)) .when( @@ -872,7 +808,7 @@ public HapiSpec duplicateAccountsInTokenTransferRejected() { } @HapiTest - public HapiSpec nonZeroTransfersRejected() { + final Stream nonZeroTransfersRejected() { return defaultHapiSpec("NonZeroTransfersRejected") .given(cryptoCreate(FIRST_TREASURY).balance(0L)) .when(tokenCreate(A_TOKEN)) @@ -883,7 +819,7 @@ public HapiSpec nonZeroTransfersRejected() { } @HapiTest - public HapiSpec balancesChangeOnTokenTransfer() { + final Stream balancesChangeOnTokenTransfer() { return defaultHapiSpec("BalancesChangeOnTokenTransfer") .given( cryptoCreate(FIRST_USER).balance(0L), @@ -909,7 +845,7 @@ public HapiSpec balancesChangeOnTokenTransfer() { } @HapiTest - public HapiSpec uniqueTokenTxnAccountBalance() { + final Stream uniqueTokenTxnAccountBalance() { return defaultHapiSpec("UniqueTokenTxnAccountBalance") .given( newKeyNamed(SUPPLY_KEY), @@ -939,7 +875,7 @@ public HapiSpec uniqueTokenTxnAccountBalance() { } @HapiTest - public HapiSpec uniqueTokenTxnAccountBalancesForTreasury() { + final Stream uniqueTokenTxnAccountBalancesForTreasury() { return defaultHapiSpec("UniqueTokenTxnAccountBalancesForTreasury") .given( newKeyNamed(SUPPLY_KEY), @@ -978,7 +914,7 @@ public HapiSpec uniqueTokenTxnAccountBalancesForTreasury() { } @HapiTest - public HapiSpec uniqueTokenTxnWithNoAssociation() { + final Stream uniqueTokenTxnWithNoAssociation() { return defaultHapiSpec("UniqueTokenTxnWithNoAssociation") .given( cryptoCreate(TOKEN_TREASURY), @@ -995,7 +931,7 @@ public HapiSpec uniqueTokenTxnWithNoAssociation() { } @HapiTest - public HapiSpec uniqueTokenTxnWithFrozenAccount() { + final Stream uniqueTokenTxnWithFrozenAccount() { return defaultHapiSpec("UniqueTokenTxnWithFrozenAccount", SnapshotMatchMode.NONDETERMINISTIC_TRANSACTION_FEES) .given( cryptoCreate(TOKEN_TREASURY).balance(0L), @@ -1016,7 +952,7 @@ public HapiSpec uniqueTokenTxnWithFrozenAccount() { } @HapiTest - public HapiSpec uniqueTokenTxnWithSenderNotSigned() { + final Stream uniqueTokenTxnWithSenderNotSigned() { return defaultHapiSpec("uniqueTokenTxnWithSenderNotSigned") .given( newKeyNamed(SUPPLY_KEY), @@ -1036,7 +972,7 @@ public HapiSpec uniqueTokenTxnWithSenderNotSigned() { } @HapiTest - public HapiSpec uniqueTokenTxnWithReceiverNotSigned() { + final Stream uniqueTokenTxnWithReceiverNotSigned() { return defaultHapiSpec("uniqueTokenTxnWithReceiverNotSigned") .given( newKeyNamed(SUPPLY_KEY), @@ -1057,7 +993,7 @@ public HapiSpec uniqueTokenTxnWithReceiverNotSigned() { } @HapiTest - public HapiSpec uniqueTokenTxnsAreAtomic() { + final Stream uniqueTokenTxnsAreAtomic() { return defaultHapiSpec("UniqueTokenTxnsAreAtomic") .given( newKeyNamed(SUPPLY_KEY), @@ -1088,7 +1024,7 @@ public HapiSpec uniqueTokenTxnsAreAtomic() { } @HapiTest - public HapiSpec uniqueTokenDeletedTxn() { + final Stream uniqueTokenDeletedTxn() { return defaultHapiSpec("UniqueTokenDeletedTxn") .given( newKeyNamed(SUPPLY_KEY), @@ -1112,7 +1048,7 @@ public HapiSpec uniqueTokenDeletedTxn() { } @HapiTest - public HapiSpec fixedHbarCaseStudy() { + final Stream fixedHbarCaseStudy() { final var alice = "Alice"; final var bob = "Bob"; final var tokenWithHbarFee = "TokenWithHbarFee"; @@ -1163,7 +1099,7 @@ public HapiSpec fixedHbarCaseStudy() { } @HapiTest - public HapiSpec fractionalCaseStudy() { + final Stream fractionalCaseStudy() { final var alice = "Alice"; final var bob = "Bob"; final var tokenWithFractionalFee = TOKEN_WITH_FRACTIONAL_FEE; @@ -1208,7 +1144,7 @@ public HapiSpec fractionalCaseStudy() { } @HapiTest - public HapiSpec fractionalNetOfTransfersCaseStudy() { + final Stream fractionalNetOfTransfersCaseStudy() { final var gerry = "gerry"; final var horace = "horace"; final var useCaseToken = TOKEN_WITH_FRACTIONAL_FEE; @@ -1253,7 +1189,7 @@ public HapiSpec fractionalNetOfTransfersCaseStudy() { } @HapiTest - public HapiSpec simpleHtsFeeCaseStudy() { + final Stream simpleHtsFeeCaseStudy() { final var claire = "Claire"; final var debbie = DEBBIE; final var simpleHtsFeeToken = "SimpleHtsFeeToken"; @@ -1309,7 +1245,7 @@ public HapiSpec simpleHtsFeeCaseStudy() { } @HapiTest - public HapiSpec nestedHbarCaseStudy() { + final Stream nestedHbarCaseStudy() { final var debbie = DEBBIE; final var edgar = EDGAR; final var tokenWithHbarFee = "TokenWithHbarFee"; @@ -1377,7 +1313,7 @@ public HapiSpec nestedHbarCaseStudy() { } @HapiTest - public HapiSpec nestedFractionalCaseStudy() { + final Stream nestedFractionalCaseStudy() { final var edgar = EDGAR; final var fern = "Fern"; final var tokenWithFractionalFee = TOKEN_WITH_FRACTIONAL_FEE; @@ -1444,7 +1380,7 @@ public HapiSpec nestedFractionalCaseStudy() { } @HapiTest - public HapiSpec multipleRoyaltyFallbackCaseStudy() { + final Stream multipleRoyaltyFallbackCaseStudy() { final var zephyr = "zephyr"; final var amelie = AMELIE; final var usdcTreasury = "bank"; @@ -1508,7 +1444,7 @@ public HapiSpec multipleRoyaltyFallbackCaseStudy() { } @HapiTest - public HapiSpec respondsCorrectlyWhenNonFungibleTokenWithRoyaltyUsedInTransferList() { + final Stream respondsCorrectlyWhenNonFungibleTokenWithRoyaltyUsedInTransferList() { final var supplyKey = "misc"; final var nonfungible = "nonfungible"; final var beneficiary = BENEFICIARY; @@ -1537,7 +1473,7 @@ public HapiSpec respondsCorrectlyWhenNonFungibleTokenWithRoyaltyUsedInTransferLi } @HapiTest - public HapiSpec royaltyAndFractionalTogetherCaseStudy() { + final Stream royaltyAndFractionalTogetherCaseStudy() { final var alice = "alice"; final var amelie = AMELIE; final var usdcTreasury = "bank"; @@ -1593,7 +1529,7 @@ public HapiSpec royaltyAndFractionalTogetherCaseStudy() { } @HapiTest - public HapiSpec normalRoyaltyCaseStudy() { + final Stream normalRoyaltyCaseStudy() { final var alice = "alice"; final var amelie = AMELIE; final var usdcTreasury = "bank"; @@ -1641,7 +1577,7 @@ public HapiSpec normalRoyaltyCaseStudy() { } @HapiTest - public HapiSpec nestedHtsCaseStudy() { + final Stream nestedHtsCaseStudy() { final var debbie = DEBBIE; final var edgar = EDGAR; final var feeToken = "FeeToken"; @@ -1717,7 +1653,7 @@ public HapiSpec nestedHtsCaseStudy() { } @HapiTest - public HapiSpec canTransactInTokenWithSelfDenominatedFixedFee() { + final Stream canTransactInTokenWithSelfDenominatedFixedFee() { final var protocolToken = "protocolToken"; final var gabriella = "gabriella"; final var harry = "harry"; @@ -1770,7 +1706,7 @@ public HapiSpec canTransactInTokenWithSelfDenominatedFixedFee() { * 9. And following getAccountNftInfos query knows that harry still has serial no 1 * */ @HapiTest - public HapiSpec nftOwnersChangeAtomically() { + final Stream nftOwnersChangeAtomically() { final var artToken = "artToken"; final var protocolToken = "protocolToken"; final var gabriella = "gabriella"; @@ -1813,7 +1749,7 @@ public HapiSpec nftOwnersChangeAtomically() { } @HapiTest - public HapiSpec treasuriesAreExemptFromAllCustomFees() { + final Stream treasuriesAreExemptFromAllCustomFees() { final var edgar = EDGAR; final var feeToken = "FeeToken"; final var topLevelToken = "TopLevelToken"; @@ -1894,7 +1830,7 @@ public HapiSpec treasuriesAreExemptFromAllCustomFees() { } @HapiTest - public HapiSpec collectorsAreExemptFromTheirOwnFeesButNotOthers() { + final Stream collectorsAreExemptFromTheirOwnFeesButNotOthers() { final var edgar = EDGAR; final var topLevelToken = "TopLevelToken"; final var treasuryForTopLevel = "TokenTreasury"; @@ -1953,7 +1889,7 @@ public HapiSpec collectorsAreExemptFromTheirOwnFeesButNotOthers() { // HIP-573 tests below @HapiTest // HERE - public HapiSpec collectorIsChargedFixedFeeUnlessExempt() { + final Stream collectorIsChargedFixedFeeUnlessExempt() { return defaultHapiSpec("CollectorIsChargedFixedFeeUnlessExempt", HIGHLY_NON_DETERMINISTIC_FEES) .given( setupWellKnownTokenWithTwoFeesOnlyOneExemptingCollectors( @@ -1980,7 +1916,7 @@ public HapiSpec collectorIsChargedFixedFeeUnlessExempt() { } @HapiTest // HERE - public HapiSpec collectorIsChargedFractionalFeeUnlessExempt() { + final Stream collectorIsChargedFractionalFeeUnlessExempt() { return defaultHapiSpec("CollectorIsChargedFractionalFeeUnlessExempt", HIGHLY_NON_DETERMINISTIC_FEES) .given( setupWellKnownTokenWithTwoFeesOnlyOneExemptingCollectors( @@ -2009,7 +1945,7 @@ public HapiSpec collectorIsChargedFractionalFeeUnlessExempt() { } @HapiTest - public HapiSpec collectorIsChargedNetOfTransferFractionalFeeUnlessExempt() { + final Stream collectorIsChargedNetOfTransferFractionalFeeUnlessExempt() { return defaultHapiSpec( "CollectorIsChargedNetOfTransferFractionalFeeUnlessExempt", HIGHLY_NON_DETERMINISTIC_FEES) .given( @@ -2038,7 +1974,7 @@ public HapiSpec collectorIsChargedNetOfTransferFractionalFeeUnlessExempt() { } @HapiTest // HERE custom fees fee collector ids in different order - public HapiSpec collectorIsChargedRoyaltyFeeUnlessExempt() { + final Stream collectorIsChargedRoyaltyFeeUnlessExempt() { return defaultHapiSpec("CollectorIsChargedRoyaltyFeeUnlessExempt", HIGHLY_NON_DETERMINISTIC_FEES) .given( setupWellKnownTokenWithTwoFeesOnlyOneExemptingCollectors( @@ -2071,7 +2007,7 @@ public HapiSpec collectorIsChargedRoyaltyFeeUnlessExempt() { } @HapiTest // HERE - public HapiSpec collectorIsChargedRoyaltyFallbackFeeUnlessExempt() { + final Stream collectorIsChargedRoyaltyFallbackFeeUnlessExempt() { return defaultHapiSpec("CollectorIsChargedRoyaltyFallbackFeeUnlessExempt", HIGHLY_NON_DETERMINISTIC_FEES) .given( setupWellKnownTokenWithTwoFeesOnlyOneExemptingCollectors( @@ -2183,9 +2119,4 @@ private Function royaltyFeePlusFallbackWith(final boolean a private String nameForCollectorOfFeeWith(final boolean allCollectorsExempt) { return allCollectorsExempt ? COLLECTOR_OF_FEE_WITH_EXEMPTIONS : COLLECTOR_OF_FEE_WITHOUT_EXEMPTIONS; } - - @Override - protected Logger getResultsLogger() { - return log; - } } diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/token/TokenUnhappyAccountsSuite.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/token/TokenUnhappyAccountsSuite.java index 4fbe6c39e12f..8b4309623e5c 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/token/TokenUnhappyAccountsSuite.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/token/TokenUnhappyAccountsSuite.java @@ -42,6 +42,12 @@ import static com.hedera.services.bdd.spec.utilops.UtilVerbs.sleepFor; import static com.hedera.services.bdd.spec.utilops.UtilVerbs.sourcing; import static com.hedera.services.bdd.spec.utilops.UtilVerbs.withOpContext; +import static com.hedera.services.bdd.suites.HapiSuite.APP_PROPERTIES; +import static com.hedera.services.bdd.suites.HapiSuite.DEFAULT_PAYER; +import static com.hedera.services.bdd.suites.HapiSuite.FUNDING; +import static com.hedera.services.bdd.suites.HapiSuite.GENESIS; +import static com.hedera.services.bdd.suites.HapiSuite.NODE; +import static com.hedera.services.bdd.suites.HapiSuite.THREE_MONTHS_IN_SECONDS; import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.ACCOUNT_EXPIRED_AND_PENDING_REMOVAL; import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.ACCOUNT_FROZEN_FOR_TOKEN; import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.ACCOUNT_IS_TREASURY; @@ -53,7 +59,6 @@ import com.hedera.services.bdd.spec.HapiSpec; import com.hedera.services.bdd.spec.assertions.BaseErroringAssertsProvider; import com.hedera.services.bdd.spec.assertions.ErroringAsserts; -import com.hedera.services.bdd.suites.HapiSuite; import com.hedera.services.bdd.suites.autorenew.AutoRenewConfigChoices; import com.hederahashgraph.api.proto.java.AccountAmount; import com.hederahashgraph.api.proto.java.TokenTransferList; @@ -62,12 +67,10 @@ import java.util.List; import java.util.Set; import java.util.concurrent.atomic.AtomicLong; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; +import java.util.stream.Stream; +import org.junit.jupiter.api.DynamicTest; -public class TokenUnhappyAccountsSuite extends HapiSuite { - - private static final Logger log = LogManager.getLogger(Hip17UnhappyAccountsSuite.class); +public class TokenUnhappyAccountsSuite { private static final String MEMO_1 = "memo1"; private static final String MEMO_2 = "memo2"; @@ -82,23 +85,8 @@ public class TokenUnhappyAccountsSuite extends HapiSuite { public static final String TOKENS = " tokens"; public static final String CREATION = "creation"; - public static void main(String... args) { - new Hip17UnhappyAccountsSuite().runSuiteSync(); - } - - @Override - public List getSpecsInSuite() { - return List.of(new HapiSpec[] { - /* Expired Account */ - uniqueTokenOperationsFailForExpiredAccount(), - /* AutoRemoved Account */ - uniqueTokenOperationsFailForAutoRemovedAccount(), - /* Expired Token */ - dissociationFromExpiredTokensAsExpected() - }); - } - - final HapiSpec uniqueTokenOperationsFailForAutoRemovedAccount() { + // (FUTURE) Enable when token expiration is implemented + final Stream uniqueTokenOperationsFailForAutoRemovedAccount() { return defaultHapiSpec("UniqueTokenOperationsFailForAutoRemovedAccount") .given( fileUpdate(APP_PROPERTIES) @@ -138,7 +126,8 @@ final HapiSpec uniqueTokenOperationsFailForAutoRemovedAccount() { wipeTokenAccount(UNIQUE_TOKEN_A, CLIENT_1, List.of(1L)).hasKnownStatus(INVALID_ACCOUNT_ID)); } - final HapiSpec uniqueTokenOperationsFailForExpiredAccount() { + // (FUTURE) Enable when token expiration is implemented + final Stream uniqueTokenOperationsFailForExpiredAccount() { return defaultHapiSpec("UniqueTokenOperationsFailForExpiredAccount") .given( fileUpdate(APP_PROPERTIES) @@ -186,7 +175,7 @@ final HapiSpec uniqueTokenOperationsFailForExpiredAccount() { // Enable when token expiration is implemented // @HapiTest - public HapiSpec dissociationFromExpiredTokensAsExpected() { + final Stream dissociationFromExpiredTokensAsExpected() { final String treasury = "accountA"; final String frozenAccount = "frozen"; final String unfrozenAccount = "unfrozen"; @@ -267,9 +256,4 @@ public ErroringAsserts> assertsFor(HapiSpec spec) { .hasToken(relationshipWith(expiringToken).freeze(Frozen)), tokenDissociate(frozenAccount, expiringToken).hasKnownStatus(ACCOUNT_FROZEN_FOR_TOKEN)); } - - @Override - protected Logger getResultsLogger() { - return log; - } } diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/token/TokenUpdateNftsSuite.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/token/TokenUpdateNftsSuite.java index 9d494bce03bf..d0cfecad7e50 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/token/TokenUpdateNftsSuite.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/token/TokenUpdateNftsSuite.java @@ -31,26 +31,24 @@ import static com.hedera.services.bdd.spec.utilops.UtilVerbs.validateChargedUsdWithin; import static com.hedera.services.bdd.spec.utilops.mod.ModificationUtils.withSuccessivelyVariedBodyIds; import static com.hedera.services.bdd.spec.utilops.records.SnapshotMatchMode.NONDETERMINISTIC_TRANSACTION_FEES; +import static com.hedera.services.bdd.suites.HapiSuite.DEFAULT_PAYER; +import static com.hedera.services.bdd.suites.HapiSuite.GENESIS; +import static com.hedera.services.bdd.suites.HapiSuite.ONE_HBAR; +import static com.hedera.services.bdd.suites.HapiSuite.ONE_HUNDRED_HBARS; import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.INVALID_SIGNATURE; import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.TOKEN_HAS_NO_METADATA_KEY; import static com.hederahashgraph.api.proto.java.TokenType.NON_FUNGIBLE_UNIQUE; import com.google.protobuf.ByteString; import com.hedera.services.bdd.junit.HapiTest; -import com.hedera.services.bdd.junit.HapiTestSuite; -import com.hedera.services.bdd.spec.HapiSpec; -import com.hedera.services.bdd.suites.HapiSuite; import com.hederahashgraph.api.proto.java.TokenSupplyType; import java.util.List; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; +import java.util.stream.Stream; +import org.junit.jupiter.api.DynamicTest; import org.junit.jupiter.api.Tag; -@HapiTestSuite @Tag(TOKEN) -public class TokenUpdateNftsSuite extends HapiSuite { - - private static final Logger log = LogManager.getLogger(TokenUpdateNftsSuite.class); +public class TokenUpdateNftsSuite { private static String TOKEN_TREASURY = "treasury"; private static final String NON_FUNGIBLE_TOKEN = "nonFungible"; private static final String SUPPLY_KEY = "supplyKey"; @@ -59,22 +57,8 @@ public class TokenUpdateNftsSuite extends HapiSuite { private static final String WIPE_KEY = "wipeKey"; private static final String NFT_TEST_METADATA = " test metadata"; - public static void main(String... args) { - new TokenUpdateNftsSuite().runSuiteAsync(); - } - - @Override - public boolean canRunConcurrent() { - return true; - } - - @Override - public List getSpecsInSuite() { - return List.of(updateMetadataOfNfts(), failsIfTokenHasNoMetadataKey(), updateSingleNftFeeChargedAsExpected()); - } - @HapiTest - public HapiSpec idVariantsTreatedAsExpected() { + final Stream idVariantsTreatedAsExpected() { return defaultHapiSpec("idVariantsTreatedAsExpected") .given( newKeyNamed("multiKey"), @@ -93,7 +77,7 @@ public HapiSpec idVariantsTreatedAsExpected() { } @HapiTest - private HapiSpec failsIfTokenHasNoMetadataKey() { + final Stream failsIfTokenHasNoMetadataKey() { return defaultHapiSpec("failsIfTokenHasNoMetadataKey") .given( newKeyNamed(SUPPLY_KEY), @@ -115,7 +99,7 @@ private HapiSpec failsIfTokenHasNoMetadataKey() { } @HapiTest - final HapiSpec updateMetadataOfNfts() { + final Stream updateMetadataOfNfts() { return defaultHapiSpec("updateMetadataOfNfts") .given( newKeyNamed(SUPPLY_KEY), @@ -157,7 +141,7 @@ final HapiSpec updateMetadataOfNfts() { } @HapiTest - final HapiSpec updateSingleNftFeeChargedAsExpected() { + final Stream updateSingleNftFeeChargedAsExpected() { final var expectedNftUpdatePriceUsd = 0.001; final var nftUpdateTxn = "nftUpdateTxn"; @@ -195,7 +179,7 @@ final HapiSpec updateSingleNftFeeChargedAsExpected() { } @HapiTest - final HapiSpec updateMultipleNftsFeeChargedAsExpected() { + final Stream updateMultipleNftsFeeChargedAsExpected() { final var expectedNftUpdatePriceUsd = 0.005; final var nftUpdateTxn = "nftUpdateTxn"; @@ -231,9 +215,4 @@ final HapiSpec updateMultipleNftsFeeChargedAsExpected() { .via(nftUpdateTxn)) .then(validateChargedUsdWithin(nftUpdateTxn, expectedNftUpdatePriceUsd, 0.01)); } - - @Override - protected Logger getResultsLogger() { - return log; - } } diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/token/TokenUpdateSpecs.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/token/TokenUpdateSpecs.java index 67419cb9d614..db386f39e03b 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/token/TokenUpdateSpecs.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/token/TokenUpdateSpecs.java @@ -45,6 +45,15 @@ import static com.hedera.services.bdd.spec.utilops.CustomSpecAssert.allRunFor; import static com.hedera.services.bdd.spec.utilops.UtilVerbs.newKeyNamed; import static com.hedera.services.bdd.spec.utilops.UtilVerbs.withOpContext; +import static com.hedera.services.bdd.suites.HapiSuite.ADDRESS_BOOK_CONTROL; +import static com.hedera.services.bdd.suites.HapiSuite.DEFAULT_PAYER; +import static com.hedera.services.bdd.suites.HapiSuite.GENESIS; +import static com.hedera.services.bdd.suites.HapiSuite.ONE_HBAR; +import static com.hedera.services.bdd.suites.HapiSuite.ONE_HUNDRED_HBARS; +import static com.hedera.services.bdd.suites.HapiSuite.ONE_MILLION_HBARS; +import static com.hedera.services.bdd.suites.HapiSuite.THREE_MONTHS_IN_SECONDS; +import static com.hedera.services.bdd.suites.HapiSuite.ZERO_BYTE_MEMO; +import static com.hedera.services.bdd.suites.HapiSuite.salted; import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.ACCOUNT_DELETED; import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.INVALID_ADMIN_KEY; import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.INVALID_AUTORENEW_ACCOUNT; @@ -56,6 +65,12 @@ import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.INVALID_ZERO_BYTE_IN_STRING; import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.NO_REMAINING_AUTOMATIC_ASSOCIATIONS; import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.TOKEN_HAS_NO_FEE_SCHEDULE_KEY; +import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.TOKEN_HAS_NO_FREEZE_KEY; +import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.TOKEN_HAS_NO_KYC_KEY; +import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.TOKEN_HAS_NO_METADATA_KEY; +import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.TOKEN_HAS_NO_PAUSE_KEY; +import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.TOKEN_HAS_NO_SUPPLY_KEY; +import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.TOKEN_HAS_NO_WIPE_KEY; import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.TOKEN_IS_IMMUTABLE; import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.TOKEN_NAME_TOO_LONG; import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.TOKEN_SYMBOL_TOO_LONG; @@ -65,27 +80,21 @@ import com.google.protobuf.ByteString; import com.hedera.services.bdd.junit.HapiTest; -import com.hedera.services.bdd.junit.HapiTestSuite; -import com.hedera.services.bdd.spec.HapiSpec; import com.hedera.services.bdd.spec.HapiSpecSetup; import com.hedera.services.bdd.spec.queries.crypto.ExpectedTokenRel; import com.hedera.services.bdd.spec.transactions.TxnUtils; -import com.hedera.services.bdd.suites.HapiSuite; import com.hederahashgraph.api.proto.java.TokenFreezeStatus; import com.hederahashgraph.api.proto.java.TokenKycStatus; import com.hederahashgraph.api.proto.java.TokenPauseStatus; import com.hederahashgraph.api.proto.java.TokenType; import java.time.Instant; import java.util.List; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; +import java.util.stream.Stream; +import org.junit.jupiter.api.DynamicTest; import org.junit.jupiter.api.Tag; -@HapiTestSuite @Tag(TOKEN) -public class TokenUpdateSpecs extends HapiSuite { - - private static final Logger log = LogManager.getLogger(TokenUpdateSpecs.class); +public class TokenUpdateSpecs { private static final int MAX_NAME_LENGTH = 100; private static final int MAX_SYMBOL_LENGTH = 100; private static final String PAYER = "payer"; @@ -96,49 +105,8 @@ public class TokenUpdateSpecs extends HapiSuite { private static final long defaultMaxLifetime = Long.parseLong(HapiSpecSetup.getDefaultNodeProps().get("entities.maxLifetime")); - public static void main(String... args) { - new TokenUpdateSpecs().runSuiteAsync(); - } - - @Override - public boolean canRunConcurrent() { - return true; - } - - @Override - public List getSpecsInSuite() { - return List.of( - symbolChanges(), - standardImmutabilitySemanticsHold(), - validAutoRenewWorks(), - tooLongNameCheckHolds(), - tooLongSymbolCheckHolds(), - nameChanges(), - keysChange(), - validatesAlreadyDeletedToken(), - treasuryEvolves(), - deletedAutoRenewAccountCheckHolds(), - renewalPeriodCheckHolds(), - invalidTreasuryCheckHolds(), - newTreasuryMustSign(), - newTreasuryAutoAssociationWorks(), - tokensCanBeMadeImmutableWithEmptyKeyList(), - updateNftTreasuryHappyPath(), - updateTokenTreasuryRequiresZeroTokenBalance(), - validatesMissingAdminKey(), - validatesMissingRef(), - validatesNewExpiry(), - /* HIP-18 */ - customFeesOnlyUpdatableWithKey(), - updateUniqueTreasuryWithNfts(), - updateHappyPath(), - safeToUpdateCustomFeesWithNewFallbackWhileTransferring(), - tokenUpdateCanClearMemo(), - canUpdateExpiryOnlyOpWithoutAdminKey()); - } - - @HapiTest - private HapiSpec canUpdateExpiryOnlyOpWithoutAdminKey() { + @HapiTest + final Stream canUpdateExpiryOnlyOpWithoutAdminKey() { final var smallBuffer = 12_345L; final var okExpiry = defaultMaxLifetime + Instant.now().getEpochSecond() - smallBuffer; String originalMemo = "First things first"; @@ -177,7 +145,7 @@ private HapiSpec canUpdateExpiryOnlyOpWithoutAdminKey() { } @HapiTest - final HapiSpec validatesNewExpiry() { + final Stream validatesNewExpiry() { final var smallBuffer = 12_345L; final var okExpiry = defaultMaxLifetime + Instant.now().getEpochSecond() - smallBuffer; final var excessiveExpiry = defaultMaxLifetime + Instant.now().getEpochSecond() + smallBuffer; @@ -190,7 +158,7 @@ final HapiSpec validatesNewExpiry() { } @HapiTest - final HapiSpec validatesAlreadyDeletedToken() { + final Stream validatesAlreadyDeletedToken() { return defaultHapiSpec("ValidatesAlreadyDeletedToken") .given( newKeyNamed("adminKey"), @@ -202,7 +170,7 @@ final HapiSpec validatesAlreadyDeletedToken() { } @HapiTest - final HapiSpec tokensCanBeMadeImmutableWithEmptyKeyList() { + final Stream tokensCanBeMadeImmutableWithEmptyKeyList() { final var mutableForNow = "mutableForNow"; return defaultHapiSpec("TokensCanBeMadeImmutableWithEmptyKeyList") .given( @@ -211,7 +179,7 @@ final HapiSpec tokensCanBeMadeImmutableWithEmptyKeyList() { tokenCreate(mutableForNow).adminKey("initialAdmin")) .when( tokenUpdate(mutableForNow) - .improperlyEmptyingAdminKey() + .usingInvalidAdminKey() .signedByPayerAnd("initialAdmin") .hasPrecheck(INVALID_ADMIN_KEY), tokenUpdate(mutableForNow).properlyEmptyingAdminKey().signedByPayerAnd("initialAdmin")) @@ -224,7 +192,7 @@ final HapiSpec tokensCanBeMadeImmutableWithEmptyKeyList() { } @HapiTest - final HapiSpec standardImmutabilitySemanticsHold() { + final Stream standardImmutabilitySemanticsHold() { long then = Instant.now().getEpochSecond() + 1_234_567L; final var immutable = "immutable"; return defaultHapiSpec("StandardImmutabilitySemanticsHold") @@ -237,7 +205,7 @@ final HapiSpec standardImmutabilitySemanticsHold() { } @HapiTest - final HapiSpec validatesMissingRef() { + final Stream validatesMissingRef() { return defaultHapiSpec("ValidatesMissingRef") .given(cryptoCreate(PAYER)) .when() @@ -255,7 +223,7 @@ final HapiSpec validatesMissingRef() { } @HapiTest - final HapiSpec validatesMissingAdminKey() { + final Stream validatesMissingAdminKey() { return defaultHapiSpec("ValidatesMissingAdminKey") .given( cryptoCreate(TOKEN_TREASURY).balance(0L), @@ -270,7 +238,7 @@ final HapiSpec validatesMissingAdminKey() { } @HapiTest - public HapiSpec keysChange() { + final Stream keysChange() { return defaultHapiSpec("KeysChange") .given( newKeyNamed("adminKey"), @@ -317,7 +285,7 @@ public HapiSpec keysChange() { } @HapiTest - public HapiSpec newTreasuryAutoAssociationWorks() { + final Stream newTreasuryAutoAssociationWorks() { return defaultHapiSpec("NewTreasuryAutoAssociationWorks") .given( newKeyNamed("adminKey"), @@ -341,7 +309,7 @@ public HapiSpec newTreasuryAutoAssociationWorks() { } @HapiTest - public HapiSpec newTreasuryMustSign() { + final Stream newTreasuryMustSign() { return defaultHapiSpec("NewTreasuryMustSign") .given( newKeyNamed("adminKey"), @@ -360,7 +328,7 @@ public HapiSpec newTreasuryMustSign() { } @HapiTest - public HapiSpec treasuryEvolves() { + final Stream treasuryEvolves() { return defaultHapiSpec("TreasuryEvolves") .given( newKeyNamed("adminKey"), @@ -389,7 +357,7 @@ public HapiSpec treasuryEvolves() { } @HapiTest - public HapiSpec validAutoRenewWorks() { + final Stream validAutoRenewWorks() { final var firstPeriod = THREE_MONTHS_IN_SECONDS; final var secondPeriod = THREE_MONTHS_IN_SECONDS + 1234; return defaultHapiSpec("validAutoRenewWorks") @@ -415,7 +383,7 @@ public HapiSpec validAutoRenewWorks() { } @HapiTest - public HapiSpec symbolChanges() { + final Stream symbolChanges() { var hopefullyUnique = "ORIGINAL" + TxnUtils.randomUppercase(5); return defaultHapiSpec("SymbolChanges") @@ -430,7 +398,7 @@ public HapiSpec symbolChanges() { } @HapiTest - public HapiSpec changeAutoRenewAccount() { + final Stream changeAutoRenewAccount() { var account = "autoRenewAccount"; return defaultHapiSpec("AutoRenewAccountChange") @@ -448,7 +416,7 @@ public HapiSpec changeAutoRenewAccount() { } @HapiTest - public HapiSpec nameChanges() { + final Stream nameChanges() { var hopefullyUnique = "ORIGINAL" + TxnUtils.randomUppercase(5); return defaultHapiSpec("NameChanges") @@ -460,7 +428,7 @@ public HapiSpec nameChanges() { } @HapiTest - public HapiSpec tooLongNameCheckHolds() { + final Stream tooLongNameCheckHolds() { var tooLongName = "ORIGINAL" + TxnUtils.randomUppercase(MAX_NAME_LENGTH + 1); return defaultHapiSpec("TooLongNameCheckHolds") @@ -473,7 +441,7 @@ public HapiSpec tooLongNameCheckHolds() { } @HapiTest - public HapiSpec tooLongSymbolCheckHolds() { + final Stream tooLongSymbolCheckHolds() { var tooLongSymbol = TxnUtils.randomUppercase(MAX_SYMBOL_LENGTH + 1); return defaultHapiSpec("TooLongSymbolCheckHolds") @@ -486,7 +454,7 @@ public HapiSpec tooLongSymbolCheckHolds() { } @HapiTest - public HapiSpec deletedAutoRenewAccountCheckHolds() { + final Stream deletedAutoRenewAccountCheckHolds() { return defaultHapiSpec("DeletedAutoRenewAccountCheckHolds") .given( newKeyNamed("adminKey"), @@ -502,7 +470,7 @@ public HapiSpec deletedAutoRenewAccountCheckHolds() { } @HapiTest - public HapiSpec renewalPeriodCheckHolds() { + final Stream renewalPeriodCheckHolds() { return defaultHapiSpec("RenewalPeriodCheckHolds") .given( newKeyNamed("adminKey"), @@ -536,7 +504,7 @@ public HapiSpec renewalPeriodCheckHolds() { } @HapiTest - public HapiSpec invalidTreasuryCheckHolds() { + final Stream invalidTreasuryCheckHolds() { return defaultHapiSpec("InvalidTreasuryCheckHolds") .given( newKeyNamed("adminKey"), @@ -552,7 +520,7 @@ public HapiSpec invalidTreasuryCheckHolds() { } @HapiTest - public HapiSpec updateHappyPath() { + final Stream updateHappyPath() { String originalMemo = "First things first"; String updatedMemo = "Nothing left to do"; String saltedName = salted("primary"); @@ -639,7 +607,7 @@ public HapiSpec updateHappyPath() { } @HapiTest - public HapiSpec updateTokenTreasuryRequiresZeroTokenBalance() { + final Stream updateTokenTreasuryRequiresZeroTokenBalance() { return defaultHapiSpec("updateTokenTreasuryRequiresZeroTokenBalance") .given( cryptoCreate("oldTreasury"), @@ -665,7 +633,7 @@ public HapiSpec updateTokenTreasuryRequiresZeroTokenBalance() { } @HapiTest - public HapiSpec tokenUpdateCanClearMemo() { + final Stream tokenUpdateCanClearMemo() { final var token = "token"; final var multiKey = "multiKey"; final var memoToBeErased = "memoToBeErased"; @@ -679,7 +647,7 @@ public HapiSpec tokenUpdateCanClearMemo() { } @HapiTest - public HapiSpec updateNftTreasuryHappyPath() { + final Stream updateNftTreasuryHappyPath() { return defaultHapiSpec("UpdateNftTreasuryHappyPath") .given( cryptoCreate(TOKEN_TREASURY), @@ -715,13 +683,12 @@ public HapiSpec updateNftTreasuryHappyPath() { } @HapiTest - final HapiSpec safeToUpdateCustomFeesWithNewFallbackWhileTransferring() { + final Stream safeToUpdateCustomFeesWithNewFallbackWhileTransferring() { final var uniqueTokenFeeKey = "uniqueTokenFeeKey"; final var hbarCollector = "hbarFee"; final var beneficiary = "luckyOne"; final var multiKey = "allSeasons"; final var sender = "sender"; - final var numRaces = 3; return defaultHapiSpec("SafeToUpdateCustomFeesWithNewFallbackWhileTransferring") .given( @@ -761,7 +728,7 @@ final HapiSpec safeToUpdateCustomFeesWithNewFallbackWhileTransferring() { } @HapiTest - final HapiSpec customFeesOnlyUpdatableWithKey() { + final Stream customFeesOnlyUpdatableWithKey() { final var origHbarFee = 1_234L; final var newHbarFee = 4_321L; @@ -817,7 +784,205 @@ final HapiSpec customFeesOnlyUpdatableWithKey() { } @HapiTest - public HapiSpec updateUniqueTreasuryWithNfts() { + final Stream tokenUpdateTokenHasNoFreezeKey() { + final var tokenName = "token"; + final var HBAR_COLLECTOR = "hbarFee"; + + final var admin = "admin"; + final var adminKey = "adminKey"; + final var freezeKey = "freezeKey"; + final var freeze = "freeze"; + final var freezeKey2 = "freezeKey2"; + + return defaultHapiSpec("tokenUpdateTokenHasNoFreezeKey") + .given( + newKeyNamed(adminKey), + newKeyNamed(freezeKey), + newKeyNamed(freezeKey2), + cryptoCreate(HBAR_COLLECTOR), + cryptoCreate(TOKEN_TREASURY), + cryptoCreate(admin).key(adminKey).balance(ONE_MILLION_HBARS), + cryptoCreate(freeze).key(freezeKey).balance(ONE_MILLION_HBARS), + tokenCreate(tokenName) + .treasury(TOKEN_TREASURY) + .initialSupply(10) + .adminKey(adminKey) + .payingWith(admin)) + .when(tokenUpdate(tokenName) + .freezeKey(freezeKey2) + .signedBy(adminKey, freezeKey) + .payingWith(freeze) + .hasKnownStatus(TOKEN_HAS_NO_FREEZE_KEY)) + .then(); + } + + @HapiTest + final Stream tokenUpdateTokenHasNoSupplyKey() { + final var tokenName = "token"; + final var HBAR_COLLECTOR = "hbarFee"; + + final var admin = "admin"; + final var adminKey = "adminKey"; + final var supplyKey = "supplyKey"; + final var supply = "supply"; + final var supplyKey2 = "supplyKey2"; + + return defaultHapiSpec("tokenUpdateTokenHasNoSupplyKey") + .given( + newKeyNamed(adminKey), + newKeyNamed(supplyKey), + newKeyNamed(supplyKey2), + cryptoCreate(HBAR_COLLECTOR), + cryptoCreate(TOKEN_TREASURY), + cryptoCreate(admin).key(adminKey).balance(ONE_MILLION_HBARS), + cryptoCreate(supply).key(supplyKey).balance(ONE_MILLION_HBARS), + tokenCreate(tokenName) + .treasury(TOKEN_TREASURY) + .initialSupply(10) + .adminKey(adminKey) + .payingWith(admin)) + .when(tokenUpdate(tokenName) + .supplyKey(supplyKey2) + .signedBy(adminKey, supplyKey) + .payingWith(supply) + .hasKnownStatus(TOKEN_HAS_NO_SUPPLY_KEY)) + .then(); + } + + @HapiTest + final Stream tokenUpdateTokenHasNoKycKey() { + final var tokenName = "token"; + final var HBAR_COLLECTOR = "hbarFee"; + + final var admin = "admin"; + final var adminKey = "adminKey"; + final var kycKey = "kycKey"; + final var kyc = "kyc"; + final var kycKey2 = "kycKey2"; + + return defaultHapiSpec("tokenUpdateTokenHasNoKycKey") + .given( + newKeyNamed(adminKey), + newKeyNamed(kycKey), + newKeyNamed(kycKey2), + cryptoCreate(HBAR_COLLECTOR), + cryptoCreate(TOKEN_TREASURY), + cryptoCreate(admin).key(adminKey).balance(ONE_MILLION_HBARS), + cryptoCreate(kyc).key(kycKey).balance(ONE_MILLION_HBARS), + tokenCreate(tokenName) + .treasury(TOKEN_TREASURY) + .initialSupply(10) + .adminKey(adminKey) + .payingWith(admin)) + .when(tokenUpdate(tokenName) + .kycKey(kycKey2) + .signedBy(adminKey, kycKey) + .payingWith(kyc) + .hasKnownStatus(TOKEN_HAS_NO_KYC_KEY)) + .then(); + } + + @HapiTest + final Stream tokenUpdateTokenHasNoWipeKey() { + final var tokenName = "token"; + final var HBAR_COLLECTOR = "hbarFee"; + + final var admin = "admin"; + final var adminKey = "adminKey"; + final var wipeKey = "wipeKey"; + final var wipe = "wipe"; + final var wipeKey2 = "wipeKey2"; + + return defaultHapiSpec("tokenUpdateTokenHasNoWipeKey") + .given( + newKeyNamed(adminKey), + newKeyNamed(wipeKey), + newKeyNamed(wipeKey2), + cryptoCreate(HBAR_COLLECTOR), + cryptoCreate(TOKEN_TREASURY), + cryptoCreate(admin).key(adminKey).balance(ONE_MILLION_HBARS), + cryptoCreate(wipe).key(wipeKey).balance(ONE_MILLION_HBARS), + tokenCreate(tokenName) + .treasury(TOKEN_TREASURY) + .initialSupply(10) + .adminKey(adminKey) + .payingWith(admin)) + .when(tokenUpdate(tokenName) + .wipeKey(wipeKey2) + .signedBy(adminKey, wipeKey) + .payingWith(wipe) + .hasKnownStatus(TOKEN_HAS_NO_WIPE_KEY)) + .then(); + } + + @HapiTest + final Stream tokenUpdateTokenHasNoPauseKey() { + final var tokenName = "token"; + final var HBAR_COLLECTOR = "hbarFee"; + + final var admin = "admin"; + final var adminKey = "adminKey"; + final var pauseKey = "pauseKey"; + final var pause = "pause"; + final var pauseKey2 = "pauseKey2"; + + return defaultHapiSpec("tokenUpdateTokenHasNoPauseKey") + .given( + newKeyNamed(adminKey), + newKeyNamed(pauseKey), + newKeyNamed(pauseKey2), + cryptoCreate(HBAR_COLLECTOR), + cryptoCreate(TOKEN_TREASURY), + cryptoCreate(admin).key(adminKey).balance(ONE_MILLION_HBARS), + cryptoCreate(pause).key(pauseKey).balance(ONE_MILLION_HBARS), + tokenCreate(tokenName) + .treasury(TOKEN_TREASURY) + .initialSupply(10) + .adminKey(adminKey) + .payingWith(admin)) + .when(tokenUpdate(tokenName) + .pauseKey(pauseKey2) + .signedBy(adminKey, pauseKey) + .payingWith(pause) + .hasKnownStatus(TOKEN_HAS_NO_PAUSE_KEY)) + .then(); + } + + @HapiTest + final Stream tokenUpdateTokenHasNoMetadataKey() { + final var tokenName = "token"; + final var HBAR_COLLECTOR = "hbarFee"; + + final var admin = "admin"; + final var adminKey = "adminKey"; + final var metadataKey = "metadataKey"; + final var metadata = "metadata"; + final var metadataKey2 = "metadataKey2"; + + return defaultHapiSpec("tokenUpdateTokenHasNoMetadataKey") + .given( + newKeyNamed(adminKey), + newKeyNamed(metadataKey), + newKeyNamed(metadataKey2), + cryptoCreate(HBAR_COLLECTOR), + cryptoCreate(TOKEN_TREASURY), + cryptoCreate(admin).key(adminKey).balance(ONE_MILLION_HBARS), + cryptoCreate(metadata).key(metadataKey).balance(ONE_MILLION_HBARS), + tokenCreate(tokenName) + .treasury(TOKEN_TREASURY) + .initialSupply(10) + .adminKey(adminKey) + .payingWith(admin)) + .when(tokenUpdate(tokenName) + .metadataKey(metadataKey2) + .signedBy(adminKey, metadataKey) + .payingWith(metadata) + .hasKnownStatus(TOKEN_HAS_NO_METADATA_KEY)) + .then(); + } + + @HapiTest + final Stream updateUniqueTreasuryWithNfts() { final var specialKey = "special"; return defaultHapiSpec("UpdateUniqueTreasuryWithNfts") @@ -849,9 +1014,4 @@ public HapiSpec updateUniqueTreasuryWithNfts() { getAccountInfo("newTreasury").logged(), getTokenInfo("tbu").hasTreasury("newTreasury")); } - - @Override - protected Logger getResultsLogger() { - return log; - } } diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/token/UniqueTokenManagementSpecs.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/token/UniqueTokenManagementSpecs.java index c217ccc124c1..5f2233f36481 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/token/UniqueTokenManagementSpecs.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/token/UniqueTokenManagementSpecs.java @@ -37,6 +37,7 @@ import static com.hedera.services.bdd.spec.transactions.token.TokenMovement.movingUnique; import static com.hedera.services.bdd.spec.utilops.CustomSpecAssert.allRunFor; import static com.hedera.services.bdd.spec.utilops.UtilVerbs.newKeyNamed; +import static com.hedera.services.bdd.suites.HapiSuite.TOKEN_TREASURY; import static com.hedera.services.bdd.suites.utils.MiscEETUtils.batchOfSize; import static com.hedera.services.bdd.suites.utils.MiscEETUtils.metadata; import static com.hedera.services.bdd.suites.utils.MiscEETUtils.metadataOfLength; @@ -58,12 +59,9 @@ import com.google.protobuf.ByteString; import com.hedera.services.bdd.junit.HapiTest; -import com.hedera.services.bdd.junit.HapiTestSuite; -import com.hedera.services.bdd.spec.HapiSpec; import com.hedera.services.bdd.spec.transactions.TxnUtils; import com.hedera.services.bdd.spec.transactions.token.TokenMovement; import com.hedera.services.bdd.spec.utilops.UtilVerbs; -import com.hedera.services.bdd.suites.HapiSuite; import com.hederahashgraph.api.proto.java.AccountID; import com.hederahashgraph.api.proto.java.ResponseCodeEnum; import com.hederahashgraph.api.proto.java.TokenSupplyType; @@ -71,14 +69,15 @@ import java.util.List; import java.util.stream.Collectors; import java.util.stream.LongStream; +import java.util.stream.Stream; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.DynamicTest; import org.junit.jupiter.api.Tag; -@HapiTestSuite @Tag(TOKEN) -public class UniqueTokenManagementSpecs extends HapiSuite { +public class UniqueTokenManagementSpecs { private static final org.apache.logging.log4j.Logger log = LogManager.getLogger(UniqueTokenManagementSpecs.class); private static final String A_TOKEN = "TokenA"; @@ -99,46 +98,8 @@ public class UniqueTokenManagementSpecs extends HapiSuite { private static final String CUSTOM_PAYER = "customPayer"; private static final String WIPE_KEY = "wipeKey"; - public static void main(String... args) { - new UniqueTokenManagementSpecs().runSuiteSync(); - } - - @Override - public List getSpecsInSuite() { - return List.of( - mintFailsWithLargeBatchSize(), - mintFailsWithTooLongMetadata(), - mintFailsWithInvalidMetadataFromBatch(), - mintUniqueTokenHappyPath(), - mintTokenWorksWhenAccountsAreFrozenByDefault(), - mintFailsWithDeletedToken(), - mintUniqueTokenWorksWithRepeatedMetadata(), - mintDistinguishesFeeSubTypes(), - mintUniqueTokenReceiptCheck(), - populatingMetadataForFungibleDoesNotWork(), - populatingAmountForNonFungibleDoesNotWork(), - finiteNftReachesMaxSupplyProperly(), - burnHappyPath(), - canOnlyBurnFromTreasury(), - burnFailsOnInvalidSerialNumber(), - burnRespectsBurnBatchConstraints(), - treasuryBalanceCorrectAfterBurn(), - burnWorksWhenAccountsAreFrozenByDefault(), - serialNumbersOnlyOnFungibleBurnFails(), - amountOnlyOnNonFungibleBurnFails(), - wipeHappyPath(), - wipeRespectsConstraints(), - commonWipeFailsWhenInvokedOnUniqueToken(), - uniqueWipeFailsWhenInvokedOnFungibleToken(), - wipeFailsWithInvalidSerialNumber(), - getTokenNftInfoWorks(), - getTokenNftInfoFailsWithNoNft(), - tokenDissociateHappyPath(), - tokenDissociateFailsIfAccountOwnsUniqueTokens()); - } - @HapiTest // here - final HapiSpec populatingMetadataForFungibleDoesNotWork() { + final Stream populatingMetadataForFungibleDoesNotWork() { return defaultHapiSpec("PopulatingMetadataForFungibleDoesNotWork") .given( newKeyNamed(SUPPLY_KEY), @@ -169,7 +130,7 @@ final HapiSpec populatingMetadataForFungibleDoesNotWork() { } @HapiTest - final HapiSpec populatingAmountForNonFungibleDoesNotWork() { + final Stream populatingAmountForNonFungibleDoesNotWork() { return defaultHapiSpec("PopulatingAmountForNonFungibleDoesNotWork") .given( newKeyNamed(SUPPLY_KEY), @@ -196,7 +157,7 @@ final HapiSpec populatingAmountForNonFungibleDoesNotWork() { } @HapiTest - final HapiSpec finiteNftReachesMaxSupplyProperly() { + final Stream finiteNftReachesMaxSupplyProperly() { return defaultHapiSpec("FiniteNftReachesMaxSupplyProperly") .given( newKeyNamed(SUPPLY_KEY), @@ -230,7 +191,7 @@ final HapiSpec finiteNftReachesMaxSupplyProperly() { } @HapiTest - final HapiSpec serialNumbersOnlyOnFungibleBurnFails() { + final Stream serialNumbersOnlyOnFungibleBurnFails() { return defaultHapiSpec("SerialNumbersOnlyOnFungibleBurnFails") .given( newKeyNamed(SUPPLY_KEY), @@ -258,7 +219,7 @@ final HapiSpec serialNumbersOnlyOnFungibleBurnFails() { } @HapiTest - final HapiSpec amountOnlyOnNonFungibleBurnFails() { + final Stream amountOnlyOnNonFungibleBurnFails() { return defaultHapiSpec("AmountOnlyOnNonFungibleBurnFails") .given( newKeyNamed(SUPPLY_KEY), @@ -285,7 +246,7 @@ final HapiSpec amountOnlyOnNonFungibleBurnFails() { } @HapiTest - final HapiSpec burnWorksWhenAccountsAreFrozenByDefault() { + final Stream burnWorksWhenAccountsAreFrozenByDefault() { return defaultHapiSpec("BurnWorksWhenAccountsAreFrozenByDefault") .given( newKeyNamed(SUPPLY_KEY), @@ -305,7 +266,7 @@ final HapiSpec burnWorksWhenAccountsAreFrozenByDefault() { } @HapiTest - final HapiSpec burnFailsOnInvalidSerialNumber() { + final Stream burnFailsOnInvalidSerialNumber() { return defaultHapiSpec("BurnFailsOnInvalidSerialNumber") .given( newKeyNamed(SUPPLY_KEY), @@ -324,7 +285,7 @@ final HapiSpec burnFailsOnInvalidSerialNumber() { } @HapiTest - final HapiSpec burnRespectsBurnBatchConstraints() { + final Stream burnRespectsBurnBatchConstraints() { return defaultHapiSpec("BurnRespectsBurnBatchConstraints") .given( newKeyNamed(SUPPLY_KEY), @@ -344,7 +305,7 @@ final HapiSpec burnRespectsBurnBatchConstraints() { } @HapiTest - final HapiSpec burnHappyPath() { + final Stream burnHappyPath() { return defaultHapiSpec("BurnHappyPath") .given( newKeyNamed(SUPPLY_KEY), @@ -367,7 +328,7 @@ final HapiSpec burnHappyPath() { } @HapiTest - final HapiSpec canOnlyBurnFromTreasury() { + final Stream canOnlyBurnFromTreasury() { final var nonTreasury = "anybodyElse"; return defaultHapiSpec("CanOnlyBurnFromTreasury") @@ -396,7 +357,7 @@ final HapiSpec canOnlyBurnFromTreasury() { } @HapiTest - final HapiSpec treasuryBalanceCorrectAfterBurn() { + final Stream treasuryBalanceCorrectAfterBurn() { return defaultHapiSpec("TreasuryBalanceCorrectAfterBurn") .given( newKeyNamed(SUPPLY_KEY), @@ -423,7 +384,7 @@ final HapiSpec treasuryBalanceCorrectAfterBurn() { } @HapiTest - final HapiSpec mintDistinguishesFeeSubTypes() { + final Stream mintDistinguishesFeeSubTypes() { return defaultHapiSpec("MintDistinguishesFeeSubTypes") .given( newKeyNamed(SUPPLY_KEY), @@ -462,7 +423,7 @@ final HapiSpec mintDistinguishesFeeSubTypes() { } @HapiTest - final HapiSpec mintFailsWithTooLongMetadata() { + final Stream mintFailsWithTooLongMetadata() { return defaultHapiSpec("MintFailsWithTooLongMetadata") .given( newKeyNamed(SUPPLY_KEY), @@ -478,7 +439,7 @@ final HapiSpec mintFailsWithTooLongMetadata() { } @HapiTest - final HapiSpec mintFailsWithInvalidMetadataFromBatch() { + final Stream mintFailsWithInvalidMetadataFromBatch() { return defaultHapiSpec("MintFailsWithInvalidMetadataFromBatch") .given( newKeyNamed(SUPPLY_KEY), @@ -495,7 +456,7 @@ final HapiSpec mintFailsWithInvalidMetadataFromBatch() { } @HapiTest - final HapiSpec mintFailsWithLargeBatchSize() { + final Stream mintFailsWithLargeBatchSize() { return defaultHapiSpec("MintFailsWithLargeBatchSize") .given( newKeyNamed(SUPPLY_KEY), @@ -511,7 +472,7 @@ final HapiSpec mintFailsWithLargeBatchSize() { } @HapiTest - final HapiSpec mintUniqueTokenHappyPath() { + final Stream mintUniqueTokenHappyPath() { return defaultHapiSpec("MintUniqueTokenHappyPath") .given( newKeyNamed(SUPPLY_KEY), @@ -548,7 +509,7 @@ final HapiSpec mintUniqueTokenHappyPath() { } @HapiTest - final HapiSpec mintTokenWorksWhenAccountsAreFrozenByDefault() { + final Stream mintTokenWorksWhenAccountsAreFrozenByDefault() { return defaultHapiSpec("MintTokenWorksWhenAccountsAreFrozenByDefault") .given( newKeyNamed(SUPPLY_KEY), @@ -572,7 +533,7 @@ final HapiSpec mintTokenWorksWhenAccountsAreFrozenByDefault() { } @HapiTest - final HapiSpec mintFailsWithDeletedToken() { + final Stream mintFailsWithDeletedToken() { return defaultHapiSpec("MintFailsWithDeletedToken") .given( newKeyNamed(SUPPLY_KEY), @@ -590,7 +551,7 @@ final HapiSpec mintFailsWithDeletedToken() { } @HapiTest - final HapiSpec getTokenNftInfoFailsWithNoNft() { + final Stream getTokenNftInfoFailsWithNoNft() { return defaultHapiSpec("GetTokenNftInfoFailsWithNoNft") .given(newKeyNamed(SUPPLY_KEY), cryptoCreate(TOKEN_TREASURY)) .when( @@ -608,7 +569,7 @@ final HapiSpec getTokenNftInfoFailsWithNoNft() { } @HapiTest - final HapiSpec getTokenNftInfoWorks() { + final Stream getTokenNftInfoWorks() { return defaultHapiSpec("GetTokenNftInfoWorks") .given(newKeyNamed(SUPPLY_KEY), cryptoCreate(TOKEN_TREASURY)) .when( @@ -632,7 +593,7 @@ final HapiSpec getTokenNftInfoWorks() { } @HapiTest - final HapiSpec mintUniqueTokenWorksWithRepeatedMetadata() { + final Stream mintUniqueTokenWorksWithRepeatedMetadata() { return defaultHapiSpec("MintUniqueTokenWorksWithRepeatedMetadata") .given( newKeyNamed(SUPPLY_KEY), @@ -662,7 +623,7 @@ final HapiSpec mintUniqueTokenWorksWithRepeatedMetadata() { } @HapiTest - final HapiSpec wipeHappyPath() { + final Stream wipeHappyPath() { return defaultHapiSpec("WipeHappyPath") .given( newKeyNamed(SUPPLY_KEY), @@ -694,7 +655,7 @@ final HapiSpec wipeHappyPath() { } @HapiTest - final HapiSpec wipeRespectsConstraints() { + final Stream wipeRespectsConstraints() { return defaultHapiSpec("WipeRespectsConstraints") .given( newKeyNamed(SUPPLY_KEY), @@ -720,7 +681,7 @@ final HapiSpec wipeRespectsConstraints() { } @HapiTest // here - final HapiSpec commonWipeFailsWhenInvokedOnUniqueToken() { + final Stream commonWipeFailsWhenInvokedOnUniqueToken() { return defaultHapiSpec("CommonWipeFailsWhenInvokedOnUniqueToken") .given( newKeyNamed(SUPPLY_KEY), @@ -752,7 +713,7 @@ final HapiSpec commonWipeFailsWhenInvokedOnUniqueToken() { } @HapiTest // here - final HapiSpec uniqueWipeFailsWhenInvokedOnFungibleToken() { // invokes unique wipe on fungible tokens + final Stream uniqueWipeFailsWhenInvokedOnFungibleToken() { // invokes unique wipe on fungible tokens return defaultHapiSpec("UniqueWipeFailsWhenInvokedOnFungibleToken") .given( newKeyNamed(WIPE_KEY), @@ -777,7 +738,7 @@ final HapiSpec uniqueWipeFailsWhenInvokedOnFungibleToken() { // invokes unique w } @HapiTest - final HapiSpec wipeFailsWithInvalidSerialNumber() { + final Stream wipeFailsWithInvalidSerialNumber() { return defaultHapiSpec("WipeFailsWithInvalidSerialNumber") .given( newKeyNamed(SUPPLY_KEY), @@ -799,7 +760,7 @@ final HapiSpec wipeFailsWithInvalidSerialNumber() { } @HapiTest - final HapiSpec mintUniqueTokenReceiptCheck() { + final Stream mintUniqueTokenReceiptCheck() { final var mintTransferTxn = "mintTransferTxn"; return defaultHapiSpec("mintUniqueTokenReceiptCheck") .given( @@ -839,7 +800,7 @@ final HapiSpec mintUniqueTokenReceiptCheck() { } @HapiTest - final HapiSpec tokenDissociateHappyPath() { + final Stream tokenDissociateHappyPath() { return defaultHapiSpec("tokenDissociateHappyPath") .given( newKeyNamed(SUPPLY_KEY), @@ -858,7 +819,7 @@ final HapiSpec tokenDissociateHappyPath() { } @HapiTest - final HapiSpec tokenDissociateFailsIfAccountOwnsUniqueTokens() { + final Stream tokenDissociateFailsIfAccountOwnsUniqueTokens() { return defaultHapiSpec("tokenDissociateFailsIfAccountOwnsUniqueTokens") .given( newKeyNamed(SUPPLY_KEY), diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/token/hip540/AuthorizingSignature.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/token/hip540/AuthorizingSignature.java new file mode 100644 index 000000000000..3f2b69fbd3de --- /dev/null +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/token/hip540/AuthorizingSignature.java @@ -0,0 +1,26 @@ +/* + * Copyright (C) 2024 Hedera Hashgraph, LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.hedera.services.bdd.suites.token.hip540; + +/** + * Enumerates the possible authorizing signatures for a HIP-540 test scenario. + */ +public enum AuthorizingSignature { + EXTANT_ADMIN, + EXTANT_NON_ADMIN, + NEW_NON_ADMIN, +} diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/token/hip540/Hip540Suite.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/token/hip540/Hip540Suite.java new file mode 100644 index 000000000000..e823f6f4ec54 --- /dev/null +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/token/hip540/Hip540Suite.java @@ -0,0 +1,41 @@ +/* + * Copyright (C) 2021-2024 Hedera Hashgraph, LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.hedera.services.bdd.suites.token.hip540; + +import static com.hedera.services.bdd.junit.TestTags.TOKEN; +import static com.hedera.services.bdd.spec.HapiSpec.defaultHapiSpec; +import static com.hedera.services.bdd.spec.utilops.UtilVerbs.inParallel; +import static com.hedera.services.bdd.suites.token.hip540.Hip540TestScenarios.ALL_HIP_540_SCENARIOS; + +import com.hedera.services.bdd.junit.HapiTest; +import com.hedera.services.bdd.spec.HapiSpecOperation; +import java.util.stream.Stream; +import org.junit.jupiter.api.DynamicTest; +import org.junit.jupiter.api.Tag; + +@Tag(TOKEN) +public class Hip540Suite { + @HapiTest + public final Stream allScenariosAsExpected() { + return defaultHapiSpec("allScenariosAsExpected") + .given() + .when() + .then(inParallel(ALL_HIP_540_SCENARIOS.stream() + .map(Hip540TestScenario::asOperation) + .toArray(HapiSpecOperation[]::new))); + } +} diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/token/hip540/Hip540TestScenario.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/token/hip540/Hip540TestScenario.java new file mode 100644 index 000000000000..80122b6f4aad --- /dev/null +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/token/hip540/Hip540TestScenario.java @@ -0,0 +1,256 @@ +/* + * Copyright (C) 2024 Hedera Hashgraph, LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.hedera.services.bdd.suites.token.hip540; + +import static com.hedera.services.bdd.spec.queries.QueryVerbs.getTokenInfo; +import static com.hedera.services.bdd.spec.transactions.TxnUtils.randomAlphaNumeric; +import static com.hedera.services.bdd.spec.transactions.TxnVerbs.tokenCreate; +import static com.hedera.services.bdd.spec.transactions.TxnVerbs.tokenUpdate; +import static com.hedera.services.bdd.spec.utilops.UtilVerbs.blockingOrder; +import static com.hedera.services.bdd.spec.utilops.UtilVerbs.logIt; +import static com.hedera.services.bdd.spec.utilops.UtilVerbs.newKeyNamed; +import static com.hedera.services.bdd.spec.utilops.UtilVerbs.withOpContext; +import static com.hedera.services.bdd.suites.HapiSuite.DEFAULT_PAYER; +import static com.hedera.services.bdd.suites.token.hip540.ManagementAction.REMOVE; +import static com.hedera.services.bdd.suites.token.hip540.ManagementAction.REPLACE; +import static com.hederahashgraph.api.proto.java.TokenKeyValidation.NO_VALIDATION; +import static java.util.Objects.requireNonNull; + +import com.google.protobuf.ByteString; +import com.hedera.services.bdd.spec.HapiSpecOperation; +import com.hedera.services.bdd.spec.queries.token.HapiGetTokenInfo; +import com.hedera.services.bdd.spec.transactions.token.HapiTokenCreate; +import com.hedera.services.bdd.spec.transactions.token.HapiTokenUpdate; +import com.hedera.services.bdd.spec.utilops.mod.ExpectedResponse; +import com.hederahashgraph.api.proto.java.Key; +import com.hederahashgraph.api.proto.java.KeyList; +import com.hederahashgraph.api.proto.java.TokenKeyValidation; +import edu.umd.cs.findbugs.annotations.NonNull; +import edu.umd.cs.findbugs.annotations.Nullable; +import java.util.ArrayList; +import java.util.List; +import java.util.Set; + +/** + * Characterizes a test scenario for HIP-540. + * + * @param targetKey which key is being managed + * @param adminKeyState the state of the admin key + * @param targetKeyState the state of the target key + * @param action the management action to take + * @param keyValidation whether to apply key validation + * @param authorizingSignatures which signatures to use + * @param expectedResponse the expected outcome + */ +public record Hip540TestScenario( + @NonNull NonAdminTokenKey targetKey, + @NonNull KeyState adminKeyState, + @NonNull KeyState targetKeyState, + @NonNull ManagementAction action, + @Nullable TokenKeyValidation keyValidation, + @NonNull Set authorizingSignatures, + @NonNull ExpectedResponse expectedResponse, + @NonNull String registryNameSalt, + @NonNull String testName) { + + public Hip540TestScenario( + @NonNull NonAdminTokenKey targetKey, + @NonNull KeyState adminKeyState, + @NonNull KeyState targetKeyState, + @NonNull ManagementAction action, + @Nullable TokenKeyValidation keyValidation, + @NonNull Set authorizingSignatures, + @NonNull ExpectedResponse expectedResponse, + @NonNull String testName) { + this( + targetKey, + adminKeyState, + targetKeyState, + action, + keyValidation, + authorizingSignatures, + expectedResponse, + randomAlphaNumeric(5), + testName); + } + + public Hip540TestScenario { + requireNonNull(targetKey); + requireNonNull(adminKeyState); + requireNonNull(targetKeyState); + requireNonNull(action); + requireNonNull(authorizingSignatures); + requireNonNull(expectedResponse); + requireNonNull(registryNameSalt); + requireNonNull(testName); + } + + private static final String ADMIN_KEY = "adminKey"; + private static final String ROLE_KEY = "roleKey"; + private static final String NEW_ROLE_KEY = "newRoleKey"; + private static final String TOKEN_UNDER_TEST = "token"; + // This key is structurally valid, but effectively unusable because there is no + // known way to invert the SHA-512 hash of its associated curve point + private static final Key ZEROED_OUT_KEY = Key.newBuilder() + .setEd25519(ByteString.fromHex("0000000000000000000000000000000000000000000000000000000000000000")) + .build(); + public static final Key IMMUTABILITY_SENTINEL_KEY = + Key.newBuilder().setKeyList(KeyList.getDefaultInstance()).build(); + // This key is truly invalid, as all Ed25519 public keys must be 32 bytes long + private static final Key STRUCTURALLY_INVALID_KEY = + Key.newBuilder().setEd25519(ByteString.fromHex("ff")).build(); + + private String adminKey() { + return ADMIN_KEY + registryNameSalt; + } + + private String roleKey() { + return ROLE_KEY + registryNameSalt; + } + + private String newRoleKey() { + return NEW_ROLE_KEY + registryNameSalt; + } + + private String tokenUnderTest() { + return TOKEN_UNDER_TEST + registryNameSalt; + } + + public HapiSpecOperation asOperation() { + final List ops = new ArrayList<>(); + ops.add(logIt("HIP-540 test scenario - " + this)); + if (adminKeyState == KeyState.USABLE) { + ops.add(newKeyNamed(adminKey())); + } + if (targetKeyState == KeyState.USABLE) { + ops.add(newKeyNamed(roleKey())); + } else if (targetKeyState == KeyState.ZEROED_OUT) { + ops.add(withOpContext((spec, opLog) -> spec.registry().saveKey(roleKey(), ZEROED_OUT_KEY))); + } + switch (action) { + case ADD, REPLACE -> ops.add(newKeyNamed(newRoleKey())); + case REMOVE -> ops.add( + withOpContext((spec, opLog) -> spec.registry().saveKey(newRoleKey(), IMMUTABILITY_SENTINEL_KEY))); + case ZERO_OUT -> ops.add( + withOpContext((spec, opLog) -> spec.registry().saveKey(newRoleKey(), ZEROED_OUT_KEY))); + case REPLACE_WITH_INVALID -> ops.add( + withOpContext((spec, opLog) -> spec.registry().saveKey(newRoleKey(), STRUCTURALLY_INVALID_KEY))); + } + ops.add(creation()); + ops.add(update()); + if (expectedResponse.isSuccess()) { + ops.add(confirmation()); + } + return blockingOrder(ops.toArray(HapiSpecOperation[]::new)); + } + + private HapiGetTokenInfo confirmation() { + final var op = getTokenInfo(tokenUnderTest()); + if (expectedResponse.isSuccess()) { + if (action == REMOVE) { + switch (targetKey) { + case WIPE_KEY -> op.hasEmptyWipeKey(); + case KYC_KEY -> op.hasEmptyKycKey(); + case SUPPLY_KEY -> op.hasEmptySupplyKey(); + case FREEZE_KEY -> op.hasEmptyFreezeKey(); + case FEE_SCHEDULE_KEY -> op.hasEmptyFeeScheduleKey(); + case PAUSE_KEY -> op.hasEmptyPauseKey(); + case METADATA_KEY -> op.hasEmptyMetadataKey(); + } + } else { + switch (targetKey) { + case WIPE_KEY -> op.hasWipeKey(newRoleKey()).searchKeysGlobally(); + case KYC_KEY -> op.hasKycKey(newRoleKey()).searchKeysGlobally(); + case SUPPLY_KEY -> op.hasSupplyKey(newRoleKey()).searchKeysGlobally(); + case FREEZE_KEY -> op.hasFreezeKey(newRoleKey()).searchKeysGlobally(); + case FEE_SCHEDULE_KEY -> op.hasFeeScheduleKey(newRoleKey()).searchKeysGlobally(); + case PAUSE_KEY -> op.hasPauseKey(newRoleKey()).searchKeysGlobally(); + case METADATA_KEY -> op.hasMetadataKey(newRoleKey()).searchKeysGlobally(); + } + } + } + return op; + } + + private HapiTokenUpdate update() { + final var op = tokenUpdate(tokenUnderTest()); + addReplacementTo(op); + addSignaturesTo(op); + if (keyValidation == NO_VALIDATION) { + op.applyNoValidationToKeys(); + } + return op; + } + + private void addReplacementTo(@NonNull final HapiTokenUpdate update) { + switch (targetKey) { + case WIPE_KEY -> update.wipeKey(newRoleKey()); + case KYC_KEY -> update.kycKey(newRoleKey()); + case SUPPLY_KEY -> update.supplyKey(newRoleKey()); + case FREEZE_KEY -> update.freezeKey(newRoleKey()); + case FEE_SCHEDULE_KEY -> update.feeScheduleKey(newRoleKey()); + case PAUSE_KEY -> update.pauseKey(newRoleKey()); + case METADATA_KEY -> update.metadataKey(newRoleKey()); + } + } + + private void addSignaturesTo(@NonNull final HapiTokenUpdate update) { + final List signatures = new ArrayList<>(); + signatures.add(DEFAULT_PAYER); + authorizingSignatures.forEach(sig -> { + switch (sig) { + case EXTANT_ADMIN -> signatures.add(adminKey()); + case EXTANT_NON_ADMIN -> signatures.add(roleKey()); + case NEW_NON_ADMIN -> signatures.add(newRoleKey()); + } + }); + update.signedBy(signatures.toArray(String[]::new)); + expectedResponse.customize(update); + } + + private HapiTokenCreate creation() { + final var op = tokenCreate(tokenUnderTest()); + if (adminKeyState != KeyState.MISSING) { + op.adminKey(adminKey()); + } + if (targetKeyState != KeyState.MISSING) { + switch (targetKey) { + case WIPE_KEY -> op.wipeKey(roleKey()); + case KYC_KEY -> op.kycKey(roleKey()); + case SUPPLY_KEY -> op.supplyKey(roleKey()); + case FREEZE_KEY -> op.freezeKey(roleKey()); + case FEE_SCHEDULE_KEY -> op.feeScheduleKey(roleKey()); + case PAUSE_KEY -> op.pauseKey(roleKey()); + case METADATA_KEY -> op.metadataKey(roleKey()); + } + } + return op; + } + + @Override + public String toString() { + return "Hip540TestScenario {" + "\n testName=" + + testName + ",\n targetKey=" + + targetKey + ",\n adminKeyState=" + + adminKeyState + ",\n targetKeyState=" + + targetKeyState + ",\n action=" + + action + ",\n keyValidation=" + + keyValidation + ",\n authorizingSignatures=" + + authorizingSignatures + ",\n expectedResponse=" + + expectedResponse + '}'; + } +} diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/token/hip540/Hip540TestScenarios.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/token/hip540/Hip540TestScenarios.java new file mode 100644 index 000000000000..701cfcf9b00b --- /dev/null +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/token/hip540/Hip540TestScenarios.java @@ -0,0 +1,245 @@ +/* + * Copyright (C) 2024 Hedera Hashgraph, LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.hedera.services.bdd.suites.token.hip540; + +import static com.hedera.services.bdd.suites.token.hip540.AuthorizingSignature.EXTANT_ADMIN; +import static com.hedera.services.bdd.suites.token.hip540.AuthorizingSignature.EXTANT_NON_ADMIN; +import static com.hedera.services.bdd.suites.token.hip540.AuthorizingSignature.NEW_NON_ADMIN; +import static com.hedera.services.bdd.suites.token.hip540.KeyState.MISSING; +import static com.hedera.services.bdd.suites.token.hip540.KeyState.USABLE; +import static com.hedera.services.bdd.suites.token.hip540.ManagementAction.REPLACE; +import static com.hedera.services.bdd.suites.token.hip540.ManagementAction.REPLACE_WITH_INVALID; +import static com.hedera.services.bdd.suites.token.hip540.ManagementAction.ZERO_OUT; +import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.INVALID_SIGNATURE; +import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.SUCCESS; +import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.TOKEN_IS_IMMUTABLE; +import static com.hederahashgraph.api.proto.java.TokenKeyValidation.FULL_VALIDATION; +import static com.hederahashgraph.api.proto.java.TokenKeyValidation.NO_VALIDATION; + +import com.hedera.services.bdd.spec.utilops.mod.ExpectedResponse; +import com.hederahashgraph.api.proto.java.TokenKeyValidation; +import edu.umd.cs.findbugs.annotations.NonNull; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.EnumSet; +import java.util.List; +import java.util.stream.Stream; + +/** + * A collection of all HIP-540 test scenarios. + */ +public class Hip540TestScenarios { + private Hip540TestScenarios() { + throw new UnsupportedOperationException("Utility Class"); + } + + /** The list of all HIP-540 test scenarios. */ + public static final List ALL_HIP_540_SCENARIOS = new ArrayList<>(); + + static { + Arrays.stream(NonAdminTokenKey.values()) + .flatMap(Hip540TestScenarios::allScenariosFor) + .forEach(ALL_HIP_540_SCENARIOS::add); + } + + private static Stream allScenariosFor(@NonNull final NonAdminTokenKey targetKey) { + final List scenarios = new ArrayList<>(); + addPositiveNonAdminScenarios(targetKey, scenarios); + addNegativeNonAdminScenarios(targetKey, scenarios); + addPositiveAdminOnlyScenarios(targetKey, scenarios); + addNegativeAdminOnlyScenarios(targetKey, scenarios); + return scenarios.stream(); + } + + /** + * Without an admin key, we are able to: + *
      + *
    1. Replace the target with a usable key with its signature and + * its replacement's signature given {@link TokenKeyValidation#FULL_VALIDATION}.
    2. + *
    3. Replace the target with a usable key its signature without + * its replacement's signature using {@link TokenKeyValidation#NO_VALIDATION}.
    4. + *
    5. Zero out the target with its signature using {@link TokenKeyValidation#NO_VALIDATION}.
    6. + *
    + * + * @param targetKey the target key to test + * @param scenarios the list of scenarios to add to + */ + private static void addPositiveNonAdminScenarios( + @NonNull final NonAdminTokenKey targetKey, @NonNull final List scenarios) { + scenarios.addAll(List.of( + new Hip540TestScenario( + targetKey, + MISSING, + USABLE, + REPLACE, + FULL_VALIDATION, + EnumSet.of(EXTANT_NON_ADMIN, NEW_NON_ADMIN), + ExpectedResponse.atConsensus(SUCCESS), + "noAdminKeyReplaceAUsableRoleKeyWithUsableKeyFullValidationSucceeds"), + new Hip540TestScenario( + targetKey, + MISSING, + USABLE, + REPLACE, + NO_VALIDATION, + EnumSet.of(EXTANT_NON_ADMIN), + ExpectedResponse.atConsensus(SUCCESS), + "noAdminKeyReplaceAUsableRoleKeyWithUsableKeyNoValidationSucceeds"), + new Hip540TestScenario( + targetKey, + MISSING, + USABLE, + ZERO_OUT, + NO_VALIDATION, + EnumSet.of(EXTANT_NON_ADMIN), + ExpectedResponse.atConsensus(SUCCESS), + "noAdminKeyZeroOutAUsableRoleKeyNoValidationSucceeds"))); + } + + /** + * Without an admin key, we are not able to: + *
      + *
    1. Remove the target even with its signature.
    2. + *
    3. Remove a target key that is already missing.
    4. + *
    5. Replace a target key without its signature.
    6. + *
    7. Zero out a target key when using {@link TokenKeyValidation#FULL_VALIDATION}, + * even when signing with the existing key.
    8. + *
    9. Replace a target key with a structurally invalid key, + * even when using {@link TokenKeyValidation#NO_VALIDATION},.
    10. + *
    + * + * @param targetKey the target key to test + * @param scenarios the list of scenarios to add to + */ + private static void addNegativeNonAdminScenarios( + @NonNull final NonAdminTokenKey targetKey, @NonNull final List scenarios) { + scenarios.addAll(List.of( + new Hip540TestScenario( + targetKey, + MISSING, + USABLE, + ManagementAction.REMOVE, + null, + EnumSet.of(EXTANT_NON_ADMIN), + ExpectedResponse.atConsensus(TOKEN_IS_IMMUTABLE), + "noAdminKeyRemoveAUsableRoleKeyFails"), + new Hip540TestScenario( + targetKey, + MISSING, + USABLE, + REPLACE, + FULL_VALIDATION, + EnumSet.of(NEW_NON_ADMIN), + ExpectedResponse.atConsensus(INVALID_SIGNATURE), + "noAdminKeyReplaceAUsableRoleKeyFullValidationWithoutNewKeySignatureFails"), + new Hip540TestScenario( + targetKey, + MISSING, + USABLE, + ZERO_OUT, + FULL_VALIDATION, + EnumSet.of(EXTANT_NON_ADMIN), + ExpectedResponse.atConsensus(INVALID_SIGNATURE), + "noAdminKeyZeroOutAUsableRoleKeyFullValidationFails"), + new Hip540TestScenario( + targetKey, + MISSING, + USABLE, + REPLACE_WITH_INVALID, + FULL_VALIDATION, + EnumSet.of(EXTANT_NON_ADMIN), + ExpectedResponse.atIngest(targetKey.invalidKeyStatus()), + "noAdminKeyReplaceAUsableRoleKeyWithInvalidKeyFullValidationFails"))); + } + + /** + * With an admin key, we are able to: + *
      + *
    1. Remove the target key without its signature.
    2. + *
    3. Replace the target with a usable key without its signature or + * its replacement's signature, even using {@link TokenKeyValidation#FULL_VALIDATION}.
    4. + *
    5. Zero out the target key without its signature. + *
    + * + * @param targetKey the target key to test + * @param scenarios the list of scenarios to add to + */ + private static void addPositiveAdminOnlyScenarios( + @NonNull final NonAdminTokenKey targetKey, @NonNull final List scenarios) { + scenarios.addAll(List.of( + new Hip540TestScenario( + targetKey, + USABLE, + USABLE, + ManagementAction.REMOVE, + null, + EnumSet.of(EXTANT_ADMIN), + ExpectedResponse.atConsensus(SUCCESS), + "withAdminKeyRemoveAUsableRoleKeySucceeds"), + new Hip540TestScenario( + targetKey, + USABLE, + USABLE, + REPLACE, + FULL_VALIDATION, + EnumSet.of(EXTANT_ADMIN), + ExpectedResponse.atConsensus(SUCCESS), + "withAdminKeyReplaceAUsableRoleKeyFullValidationSucceeds"), + new Hip540TestScenario( + targetKey, + USABLE, + USABLE, + ZERO_OUT, + FULL_VALIDATION, + EnumSet.of(EXTANT_ADMIN), + ExpectedResponse.atConsensus(SUCCESS), + "withAdminKeyZeroOutAUsableRoleKeyFullValidationSucceeds"))); + } + + /** + * Even with an admin key, we are still not able to: + *
      + *
    1. Add any key that does not already exist.
    2. + *
    3. Remove a key that does not already exist.
    4. + *
    + * + * @param targetKey the target key to test + * @param scenarios the list of scenarios to add to + */ + private static void addNegativeAdminOnlyScenarios( + @NonNull final NonAdminTokenKey targetKey, @NonNull final List scenarios) { + scenarios.addAll(List.of( + new Hip540TestScenario( + targetKey, + USABLE, + MISSING, + ManagementAction.ADD, + NO_VALIDATION, + EnumSet.of(EXTANT_ADMIN), + ExpectedResponse.atConsensus(targetKey.tokenHasNoKeyStatus()), + "withAdminKeyAndMissingRoleKeyAddAUsableRoleKeyFails"), + new Hip540TestScenario( + targetKey, + USABLE, + MISSING, + ManagementAction.REMOVE, + null, + EnumSet.of(EXTANT_ADMIN), + ExpectedResponse.atConsensus(targetKey.tokenHasNoKeyStatus()), + "withAdminKeyAndMissingRoleKeyRemoveRoleKeyFails"))); + } +} diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/token/hip540/KeyState.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/token/hip540/KeyState.java new file mode 100644 index 000000000000..c240d343db54 --- /dev/null +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/token/hip540/KeyState.java @@ -0,0 +1,26 @@ +/* + * Copyright (C) 2024 Hedera Hashgraph, LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.hedera.services.bdd.suites.token.hip540; + +/** + * Enumerates the possible states of a key in a HIP-540 test scenario. + */ +public enum KeyState { + MISSING, + USABLE, + ZEROED_OUT +} diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/token/hip540/ManagementAction.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/token/hip540/ManagementAction.java new file mode 100644 index 000000000000..13253ca5b21d --- /dev/null +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/token/hip540/ManagementAction.java @@ -0,0 +1,28 @@ +/* + * Copyright (C) 2024 Hedera Hashgraph, LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.hedera.services.bdd.suites.token.hip540; + +/** + * Enumerates the possible non-admin key management actions in a HIP-540 test scenario. + */ +public enum ManagementAction { + ADD, + REMOVE, + REPLACE, + ZERO_OUT, + REPLACE_WITH_INVALID, +} diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/token/hip540/NonAdminTokenKey.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/token/hip540/NonAdminTokenKey.java new file mode 100644 index 000000000000..93e316abe124 --- /dev/null +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/token/hip540/NonAdminTokenKey.java @@ -0,0 +1,121 @@ +/* + * Copyright (C) 2024 Hedera Hashgraph, LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.hedera.services.bdd.suites.token.hip540; + +import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.INVALID_CUSTOM_FEE_SCHEDULE_KEY; +import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.INVALID_FREEZE_KEY; +import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.INVALID_KYC_KEY; +import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.INVALID_METADATA_KEY; +import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.INVALID_PAUSE_KEY; +import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.INVALID_SUPPLY_KEY; +import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.INVALID_WIPE_KEY; +import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.TOKEN_HAS_NO_FEE_SCHEDULE_KEY; +import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.TOKEN_HAS_NO_FREEZE_KEY; +import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.TOKEN_HAS_NO_KYC_KEY; +import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.TOKEN_HAS_NO_METADATA_KEY; +import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.TOKEN_HAS_NO_PAUSE_KEY; +import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.TOKEN_HAS_NO_SUPPLY_KEY; +import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.TOKEN_HAS_NO_WIPE_KEY; + +import com.hederahashgraph.api.proto.java.ResponseCodeEnum; + +/** + * Enumerates the possible non-admin token keys in a HIP-540 test scenario. + */ +public enum NonAdminTokenKey { + WIPE_KEY { + @Override + public ResponseCodeEnum tokenHasNoKeyStatus() { + return TOKEN_HAS_NO_WIPE_KEY; + } + + @Override + public ResponseCodeEnum invalidKeyStatus() { + return INVALID_WIPE_KEY; + } + }, + KYC_KEY { + @Override + public ResponseCodeEnum tokenHasNoKeyStatus() { + return TOKEN_HAS_NO_KYC_KEY; + } + + @Override + public ResponseCodeEnum invalidKeyStatus() { + return INVALID_KYC_KEY; + } + }, + SUPPLY_KEY { + @Override + public ResponseCodeEnum tokenHasNoKeyStatus() { + return TOKEN_HAS_NO_SUPPLY_KEY; + } + + @Override + public ResponseCodeEnum invalidKeyStatus() { + return INVALID_SUPPLY_KEY; + } + }, + FREEZE_KEY { + @Override + public ResponseCodeEnum tokenHasNoKeyStatus() { + return TOKEN_HAS_NO_FREEZE_KEY; + } + + @Override + public ResponseCodeEnum invalidKeyStatus() { + return INVALID_FREEZE_KEY; + } + }, + FEE_SCHEDULE_KEY { + @Override + public ResponseCodeEnum tokenHasNoKeyStatus() { + return TOKEN_HAS_NO_FEE_SCHEDULE_KEY; + } + + @Override + public ResponseCodeEnum invalidKeyStatus() { + return INVALID_CUSTOM_FEE_SCHEDULE_KEY; + } + }, + PAUSE_KEY { + @Override + public ResponseCodeEnum tokenHasNoKeyStatus() { + return TOKEN_HAS_NO_PAUSE_KEY; + } + + @Override + public ResponseCodeEnum invalidKeyStatus() { + return INVALID_PAUSE_KEY; + } + }, + METADATA_KEY { + @Override + public ResponseCodeEnum tokenHasNoKeyStatus() { + return TOKEN_HAS_NO_METADATA_KEY; + } + + @Override + public ResponseCodeEnum invalidKeyStatus() { + return INVALID_METADATA_KEY; + } + }; + + public abstract ResponseCodeEnum tokenHasNoKeyStatus(); + + public abstract ResponseCodeEnum invalidKeyStatus(); +} diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/util/UtilPrngSuite.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/util/UtilPrngSuite.java index e12028445726..f670fba7a69c 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/util/UtilPrngSuite.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/util/UtilPrngSuite.java @@ -25,39 +25,21 @@ import static com.hedera.services.bdd.spec.utilops.UtilVerbs.validateChargedUsd; import static com.hedera.services.bdd.spec.utilops.UtilVerbs.validateChargedUsdWithin; import static com.hedera.services.bdd.spec.utilops.mod.ModificationUtils.withSuccessivelyVariedBodyIds; +import static com.hedera.services.bdd.suites.HapiSuite.ONE_HUNDRED_HBARS; import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.INVALID_PRNG_RANGE; import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.OK; import com.hedera.services.bdd.junit.HapiTest; -import com.hedera.services.bdd.junit.HapiTestSuite; -import com.hedera.services.bdd.spec.HapiSpec; -import com.hedera.services.bdd.suites.HapiSuite; -import java.util.List; import java.util.Map; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; +import java.util.stream.Stream; +import org.junit.jupiter.api.DynamicTest; -@HapiTestSuite -public class UtilPrngSuite extends HapiSuite { - private static final Logger log = LogManager.getLogger(UtilPrngSuite.class); +public class UtilPrngSuite { private static final String PRNG_IS_ENABLED = "utilPrng.isEnabled"; public static final String BOB = "bob"; - public static void main(String... args) { - new UtilPrngSuite().runSuiteSync(); - } - - @Override - public List getSpecsInSuite() { - return allOf(positiveTests()); - } - - private List positiveTests() { - return List.of(happyPathWorksForRangeAndBitString(), failsInPreCheckForNegativeRange(), usdFeeAsExpected()); - } - @HapiTest - final HapiSpec usdFeeAsExpected() { + final Stream usdFeeAsExpected() { double baseFee = 0.001; double plusRangeFee = 0.0010010316; @@ -85,7 +67,7 @@ final HapiSpec usdFeeAsExpected() { } @HapiTest - final HapiSpec failsInPreCheckForNegativeRange() { + final Stream failsInPreCheckForNegativeRange() { return defaultHapiSpec("failsInPreCheckForNegativeRange") .given( overridingAllOf(Map.of(PRNG_IS_ENABLED, "true")), @@ -101,7 +83,7 @@ final HapiSpec failsInPreCheckForNegativeRange() { } @HapiTest - public HapiSpec idVariantsTreatedAsExpected() { + final Stream idVariantsTreatedAsExpected() { return defaultHapiSpec("idVariantsTreatedAsExpected") .given() .when() @@ -109,7 +91,7 @@ public HapiSpec idVariantsTreatedAsExpected() { } @HapiTest - final HapiSpec happyPathWorksForRangeAndBitString() { + final Stream happyPathWorksForRangeAndBitString() { final var rangeTxn = "prngWithRange"; final var rangeTxn1 = "prngWithRange1"; final var prngWithoutRange = "prngWithoutRange"; @@ -166,9 +148,4 @@ final HapiSpec happyPathWorksForRangeAndBitString() { .payingWith(BOB) .hasPrecheck(INVALID_PRNG_RANGE)); } - - @Override - protected Logger getResultsLogger() { - return log; - } } diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/utils/validation/ValidationScenarios.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/utils/validation/ValidationScenarios.java index 220b74de7962..ca8ee3d64623 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/utils/validation/ValidationScenarios.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/utils/validation/ValidationScenarios.java @@ -169,6 +169,7 @@ import java.util.concurrent.atomic.AtomicLong; import java.util.concurrent.atomic.AtomicReference; import java.util.function.Consumer; +import java.util.function.Function; import java.util.function.IntConsumer; import java.util.function.LongConsumer; import java.util.function.LongSupplier; @@ -182,6 +183,7 @@ import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.DynamicTest; import org.yaml.snakeyaml.DumperOptions; import org.yaml.snakeyaml.LoaderOptions; import org.yaml.snakeyaml.Yaml; @@ -258,9 +260,9 @@ public static void main(String... args) { } @Override - public List getSpecsInSuite() { + public List> getSpecsInSuite() { var specs = Stream.of( - Optional.of(recordPayerBalance(startingBalance::set)), + ofNullable(recordPayerBalance(startingBalance::set)), ofNullable(skipScenarioPayer() ? null : ensureScenarioPayer()), ofNullable(params.getScenarios().contains(CRYPTO) ? cryptoScenario() : null), ofNullable(params.getScenarios().contains(VERSIONS) ? versionsScenario() : null), @@ -277,8 +279,11 @@ public List getSpecsInSuite() { ofNullable(params.getScenarios().contains(FEE_SNAPSHOTS) ? updatePaymentCsv() : null), ofNullable(params.getScenarios().isEmpty() ? null : recordPayerBalance(endingBalance::set))) .flatMap(Optional::stream) - .collect(toList()); - System.out.println(specs.stream().map(HapiSpec::getName).collect(toList())); + .toList(); + System.out.println(specs.stream() + .flatMap(Function.identity()) + .map(test -> ((HapiSpec) test.getExecutable()).getName()) + .toList()); return specs; } @@ -288,7 +293,7 @@ private static boolean skipScenarioPayer() { return needScenarioPayer.stream().noneMatch(params.getScenarios()::contains); } - private static HapiSpec ensureBytecode() { + private static Stream ensureBytecode() { ensureScenarios(); if (scenarios.getFeeSnapshots() == null) { scenarios.setFeeSnapshots(new FeeSnapshotsScenario()); @@ -326,7 +331,7 @@ private static HapiSpec ensureBytecode() { } } - private static HapiSpec feeSnapshots() { + private static Stream feeSnapshots() { ensureScenarios(); if (scenarios.getFeeSnapshots() == null) { scenarios.setFeeSnapshots(new FeeSnapshotsScenario()); @@ -528,7 +533,7 @@ PATTERN, targetNetwork().getScenarioPayer())), } } - private static HapiSpec updatePaymentCsv() { + private static Stream updatePaymentCsv() { ensureScenarios(); if (scenarios.getFeeSnapshots() == null) { scenarios.setFeeSnapshots(new FeeSnapshotsScenario()); @@ -625,7 +630,7 @@ private static void createInitialFeesCsv(String loc, List payments, Fee } } - private static HapiSpec doJustTransfers() { + private static Stream doJustTransfers() { try { int numNodes = targetNetwork().getNodes().size(); return customHapiSpec("DoJustTransfers") @@ -663,7 +668,7 @@ private static HapiSpec doJustTransfers() { } } - private static HapiSpec sysFilesUp() { + private static Stream sysFilesUp() { ensureScenarios(); if (scenarios.getSysFilesUp() == null) { scenarios.setSysFilesUp(new SysFilesUpScenario()); @@ -719,7 +724,7 @@ private static String passphraseEnvVarFor(long accountNum) { "%s_ACCOUNT%d_PASSPHRASE", params.getTargetNetwork().toUpperCase(), accountNum); } - private static HapiSpec sysFilesDown() { + private static Stream sysFilesDown() { ensureScenarios(); if (scenarios.getSysFilesDown() == null) { scenarios.setSysFilesDown(new SysFilesDownScenario()); @@ -814,7 +819,7 @@ private static byte[] asOrdered(byte[] svcCfgList) { } } - private static HapiSpec getSystemKeys() { + private static Stream getSystemKeys() { final long[] accounts = {2, 50, 55, 56, 57, 58}; final long[] files = {101, 102, 111, 112, 121, 122}; try { @@ -852,7 +857,7 @@ private static HapiSpec getSystemKeys() { } } - private static HapiSpec recordPayerBalance(LongConsumer learner) { + private static Stream recordPayerBalance(LongConsumer learner) { try { return customHapiSpec("RecordPayerBalance") .withProperties(Map.of( @@ -885,7 +890,7 @@ private static HapiSpec recordPayerBalance(LongConsumer learner) { } } - private static HapiSpec ensureScenarioPayer() { + private static Stream ensureScenarioPayer() { try { ensureScenarios(); long minStartingBalance = targetNetwork().getEnsureScenarioPayerHbars() * TINYBARS_PER_HBAR; @@ -918,19 +923,13 @@ private static HapiSpec ensureScenarioPayer() { } } - private static HapiSpec versionsScenario() { + private static Stream versionsScenario() { try { ensureScenarios(); if (scenarios.getVersions() == null) { scenarios.setVersions(new VersionInfoScenario()); } var versions = scenarios.getVersions(); - int[] hapiProto = Arrays.stream(versions.getHapiProtoSemVer().split("[.]")) - .mapToInt(Integer::parseInt) - .toArray(); - int[] services = Arrays.stream(versions.getServicesSemVer().split("[.]")) - .mapToInt(Integer::parseInt) - .toArray(); return customHapiSpec("VersionsScenario") .withProperties(Map.of( NODES, @@ -958,7 +957,7 @@ private static HapiSpec versionsScenario() { } } - private static HapiSpec cryptoScenario() { + private static Stream cryptoScenario() { try { ensureScenarios(); if (scenarios.getCrypto() == null) { @@ -1107,7 +1106,7 @@ private static HapiSpecOperation ensureValidatedAccountExistence( }); } - private static HapiSpec fileScenario() { + private static Stream fileScenario() { try { ensureScenarios(); if (scenarios.getFile() == null) { @@ -1262,7 +1261,7 @@ private static HapiSpecOperation ensureValidatedFileExistence( }); } - private static HapiSpec contractScenario() { + private static Stream contractScenario() { try { ensureScenarios(); if (scenarios.getContract() == null) { @@ -1467,7 +1466,7 @@ private static LongSupplier persistentContractOrNegativeOne(ContractScenario con .orElse(-1L); } - private static HapiSpec consensusScenario() { + private static Stream consensusScenario() { try { ensureScenarios(); if (scenarios.getConsensus() == null) { @@ -1520,13 +1519,12 @@ PATTERN, targetNetwork().getScenarioPayer())), } } - private static HapiSpec stakingScenario() { + private static Stream stakingScenario() { try { ensureScenarios(); if (scenarios.getStaking() == null) { scenarios.setStaking(new StakingScenario()); } - var staking = scenarios.getStaking(); return customHapiSpec("StakingScenario") .withProperties(Map.of( diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/validation/StreamValidationTest.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/validation/StreamValidationTest.java new file mode 100644 index 000000000000..5bc13bc2ef5b --- /dev/null +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/validation/StreamValidationTest.java @@ -0,0 +1,110 @@ +/* + * Copyright (C) 2024 Hedera Hashgraph, LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.hedera.services.bdd.suites.validation; + +import static com.hedera.services.bdd.junit.support.RecordStreamAccess.RECORD_STREAM_ACCESS; +import static com.hedera.services.bdd.spec.HapiSpec.hapiTest; +import static com.hedera.services.bdd.spec.transactions.TxnVerbs.cryptoTransfer; +import static com.hedera.services.bdd.spec.utilops.UtilVerbs.sleepFor; +import static com.hedera.services.bdd.spec.utilops.UtilVerbs.withOpContext; +import static com.hedera.services.bdd.suites.HapiSuite.GENESIS; +import static java.util.stream.Collectors.joining; + +import com.hedera.services.bdd.junit.LeakyHapiTest; +import com.hedera.services.bdd.junit.hedera.HederaNode; +import com.hedera.services.bdd.junit.support.RecordStreamAccess; +import com.hedera.services.bdd.junit.support.RecordStreamValidator; +import com.hedera.services.bdd.junit.support.validators.BalanceReconciliationValidator; +import com.hedera.services.bdd.junit.support.validators.BlockNoValidator; +import com.hedera.services.bdd.junit.support.validators.ExpiryRecordsValidator; +import com.hedera.services.bdd.junit.support.validators.TokenReconciliationValidator; +import com.hedera.services.bdd.junit.support.validators.TransactionBodyValidator; +import com.hedera.services.bdd.spec.HapiSpec; +import edu.umd.cs.findbugs.annotations.NonNull; +import java.io.File; +import java.nio.file.Path; +import java.util.List; +import java.util.Optional; +import java.util.stream.Stream; +import org.apache.logging.log4j.Logger; +import org.junit.jupiter.api.DynamicTest; +import org.junit.jupiter.api.Order; +import org.junit.jupiter.api.Tag; + +@Tag("STREAM_VALIDATION") +// Order this last to validate stream files after all other tests have run +@Order(Integer.MAX_VALUE) +public class StreamValidationTest { + private static final long MAX_BLOCK_TIME_MS = 2000L; + private static final long BUFFER_MS = 500L; + private static final long MIN_GZIP_SIZE_IN_BYTES = 26; + private static final String ERROR_PREFIX = "\n - "; + + private static final List STREAM_VALIDATORS = List.of( + new BlockNoValidator(), + new TransactionBodyValidator(), + new ExpiryRecordsValidator(), + new BalanceReconciliationValidator(), + new TokenReconciliationValidator()); + + @LeakyHapiTest + final Stream validateStreamFiles() { + return hapiTest( + // Ensure the next transaction will be in a new block period + sleepFor(MAX_BLOCK_TIME_MS + BUFFER_MS), + // Submit a transaction + cryptoTransfer((spec, b) -> {}).payingWith(GENESIS), + // Wait for the next record file to be created + sleepFor(2 * BUFFER_MS), + // Run our validators + withOpContext((spec, opLog) -> { + readMaybeStreamDataFor(spec, opLog).ifPresent(data -> { + final var maybeErrors = STREAM_VALIDATORS.stream() + .flatMap(v -> v.validationErrorsIn(data)) + .map(Throwable::getMessage) + .collect(joining(ERROR_PREFIX)); + if (!maybeErrors.isBlank()) { + throw new AssertionError("Record stream validation failed:" + ERROR_PREFIX + maybeErrors); + } + }); + })); + } + + private static Optional readMaybeStreamDataFor( + @NonNull final HapiSpec spec, @NonNull final Logger opLog) { + RecordStreamAccess.Data data = null; + final var streamLocs = spec.getNetworkNodes().stream() + .map(HederaNode::getRecordStreamPath) + .map(Path::toAbsolutePath) + .map(Object::toString) + .toList(); + for (final var loc : streamLocs) { + try { + opLog.info("Trying to read record files from {}", loc); + data = RECORD_STREAM_ACCESS.readStreamDataFrom( + loc, "sidecar", f -> new File(f).length() > MIN_GZIP_SIZE_IN_BYTES); + opLog.info("Read {} record files from {}", data.records().size(), loc); + } catch (Exception ignore) { + // We will try the next location, if any + } + if (data != null && !data.records().isEmpty()) { + break; + } + } + return Optional.ofNullable(data); + } +} diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/validation/TokenPuvSuite.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/validation/TokenPuvSuite.java index d46111261cc4..e53d5ff97b7e 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/validation/TokenPuvSuite.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/validation/TokenPuvSuite.java @@ -42,8 +42,10 @@ import java.nio.file.Paths; import java.util.List; import java.util.function.Supplier; +import java.util.stream.Stream; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; +import org.junit.jupiter.api.DynamicTest; public class TokenPuvSuite extends HapiSuite { @@ -58,13 +60,11 @@ public TokenPuvSuite(MiscConfig miscConfig, NetworkConfig targetInfo) { } @Override - public List getSpecsInSuite() { - return List.of(new HapiSpec[] { - cleanupIfNecessary(), initialAssociation(), initialFunding(), - }); + public List> getSpecsInSuite() { + return List.of(cleanupIfNecessary(), initialAssociation(), initialFunding()); } - final HapiSpec initialFunding() { + final Stream initialFunding() { return HapiSpec.customHapiSpec("InitialFunding") .withProperties(targetInfo.toCustomProperties(miscConfig)) .given( @@ -94,7 +94,7 @@ final HapiSpec initialFunding() { .balance(Amounts.BESTOWED_CAT_TOKENS))); } - final HapiSpec initialAssociation() { + final Stream initialAssociation() { return HapiSpec.customHapiSpec("InitialAssociation") .withProperties(targetInfo.toCustomProperties(miscConfig)) .given( @@ -112,7 +112,7 @@ final HapiSpec initialAssociation() { .kyc(TokenKycStatus.KycNotApplicable))); } - final HapiSpec cleanupIfNecessary() { + final Stream cleanupIfNecessary() { return HapiSpec.customHapiSpec("CleanupIfNecessary") .withProperties(targetInfo.toCustomProperties(miscConfig)) .given() diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/legacy/core/CommonUtils.java b/hedera-node/test-clients/src/main/java/com/hedera/services/legacy/core/CommonUtils.java deleted file mode 100644 index e506552fbaf5..000000000000 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/legacy/core/CommonUtils.java +++ /dev/null @@ -1,78 +0,0 @@ -/* - * Copyright (C) 2022-2024 Hedera Hashgraph, LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.hedera.services.legacy.core; - -import static com.swirlds.common.utility.CommonUtils.hex; - -import com.google.common.primitives.Ints; -import com.google.common.primitives.Longs; -import java.io.ByteArrayInputStream; -import java.io.IOException; -import java.io.ObjectInput; -import java.io.ObjectInputStream; -import java.util.Base64; - -/** Common utilities. */ -public class CommonUtils { - /** - * Decode base64 string to bytes - * - * @param base64string base64 string to be decoded - * @return decoded bytes - */ - public static byte[] base64decode(String base64string) { - byte[] rv = null; - rv = Base64.getDecoder().decode(base64string); - return rv; - } - - /** - * Deserialize a Java object from given bytes - * - * @param bytes given byte array to be deserialized - * @return the Java object constructed after deserialization - */ - public static Object convertFromBytes(byte[] bytes) { - try (ByteArrayInputStream bis = new ByteArrayInputStream(bytes); - ObjectInput in = new ObjectInputStream(bis)) { - return in.readObject(); - } catch (IOException | ClassNotFoundException e) { - e.printStackTrace(); - } - return null; - } - - public static String calculateSolidityAddress(int indicator, long realmNum, long accountNum) { - byte[] solidityByteArray = new byte[20]; - byte[] indicatorBytes = Ints.toByteArray(indicator); - copyArray(0, solidityByteArray, indicatorBytes); - byte[] realmNumBytes = Longs.toByteArray(realmNum); - copyArray(4, solidityByteArray, realmNumBytes); - byte[] accountNumBytes = Longs.toByteArray(accountNum); - copyArray(12, solidityByteArray, accountNumBytes); - return hex(solidityByteArray); - } - - private static void copyArray(int startInToArray, byte[] toArray, byte[] fromArray) { - if (fromArray == null || toArray == null) { - return; - } - for (int i = 0; i < fromArray.length; i++) { - toArray[i + startInToArray] = fromArray[i]; - } - } -} diff --git a/hedera-node/test-clients/src/main/java/module-info.java b/hedera-node/test-clients/src/main/java/module-info.java index dedfcb75815b..de8e2f9f081f 100644 --- a/hedera-node/test-clients/src/main/java/module-info.java +++ b/hedera-node/test-clients/src/main/java/module-info.java @@ -32,6 +32,11 @@ exports com.hedera.services.bdd.spec.verification.traceability; exports com.hedera.services.bdd.spec.assertions; exports com.hedera.services.bdd.spec.assertions.matchers; + exports com.hedera.services.bdd.junit; + exports com.hedera.services.bdd.junit.extensions; + exports com.hedera.services.bdd.junit.support.validators; + exports com.hedera.services.bdd.junit.support; + exports com.hedera.services.bdd.junit.support.validators.utils; requires transitive com.hedera.node.app.hapi.fees; requires transitive com.hedera.node.app.hapi.utils; @@ -47,19 +52,16 @@ requires transitive org.apache.commons.io; requires transitive org.apache.logging.log4j; requires transitive org.junit.jupiter.api; - requires transitive org.junit.platform.commons; - requires transitive org.junit.platform.engine; requires transitive org.testcontainers; requires transitive org.yaml.snakeyaml; requires transitive tuweni.bytes; requires com.hedera.node.app.service.contract.impl; - requires com.hedera.node.app.service.evm; requires com.hedera.node.app; requires com.hedera.node.config; requires com.fasterxml.jackson.core; requires com.fasterxml.jackson.databind; requires com.github.docker.java.api; - requires com.hedera.pbj.runtime; + requires com.hedera.evm; requires com.swirlds.base; requires com.swirlds.config.api; requires com.swirlds.config.extensions.test.fixtures; @@ -73,10 +75,13 @@ requires org.apache.logging.log4j.core; requires org.bouncycastle.provider; requires org.hyperledger.besu.datatypes; - requires org.hyperledger.besu.evm; requires org.hyperledger.besu.internal.crypto; requires org.json; + requires org.junit.platform.commons; requires org.opentest4j; requires tuweni.units; requires static com.github.spotbugs.annotations; + requires static com.hedera.pbj.runtime; + requires static org.junit.platform.engine; + requires static org.junit.platform.launcher; } diff --git a/hedera-node/test-clients/src/main/resource/AccountList.json b/hedera-node/test-clients/src/main/resource/AccountList.json deleted file mode 100644 index ee0fd2e27985..000000000000 --- a/hedera-node/test-clients/src/main/resource/AccountList.json +++ /dev/null @@ -1,227 +0,0 @@ -[ - { - "initialBalance":1000, - "proxyAccountNum":1, - "proxyRealmNum":0, - "proxyShardNum":0, - "proxyFraction":100, - "maxReceiveProxyFraction":50, - "sendRecordThreshold":100, - "receiveRecordThreshold":1000, - "receiverSigRequired":false, - "autoRenewPeriod":1382400, - "shardID":0, - "realmID":0, - "key":["1c1cf3e05f0e7e58fdb4a78dae150a2ecd57a6f9af7d1ace34a6613a4f1852cd", - "0c4a6c68ce285cfbef2d2e6fc6cff444571ae24fc2be01f0a2b33b4ce96b3519", - "2f7bb2b70b9cb51efe5bef6329910cd12113816bddc4eea5ba1574c73ee94423", - "364de0b8474c11c17e27df8d22366b91d41cb529ee25c04bb29e0945752ae6d0", - "7d00f01738f22ed29310c6b3bb9588d1f37f62cb607dd86c31b29cf594be854a"], - "newRealmAdminKey":"be526ec20a19a33d98a54e5d7f54400ae34f289aac9c854909b61f48d0273be8", - "nodeAccountNum":3, - "nodeRealmNum":0, - "nodeShardNum":0, - "transactionFee":100 - }, - { - "initialBalance":1000, - "proxyAccountNum":1, - "proxyRealmNum":0, - "proxyShardNum":0, - "proxyFraction":100, - "maxReceiveProxyFraction":50, - "sendRecordThreshold":100, - "receiveRecordThreshold":1000, - "receiverSigRequired":false, - "autoRenewPeriod":1382400, - "shardID":0, - "realmID":0, - "key":"212501a595af6db6a0e4f69debc6f36cc5b5a79b2b9148caf94406a7730f3d1c", - "newRealmAdminKey":"be526ec20a19a33d98a54e5d7f54400ae34f289aac9c854909b61f48d0273be8", - "nodeAccountNum":3, - "nodeRealmNum":0, - "nodeShardNum":0, - "transactionFee":100 - }, - { - "initialBalance":1000, - "proxyAccountNum":1, - "proxyRealmNum":0, - "proxyShardNum":0, - "proxyFraction":100, - "maxReceiveProxyFraction":50, - "sendRecordThreshold":100, - "receiveRecordThreshold":1000, - "receiverSigRequired":false, - "autoRenewPeriod":1382400, - "shardID":0, - "realmID":0, - "key":"712bf272bad9b0792fae76d301c5fa61a3adb5118a34226d28898f48dd0274c1", - "newRealmAdminKey":"be526ec20a19a33d98a54e5d7f54400ae34f289aac9c854909b61f48d0273be8", - "nodeAccountNum":3, - "nodeRealmNum":0, - "nodeShardNum":0, - "transactionFee":100 - }, - { - "initialBalance":1000, - "proxyAccountNum":1, - "proxyRealmNum":0, - "proxyShardNum":0, - "proxyFraction":100, - "maxReceiveProxyFraction":50, - "sendRecordThreshold":100, - "receiveRecordThreshold":1000, - "receiverSigRequired":false, - "autoRenewPeriod":1382400, - "shardID":0, - "realmID":0, - "key":"f14641716b444b4202bfa7bfdd5b0cdee9ba2534b52eb8840ddb66fb4a109854", - "newRealmAdminKey":"be526ec20a19a33d98a54e5d7f54400ae34f289aac9c854909b61f48d0273be8", - "nodeAccountNum":3, - "nodeRealmNum":0, - "nodeShardNum":0, - "transactionFee":100 - }, - { - "initialBalance":1000, - "proxyAccountNum":1, - "proxyRealmNum":0, - "proxyShardNum":0, - "proxyFraction":100, - "maxReceiveProxyFraction":50, - "sendRecordThreshold":100, - "receiveRecordThreshold":1000, - "receiverSigRequired":false, - "autoRenewPeriod":1382400, - "shardID":0, - "realmID":0, - "key":"72e64df4dea5e57e6d31e812650aec490509205bae137e3e53b476b9673654f4", - "newRealmAdminKey":"be526ec20a19a33d98a54e5d7f54400ae34f289aac9c854909b61f48d0273be8", - "nodeAccountNum":3, - "nodeRealmNum":0, - "nodeShardNum":0, - "transactionFee":100 - }, - { - "initialBalance":1000, - "proxyAccountNum":1, - "proxyRealmNum":0, - "proxyShardNum":0, - "proxyFraction":100, - "maxReceiveProxyFraction":50, - "sendRecordThreshold":100, - "receiveRecordThreshold":1000, - "receiverSigRequired":false, - "autoRenewPeriod":1382400, - "shardID":0, - "realmID":0, - "key":"55e1ed10b87365c70bf7b65580f69bee86c39e610dbb6ca2e5d5bd5909ec90c8", - "newRealmAdminKey":"be526ec20a19a33d98a54e5d7f54400ae34f289aac9c854909b61f48d0273be8", - "nodeAccountNum":3, - "nodeRealmNum":0, - "nodeShardNum":0, - "transactionFee":100 - }, - { - "initialBalance":1000, - "proxyAccountNum":1, - "proxyRealmNum":0, - "proxyShardNum":0, - "proxyFraction":100, - "maxReceiveProxyFraction":50, - "sendRecordThreshold":100, - "receiveRecordThreshold":1000, - "receiverSigRequired":false, - "autoRenewPeriod":1382400, - "shardID":0, - "realmID":0, - "key":"a8f23d1c8833f3ac21d88a2374c3d31b9b2902ee58dad27f3c71bfabf4866427", - "newRealmAdminKey":"be526ec20a19a33d98a54e5d7f54400ae34f289aac9c854909b61f48d0273be8", - "nodeAccountNum":3, - "nodeRealmNum":0, - "nodeShardNum":0, - "transactionFee":100 - }, - { - "initialBalance":1000, - "proxyAccountNum":1, - "proxyRealmNum":0, - "proxyShardNum":0, - "proxyFraction":100, - "maxReceiveProxyFraction":50, - "sendRecordThreshold":100, - "receiveRecordThreshold":1000, - "receiverSigRequired":false, - "autoRenewPeriod":1382400, - "shardID":0, - "realmID":0, - "key":"27e8fce3ce1ba7c15c9b679631bc87954228215ea2c62ef8b1c03ddacc8b4d55", - "newRealmAdminKey":"be526ec20a19a33d98a54e5d7f54400ae34f289aac9c854909b61f48d0273be8", - "nodeAccountNum":3, - "nodeRealmNum":0, - "nodeShardNum":0, - "transactionFee":100 - }, - { - "initialBalance":1000, - "proxyAccountNum":1, - "proxyRealmNum":0, - "proxyShardNum":0, - "proxyFraction":100, - "maxReceiveProxyFraction":50, - "sendRecordThreshold":100, - "receiveRecordThreshold":1000, - "receiverSigRequired":false, - "autoRenewPeriod":1382400, - "shardID":0, - "realmID":0, - "key":"594393a6da0ec008d10b0ba9869fdd8e3b8ae143ed5304f5eee49c41e97757ef", - "newRealmAdminKey":"be526ec20a19a33d98a54e5d7f54400ae34f289aac9c854909b61f48d0273be8", - "nodeAccountNum":3, - "nodeRealmNum":0, - "nodeShardNum":0, - "transactionFee":100 - - }, - { - "initialBalance":1000, - "proxyAccountNum":1, - "proxyRealmNum":0, - "proxyShardNum":0, - "proxyFraction":100, - "maxReceiveProxyFraction":50, - "sendRecordThreshold":100, - "receiveRecordThreshold":1000, - "receiverSigRequired":false, - "autoRenewPeriod":1382400, - "shardID":0, - "realmID":0, - "key":"c6d1a245f274777212e55131e71472afe6231c0443c7938e74b16956fa418d71", - "newRealmAdminKey":"be526ec20a19a33d98a54e5d7f54400ae34f289aac9c854909b61f48d0273be8", - "nodeAccountNum":3, - "nodeRealmNum":0, - "nodeShardNum":0, - "transactionFee":100 - }, - { - "initialBalance":1000, - "proxyAccountNum":1, - "proxyRealmNum":0, - "proxyShardNum":0, - "proxyFraction":100, - "maxReceiveProxyFraction":50, - "sendRecordThreshold":100, - "receiveRecordThreshold":1000, - "receiverSigRequired":false, - "autoRenewPeriod":1382400, - "shardID":0, - "realmID":0, - "key":"56c3a516d51638e2d488ebfa505284b5cf29481d4f81986fe10af3d984f8615f", - "newRealmAdminKey":"be526ec20a19a33d98a54e5d7f54400ae34f289aac9c854909b61f48d0273be8", - "nodeAccountNum":3, - "nodeRealmNum":0, - "nodeShardNum":0, - "transactionFee":100 - } -] \ No newline at end of file diff --git a/hedera-node/test-clients/src/main/resource/ExchangeRateProto_ONLY_NEXT.txt b/hedera-node/test-clients/src/main/resource/ExchangeRateProto_ONLY_NEXT.txt deleted file mode 100644 index 9f4e873aa944..000000000000 --- a/hedera-node/test-clients/src/main/resource/ExchangeRateProto_ONLY_NEXT.txt +++ /dev/null @@ -1 +0,0 @@ - Ńõö \ No newline at end of file diff --git a/hedera-node/test-clients/src/main/resource/META-INF/services/org.junit.platform.engine.TestEngine b/hedera-node/test-clients/src/main/resource/META-INF/services/org.junit.platform.engine.TestEngine deleted file mode 100644 index 74fc6e03739b..000000000000 --- a/hedera-node/test-clients/src/main/resource/META-INF/services/org.junit.platform.engine.TestEngine +++ /dev/null @@ -1 +0,0 @@ -com.hedera.services.bdd.junit.HapiTestEngine \ No newline at end of file diff --git a/hedera-node/test-clients/src/main/resource/META-INF/services/org.junit.platform.launcher.LauncherSessionListener b/hedera-node/test-clients/src/main/resource/META-INF/services/org.junit.platform.launcher.LauncherSessionListener new file mode 100644 index 000000000000..23348cdb9572 --- /dev/null +++ b/hedera-node/test-clients/src/main/resource/META-INF/services/org.junit.platform.launcher.LauncherSessionListener @@ -0,0 +1 @@ +com.hedera.services.bdd.junit.SharedNetworkLauncherSessionListener \ No newline at end of file diff --git a/hedera-node/test-clients/src/main/resource/NodeAccounts.txt b/hedera-node/test-clients/src/main/resource/NodeAccounts.txt deleted file mode 100644 index 70200818df3c..000000000000 --- a/hedera-node/test-clients/src/main/resource/NodeAccounts.txt +++ /dev/null @@ -1 +0,0 @@ -rO0ABXNyABFqYXZhLnV0aWwuSGFzaE1hcAUH2sHDFmDRAwACRgAKbG9hZEZhY3RvckkACXRocmVzaG9sZHhwP0AAAAAAAAx3CAAAABAAAAABdAAMTk9ERV9BQ0NPVU5Uc3IAHWNvbS5vcGVuY3Jvd2QuY29yZS5LZXlQYWlyT2Jqfu50MIDYVscCAANMAAdwcml2S2V5dAAaTGphdmEvc2VjdXJpdHkvUHJpdmF0ZUtleTtMAApwcml2YXRlS2V5dAASTGphdmEvbGFuZy9TdHJpbmc7TAAJcHVibGljS2V5cQB+AAV4cHB0AGAzMDJlMDIwMTAwMzAwNTA2MDMyYjY1NzAwNDIyMDQyMDgzMmMwYWQ0MmJkNTUxY2NkN2JjOTI4OWIwMDE0MDkxMzNlMmUyNDQxYjVlMjM1ZDU2YTA5NTkxZWVjZDVjNmJ0AFgzMDJhMzAwNTA2MDMyYjY1NzAwMzIxMDAxOTQzNzMzMzIzOWFjMGQ1MjYxMTNjMDg0YzQ5ZjcwYjVkNGQ5ZThkZmNiNDk4YzAzNTNmMTI2NTVhNTNmZjA2eA== \ No newline at end of file diff --git a/hedera-node/test-clients/src/main/resource/TransferList.json b/hedera-node/test-clients/src/main/resource/TransferList.json deleted file mode 100644 index aa55cdfa1912..000000000000 --- a/hedera-node/test-clients/src/main/resource/TransferList.json +++ /dev/null @@ -1,38 +0,0 @@ -[ - { - "nodeAccountNum":3, - "nodeRealmNum":0, - "nodeShardNum":0, - "toAccountNum":1008, - "toRealmNum":0, - "toShardNum":0, - "amount":5000000000 - }, - { - "nodeAccountNum":3, - "nodeRealmNum":0, - "nodeShardNum":0, - "toAccountNum":1009, - "toRealmNum":0, - "toShardNum":0, - "amount":10000 - }, - { - "nodeAccountNum":3, - "nodeRealmNum":0, - "nodeShardNum":0, - "toAccountNum":1010, - "toRealmNum":0, - "toShardNum":0, - "amount":10000 - }, - { - "nodeAccountNum":3, - "nodeRealmNum":0, - "nodeShardNum":0, - "toAccountNum":1011, - "toRealmNum":0, - "toShardNum":0, - "amount":10000 - } -] \ No newline at end of file diff --git a/hedera-node/test-clients/src/main/resource/application.properties.MAIN_NET b/hedera-node/test-clients/src/main/resource/application.properties.MAIN_NET deleted file mode 100644 index 7353e69ec91f..000000000000 --- a/hedera-node/test-clients/src/main/resource/application.properties.MAIN_NET +++ /dev/null @@ -1,67 +0,0 @@ -# A value of 1 is for production, where all nodes listen on same default port. A value of 0 is for development, i.e. running HGCApp locally. -#productionFlag=0 - -# account ID for the default listening node -defaultListeningNodeAccount=0.0.3 - -# Effective only if productionFlag is 0. A value of 1 allows all nodes listening on different ports, useful for local testing and requiring submitting requests to different local nodes -#uniqueListeningPortFlag=1 - -# Default listening node port -port = 50211 -# -# Default listening node host -#Enable below localhost -#host=localhost -# -#Enable below Hedera Public Test-Net -#host=0.testnet.hedera.com -# -#Enable below Hedera Main-Net Node=3 -host=35.237.200.180 -# -#Start up Account file -# -##Enable below for localhost Account Startup File for Genesis Account -#startUpFile=src/main/resource/StartUpAccount.txt -# -##Enable below for Test Net Account Startup File for AccountNum= -#startUpFile=src/main/resource/TestNet_AccountForTestsStartUp.txt -# -##Enable below for Main Net Account Startup File for AccountNum= -startUpFile=src/main/resource/MainNet_AccountForTestsStartUp.txt -# -#startUpFile=src/main/resource/OpenCrowd_8013_AccountForTestsStartUp.txt -# -# default REALM/SHARD properties -NODE_REALM_NUMBER = 0 -NODE_SHARD_NUMBER = 0 -# TPS Test Properties -TPS = 100 -retrieveReceipt =true -BATCH_SIZE= 1000 -COUNT_SIZE =1000 - -# Token transfer test properties -OCT_ACCOUNTS = 7 -OCT_TRANSFERS = 7 -#Special Admin account number -specialAccountNum=50 -#Genesis Account Number -genesisAccountNum=2 - -#Lifetime for creating objects, in seconds. -CONTRACT_DURATION = 7890000 -FILE_DURATION = 7890000 -ACCOUNT_DURATION = 7890000 -UPDATE_DURATION_VALUE=7885000 - -# Smart contract local calls require that the payment cover both fees and gas. -# This estimate must cover the gas needed for local calls in any test. -LOCAL_CALL_GAS = 260000 - -#Number of nodes for MultiNodeTest -nummberOfNodesToTest=3 -# -payerAccountForTests=2 -payerAccountPEMKeyFile= diff --git a/hedera-node/test-clients/src/main/resource/application.properties.TEST_NET b/hedera-node/test-clients/src/main/resource/application.properties.TEST_NET deleted file mode 100644 index bff16e92a37c..000000000000 --- a/hedera-node/test-clients/src/main/resource/application.properties.TEST_NET +++ /dev/null @@ -1,67 +0,0 @@ -# A value of 1 is for production, where all nodes listen on same default port. A value of 0 is for development, i.e. running HGCApp locally. -#productionFlag=0 - -# account ID for the default listening node -defaultListeningNodeAccount=0.0.3 - -# Effective only if productionFlag is 0. A value of 1 allows all nodes listening on different ports, useful for local testing and requiring submitting requests to different local nodes -#uniqueListeningPortFlag=1 - -# Default listening node port -port = 50211 -# -# Default listening node host -#Enable below localhost -#host=localhost -# -#Enable below Hedera Public Test-Net -host=0.testnet.hedera.com -# -#Enable below Hedera Main-Net Node=3 -#host=35.237.200.180 -# -#Start up Account file -# -##Enable below for localhost Account Startup File for Genesis Account -#startUpFile=src/main/resource/StartUpAccount.txt -# -##Enable below for Test Net Account Startup File for AccountNum= -startUpFile=src/main/resource/TestNet_AccountForTestsStartUp.txt -# -##Enable below for Main Net Account Startup File for AccountNum= -#startUpFile=src/main/resource/MainNet_AccountForTestsStartUp.txt -# -#startUpFile=src/main/resource/OpenCrowd_8013_AccountForTestsStartUp.txt -# -# default REALM/SHARD properties -NODE_REALM_NUMBER = 0 -NODE_SHARD_NUMBER = 0 -# TPS Test Properties -TPS = 100 -retrieveReceipt =true -BATCH_SIZE= 1000 -COUNT_SIZE =1000 - -# Token transfer test properties -OCT_ACCOUNTS = 7 -OCT_TRANSFERS = 7 -#Special Admin account number -specialAccountNum=50 -#Genesis Account Number -genesisAccountNum=2 - -#Lifetime for creating objects, in seconds. -CONTRACT_DURATION = 7890000 -FILE_DURATION = 7890000 -ACCOUNT_DURATION = 7890000 -UPDATE_DURATION_VALUE=7885000 - -# Smart contract local calls require that the payment cover both fees and gas. -# This estimate must cover the gas needed for local calls in any test. -LOCAL_CALL_GAS = 260000 - -#Number of nodes for MultiNodeTest -nummberOfNodesToTest=3 -# -payerAccountForTests=2 -payerAccountPEMKeyFile= diff --git a/hedera-node/test-clients/src/main/resource/bootstrap.properties b/hedera-node/test-clients/src/main/resource/bootstrap.properties index a8a3886dffd0..33d4f10f9187 100644 --- a/hedera-node/test-clients/src/main/resource/bootstrap.properties +++ b/hedera-node/test-clients/src/main/resource/bootstrap.properties @@ -71,7 +71,7 @@ contracts.chainId=298 contracts.defaultLifetime=7890000 contracts.enforceCreationThrottle=false contracts.evm.allowCallsToNonContractAccounts=true -contracts.evm.version=v0.46 +contracts.evm.version=v0.50 contracts.evm.version.dynamic=true contracts.permittedDelegateCallers=1062787,1461860 contracts.freeStorageTierLimit=100 diff --git a/hedera-node/test-clients/src/main/resource/ciSpecBucketThrottles-3NodeNetwork.properties b/hedera-node/test-clients/src/main/resource/ciSpecBucketThrottles-3NodeNetwork.properties deleted file mode 100644 index d34937993941..000000000000 --- a/hedera-node/test-clients/src/main/resource/ciSpecBucketThrottles-3NodeNetwork.properties +++ /dev/null @@ -1,11 +0,0 @@ -hapi.throttling.config.useLegacyProps=false -hapi.throttling.defaults.capacity=3.0 -hapi.throttling.defaults.queryBucket=smallQueryBucket -hapi.throttling.buckets.smallQueryBucket.burstPeriod=2.0 -hapi.throttling.buckets.receiptsBucket.capacity=60.0 -hapi.throttling.buckets.myTransferBucketOverflow.capacity=6.0 -hapi.throttling.buckets.myTransferBucket.capacity=6.0 -hapi.throttling.buckets.myTransferBucket.overflow=myTransferBucketOverflow -hapi.throttling.ops.cryptoTransfer.bucket=myTransferBucket -hapi.throttling.ops.cryptoTransfer.capacityRequired=2.0 -hapi.throttling.ops.transactionGetReceipt.bucket=receiptsBucket diff --git a/hedera-node/test-clients/src/main/resource/ciSpecBucketThrottles-4NodeNetwork.properties b/hedera-node/test-clients/src/main/resource/ciSpecBucketThrottles-4NodeNetwork.properties deleted file mode 100644 index abb524685a1f..000000000000 --- a/hedera-node/test-clients/src/main/resource/ciSpecBucketThrottles-4NodeNetwork.properties +++ /dev/null @@ -1,11 +0,0 @@ -hapi.throttling.config.useLegacyProps=false -hapi.throttling.defaults.capacity=4.0 -hapi.throttling.defaults.queryBucket=smallQueryBucket -hapi.throttling.buckets.smallQueryBucket.burstPeriod=2.0 -hapi.throttling.buckets.receiptsBucket.capacity=80.0 -hapi.throttling.buckets.myTransferBucketOverflow.capacity=8.0 -hapi.throttling.buckets.myTransferBucket.capacity=8.0 -hapi.throttling.buckets.myTransferBucket.overflow=myTransferBucketOverflow -hapi.throttling.ops.cryptoTransfer.bucket=myTransferBucket -hapi.throttling.ops.cryptoTransfer.capacityRequired=2.0 -hapi.throttling.ops.transactionGetReceipt.bucket=receiptsBucket diff --git a/hedera-node/test-clients/src/main/resource/ciSpecBucketThrottles-6NodeNetwork.properties b/hedera-node/test-clients/src/main/resource/ciSpecBucketThrottles-6NodeNetwork.properties deleted file mode 100644 index be313081e6f2..000000000000 --- a/hedera-node/test-clients/src/main/resource/ciSpecBucketThrottles-6NodeNetwork.properties +++ /dev/null @@ -1,11 +0,0 @@ -hapi.throttling.config.useLegacyProps=false -hapi.throttling.defaults.capacity=6.0 -hapi.throttling.defaults.queryBucket=smallQueryBucket -hapi.throttling.buckets.smallQueryBucket.burstPeriod=2.0 -hapi.throttling.buckets.receiptsBucket.capacity=120.0 -hapi.throttling.buckets.myTransferBucketOverflow.capacity=12.0 -hapi.throttling.buckets.myTransferBucket.capacity=12.0 -hapi.throttling.buckets.myTransferBucket.overflow=myTransferBucketOverflow -hapi.throttling.ops.cryptoTransfer.bucket=myTransferBucket -hapi.throttling.ops.cryptoTransfer.capacityRequired=2.0 -hapi.throttling.ops.transactionGetReceipt.bucket=receiptsBucket diff --git a/hedera-node/test-clients/src/main/resource/contract/contracts/ExchangeRatePrecompile/ExchangeRatePrecompile.bin b/hedera-node/test-clients/src/main/resource/contract/contracts/ExchangeRatePrecompile/ExchangeRatePrecompile.bin index 3ed6a5821c19..6fddea389549 100644 --- a/hedera-node/test-clients/src/main/resource/contract/contracts/ExchangeRatePrecompile/ExchangeRatePrecompile.bin +++ b/hedera-node/test-clients/src/main/resource/contract/contracts/ExchangeRatePrecompile/ExchangeRatePrecompile.bin @@ -1 +1 @@ -608060405234801561001057600080fd5b506040516106503803806106508339818101604052810190610032919061007a565b80600081905550506100a7565b600080fd5b6000819050919050565b61005781610044565b811461006257600080fd5b50565b6000815190506100748161004e565b92915050565b6000602082840312156100905761008f61003f565b5b600061009e84828501610065565b91505092915050565b61059a806100b66000396000f3fe6080604052600436106100345760003560e01c806303b636e61461003957806389dea1d014610043578063bff4f54f1461004d575b600080fd5b61004161006b565b005b61004b610154565b005b61005561018a565b60405161006291906103d1565b60405180910390f35b60008061016873ffffffffffffffffffffffffffffffffffffffff1663bff4f54f60e01b604051602401604051602081830303815290604052907bffffffffffffffffffffffffffffffffffffffffffffffffffffffff19166020820180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff83818316178352505050506040516100ff9190610466565b6000604051808303816000865af19150503d806000811461013c576040519150601f19603f3d011682016040523d82523d6000602084013e610141565b606091505b50915091508161015057600080fd5b5050565b60005460006305f5e1008261016991906104ac565b905060006101768261019a565b90508034101561018557600080fd5b505050565b6000610195346102a9565b905090565b600080600061016873ffffffffffffffffffffffffffffffffffffffff16632e3cff6a60e01b856040516024016101d191906103d1565b604051602081830303815290604052907bffffffffffffffffffffffffffffffffffffffffffffffffffffffff19166020820180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff838183161783525050505060405161023b9190610466565b6000604051808303816000865af19150503d8060008114610278576040519150601f19603f3d011682016040523d82523d6000602084013e61027d565b606091505b50915091508161028c57600080fd5b808060200190518101906102a09190610537565b92505050919050565b600080600061016873ffffffffffffffffffffffffffffffffffffffff166343a8822960e01b856040516024016102e091906103d1565b604051602081830303815290604052907bffffffffffffffffffffffffffffffffffffffffffffffffffffffff19166020820180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff838183161783525050505060405161034a9190610466565b6000604051808303816000865af19150503d8060008114610387576040519150601f19603f3d011682016040523d82523d6000602084013e61038c565b606091505b50915091508161039b57600080fd5b808060200190518101906103af9190610537565b92505050919050565b6000819050919050565b6103cb816103b8565b82525050565b60006020820190506103e660008301846103c2565b92915050565b600081519050919050565b600081905092915050565b60005b83811015610420578082015181840152602081019050610405565b8381111561042f576000848401525b50505050565b6000610440826103ec565b61044a81856103f7565b935061045a818560208601610402565b80840191505092915050565b60006104728284610435565b915081905092915050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b60006104b7826103b8565b91506104c2836103b8565b9250817fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff04831182151516156104fb576104fa61047d565b5b828202905092915050565b600080fd5b610514816103b8565b811461051f57600080fd5b50565b6000815190506105318161050b565b92915050565b60006020828403121561054d5761054c610506565b5b600061055b84828501610522565b9150509291505056fea26469706673582212209dd36ad26d305d7a60354b6e84fa8424f3555a41d74121cdadc47dfc040a11d664736f6c63430008090033 \ No newline at end of file +608060405234801561000f575f80fd5b5060405161041938038061041983398101604081905261002e91610035565b5f5561004c565b5f60208284031215610045575f80fd5b5051919050565b6103c0806100595f395ff3fe60806040526004361061003e575f3560e01c806303b636e6146100425780633f39929f1461004c57806389dea1d01461005f578063bff4f54f14610067575b5f80fd5b61004a610081565b005b61004a61005a366004610307565b61010c565b61004a610118565b61006f610149565b60405190815260200160405180910390f35b60408051600481526024810182526020810180516001600160e01b031663bff4f54f60e01b17905290515f918291610168916100bc9161031e565b5f604051808303815f865af19150503d805f81146100f5576040519150601f19603f3d011682016040523d82523d5f602084013e6100fa565b606091505b509150915081610108575f80fd5b5050565b61011581610158565b50565b5f80549061012a6305f5e1008361034a565b90505f6101368261020d565b905080341015610144575f80fd5b505050565b5f610153346102dd565b905090565b5f806101686001600160a01b0316346343a8822960e01b8560405160240161018291815260200190565b60408051601f198184030181529181526020820180516001600160e01b03166001600160e01b03199094169390931790925290516101c0919061031e565b5f6040518083038185875af1925050503d805f81146101fa576040519150601f19603f3d011682016040523d82523d5f602084013e6101ff565b606091505b509150915081610144575f80fd5b5f805f6101686001600160a01b0316632e3cff6a60e01b8560405160240161023791815260200190565b60408051601f198184030181529181526020820180516001600160e01b03166001600160e01b0319909416939093179092529051610275919061031e565b5f604051808303815f865af19150503d805f81146102ae576040519150601f19603f3d011682016040523d82523d5f602084013e6102b3565b606091505b5091509150816102c1575f80fd5b808060200190518101906102d59190610373565b949350505050565b5f805f6101686001600160a01b03166343a8822960e01b8560405160240161023791815260200190565b5f60208284031215610317575f80fd5b5035919050565b5f82515f5b8181101561033d5760208186018101518583015201610323565b505f920191825250919050565b808202811582820484141761036d57634e487b7160e01b5f52601160045260245ffd5b92915050565b5f60208284031215610383575f80fd5b505191905056fea2646970667358221220fbb1848818142a7e5b6f1a743d4ea51e969fba3350e33fa6541dc8fe34b198a264736f6c63430008180033 \ No newline at end of file diff --git a/hedera-node/test-clients/src/main/resource/contract/contracts/ExchangeRatePrecompile/ExchangeRatePrecompile.json b/hedera-node/test-clients/src/main/resource/contract/contracts/ExchangeRatePrecompile/ExchangeRatePrecompile.json index d026a0f71baf..861e42ba3a60 100644 --- a/hedera-node/test-clients/src/main/resource/contract/contracts/ExchangeRatePrecompile/ExchangeRatePrecompile.json +++ b/hedera-node/test-clients/src/main/resource/contract/contracts/ExchangeRatePrecompile/ExchangeRatePrecompile.json @@ -1 +1,53 @@ -[{"inputs":[{"internalType":"uint256","name":"_toll","type":"uint256"}],"stateMutability":"nonpayable","type":"constructor"},{"inputs":[],"name":"approxUsdValue","outputs":[{"internalType":"uint256","name":"tinycents","type":"uint256"}],"stateMutability":"payable","type":"function"},{"inputs":[],"name":"gatedAccess","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[],"name":"invalidCall","outputs":[],"stateMutability":"payable","type":"function"}] \ No newline at end of file +[ + { + "inputs": [ + { + "internalType": "uint256", + "name": "_toll", + "type": "uint256" + } + ], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "inputs": [], + "name": "approxUsdValue", + "outputs": [ + { + "internalType": "uint256", + "name": "tinycents", + "type": "uint256" + } + ], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "_value", + "type": "uint256" + } + ], + "name": "callWithValue", + "outputs": [], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [], + "name": "gatedAccess", + "outputs": [], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [], + "name": "invalidCall", + "outputs": [], + "stateMutability": "payable", + "type": "function" + } +] \ No newline at end of file diff --git a/hedera-node/test-clients/src/main/resource/contract/contracts/ExchangeRatePrecompile/ExchangeRatePrecompile.sol b/hedera-node/test-clients/src/main/resource/contract/contracts/ExchangeRatePrecompile/ExchangeRatePrecompile.sol index ccd44cc149cc..4a4845dfa57d 100644 --- a/hedera-node/test-clients/src/main/resource/contract/contracts/ExchangeRatePrecompile/ExchangeRatePrecompile.sol +++ b/hedera-node/test-clients/src/main/resource/contract/contracts/ExchangeRatePrecompile/ExchangeRatePrecompile.sol @@ -1,4 +1,5 @@ // SPDX-License-Identifier: Apache-2.0 +pragma solidity >=0.5.0 <0.9.0; import "./SelfFunding.sol"; @@ -18,6 +19,10 @@ contract ExchangeRatePrecompile is SelfFunding { tinycents = tinybarsToTinycents(msg.value); } + function callWithValue(uint256 _value) external payable { + callToPrecompileWithValue(_value); + } + function invalidCall() external payable { // Should fail, this is not a valid selector (bool success, bytes memory result) = PRECOMPILE_ADDRESS.call( diff --git a/hedera-node/test-clients/src/main/resource/contract/contracts/ExchangeRatePrecompile/SelfFunding.sol b/hedera-node/test-clients/src/main/resource/contract/contracts/ExchangeRatePrecompile/SelfFunding.sol index e3e4bdbbbd85..3823050ae7bc 100644 --- a/hedera-node/test-clients/src/main/resource/contract/contracts/ExchangeRatePrecompile/SelfFunding.sol +++ b/hedera-node/test-clients/src/main/resource/contract/contracts/ExchangeRatePrecompile/SelfFunding.sol @@ -21,6 +21,12 @@ abstract contract SelfFunding { tinycents = abi.decode(result, (uint256)); } + function callToPrecompileWithValue(uint256 tinybars) internal { + (bool success, bytes memory result) = PRECOMPILE_ADDRESS.call{value: msg.value}( + abi.encodeWithSelector(IExchangeRate.tinybarsToTinycents.selector, tinybars)); + require(success); + } + modifier costsCents(uint256 cents) { uint256 tinycents = cents * TINY_PARTS_PER_WHOLE; uint256 requiredTinybars = tinycentsToTinybars(tinycents); diff --git a/hedera-node/test-clients/src/main/resource/contract/contracts/IHederaTokenService/IHederaTokenService.bin b/hedera-node/test-clients/src/main/resource/contract/contracts/IHederaTokenService/IHederaTokenService.bin new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/hedera-node/test-clients/src/main/resource/contract/contracts/IHederaTokenService/IHederaTokenService.json b/hedera-node/test-clients/src/main/resource/contract/contracts/IHederaTokenService/IHederaTokenService.json new file mode 100644 index 000000000000..f41ec49e1566 --- /dev/null +++ b/hedera-node/test-clients/src/main/resource/contract/contracts/IHederaTokenService/IHederaTokenService.json @@ -0,0 +1 @@ +[{"inputs":[{"internalType":"address","name":"token","type":"address"},{"internalType":"address","name":"owner","type":"address"},{"internalType":"address","name":"spender","type":"address"}],"name":"allowance","outputs":[{"internalType":"int64","name":"responseCode","type":"int64"},{"internalType":"uint256","name":"allowance","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"token","type":"address"},{"internalType":"address","name":"spender","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"approve","outputs":[{"internalType":"int64","name":"responseCode","type":"int64"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"token","type":"address"},{"internalType":"address","name":"approved","type":"address"},{"internalType":"int64","name":"serialNumber","type":"int64"}],"name":"approveNFT","outputs":[{"internalType":"int64","name":"responseCode","type":"int64"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"account","type":"address"},{"internalType":"address","name":"token","type":"address"}],"name":"associateToken","outputs":[{"internalType":"int64","name":"responseCode","type":"int64"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"account","type":"address"},{"internalType":"address[]","name":"tokens","type":"address[]"}],"name":"associateTokens","outputs":[{"internalType":"int64","name":"responseCode","type":"int64"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"token","type":"address"},{"internalType":"uint64","name":"amount","type":"uint64"},{"internalType":"int64[]","name":"serialNumbers","type":"int64[]"}],"name":"burnToken","outputs":[{"internalType":"int64","name":"responseCode","type":"int64"},{"internalType":"uint64","name":"newTotalSupply","type":"uint64"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"components":[{"internalType":"string","name":"name","type":"string"},{"internalType":"string","name":"symbol","type":"string"},{"internalType":"address","name":"treasury","type":"address"},{"internalType":"string","name":"memo","type":"string"},{"internalType":"bool","name":"tokenSupplyType","type":"bool"},{"internalType":"uint32","name":"maxSupply","type":"uint32"},{"internalType":"bool","name":"freezeDefault","type":"bool"},{"components":[{"internalType":"uint256","name":"keyType","type":"uint256"},{"components":[{"internalType":"bool","name":"inheritAccountKey","type":"bool"},{"internalType":"address","name":"contractId","type":"address"},{"internalType":"bytes","name":"ed25519","type":"bytes"},{"internalType":"bytes","name":"ECDSA_secp256k1","type":"bytes"},{"internalType":"address","name":"delegatableContractId","type":"address"}],"internalType":"struct IHederaTokenService.KeyValue","name":"key","type":"tuple"}],"internalType":"struct IHederaTokenService.TokenKey[]","name":"tokenKeys","type":"tuple[]"},{"components":[{"internalType":"uint32","name":"second","type":"uint32"},{"internalType":"address","name":"autoRenewAccount","type":"address"},{"internalType":"uint32","name":"autoRenewPeriod","type":"uint32"}],"internalType":"struct IHederaTokenService.Expiry","name":"expiry","type":"tuple"}],"internalType":"struct IHederaTokenService.HederaToken","name":"token","type":"tuple"},{"internalType":"uint256","name":"initialTotalSupply","type":"uint256"},{"internalType":"uint256","name":"decimals","type":"uint256"}],"name":"createFungibleToken","outputs":[{"internalType":"int64","name":"responseCode","type":"int64"},{"internalType":"address","name":"tokenAddress","type":"address"}],"stateMutability":"payable","type":"function"},{"inputs":[{"components":[{"internalType":"string","name":"name","type":"string"},{"internalType":"string","name":"symbol","type":"string"},{"internalType":"address","name":"treasury","type":"address"},{"internalType":"string","name":"memo","type":"string"},{"internalType":"bool","name":"tokenSupplyType","type":"bool"},{"internalType":"uint32","name":"maxSupply","type":"uint32"},{"internalType":"bool","name":"freezeDefault","type":"bool"},{"components":[{"internalType":"uint256","name":"keyType","type":"uint256"},{"components":[{"internalType":"bool","name":"inheritAccountKey","type":"bool"},{"internalType":"address","name":"contractId","type":"address"},{"internalType":"bytes","name":"ed25519","type":"bytes"},{"internalType":"bytes","name":"ECDSA_secp256k1","type":"bytes"},{"internalType":"address","name":"delegatableContractId","type":"address"}],"internalType":"struct IHederaTokenService.KeyValue","name":"key","type":"tuple"}],"internalType":"struct IHederaTokenService.TokenKey[]","name":"tokenKeys","type":"tuple[]"},{"components":[{"internalType":"uint32","name":"second","type":"uint32"},{"internalType":"address","name":"autoRenewAccount","type":"address"},{"internalType":"uint32","name":"autoRenewPeriod","type":"uint32"}],"internalType":"struct IHederaTokenService.Expiry","name":"expiry","type":"tuple"}],"internalType":"struct IHederaTokenService.HederaToken","name":"token","type":"tuple"},{"internalType":"uint256","name":"initialTotalSupply","type":"uint256"},{"internalType":"uint256","name":"decimals","type":"uint256"},{"components":[{"internalType":"uint32","name":"amount","type":"uint32"},{"internalType":"address","name":"tokenId","type":"address"},{"internalType":"bool","name":"useHbarsForPayment","type":"bool"},{"internalType":"bool","name":"useCurrentTokenForPayment","type":"bool"},{"internalType":"address","name":"feeCollector","type":"address"}],"internalType":"struct IHederaTokenService.FixedFee[]","name":"fixedFees","type":"tuple[]"},{"components":[{"internalType":"uint32","name":"numerator","type":"uint32"},{"internalType":"uint32","name":"denominator","type":"uint32"},{"internalType":"uint32","name":"minimumAmount","type":"uint32"},{"internalType":"uint32","name":"maximumAmount","type":"uint32"},{"internalType":"bool","name":"netOfTransfers","type":"bool"},{"internalType":"address","name":"feeCollector","type":"address"}],"internalType":"struct IHederaTokenService.FractionalFee[]","name":"fractionalFees","type":"tuple[]"}],"name":"createFungibleTokenWithCustomFees","outputs":[{"internalType":"int64","name":"responseCode","type":"int64"},{"internalType":"address","name":"tokenAddress","type":"address"}],"stateMutability":"payable","type":"function"},{"inputs":[{"components":[{"internalType":"string","name":"name","type":"string"},{"internalType":"string","name":"symbol","type":"string"},{"internalType":"address","name":"treasury","type":"address"},{"internalType":"string","name":"memo","type":"string"},{"internalType":"bool","name":"tokenSupplyType","type":"bool"},{"internalType":"uint32","name":"maxSupply","type":"uint32"},{"internalType":"bool","name":"freezeDefault","type":"bool"},{"components":[{"internalType":"uint256","name":"keyType","type":"uint256"},{"components":[{"internalType":"bool","name":"inheritAccountKey","type":"bool"},{"internalType":"address","name":"contractId","type":"address"},{"internalType":"bytes","name":"ed25519","type":"bytes"},{"internalType":"bytes","name":"ECDSA_secp256k1","type":"bytes"},{"internalType":"address","name":"delegatableContractId","type":"address"}],"internalType":"struct IHederaTokenService.KeyValue","name":"key","type":"tuple"}],"internalType":"struct IHederaTokenService.TokenKey[]","name":"tokenKeys","type":"tuple[]"},{"components":[{"internalType":"uint32","name":"second","type":"uint32"},{"internalType":"address","name":"autoRenewAccount","type":"address"},{"internalType":"uint32","name":"autoRenewPeriod","type":"uint32"}],"internalType":"struct IHederaTokenService.Expiry","name":"expiry","type":"tuple"}],"internalType":"struct IHederaTokenService.HederaToken","name":"token","type":"tuple"}],"name":"createNonFungibleToken","outputs":[{"internalType":"int64","name":"responseCode","type":"int64"},{"internalType":"address","name":"tokenAddress","type":"address"}],"stateMutability":"payable","type":"function"},{"inputs":[{"components":[{"internalType":"string","name":"name","type":"string"},{"internalType":"string","name":"symbol","type":"string"},{"internalType":"address","name":"treasury","type":"address"},{"internalType":"string","name":"memo","type":"string"},{"internalType":"bool","name":"tokenSupplyType","type":"bool"},{"internalType":"uint32","name":"maxSupply","type":"uint32"},{"internalType":"bool","name":"freezeDefault","type":"bool"},{"components":[{"internalType":"uint256","name":"keyType","type":"uint256"},{"components":[{"internalType":"bool","name":"inheritAccountKey","type":"bool"},{"internalType":"address","name":"contractId","type":"address"},{"internalType":"bytes","name":"ed25519","type":"bytes"},{"internalType":"bytes","name":"ECDSA_secp256k1","type":"bytes"},{"internalType":"address","name":"delegatableContractId","type":"address"}],"internalType":"struct IHederaTokenService.KeyValue","name":"key","type":"tuple"}],"internalType":"struct IHederaTokenService.TokenKey[]","name":"tokenKeys","type":"tuple[]"},{"components":[{"internalType":"uint32","name":"second","type":"uint32"},{"internalType":"address","name":"autoRenewAccount","type":"address"},{"internalType":"uint32","name":"autoRenewPeriod","type":"uint32"}],"internalType":"struct IHederaTokenService.Expiry","name":"expiry","type":"tuple"}],"internalType":"struct IHederaTokenService.HederaToken","name":"token","type":"tuple"},{"components":[{"internalType":"uint32","name":"amount","type":"uint32"},{"internalType":"address","name":"tokenId","type":"address"},{"internalType":"bool","name":"useHbarsForPayment","type":"bool"},{"internalType":"bool","name":"useCurrentTokenForPayment","type":"bool"},{"internalType":"address","name":"feeCollector","type":"address"}],"internalType":"struct IHederaTokenService.FixedFee[]","name":"fixedFees","type":"tuple[]"},{"components":[{"internalType":"uint32","name":"numerator","type":"uint32"},{"internalType":"uint32","name":"denominator","type":"uint32"},{"internalType":"uint32","name":"amount","type":"uint32"},{"internalType":"address","name":"tokenId","type":"address"},{"internalType":"bool","name":"useHbarsForPayment","type":"bool"},{"internalType":"address","name":"feeCollector","type":"address"}],"internalType":"struct IHederaTokenService.RoyaltyFee[]","name":"royaltyFees","type":"tuple[]"}],"name":"createNonFungibleTokenWithCustomFees","outputs":[{"internalType":"int64","name":"responseCode","type":"int64"},{"internalType":"address","name":"tokenAddress","type":"address"}],"stateMutability":"payable","type":"function"},{"inputs":[{"components":[{"internalType":"address","name":"token","type":"address"},{"components":[{"internalType":"address","name":"accountID","type":"address"},{"internalType":"int64","name":"amount","type":"int64"}],"internalType":"struct IHederaTokenService.AccountAmount[]","name":"transfers","type":"tuple[]"},{"components":[{"internalType":"address","name":"senderAccountID","type":"address"},{"internalType":"address","name":"receiverAccountID","type":"address"},{"internalType":"int64","name":"serialNumber","type":"int64"}],"internalType":"struct IHederaTokenService.NftTransfer[]","name":"nftTransfers","type":"tuple[]"}],"internalType":"struct IHederaTokenService.TokenTransferList[]","name":"tokenTransfers","type":"tuple[]"}],"name":"cryptoTransfer","outputs":[{"internalType":"int64","name":"responseCode","type":"int64"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"token","type":"address"}],"name":"deleteToken","outputs":[{"internalType":"int64","name":"responseCode","type":"int64"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"account","type":"address"},{"internalType":"address","name":"token","type":"address"}],"name":"dissociateToken","outputs":[{"internalType":"int64","name":"responseCode","type":"int64"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"account","type":"address"},{"internalType":"address[]","name":"tokens","type":"address[]"}],"name":"dissociateTokens","outputs":[{"internalType":"int64","name":"responseCode","type":"int64"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"token","type":"address"},{"internalType":"address","name":"account","type":"address"}],"name":"freezeToken","outputs":[{"internalType":"int64","name":"responseCode","type":"int64"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"token","type":"address"},{"internalType":"int64","name":"serialNumber","type":"int64"}],"name":"getApproved","outputs":[{"internalType":"int64","name":"responseCode","type":"int64"},{"internalType":"address","name":"approved","type":"address"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"token","type":"address"}],"name":"getFungibleTokenInfo","outputs":[{"internalType":"int64","name":"responseCode","type":"int64"},{"components":[{"components":[{"components":[{"internalType":"string","name":"name","type":"string"},{"internalType":"string","name":"symbol","type":"string"},{"internalType":"address","name":"treasury","type":"address"},{"internalType":"string","name":"memo","type":"string"},{"internalType":"bool","name":"tokenSupplyType","type":"bool"},{"internalType":"uint32","name":"maxSupply","type":"uint32"},{"internalType":"bool","name":"freezeDefault","type":"bool"},{"components":[{"internalType":"uint256","name":"keyType","type":"uint256"},{"components":[{"internalType":"bool","name":"inheritAccountKey","type":"bool"},{"internalType":"address","name":"contractId","type":"address"},{"internalType":"bytes","name":"ed25519","type":"bytes"},{"internalType":"bytes","name":"ECDSA_secp256k1","type":"bytes"},{"internalType":"address","name":"delegatableContractId","type":"address"}],"internalType":"struct IHederaTokenService.KeyValue","name":"key","type":"tuple"}],"internalType":"struct IHederaTokenService.TokenKey[]","name":"tokenKeys","type":"tuple[]"},{"components":[{"internalType":"uint32","name":"second","type":"uint32"},{"internalType":"address","name":"autoRenewAccount","type":"address"},{"internalType":"uint32","name":"autoRenewPeriod","type":"uint32"}],"internalType":"struct IHederaTokenService.Expiry","name":"expiry","type":"tuple"}],"internalType":"struct IHederaTokenService.HederaToken","name":"token","type":"tuple"},{"internalType":"int64","name":"totalSupply","type":"int64"},{"internalType":"bool","name":"deleted","type":"bool"},{"internalType":"bool","name":"defaultKycStatus","type":"bool"},{"internalType":"bool","name":"pauseStatus","type":"bool"},{"components":[{"internalType":"uint32","name":"amount","type":"uint32"},{"internalType":"address","name":"tokenId","type":"address"},{"internalType":"bool","name":"useHbarsForPayment","type":"bool"},{"internalType":"bool","name":"useCurrentTokenForPayment","type":"bool"},{"internalType":"address","name":"feeCollector","type":"address"}],"internalType":"struct IHederaTokenService.FixedFee[]","name":"fixedFees","type":"tuple[]"},{"components":[{"internalType":"uint32","name":"numerator","type":"uint32"},{"internalType":"uint32","name":"denominator","type":"uint32"},{"internalType":"uint32","name":"minimumAmount","type":"uint32"},{"internalType":"uint32","name":"maximumAmount","type":"uint32"},{"internalType":"bool","name":"netOfTransfers","type":"bool"},{"internalType":"address","name":"feeCollector","type":"address"}],"internalType":"struct IHederaTokenService.FractionalFee[]","name":"fractionalFees","type":"tuple[]"},{"components":[{"internalType":"uint32","name":"numerator","type":"uint32"},{"internalType":"uint32","name":"denominator","type":"uint32"},{"internalType":"uint32","name":"amount","type":"uint32"},{"internalType":"address","name":"tokenId","type":"address"},{"internalType":"bool","name":"useHbarsForPayment","type":"bool"},{"internalType":"address","name":"feeCollector","type":"address"}],"internalType":"struct IHederaTokenService.RoyaltyFee[]","name":"royaltyFees","type":"tuple[]"},{"internalType":"string","name":"ledgerId","type":"string"}],"internalType":"struct IHederaTokenService.TokenInfo","name":"tokenInfo","type":"tuple"},{"internalType":"uint32","name":"decimals","type":"uint32"}],"internalType":"struct IHederaTokenService.FungibleTokenInfo","name":"fungibleTokenInfo","type":"tuple"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"token","type":"address"},{"internalType":"int64","name":"serialNumber","type":"int64"}],"name":"getNonFungibleTokenInfo","outputs":[{"internalType":"int64","name":"responseCode","type":"int64"},{"components":[{"components":[{"components":[{"internalType":"string","name":"name","type":"string"},{"internalType":"string","name":"symbol","type":"string"},{"internalType":"address","name":"treasury","type":"address"},{"internalType":"string","name":"memo","type":"string"},{"internalType":"bool","name":"tokenSupplyType","type":"bool"},{"internalType":"uint32","name":"maxSupply","type":"uint32"},{"internalType":"bool","name":"freezeDefault","type":"bool"},{"components":[{"internalType":"uint256","name":"keyType","type":"uint256"},{"components":[{"internalType":"bool","name":"inheritAccountKey","type":"bool"},{"internalType":"address","name":"contractId","type":"address"},{"internalType":"bytes","name":"ed25519","type":"bytes"},{"internalType":"bytes","name":"ECDSA_secp256k1","type":"bytes"},{"internalType":"address","name":"delegatableContractId","type":"address"}],"internalType":"struct IHederaTokenService.KeyValue","name":"key","type":"tuple"}],"internalType":"struct IHederaTokenService.TokenKey[]","name":"tokenKeys","type":"tuple[]"},{"components":[{"internalType":"uint32","name":"second","type":"uint32"},{"internalType":"address","name":"autoRenewAccount","type":"address"},{"internalType":"uint32","name":"autoRenewPeriod","type":"uint32"}],"internalType":"struct IHederaTokenService.Expiry","name":"expiry","type":"tuple"}],"internalType":"struct IHederaTokenService.HederaToken","name":"token","type":"tuple"},{"internalType":"int64","name":"totalSupply","type":"int64"},{"internalType":"bool","name":"deleted","type":"bool"},{"internalType":"bool","name":"defaultKycStatus","type":"bool"},{"internalType":"bool","name":"pauseStatus","type":"bool"},{"components":[{"internalType":"uint32","name":"amount","type":"uint32"},{"internalType":"address","name":"tokenId","type":"address"},{"internalType":"bool","name":"useHbarsForPayment","type":"bool"},{"internalType":"bool","name":"useCurrentTokenForPayment","type":"bool"},{"internalType":"address","name":"feeCollector","type":"address"}],"internalType":"struct IHederaTokenService.FixedFee[]","name":"fixedFees","type":"tuple[]"},{"components":[{"internalType":"uint32","name":"numerator","type":"uint32"},{"internalType":"uint32","name":"denominator","type":"uint32"},{"internalType":"uint32","name":"minimumAmount","type":"uint32"},{"internalType":"uint32","name":"maximumAmount","type":"uint32"},{"internalType":"bool","name":"netOfTransfers","type":"bool"},{"internalType":"address","name":"feeCollector","type":"address"}],"internalType":"struct IHederaTokenService.FractionalFee[]","name":"fractionalFees","type":"tuple[]"},{"components":[{"internalType":"uint32","name":"numerator","type":"uint32"},{"internalType":"uint32","name":"denominator","type":"uint32"},{"internalType":"uint32","name":"amount","type":"uint32"},{"internalType":"address","name":"tokenId","type":"address"},{"internalType":"bool","name":"useHbarsForPayment","type":"bool"},{"internalType":"address","name":"feeCollector","type":"address"}],"internalType":"struct IHederaTokenService.RoyaltyFee[]","name":"royaltyFees","type":"tuple[]"},{"internalType":"string","name":"ledgerId","type":"string"}],"internalType":"struct IHederaTokenService.TokenInfo","name":"tokenInfo","type":"tuple"},{"internalType":"int64","name":"serialNumber","type":"int64"},{"internalType":"address","name":"ownerId","type":"address"},{"internalType":"int64","name":"creationTime","type":"int64"},{"internalType":"bytes","name":"metadata","type":"bytes"},{"internalType":"address","name":"spenderId","type":"address"}],"internalType":"struct IHederaTokenService.NonFungibleTokenInfo","name":"nonFungibleTokenInfo","type":"tuple"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"token","type":"address"}],"name":"getTokenCustomFees","outputs":[{"internalType":"int64","name":"responseCode","type":"int64"},{"components":[{"internalType":"uint32","name":"amount","type":"uint32"},{"internalType":"address","name":"tokenId","type":"address"},{"internalType":"bool","name":"useHbarsForPayment","type":"bool"},{"internalType":"bool","name":"useCurrentTokenForPayment","type":"bool"},{"internalType":"address","name":"feeCollector","type":"address"}],"internalType":"struct IHederaTokenService.FixedFee[]","name":"fixedFees","type":"tuple[]"},{"components":[{"internalType":"uint32","name":"numerator","type":"uint32"},{"internalType":"uint32","name":"denominator","type":"uint32"},{"internalType":"uint32","name":"minimumAmount","type":"uint32"},{"internalType":"uint32","name":"maximumAmount","type":"uint32"},{"internalType":"bool","name":"netOfTransfers","type":"bool"},{"internalType":"address","name":"feeCollector","type":"address"}],"internalType":"struct IHederaTokenService.FractionalFee[]","name":"fractionalFees","type":"tuple[]"},{"components":[{"internalType":"uint32","name":"numerator","type":"uint32"},{"internalType":"uint32","name":"denominator","type":"uint32"},{"internalType":"uint32","name":"amount","type":"uint32"},{"internalType":"address","name":"tokenId","type":"address"},{"internalType":"bool","name":"useHbarsForPayment","type":"bool"},{"internalType":"address","name":"feeCollector","type":"address"}],"internalType":"struct IHederaTokenService.RoyaltyFee[]","name":"royaltyFees","type":"tuple[]"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"token","type":"address"}],"name":"getTokenDefaultFreezeStatus","outputs":[{"internalType":"int64","name":"responseCode","type":"int64"},{"internalType":"bool","name":"defaultFreezeStatus","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"token","type":"address"}],"name":"getTokenDefaultKycStatus","outputs":[{"internalType":"int64","name":"responseCode","type":"int64"},{"internalType":"bool","name":"defaultKycStatus","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"token","type":"address"}],"name":"getTokenExpiryInfo","outputs":[{"internalType":"int64","name":"responseCode","type":"int64"},{"components":[{"internalType":"uint32","name":"second","type":"uint32"},{"internalType":"address","name":"autoRenewAccount","type":"address"},{"internalType":"uint32","name":"autoRenewPeriod","type":"uint32"}],"internalType":"struct IHederaTokenService.Expiry","name":"expiry","type":"tuple"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"token","type":"address"}],"name":"getTokenInfo","outputs":[{"internalType":"int64","name":"responseCode","type":"int64"},{"components":[{"components":[{"internalType":"string","name":"name","type":"string"},{"internalType":"string","name":"symbol","type":"string"},{"internalType":"address","name":"treasury","type":"address"},{"internalType":"string","name":"memo","type":"string"},{"internalType":"bool","name":"tokenSupplyType","type":"bool"},{"internalType":"uint32","name":"maxSupply","type":"uint32"},{"internalType":"bool","name":"freezeDefault","type":"bool"},{"components":[{"internalType":"uint256","name":"keyType","type":"uint256"},{"components":[{"internalType":"bool","name":"inheritAccountKey","type":"bool"},{"internalType":"address","name":"contractId","type":"address"},{"internalType":"bytes","name":"ed25519","type":"bytes"},{"internalType":"bytes","name":"ECDSA_secp256k1","type":"bytes"},{"internalType":"address","name":"delegatableContractId","type":"address"}],"internalType":"struct IHederaTokenService.KeyValue","name":"key","type":"tuple"}],"internalType":"struct IHederaTokenService.TokenKey[]","name":"tokenKeys","type":"tuple[]"},{"components":[{"internalType":"uint32","name":"second","type":"uint32"},{"internalType":"address","name":"autoRenewAccount","type":"address"},{"internalType":"uint32","name":"autoRenewPeriod","type":"uint32"}],"internalType":"struct IHederaTokenService.Expiry","name":"expiry","type":"tuple"}],"internalType":"struct IHederaTokenService.HederaToken","name":"token","type":"tuple"},{"internalType":"int64","name":"totalSupply","type":"int64"},{"internalType":"bool","name":"deleted","type":"bool"},{"internalType":"bool","name":"defaultKycStatus","type":"bool"},{"internalType":"bool","name":"pauseStatus","type":"bool"},{"components":[{"internalType":"uint32","name":"amount","type":"uint32"},{"internalType":"address","name":"tokenId","type":"address"},{"internalType":"bool","name":"useHbarsForPayment","type":"bool"},{"internalType":"bool","name":"useCurrentTokenForPayment","type":"bool"},{"internalType":"address","name":"feeCollector","type":"address"}],"internalType":"struct IHederaTokenService.FixedFee[]","name":"fixedFees","type":"tuple[]"},{"components":[{"internalType":"uint32","name":"numerator","type":"uint32"},{"internalType":"uint32","name":"denominator","type":"uint32"},{"internalType":"uint32","name":"minimumAmount","type":"uint32"},{"internalType":"uint32","name":"maximumAmount","type":"uint32"},{"internalType":"bool","name":"netOfTransfers","type":"bool"},{"internalType":"address","name":"feeCollector","type":"address"}],"internalType":"struct IHederaTokenService.FractionalFee[]","name":"fractionalFees","type":"tuple[]"},{"components":[{"internalType":"uint32","name":"numerator","type":"uint32"},{"internalType":"uint32","name":"denominator","type":"uint32"},{"internalType":"uint32","name":"amount","type":"uint32"},{"internalType":"address","name":"tokenId","type":"address"},{"internalType":"bool","name":"useHbarsForPayment","type":"bool"},{"internalType":"address","name":"feeCollector","type":"address"}],"internalType":"struct IHederaTokenService.RoyaltyFee[]","name":"royaltyFees","type":"tuple[]"},{"internalType":"string","name":"ledgerId","type":"string"}],"internalType":"struct IHederaTokenService.TokenInfo","name":"tokenInfo","type":"tuple"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"token","type":"address"},{"internalType":"uint256","name":"keyType","type":"uint256"}],"name":"getTokenKey","outputs":[{"internalType":"int64","name":"responseCode","type":"int64"},{"components":[{"internalType":"bool","name":"inheritAccountKey","type":"bool"},{"internalType":"address","name":"contractId","type":"address"},{"internalType":"bytes","name":"ed25519","type":"bytes"},{"internalType":"bytes","name":"ECDSA_secp256k1","type":"bytes"},{"internalType":"address","name":"delegatableContractId","type":"address"}],"internalType":"struct IHederaTokenService.KeyValue","name":"key","type":"tuple"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"token","type":"address"}],"name":"getTokenType","outputs":[{"internalType":"int64","name":"responseCode","type":"int64"},{"internalType":"int32","name":"tokenType","type":"int32"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"token","type":"address"},{"internalType":"address","name":"account","type":"address"}],"name":"grantTokenKyc","outputs":[{"internalType":"int64","name":"responseCode","type":"int64"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"token","type":"address"},{"internalType":"address","name":"owner","type":"address"},{"internalType":"address","name":"operator","type":"address"}],"name":"isApprovedForAll","outputs":[{"internalType":"int64","name":"responseCode","type":"int64"},{"internalType":"bool","name":"approved","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"token","type":"address"},{"internalType":"address","name":"account","type":"address"}],"name":"isFrozen","outputs":[{"internalType":"int64","name":"responseCode","type":"int64"},{"internalType":"bool","name":"frozen","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"token","type":"address"},{"internalType":"address","name":"account","type":"address"}],"name":"isKyc","outputs":[{"internalType":"int64","name":"responseCode","type":"int64"},{"internalType":"bool","name":"kycGranted","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"token","type":"address"}],"name":"isToken","outputs":[{"internalType":"int64","name":"responseCode","type":"int64"},{"internalType":"bool","name":"isToken","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"token","type":"address"},{"internalType":"uint64","name":"amount","type":"uint64"},{"internalType":"bytes[]","name":"metadata","type":"bytes[]"}],"name":"mintToken","outputs":[{"internalType":"int64","name":"responseCode","type":"int64"},{"internalType":"uint64","name":"newTotalSupply","type":"uint64"},{"internalType":"int64[]","name":"serialNumbers","type":"int64[]"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"token","type":"address"}],"name":"pauseToken","outputs":[{"internalType":"int64","name":"responseCode","type":"int64"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"token","type":"address"},{"internalType":"bytes","name":"data","type":"bytes"}],"name":"redirectForToken","outputs":[{"internalType":"int256","name":"responseCode","type":"int256"},{"internalType":"bytes","name":"result","type":"bytes"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"token","type":"address"},{"internalType":"address","name":"account","type":"address"}],"name":"revokeTokenKyc","outputs":[{"internalType":"int64","name":"responseCode","type":"int64"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"token","type":"address"},{"internalType":"address","name":"operator","type":"address"},{"internalType":"bool","name":"approved","type":"bool"}],"name":"setApprovalForAll","outputs":[{"internalType":"int64","name":"responseCode","type":"int64"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"token","type":"address"},{"internalType":"address","name":"sender","type":"address"},{"internalType":"address","name":"recipient","type":"address"},{"internalType":"int64","name":"serialNumber","type":"int64"}],"name":"transferNFT","outputs":[{"internalType":"int64","name":"responseCode","type":"int64"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"token","type":"address"},{"internalType":"address[]","name":"sender","type":"address[]"},{"internalType":"address[]","name":"receiver","type":"address[]"},{"internalType":"int64[]","name":"serialNumber","type":"int64[]"}],"name":"transferNFTs","outputs":[{"internalType":"int64","name":"responseCode","type":"int64"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"token","type":"address"},{"internalType":"address","name":"sender","type":"address"},{"internalType":"address","name":"recipient","type":"address"},{"internalType":"int64","name":"amount","type":"int64"}],"name":"transferToken","outputs":[{"internalType":"int64","name":"responseCode","type":"int64"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"token","type":"address"},{"internalType":"address[]","name":"accountId","type":"address[]"},{"internalType":"int64[]","name":"amount","type":"int64[]"}],"name":"transferTokens","outputs":[{"internalType":"int64","name":"responseCode","type":"int64"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"token","type":"address"},{"internalType":"address","name":"account","type":"address"}],"name":"unfreezeToken","outputs":[{"internalType":"int64","name":"responseCode","type":"int64"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"token","type":"address"}],"name":"unpauseToken","outputs":[{"internalType":"int64","name":"responseCode","type":"int64"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"token","type":"address"},{"components":[{"internalType":"uint32","name":"second","type":"uint32"},{"internalType":"address","name":"autoRenewAccount","type":"address"},{"internalType":"uint32","name":"autoRenewPeriod","type":"uint32"}],"internalType":"struct IHederaTokenService.Expiry","name":"expiryInfo","type":"tuple"}],"name":"updateTokenExpiryInfo","outputs":[{"internalType":"int64","name":"responseCode","type":"int64"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"token","type":"address"},{"components":[{"internalType":"string","name":"name","type":"string"},{"internalType":"string","name":"symbol","type":"string"},{"internalType":"address","name":"treasury","type":"address"},{"internalType":"string","name":"memo","type":"string"},{"internalType":"bool","name":"tokenSupplyType","type":"bool"},{"internalType":"uint32","name":"maxSupply","type":"uint32"},{"internalType":"bool","name":"freezeDefault","type":"bool"},{"components":[{"internalType":"uint256","name":"keyType","type":"uint256"},{"components":[{"internalType":"bool","name":"inheritAccountKey","type":"bool"},{"internalType":"address","name":"contractId","type":"address"},{"internalType":"bytes","name":"ed25519","type":"bytes"},{"internalType":"bytes","name":"ECDSA_secp256k1","type":"bytes"},{"internalType":"address","name":"delegatableContractId","type":"address"}],"internalType":"struct IHederaTokenService.KeyValue","name":"key","type":"tuple"}],"internalType":"struct IHederaTokenService.TokenKey[]","name":"tokenKeys","type":"tuple[]"},{"components":[{"internalType":"uint32","name":"second","type":"uint32"},{"internalType":"address","name":"autoRenewAccount","type":"address"},{"internalType":"uint32","name":"autoRenewPeriod","type":"uint32"}],"internalType":"struct IHederaTokenService.Expiry","name":"expiry","type":"tuple"}],"internalType":"struct IHederaTokenService.HederaToken","name":"tokenInfo","type":"tuple"}],"name":"updateTokenInfo","outputs":[{"internalType":"int64","name":"responseCode","type":"int64"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"token","type":"address"},{"components":[{"internalType":"uint256","name":"keyType","type":"uint256"},{"components":[{"internalType":"bool","name":"inheritAccountKey","type":"bool"},{"internalType":"address","name":"contractId","type":"address"},{"internalType":"bytes","name":"ed25519","type":"bytes"},{"internalType":"bytes","name":"ECDSA_secp256k1","type":"bytes"},{"internalType":"address","name":"delegatableContractId","type":"address"}],"internalType":"struct IHederaTokenService.KeyValue","name":"key","type":"tuple"}],"internalType":"struct IHederaTokenService.TokenKey[]","name":"keys","type":"tuple[]"}],"name":"updateTokenKeys","outputs":[{"internalType":"int64","name":"responseCode","type":"int64"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"token","type":"address"},{"internalType":"address","name":"account","type":"address"},{"internalType":"uint32","name":"amount","type":"uint32"}],"name":"wipeTokenAccount","outputs":[{"internalType":"int64","name":"responseCode","type":"int64"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"token","type":"address"},{"internalType":"address","name":"account","type":"address"},{"internalType":"int64[]","name":"serialNumbers","type":"int64[]"}],"name":"wipeTokenAccountNFT","outputs":[{"internalType":"int64","name":"responseCode","type":"int64"}],"stateMutability":"nonpayable","type":"function"}] \ No newline at end of file diff --git a/hedera-node/test-clients/src/main/resource/contract/contracts/IHederaTokenService/IHederaTokenService.sol b/hedera-node/test-clients/src/main/resource/contract/contracts/IHederaTokenService/IHederaTokenService.sol new file mode 100644 index 000000000000..cd2fd728882e --- /dev/null +++ b/hedera-node/test-clients/src/main/resource/contract/contracts/IHederaTokenService/IHederaTokenService.sol @@ -0,0 +1,766 @@ +// SPDX-License-Identifier: Apache-2.0 +pragma solidity >=0.4.9 <0.9.0; +pragma experimental ABIEncoderV2; + +interface IHederaTokenService { + + /// Transfers cryptocurrency among two or more accounts by making the desired adjustments to their + /// balances. Each transfer list can specify up to 10 adjustments. Each negative amount is withdrawn + /// from the corresponding account (a sender), and each positive one is added to the corresponding + /// account (a receiver). The amounts list must sum to zero. Each amount is a number of tinybars + /// (there are 100,000,000 tinybars in one hbar). If any sender account fails to have sufficient + /// hbars, then the entire transaction fails, and none of those transfers occur, though the + /// transaction fee is still charged. This transaction must be signed by the keys for all the sending + /// accounts, and for any receiving accounts that have receiverSigRequired == true. The signatures + /// are in the same order as the accounts, skipping those accounts that don't need a signature. + struct AccountAmount { + // The Account ID, as a solidity address, that sends/receives cryptocurrency or tokens + address accountID; + + // The amount of the lowest denomination of the given token that + // the account sends(negative) or receives(positive) + int64 amount; + } + + /// A sender account, a receiver account, and the serial number of an NFT of a Token with + /// NON_FUNGIBLE_UNIQUE type. When minting NFTs the sender will be the default AccountID instance + /// (0.0.0 aka 0x0) and when burning NFTs, the receiver will be the default AccountID instance. + struct NftTransfer { + // The solidity address of the sender + address senderAccountID; + + // The solidity address of the receiver + address receiverAccountID; + + // The serial number of the NFT + int64 serialNumber; + } + + struct TokenTransferList { + // The ID of the token as a solidity address + address token; + + // Applicable to tokens of type FUNGIBLE_COMMON. Multiple list of AccountAmounts, each of which + // has an account and amount. + AccountAmount[] transfers; + + // Applicable to tokens of type NON_FUNGIBLE_UNIQUE. Multiple list of NftTransfers, each of + // which has a sender and receiver account, including the serial number of the NFT + NftTransfer[] nftTransfers; + } + + /// Expiry properties of a Hedera token - second, autoRenewAccount, autoRenewPeriod + struct Expiry { + // The epoch second at which the token should expire; if an auto-renew account and period are + // specified, this is coerced to the current epoch second plus the autoRenewPeriod + uint32 second; + + // ID of an account which will be automatically charged to renew the token's expiration, at + // autoRenewPeriod interval, expressed as a solidity address + address autoRenewAccount; + + // The interval at which the auto-renew account will be charged to extend the token's expiry + uint32 autoRenewPeriod; + } + + /// A Key can be a public key from either the Ed25519 or ECDSA(secp256k1) signature schemes, where + /// in the ECDSA(secp256k1) case we require the 33-byte compressed form of the public key. We call + /// these public keys primitive keys. + /// A Key can also be the ID of a smart contract instance, which is then authorized to perform any + /// precompiled contract action that requires this key to sign. + /// Note that when a Key is a smart contract ID, it doesn't mean the contract with that ID + /// will actually create a cryptographic signature. It only means that when the contract calls a + /// precompiled contract, the resulting "child transaction" will be authorized to perform any action + /// controlled by the Key. + /// Exactly one of the possible values should be populated in order for the Key to be valid. + struct KeyValue { + + // if set to true, the key of the calling Hedera account will be inherited as the token key + bool inheritAccountKey; + + // smart contract instance that is authorized as if it had signed with a key + address contractId; + + // Ed25519 public key bytes + bytes ed25519; + + // Compressed ECDSA(secp256k1) public key bytes + bytes ECDSA_secp256k1; + + // A smart contract that, if the recipient of the active message frame, should be treated + // as having signed. (Note this does not mean the code being executed in the frame + // will belong to the given contract, since it could be running another contract's code via + // delegatecall. So setting this key is a more permissive version of setting the + // contractID key, which also requires the code in the active message frame belong to the + // the contract with the given id.) + address delegatableContractId; + } + + /// A list of token key types the key should be applied to and the value of the key + struct TokenKey { + + // bit field representing the key type. Keys of all types that have corresponding bits set to 1 + // will be created for the token. + // 0th bit: adminKey + // 1st bit: kycKey + // 2nd bit: freezeKey + // 3rd bit: wipeKey + // 4th bit: supplyKey + // 5th bit: feeScheduleKey + // 6th bit: pauseKey + // 7th bit: ignored + uint keyType; + + // the value that will be set to the key type + KeyValue key; + } + + /// Basic properties of a Hedera Token - name, symbol, memo, tokenSupplyType, maxSupply, + /// treasury, freezeDefault. These properties are related both to Fungible and NFT token types. + struct HederaToken { + // The publicly visible name of the token. The token name is specified as a Unicode string. + // Its UTF-8 encoding cannot exceed 100 bytes, and cannot contain the 0 byte (NUL). + string name; + + // The publicly visible token symbol. The token symbol is specified as a Unicode string. + // Its UTF-8 encoding cannot exceed 100 bytes, and cannot contain the 0 byte (NUL). + string symbol; + + // The ID of the account which will act as a treasury for the token as a solidity address. + // This account will receive the specified initial supply or the newly minted NFTs in + // the case for NON_FUNGIBLE_UNIQUE Type + address treasury; + + // The memo associated with the token (UTF-8 encoding max 100 bytes) + string memo; + + // IWA compatibility. Specified the token supply type. Defaults to INFINITE + bool tokenSupplyType; + + // IWA Compatibility. Depends on TokenSupplyType. For tokens of type FUNGIBLE_COMMON - the + // maximum number of tokens that can be in circulation. For tokens of type NON_FUNGIBLE_UNIQUE - + // the maximum number of NFTs (serial numbers) that can be minted. This field can never be changed! + uint32 maxSupply; + + // The default Freeze status (frozen or unfrozen) of Hedera accounts relative to this token. If + // true, an account must be unfrozen before it can receive the token + bool freezeDefault; + + // list of keys to set to the token + TokenKey[] tokenKeys; + + // expiry properties of a Hedera token - second, autoRenewAccount, autoRenewPeriod + Expiry expiry; + } + + /// Additional post creation fungible and non fungible properties of a Hedera Token. + struct TokenInfo { + /// Basic properties of a Hedera Token + HederaToken token; + + /// The number of tokens (fungible) or serials (non-fungible) of the token + int64 totalSupply; + + /// Specifies whether the token is deleted or not + bool deleted; + + /// Specifies whether the token kyc was defaulted with KycNotApplicable (true) or Revoked (false) + bool defaultKycStatus; + + /// Specifies whether the token is currently paused or not + bool pauseStatus; + + /// The fixed fees collected when transferring the token + FixedFee[] fixedFees; + + /// The fractional fees collected when transferring the token + FractionalFee[] fractionalFees; + + /// The royalty fees collected when transferring the token + RoyaltyFee[] royaltyFees; + + /// The ID of the network ledger + string ledgerId; + } + + /// Additional fungible properties of a Hedera Token. + struct FungibleTokenInfo { + /// The shared hedera token info + TokenInfo tokenInfo; + + /// The number of decimal places a token is divisible by + uint32 decimals; + } + + /// Additional non fungible properties of a Hedera Token. + struct NonFungibleTokenInfo { + /// The shared hedera token info + TokenInfo tokenInfo; + + /// The serial number of the nft + int64 serialNumber; + + /// The account id specifying the owner of the non fungible token + address ownerId; + + /// The epoch second at which the token was created. + int64 creationTime; + + /// The unique metadata of the NFT + bytes metadata; + + /// The account id specifying an account that has been granted spending permissions on this nft + address spenderId; + } + + /// A fixed number of units (hbar or token) to assess as a fee during a transfer of + /// units of the token to which this fixed fee is attached. The denomination of + /// the fee depends on the values of tokenId, useHbarsForPayment and + /// useCurrentTokenForPayment. Exactly one of the values should be set. + struct FixedFee { + + uint32 amount; + + // Specifies ID of token that should be used for fixed fee denomination + address tokenId; + + // Specifies this fixed fee should be denominated in Hbar + bool useHbarsForPayment; + + // Specifies this fixed fee should be denominated in the Token currently being created + bool useCurrentTokenForPayment; + + // The ID of the account to receive the custom fee, expressed as a solidity address + address feeCollector; + } + + /// A fraction of the transferred units of a token to assess as a fee. The amount assessed will never + /// be less than the given minimumAmount, and never greater than the given maximumAmount. The + /// denomination is always units of the token to which this fractional fee is attached. + struct FractionalFee { + // A rational number's numerator, used to set the amount of a value transfer to collect as a custom fee + uint32 numerator; + + // A rational number's denominator, used to set the amount of a value transfer to collect as a custom fee + uint32 denominator; + + // The minimum amount to assess + uint32 minimumAmount; + + // The maximum amount to assess (zero implies no maximum) + uint32 maximumAmount; + bool netOfTransfers; + + // The ID of the account to receive the custom fee, expressed as a solidity address + address feeCollector; + } + + /// A fee to assess during a transfer that changes ownership of an NFT. Defines the fraction of + /// the fungible value exchanged for an NFT that the ledger should collect as a royalty. ("Fungible + /// value" includes both â„ and units of fungible HTS tokens.) When the NFT sender does not receive + /// any fungible value, the ledger will assess the fallback fee, if present, to the new NFT owner. + /// Royalty fees can only be added to tokens of type type NON_FUNGIBLE_UNIQUE. + struct RoyaltyFee { + // A fraction's numerator of fungible value exchanged for an NFT to collect as royalty + uint32 numerator; + + // A fraction's denominator of fungible value exchanged for an NFT to collect as royalty + uint32 denominator; + + // If present, the fee to assess to the NFT receiver when no fungible value + // is exchanged with the sender. Consists of: + // amount: the amount to charge for the fee + // tokenId: Specifies ID of token that should be used for fixed fee denomination + // useHbarsForPayment: Specifies this fee should be denominated in Hbar + uint32 amount; + address tokenId; + bool useHbarsForPayment; + + // The ID of the account to receive the custom fee, expressed as a solidity address + address feeCollector; + } + + /********************** + * Direct HTS Calls * + **********************/ + + /// Initiates a Token Transfer + /// @param tokenTransfers the list of transfers to do + /// @return responseCode The response code for the status of the request. SUCCESS is 22. + function cryptoTransfer(TokenTransferList[] memory tokenTransfers) + external + returns (int64 responseCode); + + /// Mints an amount of the token to the defined treasury account + /// @param token The token for which to mint tokens. If token does not exist, transaction results in + /// INVALID_TOKEN_ID + /// @param amount Applicable to tokens of type FUNGIBLE_COMMON. The amount to mint to the Treasury Account. + /// Amount must be a positive non-zero number represented in the lowest denomination of the + /// token. The new supply must be lower than 2^63. + /// @param metadata Applicable to tokens of type NON_FUNGIBLE_UNIQUE. A list of metadata that are being created. + /// Maximum allowed size of each metadata is 100 bytes + /// @return responseCode The response code for the status of the request. SUCCESS is 22. + /// @return newTotalSupply The new supply of tokens. For NFTs it is the total count of NFTs + /// @return serialNumbers If the token is an NFT the newly generate serial numbers, othersise empty. + function mintToken( + address token, + uint64 amount, + bytes[] memory metadata + ) + external + returns ( + int64 responseCode, + uint64 newTotalSupply, + int64[] memory serialNumbers + ); + + /// Burns an amount of the token from the defined treasury account + /// @param token The token for which to burn tokens. If token does not exist, transaction results in + /// INVALID_TOKEN_ID + /// @param amount Applicable to tokens of type FUNGIBLE_COMMON. The amount to burn from the Treasury Account. + /// Amount must be a positive non-zero number, not bigger than the token balance of the treasury + /// account (0; balance], represented in the lowest denomination. + /// @param serialNumbers Applicable to tokens of type NON_FUNGIBLE_UNIQUE. The list of serial numbers to be burned. + /// @return responseCode The response code for the status of the request. SUCCESS is 22. + /// @return newTotalSupply The new supply of tokens. For NFTs it is the total count of NFTs + function burnToken( + address token, + uint64 amount, + int64[] memory serialNumbers + ) external returns (int64 responseCode, uint64 newTotalSupply); + + /// Associates the provided account with the provided tokens. Must be signed by the provided + /// Account's key or called from the accounts contract key + /// If the provided account is not found, the transaction will resolve to INVALID_ACCOUNT_ID. + /// If the provided account has been deleted, the transaction will resolve to ACCOUNT_DELETED. + /// If any of the provided tokens is not found, the transaction will resolve to INVALID_TOKEN_REF. + /// If any of the provided tokens has been deleted, the transaction will resolve to TOKEN_WAS_DELETED. + /// If an association between the provided account and any of the tokens already exists, the + /// transaction will resolve to TOKEN_ALREADY_ASSOCIATED_TO_ACCOUNT. + /// If the provided account's associations count exceed the constraint of maximum token associations + /// per account, the transaction will resolve to TOKENS_PER_ACCOUNT_LIMIT_EXCEEDED. + /// On success, associations between the provided account and tokens are made and the account is + /// ready to interact with the tokens. + /// @param account The account to be associated with the provided tokens + /// @param tokens The tokens to be associated with the provided account. In the case of NON_FUNGIBLE_UNIQUE + /// Type, once an account is associated, it can hold any number of NFTs (serial numbers) of that + /// token type + /// @return responseCode The response code for the status of the request. SUCCESS is 22. + function associateTokens(address account, address[] memory tokens) + external + returns (int64 responseCode); + + /// Single-token variant of associateTokens. Will be mapped to a single entry array call of associateTokens + /// @param account The account to be associated with the provided token + /// @param token The token to be associated with the provided account + function associateToken(address account, address token) + external + returns (int64 responseCode); + + /// Dissociates the provided account with the provided tokens. Must be signed by the provided + /// Account's key. + /// If the provided account is not found, the transaction will resolve to INVALID_ACCOUNT_ID. + /// If the provided account has been deleted, the transaction will resolve to ACCOUNT_DELETED. + /// If any of the provided tokens is not found, the transaction will resolve to INVALID_TOKEN_REF. + /// If any of the provided tokens has been deleted, the transaction will resolve to TOKEN_WAS_DELETED. + /// If an association between the provided account and any of the tokens does not exist, the + /// transaction will resolve to TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. + /// If a token has not been deleted and has not expired, and the user has a nonzero balance, the + /// transaction will resolve to TRANSACTION_REQUIRES_ZERO_TOKEN_BALANCES. + /// If a fungible token has expired, the user can disassociate even if their token balance is + /// not zero. + /// If a non fungible token has expired, the user can not disassociate if their token + /// balance is not zero. The transaction will resolve to TRANSACTION_REQUIRED_ZERO_TOKEN_BALANCES. + /// On success, associations between the provided account and tokens are removed. + /// @param account The account to be dissociated from the provided tokens + /// @param tokens The tokens to be dissociated from the provided account. + /// @return responseCode The response code for the status of the request. SUCCESS is 22. + function dissociateTokens(address account, address[] memory tokens) + external + returns (int64 responseCode); + + /// Single-token variant of dissociateTokens. Will be mapped to a single entry array call of dissociateTokens + /// @param account The account to be associated with the provided token + /// @param token The token to be associated with the provided account + function dissociateToken(address account, address token) + external + returns (int64 responseCode); + + /// Creates a Fungible Token with the specified properties + /// @param token the basic properties of the token being created + /// @param initialTotalSupply Specifies the initial supply of tokens to be put in circulation. The + /// initial supply is sent to the Treasury Account. The supply is in the lowest denomination possible. + /// @param decimals the number of decimal places a token is divisible by + /// @return responseCode The response code for the status of the request. SUCCESS is 22. + /// @return tokenAddress the created token's address + function createFungibleToken( + HederaToken memory token, + uint initialTotalSupply, + uint decimals + ) external payable returns (int64 responseCode, address tokenAddress); + + /// Creates a Fungible Token with the specified properties + /// @param token the basic properties of the token being created + /// @param initialTotalSupply Specifies the initial supply of tokens to be put in circulation. The + /// initial supply is sent to the Treasury Account. The supply is in the lowest denomination possible. + /// @param decimals the number of decimal places a token is divisible by. + /// @param fixedFees list of fixed fees to apply to the token + /// @param fractionalFees list of fractional fees to apply to the token + /// @return responseCode The response code for the status of the request. SUCCESS is 22. + /// @return tokenAddress the created token's address + function createFungibleTokenWithCustomFees( + HederaToken memory token, + uint initialTotalSupply, + uint decimals, + FixedFee[] memory fixedFees, + FractionalFee[] memory fractionalFees + ) external payable returns (int64 responseCode, address tokenAddress); + + /// Creates an Non Fungible Unique Token with the specified properties + /// @param token the basic properties of the token being created + /// @return responseCode The response code for the status of the request. SUCCESS is 22. + /// @return tokenAddress the created token's address + function createNonFungibleToken(HederaToken memory token) + external + payable + returns (int64 responseCode, address tokenAddress); + + /// Creates an Non Fungible Unique Token with the specified properties + /// @param token the basic properties of the token being created + /// @param fixedFees list of fixed fees to apply to the token + /// @param royaltyFees list of royalty fees to apply to the token + /// @return responseCode The response code for the status of the request. SUCCESS is 22. + /// @return tokenAddress the created token's address + function createNonFungibleTokenWithCustomFees( + HederaToken memory token, + FixedFee[] memory fixedFees, + RoyaltyFee[] memory royaltyFees + ) external payable returns (int64 responseCode, address tokenAddress); + + /********************** + * ABIV1 calls * + **********************/ + + /// Initiates a Fungible Token Transfer + /// @param token The ID of the token as a solidity address + /// @param accountId account to do a transfer to/from + /// @param amount The amount from the accountId at the same index + function transferTokens( + address token, + address[] memory accountId, + int64[] memory amount + ) external returns (int64 responseCode); + + /// Initiates a Non-Fungable Token Transfer + /// @param token The ID of the token as a solidity address + /// @param sender the sender of an nft + /// @param receiver the receiver of the nft sent by the same index at sender + /// @param serialNumber the serial number of the nft sent by the same index at sender + function transferNFTs( + address token, + address[] memory sender, + address[] memory receiver, + int64[] memory serialNumber + ) external returns (int64 responseCode); + + /// Transfers tokens where the calling account/contract is implicitly the first entry in the token transfer list, + /// where the amount is the value needed to zero balance the transfers. Regular signing rules apply for sending + /// (positive amount) or receiving (negative amount) + /// @param token The token to transfer to/from + /// @param sender The sender for the transaction + /// @param recipient The receiver of the transaction + /// @param amount Non-negative value to send. a negative value will result in a failure. + function transferToken( + address token, + address sender, + address recipient, + int64 amount + ) external returns (int64 responseCode); + + /// Transfers tokens where the calling account/contract is implicitly the first entry in the token transfer list, + /// where the amount is the value needed to zero balance the transfers. Regular signing rules apply for sending + /// (positive amount) or receiving (negative amount) + /// @param token The token to transfer to/from + /// @param sender The sender for the transaction + /// @param recipient The receiver of the transaction + /// @param serialNumber The serial number of the NFT to transfer. + function transferNFT( + address token, + address sender, + address recipient, + int64 serialNumber + ) external returns (int64 responseCode); + + /// Allows spender to withdraw from your account multiple times, up to the value amount. If this function is called + /// again it overwrites the current allowance with value. + /// Only Applicable to Fungible Tokens + /// @param token The hedera token address to approve + /// @param spender the account address authorized to spend + /// @param amount the amount of tokens authorized to spend. + /// @return responseCode The response code for the status of the request. SUCCESS is 22. + function approve( + address token, + address spender, + uint256 amount + ) external returns (int64 responseCode); + + /// Returns the amount which spender is still allowed to withdraw from owner. + /// Only Applicable to Fungible Tokens + /// @param token The Hedera token address to check the allowance of + /// @param owner the owner of the tokens to be spent + /// @param spender the spender of the tokens + /// @return responseCode The response code for the status of the request. SUCCESS is 22. + /// @return allowance The amount which spender is still allowed to withdraw from owner. + function allowance( + address token, + address owner, + address spender + ) external returns (int64 responseCode, uint256 allowance); + + /// Allow or reaffirm the approved address to transfer an NFT the approved address does not own. + /// Only Applicable to NFT Tokens + /// @param token The Hedera NFT token address to approve + /// @param approved The new approved NFT controller. To revoke approvals pass in the zero address. + /// @param serialNumber The NFT serial number to approve + /// @return responseCode The response code for the status of the request. SUCCESS is 22. + function approveNFT( + address token, + address approved, + int64 serialNumber + ) external returns (int64 responseCode); + + /// Get the approved address for a single NFT + /// Only Applicable to NFT Tokens + /// @param token The Hedera NFT token address to check approval + /// @param serialNumber The NFT to find the approved address for + /// @return responseCode The response code for the status of the request. SUCCESS is 22. + /// @return approved The approved address for this NFT, or the zero address if there is none + function getApproved(address token, int64 serialNumber) + external + returns (int64 responseCode, address approved); + + /// Enable or disable approval for a third party ("operator") to manage + /// all of `msg.sender`'s assets + /// @param token The Hedera NFT token address to approve + /// @param operator Address to add to the set of authorized operators + /// @param approved True if the operator is approved, false to revoke approval + /// @return responseCode The response code for the status of the request. SUCCESS is 22. + function setApprovalForAll( + address token, + address operator, + bool approved + ) external returns (int64 responseCode); + + /// Query if an address is an authorized operator for another address + /// Only Applicable to NFT Tokens + /// @param token The Hedera NFT token address to approve + /// @param owner The address that owns the NFTs + /// @param operator The address that acts on behalf of the owner + /// @return responseCode The response code for the status of the request. SUCCESS is 22. + /// @return approved True if `operator` is an approved operator for `owner`, false otherwise + function isApprovedForAll( + address token, + address owner, + address operator + ) external returns (int64 responseCode, bool approved); + + /// Query if token account is frozen + /// @param token The token address to check + /// @param account The account address associated with the token + /// @return responseCode The response code for the status of the request. SUCCESS is 22. + /// @return frozen True if `account` is frozen for `token` + function isFrozen(address token, address account) + external + returns (int64 responseCode, bool frozen); + + /// Query if token account has kyc granted + /// @param token The token address to check + /// @param account The account address associated with the token + /// @return responseCode The response code for the status of the request. SUCCESS is 22. + /// @return kycGranted True if `account` has kyc granted for `token` + function isKyc(address token, address account) + external + returns (int64 responseCode, bool kycGranted); + + /// Operation to delete token + /// @param token The token address to be deleted + /// @return responseCode The response code for the status of the request. SUCCESS is 22. + function deleteToken(address token) external returns (int64 responseCode); + + /// Query token custom fees + /// @param token The token address to check + /// @return responseCode The response code for the status of the request. SUCCESS is 22. + /// @return fixedFees Set of fixed fees for `token` + /// @return fractionalFees Set of fractional fees for `token` + /// @return royaltyFees Set of royalty fees for `token` + function getTokenCustomFees(address token) + external + returns (int64 responseCode, FixedFee[] memory fixedFees, FractionalFee[] memory fractionalFees, RoyaltyFee[] memory royaltyFees); + + /// Query token default freeze status + /// @param token The token address to check + /// @return responseCode The response code for the status of the request. SUCCESS is 22. + /// @return defaultFreezeStatus True if `token` default freeze status is frozen. + function getTokenDefaultFreezeStatus(address token) + external + returns (int64 responseCode, bool defaultFreezeStatus); + + /// Query token default kyc status + /// @param token The token address to check + /// @return responseCode The response code for the status of the request. SUCCESS is 22. + /// @return defaultKycStatus True if `token` default kyc status is KycNotApplicable and false if Revoked. + function getTokenDefaultKycStatus(address token) + external + returns (int64 responseCode, bool defaultKycStatus); + + /// Query fungible token info + /// @param token The token address to check + /// @return responseCode The response code for the status of the request. SUCCESS is 22. + /// @return fungibleTokenInfo FungibleTokenInfo info for `token` + function getFungibleTokenInfo(address token) + external + returns (int64 responseCode, FungibleTokenInfo memory fungibleTokenInfo); + + /// Query token info + /// @param token The token address to check + /// @return responseCode The response code for the status of the request. SUCCESS is 22. + /// @return tokenInfo TokenInfo info for `token` + function getTokenInfo(address token) + external + returns (int64 responseCode, TokenInfo memory tokenInfo); + + /// Query token KeyValue + /// @param token The token address to check + /// @param keyType The keyType of the desired KeyValue + /// @return responseCode The response code for the status of the request. SUCCESS is 22. + /// @return key KeyValue info for key of type `keyType` + function getTokenKey(address token, uint keyType) + external + returns (int64 responseCode, KeyValue memory key); + + /// Query non fungible token info + /// @param token The token address to check + /// @param serialNumber The NFT serialNumber to check + /// @return responseCode The response code for the status of the request. SUCCESS is 22. + /// @return nonFungibleTokenInfo NonFungibleTokenInfo info for `token` `serialNumber` + function getNonFungibleTokenInfo(address token, int64 serialNumber) + external + returns (int64 responseCode, NonFungibleTokenInfo memory nonFungibleTokenInfo); + + /// Operation to freeze token account + /// @param token The token address + /// @param account The account address to be frozen + /// @return responseCode The response code for the status of the request. SUCCESS is 22. + function freezeToken(address token, address account) + external + returns (int64 responseCode); + + /// Operation to unfreeze token account + /// @param token The token address + /// @param account The account address to be unfrozen + /// @return responseCode The response code for the status of the request. SUCCESS is 22. + function unfreezeToken(address token, address account) + external + returns (int64 responseCode); + + /// Operation to grant kyc to token account + /// @param token The token address + /// @param account The account address to grant kyc + /// @return responseCode The response code for the status of the request. SUCCESS is 22. + function grantTokenKyc(address token, address account) + external + returns (int64 responseCode); + + /// Operation to revoke kyc to token account + /// @param token The token address + /// @param account The account address to revoke kyc + /// @return responseCode The response code for the status of the request. SUCCESS is 22. + function revokeTokenKyc(address token, address account) + external + returns (int64 responseCode); + + /// Operation to pause token + /// @param token The token address to be paused + /// @return responseCode The response code for the status of the request. SUCCESS is 22. + function pauseToken(address token) external returns (int64 responseCode); + + /// Operation to unpause token + /// @param token The token address to be unpaused + /// @return responseCode The response code for the status of the request. SUCCESS is 22. + function unpauseToken(address token) external returns (int64 responseCode); + + /// Operation to wipe fungible tokens from account + /// @param token The token address + /// @param account The account address to revoke kyc + /// @param amount The number of tokens to wipe + /// @return responseCode The response code for the status of the request. SUCCESS is 22. + function wipeTokenAccount( + address token, + address account, + uint32 amount + ) external returns (int64 responseCode); + + /// Operation to wipe non fungible tokens from account + /// @param token The token address + /// @param account The account address to revoke kyc + /// @param serialNumbers The serial numbers of token to wipe + /// @return responseCode The response code for the status of the request. SUCCESS is 22. + function wipeTokenAccountNFT( + address token, + address account, + int64[] memory serialNumbers + ) external returns (int64 responseCode); + + /// Operation to update token info + /// @param token The token address + /// @param tokenInfo The hedera token info to update token with + /// @return responseCode The response code for the status of the request. SUCCESS is 22. + function updateTokenInfo(address token, HederaToken memory tokenInfo) + external + returns (int64 responseCode); + + /// Operation to update token expiry info + /// @param token The token address + /// @param expiryInfo The hedera token expiry info + /// @return responseCode The response code for the status of the request. SUCCESS is 22. + function updateTokenExpiryInfo(address token, Expiry memory expiryInfo) + external + returns (int64 responseCode); + + /// Query token expiry info + /// @param token The token address to check + /// @return responseCode The response code for the status of the request. SUCCESS is 22. + /// @return expiry Expiry info for `token` + function getTokenExpiryInfo(address token) + external + returns (int64 responseCode, Expiry memory expiry); + + /// Operation to update token keys + /// @param token The token address + /// @param keys The token keys + /// @return responseCode The response code for the status of the request. SUCCESS is 22. + function updateTokenKeys(address token, TokenKey[] memory keys) + external + returns (int64 responseCode); + + /// Query if valid token found for the given address + /// @param token The token address + /// @return responseCode The response code for the status of the request. SUCCESS is 22. + /// @return isToken True if valid token found for the given address + function isToken(address token) + external returns + (int64 responseCode, bool isToken); + + /// Query to return the token type for a given address + /// @param token The token address + /// @return responseCode The response code for the status of the request. SUCCESS is 22. + /// @return tokenType the token type. 0 is FUNGIBLE_COMMON, 1 is NON_FUNGIBLE_UNIQUE, -1 is UNRECOGNIZED + function getTokenType(address token) + external returns + (int64 responseCode, int32 tokenType); + + /// Initiates a Redirect For Token + /// @param token the token's address + /// @param data The function selector from the ERC20 interface + the bytes input for the function called + function redirectForToken(address token, bytes memory data) external returns (int responseCode, bytes memory result); +} diff --git a/hedera-node/test-clients/src/main/resource/contract/contracts/LowLevelCall/LowLevelCall.bin b/hedera-node/test-clients/src/main/resource/contract/contracts/LowLevelCall/LowLevelCall.bin index 83c122fa491e..cfd7e0ee1225 100644 --- a/hedera-node/test-clients/src/main/resource/contract/contracts/LowLevelCall/LowLevelCall.bin +++ b/hedera-node/test-clients/src/main/resource/contract/contracts/LowLevelCall/LowLevelCall.bin @@ -1 +1 @@ -608060405234801561001057600080fd5b506103cc806100206000396000f3fe608060405234801561001057600080fd5b506004361061002b5760003560e01c8063194f12bd14610030575b600080fd5b61004a600480360381019061004591906101ef565b610061565b60405161005892919061030e565b60405180910390f35b600060608573ffffffffffffffffffffffffffffffffffffffff1683868660405161008d92919061037d565b60006040518083038160008787f1925050503d80600081146100cb576040519150601f19603f3d011682016040523d82523d6000602084013e6100d0565b606091505b508092508193505050816100e357600080fd5b94509492505050565b600080fd5b600080fd5b600073ffffffffffffffffffffffffffffffffffffffff82169050919050565b6000610121826100f6565b9050919050565b61013181610116565b811461013c57600080fd5b50565b60008135905061014e81610128565b92915050565b600080fd5b600080fd5b600080fd5b60008083601f84011261017957610178610154565b5b8235905067ffffffffffffffff81111561019657610195610159565b5b6020830191508360018202830111156101b2576101b161015e565b5b9250929050565b6000819050919050565b6101cc816101b9565b81146101d757600080fd5b50565b6000813590506101e9816101c3565b92915050565b60008060008060608587031215610209576102086100ec565b5b60006102178782880161013f565b945050602085013567ffffffffffffffff811115610238576102376100f1565b5b61024487828801610163565b93509350506040610257878288016101da565b91505092959194509250565b60008115159050919050565b61027881610263565b82525050565b600081519050919050565b600082825260208201905092915050565b60005b838110156102b857808201518184015260208101905061029d565b60008484015250505050565b6000601f19601f8301169050919050565b60006102e08261027e565b6102ea8185610289565b93506102fa81856020860161029a565b610303816102c4565b840191505092915050565b6000604082019050610323600083018561026f565b818103602083015261033581846102d5565b90509392505050565b600081905092915050565b82818337600083830152505050565b6000610364838561033e565b9350610371838584610349565b82840190509392505050565b600061038a828486610358565b9150819050939250505056fea2646970667358221220f7c9b2ef7966fc012dc86c031c4e70efd381766f7609baa3e425063c5f480fc164736f6c63430008100033 \ No newline at end of file +608060405234801561001057600080fd5b50610489806100206000396000f3fe608060405234801561001057600080fd5b50600436106100365760003560e01c8063194f12bd1461003b57806346c1b5441461006c575b600080fd5b610055600480360381019061005091906102ac565b61009d565b6040516100639291906103cb565b60405180910390f35b610086600480360381019061008191906102ac565b610128565b6040516100949291906103cb565b60405180910390f35b600060608573ffffffffffffffffffffffffffffffffffffffff168386866040516100c992919061043a565b60006040518083038160008787f1925050503d8060008114610107576040519150601f19603f3d011682016040523d82523d6000602084013e61010c565b606091505b5080925081935050508161011f57600080fd5b94509492505050565b600060608573ffffffffffffffffffffffffffffffffffffffff1683868660405161015492919061043a565b60006040518083038160008787f1925050503d8060008114610192576040519150601f19603f3d011682016040523d82523d6000602084013e610197565b606091505b50809250819350505094509492505050565b600080fd5b600080fd5b600073ffffffffffffffffffffffffffffffffffffffff82169050919050565b60006101de826101b3565b9050919050565b6101ee816101d3565b81146101f957600080fd5b50565b60008135905061020b816101e5565b92915050565b600080fd5b600080fd5b600080fd5b60008083601f84011261023657610235610211565b5b8235905067ffffffffffffffff81111561025357610252610216565b5b60208301915083600182028301111561026f5761026e61021b565b5b9250929050565b6000819050919050565b61028981610276565b811461029457600080fd5b50565b6000813590506102a681610280565b92915050565b600080600080606085870312156102c6576102c56101a9565b5b60006102d4878288016101fc565b945050602085013567ffffffffffffffff8111156102f5576102f46101ae565b5b61030187828801610220565b9350935050604061031487828801610297565b91505092959194509250565b60008115159050919050565b61033581610320565b82525050565b600081519050919050565b600082825260208201905092915050565b60005b8381101561037557808201518184015260208101905061035a565b60008484015250505050565b6000601f19601f8301169050919050565b600061039d8261033b565b6103a78185610346565b93506103b7818560208601610357565b6103c081610381565b840191505092915050565b60006040820190506103e0600083018561032c565b81810360208301526103f28184610392565b90509392505050565b600081905092915050565b82818337600083830152505050565b600061042183856103fb565b935061042e838584610406565b82840190509392505050565b6000610447828486610415565b9150819050939250505056fea26469706673582212209bb6cc2e4937718f963b6070106855fae53276cfe99fbf8a1809455f9663ccde64736f6c63430008100033 \ No newline at end of file diff --git a/hedera-node/test-clients/src/main/resource/contract/contracts/LowLevelCall/LowLevelCall.json b/hedera-node/test-clients/src/main/resource/contract/contracts/LowLevelCall/LowLevelCall.json index 15bdeeefa8d6..7caa7d8ed390 100644 --- a/hedera-node/test-clients/src/main/resource/contract/contracts/LowLevelCall/LowLevelCall.json +++ b/hedera-node/test-clients/src/main/resource/contract/contracts/LowLevelCall/LowLevelCall.json @@ -1 +1 @@ -[{"inputs":[{"internalType":"address","name":"target","type":"address"},{"internalType":"bytes","name":"payload","type":"bytes"},{"internalType":"uint256","name":"gasAmount","type":"uint256"}],"name":"callRequested","outputs":[{"internalType":"bool","name":"success","type":"bool"},{"internalType":"bytes","name":"result","type":"bytes"}],"stateMutability":"nonpayable","type":"function"}] \ No newline at end of file +[{"inputs":[{"internalType":"address","name":"target","type":"address"},{"internalType":"bytes","name":"payload","type":"bytes"},{"internalType":"uint256","name":"gasAmount","type":"uint256"}],"name":"callRequested","outputs":[{"internalType":"bool","name":"success","type":"bool"},{"internalType":"bytes","name":"result","type":"bytes"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"target","type":"address"},{"internalType":"bytes","name":"payload","type":"bytes"},{"internalType":"uint256","name":"gasAmount","type":"uint256"}],"name":"callRequestedAndIgnoreFailure","outputs":[{"internalType":"bool","name":"success","type":"bool"},{"internalType":"bytes","name":"result","type":"bytes"}],"stateMutability":"nonpayable","type":"function"}] \ No newline at end of file diff --git a/hedera-node/test-clients/src/main/resource/contract/contracts/LowLevelCall/LowLevelCall.sol b/hedera-node/test-clients/src/main/resource/contract/contracts/LowLevelCall/LowLevelCall.sol index 04bc3f7572ad..cba0e2aef1b7 100644 --- a/hedera-node/test-clients/src/main/resource/contract/contracts/LowLevelCall/LowLevelCall.sol +++ b/hedera-node/test-clients/src/main/resource/contract/contracts/LowLevelCall/LowLevelCall.sol @@ -12,4 +12,12 @@ contract LowLevelCall { // Optionally, revert the transaction if the call fails require(success); } + + function callRequestedAndIgnoreFailure( + address target, + bytes calldata payload, + uint256 gasAmount + ) external returns (bool success, bytes memory result) { + (success, result) = target.call{gas: gasAmount}(payload); + } } diff --git a/hedera-node/test-clients/src/main/resource/contract/contracts/MakeCalls/MakeCalls.bin b/hedera-node/test-clients/src/main/resource/contract/contracts/MakeCalls/MakeCalls.bin new file mode 100644 index 000000000000..0ba787ed1191 --- /dev/null +++ b/hedera-node/test-clients/src/main/resource/contract/contracts/MakeCalls/MakeCalls.bin @@ -0,0 +1 @@ +608060405234801561000f575f80fd5b506104d18061001d5f395ff3fe608060405260043610610028575f3560e01c80637e38d3a31461002c5780639154822814610069575b5f80fd5b348015610037575f80fd5b50610052600480360381019061004d919061032f565b61009a565b60405161006092919061041d565b60405180910390f35b610083600480360381019061007e919061032f565b610110565b60405161009192919061041d565b60405180910390f35b5f60608373ffffffffffffffffffffffffffffffffffffffff16836040516100c29190610485565b5f604051808303815f865af19150503d805f81146100fb576040519150601f19603f3d011682016040523d82523d5f602084013e610100565b606091505b5080925081935050509250929050565b5f60608373ffffffffffffffffffffffffffffffffffffffff1634846040516101399190610485565b5f6040518083038185875af1925050503d805f8114610173576040519150601f19603f3d011682016040523d82523d5f602084013e610178565b606091505b5080925081935050509250929050565b5f604051905090565b5f80fd5b5f80fd5b5f73ffffffffffffffffffffffffffffffffffffffff82169050919050565b5f6101c282610199565b9050919050565b6101d2816101b8565b81146101dc575f80fd5b50565b5f813590506101ed816101c9565b92915050565b5f80fd5b5f80fd5b5f601f19601f8301169050919050565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52604160045260245ffd5b610241826101fb565b810181811067ffffffffffffffff821117156102605761025f61020b565b5b80604052505050565b5f610272610188565b905061027e8282610238565b919050565b5f67ffffffffffffffff82111561029d5761029c61020b565b5b6102a6826101fb565b9050602081019050919050565b828183375f83830152505050565b5f6102d36102ce84610283565b610269565b9050828152602081018484840111156102ef576102ee6101f7565b5b6102fa8482856102b3565b509392505050565b5f82601f830112610316576103156101f3565b5b81356103268482602086016102c1565b91505092915050565b5f806040838503121561034557610344610191565b5b5f610352858286016101df565b925050602083013567ffffffffffffffff81111561037357610372610195565b5b61037f85828601610302565b9150509250929050565b5f8115159050919050565b61039d81610389565b82525050565b5f81519050919050565b5f82825260208201905092915050565b5f5b838110156103da5780820151818401526020810190506103bf565b5f8484015250505050565b5f6103ef826103a3565b6103f981856103ad565b93506104098185602086016103bd565b610412816101fb565b840191505092915050565b5f6040820190506104305f830185610394565b818103602083015261044281846103e5565b90509392505050565b5f81905092915050565b5f61045f826103a3565b610469818561044b565b93506104798185602086016103bd565b80840191505092915050565b5f6104908284610455565b91508190509291505056fea2646970667358221220024b087a39503641d853603076a748cfedfd8eeda00fd4452e7514ca1e9b375c64736f6c63430008180033 \ No newline at end of file diff --git a/hedera-node/test-clients/src/main/resource/contract/contracts/MakeCalls/MakeCalls.json b/hedera-node/test-clients/src/main/resource/contract/contracts/MakeCalls/MakeCalls.json new file mode 100644 index 000000000000..843cf6cc835d --- /dev/null +++ b/hedera-node/test-clients/src/main/resource/contract/contracts/MakeCalls/MakeCalls.json @@ -0,0 +1,60 @@ +[ + { + "inputs": [ + { + "internalType": "address", + "name": "_to", + "type": "address" + }, + { + "internalType": "bytes", + "name": "_data", + "type": "bytes" + } + ], + "name": "makeCallWithAmount", + "outputs": [ + { + "internalType": "bool", + "name": "success", + "type": "bool" + }, + { + "internalType": "bytes", + "name": "returnData", + "type": "bytes" + } + ], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_to", + "type": "address" + }, + { + "internalType": "bytes", + "name": "_data", + "type": "bytes" + } + ], + "name": "makeCallWithoutAmount", + "outputs": [ + { + "internalType": "bool", + "name": "success", + "type": "bool" + }, + { + "internalType": "bytes", + "name": "returnData", + "type": "bytes" + } + ], + "stateMutability": "nonpayable", + "type": "function" + } +] \ No newline at end of file diff --git a/hedera-node/test-clients/src/main/resource/contract/contracts/MakeCalls/MakeCalls.sol b/hedera-node/test-clients/src/main/resource/contract/contracts/MakeCalls/MakeCalls.sol new file mode 100644 index 000000000000..7e233b0fb1d0 --- /dev/null +++ b/hedera-node/test-clients/src/main/resource/contract/contracts/MakeCalls/MakeCalls.sol @@ -0,0 +1,11 @@ +pragma solidity ^0.8.12; + +contract MakeCalls { + function makeCallWithAmount(address _to, bytes memory _data) external payable returns (bool success, bytes memory returnData) { + (success, returnData) = _to.call{value: msg.value}(_data); + } + + function makeCallWithoutAmount(address _to, bytes memory _data) external returns (bool success, bytes memory returnData) { + (success, returnData) = _to.call(_data); + } +} \ No newline at end of file diff --git a/hedera-node/test-clients/src/main/resource/contract/contracts/StateContract/StateContract.bin b/hedera-node/test-clients/src/main/resource/contract/contracts/StateContract/StateContract.bin new file mode 100644 index 000000000000..2234339db2de --- /dev/null +++ b/hedera-node/test-clients/src/main/resource/contract/contracts/StateContract/StateContract.bin @@ -0,0 +1 @@ +608060405234801561001057600080fd5b50612a6f806100206000396000f3fe608060405234801561001057600080fd5b506004361061027f5760003560e01c80636ef3d3ab1161015c578063a78bdf39116100ce578063d582223911610087578063d5822239146106a5578063e220c318146106c3578063ed82777c146106df578063f71647ef146106fb578063f79b059f14610717578063f945bc26146107355761027f565b8063a78bdf3914610609578063b47bb98114610627578063ba525ac014610631578063c88ac73d1461064d578063ccd685711461066b578063cf93a442146106875761027f565b80638c1d2db8116101205780638c1d2db81461055b57806391aa423a146105795780639b9dfb62146105955780639e855d02146105b1578063a2ccbe1b146105cf578063a4ac27f3146105eb5761027f565b80636ef3d3ab146104c757806370c6234f146104e557806373061ce414610503578063801d190e146105215780638b7901e21461053f5761027f565b8063330cdc94116101f5578063530b65ad116101b9578063530b65ad1461042a578063636156721461044657806365618c2514610465578063664c8d9d146104815780636b83cbf11461049f5780636c60cd75146104bd5761027f565b8063330cdc94146103be578063387d9f04146103c85780633a261a2b146103e65780634112b645146103f0578063451ea5a61461040e5761027f565b80631f28d0a1116102475780631f28d0a114610310578063214d3c991461032c57806322a410f21461034a57806328d522b11461036657806330467dd91461038457806331a6439f146103a25761027f565b806301af096c14610284578063098328ec146102a05780631417aa4f146102bc57806319aeb7ea146102d85780631dd36ffe146102f4575b600080fd5b61029e6004803603810190610299919061140e565b610753565b005b6102ba60048036038101906102b59190611460565b610792565b005b6102d660048036038101906102d191906114f2565b6107bf565b005b6102f260048036038101906102ed9190611578565b610815565b005b61030e600480360381019061030991906115de565b610836565b005b61032a60048036038101906103259190611647565b610854565b005b610334610878565b604051610341919061168d565b60405180910390f35b610364600480360381019061035f91906117e9565b610882565b005b61036e6108b7565b60405161037b919061184e565b60405180910390f35b61038c6108ce565b6040516103999190611886565b60405180910390f35b6103bc60048036038101906103b791906118da565b6108e5565b005b6103c6610908565b005b6103d0610918565b6040516103dd9190611916565b60405180910390f35b6103ee61092f565b005b6103f861099a565b6040516104059190611940565b60405180910390f35b610428600480360381019061042391906119a3565b6109b1565b005b610444600480360381019061043f9190611a08565b6109ed565b005b61044e610a09565b60405161045c929190611afd565b60405180910390f35b61047f600480360381019061047a9190611b74565b610abc565b005b610489610ae8565b6040516104969190611bb0565b60405180910390f35b6104a7610b05565b6040516104b49190611bda565b60405180910390f35b6104c5610b0f565b005b6104cf610b2d565b6040516104dc9190611c04565b60405180910390f35b6104ed610b52565b6040516104fa9190611c38565b60405180910390f35b61050b610b5c565b6040516105189190611c62565b60405180910390f35b610529610b72565b6040516105369190611cfc565b60405180910390f35b61055960048036038101906105549190611d7c565b610c04565b005b610563610c48565b6040516105709190611db8565b60405180910390f35b610593600480360381019061058e91906117e9565b610c5f565b005b6105af60048036038101906105aa9190611dff565b610c72565b005b6105b9610c92565b6040516105c69190611e3b565b60405180910390f35b6105e960048036038101906105e49190611e8f565b610cbc565b005b6105f3610ceb565b6040516106009190611cfc565b60405180910390f35b610611610d7d565b60405161061e91906120b4565b60405180910390f35b61062f610fb8565b005b61064b60048036038101906106469190612102565b61103a565b005b610655611044565b604051610662919061213e565b60405180910390f35b61068560048036038101906106809190612185565b61105d565b005b61068f611067565b60405161069c91906121c1565b60405180910390f35b6106ad61107e565b6040516106ba919061223b565b60405180910390f35b6106dd60048036038101906106d89190612282565b6110a8565b005b6106f960048036038101906106f491906122db565b6110cf565b005b610715600480360381019061071091906124dd565b6110d9565b005b61071f6111b6565b60405161072c9190612535565b60405180910390f35b61073d6111cc565b60405161074a919061255f565b60405180910390f35b806002600f6101000a8154816fffffffffffffffffffffffffffffffff0219169083600f0b6fffffffffffffffffffffffffffffffff16021790555050565b80600860006101000a81548160ff021916908360038111156107b7576107b6611f24565b5b021790555050565b8181600991906107d09291906111e3565b50600060099050808054806107e8576107e761257a565b5b6001900381819060005260206000200160009055905580600a90805461080f929190611230565b50505050565b80600260006101000a81548160ff021916908360000b60ff16021790555050565b80600060016101000a81548160ff021916908360ff16021790555050565b80600060046101000a81548163ffffffff021916908363ffffffff16021790555050565b6000600354905090565b600b816040516020016108969291906126dd565b604051602081830303815290604052600b90816108b3919061288e565b5050565b6000600260039054906101000a900460030b905090565b60008060029054906101000a900461ffff16905090565b80600260016101000a81548161ffff021916908360010b61ffff16021790555050565b600b60006109169190611282565b565b60006002600f9054906101000a9004600f0b905090565b60405161093b906112c2565b604051809103906000f080158015610957573d6000803e3d6000fd5b50600560006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff160217905550565b6000600260019054906101000a900460010b905090565b80600060106101000a8154816fffffffffffffffffffffffffffffffff02191690836fffffffffffffffffffffffffffffffff16021790555050565b806000806101000a81548160ff02191690831515021790555050565b6060806009600a81805480602002602001604051908101604052809291908181526020018280548015610a5b57602002820191906000526020600020905b815481526020019060010190808311610a47575b5050505050915080805480602002602001604051908101604052809291908181526020018280548015610aad57602002820191906000526020600020905b815481526020019060010190808311610a99575b50505050509050915091509091565b80600060086101000a81548167ffffffffffffffff021916908367ffffffffffffffff16021790555050565b60008060089054906101000a900467ffffffffffffffff16905090565b6000600154905090565b60096000610b1d91906112ce565b600a6000610b2b91906112ce565b565b60008060109054906101000a90046fffffffffffffffffffffffffffffffff16905090565b6000600654905090565b60008060019054906101000a900460ff16905090565b6060600b8054610b81906125d8565b80601f0160208091040260200160405190810160405280929190818152602001828054610bad906125d8565b8015610bfa5780601f10610bcf57610100808354040283529160200191610bfa565b820191906000526020600020905b815481529060010190602001808311610bdd57829003601f168201915b5050505050905090565b80600460006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff16021790555050565b6000600260009054906101000a900460000b905090565b8060079081610c6e919061288e565b5050565b80600060026101000a81548161ffff021916908361ffff16021790555050565b6000600460009054906101000a900473ffffffffffffffffffffffffffffffffffffffff16905090565b80600260076101000a81548167ffffffffffffffff021916908360070b67ffffffffffffffff16021790555050565b606060078054610cfa906125d8565b80601f0160208091040260200160405190810160405280929190818152602001828054610d26906125d8565b8015610d735780601f10610d4857610100808354040283529160200191610d73565b820191906000526020600020905b815481529060010190602001808311610d5657829003601f168201915b5050505050905090565b610d856112ef565b600c6040518060e0016040529081600082015481526020016001820160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200160028201548152602001600382018054610e0c906125d8565b80601f0160208091040260200160405190810160405280929190818152602001828054610e38906125d8565b8015610e855780601f10610e5a57610100808354040283529160200191610e85565b820191906000526020600020905b815481529060010190602001808311610e6857829003601f168201915b505050505081526020016004820160009054906101000a900460ff166003811115610eb357610eb2611f24565b5b6003811115610ec557610ec4611f24565b5b815260200160058201805480602002602001604051908101604052809291908181526020018280548015610f1857602002820191906000526020600020905b815481526020019060010190808311610f04575b50505050508152602001600682018054610f31906125d8565b80601f0160208091040260200160405190810160405280929190818152602001828054610f5d906125d8565b8015610faa5780601f10610f7f57610100808354040283529160200191610faa565b820191906000526020600020905b815481529060010190602001808311610f8d57829003601f168201915b505050505081525050905090565b600c6000808201600090556001820160006101000a81549073ffffffffffffffffffffffffffffffffffffffff021916905560028201600090556003820160006110029190611282565b6004820160006101000a81549060ff021916905560058201600061102691906112ce565b6006820160006110369190611282565b5050565b8060018190555050565b60008060049054906101000a900463ffffffff16905090565b8060038190555050565b6000600260079054906101000a900460070b905090565b6000600560009054906101000a900473ffffffffffffffffffffffffffffffffffffffff16905090565b80600260036101000a81548163ffffffff021916908360030b63ffffffff16021790555050565b8060068190555050565b80600c6000820151816000015560208201518160010160006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff16021790555060408201518160020155606082015181600301908161114c919061288e565b5060808201518160040160006101000a81548160ff0219169083600381111561117857611177611f24565b5b021790555060a0820151816005019080519060200190611199929190611357565b5060c08201518160060190816111af919061288e565b5090505050565b60008060009054906101000a900460ff16905090565b6000600860009054906101000a900460ff16905090565b82805482825590600052602060002090810192821561121f579160200282015b8281111561121e578235825591602001919060010190611203565b5b50905061122c91906113a4565b5090565b8280548282559060005260206000209081019282156112715760005260206000209182015b82811115611270578254825591600101919060010190611255565b5b50905061127e91906113a4565b5090565b50805461128e906125d8565b6000825580601f106112a057506112bf565b601f0160209004906000526020600020908101906112be91906113a4565b5b50565b60d98061296183390190565b50805460008255906000526020600020908101906112ec91906113a4565b50565b6040518060e0016040528060008152602001600073ffffffffffffffffffffffffffffffffffffffff16815260200160008019168152602001606081526020016000600381111561134357611342611f24565b5b815260200160608152602001606081525090565b828054828255906000526020600020908101928215611393579160200282015b82811115611392578251825591602001919060010190611377565b5b5090506113a091906113a4565b5090565b5b808211156113bd5760008160009055506001016113a5565b5090565b6000604051905090565b600080fd5b600080fd5b600081600f0b9050919050565b6113eb816113d5565b81146113f657600080fd5b50565b600081359050611408816113e2565b92915050565b600060208284031215611424576114236113cb565b5b6000611432848285016113f9565b91505092915050565b6004811061144857600080fd5b50565b60008135905061145a8161143b565b92915050565b600060208284031215611476576114756113cb565b5b60006114848482850161144b565b91505092915050565b600080fd5b600080fd5b600080fd5b60008083601f8401126114b2576114b161148d565b5b8235905067ffffffffffffffff8111156114cf576114ce611492565b5b6020830191508360208202830111156114eb576114ea611497565b5b9250929050565b60008060208385031215611509576115086113cb565b5b600083013567ffffffffffffffff811115611527576115266113d0565b5b6115338582860161149c565b92509250509250929050565b60008160000b9050919050565b6115558161153f565b811461156057600080fd5b50565b6000813590506115728161154c565b92915050565b60006020828403121561158e5761158d6113cb565b5b600061159c84828501611563565b91505092915050565b600060ff82169050919050565b6115bb816115a5565b81146115c657600080fd5b50565b6000813590506115d8816115b2565b92915050565b6000602082840312156115f4576115f36113cb565b5b6000611602848285016115c9565b91505092915050565b600063ffffffff82169050919050565b6116248161160b565b811461162f57600080fd5b50565b6000813590506116418161161b565b92915050565b60006020828403121561165d5761165c6113cb565b5b600061166b84828501611632565b91505092915050565b6000819050919050565b61168781611674565b82525050565b60006020820190506116a2600083018461167e565b92915050565b600080fd5b6000601f19601f8301169050919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b6116f6826116ad565b810181811067ffffffffffffffff82111715611715576117146116be565b5b80604052505050565b60006117286113c1565b905061173482826116ed565b919050565b600067ffffffffffffffff821115611754576117536116be565b5b61175d826116ad565b9050602081019050919050565b82818337600083830152505050565b600061178c61178784611739565b61171e565b9050828152602081018484840111156117a8576117a76116a8565b5b6117b384828561176a565b509392505050565b600082601f8301126117d0576117cf61148d565b5b81356117e0848260208601611779565b91505092915050565b6000602082840312156117ff576117fe6113cb565b5b600082013567ffffffffffffffff81111561181d5761181c6113d0565b5b611829848285016117bb565b91505092915050565b60008160030b9050919050565b61184881611832565b82525050565b6000602082019050611863600083018461183f565b92915050565b600061ffff82169050919050565b61188081611869565b82525050565b600060208201905061189b6000830184611877565b92915050565b60008160010b9050919050565b6118b7816118a1565b81146118c257600080fd5b50565b6000813590506118d4816118ae565b92915050565b6000602082840312156118f0576118ef6113cb565b5b60006118fe848285016118c5565b91505092915050565b611910816113d5565b82525050565b600060208201905061192b6000830184611907565b92915050565b61193a816118a1565b82525050565b60006020820190506119556000830184611931565b92915050565b60006fffffffffffffffffffffffffffffffff82169050919050565b6119808161195b565b811461198b57600080fd5b50565b60008135905061199d81611977565b92915050565b6000602082840312156119b9576119b86113cb565b5b60006119c78482850161198e565b91505092915050565b60008115159050919050565b6119e5816119d0565b81146119f057600080fd5b50565b600081359050611a02816119dc565b92915050565b600060208284031215611a1e57611a1d6113cb565b5b6000611a2c848285016119f3565b91505092915050565b600081519050919050565b600082825260208201905092915050565b6000819050602082019050919050565b6000819050919050565b611a7481611a61565b82525050565b6000611a868383611a6b565b60208301905092915050565b6000602082019050919050565b6000611aaa82611a35565b611ab48185611a40565b9350611abf83611a51565b8060005b83811015611af0578151611ad78882611a7a565b9750611ae283611a92565b925050600181019050611ac3565b5085935050505092915050565b60006040820190508181036000830152611b178185611a9f565b90508181036020830152611b2b8184611a9f565b90509392505050565b600067ffffffffffffffff82169050919050565b611b5181611b34565b8114611b5c57600080fd5b50565b600081359050611b6e81611b48565b92915050565b600060208284031215611b8a57611b896113cb565b5b6000611b9884828501611b5f565b91505092915050565b611baa81611b34565b82525050565b6000602082019050611bc56000830184611ba1565b92915050565b611bd481611a61565b82525050565b6000602082019050611bef6000830184611bcb565b92915050565b611bfe8161195b565b82525050565b6000602082019050611c196000830184611bf5565b92915050565b6000819050919050565b611c3281611c1f565b82525050565b6000602082019050611c4d6000830184611c29565b92915050565b611c5c816115a5565b82525050565b6000602082019050611c776000830184611c53565b92915050565b600081519050919050565b600082825260208201905092915050565b60005b83811015611cb7578082015181840152602081019050611c9c565b60008484015250505050565b6000611cce82611c7d565b611cd88185611c88565b9350611ce8818560208601611c99565b611cf1816116ad565b840191505092915050565b60006020820190508181036000830152611d168184611cc3565b905092915050565b600073ffffffffffffffffffffffffffffffffffffffff82169050919050565b6000611d4982611d1e565b9050919050565b611d5981611d3e565b8114611d6457600080fd5b50565b600081359050611d7681611d50565b92915050565b600060208284031215611d9257611d916113cb565b5b6000611da084828501611d67565b91505092915050565b611db28161153f565b82525050565b6000602082019050611dcd6000830184611da9565b92915050565b611ddc81611869565b8114611de757600080fd5b50565b600081359050611df981611dd3565b92915050565b600060208284031215611e1557611e146113cb565b5b6000611e2384828501611dea565b91505092915050565b611e3581611d3e565b82525050565b6000602082019050611e506000830184611e2c565b92915050565b60008160070b9050919050565b611e6c81611e56565b8114611e7757600080fd5b50565b600081359050611e8981611e63565b92915050565b600060208284031215611ea557611ea46113cb565b5b6000611eb384828501611e7a565b91505092915050565b611ec581611d3e565b82525050565b611ed481611c1f565b82525050565b600082825260208201905092915050565b6000611ef682611c7d565b611f008185611eda565b9350611f10818560208601611c99565b611f19816116ad565b840191505092915050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052602160045260246000fd5b60048110611f6457611f63611f24565b5b50565b6000819050611f7582611f53565b919050565b6000611f8582611f67565b9050919050565b611f9581611f7a565b82525050565b600082825260208201905092915050565b6000611fb782611a35565b611fc18185611f9b565b9350611fcc83611a51565b8060005b83811015611ffd578151611fe48882611a7a565b9750611fef83611a92565b925050600181019050611fd0565b5085935050505092915050565b600060e0830160008301516120226000860182611a6b565b5060208301516120356020860182611ebc565b5060408301516120486040860182611ecb565b50606083015184820360608601526120608282611eeb565b91505060808301516120756080860182611f8c565b5060a083015184820360a086015261208d8282611fac565b91505060c083015184820360c08601526120a78282611eeb565b9150508091505092915050565b600060208201905081810360008301526120ce818461200a565b905092915050565b6120df81611a61565b81146120ea57600080fd5b50565b6000813590506120fc816120d6565b92915050565b600060208284031215612118576121176113cb565b5b6000612126848285016120ed565b91505092915050565b6121388161160b565b82525050565b6000602082019050612153600083018461212f565b92915050565b61216281611674565b811461216d57600080fd5b50565b60008135905061217f81612159565b92915050565b60006020828403121561219b5761219a6113cb565b5b60006121a984828501612170565b91505092915050565b6121bb81611e56565b82525050565b60006020820190506121d660008301846121b2565b92915050565b6000819050919050565b60006122016121fc6121f784611d1e565b6121dc565b611d1e565b9050919050565b6000612213826121e6565b9050919050565b600061222582612208565b9050919050565b6122358161221a565b82525050565b6000602082019050612250600083018461222c565b92915050565b61225f81611832565b811461226a57600080fd5b50565b60008135905061227c81612256565b92915050565b600060208284031215612298576122976113cb565b5b60006122a68482850161226d565b91505092915050565b6122b881611c1f565b81146122c357600080fd5b50565b6000813590506122d5816122af565b92915050565b6000602082840312156122f1576122f06113cb565b5b60006122ff848285016122c6565b91505092915050565b600080fd5b600080fd5b600067ffffffffffffffff82111561232d5761232c6116be565b5b602082029050602081019050919050565b600061235161234c84612312565b61171e565b9050808382526020820190506020840283018581111561237457612373611497565b5b835b8181101561239d578061238988826120ed565b845260208401935050602081019050612376565b5050509392505050565b600082601f8301126123bc576123bb61148d565b5b81356123cc84826020860161233e565b91505092915050565b600060e082840312156123eb576123ea612308565b5b6123f560e061171e565b90506000612405848285016120ed565b600083015250602061241984828501611d67565b602083015250604061242d848285016122c6565b604083015250606082013567ffffffffffffffff8111156124515761245061230d565b5b61245d848285016117bb565b60608301525060806124718482850161144b565b60808301525060a082013567ffffffffffffffff8111156124955761249461230d565b5b6124a1848285016123a7565b60a08301525060c082013567ffffffffffffffff8111156124c5576124c461230d565b5b6124d1848285016117bb565b60c08301525092915050565b6000602082840312156124f3576124f26113cb565b5b600082013567ffffffffffffffff811115612511576125106113d0565b5b61251d848285016123d5565b91505092915050565b61252f816119d0565b82525050565b600060208201905061254a6000830184612526565b92915050565b61255981611f7a565b82525050565b60006020820190506125746000830184612550565b92915050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052603160045260246000fd5b7f4e487b7100000000000000000000000000000000000000000000000000000000600052602260045260246000fd5b600060028204905060018216806125f057607f821691505b602082108103612603576126026125a9565b5b50919050565b600081905092915050565b60008190508160005260206000209050919050565b60008154612636816125d8565b6126408186612609565b9450600182166000811461265b5760018114612670576126a3565b60ff19831686528115158202860193506126a3565b61267985612614565b60005b8381101561269b5781548189015260018201915060208101905061267c565b838801955050505b50505092915050565b60006126b782611c7d565b6126c18185612609565b93506126d1818560208601611c99565b80840191505092915050565b60006126e98285612629565b91506126f582846126ac565b91508190509392505050565b60006020601f8301049050919050565b600082821b905092915050565b60006008830261274e7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff82612711565b6127588683612711565b95508019841693508086168417925050509392505050565b600061278b61278661278184611a61565b6121dc565b611a61565b9050919050565b6000819050919050565b6127a583612770565b6127b96127b182612792565b84845461271e565b825550505050565b600090565b6127ce6127c1565b6127d981848461279c565b505050565b5b818110156127fd576127f26000826127c6565b6001810190506127df565b5050565b601f8211156128425761281381612614565b61281c84612701565b8101602085101561282b578190505b61283f61283785612701565b8301826127de565b50505b505050565b600082821c905092915050565b600061286560001984600802612847565b1980831691505092915050565b600061287e8383612854565b9150826002028217905092915050565b61289782611c7d565b67ffffffffffffffff8111156128b0576128af6116be565b5b6128ba82546125d8565b6128c5828285612801565b600060209050601f8311600181146128f857600084156128e6578287015190505b6128f08582612872565b865550612958565b601f19841661290686612614565b60005b8281101561292e57848901518255600182019150602085019450602081019050612909565b8683101561294b5784890151612947601f891682612854565b8355505b6001600288020188555050505b50505050505056fe608060405234801561001057600080fd5b504360008190555060b3806100266000396000f3fe6080604052348015600f57600080fd5b506004361060285760003560e01c80638381f58a14602d575b600080fd5b60336047565b604051603e91906064565b60405180910390f35b60005481565b6000819050919050565b605e81604d565b82525050565b6000602082019050607760008301846057565b9291505056fea2646970667358221220719ffefb243bf1adc401134f5e195237dfda17536ba557ab04c71885969dfbf864736f6c63430008100033a26469706673582212203cf8d90ea2e6932e7f6eec7045319776a3bfdf273c2f7c80ac37436333f0977e64736f6c63430008100033 \ No newline at end of file diff --git a/hedera-node/test-clients/src/main/resource/contract/contracts/StateContract/StateContract.json b/hedera-node/test-clients/src/main/resource/contract/contracts/StateContract/StateContract.json new file mode 100644 index 000000000000..809fa2f8b894 --- /dev/null +++ b/hedera-node/test-clients/src/main/resource/contract/contracts/StateContract/StateContract.json @@ -0,0 +1 @@ +[{"inputs":[],"stateMutability":"nonpayable","type":"constructor"},{"inputs":[],"name":"deleteVarContractStruct","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"deleteVarIntArrDataAlloc","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"deleteVarStringConcat","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"getVarAddress","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getVarBool","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getVarBytes32","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getVarContractStruct","outputs":[{"components":[{"internalType":"uint256","name":"varUint256","type":"uint256"},{"internalType":"address","name":"varAddress","type":"address"},{"internalType":"bytes32","name":"varBytes32","type":"bytes32"},{"internalType":"string","name":"varString","type":"string"},{"internalType":"enum Choices","name":"varContractType","type":"uint8"},{"internalType":"uint256[]","name":"varUint256Arr","type":"uint256[]"},{"internalType":"string","name":"varStringConcat","type":"string"}],"internalType":"struct ContractStruct","name":"","type":"tuple"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getVarContractType","outputs":[{"internalType":"contract ContractType","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getVarEnum","outputs":[{"internalType":"enum Choices","name":"","type":"uint8"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getVarInt128","outputs":[{"internalType":"int128","name":"","type":"int128"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getVarInt16","outputs":[{"internalType":"int16","name":"","type":"int16"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getVarInt256","outputs":[{"internalType":"int256","name":"","type":"int256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getVarInt32","outputs":[{"internalType":"int32","name":"","type":"int32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getVarInt64","outputs":[{"internalType":"int64","name":"","type":"int64"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getVarInt8","outputs":[{"internalType":"int8","name":"","type":"int8"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getVarIntArrDataAlloc","outputs":[{"internalType":"uint256[]","name":"","type":"uint256[]"},{"internalType":"uint256[]","name":"","type":"uint256[]"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getVarString","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getVarStringConcat","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getVarUint128","outputs":[{"internalType":"uint128","name":"","type":"uint128"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getVarUint16","outputs":[{"internalType":"uint16","name":"","type":"uint16"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getVarUint256","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getVarUint32","outputs":[{"internalType":"uint32","name":"","type":"uint32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getVarUint64","outputs":[{"internalType":"uint64","name":"","type":"uint64"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getVarUint8","outputs":[{"internalType":"uint8","name":"","type":"uint8"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"newVar","type":"address"}],"name":"setVarAddress","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bool","name":"newVar","type":"bool"}],"name":"setVarBool","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"newVar","type":"bytes32"}],"name":"setVarBytes32","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"components":[{"internalType":"uint256","name":"varUint256","type":"uint256"},{"internalType":"address","name":"varAddress","type":"address"},{"internalType":"bytes32","name":"varBytes32","type":"bytes32"},{"internalType":"string","name":"varString","type":"string"},{"internalType":"enum Choices","name":"varContractType","type":"uint8"},{"internalType":"uint256[]","name":"varUint256Arr","type":"uint256[]"},{"internalType":"string","name":"varStringConcat","type":"string"}],"internalType":"struct ContractStruct","name":"newVar","type":"tuple"}],"name":"setVarContractStruct","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"setVarContractType","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"enum Choices","name":"newVar","type":"uint8"}],"name":"setVarEnum","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"int128","name":"newVar","type":"int128"}],"name":"setVarInt128","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"int16","name":"newVar","type":"int16"}],"name":"setVarInt16","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"int256","name":"newVar","type":"int256"}],"name":"setVarInt256","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"int32","name":"newVar","type":"int32"}],"name":"setVarInt32","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"int64","name":"newVar","type":"int64"}],"name":"setVarInt64","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"int8","name":"newVar","type":"int8"}],"name":"setVarInt8","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256[]","name":"newVar","type":"uint256[]"}],"name":"setVarIntArrDataAlloc","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"string","name":"newVar","type":"string"}],"name":"setVarString","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"string","name":"newVar","type":"string"}],"name":"setVarStringConcat","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint128","name":"newVar","type":"uint128"}],"name":"setVarUint128","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint16","name":"newVar","type":"uint16"}],"name":"setVarUint16","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"newVar","type":"uint256"}],"name":"setVarUint256","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint32","name":"newVar","type":"uint32"}],"name":"setVarUint32","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint64","name":"newVar","type":"uint64"}],"name":"setVarUint64","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint8","name":"newVar","type":"uint8"}],"name":"setVarUint8","outputs":[],"stateMutability":"nonpayable","type":"function"}] \ No newline at end of file diff --git a/hedera-node/test-clients/src/main/resource/contract/contracts/StateContract/StateContract.sol b/hedera-node/test-clients/src/main/resource/contract/contracts/StateContract/StateContract.sol new file mode 100644 index 000000000000..ee639add4867 --- /dev/null +++ b/hedera-node/test-clients/src/main/resource/contract/contracts/StateContract/StateContract.sol @@ -0,0 +1,243 @@ +// SPDX-License-Identifier: Apache-2.0 +pragma solidity ^0.8.0; + +enum Choices {Top, Bottom, Left, Right} + +struct ContractStruct { + uint256 varUint256; + address varAddress; + bytes32 varBytes32; + string varString; + Choices varContractType; + uint256[] varUint256Arr; + string varStringConcat; +} + +contract ContractType { + uint256 public number; + + constructor() { + number = block.number; + } +} + +contract StateContract { + + bool varBool; + + uint8 varUint8; + uint16 varUint16; + uint32 varUint32; + uint64 varUint64; + uint128 varUint128; + uint256 varUint256; + + int8 varInt8; + int16 varInt16; + int32 varInt32; + int64 varInt64; + int128 varInt128; + int256 varInt256; + + address varAddress; + ContractType varContractType; + bytes32 varBytes32; + string varString; + + + Choices varEnum; + + uint[] varIntArrDataAllocBefore; + uint[] varIntArrDataAllocAfter; + + string varStringConcat; + ContractStruct varContractStruct; + + constructor() {} + + function setVarBool(bool newVar) external { + varBool = newVar; + } + + function getVarBool() external view returns (bool) { + return varBool; + } + + function setVarUint8(uint8 newVar) external { + varUint8 = newVar; + } + + function getVarUint8() external view returns (uint8) { + return varUint8; + } + + function setVarUint16(uint16 newVar) external { + varUint16 = newVar; + } + + function getVarUint16() external view returns (uint16) { + return varUint16; + } + + function setVarUint32(uint32 newVar) external { + varUint32 = newVar; + } + + function getVarUint32() external view returns (uint32) { + return varUint32; + } + + function setVarUint64(uint64 newVar) external { + varUint64 = newVar; + } + + function getVarUint64() external view returns (uint64) { + return varUint64; + } + + function setVarUint128(uint128 newVar) external { + varUint128 = newVar; + } + + function getVarUint128() external view returns (uint128) { + return varUint128; + } + + function setVarUint256(uint256 newVar) external { + varUint256 = newVar; + } + + function getVarUint256() external view returns (uint256) { + return varUint256; + } + + function setVarInt8(int8 newVar) external { + varInt8 = newVar; + } + + function getVarInt8() external view returns (int8) { + return varInt8; + } + + function setVarInt16(int16 newVar) external { + varInt16 = newVar; + } + + function getVarInt16() external view returns (int16) { + return varInt16; + } + + function setVarInt32(int32 newVar) external { + varInt32 = newVar; + } + + function getVarInt32() external view returns (int32) { + return varInt32; + } + + function setVarInt64(int64 newVar) external { + varInt64 = newVar; + } + + function getVarInt64() external view returns (int64) { + return varInt64; + } + + function setVarInt128(int128 newVar) external { + varInt128 = newVar; + } + + function getVarInt128() external view returns (int128) { + return varInt128; + } + + function setVarInt256(int256 newVar) external { + varInt256 = newVar; + } + + function getVarInt256() external view returns (int256) { + return varInt256; + } + + function setVarAddress(address newVar) external { + varAddress = newVar; + } + + function getVarAddress() external view returns (address) { + return varAddress; + } + + function setVarContractType() external { + varContractType = new ContractType(); + } + + function getVarContractType() external view returns (ContractType) { + return varContractType; + } + + function setVarBytes32(bytes32 newVar) external { + varBytes32 = newVar; + } + + function getVarBytes32() external view returns (bytes32) { + return varBytes32; + } + + function setVarString(string memory newVar) external { + varString = newVar; + } + + function getVarString() external view returns (string memory) { + return varString; + } + + function setVarEnum(Choices newVar) external { + varEnum = newVar; + } + + function getVarEnum() external view returns (Choices) { + return varEnum; + } + + function setVarIntArrDataAlloc(uint[] calldata newVar) external { + varIntArrDataAllocBefore = newVar; + + uint[] storage localVar = varIntArrDataAllocBefore; + localVar.pop(); + // pointer to varIntArrDataAllocBefore, so varIntArrDataAllocAfter should pop varIntArrDataAllocBefore as well + varIntArrDataAllocAfter = localVar; + } + + function getVarIntArrDataAlloc() external view returns (uint[] memory, uint[] memory) { + return (varIntArrDataAllocBefore, varIntArrDataAllocAfter); + } + + function deleteVarIntArrDataAlloc() external { + delete varIntArrDataAllocBefore; + delete varIntArrDataAllocAfter; + } + + function setVarStringConcat(string memory newVar) external { + varStringConcat = string.concat(varStringConcat, newVar); + } + + function getVarStringConcat() external view returns (string memory) { + return varStringConcat; + } + + function deleteVarStringConcat() external { + delete varStringConcat; + } + + function setVarContractStruct(ContractStruct memory newVar) external { + varContractStruct = newVar; + } + + function getVarContractStruct() external view returns (ContractStruct memory) { + return varContractStruct; + } + + function deleteVarContractStruct() external { + delete varContractStruct; + } +} diff --git a/hedera-node/test-clients/src/main/resource/contract/contracts/TestTokenCreateContract/TestTokenCreateContract.bin b/hedera-node/test-clients/src/main/resource/contract/contracts/TestTokenCreateContract/TestTokenCreateContract.bin new file mode 100644 index 000000000000..31ac1047f785 --- /dev/null +++ b/hedera-node/test-clients/src/main/resource/contract/contracts/TestTokenCreateContract/TestTokenCreateContract.bin @@ -0,0 +1 @@ +0x60c06040526009608090815268746f6b656e4e616d6560b81b60a0526002906200002a90826200026c565b5060408051808201909152600b81526a1d1bdad95b94de5b589bdb60aa1b60208201526003906200005c90826200026c565b50604080518082019091526004808252636d656d6f60e01b6020830152906200008690826200026c565b50600580546001600160a81b0319167008000000000000271000000000000003e8179055348015620000b757600080fd5b50600160208181527fa6eef7e35abe7026729641147f7915573c7e97b47efa546f5f6e3230263bcb499190915560027fcc69885fda6bcc1a4ace058b4a62bf5e179ea78fd58a1ccd71c22cc9b688792f5560047fd9d16d34ffb15ba3a3d852f0d403e2ce1d691fb54de27ac87cd2f993f3ec330f5560087f7dfe757ecd65cbd7922a9c0161e935dd7fdbcc0e999689c7d31633896b1fc60b5560107fedc95719e9a3b28dd8e80877cb5880a9be7de1a13fc8b05e7999683b6b567643557fe2689cd4a84e23ad2f564004f1c9013e9589d260bde6380aba3ca7e09e4df40c55600660005260407f8f331abe73332f95a25873e8b430885974c0409691f89d643119a11623a7924a5562000338565b634e487b7160e01b600052604160045260246000fd5b600181811c90821680620001f057607f821691505b6020821081036200021157634e487b7160e01b600052602260045260246000fd5b50919050565b601f82111562000267576000816000526020600020601f850160051c81016020861015620002425750805b601f850160051c820191505b8181101562000263578281556001016200024e565b5050505b505050565b81516001600160401b03811115620002885762000288620001c5565b620002a081620002998454620001db565b8462000217565b602080601f831160018114620002d85760008415620002bf5750858301515b600019600386901b1c1916600185901b17855562000263565b600085815260208120601f198616915b828110156200030957888601518255948401946001909101908401620002e8565b5085821015620003285787850151600019600388901b60f8161c191681555b5050505050600190811b01905550565b613f6b80620003486000396000f3fe6080604052600436106101145760003560e01c80638ba74da0116100a0578063d85f74c111610064578063d85f74c1146102db578063de84a779146102ee578063e9f732261461030e578063f4a01e5b14610321578063f848fec41461034157600080fd5b80638ba74da0146102575780638f74a17b1461026a5780639b23d3d914610295578063b35d417a146102b5578063cc265af4146102c857600080fd5b806336348de3116100e757806336348de3146101b45780634b5c6687146101d45780634d1769d6146101e7578063618dc65e1461021657806371de37071461024457600080fd5b80630488c939146101195780630fd2601e1461012e578063118741e71461016657806315dacbea14610194575b600080fd5b61012c610127366004612f56565b610361565b005b34801561013a57600080fd5b5061014e610149366004612f56565b6108d4565b60405160079190910b81526020015b60405180910390f35b34801561017257600080fd5b5061018661018136600461306a565b610931565b60405190815260200161015d565b3480156101a057600080fd5b5061014e6101af36600461311e565b610985565b3480156101c057600080fd5b506101866101cf3660046133e4565b610a79565b61012c6101e236600461352c565b610a85565b3480156101f357600080fd5b50610207610202366004613549565b610e8d565b60405161015d93929190613660565b34801561022257600080fd5b5061023661023136600461368b565b610f4e565b60405161015d92919061372b565b61012c61025236600461374c565b61106c565b61012c610265366004612f56565b6111d1565b61027d61027836600461368b565b61163d565b6040516001600160a01b03909116815260200161015d565b3480156102a157600080fd5b5061014e6102b036600461311e565b6119ff565b61012c6102c336600461368b565b611a44565b61012c6102d636600461368b565b611e15565b61012c6102e936600461352c565b611eec565b3480156102fa57600080fd5b50610207610309366004613549565b612287565b61027d61031c36600461368b565b612321565b34801561032d57600080fd5b5061018661033c366004612f56565b6123fb565b34801561034d57600080fd5b5061018661035c3660046137b0565b612407565b60408051600580825260c08201909252600091816020015b610381612ed1565b8152602001906001900390816103795790505090506103b460006006600060405180602001604052806000815250612463565b816000815181106103c7576103c76137f1565b60200260200101819052506103ee600160006040518060200160405280600081525061249a565b81600181518110610401576104016137f1565b6020026020010181905250610428600260006040518060200160405280600081525061249a565b8160028151811061043b5761043b6137f1565b6020026020010181905250610462600460006040518060200160405280600081525061249a565b81600381518110610475576104756137f1565b602002602001018190525061049c600360006040518060200160405280600081525061249a565b816004815181106104af576104af6137f1565b602002602001018190525060006040518060600160405280600060070b8152602001856001600160a01b03168152602001627a120060070b815250905060006040518061012001604052806002805461050790613807565b80601f016020809104026020016040519081016040528092919081815260200182805461053390613807565b80156105805780601f1061055557610100808354040283529160200191610580565b820191906000526020600020905b81548152906001019060200180831161056357829003601f168201915b505050505081526020016003805461059790613807565b80601f01602080910402602001604051908101604052809291908181526020018280546105c390613807565b80156106105780601f106105e557610100808354040283529160200191610610565b820191906000526020600020905b8154815290600101906020018083116105f357829003601f168201915b50505050508152602001866001600160a01b031681526020016004805461063690613807565b80601f016020809104026020016040519081016040528092919081815260200182805461066290613807565b80156106af5780601f10610684576101008083540402835291602001916106af565b820191906000526020600020905b81548152906001019060200180831161069257829003601f168201915b5050509183525050600160208201819052600554600160401b810460070b6040840152600160a01b900460ff16151560608301526080820186905260a090910184905290915060009060405190808252806020026020018201604052801561075157816020015b6040805160a0810182526000808252602080830182905292820181905260608201819052608082015282526000199092019101816107165790505b506040805160a081018252600181526001600160a01b0380891660208301526000928201839052606082018390528916608082015282519293509183919061079b5761079b6137f1565b6020908102919091010152604080516001808252818301909252600091816020015b6040805160c08101825260008082526020808301829052928201819052606082018190526080820181905260a082015282526000199092019101816107bd5790505090506040518060c00160405280600460070b8152602001600560070b8152602001600a60070b8152602001876001600160a01b03168152602001600015158152602001886001600160a01b031681525081600081518110610862576108626137f1565b602002602001018190525060008061087b8585856124cf565b90925090506016821461088d57600080fd5b6040516001600160a01b03821681527f7bb17726df1f3adee8aa00ba8e8bc5d6f182af3bbf77604639cb7f008dd3b4ed9060200160405180910390a1505050505050505050565b60006108e083836125f7565b604051600782900b81529091507f90a5cf4cffe88b4edbb041cfc7a8a812c48a5ec30b84640fb37690875168e3aa9060200160405180910390a1600781900b60161461092b57600080fd5b92915050565b600061093d83836126da565b90507f90a5cf4cffe88b4edbb041cfc7a8a812c48a5ec30b84640fb37690875168e3aa8160405161097091815260200190565b60405180910390a16016811461092b57600080fd5b6040516001600160a01b038581166024830152848116604483015283166064820152608481018290526000908190819061016790630aed65f560e11b9060a4015b60408051601f198184030181529181526020820180516001600160e01b03166001600160e01b0319909416939093179092529051610a049190613841565b6000604051808303816000865af19150503d8060008114610a41576040519150601f19603f3d011682016040523d82523d6000602084013e610a46565b606091505b509150915081610a57576015610a6b565b80806020019051810190610a6b919061386f565b60030b979650505050505050565b600061093d8383612706565b60408051600580825260c08201909252600091816020015b610aa5612ed1565b815260200190600190039081610a9d579050509050610ad860006006600060405180602001604052806000815250612463565b81600081518110610aeb57610aeb6137f1565b6020026020010181905250610b12600160006040518060200160405280600081525061249a565b81600181518110610b2557610b256137f1565b6020026020010181905250610b4c600260006040518060200160405280600081525061249a565b81600281518110610b5f57610b5f6137f1565b6020026020010181905250610b86600360006040518060200160405280600081525061249a565b81600381518110610b9957610b996137f1565b6020026020010181905250610bc0600460006040518060200160405280600081525061249a565b81600481518110610bd357610bd36137f1565b602002602001018190525060006040518060600160405280600060070b8152602001846001600160a01b03168152602001627a120060070b8152509050600060405180610120016040528060028054610c2b90613807565b80601f0160208091040260200160405190810160405280929190818152602001828054610c5790613807565b8015610ca45780601f10610c7957610100808354040283529160200191610ca4565b820191906000526020600020905b815481529060010190602001808311610c8757829003601f168201915b5050505050815260200160038054610cbb90613807565b80601f0160208091040260200160405190810160405280929190818152602001828054610ce790613807565b8015610d345780601f10610d0957610100808354040283529160200191610d34565b820191906000526020600020905b815481529060010190602001808311610d1757829003601f168201915b50505050508152602001856001600160a01b0316815260200160048054610d5a90613807565b80601f0160208091040260200160405190810160405280929190818152602001828054610d8690613807565b8015610dd35780601f10610da857610100808354040283529160200191610dd3565b820191906000526020600020905b815481529060010190602001808311610db657829003601f168201915b505050918352505060016020820152600554600160401b8104600790810b604084015260ff600160a01b830416151560608401526080830187905260a09092018590529192506000918291610e3791859181900b90600160801b900460030b612732565b909250905060168214610e4957600080fd5b6040516001600160a01b03821681527f7bb17726df1f3adee8aa00ba8e8bc5d6f182af3bbf77604639cb7f008dd3b4ed9060200160405180910390a1505050505050565b6000806060610e9d86868661279f565b60405183815292955090935091507f90a5cf4cffe88b4edbb041cfc7a8a812c48a5ec30b84640fb37690875168e3aa9060200160405180910390a160168314610ee557600080fd5b7ffc6b20023c4bac8ff1c48c1693e0cea5cd3c2163e9c2da41c58f17dd6d9f163d8282604051610f1692919061388a565b60405180910390a1610f4486303384600081518110610f3757610f376137f1565b602002602001015161289d565b5093509350939050565b600060606000806101676001600160a01b031663618dc65e60e01b8787604051602401610f7c9291906138a6565b60408051601f198184030181529181526020820180516001600160e01b03166001600160e01b0319909416939093179092529051610fba9190613841565b6000604051808303816000865af19150503d8060008114610ff7576040519150601f19603f3d011682016040523d82523d6000602084013e610ffc565b606091505b50915091507f4af4780e06fe8cb9df64b0794fa6f01399af979175bb988e35e0e57e594567bc82826040516110329291906138c8565b60405180910390a1816110565760156040518060200160405280600081525061105a565b6016815b60039190910b97909650945050505050565b604051638f74a17b60e01b81526000903090638f74a17b90349061109690889088906004016138a6565b60206040518083038185885af11580156110b4573d6000803e3d6000fd5b50505050506040513d601f19601f820116820180604052508101906110d991906138e3565b60405163f4a01e5b60e01b81523360048201526001600160a01b0382166024820152909150309063f4a01e5b906044016020604051808303816000875af1158015611128573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061114c9190613900565b506040516307e9300f60e11b81526001600160a01b03821660048201523360248201523090630fd2601e906044016020604051808303816000875af1158015611199573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906111bd9190613919565b506111ca813033856128e5565b5050505050565b604080516001808252818301909252600091816020015b6111f0612ed1565b8152602001906001900390816111e8579050509050611222600080600060405180602001604052806000815250612463565b81600081518110611235576112356137f1565b602002602001018190525060006040518060600160405280600060070b8152602001856001600160a01b03168152602001627a120060070b815250905060006040518061012001604052806002805461128d90613807565b80601f01602080910402602001604051908101604052809291908181526020018280546112b990613807565b80156113065780601f106112db57610100808354040283529160200191611306565b820191906000526020600020905b8154815290600101906020018083116112e957829003601f168201915b505050505081526020016003805461131d90613807565b80601f016020809104026020016040519081016040528092919081815260200182805461134990613807565b80156113965780601f1061136b57610100808354040283529160200191611396565b820191906000526020600020905b81548152906001019060200180831161137957829003601f168201915b50505050508152602001866001600160a01b03168152602001600480546113bc90613807565b80601f01602080910402602001604051908101604052809291908181526020018280546113e890613807565b80156114355780601f1061140a57610100808354040283529160200191611435565b820191906000526020600020905b81548152906001019060200180831161141857829003601f168201915b5050509183525050600160208201819052600554600160401b900460070b6040808401919091526000606084018190526080840188905260a09093018690528051828152808201909152929350909190816020015b6040805160a08101825260008082526020808301829052928201819052606082018190526080820152825260001990920191018161148a5750506040805160a081018252600181526001600160a01b0388811660208301526000928201839052606082018390528916608082015282519293509183919061150d5761150d6137f1565b6020908102919091010152604080516001808252818301909252600091816020015b6040805160c08101825260008082526020808301829052928201819052606082018190526080820181905260a0820152825260001990920191018161152f5750506040805160c0810182526004815260056020820152600a91810191909152601e60608201526000608082018190526001600160a01b038a1660a0830152825192935090918391906115c3576115c36137f1565b602090810291909101015260055460009081906115f3908690600781900b90600160801b900460030b878761292d565b915091507f90a5cf4cffe88b4edbb041cfc7a8a812c48a5ec30b84640fb37690875168e3aa8260405161162891815260200190565b60405180910390a16016821461088d57600080fd5b60408051600580825260c08201909252600091829190816020015b611660612ed1565b81526020019060019003908161165857905050905061168460006006600386612463565b81600081518110611697576116976137f1565b60200260200101819052506116af600160038561249a565b816001815181106116c2576116c26137f1565b60200260200101819052506116da600260038561249a565b816002815181106116ed576116ed6137f1565b6020026020010181905250611705600460038561249a565b81600381518110611718576117186137f1565b602002602001018190525061172f6003808561249a565b81600481518110611742576117426137f1565b602002602001018190525060006040518060600160405280600060070b8152602001866001600160a01b03168152602001627a120060070b815250905060006040518061012001604052806002805461179a90613807565b80601f01602080910402602001604051908101604052809291908181526020018280546117c690613807565b80156118135780601f106117e857610100808354040283529160200191611813565b820191906000526020600020905b8154815290600101906020018083116117f657829003601f168201915b505050505081526020016003805461182a90613807565b80601f016020809104026020016040519081016040528092919081815260200182805461185690613807565b80156118a35780601f10611878576101008083540402835291602001916118a3565b820191906000526020600020905b81548152906001019060200180831161188657829003601f168201915b50505050508152602001876001600160a01b03168152602001600480546118c990613807565b80601f01602080910402602001604051908101604052809291908181526020018280546118f590613807565b80156119425780601f1061191757610100808354040283529160200191611942565b820191906000526020600020905b81548152906001019060200180831161192557829003601f168201915b505050918352505060016020820152600554600160401b8104600790810b604084015260ff600160a01b830416151560608401526080830187905260a090920185905291925060009182916119a691859181900b90600160801b900460030b612732565b9092509050601682146119b857600080fd5b6040516001600160a01b03821681527f7bb17726df1f3adee8aa00ba8e8bc5d6f182af3bbf77604639cb7f008dd3b4ed9060200160405180910390a1979650505050505050565b6040516001600160a01b038581166024830152848116604483015283166064820152608481018290526000908190819061016790639b23d3d960e01b9060a4016109c6565b60408051600680825260e08201909252600091816020015b611a64612ed1565b815260200190600190039081611a5c579050509050611a8860006006600385612463565b81600081518110611a9b57611a9b6137f1565b6020026020010181905250611ab3600160038461249a565b81600181518110611ac657611ac66137f1565b6020026020010181905250611ade600260038461249a565b81600281518110611af157611af16137f1565b6020026020010181905250611b09600460038461249a565b81600381518110611b1c57611b1c6137f1565b6020026020010181905250611b336003808461249a565b81600481518110611b4657611b466137f1565b6020026020010181905250611b5e6000600130612a5b565b81600581518110611b7157611b716137f1565b602002602001018190525060006040518060600160405280600060070b8152602001856001600160a01b03168152602001627a120060070b8152509050600060405180610120016040528060028054611bc990613807565b80601f0160208091040260200160405190810160405280929190818152602001828054611bf590613807565b8015611c425780601f10611c1757610100808354040283529160200191611c42565b820191906000526020600020905b815481529060010190602001808311611c2557829003601f168201915b5050505050815260200160038054611c5990613807565b80601f0160208091040260200160405190810160405280929190818152602001828054611c8590613807565b8015611cd25780601f10611ca757610100808354040283529160200191611cd2565b820191906000526020600020905b815481529060010190602001808311611cb557829003601f168201915b50505050508152602001866001600160a01b0316815260200160048054611cf890613807565b80601f0160208091040260200160405190810160405280929190818152602001828054611d2490613807565b8015611d715780601f10611d4657610100808354040283529160200191611d71565b820191906000526020600020905b815481529060010190602001808311611d5457829003601f168201915b505050918352505060016020820152600554600160401b810460070b6040830152600160a01b900460ff16151560608201526080810185905260a0018390529050600080611dbe83612a86565b909250905060168214611dd057600080fd5b6040516001600160a01b03821681527f7bb17726df1f3adee8aa00ba8e8bc5d6f182af3bbf77604639cb7f008dd3b4ed9060200160405180910390a150505050505050565b60408051600480825260a08201909252600091816020015b611e35612ed1565b815260200190600190039081611e2d579050509050611e5960006006600385612463565b81600081518110611e6c57611e6c6137f1565b6020026020010181905250611e84600260038461249a565b81600181518110611e9757611e976137f1565b6020026020010181905250611eaf600460038461249a565b81600281518110611ec257611ec26137f1565b6020026020010181905250611ed96003808461249a565b81600381518110611b7157611b716137f1565b60408051600580825260c08201909252600091816020015b611f0c612ed1565b815260200190600190039081611f04579050509050611f3f60006006600060405180602001604052806000815250612463565b81600081518110611f5257611f526137f1565b6020026020010181905250611f79600160006040518060200160405280600081525061249a565b81600181518110611f8c57611f8c6137f1565b6020026020010181905250611fb3600260006040518060200160405280600081525061249a565b81600281518110611fc657611fc66137f1565b6020026020010181905250611fed600460006040518060200160405280600081525061249a565b81600381518110612000576120006137f1565b6020026020010181905250612027600360006040518060200160405280600081525061249a565b8160048151811061203a5761203a6137f1565b602002602001018190525060006040518060600160405280600060070b8152602001846001600160a01b03168152602001627a120060070b815250905060006040518061012001604052806002805461209290613807565b80601f01602080910402602001604051908101604052809291908181526020018280546120be90613807565b801561210b5780601f106120e05761010080835404028352916020019161210b565b820191906000526020600020905b8154815290600101906020018083116120ee57829003601f168201915b505050505081526020016003805461212290613807565b80601f016020809104026020016040519081016040528092919081815260200182805461214e90613807565b801561219b5780601f106121705761010080835404028352916020019161219b565b820191906000526020600020905b81548152906001019060200180831161217e57829003601f168201915b50505050508152602001856001600160a01b03168152602001600480546121c190613807565b80601f01602080910402602001604051908101604052809291908181526020018280546121ed90613807565b801561223a5780601f1061220f5761010080835404028352916020019161223a565b820191906000526020600020905b81548152906001019060200180831161221d57829003601f168201915b505050918352505060016020820152600554600160401b810460070b6040830152600160a01b900460ff16151560608201526080810185905260a0018390529050600080610e3783612a86565b600080606061229786868661279f565b60405183815292955090935091507f90a5cf4cffe88b4edbb041cfc7a8a812c48a5ec30b84640fb37690875168e3aa9060200160405180910390a1601683146122df57600080fd5b7ffc6b20023c4bac8ff1c48c1693e0cea5cd3c2163e9c2da41c58f17dd6d9f163d828260405161231092919061388a565b60405180910390a193509350939050565b60408051600480825260a08201909252600091829190816020015b612344612ed1565b81526020019060019003908161233c57905050905061236860006006600386612463565b8160008151811061237b5761237b6137f1565b6020026020010181905250612393600260038561249a565b816001815181106123a6576123a66137f1565b60200260200101819052506123be600460038561249a565b816002815181106123d1576123d16137f1565b60200260200101819052506123e86003808561249a565b81600381518110611742576117426137f1565b600061093d8383612b96565b6000612414848484612bcc565b90507f90a5cf4cffe88b4edbb041cfc7a8a812c48a5ec30b84640fb37690875168e3aa8160405161244791815260200190565b60405180910390a16016811461245c57600080fd5b9392505050565b61246b612ed1565b60405180604001604052806124808787612ca5565b815260200161248f8585612ce0565b905295945050505050565b6124a2612ed1565b60405180604001604052806124b686612df1565b81526020016124c58585612ce0565b9052949350505050565b600080848061010001516000015160070b60001480156124f957506101008101516040015160070b155b1561250f576101008101516276a7006040909101525b6000806101676001600160a01b03163463abb54eb560e01b8a8a8a60405160240161253c93929190613b6d565b60408051601f198184030181529181526020820180516001600160e01b03166001600160e01b031990941693909317909252905161257a9190613841565b60006040518083038185875af1925050503d80600081146125b7576040519150601f19603f3d011682016040523d82523d6000602084013e6125bc565b606091505b5091509150816125cf57601560006125e3565b808060200190518101906125e39190613c26565b60039190910b999098509650505050505050565b6040516001600160a01b038381166024830152821660448201526000908190819061016790638f8d7f9960e01b906064015b60408051601f198184030181529181526020820180516001600160e01b03166001600160e01b03199094169390931790925290516126679190613841565b6000604051808303816000865af19150503d80600081146126a4576040519150601f19603f3d011682016040523d82523d6000602084013e6126a9565b606091505b5091509150816126ba5760156126ce565b808060200190518101906126ce919061386f565b60030b95945050505050565b60008060006101676001600160a01b0316632e63879b60e01b8686604051602401612629929190613c52565b60008060006101676001600160a01b0316637d305cfa60e01b8686604051602401612629929190613caf565b600080848061010001516000015160070b600014801561275c57506101008101516040015160070b155b15612772576101008101516276a7006040909101525b6000806101676001600160a01b031634630fb65bf360e01b8a8a8a60405160240161253c93929190613cd1565b60008060606000806101676001600160a01b031663e0f4059a60e01b8989896040516024016127d093929190613d00565b60408051601f198184030181529181526020820180516001600160e01b03166001600160e01b031990941693909317909252905161280e9190613841565b6000604051808303816000865af19150503d806000811461284b576040519150601f19603f3d011682016040523d82523d6000602084013e612850565b606091505b509150915081612873576040805160008082526020820190925260159190612887565b808060200190518101906128879190613d7f565b60039290920b9a90995090975095505050505050565b6040516001600160a01b038581166024830152848116604483015283166064820152600782900b60848201526000908190819061016790635cfc901160e01b9060a4016109c6565b6040516001600160a01b038581166024830152848116604483015283166064820152600782900b6084820152600090819081906101679063eca3691760e01b9060a4016109c6565b600080868061010001516000015160070b600014801561295757506101008101516040015160070b155b1561296d576101008101516276a7006040909101525b6000806101676001600160a01b031634632af0c59a60e01b8c8c8c8c8c60405160240161299e959493929190613e3f565b60408051601f198184030181529181526020820180516001600160e01b03166001600160e01b03199094169390931790925290516129dc9190613841565b60006040518083038185875af1925050503d8060008114612a19576040519150601f19603f3d011682016040523d82523d6000602084013e612a1e565b606091505b509150915081612a315760156000612a45565b80806020019051810190612a459190613c26565b60039190910b9b909a5098505050505050505050565b612a63612ed1565b6040518060400160405280612a7786612df1565b81526020016124c58585612e30565b600080828061010001516000015160070b6000148015612ab057506101008101516040015160070b155b15612ac6576101008101516276a7006040909101525b6000806101676001600160a01b03163463ea83f29360e01b88604051602401612aef9190613f0c565b60408051601f198184030181529181526020820180516001600160e01b03166001600160e01b0319909416939093179092529051612b2d9190613841565b60006040518083038185875af1925050503d8060008114612b6a576040519150601f19603f3d011682016040523d82523d6000602084013e612b6f565b606091505b509150915081612b82576015600061105a565b8080602001905181019061105a9190613c26565b6040516001600160a01b03838116602483015282166044820152600090819081906101679063248a35ef60e11b90606401612629565b604080516001600160a01b03858116602483015284166044820152606480820184905282518083039091018152608490910182526020810180516001600160e01b031663e1f21c6760e01b17905290516000918291829161016791612c319190613841565b6000604051808303816000865af19150503d8060008114612c6e576040519150601f19603f3d011682016040523d82523d6000602084013e612c73565b606091505b509150915081612c84576015612c98565b80806020019051810190612c98919061386f565b60030b9695505050505050565b6000612cc9836006811115612cbc57612cbc613f1f565b600160ff9091161b821790565b905061245c826006811115612cbc57612cbc613f1f565b612d266040518060a0016040528060001515815260200160006001600160a01b03168152602001606081526020016060815260200160006001600160a01b031681525090565b6000836004811115612d3a57612d3a613f1f565b03612d48576001815261092b565b6001836004811115612d5c57612d5c613f1f565b03612d77576000546001600160a01b0316602082015261092b565b6002836004811115612d8b57612d8b613f1f565b03612d9c576040810182905261092b565b6003836004811115612db057612db0613f1f565b03612dc1576060810182905261092b565b6004836004811115612dd557612dd5613f1f565b0361092b576000546001600160a01b0316608082015292915050565b600060016000836006811115612e0957612e09613f1f565b6006811115612e1a57612e1a613f1f565b8152602001908152602001600020549050919050565b612e766040518060a0016040528060001515815260200160006001600160a01b03168152602001606081526020016060815260200160006001600160a01b031681525090565b6001836004811115612e8a57612e8a613f1f565b03612ea3576001600160a01b038216602082015261092b565b6004836004811115612eb757612eb7613f1f565b0361092b576001600160a01b038216608082015292915050565b604051806040016040528060008152602001612f296040518060a0016040528060001515815260200160006001600160a01b03168152602001606081526020016060815260200160006001600160a01b031681525090565b905290565b6001600160a01b0381168114612f4357600080fd5b50565b8035612f5181612f2e565b919050565b60008060408385031215612f6957600080fd5b8235612f7481612f2e565b91506020830135612f8481612f2e565b809150509250929050565b634e487b7160e01b600052604160045260246000fd5b6040805190810167ffffffffffffffff81118282101715612fc857612fc8612f8f565b60405290565b60405160a0810167ffffffffffffffff81118282101715612fc857612fc8612f8f565b604051610120810167ffffffffffffffff81118282101715612fc857612fc8612f8f565b604051601f8201601f1916810167ffffffffffffffff8111828210171561303e5761303e612f8f565b604052919050565b600067ffffffffffffffff82111561306057613060612f8f565b5060051b60200190565b6000806040838503121561307d57600080fd5b823561308881612f2e565b915060208381013567ffffffffffffffff8111156130a557600080fd5b8401601f810186136130b657600080fd5b80356130c96130c482613046565b613015565b81815260059190911b820183019083810190888311156130e857600080fd5b928401925b8284101561310f57833561310081612f2e565b825292840192908401906130ed565b80955050505050509250929050565b6000806000806080858703121561313457600080fd5b843561313f81612f2e565b9350602085013561314f81612f2e565b9250604085013561315f81612f2e565b9396929550929360600135925050565b600082601f83011261318057600080fd5b813567ffffffffffffffff81111561319a5761319a612f8f565b6131ad601f8201601f1916602001613015565b8181528460208386010111156131c257600080fd5b816020850160208301376000918101602001919091529392505050565b80358015158114612f5157600080fd5b8060070b8114612f4357600080fd5b8035612f51816131ef565b600082601f83011261321a57600080fd5b8135602061322a6130c483613046565b82815260059290921b8401810191818101908684111561324957600080fd5b8286015b8481101561336357803567ffffffffffffffff8082111561326d57600080fd5b908801906040601f19838c03810182131561328757600080fd5b61328f612fa5565b88850135815282850135848111156132a657600080fd5b949094019360a0858e03830112156132be5760008081fd5b6132c6612fce565b91506132d38986016131df565b8252828501356132e281612f2e565b828a0152606085810135858111156132fa5760008081fd5b6133088f8c838a010161316f565b85850152506080935083860135858111156133235760008081fd5b6133318f8c838a010161316f565b82850152505060a0850135945061334785612f2e565b918101939093528087019290925250835291830191830161324d565b509695505050505050565b60006060828403121561338057600080fd5b6040516060810181811067ffffffffffffffff821117156133a3576133a3612f8f565b60405290508082356133b4816131ef565b815260208301356133c481612f2e565b602082015260408301356133d7816131ef565b6040919091015292915050565b600080604083850312156133f757600080fd5b823561340281612f2e565b9150602083013567ffffffffffffffff8082111561341f57600080fd5b90840190610160828703121561343457600080fd5b61343c612ff1565b82358281111561344b57600080fd5b6134578882860161316f565b82525060208301358281111561346c57600080fd5b6134788882860161316f565b60208301525061348a60408401612f46565b60408201526060830135828111156134a157600080fd5b6134ad8882860161316f565b6060830152506134bf608084016131df565b60808201526134d060a084016131fe565b60a08201526134e160c084016131df565b60c082015260e0830135828111156134f857600080fd5b61350488828601613209565b60e083015250610100915061351b8783850161336e565b828201528093505050509250929050565b60006020828403121561353e57600080fd5b813561245c81612f2e565b60008060006060848603121561355e57600080fd5b833561356981612f2e565b925060208481013561357a816131ef565b9250604085013567ffffffffffffffff8082111561359757600080fd5b818701915087601f8301126135ab57600080fd5b81356135b96130c482613046565b81815260059190911b8301840190848101908a8311156135d857600080fd5b8585015b83811015613610578035858111156135f45760008081fd5b6136028d89838a010161316f565b8452509186019186016135dc565b508096505050505050509250925092565b60008151808452602080850194506020840160005b8381101561365557815160070b87529582019590820190600101613636565b509495945050505050565b8381528260070b60208201526060604082015260006136826060830184613621565b95945050505050565b6000806040838503121561369e57600080fd5b82356136a981612f2e565b9150602083013567ffffffffffffffff8111156136c557600080fd5b6136d18582860161316f565b9150509250929050565b60005b838110156136f65781810151838201526020016136de565b50506000910152565b600081518084526137178160208601602086016136db565b601f01601f19169290920160200192915050565b82815260406020820152600061374460408301846136ff565b949350505050565b60008060006060848603121561376157600080fd5b833561376c81612f2e565b9250602084013567ffffffffffffffff81111561378857600080fd5b6137948682870161316f565b92505060408401356137a5816131ef565b809150509250925092565b6000806000606084860312156137c557600080fd5b83356137d081612f2e565b925060208401356137e081612f2e565b929592945050506040919091013590565b634e487b7160e01b600052603260045260246000fd5b600181811c9082168061381b57607f821691505b60208210810361383b57634e487b7160e01b600052602260045260246000fd5b50919050565b600082516138538184602087016136db565b9190910192915050565b8051600381900b8114612f5157600080fd5b60006020828403121561388157600080fd5b61245c8261385d565b8260070b81526040602082015260006137446040830184613621565b6001600160a01b038316815260406020820152600061374460408301846136ff565b821515815260406020820152600061374460408301846136ff565b6000602082840312156138f557600080fd5b815161245c81612f2e565b60006020828403121561391257600080fd5b5051919050565b60006020828403121561392b57600080fd5b815161245c816131ef565b600082825180855260208086019550808260051b84010181860160005b848110156139f057601f198684030189528151604081518552858201519150808686015281511515818601528582015160606001600160a01b038083168289015283850151935060a09250608083818a01526139b260e08a01866136ff565b92860151898403603f1901858b01529294506139ce85846136ff565b9501511660c09790970196909652505098840198925090830190600101613953565b5090979650505050505050565b60006101608251818552613a13828601826136ff565b91505060208301518482036020860152613a2d82826136ff565b9150506040830151613a4a60408601826001600160a01b03169052565b5060608301518482036060860152613a6282826136ff565b9150506080830151613a78608086018215159052565b5060a0830151613a8d60a086018260070b9052565b5060c0830151613aa160c086018215159052565b5060e083015184820360e0860152613ab98282613936565b91505061010080840151613af582870182805160070b82526001600160a01b036020820151166020830152604081015160070b60408301525050565b5090949350505050565b60008151808452602080850194506020840160005b83811015613655578151805160070b8852838101516001600160a01b03908116858a01526040808301511515908a01526060808301511515908a0152608091820151169088015260a09096019590820190600101613b14565b60006060808352613b8160608401876139fd565b602084820381860152613b948288613aff565b9150604085830360408701528287518085528385019150838901945060005b81811015613c165785518051600790810b855286820151810b8786015285820151900b85850152878101516001600160a01b039081168986015260808083015115159086015260a09182015116908401529484019460c090920191600101613bb3565b50909a9950505050505050505050565b60008060408385031215613c3957600080fd5b613c428361385d565b91506020830151612f8481612f2e565b6000604082016001600160a01b03808616845260206040602086015282865180855260608701915060208801945060005b81811015613ca1578551851683529483019491830191600101613c83565b509098975050505050505050565b6001600160a01b038316815260406020820152600061374460408301846139fd565b606081526000613ce460608301866139fd565b90508360070b60208301528260030b6040830152949350505050565b6000606082016001600160a01b038616835260208560070b60208501526060604085015281855180845260808601915060808160051b87010193506020870160005b82811015613d7057607f19888703018452613d5e8683516136ff565b95509284019290840190600101613d42565b50939998505050505050505050565b600080600060608486031215613d9457600080fd5b613d9d8461385d565b9250602080850151613dae816131ef565b604086015190935067ffffffffffffffff811115613dcb57600080fd5b8501601f81018713613ddc57600080fd5b8051613dea6130c482613046565b81815260059190911b82018301908381019089831115613e0957600080fd5b928401925b82841015613e30578351613e21816131ef565b82529284019290840190613e0e565b80955050505050509250925092565b600060a0808352613e5360a08401896139fd565b602060078960070b8287015260408960030b604088015260608785036060890152613e7e858b613aff565b9450608088860360808a0152858a518088528688019150868c01975060005b81811015613ef75788518051880b845288810151880b8985015286810151880b8785015285810151880b86850152848101511515858501528a01516001600160a01b03168a8401529787019760c090920191600101613e9d565b50909f9e505050505050505050505050505050565b60208152600061245c60208301846139fd565b634e487b7160e01b600052602160045260246000fdfea264697066735822122086358a260252798c8ac40863c5ad0f37d3e9a55cf4ce9fb75f70d11b779b7b0a64736f6c63430008170033 \ No newline at end of file diff --git a/hedera-node/test-clients/src/main/resource/contract/contracts/TestTokenCreateContract/TestTokenCreateContract.json b/hedera-node/test-clients/src/main/resource/contract/contracts/TestTokenCreateContract/TestTokenCreateContract.json new file mode 100644 index 000000000000..846eb5c704a6 --- /dev/null +++ b/hedera-node/test-clients/src/main/resource/contract/contracts/TestTokenCreateContract/TestTokenCreateContract.json @@ -0,0 +1,651 @@ +[ + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "bool", + "name": "", + "type": "bool" + }, + { + "indexed": false, + "internalType": "bytes", + "name": "", + "type": "bytes" + } + ], + "name": "CallResponseEvent", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "tokenAddress", + "type": "address" + } + ], + "name": "CreatedToken", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "bool", + "name": "kycGranted", + "type": "bool" + } + ], + "name": "KycGranted", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "int64", + "name": "newTotalSupply", + "type": "int64" + }, + { + "indexed": false, + "internalType": "int64[]", + "name": "serialNumbers", + "type": "int64[]" + } + ], + "name": "MintedToken", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "int256", + "name": "responseCode", + "type": "int256" + } + ], + "name": "ResponseCode", + "type": "event" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "token", + "type": "address" + }, + { + "internalType": "address", + "name": "spender", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "approvePublic", + "outputs": [ + { + "internalType": "int256", + "name": "responseCode", + "type": "int256" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "account", + "type": "address" + }, + { + "internalType": "address", + "name": "token", + "type": "address" + } + ], + "name": "associateTokenPublic", + "outputs": [ + { + "internalType": "int256", + "name": "responseCode", + "type": "int256" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "account", + "type": "address" + }, + { + "internalType": "address[]", + "name": "tokens", + "type": "address[]" + } + ], + "name": "associateTokensPublic", + "outputs": [ + { + "internalType": "int256", + "name": "responseCode", + "type": "int256" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "treasury", + "type": "address" + } + ], + "name": "createFungibleTokenPublic", + "outputs": [], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "treasury", + "type": "address" + }, + { + "internalType": "address", + "name": "fixedFeeTokenAddress", + "type": "address" + } + ], + "name": "createFungibleTokenWithCustomFeesPublic", + "outputs": [], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "treasury", + "type": "address" + }, + { + "internalType": "bytes", + "name": "adminKey", + "type": "bytes" + }, + { + "internalType": "int64", + "name": "amount", + "type": "int64" + } + ], + "name": "createFungibleTokenWithSECP256K1AdminKeyAssociateAndTransferToAddressPublic", + "outputs": [], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "treasury", + "type": "address" + }, + { + "internalType": "bytes", + "name": "adminKey", + "type": "bytes" + } + ], + "name": "createFungibleTokenWithSECP256K1AdminKeyPublic", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "treasury", + "type": "address" + }, + { + "internalType": "bytes", + "name": "adminKey", + "type": "bytes" + } + ], + "name": "createFungibleTokenWithSECP256K1AdminKeyWithoutKYCPublic", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "treasury", + "type": "address" + } + ], + "name": "createNonFungibleTokenPublic", + "outputs": [], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "treasury", + "type": "address" + }, + { + "internalType": "address", + "name": "fixedFeeTokenAddress", + "type": "address" + } + ], + "name": "createNonFungibleTokenWithCustomFeesPublic", + "outputs": [], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "treasury", + "type": "address" + }, + { + "internalType": "bytes", + "name": "adminKey", + "type": "bytes" + } + ], + "name": "createNonFungibleTokenWithSECP256K1AdminKeyPublic", + "outputs": [], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "treasury", + "type": "address" + }, + { + "internalType": "bytes", + "name": "adminKey", + "type": "bytes" + } + ], + "name": "createNonFungibleTokenWithSECP256K1AdminKeyWithoutKYCPublic", + "outputs": [], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "token", + "type": "address" + }, + { + "internalType": "address", + "name": "account", + "type": "address" + } + ], + "name": "grantTokenKycPublic", + "outputs": [ + { + "internalType": "int64", + "name": "responseCode", + "type": "int64" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "token", + "type": "address" + }, + { + "internalType": "int64", + "name": "amount", + "type": "int64" + }, + { + "internalType": "bytes[]", + "name": "metadata", + "type": "bytes[]" + } + ], + "name": "mintTokenPublic", + "outputs": [ + { + "internalType": "int256", + "name": "responseCode", + "type": "int256" + }, + { + "internalType": "int64", + "name": "newTotalSupply", + "type": "int64" + }, + { + "internalType": "int64[]", + "name": "serialNumbers", + "type": "int64[]" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "token", + "type": "address" + }, + { + "internalType": "int64", + "name": "amount", + "type": "int64" + }, + { + "internalType": "bytes[]", + "name": "metadata", + "type": "bytes[]" + } + ], + "name": "mintTokenToAddressPublic", + "outputs": [ + { + "internalType": "int256", + "name": "responseCode", + "type": "int256" + }, + { + "internalType": "int64", + "name": "newTotalSupply", + "type": "int64" + }, + { + "internalType": "int64[]", + "name": "serialNumbers", + "type": "int64[]" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "token", + "type": "address" + }, + { + "internalType": "bytes", + "name": "encodedFunctionSelector", + "type": "bytes" + } + ], + "name": "redirectForToken", + "outputs": [ + { + "internalType": "int256", + "name": "responseCode", + "type": "int256" + }, + { + "internalType": "bytes", + "name": "response", + "type": "bytes" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "token", + "type": "address" + }, + { + "internalType": "address", + "name": "from", + "type": "address" + }, + { + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "transferFrom", + "outputs": [ + { + "internalType": "int64", + "name": "responseCode", + "type": "int64" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "token", + "type": "address" + }, + { + "internalType": "address", + "name": "from", + "type": "address" + }, + { + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "internalType": "uint256", + "name": "serialNumber", + "type": "uint256" + } + ], + "name": "transferFromNFT", + "outputs": [ + { + "internalType": "int64", + "name": "responseCode", + "type": "int64" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "token", + "type": "address" + }, + { + "components": [ + { + "internalType": "string", + "name": "name", + "type": "string" + }, + { + "internalType": "string", + "name": "symbol", + "type": "string" + }, + { + "internalType": "address", + "name": "treasury", + "type": "address" + }, + { + "internalType": "string", + "name": "memo", + "type": "string" + }, + { + "internalType": "bool", + "name": "tokenSupplyType", + "type": "bool" + }, + { + "internalType": "int64", + "name": "maxSupply", + "type": "int64" + }, + { + "internalType": "bool", + "name": "freezeDefault", + "type": "bool" + }, + { + "components": [ + { + "internalType": "uint256", + "name": "keyType", + "type": "uint256" + }, + { + "components": [ + { + "internalType": "bool", + "name": "inheritAccountKey", + "type": "bool" + }, + { + "internalType": "address", + "name": "contractId", + "type": "address" + }, + { + "internalType": "bytes", + "name": "ed25519", + "type": "bytes" + }, + { + "internalType": "bytes", + "name": "ECDSA_secp256k1", + "type": "bytes" + }, + { + "internalType": "address", + "name": "delegatableContractId", + "type": "address" + } + ], + "internalType": "struct IHederaTokenService.KeyValue", + "name": "key", + "type": "tuple" + } + ], + "internalType": "struct IHederaTokenService.TokenKey[]", + "name": "tokenKeys", + "type": "tuple[]" + }, + { + "components": [ + { + "internalType": "int64", + "name": "second", + "type": "int64" + }, + { + "internalType": "address", + "name": "autoRenewAccount", + "type": "address" + }, + { + "internalType": "int64", + "name": "autoRenewPeriod", + "type": "int64" + } + ], + "internalType": "struct IHederaTokenService.Expiry", + "name": "expiry", + "type": "tuple" + } + ], + "internalType": "struct IHederaTokenService.HederaToken", + "name": "tokenInfo", + "type": "tuple" + } + ], + "name": "updateTokenInfoPublic", + "outputs": [ + { + "internalType": "int256", + "name": "responseCode", + "type": "int256" + } + ], + "stateMutability": "nonpayable", + "type": "function" + } +] \ No newline at end of file diff --git a/hedera-node/test-clients/src/main/resource/contract/contracts/TestTokenCreateContract/TestTokenCreateContract.sol b/hedera-node/test-clients/src/main/resource/contract/contracts/TestTokenCreateContract/TestTokenCreateContract.sol new file mode 100644 index 000000000000..1884183de360 --- /dev/null +++ b/hedera-node/test-clients/src/main/resource/contract/contracts/TestTokenCreateContract/TestTokenCreateContract.sol @@ -0,0 +1,339 @@ +// SPDX-License-Identifier: Apache-2.0 +pragma solidity >=0.5.0 <0.9.0; +pragma experimental ABIEncoderV2; + +import "../../HederaTokenService.sol"; +import "../../ExpiryHelper.sol"; +import "../../KeyHelper.sol"; + +contract TestTokenCreateContract is HederaTokenService, ExpiryHelper, KeyHelper { + + string name = "tokenName"; + string symbol = "tokenSymbol"; + string memo = "memo"; + int64 initialTotalSupply = 1000; + int64 maxSupply = 10000; + int32 decimals = 8; + bool freezeDefaultStatus = false; + + event ResponseCode(int responseCode); + event CreatedToken(address tokenAddress); + event MintedToken(int64 newTotalSupply, int64[] serialNumbers); + event KycGranted(bool kycGranted); + + function createFungibleTokenPublic( + address treasury + ) public payable { + IHederaTokenService.TokenKey[] memory keys = new IHederaTokenService.TokenKey[](5); + keys[0] = getSingleKey(KeyType.ADMIN, KeyType.PAUSE, KeyValueType.INHERIT_ACCOUNT_KEY, bytes("")); + keys[1] = getSingleKey(KeyType.KYC, KeyValueType.INHERIT_ACCOUNT_KEY, bytes("")); + keys[2] = getSingleKey(KeyType.FREEZE, KeyValueType.INHERIT_ACCOUNT_KEY, bytes("")); + keys[3] = getSingleKey(KeyType.WIPE, KeyValueType.INHERIT_ACCOUNT_KEY, bytes("")); + keys[4] = getSingleKey(KeyType.SUPPLY, KeyValueType.INHERIT_ACCOUNT_KEY, bytes("")); + + IHederaTokenService.Expiry memory expiry = IHederaTokenService.Expiry( + 0, treasury, 8000000 + ); + + IHederaTokenService.HederaToken memory token = IHederaTokenService.HederaToken( + name, symbol, treasury, memo, true, maxSupply, freezeDefaultStatus, keys, expiry + ); + + (int responseCode, address tokenAddress) = + HederaTokenService.createFungibleToken(token, initialTotalSupply, decimals); + + if (responseCode != HederaResponseCodes.SUCCESS) { + revert (); + } + + emit CreatedToken(tokenAddress); + } + + function createFungibleTokenWithSECP256K1AdminKeyPublic( + address treasury, bytes memory adminKey + ) public payable returns (address) { + IHederaTokenService.TokenKey[] memory keys = new IHederaTokenService.TokenKey[](5); + keys[0] = getSingleKey(KeyType.ADMIN, KeyType.PAUSE, KeyValueType.SECP256K1, adminKey); + keys[1] = getSingleKey(KeyType.KYC, KeyValueType.SECP256K1, adminKey); + keys[2] = getSingleKey(KeyType.FREEZE, KeyValueType.SECP256K1, adminKey); + keys[3] = getSingleKey(KeyType.SUPPLY, KeyValueType.SECP256K1, adminKey); + keys[4] = getSingleKey(KeyType.WIPE, KeyValueType.SECP256K1, adminKey); + + IHederaTokenService.Expiry memory expiry = IHederaTokenService.Expiry( + 0, treasury, 8000000 + ); + + IHederaTokenService.HederaToken memory token = IHederaTokenService.HederaToken( + name, symbol, treasury, memo, true, maxSupply, freezeDefaultStatus, keys, expiry + ); + + (int responseCode, address tokenAddress) = + HederaTokenService.createFungibleToken(token, initialTotalSupply, decimals); + + if (responseCode != HederaResponseCodes.SUCCESS) { + revert (); + } + + emit CreatedToken(tokenAddress); + + return tokenAddress; + } + + function createFungibleTokenWithSECP256K1AdminKeyAssociateAndTransferToAddressPublic(address treasury, bytes memory adminKey, int64 amount) public payable { + address tokenAddress = this.createFungibleTokenWithSECP256K1AdminKeyPublic{value : msg.value}(treasury, adminKey); + this.associateTokenPublic(msg.sender, tokenAddress); + this.grantTokenKycPublic(tokenAddress, msg.sender); + HederaTokenService.transferToken(tokenAddress, address(this), msg.sender, amount); + } + + function createFungibleTokenWithSECP256K1AdminKeyWithoutKYCPublic( + address treasury, bytes memory adminKey + ) public payable returns (address) { + IHederaTokenService.TokenKey[] memory keys = new IHederaTokenService.TokenKey[](4); + keys[0] = getSingleKey(KeyType.ADMIN, KeyType.PAUSE, KeyValueType.SECP256K1, adminKey); + keys[1] = getSingleKey(KeyType.FREEZE, KeyValueType.SECP256K1, adminKey); + keys[2] = getSingleKey(KeyType.SUPPLY, KeyValueType.SECP256K1, adminKey); + keys[3] = getSingleKey(KeyType.WIPE, KeyValueType.SECP256K1, adminKey); + + IHederaTokenService.Expiry memory expiry = IHederaTokenService.Expiry( + 0, treasury, 8000000 + ); + + IHederaTokenService.HederaToken memory token = IHederaTokenService.HederaToken( + name, symbol, treasury, memo, true, maxSupply, freezeDefaultStatus, keys, expiry + ); + + (int responseCode, address tokenAddress) = + HederaTokenService.createFungibleToken(token, initialTotalSupply, decimals); + + if (responseCode != HederaResponseCodes.SUCCESS) { + revert (); + } + + emit CreatedToken(tokenAddress); + + return tokenAddress; + } + + function createFungibleTokenWithCustomFeesPublic( + address treasury, + address fixedFeeTokenAddress + ) public payable { + IHederaTokenService.TokenKey[] memory keys = new IHederaTokenService.TokenKey[](1); + keys[0] = getSingleKey(KeyType.ADMIN, KeyType.ADMIN, KeyValueType.INHERIT_ACCOUNT_KEY, bytes("")); + + IHederaTokenService.Expiry memory expiry = IHederaTokenService.Expiry( + 0, treasury, 8000000 + ); + + IHederaTokenService.HederaToken memory token = IHederaTokenService.HederaToken( + name, symbol, treasury, memo, true, maxSupply, false, keys, expiry + ); + + IHederaTokenService.FixedFee[] memory fixedFees = new IHederaTokenService.FixedFee[](1); + fixedFees[0] = IHederaTokenService.FixedFee(1, fixedFeeTokenAddress, false, false, treasury); + + IHederaTokenService.FractionalFee[] memory fractionalFees = new IHederaTokenService.FractionalFee[](1); + fractionalFees[0] = IHederaTokenService.FractionalFee(4, 5, 10, 30, false, treasury); + + (int responseCode, address tokenAddress) = + HederaTokenService.createFungibleTokenWithCustomFees(token, initialTotalSupply, decimals, fixedFees, fractionalFees); + emit ResponseCode(responseCode); + + if (responseCode != HederaResponseCodes.SUCCESS) { + revert (); + } + + emit CreatedToken(tokenAddress); + } + + function createNonFungibleTokenPublic( + address treasury + ) public payable { + IHederaTokenService.TokenKey[] memory keys = new IHederaTokenService.TokenKey[](5); + keys[0] = getSingleKey(KeyType.ADMIN, KeyType.PAUSE, KeyValueType.INHERIT_ACCOUNT_KEY, bytes("")); + keys[1] = getSingleKey(KeyType.KYC, KeyValueType.INHERIT_ACCOUNT_KEY, bytes("")); + keys[2] = getSingleKey(KeyType.FREEZE, KeyValueType.INHERIT_ACCOUNT_KEY, bytes("")); + keys[3] = getSingleKey(KeyType.SUPPLY, KeyValueType.INHERIT_ACCOUNT_KEY, bytes("")); + keys[4] = getSingleKey(KeyType.WIPE, KeyValueType.INHERIT_ACCOUNT_KEY, bytes("")); + + IHederaTokenService.Expiry memory expiry = IHederaTokenService.Expiry( + 0, treasury, 8000000 + ); + + IHederaTokenService.HederaToken memory token = IHederaTokenService.HederaToken( + name, symbol, treasury, memo, true, maxSupply, freezeDefaultStatus, keys, expiry + ); + + (int responseCode, address tokenAddress) = + HederaTokenService.createNonFungibleToken(token); + + if (responseCode != HederaResponseCodes.SUCCESS) { + revert (); + } + + emit CreatedToken(tokenAddress); + } + + function createNonFungibleTokenWithSECP256K1AdminKeyPublic( + address treasury, bytes memory adminKey + ) public payable { + IHederaTokenService.TokenKey[] memory keys = new IHederaTokenService.TokenKey[](5); + keys[0] = getSingleKey(KeyType.ADMIN, KeyType.PAUSE, KeyValueType.SECP256K1, adminKey); + keys[1] = getSingleKey(KeyType.KYC, KeyValueType.SECP256K1, adminKey); + keys[2] = getSingleKey(KeyType.FREEZE, KeyValueType.SECP256K1, adminKey); + keys[3] = getSingleKey(KeyType.SUPPLY, KeyValueType.SECP256K1, adminKey); + keys[4] = getSingleKey(KeyType.WIPE, KeyValueType.SECP256K1, adminKey); + + IHederaTokenService.Expiry memory expiry = IHederaTokenService.Expiry( + 0, treasury, 8000000 + ); + + IHederaTokenService.HederaToken memory token = IHederaTokenService.HederaToken( + name, symbol, treasury, memo, true, maxSupply, freezeDefaultStatus, keys, expiry + ); + + (int responseCode, address tokenAddress) = + HederaTokenService.createNonFungibleToken(token); + + if (responseCode != HederaResponseCodes.SUCCESS) { + revert (); + } + + emit CreatedToken(tokenAddress); + } + + function createNonFungibleTokenWithSECP256K1AdminKeyWithoutKYCPublic( + address treasury, bytes memory adminKey + ) public payable { + IHederaTokenService.TokenKey[] memory keys = new IHederaTokenService.TokenKey[](4); + keys[0] = getSingleKey(KeyType.ADMIN, KeyType.PAUSE, KeyValueType.SECP256K1, adminKey); + keys[1] = getSingleKey(KeyType.FREEZE, KeyValueType.SECP256K1, adminKey); + keys[2] = getSingleKey(KeyType.SUPPLY, KeyValueType.SECP256K1, adminKey); + keys[3] = getSingleKey(KeyType.WIPE, KeyValueType.SECP256K1, adminKey); + + IHederaTokenService.Expiry memory expiry = IHederaTokenService.Expiry( + 0, treasury, 8000000 + ); + + IHederaTokenService.HederaToken memory token = IHederaTokenService.HederaToken( + name, symbol, treasury, memo, true, maxSupply, freezeDefaultStatus, keys, expiry + ); + + (int responseCode, address tokenAddress) = + HederaTokenService.createNonFungibleToken(token); + + if (responseCode != HederaResponseCodes.SUCCESS) { + revert (); + } + + emit CreatedToken(tokenAddress); + } + + function createNonFungibleTokenWithCustomFeesPublic( + address treasury, + address fixedFeeTokenAddress + ) public payable { + IHederaTokenService.TokenKey[] memory keys = new IHederaTokenService.TokenKey[](5); + keys[0] = getSingleKey(KeyType.ADMIN, KeyType.PAUSE, KeyValueType.INHERIT_ACCOUNT_KEY, bytes("")); + keys[1] = getSingleKey(KeyType.KYC, KeyValueType.INHERIT_ACCOUNT_KEY, bytes("")); + keys[2] = getSingleKey(KeyType.FREEZE, KeyValueType.INHERIT_ACCOUNT_KEY, bytes("")); + keys[3] = getSingleKey(KeyType.SUPPLY, KeyValueType.INHERIT_ACCOUNT_KEY, bytes("")); + keys[4] = getSingleKey(KeyType.WIPE, KeyValueType.INHERIT_ACCOUNT_KEY, bytes("")); + + IHederaTokenService.Expiry memory expiry = IHederaTokenService.Expiry( + 0, treasury, 8000000 + ); + + IHederaTokenService.HederaToken memory token = IHederaTokenService.HederaToken( + name, symbol, treasury, memo, true, maxSupply, freezeDefaultStatus, keys, expiry + ); + + IHederaTokenService.FixedFee[] memory fixedFees = new IHederaTokenService.FixedFee[](1); + fixedFees[0] = IHederaTokenService.FixedFee(1, fixedFeeTokenAddress, false, false, treasury); + + IHederaTokenService.RoyaltyFee[] memory royaltyFees = new IHederaTokenService.RoyaltyFee[](1); + royaltyFees[0] = IHederaTokenService.RoyaltyFee(4, 5, 10, fixedFeeTokenAddress, false, treasury); + + (int responseCode, address tokenAddress) = + HederaTokenService.createNonFungibleTokenWithCustomFees(token, fixedFees, royaltyFees); + + if (responseCode != HederaResponseCodes.SUCCESS) { + revert (); + } + + emit CreatedToken(tokenAddress); + } + + function mintTokenPublic(address token, int64 amount, bytes[] memory metadata) public + returns (int responseCode, int64 newTotalSupply, int64[] memory serialNumbers) { + (responseCode, newTotalSupply, serialNumbers) = HederaTokenService.mintToken(token, amount, metadata); + emit ResponseCode(responseCode); + + if (responseCode != HederaResponseCodes.SUCCESS) { + revert(); + } + + emit MintedToken(newTotalSupply, serialNumbers); + } + + function mintTokenToAddressPublic(address token, int64 amount, bytes[] memory metadata) public + returns (int responseCode, int64 newTotalSupply, int64[] memory serialNumbers) { + (responseCode, newTotalSupply, serialNumbers) = HederaTokenService.mintToken(token, amount, metadata); + emit ResponseCode(responseCode); + + if (responseCode != HederaResponseCodes.SUCCESS) { + revert(); + } + + emit MintedToken(newTotalSupply, serialNumbers); + + HederaTokenService.transferNFT(token, address(this), msg.sender, serialNumbers[0]); + } + + function associateTokensPublic(address account, address[] memory tokens) external returns (int256 responseCode) { + (responseCode) = HederaTokenService.associateTokens(account, tokens); + emit ResponseCode(responseCode); + + if (responseCode != HederaResponseCodes.SUCCESS) { + revert (); + } + } + + function associateTokenPublic(address account, address token) public returns (int responseCode) { + responseCode = HederaTokenService.associateToken(account, token); + emit ResponseCode(responseCode); + + if (responseCode != HederaResponseCodes.SUCCESS) { + revert (); + } + } + + function grantTokenKycPublic(address token, address account) external returns (int64 responseCode) { + (responseCode) = HederaTokenService.grantTokenKyc(token, account); + emit ResponseCode(responseCode); + + if (responseCode != HederaResponseCodes.SUCCESS) { + revert(); + } + } + + //Adding this function to showcase token functionality in extended updateTokenInfo test suite + function updateTokenInfoPublic(address token, IHederaTokenService.HederaToken memory tokenInfo)external returns (int responseCode) { + (responseCode) = HederaTokenService.updateTokenInfo(token, tokenInfo); + emit ResponseCode(responseCode); + + if (responseCode != HederaResponseCodes.SUCCESS) { + revert(); + } + } + + function approvePublic(address token, address spender, uint256 amount) public returns (int responseCode) { + responseCode = HederaTokenService.approve(token, spender, amount); + emit ResponseCode(responseCode); + + if (responseCode != HederaResponseCodes.SUCCESS) { + revert (); + } + } +} diff --git a/hedera-node/test-clients/src/main/resource/contract/contracts/TokenInfoSingularUpdate/TokenInfoSingularUpdate.bin b/hedera-node/test-clients/src/main/resource/contract/contracts/TokenInfoSingularUpdate/TokenInfoSingularUpdate.bin new file mode 100644 index 000000000000..4eb20ee68090 --- /dev/null +++ b/hedera-node/test-clients/src/main/resource/contract/contracts/TokenInfoSingularUpdate/TokenInfoSingularUpdate.bin @@ -0,0 +1 @@ +608060405234801561001057600080fd5b50600160208181527fa6eef7e35abe7026729641147f7915573c7e97b47efa546f5f6e3230263bcb499190915560027fcc69885fda6bcc1a4ace058b4a62bf5e179ea78fd58a1ccd71c22cc9b688792f5560047fd9d16d34ffb15ba3a3d852f0d403e2ce1d691fb54de27ac87cd2f993f3ec330f5560087f7dfe757ecd65cbd7922a9c0161e935dd7fdbcc0e999689c7d31633896b1fc60b5560107fedc95719e9a3b28dd8e80877cb5880a9be7de1a13fc8b05e7999683b6b567643557fe2689cd4a84e23ad2f564004f1c9013e9589d260bde6380aba3ca7e09e4df40c55600660005260407f8f331abe73332f95a25873e8b430885974c0409691f89d643119a11623a7924a556110b3806101276000396000f3fe608060405234801561001057600080fd5b50600436106100b95760003560e01c80639b23d3d911610081578063b2d8461c1161005b578063b2d8461c1461016f578063c8c04a0914610182578063f00729901461019557600080fd5b80639b23d3d9146101365780639bc2f98214610149578063a1a2497d1461015c57600080fd5b806315dacbea146100be5780631e5f0ad4146100e8578063618dc65e146100fd5780638c649cf91461011057806398142caf14610123575b600080fd5b6100d16100cc366004610acb565b6101a8565b60405160079190910b815260200160405180910390f35b6100fb6100f6366004610b16565b61029c565b005b6100fb61010b366004610c06565b610337565b6100fb61011e366004610c54565b610425565b6100fb610131366004610cac565b610498565b6100d1610144366004610acb565b6104d7565b6100fb610157366004610cee565b61051c565b6100fb61016a366004610c54565b6105ef565b6100fb61017d366004610d31565b610606565b6100fb610190366004610c54565b61062b565b6100fb6101a3366004610cac565b610646565b6040516001600160a01b038581166024830152848116604483015283166064820152608481018290526000908190819061016790630aed65f560e11b9060a4015b60408051601f198184030181529181526020820180516001600160e01b03166001600160e01b03199094169390931790925290516102279190610db2565b6000604051808303816000865af19150503d8060008114610264576040519150601f19603f3d011682016040523d82523d6000602084013e610269565b606091505b50915091508161027a57601561028e565b8080602001905181019061028e9190610dce565b60030b979650505050505050565b6102a46109ef565b60408051606081018252600080825260208201819052600785900b928201929092526101008301819052906102d98584610665565b9050601681146103305760405162461bcd60e51b815260206004820152601b60248201527f557064617465206f6620746f6b656e496e666f206661696c656421000000000060448201526064015b60405180910390fd5b5050505050565b6000806101676001600160a01b031663618dc65e60e01b8585604051602401610361929190610e1d565b60408051601f198184030181529181526020820180516001600160e01b03166001600160e01b031990941693909317909252905161039f9190610db2565b6000604051808303816000865af19150503d80600081146103dc576040519150601f19603f3d011682016040523d82523d6000602084013e6103e1565b606091505b50915091507f4af4780e06fe8cb9df64b0794fa6f01399af979175bb988e35e0e57e594567bc8282604051610417929190610e47565b60405180910390a150505050565b61042d6109ef565b6060810182905260006104408483610665565b9050601681146104925760405162461bcd60e51b815260206004820152601b60248201527f557064617465206f6620746f6b656e496e666f206661696c65642100000000006044820152606401610327565b50505050565b6104a06109ef565b6040805160608101825260008082529181018290526001600160a01b03841660208201526101008301819052906102d98584610665565b6040516001600160a01b038581166024830152848116604483015283166064820152608481018290526000908190819061016790639b23d3d960e01b9060a4016101e9565b6105246109ef565b604080516001808252818301909252600091816020015b610543610a57565b81526020019060019003908161053b57905050905061056483600186610742565b8160008151811061057757610577610e62565b602090810291909101015260e0820181905260006105958684610665565b9050601681146105e75760405162461bcd60e51b815260206004820152601b60248201527f557064617465206f6620746f6b656e496e666f206661696c65642100000000006044820152606401610327565b505050505050565b6105f76109ef565b81815260006104408483610665565b61060e6109ef565b61061a82600285610777565b60e082015260006102d98583610665565b6106336109ef565b6020810182905260006104408483610665565b61064e6109ef565b6001600160a01b0382166040820152600061044084835b60008060006101676001600160a01b0316637d305cfa60e01b8686604051602401610691929190610f3c565b60408051601f198184030181529181526020820180516001600160e01b03166001600160e01b03199094169390931790925290516106cf9190610db2565b6000604051808303816000865af19150503d806000811461070c576040519150601f19603f3d011682016040523d82523d6000602084013e610711565b606091505b509150915081610722576015610736565b808060200190518101906107369190610dce565b60030b95945050505050565b61074a610a57565b604051806040016040528061075e866107f6565b815260200161076d8585610835565b9052949350505050565b604080516001808252818301909252606091816020015b610796610a57565b81526020019060019003908161078e57905050905060405180604001604052806107bf866107f6565b81526020016107ce85856108d9565b815250816000815181106107e4576107e4610e62565b60200260200101819052509392505050565b60006001600083600681111561080e5761080e611067565b600681111561081f5761081f611067565b8152602001908152602001600020549050919050565b61087b6040518060a0016040528060001515815260200160006001600160a01b03168152602001606081526020016060815260200160006001600160a01b031681525090565b600183600481111561088f5761088f611067565b14156108a9576001600160a01b03821660208201526108d3565b60048360048111156108bd576108bd611067565b14156108d3576001600160a01b03821660808201525b92915050565b61091f6040518060a0016040528060001515815260200160006001600160a01b03168152602001606081526020016060815260200160006001600160a01b031681525090565b600083600481111561093357610933611067565b141561094257600181526108d3565b600183600481111561095657610956611067565b1415610972576000546001600160a01b031660208201526108d3565b600283600481111561098657610986611067565b141561099857604081018290526108d3565b60038360048111156109ac576109ac611067565b14156109be57606081018290526108d3565b60048360048111156109d2576109d2611067565b14156108d3576000546001600160a01b0316608082015292915050565b60408051610120810182526060808252602080830182905260008385018190528284018390526080840181905260a0840181905260c0840181905260e0840183905284519283018552808352908201819052928101929092529061010082015290565b905290565b604051806040016040528060008152602001610a526040518060a0016040528060001515815260200160006001600160a01b03168152602001606081526020016060815260200160006001600160a01b031681525090565b80356001600160a01b0381168114610ac657600080fd5b919050565b60008060008060808587031215610ae157600080fd5b610aea85610aaf565b9350610af860208601610aaf565b9250610b0660408601610aaf565b9396929550929360600135925050565b60008060408385031215610b2957600080fd5b610b3283610aaf565b915060208301358060070b8114610b4857600080fd5b809150509250929050565b634e487b7160e01b600052604160045260246000fd5b600067ffffffffffffffff80841115610b8457610b84610b53565b604051601f8501601f19908116603f01168101908282118183101715610bac57610bac610b53565b81604052809350858152868686011115610bc557600080fd5b858560208301376000602087830101525050509392505050565b600082601f830112610bf057600080fd5b610bff83833560208501610b69565b9392505050565b60008060408385031215610c1957600080fd5b610c2283610aaf565b9150602083013567ffffffffffffffff811115610c3e57600080fd5b610c4a85828601610bdf565b9150509250929050565b60008060408385031215610c6757600080fd5b610c7083610aaf565b9150602083013567ffffffffffffffff811115610c8c57600080fd5b8301601f81018513610c9d57600080fd5b610c4a85823560208401610b69565b60008060408385031215610cbf57600080fd5b610cc883610aaf565b9150610cd660208401610aaf565b90509250929050565b803560078110610ac657600080fd5b600080600060608486031215610d0357600080fd5b610d0c84610aaf565b9250610d1a60208501610aaf565b9150610d2860408501610cdf565b90509250925092565b600080600060608486031215610d4657600080fd5b610d4f84610aaf565b9250602084013567ffffffffffffffff811115610d6b57600080fd5b610d7786828701610bdf565b925050610d2860408501610cdf565b60005b83811015610da1578181015183820152602001610d89565b838111156104925750506000910152565b60008251610dc4818460208701610d86565b9190910192915050565b600060208284031215610de057600080fd5b81518060030b8114610bff57600080fd5b60008151808452610e09816020860160208601610d86565b601f01601f19169290920160200192915050565b6001600160a01b0383168152604060208201526000610e3f6040830184610df1565b949350505050565b8215158152604060208201526000610e3f6040830184610df1565b634e487b7160e01b600052603260045260246000fd5b600081518084526020808501808196508360051b8101915082860160005b85811015610f2f5782840389528151604081518652868201519150808787015281511515818701528682015160606001600160a01b03808316828a015283850151935060a09250608083818b0152610ef160e08b0186610df1565b928601518a8403603f1901858c0152929450610f0d8584610df1565b9501511660c09890980197909752505098850198935090840190600101610e96565b5091979650505050505050565b6001600160a01b038316815260406020820152600082516101606040840152610f696101a0840182610df1565b90506020840151603f1980858403016060860152610f878383610df1565b925060408601519150610fa560808601836001600160a01b03169052565b60608601519150808584030160a0860152610fc08383610df1565b925060808601519150610fd760c086018315159052565b60a08601519150610fed60e086018360070b9052565b60c086015191506101006110048187018415159052565b60e0870151925081868503016101208701526110208484610e78565b908701518051600790810b61014089015260208201516001600160a01b03166101608901526040820151900b610180880152909350915061105e9050565b50949350505050565b634e487b7160e01b600052602160045260246000fdfea264697066735822122042878992db6d76a40cfc39313e31aff09c7cc8234add74b5c93f4b6d550d0a7964736f6c63430008090033 \ No newline at end of file diff --git a/hedera-node/test-clients/src/main/resource/contract/contracts/TokenInfoSingularUpdate/TokenInfoSingularUpdate.json b/hedera-node/test-clients/src/main/resource/contract/contracts/TokenInfoSingularUpdate/TokenInfoSingularUpdate.json new file mode 100644 index 000000000000..1f637c75ed3e --- /dev/null +++ b/hedera-node/test-clients/src/main/resource/contract/contracts/TokenInfoSingularUpdate/TokenInfoSingularUpdate.json @@ -0,0 +1,261 @@ +[ + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "bool", + "name": "", + "type": "bool" + }, + { + "indexed": false, + "internalType": "bytes", + "name": "", + "type": "bytes" + } + ], + "name": "CallResponseEvent", + "type": "event" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "token", + "type": "address" + }, + { + "internalType": "bytes", + "name": "encodedFunctionSelector", + "type": "bytes" + } + ], + "name": "redirectForToken", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "token", + "type": "address" + }, + { + "internalType": "address", + "name": "from", + "type": "address" + }, + { + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "transferFrom", + "outputs": [ + { + "internalType": "int64", + "name": "responseCode", + "type": "int64" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "token", + "type": "address" + }, + { + "internalType": "address", + "name": "from", + "type": "address" + }, + { + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "internalType": "uint256", + "name": "serialNumber", + "type": "uint256" + } + ], + "name": "transferFromNFT", + "outputs": [ + { + "internalType": "int64", + "name": "responseCode", + "type": "int64" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "tokenAddress", + "type": "address" + }, + { + "internalType": "address", + "name": "autoRenewAccount", + "type": "address" + } + ], + "name": "updateTokenAutoRenewAccount", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "tokenAddress", + "type": "address" + }, + { + "internalType": "int64", + "name": "autoRenewPeriod", + "type": "int64" + } + ], + "name": "updateTokenAutoRenewPeriod", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "tokenAddress", + "type": "address" + }, + { + "internalType": "address", + "name": "contractId", + "type": "address" + }, + { + "internalType": "enum KeyHelper.KeyType", + "name": "keyType", + "type": "uint8" + } + ], + "name": "updateTokenKeyContractId", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "tokenAddress", + "type": "address" + }, + { + "internalType": "bytes", + "name": "newKey", + "type": "bytes" + }, + { + "internalType": "enum KeyHelper.KeyType", + "name": "keyType", + "type": "uint8" + } + ], + "name": "updateTokenKeyEd", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "tokenAddress", + "type": "address" + }, + { + "internalType": "string", + "name": "memo", + "type": "string" + } + ], + "name": "updateTokenMemo", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "tokenAddress", + "type": "address" + }, + { + "internalType": "string", + "name": "name", + "type": "string" + } + ], + "name": "updateTokenName", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "tokenAddress", + "type": "address" + }, + { + "internalType": "string", + "name": "symbol", + "type": "string" + } + ], + "name": "updateTokenSymbol", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "tokenAddress", + "type": "address" + }, + { + "internalType": "address", + "name": "treasury", + "type": "address" + } + ], + "name": "updateTokenTreasury", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + } +] \ No newline at end of file diff --git a/hedera-node/test-clients/src/main/resource/contract/contracts/TokenInfoSingularUpdate/TokenInfoSingularUpdate.sol b/hedera-node/test-clients/src/main/resource/contract/contracts/TokenInfoSingularUpdate/TokenInfoSingularUpdate.sol new file mode 100644 index 000000000000..dea80aaafce6 --- /dev/null +++ b/hedera-node/test-clients/src/main/resource/contract/contracts/TokenInfoSingularUpdate/TokenInfoSingularUpdate.sol @@ -0,0 +1,96 @@ +// SPDX-License-Identifier: Apache-2.0 +pragma solidity ^0.8.0; +pragma experimental ABIEncoderV2; + +import "./KeyHelper.sol"; + +contract TokenInfoSingularUpdate is HederaTokenService, KeyHelper { + function updateTokenName(address tokenAddress, string memory name) external { + IHederaTokenService.HederaToken memory token; + token.name = name; + int responseCode = HederaTokenService.updateTokenInfo(tokenAddress, token); + + if (responseCode != HederaResponseCodes.SUCCESS) { + revert ("Update of tokenInfo failed!"); + } + } + + function updateTokenSymbol(address tokenAddress, string memory symbol) external { + IHederaTokenService.HederaToken memory token; + token.symbol = symbol; + int responseCode = HederaTokenService.updateTokenInfo(tokenAddress, token); + + if (responseCode != HederaResponseCodes.SUCCESS) { + revert ("Update of tokenInfo failed!"); + } + } + + function updateTokenMemo(address tokenAddress, string memory memo) external { + IHederaTokenService.HederaToken memory token; + token.memo = memo; + int responseCode = HederaTokenService.updateTokenInfo(tokenAddress, token); + + if (responseCode != HederaResponseCodes.SUCCESS) { + revert ("Update of tokenInfo failed!"); + } + } + + function updateTokenTreasury(address tokenAddress, address treasury) external { + IHederaTokenService.HederaToken memory token; + token.treasury = treasury; + int responseCode = HederaTokenService.updateTokenInfo(tokenAddress, token); + + if (responseCode != HederaResponseCodes.SUCCESS) { + revert ("Update of tokenInfo failed!"); + } + } + + function updateTokenAutoRenewAccount(address tokenAddress, address autoRenewAccount) external { + IHederaTokenService.HederaToken memory token; + IHederaTokenService.Expiry memory expiry; + + expiry.autoRenewAccount = autoRenewAccount; + token.expiry = expiry; + int responseCode = HederaTokenService.updateTokenInfo(tokenAddress, token); + + if (responseCode != HederaResponseCodes.SUCCESS) { + revert ("Update of tokenInfo failed!"); + } + } + + function updateTokenAutoRenewPeriod(address tokenAddress, int64 autoRenewPeriod) external { + IHederaTokenService.HederaToken memory token; + IHederaTokenService.Expiry memory expiry; + + expiry.autoRenewPeriod = autoRenewPeriod; + token.expiry = expiry; + + int responseCode = HederaTokenService.updateTokenInfo(tokenAddress, token); + + if (responseCode != HederaResponseCodes.SUCCESS) { + revert ("Update of tokenInfo failed!"); + } + } + + function updateTokenKeyEd(address tokenAddress, bytes memory newKey, KeyType keyType) external { + IHederaTokenService.HederaToken memory token; + token.tokenKeys = getCustomSingleTypeKeys(keyType, KeyValueType.ED25519, newKey); + int responseCode = HederaTokenService.updateTokenInfo(tokenAddress, token); + + if (responseCode != HederaResponseCodes.SUCCESS) { + revert ("Update of tokenInfo failed!"); + } + } + + function updateTokenKeyContractId(address tokenAddress, address contractId, KeyType keyType) external { + IHederaTokenService.HederaToken memory token; + IHederaTokenService.TokenKey[] memory tokenKeys = new IHederaTokenService.TokenKey[](1); + tokenKeys[0] = KeyHelper.getSingleKey(keyType, KeyValueType.CONTRACT_ID, contractId); + token.tokenKeys = tokenKeys; + int responseCode = HederaTokenService.updateTokenInfo(tokenAddress, token); + + if (responseCode != HederaResponseCodes.SUCCESS) { + revert ("Update of tokenInfo failed!"); + } + } +} \ No newline at end of file diff --git a/hedera-node/test-clients/src/main/resource/downscaled-throttle.properties b/hedera-node/test-clients/src/main/resource/downscaled-throttle.properties deleted file mode 100644 index 2f1319e9bd36..000000000000 --- a/hedera-node/test-clients/src/main/resource/downscaled-throttle.properties +++ /dev/null @@ -1,52 +0,0 @@ -### v0.5.8 configuration -hapi.throttling.config.useLegacyProps=false -## Default buckets -hapi.throttling.defaults.txnBucket=fastOpBucket -hapi.throttling.defaults.queryBucket=fastOpBucket -## Bucket definitions -hapi.throttling.buckets.slowOpBucket.capacity=26.0 -hapi.throttling.buckets.slowOpBucket.burstPeriod=2.0 -hapi.throttling.buckets.fastOpBucket.capacity=13.0 -hapi.throttling.buckets.fastOpBucket.overflow=slowOpBucket -hapi.throttling.buckets.receiptsBucket.capacity=1000000.0 -# The desired network tps for CreateTopic is 5.0. With -# a burstPeriod of 1.0, the bucket's -# capacity = tps * burstPeriod -# when split across 13 nodes, will yield a nodeCapacity -# of ~0.385, and all transactions will be throttled at -# a capacityRequired of 1.0. Thus we increase the burstPeriod -# to 2.6, yielding -# nodeCapacity = (5.0 * 2.6) / 13 = 1.0 -hapi.throttling.buckets.createTopicBucket.capacity=13.0 -hapi.throttling.buckets.createTopicBucket.burstPeriod=2.6 -## Per-operation configs -hapi.throttling.ops.transactionGetReceipt.bucket=receiptsBucket -hapi.throttling.ops.consensusCreateTopic.bucket=createTopicBucket -# Smart Contract Service -hapi.throttling.ops.contractCall.bucket=slowOpBucket -hapi.throttling.ops.contractCall.capacityRequired=6.5 -hapi.throttling.ops.contractCreate.bucket=slowOpBucket -hapi.throttling.ops.contractCreate.capacityRequired=6.5 -hapi.throttling.ops.contractUpdate.bucket=slowOpBucket -hapi.throttling.ops.contractUpdate.capacityRequired=6.5 -hapi.throttling.ops.contractDelete.bucket=slowOpBucket -hapi.throttling.ops.contractDelete.capacityRequired=6.5 -hapi.throttling.ops.contractCallLocal.bucket=slowOpBucket -hapi.throttling.ops.contractCallLocal.capacityRequired=6.5 -hapi.throttling.ops.contractGetInfo.bucket=slowOpBucket -hapi.throttling.ops.contractGetInfo.capacityRequired=6.5 -hapi.throttling.ops.contractGetBytecode.bucket=slowOpBucket -hapi.throttling.ops.contractGetBytecode.capacityRequired=6.5 -# File Service -hapi.throttling.ops.fileAppend.bucket=slowOpBucket -hapi.throttling.ops.fileAppend.capacityRequired=6.5 -hapi.throttling.ops.fileCreate.bucket=slowOpBucket -hapi.throttling.ops.fileCreate.capacityRequired=6.5 -hapi.throttling.ops.fileDelete.bucket=slowOpBucket -hapi.throttling.ops.fileDelete.capacityRequired=6.5 -hapi.throttling.ops.fileUpdate.bucket=slowOpBucket -hapi.throttling.ops.fileUpdate.capacityRequired=6.5 -hapi.throttling.ops.fileGetContents.bucket=slowOpBucket -hapi.throttling.ops.fileGetContents.capacityRequired=6.5 -hapi.throttling.ops.fileGetInfo.bucket=slowOpBucket -hapi.throttling.ops.fileGetInfo.capacityRequired=6.5 diff --git a/hedera-node/test-clients/src/main/resource/log4j2.xml b/hedera-node/test-clients/src/main/resource/log4j2.xml index d9dd85d2afbe..87ae8536eb55 100644 --- a/hedera-node/test-clients/src/main/resource/log4j2.xml +++ b/hedera-node/test-clients/src/main/resource/log4j2.xml @@ -18,7 +18,18 @@ + filePattern="output/transaction-state/state-changes-%d{yyyy-MM-dd--HH-mm-ss}-%i.log"> + + %d{yyyy-MM-dd HH:mm:ss.SSS} - %m{nolookups}%n + + + + + + + %d{yyyy-MM-dd HH:mm:ss.SSS} - %m{nolookups}%n @@ -33,6 +44,11 @@ + + + + + diff --git a/hedera-node/test-clients/src/main/resource/spec-default.properties b/hedera-node/test-clients/src/main/resource/spec-default.properties index bd00f7bc297e..fc6cd062bbe0 100644 --- a/hedera-node/test-clients/src/main/resource/spec-default.properties +++ b/hedera-node/test-clients/src/main/resource/spec-default.properties @@ -94,7 +94,6 @@ genesis.account=0.0.2 genesis.account.name=GENESIS invalid.contract=1.1.1 invalid.contract.name=INVALID_CONTRACT -measure.consensus.latency=false node.addressBook.name=NODE_ADDRESS_BOOK node.details.id=0.0.102 node.details.name=NODE_DETAILS diff --git a/hedera-node/test-clients/src/main/resource/spec-mainnet.properties b/hedera-node/test-clients/src/main/resource/spec-mainnet.properties deleted file mode 100644 index eb5184ff0d8c..000000000000 --- a/hedera-node/test-clients/src/main/resource/spec-mainnet.properties +++ /dev/null @@ -1,91 +0,0 @@ -address.book.id=0.0.101 -address.book.name=ADDRESS_BOOK -api.permissions.id=0.0.122 -api.permissions.name=API_PERMISSIONS -api.permissions.defaultsPath=system-files/apiPermissions.bin -app.properties.id=0.0.121 -app.properties.name=APP_PROPERTIES -app.properties.defaultsPath=system-files/appProperties.bin -### only contract/file load tests ### -#ci.properties.map=tps=20,mins=1,burstSize=5,allowedSecsBelow=10 -### crypto/consensus mix load tests ### -#ci.properties.map=tps=500,mins=1,burstSize=100,allowedSecsBelow=10 -### EetUmbrellaSub ### -#ci.properties.map=duration=10,unit=SECONDS -### RecordStreamValidation ### -ci.properties.map= -client.feeSchedule.fromDisk=true -client.feeSchedule.path=system-files/feeSchedule.bin -client.exchangeRates.fromDisk=true -client.exchangeRates.path=system-files/exchangeRates.bin -cost.snapshot.dir=default -cost.snapshot.mode=OFF -# 90 days lifetime in mainnet right now -default.autorenew.secs=7776000 -default.balance.tinyBars=1000000000 -default.consensus.autoRenewId=0.0.2 -default.consensus.message=What but eye and ear silence the mind -default.contract.balance.tinyBars=0 -default.contract.bytecode=CreateTrivial -default.call.gas=200000 -default.create.gas=200000 -default.expiration.secs=7000000 -default.fee=100000000 -default.file.contents=Hello World! -default.keyType=SIMPLE -default.listKey.N=3 -default.max.localCall.retBytes=1024 -default.memo=cellar door -default.node=0.0.3 -default.node.name=DEFAULT_NODE -default.nodePayment.tinyBars=10 -#default.payer=0.0.2 -default.payer=0.0.950 -#default.payer=0.0.982 -default.payer.name=GENESIS -default.proxy=n/a -default.proxy.name=DEFAULT_PROXY -default.queueSaturation.ms=100 -default.realm=0 -default.receiverSigRequired=false -default.receiveThreshold.tinyBars=5000000000000000000 -default.sendThreshold.tinyBars=5000000000000000000 -default.shard=0 -default.thresholdKey.M=2 -default.thresholdKey.N=3 -default.throughputObs.expiry.ms=30000 -default.throughputObs.sleep.ms=15 -default.transfer=0.0.2 -default.transfer.name=GENESIS -default.validDuration.secs=120 -exchange.rates.id=0.0.112 -exchange.rates.name=EXCHANGE_RATES -expected.final.status=PASSED -fee.schedule.controlAccount.id=0.0.56 -fee.schedule.controlAccount.name=FEE_SCHEDULE_CONTROL -fee.schedule.fetch.fee=100000000 -fee.schedule.id=0.0.111 -fee.schedule.name=FEE_SCHEDULE -funding.account=0.0.98 -funding.account.name=FUNDING -genesisAccount.name=GENESIS -genesisAccount.startupKey=START_ACCOUNT -hbar.float=5000000000000000000 -invalid.contract=1.1.1 -invalid.contract.name=INVALID_CONTRACT -measure.consensus.latency=false -node.details.id=0.0.102 -node.details.name=NODE_DETAILS -nodes=35.237.200.180:0.0.3 -num.opFinisher.threads=8 -record.ttl.ms=30000 -startupAccounts.path=src/main/resource/MainNet_Account950_StartUp.txt -status.deferredResolves.doAsync=true -status.preResolve.pause.ms=0 -status.wait.sleep.ms=500 -status.wait.timeout.ms=100000 -strong.control.account=0.0.50 -strong.control.name=strong-control -txn.start.offset.secs=-60 -weak.control.account=0.0.57 -weak.control.name=weak-control diff --git a/hedera-node/test-clients/src/main/resource/spec-testnet.properties b/hedera-node/test-clients/src/main/resource/spec-testnet.properties deleted file mode 100644 index 1aa493b1b5a0..000000000000 --- a/hedera-node/test-clients/src/main/resource/spec-testnet.properties +++ /dev/null @@ -1,89 +0,0 @@ -address.book.id=0.0.101 -address.book.name=ADDRESS_BOOK -api.permissions.id=0.0.122 -api.permissions.name=API_PERMISSIONS -api.permissions.defaultsPath=dev-system-files/apiPermissions.bin -app.properties.id=0.0.121 -app.properties.name=APP_PROPERTIES -app.properties.defaultsPath=dev-system-files/appProperties.bin -### only contract/file load tests ### -#ci.properties.map=tps=20,mins=1,burstSize=5,allowedSecsBelow=10 -### crypto/consensus mix load tests ### -#ci.properties.map=tps=500,mins=1,burstSize=100,allowedSecsBelow=10 -### EetUmbrellaSub ### -#ci.properties.map=duration=10,unit=SECONDS -### RecordStreamValidation ### -ci.properties.map= -client.feeSchedule.fromDisk=false -client.feeSchedule.path=dev-system-files/feeSchedule.bin -client.exchangeRates.fromDisk=false -client.exchangeRates.path=dev-system-files/exchangeRates.bin -cost.snapshot.dir=default -cost.snapshot.mode=OFF -# 90 days lifetime in mainnet right now -default.autorenew.secs=7776000 -default.balance.tinyBars=1000000000 -default.consensus.autoRenewId=0.0.2 -default.consensus.message=What but eye and ear silence the mind -default.contract.balance.tinyBars=0 -default.contract.bytecode=CreateTrivial -default.call.gas=200000 -default.create.gas=200000 -default.expiration.secs=7000000 -default.fee=100000000 -default.file.contents=Hello World! -default.keyType=SIMPLE -default.listKey.N=3 -default.max.localCall.retBytes=1024 -default.memo=cellar door -default.node=0.0.3 -default.node.name=DEFAULT_NODE -default.nodePayment.tinyBars=10 -default.payer=0.0.950 -default.payer.name=GENESIS -default.proxy=n/a -default.proxy.name=DEFAULT_PROXY -default.queueSaturation.ms=100 -default.realm=0 -default.receiverSigRequired=false -default.receiveThreshold.tinyBars=5000000000000000000 -default.sendThreshold.tinyBars=5000000000000000000 -default.shard=0 -default.thresholdKey.M=2 -default.thresholdKey.N=3 -default.throughputObs.expiry.ms=30000 -default.throughputObs.sleep.ms=15 -default.transfer=0.0.2 -default.transfer.name=GENESIS -default.validDuration.secs=120 -exchange.rates.id=0.0.112 -exchange.rates.name=EXCHANGE_RATES -expected.final.status=PASSED -fee.schedule.controlAccount.id=0.0.56 -fee.schedule.controlAccount.name=FEE_SCHEDULE_CONTROL -fee.schedule.fetch.fee=100000000 -fee.schedule.id=0.0.111 -fee.schedule.name=FEE_SCHEDULE -funding.account=0.0.98 -funding.account.name=FUNDING -genesisAccount.name=GENESIS -genesisAccount.startupKey=START_ACCOUNT -hbar.float=5000000000000000000 -invalid.contract=1.1.1 -invalid.contract.name=INVALID_CONTRACT -measure.consensus.latency=false -node.details.id=0.0.102 -node.details.name=NODE_DETAILS -nodes=35.188.20.11:0.0.3 -num.opFinisher.threads=8 -record.ttl.ms=30000 -startupAccounts.path=src/main/resource/TestNet_Account982_StartUp.txt -status.deferredResolves.doAsync=true -status.preResolve.pause.ms=0 -status.wait.sleep.ms=500 -status.wait.timeout.ms=100000 -strong.control.account=0.0.50 -strong.control.name=strong-control -txn.start.offset.secs=-60 -weak.control.account=0.0.57 -weak.control.name=weak-control diff --git a/hedera-node/test-clients/src/main/resource/testSystemFiles/artificial-limits-6N.json b/hedera-node/test-clients/src/main/resource/testSystemFiles/artificial-limits-6N.json deleted file mode 100644 index 1f0b1a4823ce..000000000000 --- a/hedera-node/test-clients/src/main/resource/testSystemFiles/artificial-limits-6N.json +++ /dev/null @@ -1,135 +0,0 @@ -{ - "buckets": [ - { - "name": "ThroughputLimits", - "burstPeriod": 3, - "throttleGroups": [ - { - "opsPerSec": 100, - "operations": [ - "CryptoCreate", - "CryptoTransfer", - "UtilPrng", - "CryptoUpdate", - "CryptoDelete", - "CryptoGetInfo", - "CryptoGetAccountRecords", - "CryptoApproveAllowance", - "CryptoDeleteAllowance", - "ConsensusCreateTopic", - "ConsensusSubmitMessage", - "ConsensusUpdateTopic", - "ConsensusDeleteTopic", - "ConsensusGetTopicInfo", - "TokenGetInfo", - "TokenGetNftInfo", - "TokenGetNftInfos", - "TokenGetAccountNftInfos", - "ScheduleDelete", - "ScheduleGetInfo", - "FileGetContents", - "FileGetInfo", - "ContractUpdate", - "ContractDelete", - "ContractGetInfo", - "ContractGetBytecode", - "ContractGetRecords", - "ContractCallLocal", - "TransactionGetRecord", - "GetVersionInfo" - ] - }, - { - "opsPerSec": 3, - "operations": [ - "ContractCall", - "ContractCreate", - "FileCreate", - "FileUpdate", - "FileAppend", - "FileDelete", - "EthereumTransaction" - ] - }, - { - "opsPerSec": 30, - "operations": [ - "ScheduleSign", - "TokenCreate", - "TokenDelete", - "TokenMint", - "TokenBurn", - "TokenPause", - "TokenUnpause", - "TokenUpdate", - "TokenFeeScheduleUpdate", - "TokenAssociateToAccount", - "TokenAccountWipe", - "TokenDissociateFromAccount", - "TokenFreezeAccount", - "TokenUnfreezeAccount", - "TokenGrantKycToAccount", - "TokenRevokeKycFromAccount" - ] - } - ] - }, - { - "name": "PriorityReservations", - "burstPeriod": 3, - "throttleGroups": [ - { - "opsPerSec": 3, - "operations": [ - "ContractCall", - "ContractCreate", - "FileCreate", - "FileUpdate", - "FileAppend", - "FileDelete", - "EthereumTransaction" - ] - } - ] - }, - { - "name": "CreationLimits", - "burstPeriod": 6, - "throttleGroups": [ - { - "opsPerSec": 2, - "operations": [ - "CryptoCreate" - ] - }, - { - "opsPerSec": 2, - "operations": [ - "ConsensusCreateTopic" - ] - }, - { - "opsPerSec": 2, - "operations": [ - "TokenCreate", - "TokenAssociateToAccount", - "ScheduleCreate" - ] - } - ] - }, - { - "name": "FreeQueryLimits", - "burstPeriod": 1, - "throttleGroups": [ - { - "opsPerSec": 100, - "operations": [ - "CryptoGetAccountBalance", - "TransactionGetReceipt" - ] - } - ] - } - ] -} diff --git a/hedera-node/test-clients/src/main/resource/testSystemFiles/throttles-for-acct-balances-tests.json b/hedera-node/test-clients/src/main/resource/testSystemFiles/throttles-for-acct-balances-tests.json deleted file mode 100644 index 4704d39e9fef..000000000000 --- a/hedera-node/test-clients/src/main/resource/testSystemFiles/throttles-for-acct-balances-tests.json +++ /dev/null @@ -1,141 +0,0 @@ -{ - "buckets": [ - { - "name": "ThroughputLimits", - "burstPeriod": 1, - "throttleGroups": [ - { - "opsPerSec": 20000, - "operations": [ - "CryptoCreate", - "CryptoTransfer", - "UtilPrng", - "CryptoUpdate", - "CryptoDelete", - "CryptoGetInfo", - "CryptoGetAccountRecords", - "CryptoApproveAllowance", - "CryptoDeleteAllowance", - "ConsensusCreateTopic", - "ConsensusSubmitMessage", - "ConsensusUpdateTopic", - "ConsensusDeleteTopic", - "ConsensusGetTopicInfo", - "TokenGetInfo", - "TokenGetNftInfo", - "TokenGetNftInfos", - "TokenGetAccountNftInfos", - "ScheduleDelete", - "ScheduleGetInfo", - "FileGetContents", - "FileGetInfo", - "ContractUpdate", - "ContractDelete", - "ContractGetInfo", - "ContractGetBytecode", - "ContractGetRecords", - "ContractCallLocal", - "TransactionGetRecord", - "GetVersionInfo" - ] - }, - { - "opsPerSec": 1000, - "operations": [ - "ContractCall", - "ContractCreate", - "FileCreate", - "FileUpdate", - "FileAppend", - "FileDelete", - "EthereumTransaction" - ] - }, - { - "opsPerSec": 6000, - "operations": [ - "ScheduleSign", - "TokenCreate", - "TokenDelete", - "TokenMint", - "TokenBurn", - "TokenPause", - "TokenUnpause", - "TokenUpdate", - "TokenFeeScheduleUpdate", - "TokenAssociateToAccount", - "TokenAccountWipe", - "TokenDissociateFromAccount", - "TokenFreezeAccount", - "TokenUnfreezeAccount", - "TokenGrantKycToAccount", - "TokenRevokeKycFromAccount" - ] - } - ] - }, - { - "name": "PriorityReservations", - "burstPeriod": 1, - "throttleGroups": [ - { - "opsPerSec": 1000, - "operations": [ - "ContractCall", - "ContractCreate", - "FileCreate", - "FileUpdate", - "FileAppend", - "FileDelete", - "EthereumTransaction" - ] - } - ] - }, - { - "name": "DevCreationLimits", - "burstPeriod": 10, - "throttleGroups": [ - { - "opsPerSec": 20000, - "operations": [ - "CryptoCreate" - ] - }, - { - "opsPerSec": 1000, - "operations": [ - "FileCreate" - ] - }, - { - "opsPerSec": 5000, - "operations": [ - "ConsensusCreateTopic" - ] - }, - { - "opsPerSec": 10000, - "operations": [ - "TokenCreate", - "TokenAssociateToAccount", - "ScheduleCreate" - ] - } - ] - }, - { - "name": "FreeQueryLimits", - "burstPeriod": 1, - "throttleGroups": [ - { - "opsPerSec": 1000000, - "operations": [ - "CryptoGetAccountBalance", - "TransactionGetReceipt" - ] - } - ] - } - ] -} diff --git a/hedera-node/test-clients/src/main/resource/umbrellaTest.properties b/hedera-node/test-clients/src/main/resource/umbrellaTest.properties deleted file mode 100644 index 49d089ac49ed..000000000000 --- a/hedera-node/test-clients/src/main/resource/umbrellaTest.properties +++ /dev/null @@ -1,121 +0,0 @@ -# port of node; default value is 50211 -# port=50777 -# number of threads to concurrently submit requests -numThreads=5 -# number of requests per second -requestPerSec=10 -# whether payer account is randomly selected -isRandomPayer=true -# type of file for creation -fileTypes=txt,jpg,pdf,bin -# size of file to create -fileSizesK=1,2,3,4,5,9 -# max number of requests in K to run, no cap (i.e. run indefinitely) if value is -1. -# Note this number can be a decimal value for fine grained control. e.g. a valuue of 0.1 translates into 100 total requests. -maxRequestsInK=1 -# max transfer amount. For a given transfer a random amount between 1 and the max will be selected -maxTransferAmount=100 -# -# number of seed accounts used as payers of transactions. These accounts may be to be created by genesis. -# Alternatively, they can be the system acounts when the flag useSystemAccountAsPayer is set (see below). -# In this case, the value should be set such that when combined with the genesis and node accounts, -# the total should not exceed 100. If the limit is exceeded, only accounts under the limit will be used. -numCryptoAccounts=1 -# -# number of seed accounts to be created by genesis. These accounts are used as transfer's from and to parties. -numTransferAccounts=2 -# -# whether transfer account (from or to) is randomly selected -isRandomTransferAccount=true -# -# -## API inclusions by group as comma separated list. -# where each entry is a pair of group name and number of calls per each member API of the group separated by "|". -# If the number of calls is specified as "-1", then this API will continue to be called unless there's cap on maxRequestsInK. -# The group choices include cryptoAPI,fileAPI,contractAPI. -# Note singleInclusions and groupInclusions are additive. If there is overlap, singleInclusions specs override those of groupInclusions. -# If groupInclusions are not specified, then maxRequestsInK and singleInclusions apply -# If both singleInclusions and groupInclusions are not specified, then all APIs are included and maxRequestsInK apply -# -# groupInclusions=cryptoAPI|0,fileAPI|0,contractAPI|0 -# -# API inclusions by individual APIs as comma separated list, where each entry is a pair of API name and number of calls separated by "|". -# If the number of calls is specified as "-1", then this API will continue to be called unless there's cap on maxRequestsInK. -# The API choices are as follows. -# cryptoAPI: cryptoCreate, cryptoTransfer, cryptoUpdate, cryptoGetInfo, cryptoGetBalance, cryptoGetRecords, getTxReceipt, getTxFastRecord, getTxRecord -# fileAPI: fileUpload, fileGetInfo, fileGetContent, fileUpdate, fileDelete, -# cryptoAPI: cryptoCreate, cryptoTransfer, cryptoUpdate, cryptoGetInfo, cryptoGetBalance, cryptoGetRecords, -# contractAPI: createContract, updateContract, contractCallMethod, contractCallLocalMethod, contractGetBytecode, getContractInfo, getBySolidityID, getTxRecordByContractID -# If singleInclusions are not specified, then maxRequestsInK and groupInclusions apply -# If both singleInclusions and groupInclusions are not specified, then all APIs are included and maxRequestsInK apply -# -#singleInclusions=createContract|1 -# -# All APIs (Large RUN ) -#singleInclusions=cryptoTransfer|100,cryptoCreate|5,cryptoUpdate|100,cryptoGetInfo|100,cryptoGetBalance|100,cryptoGetRecords|100,getTxReceipt|100,getTxRecord|100,fileUpload|2,fileGetInfo|100,fileGetContent|20,fileUpdate|100,fileDelete|100,createContract|2,updateContract|10,contractCallMethod|100,contractCallLocalMethod|100,contractGetBytecode|100,getContractInfo|100,getBySolidityID|100,getTxRecordByContractID|100 -## DEFAULT_INITIAL_ACCOUNT_BALANCE , when test payer account is created it will be created with this balance -#initialAccountBalance=150000000000 -# -# All APIs (Medium RUN) -#singleInclusions=cryptoTransfer|50,cryptoCreate|4,cryptoUpdate|50,cryptoGetInfo|50,cryptoGetBalance|50,cryptoGetRecords|50,getTxReceipt|50,getTxRecord|50,fileUpload|2,fileGetInfo|10,fileGetContent|10,fileUpdate|2,fileDelete|2,createContract|2,updateContract|2,contractCallMethod|2,contractCallLocalMethod|2,contractGetBytecode|2,getContractInfo|2,getBySolidityID|2,getTxRecordByContractID|2 -## DEFAULT_INITIAL_ACCOUNT_BALANCE , when test payer account is created it will be created with this balance -#initialAccountBalance=100000000000 -# -# ALl APIs (Small RUN ) -# singleInclusions=cryptoTransfer|20,cryptoCreate|2,cryptoUpdate|20,cryptoGetInfo|20,cryptoGetBalance|20,cryptoGetRecords|20,getTxReceipt|20,getTxRecord|20,fileUpload|2,fileGetInfo|10,fileGetContent|10,fileUpdate|2,fileDelete|2,createContract|2,updateContract|2,contractCallMethod|2,contractCallLocalMethod|2,contractGetBytecode|2,getContractInfo|2,getBySolidityID|2,getTxRecordByContractID|2 -# DEFAULT_INITIAL_ACCOUNT_BALANCE , when test payer account is created it will be created with this balance -initialAccountBalance=10080000000000 -# -# -## variable to control small account balance ,i.e initialAccountBalance / smallAccountBalanceFactor -smallAccountBalanceFactor=1001000000 -# -##Max transaction fee for Crypto operations -cryptoMaxFee=500000000 -# -##Max transaction fee for file operations -fileMaxFee=800000000 -# -##Max transaction fee for Smart Contract operations -contractMaxFee=6000000000 -# -# -## Account key type. The default is single, use single for main-net or test-net. -# accountKeyType=single -accountKeyType=single,keylist,thresholdKey -# -# -## flag whether or not to change genesis account key and rollback to start up key at end of the test -# changeGenesisKey=true -# -# -## Tx signature format: SignatureList, SignatureMap or Random (i.e. any of the two at random). The default is SignatureMap. -signatureFormat=SignatureMap -# -# -## Number of WACL keys to generate -numWaclKeys=3 -# -# -## flag whether or not to get receipts after a transfer transaction -getReceipt=true -# -# -## Tx body format: Body, BodyBytes, or Random (i.e. any of the two at random). The default is BodyBytes. -transactionBodyFormat=Random -# -# -## flag whether or not to use system accounts (i.e. account number under 100 excluding genesis and node accounts) -useSystemAccountAsPayer=false -# -# time in ms to retry api if server response receive as a busy -busyRetrySleep=100 -# -# how many time call need to retry -maxBusyRetry=15 -# -## Maximum Bytes per Transaction -transactionMaxBytes=6144 -# -## Code to return if there are test failures -errorReturnCode=3 diff --git a/hedera-node/test-clients/src/test/java/EndToEndPackageRunner.java b/hedera-node/test-clients/src/test/java/EndToEndPackageRunner.java deleted file mode 100644 index cd47849346d8..000000000000 --- a/hedera-node/test-clients/src/test/java/EndToEndPackageRunner.java +++ /dev/null @@ -1,659 +0,0 @@ -/* - * Copyright (C) 2022-2024 Hedera Hashgraph, LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import com.hedera.services.bdd.junit.TestBase; -import com.hedera.services.bdd.suites.autorenew.AccountAutoRenewalSuite; -import com.hedera.services.bdd.suites.autorenew.AutoRemovalCasesSuite; -import com.hedera.services.bdd.suites.autorenew.GracePeriodRestrictionsSuite; -import com.hedera.services.bdd.suites.autorenew.MacroFeesChargedSanityCheckSuite; -import com.hedera.services.bdd.suites.autorenew.NoGprIfNoAutoRenewSuite; -import com.hedera.services.bdd.suites.autorenew.TopicAutoRenewalSuite; -import com.hedera.services.bdd.suites.compose.LocalNetworkCheck; -import com.hedera.services.bdd.suites.compose.PerpetualLocalCalls; -import com.hedera.services.bdd.suites.consensus.AssortedHcsOps; -import com.hedera.services.bdd.suites.consensus.ChunkingSuite; -import com.hedera.services.bdd.suites.consensus.SubmitMessageSuite; -import com.hedera.services.bdd.suites.consensus.TopicCreateSuite; -import com.hedera.services.bdd.suites.consensus.TopicDeleteSuite; -import com.hedera.services.bdd.suites.consensus.TopicGetInfoSuite; -import com.hedera.services.bdd.suites.consensus.TopicUpdateSuite; -import com.hedera.services.bdd.suites.contract.hapi.ContractCallLocalSuite; -import com.hedera.services.bdd.suites.contract.hapi.ContractCallSuite; -import com.hedera.services.bdd.suites.contract.hapi.ContractCreateSuite; -import com.hedera.services.bdd.suites.contract.hapi.ContractDeleteSuite; -import com.hedera.services.bdd.suites.contract.hapi.ContractGetBytecodeSuite; -import com.hedera.services.bdd.suites.contract.hapi.ContractGetInfoSuite; -import com.hedera.services.bdd.suites.contract.hapi.ContractMusicalChairsSuite; -import com.hedera.services.bdd.suites.contract.hapi.ContractUpdateSuite; -import com.hedera.services.bdd.suites.contract.opcodes.CreateOperationSuite; -import com.hedera.services.bdd.suites.contract.opcodes.GlobalPropertiesSuite; -import com.hedera.services.bdd.suites.contract.opcodes.SStoreSuite; -import com.hedera.services.bdd.suites.contract.opcodes.SelfDestructSuite; -import com.hedera.services.bdd.suites.contract.openzeppelin.ERC1155ContractInteractions; -import com.hedera.services.bdd.suites.contract.openzeppelin.ERC20ContractInteractions; -import com.hedera.services.bdd.suites.contract.openzeppelin.ERC721ContractInteractions; -import com.hedera.services.bdd.suites.contract.precompile.AssociatePrecompileSuite; -import com.hedera.services.bdd.suites.contract.precompile.ContractBurnHTSSuite; -import com.hedera.services.bdd.suites.contract.precompile.ContractHTSSuite; -import com.hedera.services.bdd.suites.contract.precompile.ContractKeysHTSSuite; -import com.hedera.services.bdd.suites.contract.precompile.ContractMintHTSSuite; -import com.hedera.services.bdd.suites.contract.precompile.CreatePrecompileSuite; -import com.hedera.services.bdd.suites.contract.precompile.CryptoTransferHTSSuite; -import com.hedera.services.bdd.suites.contract.precompile.DelegatePrecompileSuite; -import com.hedera.services.bdd.suites.contract.records.LogsSuite; -import com.hedera.services.bdd.suites.contract.records.RecordsSuite; -import com.hedera.services.bdd.suites.contract.traceability.TraceabilitySuite; -import com.hedera.services.bdd.suites.crypto.AutoAccountCreationSuite; -import com.hedera.services.bdd.suites.crypto.AutoAccountUpdateSuite; -import com.hedera.services.bdd.suites.crypto.CryptoApproveAllowanceSuite; -import com.hedera.services.bdd.suites.crypto.CryptoCornerCasesSuite; -import com.hedera.services.bdd.suites.crypto.CryptoCreateSuite; -import com.hedera.services.bdd.suites.crypto.CryptoDeleteSuite; -import com.hedera.services.bdd.suites.crypto.CryptoGetInfoRegression; -import com.hedera.services.bdd.suites.crypto.CryptoGetRecordsRegression; -import com.hedera.services.bdd.suites.crypto.CryptoTransferSuite; -import com.hedera.services.bdd.suites.crypto.CryptoUpdateSuite; -import com.hedera.services.bdd.suites.crypto.CrytoCreateSuiteWithUTF8; -import com.hedera.services.bdd.suites.crypto.HelloWorldSpec; -import com.hedera.services.bdd.suites.crypto.MiscCryptoSuite; -import com.hedera.services.bdd.suites.crypto.QueryPaymentSuite; -import com.hedera.services.bdd.suites.crypto.RandomOps; -import com.hedera.services.bdd.suites.crypto.TransferWithCustomFixedFees; -import com.hedera.services.bdd.suites.crypto.TransferWithCustomFractionalFees; -import com.hedera.services.bdd.suites.crypto.TxnReceiptRegression; -import com.hedera.services.bdd.suites.crypto.TxnRecordRegression; -import com.hedera.services.bdd.suites.crypto.UnsupportedQueriesRegression; -import com.hedera.services.bdd.suites.fees.AllBaseOpFeesSuite; -import com.hedera.services.bdd.suites.fees.CongestionPricingSuite; -import com.hedera.services.bdd.suites.fees.CostOfEverythingSuite; -import com.hedera.services.bdd.suites.fees.CreateAndUpdateOps; -import com.hedera.services.bdd.suites.fees.OverlappingKeysSuite; -import com.hedera.services.bdd.suites.fees.QueryPaymentExploitsSuite; -import com.hedera.services.bdd.suites.fees.SpecialAccountsAreExempted; -import com.hedera.services.bdd.suites.file.DiverseStateCreation; -import com.hedera.services.bdd.suites.file.DiverseStateValidation; -import com.hedera.services.bdd.suites.file.ExchangeRateControlSuite; -import com.hedera.services.bdd.suites.file.FetchSystemFiles; -import com.hedera.services.bdd.suites.file.FileAppendSuite; -import com.hedera.services.bdd.suites.file.FileCreateSuite; -import com.hedera.services.bdd.suites.file.FileDeleteSuite; -import com.hedera.services.bdd.suites.file.FileUpdateSuite; -import com.hedera.services.bdd.suites.file.PermissionSemanticsSpec; -import com.hedera.services.bdd.suites.file.ProtectedFilesUpdateSuite; -import com.hedera.services.bdd.suites.file.ValidateNewAddressBook; -import com.hedera.services.bdd.suites.file.negative.AppendFailuresSpec; -import com.hedera.services.bdd.suites.file.negative.CreateFailuresSpec; -import com.hedera.services.bdd.suites.file.negative.DeleteFailuresSpec; -import com.hedera.services.bdd.suites.file.negative.QueryFailuresSpec; -import com.hedera.services.bdd.suites.file.negative.UpdateFailuresSpec; -import com.hedera.services.bdd.suites.file.positive.CreateSuccessSpec; -import com.hedera.services.bdd.suites.file.positive.SysDelSysUndelSpec; -import com.hedera.services.bdd.suites.issues.Issue1648Suite; -import com.hedera.services.bdd.suites.issues.Issue1741Suite; -import com.hedera.services.bdd.suites.issues.Issue1742Suite; -import com.hedera.services.bdd.suites.issues.Issue1744Suite; -import com.hedera.services.bdd.suites.issues.Issue1758Suite; -import com.hedera.services.bdd.suites.issues.Issue1765Suite; -import com.hedera.services.bdd.suites.issues.Issue2051Spec; -import com.hedera.services.bdd.suites.issues.Issue2098Spec; -import com.hedera.services.bdd.suites.issues.Issue2143Spec; -import com.hedera.services.bdd.suites.issues.Issue2150Spec; -import com.hedera.services.bdd.suites.issues.Issue2319Spec; -import com.hedera.services.bdd.suites.issues.Issue305Spec; -import com.hedera.services.bdd.suites.issues.Issue310Suite; -import com.hedera.services.bdd.suites.meta.VersionInfoSpec; -import com.hedera.services.bdd.suites.perf.AccountBalancesClientSaveLoadTest; -import com.hedera.services.bdd.suites.perf.AdjustFeeScheduleSuite; -import com.hedera.services.bdd.suites.perf.FileContractMemoPerfSuite; -import com.hedera.services.bdd.suites.perf.QueryOnlyLoadTest; -import com.hedera.services.bdd.suites.perf.contract.ContractCallLoadTest; -import com.hedera.services.bdd.suites.perf.contract.ContractCallLocalPerfSuite; -import com.hedera.services.bdd.suites.perf.contract.ContractCallPerfSuite; -import com.hedera.services.bdd.suites.perf.contract.ContractPerformanceSuite; -import com.hedera.services.bdd.suites.perf.contract.FibonacciPlusLoadProvider; -import com.hedera.services.bdd.suites.perf.contract.MixedSmartContractOpsLoadTest; -import com.hedera.services.bdd.suites.perf.contract.opcodes.SStoreOperationLoadTest; -import com.hedera.services.bdd.suites.perf.crypto.CryptoAllowancePerfSuite; -import com.hedera.services.bdd.suites.perf.crypto.CryptoCreatePerfSuite; -import com.hedera.services.bdd.suites.perf.crypto.CryptoTransferLoadTest; -import com.hedera.services.bdd.suites.perf.crypto.CryptoTransferLoadTestWithAutoAccounts; -import com.hedera.services.bdd.suites.perf.crypto.CryptoTransferLoadTestWithInvalidAccounts; -import com.hedera.services.bdd.suites.perf.crypto.CryptoTransferPerfSuite; -import com.hedera.services.bdd.suites.perf.crypto.CryptoTransferPerfSuiteWOpProvider; -import com.hedera.services.bdd.suites.perf.crypto.SimpleXfersAvoidingHotspot; -import com.hedera.services.bdd.suites.perf.file.FileExpansionLoadProvider; -import com.hedera.services.bdd.suites.perf.file.FileUpdateLoadTest; -import com.hedera.services.bdd.suites.perf.file.MixedFileOpsLoadTest; -import com.hedera.services.bdd.suites.perf.mixedops.MixedOpsMemoPerfSuite; -import com.hedera.services.bdd.suites.perf.mixedops.MixedTransferAndSubmitLoadTest; -import com.hedera.services.bdd.suites.perf.mixedops.MixedTransferCallAndSubmitLoadTest; -import com.hedera.services.bdd.suites.perf.schedule.OnePendingSigScheduledXfersLoad; -import com.hedera.services.bdd.suites.perf.schedule.ReadyToRunScheduledXfersLoad; -import com.hedera.services.bdd.suites.perf.token.TokenCreatePerfSuite; -import com.hedera.services.bdd.suites.perf.token.TokenRelStatusChanges; -import com.hedera.services.bdd.suites.perf.token.TokenTransferBasicLoadTest; -import com.hedera.services.bdd.suites.perf.token.TokenTransfersLoadProvider; -import com.hedera.services.bdd.suites.perf.token.UniqueTokenStateSetup; -import com.hedera.services.bdd.suites.perf.topic.CreateTopicPerfSuite; -import com.hedera.services.bdd.suites.perf.topic.HCSChunkingRealisticPerfSuite; -import com.hedera.services.bdd.suites.perf.topic.SubmitMessageLoadTest; -import com.hedera.services.bdd.suites.perf.topic.SubmitMessagePerfSuite; -import com.hedera.services.bdd.suites.perf.topic.createTopicLoadTest; -import com.hedera.services.bdd.suites.records.ContractRecordsSanityCheckSuite; -import com.hedera.services.bdd.suites.records.CryptoRecordsSanityCheckSuite; -import com.hedera.services.bdd.suites.records.DuplicateManagementTest; -import com.hedera.services.bdd.suites.records.FileRecordsSanityCheckSuite; -import com.hedera.services.bdd.suites.records.RecordCreationSuite; -import com.hedera.services.bdd.suites.records.SignedTransactionBytesRecordsSuite; -import com.hedera.services.bdd.suites.regression.SplittingThrottlesWorks; -import com.hedera.services.bdd.suites.regression.SteadyStateThrottlingCheck; -import com.hedera.services.bdd.suites.regression.UmbrellaRedux; -import com.hedera.services.bdd.suites.schedule.ScheduleCreateSpecs; -import com.hedera.services.bdd.suites.schedule.ScheduleDeleteSpecs; -import com.hedera.services.bdd.suites.schedule.ScheduleExecutionSpecStateful; -import com.hedera.services.bdd.suites.schedule.ScheduleExecutionSpecs; -import com.hedera.services.bdd.suites.schedule.ScheduleLongTermExecutionSpecs; -import com.hedera.services.bdd.suites.schedule.ScheduleLongTermSignSpecs; -import com.hedera.services.bdd.suites.schedule.ScheduleRecordSpecs; -import com.hedera.services.bdd.suites.schedule.ScheduleSignSpecs; -import com.hedera.services.bdd.suites.streaming.RunTransfers; -import com.hedera.services.bdd.suites.throttling.GasLimitThrottlingSuite; -import com.hedera.services.bdd.suites.throttling.PrivilegedOpsSuite; -import com.hedera.services.bdd.suites.throttling.ThrottleDefValidationSuite; -import com.hedera.services.bdd.suites.token.Hip17UnhappyAccountsSuite; -import com.hedera.services.bdd.suites.token.Hip17UnhappyTokensSuite; -import com.hedera.services.bdd.suites.token.TokenAssociationSpecs; -import com.hedera.services.bdd.suites.token.TokenCreateSpecs; -import com.hedera.services.bdd.suites.token.TokenDeleteSpecs; -import com.hedera.services.bdd.suites.token.TokenFeeScheduleUpdateSpecs; -import com.hedera.services.bdd.suites.token.TokenManagementSpecs; -import com.hedera.services.bdd.suites.token.TokenManagementSpecsStateful; -import com.hedera.services.bdd.suites.token.TokenMiscOps; -import com.hedera.services.bdd.suites.token.TokenPauseSpecs; -import com.hedera.services.bdd.suites.token.TokenTotalSupplyAfterMintBurnWipeSuite; -import com.hedera.services.bdd.suites.token.TokenTransactSpecs; -import com.hedera.services.bdd.suites.token.TokenUpdateSpecs; -import com.hedera.services.bdd.suites.token.UniqueTokenManagementSpecs; -import java.util.Collection; -import java.util.List; -import org.junit.jupiter.api.DynamicContainer; -import org.junit.jupiter.api.Tag; -import org.junit.jupiter.api.TestFactory; - -@SuppressWarnings({"java:S2699", "java:S3577"}) -class EndToEndPackageRunner extends TestBase { - - @Tag("autorenew") - @TestFactory - Collection autorenew() { - return List.of( - extractSpecsFromSuite(AccountAutoRenewalSuite::new), - extractSpecsFromSuite(AutoRemovalCasesSuite::new), - extractSpecsFromSuite(GracePeriodRestrictionsSuite::new), - extractSpecsFromSuite(MacroFeesChargedSanityCheckSuite::new), - extractSpecsFromSuite(NoGprIfNoAutoRenewSuite::new), - extractSpecsFromSuite(TopicAutoRenewalSuite::new)); - } - - @Tag("compose") - @TestFactory - Collection compose() { - return List.of(extractSpecsFromSuite(LocalNetworkCheck::new), extractSpecsFromSuite(PerpetualLocalCalls::new)); - } - - @Tag("consensus") - @TestFactory - Collection consensus() { - return List.of( - extractSpecsFromSuite(AssortedHcsOps::new), - extractSpecsFromSuite(ChunkingSuite::new), - extractSpecsFromSuite(SubmitMessageSuite::new), - extractSpecsFromSuite(TopicCreateSuite::new), - extractSpecsFromSuite(TopicDeleteSuite::new), - extractSpecsFromSuite(TopicGetInfoSuite::new), - extractSpecsFromSuite(TopicUpdateSuite::new)); - } - - @Tag("contract") - @Tag("contract.precompile") - @Tag("contract.precompile.part1") - @TestFactory - Collection contractPrecompile() { - return List.of( - extractSpecsFromSuite(AssociatePrecompileSuite::new), - extractSpecsFromSuite(ContractBurnHTSSuite::new), - extractSpecsFromSuite(ContractHTSSuite::new), - extractSpecsFromSuite(ContractKeysHTSSuite::new), - extractSpecsFromSuite(ContractMintHTSSuite::new), - extractSpecsFromSuite(CreatePrecompileSuite::new)); - } - - @Tag("contract") - @Tag("contract.precompile") - @Tag("contract.precompile.part1.eth") - @TestFactory - Collection contractPrecompileEth() { - return List.of(new DynamicContainer[] { - extractSpecsFromSuiteForEth(AssociatePrecompileSuite::new), - extractSpecsFromSuiteForEth(ContractBurnHTSSuite::new), - extractSpecsFromSuiteForEth(ContractHTSSuite::new), - extractSpecsFromSuiteForEth(ContractKeysHTSSuite::new), - extractSpecsFromSuiteForEth(ContractMintHTSSuite::new), - extractSpecsFromSuiteForEth(CreatePrecompileSuite::new) - }); - } - - @Tag("contract") - @Tag("contract.precompile") - @Tag("contract.precompile.part2") - @TestFactory - Collection contractPrecompile2() { - return List.of(new DynamicContainer[] { - extractSpecsFromSuite(CryptoTransferHTSSuite::new), extractSpecsFromSuite(DelegatePrecompileSuite::new), - }); - } - - @Tag("contract") - @Tag("contract.precompile") - @Tag("contract.precompile.part2.eth") - @TestFactory - Collection contractPrecompile2Eth() { - return List.of(new DynamicContainer[] { - extractSpecsFromSuiteForEth(CryptoTransferHTSSuite::new), - extractSpecsFromSuiteForEth(DelegatePrecompileSuite::new) - }); - } - - @Tag("contract") - @Tag("contract.openzeppelin") - @TestFactory - Collection contractOpenZeppelin() { - return List.of( - extractSpecsFromSuite(ERC20ContractInteractions::new), - extractSpecsFromSuite(ERC721ContractInteractions::new), - extractSpecsFromSuite(ERC1155ContractInteractions::new)); - } - - @Tag("contract") - @Tag("contract.openzeppelin.eth") - @TestFactory - Collection contractOpenZeppelinEth() { - return List.of(new DynamicContainer[] { - extractSpecsFromSuiteForEth(ERC20ContractInteractions::new), - extractSpecsFromSuiteForEth(ERC721ContractInteractions::new), - extractSpecsFromSuiteForEth(ERC1155ContractInteractions::new) - }); - } - - @Tag("contract") - @Tag("contract.records") - @TestFactory - Collection contractRecords() { - return List.of(extractSpecsFromSuite(LogsSuite::new), extractSpecsFromSuite(RecordsSuite::new)); - } - - @Tag("contract") - @Tag("contract.records.eth") - @TestFactory - Collection contractRecordsEth() { - return List.of(extractSpecsFromSuiteForEth(LogsSuite::new), extractSpecsFromSuiteForEth(RecordsSuite::new)); - } - - @Tag("contract") - @Tag("contract.opcodes") - @TestFactory - Collection contractOpcodes() { - return List.of( - extractSpecsFromSuite(CreateOperationSuite::new), - extractSpecsFromSuite(GlobalPropertiesSuite::new), - extractSpecsFromSuite(SelfDestructSuite::new), - extractSpecsFromSuite(SStoreSuite::new)); - } - - @Tag("contract") - @Tag("contract.opcodes.eth") - @TestFactory - Collection contractOpcodesEth() { - return List.of(new DynamicContainer[] { - extractSpecsFromSuiteForEth(CreateOperationSuite::new), - extractSpecsFromSuiteForEth(GlobalPropertiesSuite::new), - extractSpecsFromSuiteForEth(SelfDestructSuite::new), - extractSpecsFromSuiteForEth(SStoreSuite::new) - }); - } - - @Tag("contract") - @Tag("contract.hapi") - @TestFactory - Collection contractHapi() { - return List.of(new DynamicContainer[] { - extractSpecsFromSuite(ContractCallLocalSuite::new), - extractSpecsFromSuite(ContractCallSuite::new), - extractSpecsFromSuite(ContractCreateSuite::new), - extractSpecsFromSuite(ContractDeleteSuite::new), - extractSpecsFromSuite(ContractGetBytecodeSuite::new), - extractSpecsFromSuite(ContractGetInfoSuite::new), - extractSpecsFromSuite(ContractMusicalChairsSuite::new), - extractSpecsFromSuite(ContractUpdateSuite::new) - }); - } - - @Tag("contract") - @Tag("contract.hapi.eth") - @TestFactory - Collection contractHapiEth() { - return List.of(new DynamicContainer[] { - extractSpecsFromSuiteForEth(ContractCallLocalSuite::new), - extractSpecsFromSuiteForEth(ContractCallSuite::new), - extractSpecsFromSuiteForEth(ContractCreateSuite::new), - extractSpecsFromSuiteForEth(ContractDeleteSuite::new), - extractSpecsFromSuiteForEth(ContractGetBytecodeSuite::new), - extractSpecsFromSuiteForEth(ContractGetInfoSuite::new), - extractSpecsFromSuiteForEth(ContractMusicalChairsSuite::new), - extractSpecsFromSuiteForEth(ContractUpdateSuite::new) - }); - } - - @Tag("contract") - @Tag("contract.traceability") - @TestFactory - Collection contractTraceability() { - return List.of(extractSpecsFromSuite(TraceabilitySuite::new)); - } - - @Tag("contract") - @Tag("contract.traceability.eth") - @TestFactory - Collection contractTraceabilityEth() { - // FUTURE WORK - re-add traceability suites when updated for sidecar support - return List.of(); - } - - @Tag("crypto") - @TestFactory - Collection crypto() { - return List.of( - extractSpecsFromSuite(AutoAccountCreationSuite::new), - extractSpecsFromSuite(AutoAccountUpdateSuite::new), - extractSpecsFromSuite(CryptoApproveAllowanceSuite::new), - extractSpecsFromSuite(CryptoCornerCasesSuite::new), - extractSpecsFromSuite(CryptoCreateSuite::new), - extractSpecsFromSuite(CryptoDeleteSuite::new), - extractSpecsFromSuite(CryptoGetInfoRegression::new), - extractSpecsFromSuite(CryptoGetRecordsRegression::new), - extractSpecsFromSuite(CryptoTransferSuite::new), - extractSpecsFromSuite(CryptoUpdateSuite::new), - extractSpecsFromSuite(CrytoCreateSuiteWithUTF8::new), - extractSpecsFromSuite(HelloWorldSpec::new), - extractSpecsFromSuite(MiscCryptoSuite::new), - extractSpecsFromSuite(QueryPaymentSuite::new), - extractSpecsFromSuite(RandomOps::new), - extractSpecsFromSuite(TransferWithCustomFixedFees::new), - extractSpecsFromSuite(TransferWithCustomFractionalFees::new), - extractSpecsFromSuite(TxnReceiptRegression::new), - extractSpecsFromSuite(TxnRecordRegression::new), - extractSpecsFromSuite(UnsupportedQueriesRegression::new)); - } - - @Tag("fees") - @TestFactory - Collection fees() { - return List.of( - extractSpecsFromSuite(AllBaseOpFeesSuite::new), - extractSpecsFromSuite(CongestionPricingSuite::new), - extractSpecsFromSuite(CostOfEverythingSuite::new), - extractSpecsFromSuite(CreateAndUpdateOps::new), - extractSpecsFromSuite(OverlappingKeysSuite::new), - extractSpecsFromSuite(QueryPaymentExploitsSuite::new), - extractSpecsFromSuite(SpecialAccountsAreExempted::new)); - } - - @Tag("file") - @TestFactory - Collection file() { - return List.of( - extractSpecsFromSuite(DiverseStateCreation::new), - extractSpecsFromSuite(DiverseStateValidation::new), - extractSpecsFromSuite(ExchangeRateControlSuite::new), - extractSpecsFromSuite(FetchSystemFiles::new), - extractSpecsFromSuite(FileAppendSuite::new), - extractSpecsFromSuite(FileCreateSuite::new), - extractSpecsFromSuite(FileDeleteSuite::new), - extractSpecsFromSuite(FileUpdateSuite::new), - extractSpecsFromSuite(PermissionSemanticsSpec::new), - extractSpecsFromSuite(ProtectedFilesUpdateSuite::new), - extractSpecsFromSuite(ValidateNewAddressBook::new)); - } - - @Tag("file") - @Tag("file.positive") - @TestFactory - Collection filePositive() { - return List.of(extractSpecsFromSuite(CreateSuccessSpec::new), extractSpecsFromSuite(SysDelSysUndelSpec::new)); - } - - @Tag("file") - @Tag("file.negative") - @TestFactory - Collection fileNegative() { - return List.of( - extractSpecsFromSuite(AppendFailuresSpec::new), - extractSpecsFromSuite(CreateFailuresSpec::new), - extractSpecsFromSuite(DeleteFailuresSpec::new), - extractSpecsFromSuite(QueryFailuresSpec::new), - extractSpecsFromSuite(UpdateFailuresSpec::new)); - } - - @Tag("issues") - @TestFactory - Collection issues() { - return List.of( - extractSpecsFromSuite(Issue305Spec::new), - extractSpecsFromSuite(Issue310Suite::new), - extractSpecsFromSuite(Issue1648Suite::new), - extractSpecsFromSuite(Issue1741Suite::new), - extractSpecsFromSuite(Issue1742Suite::new), - extractSpecsFromSuite(Issue1744Suite::new), - extractSpecsFromSuite(Issue1758Suite::new), - extractSpecsFromSuite(Issue1765Suite::new), - extractSpecsFromSuite(Issue2051Spec::new), - extractSpecsFromSuite(Issue2098Spec::new), - extractSpecsFromSuite(Issue2143Spec::new), - extractSpecsFromSuite(Issue2150Spec::new), - extractSpecsFromSuite(Issue2319Spec::new)); - } - - @Tag("meta") - @TestFactory - Collection meta() { - return List.of(extractSpecsFromSuite(VersionInfoSpec::new)); - } - - @Tag("perf") - @TestFactory - Collection perf() { - return List.of( - extractSpecsFromSuite(AccountBalancesClientSaveLoadTest::new), - extractSpecsFromSuite(AdjustFeeScheduleSuite::new), - extractSpecsFromSuite(FileContractMemoPerfSuite::new), - extractSpecsFromSuite(QueryOnlyLoadTest::new)); - } - - @Tag("perf") - @Tag("perf.contract") - @TestFactory - Collection perfContract() { - return List.of( - extractSpecsFromSuite(ContractCallLoadTest::new), - extractSpecsFromSuite(ContractCallLocalPerfSuite::new), - extractSpecsFromSuite(ContractCallPerfSuite::new), - extractSpecsFromSuite(ContractPerformanceSuite::new), - extractSpecsFromSuite(FibonacciPlusLoadProvider::new), - extractSpecsFromSuite(MixedSmartContractOpsLoadTest::new)); - } - - @Tag("perf") - @Tag("perf.contract.opcodes") - @TestFactory - Collection perfContractOpcodes() { - return List.of(extractSpecsFromSuite(SStoreOperationLoadTest::new)); - } - - @Tag("perf") - @Tag("perf.crypto") - @TestFactory - Collection perfCrypto() { - return List.of( - extractSpecsFromSuite(CryptoAllowancePerfSuite::new), - extractSpecsFromSuite(CryptoCreatePerfSuite::new), - extractSpecsFromSuite(CryptoTransferLoadTest::new), - extractSpecsFromSuite(CryptoTransferLoadTestWithAutoAccounts::new), - extractSpecsFromSuite(CryptoTransferLoadTestWithInvalidAccounts::new), - extractSpecsFromSuite(CryptoTransferPerfSuite::new), - extractSpecsFromSuite(CryptoTransferPerfSuiteWOpProvider::new), - extractSpecsFromSuite(SimpleXfersAvoidingHotspot::new)); - } - - @Tag("perf") - @Tag("perf.file") - @TestFactory - Collection perfFile() { - return List.of( - extractSpecsFromSuite(FileExpansionLoadProvider::new), - extractSpecsFromSuite(FileUpdateLoadTest::new), - extractSpecsFromSuite(MixedFileOpsLoadTest::new)); - } - - @Tag("perf") - @Tag("perf.mixedops") - @TestFactory - Collection perfMixedOps() { - return List.of( - extractSpecsFromSuite(MixedFileOpsLoadTest::new), - extractSpecsFromSuite(MixedOpsMemoPerfSuite::new), - extractSpecsFromSuite(MixedTransferAndSubmitLoadTest::new), - extractSpecsFromSuite(MixedTransferCallAndSubmitLoadTest::new)); - } - - @Tag("perf") - @Tag("perf.schedule") - @TestFactory - Collection perfSchedule() { - return List.of( - extractSpecsFromSuite(OnePendingSigScheduledXfersLoad::new), - extractSpecsFromSuite(ReadyToRunScheduledXfersLoad::new)); - } - - @Tag("perf") - @Tag("perf.token") - @TestFactory - Collection perfToken() { - return List.of( - extractSpecsFromSuite(TokenCreatePerfSuite::new), - extractSpecsFromSuite(TokenRelStatusChanges::new), - extractSpecsFromSuite(TokenTransferBasicLoadTest::new), - extractSpecsFromSuite(TokenTransfersLoadProvider::new), - extractSpecsFromSuite(UniqueTokenStateSetup::new)); - } - - @Tag("perf") - @Tag("perf.topic") - @TestFactory - Collection perfTopic() { - return List.of( - extractSpecsFromSuite(createTopicLoadTest::new), - extractSpecsFromSuite(CreateTopicPerfSuite::new), - extractSpecsFromSuite(HCSChunkingRealisticPerfSuite::new), - extractSpecsFromSuite(SubmitMessageLoadTest::new), - extractSpecsFromSuite(SubmitMessagePerfSuite::new)); - } - - @Tag("records") - @TestFactory - Collection records() { - return List.of( - extractSpecsFromSuite(ContractRecordsSanityCheckSuite::new), - extractSpecsFromSuite(CryptoRecordsSanityCheckSuite::new), - extractSpecsFromSuite(DuplicateManagementTest::new), - extractSpecsFromSuite(FileRecordsSanityCheckSuite::new), - extractSpecsFromSuite(RecordCreationSuite::new), - extractSpecsFromSuite(SignedTransactionBytesRecordsSuite::new)); - } - - @Tag("regression") - @TestFactory - Collection regression() { - return List.of( - extractSpecsFromSuite(SplittingThrottlesWorks::new), - extractSpecsFromSuite(SteadyStateThrottlingCheck::new), - extractSpecsFromSuite(UmbrellaRedux::new)); - } - - @Tag("schedule") - @TestFactory - Collection schedule() { - return List.of( - extractSpecsFromSuite(ScheduleCreateSpecs::new), - extractSpecsFromSuite(ScheduleDeleteSpecs::new), - extractSpecsFromSuite(ScheduleExecutionSpecs::new), - extractSpecsFromSuite(ScheduleExecutionSpecStateful::new), - extractSpecsFromSuite(ScheduleRecordSpecs::new), - extractSpecsFromSuite(ScheduleSignSpecs::new), - extractSpecsFromSuite(ScheduleLongTermExecutionSpecs::new), - extractSpecsFromSuite(ScheduleLongTermSignSpecs::new)); - } - - @Tag("streaming") - @TestFactory - Collection streaming() { - return List.of(extractSpecsFromSuite(RunTransfers::new)); - } - - @Tag("throttling") - @TestFactory - Collection throttling() { - return List.of( - extractSpecsFromSuite(GasLimitThrottlingSuite::new), - extractSpecsFromSuite(PrivilegedOpsSuite::new), - extractSpecsFromSuite(ThrottleDefValidationSuite::new)); - } - - @Tag("token") - @TestFactory - Collection token() { - return List.of( - extractSpecsFromSuite(Hip17UnhappyAccountsSuite::new), - extractSpecsFromSuite(Hip17UnhappyTokensSuite::new), - extractSpecsFromSuite(TokenAssociationSpecs::new), - extractSpecsFromSuite(TokenCreateSpecs::new), - extractSpecsFromSuite(TokenDeleteSpecs::new), - extractSpecsFromSuite(TokenFeeScheduleUpdateSpecs::new), - extractSpecsFromSuite(TokenManagementSpecs::new), - extractSpecsFromSuite(TokenManagementSpecsStateful::new), - extractSpecsFromSuite(TokenMiscOps::new), - extractSpecsFromSuite(TokenPauseSpecs::new), - extractSpecsFromSuite(TokenTotalSupplyAfterMintBurnWipeSuite::new), - extractSpecsFromSuite(TokenTransactSpecs::new), - extractSpecsFromSuite(TokenUpdateSpecs::new), - extractSpecsFromSuite(UniqueTokenManagementSpecs::new)); - } - - @Tag("utils") - @TestFactory - Collection utils() { - return List.of(extractSpecsFromSuite(SubmitMessagePerfSuite::new)); - } -} diff --git a/hedera-node/test-clients/src/yahcli/java/com/hedera/services/yahcli/suites/BalanceSuite.java b/hedera-node/test-clients/src/yahcli/java/com/hedera/services/yahcli/suites/BalanceSuite.java index 806c65647ae8..8039d59374e4 100644 --- a/hedera-node/test-clients/src/yahcli/java/com/hedera/services/yahcli/suites/BalanceSuite.java +++ b/hedera-node/test-clients/src/yahcli/java/com/hedera/services/yahcli/suites/BalanceSuite.java @@ -25,8 +25,10 @@ import java.util.List; import java.util.Map; import java.util.stream.Collectors; +import java.util.stream.Stream; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; +import org.junit.jupiter.api.DynamicTest; public class BalanceSuite extends HapiSuite { private static final Logger log = LogManager.getLogger(BalanceSuite.class); @@ -44,13 +46,13 @@ private List rationalized(final String[] accounts) { } @Override - public List getSpecsInSuite() { - List specToRun = new ArrayList<>(); + public List> getSpecsInSuite() { + List> specToRun = new ArrayList<>(); accounts.forEach(s -> specToRun.add(getBalance(s))); return specToRun; } - private HapiSpec getBalance(String accountID) { + final Stream getBalance(String accountID) { return HapiSpec.customHapiSpec("getBalance") .withProperties(specConfig) .given() diff --git a/hedera-node/test-clients/src/yahcli/java/com/hedera/services/yahcli/suites/CostOfEveryThingSuite.java b/hedera-node/test-clients/src/yahcli/java/com/hedera/services/yahcli/suites/CostOfEveryThingSuite.java index de02a2667fce..287f2256c694 100644 --- a/hedera-node/test-clients/src/yahcli/java/com/hedera/services/yahcli/suites/CostOfEveryThingSuite.java +++ b/hedera-node/test-clients/src/yahcli/java/com/hedera/services/yahcli/suites/CostOfEveryThingSuite.java @@ -16,6 +16,7 @@ package com.hedera.services.yahcli.suites; +import static com.hedera.services.bdd.spec.HapiSpec.customHapiSpec; import static com.hedera.services.bdd.spec.transactions.TxnVerbs.scheduleDelete; import static com.hedera.services.bdd.spec.utilops.UtilVerbs.getTransactionFee; import static com.hedera.services.bdd.spec.utilops.UtilVerbs.withOpContext; @@ -40,6 +41,7 @@ import java.util.stream.Stream; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; +import org.junit.jupiter.api.DynamicTest; public class CostOfEveryThingSuite extends HapiSuite { private static final Logger log = LogManager.getLogger(CostOfEveryThingSuite.class); @@ -80,7 +82,7 @@ protected Logger getResultsLogger() { } @Override - public List getSpecsInSuite() { + public List> getSpecsInSuite() { return Stream.of( Optional.ofNullable( ServiceTypes.contains(Utils.ServiceType.CRYPTO) ? canonicalCryptoOps() : null), @@ -97,8 +99,8 @@ public List getSpecsInSuite() { .collect(Collectors.toList()); } - HapiSpec canonicalContractOps() { - return HapiSpec.customHapiSpec("canonicalContractOps") + final Stream canonicalContractOps() { + return customHapiSpec("canonicalContractOps") .withProperties(specConfig, Map.of(COST_SNAPSHOT_MODE, costSnapshotMode.toString())) .given( UtilVerbs.newKeyNamed("key").shape(KeyShape.SIMPLE), @@ -154,12 +156,12 @@ HapiSpec canonicalContractOps() { getTransactionFee("canonicalContractDelete", feeTableBuilder, "contractDelete")); } - HapiSpec canonicalFileOps() { + final Stream canonicalFileOps() { int fileSize = 1000; final byte[] first = TxnUtils.randomUtf8Bytes(fileSize); final byte[] next = TxnUtils.randomUtf8Bytes(fileSize); - return HapiSpec.customHapiSpec("canonicalFileOps") + return customHapiSpec("canonicalFileOps") .withProperties(specConfig, Map.of(COST_SNAPSHOT_MODE, costSnapshotMode.toString())) .given( UtilVerbs.newKeyNamed("key").shape(KeyShape.SIMPLE), @@ -202,8 +204,8 @@ HapiSpec canonicalFileOps() { getTransactionFee("canonicalFileDelete", feeTableBuilder, "fileDelete")); } - HapiSpec canonicalTopicOps() { - return HapiSpec.customHapiSpec("canonicalTopicOps") + final Stream canonicalTopicOps() { + return customHapiSpec("canonicalTopicOps") .withProperties(specConfig, Map.of(COST_SNAPSHOT_MODE, costSnapshotMode.toString())) .given( UtilVerbs.newKeyNamed("key").shape(KeyShape.SIMPLE), @@ -238,8 +240,8 @@ HapiSpec canonicalTopicOps() { getTransactionFee("canonicalTopicDelete", feeTableBuilder, "consensusDeleteTopic")); } - HapiSpec canonicalTokenOps() { - return HapiSpec.customHapiSpec("canonicalTokenOps") + final Stream canonicalTokenOps() { + return customHapiSpec("canonicalTokenOps") .withProperties(specConfig, Map.of(COST_SNAPSHOT_MODE, costSnapshotMode.toString())) .given( UtilVerbs.newKeyNamed("key").shape(KeyShape.SIMPLE), @@ -350,9 +352,9 @@ HapiSpec canonicalTokenOps() { getTransactionFee("canonicalTokenDelete", feeTableBuilder, "tokenDelete")); } - HapiSpec canonicalCryptoOps() { + final Stream canonicalCryptoOps() { - return HapiSpec.customHapiSpec("canonicalCryptoOps") + return customHapiSpec("canonicalCryptoOps") .withProperties(specConfig, Map.of(COST_SNAPSHOT_MODE, costSnapshotMode.toString())) .given( UtilVerbs.newKeyNamed("key").shape(KeyShape.SIMPLE), @@ -397,8 +399,8 @@ HapiSpec canonicalCryptoOps() { getTransactionFee("canonicalCryptoDeletion", feeTableBuilder, "cryptoDelete")); } - HapiSpec canonicalScheduleOps() { - return HapiSpec.customHapiSpec("canonicalScheduleOps") + final Stream canonicalScheduleOps() { + return customHapiSpec("canonicalScheduleOps") .withProperties(specConfig, Map.of(COST_SNAPSHOT_MODE, costSnapshotMode.toString())) .given( TxnVerbs.cryptoCreate(PAYING_SENDER).balance(HapiSuite.ONE_HUNDRED_HBARS), diff --git a/hedera-node/test-clients/src/yahcli/java/com/hedera/services/yahcli/suites/CreateSuite.java b/hedera-node/test-clients/src/yahcli/java/com/hedera/services/yahcli/suites/CreateSuite.java index bb4904336ab2..b1d5b313eec0 100644 --- a/hedera-node/test-clients/src/yahcli/java/com/hedera/services/yahcli/suites/CreateSuite.java +++ b/hedera-node/test-clients/src/yahcli/java/com/hedera/services/yahcli/suites/CreateSuite.java @@ -33,8 +33,10 @@ import java.util.Map; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicLong; +import java.util.stream.Stream; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; +import org.junit.jupiter.api.DynamicTest; public class CreateSuite extends HapiSuite { private static final Logger log = LogManager.getLogger(CreateSuite.class); @@ -68,11 +70,11 @@ public CreateSuite( } @Override - public List getSpecsInSuite() { + public List> getSpecsInSuite() { return List.of(doCreate()); } - private HapiSpec doCreate() { + final Stream doCreate() { if (!novelTarget.endsWith(NOVELTY + ".pem")) { throw new IllegalArgumentException("Only accepts tentative new key material named 'novel.pem'"); } diff --git a/hedera-node/test-clients/src/yahcli/java/com/hedera/services/yahcli/suites/FreezeHelperSuite.java b/hedera-node/test-clients/src/yahcli/java/com/hedera/services/yahcli/suites/FreezeHelperSuite.java index 127c64064b90..cdb17a10426f 100644 --- a/hedera-node/test-clients/src/yahcli/java/com/hedera/services/yahcli/suites/FreezeHelperSuite.java +++ b/hedera-node/test-clients/src/yahcli/java/com/hedera/services/yahcli/suites/FreezeHelperSuite.java @@ -23,8 +23,10 @@ import java.time.Instant; import java.util.List; import java.util.Map; +import java.util.stream.Stream; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; +import org.junit.jupiter.api.DynamicTest; public class FreezeHelperSuite extends HapiSuite { private static final Logger log = LogManager.getLogger(FreezeHelperSuite.class); @@ -42,11 +44,11 @@ public FreezeHelperSuite( } @Override - public List getSpecsInSuite() { - return List.of(new HapiSpec[] {doFreeze()}); + public List> getSpecsInSuite() { + return List.of(doFreeze()); } - private HapiSpec doFreeze() { + final Stream doFreeze() { return HapiSpec.customHapiSpec("DoFreeze") .withProperties(specConfig) .given() diff --git a/hedera-node/test-clients/src/yahcli/java/com/hedera/services/yahcli/suites/InfoSuite.java b/hedera-node/test-clients/src/yahcli/java/com/hedera/services/yahcli/suites/InfoSuite.java index fd2f4a0e17c9..45f59214ca8c 100644 --- a/hedera-node/test-clients/src/yahcli/java/com/hedera/services/yahcli/suites/InfoSuite.java +++ b/hedera-node/test-clients/src/yahcli/java/com/hedera/services/yahcli/suites/InfoSuite.java @@ -24,8 +24,10 @@ import java.util.Arrays; import java.util.List; import java.util.Map; +import java.util.stream.Stream; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; +import org.junit.jupiter.api.DynamicTest; public class InfoSuite extends HapiSuite { private static final Logger log = LogManager.getLogger(InfoSuite.class); @@ -43,13 +45,13 @@ private List rationalized(final String[] accounts) { } @Override - public List getSpecsInSuite() { - List specToRun = new ArrayList<>(); + public List> getSpecsInSuite() { + List> specToRun = new ArrayList<>(); accounts.forEach(s -> specToRun.add(getInfo(s))); return specToRun; } - private HapiSpec getInfo(String accountID) { + final Stream getInfo(String accountID) { return HapiSpec.customHapiSpec("getInfo") .withProperties(specConfig) .given() diff --git a/hedera-node/test-clients/src/yahcli/java/com/hedera/services/yahcli/suites/PayerFundingSuite.java b/hedera-node/test-clients/src/yahcli/java/com/hedera/services/yahcli/suites/PayerFundingSuite.java index 7d586dff0ee0..33b8bb2edb59 100644 --- a/hedera-node/test-clients/src/yahcli/java/com/hedera/services/yahcli/suites/PayerFundingSuite.java +++ b/hedera-node/test-clients/src/yahcli/java/com/hedera/services/yahcli/suites/PayerFundingSuite.java @@ -26,8 +26,10 @@ import com.hedera.services.yahcli.commands.validation.ValidationCommand; import java.util.List; import java.util.Map; +import java.util.stream.Stream; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; +import org.junit.jupiter.api.DynamicTest; public class PayerFundingSuite extends HapiSuite { private static final Logger log = LogManager.getLogger(PayerFundingSuite.class); @@ -41,13 +43,11 @@ public PayerFundingSuite(long guaranteedBalance, Map specConfig) } @Override - public List getSpecsInSuite() { - return List.of(new HapiSpec[] { - fundPayer(), - }); + public List> getSpecsInSuite() { + return List.of(fundPayer()); } - private HapiSpec fundPayer() { + final Stream fundPayer() { return HapiSpec.customHapiSpec("FundPayer") .withProperties(specConfig) .given(UtilVerbs.withOpContext((spec, opLog) -> { diff --git a/hedera-node/test-clients/src/yahcli/java/com/hedera/services/yahcli/suites/RekeySuite.java b/hedera-node/test-clients/src/yahcli/java/com/hedera/services/yahcli/suites/RekeySuite.java index c7cf12e5186e..b75c82f7e440 100644 --- a/hedera-node/test-clients/src/yahcli/java/com/hedera/services/yahcli/suites/RekeySuite.java +++ b/hedera-node/test-clients/src/yahcli/java/com/hedera/services/yahcli/suites/RekeySuite.java @@ -27,8 +27,10 @@ import java.io.File; import java.util.List; import java.util.Map; +import java.util.stream.Stream; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; +import org.junit.jupiter.api.DynamicTest; public class RekeySuite extends HapiSuite { private static final Logger log = LogManager.getLogger(RekeySuite.class); @@ -49,11 +51,11 @@ public RekeySuite( } @Override - public List getSpecsInSuite() { + public List> getSpecsInSuite() { return List.of(rekey()); } - private HapiSpec rekey() { + final Stream rekey() { final var replKey = "replKey"; final var newKeyLoc = replTarget.endsWith(".pem") ? replTarget : replTarget.replace(".pem", ".words"); final var newKeyPass = TxnUtils.randomAlphaNumeric(12); diff --git a/hedera-node/test-clients/src/yahcli/java/com/hedera/services/yahcli/suites/ScheduleSuite.java b/hedera-node/test-clients/src/yahcli/java/com/hedera/services/yahcli/suites/ScheduleSuite.java index 4a551d7d399e..d4075833b040 100644 --- a/hedera-node/test-clients/src/yahcli/java/com/hedera/services/yahcli/suites/ScheduleSuite.java +++ b/hedera-node/test-clients/src/yahcli/java/com/hedera/services/yahcli/suites/ScheduleSuite.java @@ -21,8 +21,10 @@ import com.hedera.services.bdd.suites.HapiSuite; import java.util.List; import java.util.Map; +import java.util.stream.Stream; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; +import org.junit.jupiter.api.DynamicTest; public class ScheduleSuite extends HapiSuite { private static final Logger log = LogManager.getLogger(ScheduleSuite.class); @@ -36,11 +38,11 @@ public ScheduleSuite(final Map specConfig, final String schedule } @Override - public List getSpecsInSuite() { + public List> getSpecsInSuite() { return List.of(doSchedule()); } - private HapiSpec doSchedule() { + final Stream doSchedule() { var schedule = new HapiScheduleSign(HapiSuite.DEFAULT_SHARD_REALM + scheduleId); return HapiSpec.customHapiSpec("DoSchedule") .withProperties(specConfig) diff --git a/hedera-node/test-clients/src/yahcli/java/com/hedera/services/yahcli/suites/SchedulesValidationSuite.java b/hedera-node/test-clients/src/yahcli/java/com/hedera/services/yahcli/suites/SchedulesValidationSuite.java index 76bbdb662aad..675afae7a5b8 100644 --- a/hedera-node/test-clients/src/yahcli/java/com/hedera/services/yahcli/suites/SchedulesValidationSuite.java +++ b/hedera-node/test-clients/src/yahcli/java/com/hedera/services/yahcli/suites/SchedulesValidationSuite.java @@ -31,8 +31,10 @@ import java.util.List; import java.util.Map; import java.util.concurrent.atomic.AtomicLong; +import java.util.stream.Stream; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; +import org.junit.jupiter.api.DynamicTest; public class SchedulesValidationSuite extends HapiSuite { private static final Logger log = LogManager.getLogger(SchedulesValidationSuite.class); @@ -44,13 +46,11 @@ public SchedulesValidationSuite(Map specConfig) { } @Override - public List getSpecsInSuite() { - return List.of(new HapiSpec[] { - validateScheduling(), - }); + public List> getSpecsInSuite() { + return List.of(validateScheduling()); } - private HapiSpec validateScheduling() { + final Stream validateScheduling() { String inSpecSchedule = "forImmediateExecution"; AtomicLong seqNo = new AtomicLong(); diff --git a/hedera-node/test-clients/src/yahcli/java/com/hedera/services/yahcli/suites/SendSuite.java b/hedera-node/test-clients/src/yahcli/java/com/hedera/services/yahcli/suites/SendSuite.java index 39241719989c..596913a60337 100644 --- a/hedera-node/test-clients/src/yahcli/java/com/hedera/services/yahcli/suites/SendSuite.java +++ b/hedera-node/test-clients/src/yahcli/java/com/hedera/services/yahcli/suites/SendSuite.java @@ -25,8 +25,10 @@ import edu.umd.cs.findbugs.annotations.Nullable; import java.util.List; import java.util.Map; +import java.util.stream.Stream; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; +import org.junit.jupiter.api.DynamicTest; public class SendSuite extends HapiSuite { private static final Logger log = LogManager.getLogger(SendSuite.class); @@ -57,11 +59,11 @@ public SendSuite( } @Override - public List getSpecsInSuite() { + public List> getSpecsInSuite() { return List.of(doSend()); } - private HapiSpec doSend() { + final Stream doSend() { var transfer = denomination == null ? (HapiTxnOp) TxnVerbs.cryptoTransfer( HapiCryptoTransfer.tinyBarsFromTo(HapiSuite.DEFAULT_PAYER, beneficiary, unitsToSend)) diff --git a/hedera-node/test-clients/src/yahcli/java/com/hedera/services/yahcli/suites/SpecialFileHashSuite.java b/hedera-node/test-clients/src/yahcli/java/com/hedera/services/yahcli/suites/SpecialFileHashSuite.java index 65285b197a04..fd90afe59cef 100644 --- a/hedera-node/test-clients/src/yahcli/java/com/hedera/services/yahcli/suites/SpecialFileHashSuite.java +++ b/hedera-node/test-clients/src/yahcli/java/com/hedera/services/yahcli/suites/SpecialFileHashSuite.java @@ -25,8 +25,10 @@ import com.hedera.services.bdd.suites.HapiSuite; import java.util.List; import java.util.Map; +import java.util.stream.Stream; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; +import org.junit.jupiter.api.DynamicTest; public class SpecialFileHashSuite extends HapiSuite { private static final Logger log = LogManager.getLogger(SpecialFileHashSuite.class); @@ -40,11 +42,11 @@ public SpecialFileHashSuite(Map specConfig, String specialFile) } @Override - public List getSpecsInSuite() { + public List> getSpecsInSuite() { return List.of(getSpecialFileHash()); } - private HapiSpec getSpecialFileHash() { + final Stream getSpecialFileHash() { long target = Utils.rationalized(specialFile); return HapiSpec.customHapiSpec("GetSpecialFileHash") diff --git a/hedera-node/test-clients/src/yahcli/java/com/hedera/services/yahcli/suites/StakeSetupSuite.java b/hedera-node/test-clients/src/yahcli/java/com/hedera/services/yahcli/suites/StakeSetupSuite.java index 23742fe89d31..98af523f3cb7 100644 --- a/hedera-node/test-clients/src/yahcli/java/com/hedera/services/yahcli/suites/StakeSetupSuite.java +++ b/hedera-node/test-clients/src/yahcli/java/com/hedera/services/yahcli/suites/StakeSetupSuite.java @@ -30,8 +30,10 @@ import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.stream.Stream; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; +import org.junit.jupiter.api.DynamicTest; public class StakeSetupSuite extends HapiSuite { private static final Logger log = LogManager.getLogger(StakeSetupSuite.class); @@ -60,11 +62,11 @@ public StakeSetupSuite( } @Override - public List getSpecsInSuite() { + public List> getSpecsInSuite() { return List.of(startStakingAndExportCreatedStakers()); } - private HapiSpec startStakingAndExportCreatedStakers() { + final Stream startStakingAndExportCreatedStakers() { return HapiSpec.customHapiSpec("StartStakingAndExportCreatedStakers") .withProperties(specConfig) .given( diff --git a/hedera-node/test-clients/src/yahcli/java/com/hedera/services/yahcli/suites/StakeSuite.java b/hedera-node/test-clients/src/yahcli/java/com/hedera/services/yahcli/suites/StakeSuite.java index b4cecbdb1288..480c1166e61e 100644 --- a/hedera-node/test-clients/src/yahcli/java/com/hedera/services/yahcli/suites/StakeSuite.java +++ b/hedera-node/test-clients/src/yahcli/java/com/hedera/services/yahcli/suites/StakeSuite.java @@ -28,8 +28,10 @@ import edu.umd.cs.findbugs.annotations.Nullable; import java.util.List; import java.util.Map; +import java.util.stream.Stream; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; +import org.junit.jupiter.api.DynamicTest; public class StakeSuite extends HapiSuite { private static final Logger log = LogManager.getLogger(StakeSuite.class); @@ -64,7 +66,7 @@ public StakeSuite( } @Override - public List getSpecsInSuite() { + public List> getSpecsInSuite() { return List.of(doStake()); } @@ -85,7 +87,7 @@ private long numberOf(final String id) { return Long.parseLong(id.substring(id.lastIndexOf(".") + 1)); } - private HapiSpec doStake() { + final Stream doStake() { final var toUpdate = stakingAccount == null ? HapiSuite.DEFAULT_PAYER : stakingAccount; return HapiSpec.customHapiSpec("DoStake") .withProperties(specConfig) diff --git a/hedera-node/test-clients/src/yahcli/java/com/hedera/services/yahcli/suites/SysFileDownloadSuite.java b/hedera-node/test-clients/src/yahcli/java/com/hedera/services/yahcli/suites/SysFileDownloadSuite.java index 921e2ebdd1d7..a36cc624a223 100644 --- a/hedera-node/test-clients/src/yahcli/java/com/hedera/services/yahcli/suites/SysFileDownloadSuite.java +++ b/hedera-node/test-clients/src/yahcli/java/com/hedera/services/yahcli/suites/SysFileDownloadSuite.java @@ -29,8 +29,10 @@ import java.util.Arrays; import java.util.List; import java.util.Map; +import java.util.stream.Stream; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; +import org.junit.jupiter.api.DynamicTest; public class SysFileDownloadSuite extends HapiSuite { private static final Logger log = LogManager.getLogger(SysFileDownloadSuite.class); @@ -46,13 +48,11 @@ public SysFileDownloadSuite(String destDir, Map specConfig, Stri } @Override - public List getSpecsInSuite() { - return List.of(new HapiSpec[] { - downloadSysFiles(), - }); + public List> getSpecsInSuite() { + return List.of(downloadSysFiles()); } - private HapiSpec downloadSysFiles() { + final Stream downloadSysFiles() { long[] targets = Utils.rationalized(sysFilesToDownload); return HapiSpec.customHapiSpec("downloadSysFiles") diff --git a/hedera-node/test-clients/src/yahcli/java/com/hedera/services/yahcli/suites/SysFileUploadSuite.java b/hedera-node/test-clients/src/yahcli/java/com/hedera/services/yahcli/suites/SysFileUploadSuite.java index 36d3c30c184d..9b500191c989 100644 --- a/hedera-node/test-clients/src/yahcli/java/com/hedera/services/yahcli/suites/SysFileUploadSuite.java +++ b/hedera-node/test-clients/src/yahcli/java/com/hedera/services/yahcli/suites/SysFileUploadSuite.java @@ -39,8 +39,10 @@ import java.util.Map; import java.util.OptionalLong; import java.util.concurrent.atomic.AtomicInteger; +import java.util.stream.Stream; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; +import org.junit.jupiter.api.DynamicTest; public class SysFileUploadSuite extends HapiSuite { private static final Logger log = LogManager.getLogger(SysFileUploadSuite.class); @@ -85,12 +87,12 @@ protected Logger getResultsLogger() { } @Override - public List getSpecsInSuite() { + public List> getSpecsInSuite() { uploadData = appropriateContents(sysFileId); return isDryRun ? Collections.emptyList() : List.of(uploadSysFiles()); } - private HapiSpec uploadSysFiles() { + final Stream uploadSysFiles() { final var name = String.format("UploadSystemFile-%s", sysFileId); final var fileId = String.format("0.0.%d", sysFileId); final var isSpecial = isSpecialFile(sysFileId); diff --git a/hedera-node/test-clients/src/yahcli/java/com/hedera/services/yahcli/suites/TokenValidationSuite.java b/hedera-node/test-clients/src/yahcli/java/com/hedera/services/yahcli/suites/TokenValidationSuite.java index e063a2deeb1c..fb32258a7a1d 100644 --- a/hedera-node/test-clients/src/yahcli/java/com/hedera/services/yahcli/suites/TokenValidationSuite.java +++ b/hedera-node/test-clients/src/yahcli/java/com/hedera/services/yahcli/suites/TokenValidationSuite.java @@ -29,8 +29,10 @@ import java.util.List; import java.util.Map; import java.util.concurrent.atomic.AtomicLong; +import java.util.stream.Stream; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; +import org.junit.jupiter.api.DynamicTest; public class TokenValidationSuite extends HapiSuite { private static final Logger log = LogManager.getLogger(TokenValidationSuite.class); @@ -42,13 +44,11 @@ public TokenValidationSuite(Map specConfig) { } @Override - public List getSpecsInSuite() { - return List.of(new HapiSpec[] { - validateTokens(), - }); + public List> getSpecsInSuite() { + return List.of(validateTokens()); } - private HapiSpec validateTokens() { + final Stream validateTokens() { AtomicLong initialTreasuryBalance = new AtomicLong(); return HapiSpec.customHapiSpec("validateTokens") .withProperties(specConfig) diff --git a/hedera-node/test-clients/src/yahcli/java/com/hedera/services/yahcli/suites/UpdateSuite.java b/hedera-node/test-clients/src/yahcli/java/com/hedera/services/yahcli/suites/UpdateSuite.java index 4e670b4a7b4c..fec168aa0564 100644 --- a/hedera-node/test-clients/src/yahcli/java/com/hedera/services/yahcli/suites/UpdateSuite.java +++ b/hedera-node/test-clients/src/yahcli/java/com/hedera/services/yahcli/suites/UpdateSuite.java @@ -25,8 +25,10 @@ import com.hederahashgraph.api.proto.java.*; import java.util.List; import java.util.Map; +import java.util.stream.Stream; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; +import org.junit.jupiter.api.DynamicTest; public class UpdateSuite extends HapiSuite { private static final Logger log = LogManager.getLogger(UpdateSuite.class); @@ -51,11 +53,11 @@ public UpdateSuite( } @Override - public List getSpecsInSuite() { + public List> getSpecsInSuite() { return List.of(doUpdate()); } - private HapiSpec doUpdate() { + final Stream doUpdate() { Key newList = Key.newBuilder() .setKeyList(KeyList.newBuilder().addAllKeys(keys)) .build(); diff --git a/hedera-node/test-clients/src/yahcli/java/com/hedera/services/yahcli/suites/UpgradeHelperSuite.java b/hedera-node/test-clients/src/yahcli/java/com/hedera/services/yahcli/suites/UpgradeHelperSuite.java index 3a2be3f31ec1..f80d0d5d6f35 100644 --- a/hedera-node/test-clients/src/yahcli/java/com/hedera/services/yahcli/suites/UpgradeHelperSuite.java +++ b/hedera-node/test-clients/src/yahcli/java/com/hedera/services/yahcli/suites/UpgradeHelperSuite.java @@ -24,8 +24,10 @@ import java.time.Instant; import java.util.List; import java.util.Map; +import java.util.stream.Stream; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; +import org.junit.jupiter.api.DynamicTest; public class UpgradeHelperSuite extends HapiSuite { private static final Logger log = LogManager.getLogger(UpgradeHelperSuite.class); @@ -64,11 +66,11 @@ public UpgradeHelperSuite( } @Override - public List getSpecsInSuite() { - return List.of(new HapiSpec[] {doStagingAction()}); + public List> getSpecsInSuite() { + return List.of(doStagingAction()); } - private HapiSpec doStagingAction() { + final Stream doStagingAction() { final HapiSpecOperation op; if (startTime == null) { diff --git a/platform-sdk/build.gradle.kts b/platform-sdk/build.gradle.kts index e59de6e7a9ec..c88872a30022 100644 --- a/platform-sdk/build.gradle.kts +++ b/platform-sdk/build.gradle.kts @@ -14,7 +14,7 @@ * limitations under the License. */ -plugins { id("com.hedera.hashgraph.java") } +plugins { id("com.hedera.gradle.java") } val sdkDir = layout.projectDirectory.dir("sdk") diff --git a/platform-sdk/docs/core/wiring-diagram.svg b/platform-sdk/docs/core/wiring-diagram.svg index fa2173a1f828..8c42889a4f9a 100644 --- a/platform-sdk/docs/core/wiring-diagram.svg +++ b/platform-sdk/docs/core/wiring-diagram.svg @@ -1 +1 @@ -
    Transaction Handling
    State Verification
    State Signature Collection
    State File Management
    Preconsensus Event Stream
    PCES Replay
    Heartbeat
    Gossip
    Event Validation
    Event Hashing
    Event Creation
    Consensus
    Consensus Engine
    â”
    🌀
    🚽
    EventCreationManager
    â”â¤ï¸ðŸŒ€ðŸš¦
    transactionPool
    🖋ï¸
    ðŸŽ
    postHashCollector
    EventDeduplicator
    â”🌀
    InOrderLinker
    â”🌀📬
    gossip
    â”
    shadowgraph
    🌀
    â¤ï¸
    Mystery Input
    Orphan Buffer
    â”🌀
    ✅
    PcesWriter
    ✅â”🌀📀🚽
    RunningEventHashOverride
    â”
    💾
    📀
    📦
    latestCompleteStateNexus
    🌀
    💢
    ISS Detector
    â”
    💀
    💥
    🖋ï¸
    StatusStateMachine
    â¤ï¸ðŸ’€ðŸ’¾
    Consensus Round Handler
    💨🔮
    latestImmutableStateNexus
    notifier
    â”💢💥📦🚦
    â”
    💨
    📬
    🔮
    🔰
    🚦
    rounds
    rounds
    event window
    flush request
    state and round
    registerState
    setState
    self event
    get transactions
    GossipEvent
    unordered events
    PlatformStatusAction
    IssNotification
    EventImpl
    non-deduplicated events
    mystery data
    GossipEvent
    unsequenced event
    GossipEvent
    GossipEvent
    events to gossip
    preconsensus signatures
    GossipEvent
    events to write
    durable event info
    rounds
    hash override
    future hash
    non-validated events
    PlatformStatusAction
    minimum identifier to store
    state written notification
    stateAndRound
    consensus events
    state
    state
    state to sign
    states
    complete state
    complete state notification
    setCurrentStatus
    PlatformStatus
    futures
    GossipEvent
    unhashed event
    get events
    heartbeat
    unhashed event
    done streaming pces
    non-validated events
    signature transactions
    \ No newline at end of file +
    Transaction Handling
    State Verification
    State Signature Collection
    State File Management
    Stale Events
    Preconsensus Event Stream
    PCES Replay
    Heartbeat
    Event Validation
    Event Hashing
    Event Creation
    Consensus
    Consensus Engine
    â”
    🌀
    🚽
    EventCreationManager
    â”â¤ï¸ðŸŒ€ðŸš¦
    TransactionPool
    â™»ï¸â”🖋ï¸
    PostHashCollector
    EventDeduplicator
    â”🌀
    â¤ï¸
    Mystery Input
    Orphan Buffer
    â”🌀
    ✅
    PlatformPublisher
    âš°ï¸â”
    PcesWriter
    ✅â”🌀📀🚽
    RunningEventHashOverride
    â”
    â™»ï¸
    âš°ï¸
    ðŸŽ
    💾
    📀
    📦
    latestCompleteStateNexus
    🌀
    💢
    ISS Detector
    â”
    💀
    💥
    🖋ï¸
    StatusStateMachine
    â¤ï¸ðŸ’€ðŸ’¾
    Consensus Round Handler
    💨🔮
    latestImmutableStateNexus
    gossip
    â”🌀📬
    notifier
    â”💢💥📦🚦
    â”
    💨
    📬
    🔮
    🔰
    🚦
    consensus events
    rounds
    rounds
    event window
    flush request
    unhashed state and round
    registerState
    setState
    future hash
    self events
    get transactions
    GossipEvent
    GossipEvent
    unordered events
    PlatformStatusAction
    IssNotification
    non-deduplicated events
    mystery data
    GossipEvent
    unsequenced event
    GossipEvent
    GossipEvent
    events to gossip
    preconsensus signatures
    GossipEvent
    events to write
    durable event info
    non-validated events
    rounds
    hash override
    self events
    stale events
    publishStaleEvent
    non-validated events
    handleStateAndRound
    consensus events
    hashed states
    state
    state to sign
    states
    complete state
    complete state notification
    PlatformStatusAction
    minimum identifier to store
    state written notification
    setCurrentStatus
    PlatformStatus
    submit transaction
    futures
    unhashed event
    heartbeat
    unhashed event
    done streaming pces
    submit transaction
    \ No newline at end of file diff --git a/platform-sdk/platform-apps/demos/CryptocurrencyDemo/build.gradle.kts b/platform-sdk/platform-apps/demos/CryptocurrencyDemo/build.gradle.kts index cfc69c8c3490..96c6c136e38b 100644 --- a/platform-sdk/platform-apps/demos/CryptocurrencyDemo/build.gradle.kts +++ b/platform-sdk/platform-apps/demos/CryptocurrencyDemo/build.gradle.kts @@ -14,6 +14,6 @@ * limitations under the License. */ -plugins { id("com.hedera.hashgraph.application") } +plugins { id("com.hedera.gradle.application") } application.mainClass.set("com.swirlds.demo.crypto.CryptocurrencyDemoMain") diff --git a/platform-sdk/platform-apps/demos/HelloSwirldDemo/build.gradle.kts b/platform-sdk/platform-apps/demos/HelloSwirldDemo/build.gradle.kts index 44597dc40985..076aa930d40c 100644 --- a/platform-sdk/platform-apps/demos/HelloSwirldDemo/build.gradle.kts +++ b/platform-sdk/platform-apps/demos/HelloSwirldDemo/build.gradle.kts @@ -14,6 +14,6 @@ * limitations under the License. */ -plugins { id("com.hedera.hashgraph.application") } +plugins { id("com.hedera.gradle.application") } application.mainClass.set("com.swirlds.demo.hello.HelloSwirldDemoMain") diff --git a/platform-sdk/platform-apps/demos/HelloSwirldDemo/src/main/java/com/swirlds/demo/hello/HelloSwirldDemoMain.java b/platform-sdk/platform-apps/demos/HelloSwirldDemo/src/main/java/com/swirlds/demo/hello/HelloSwirldDemoMain.java index 6ffe753938f0..23b73683b9c5 100644 --- a/platform-sdk/platform-apps/demos/HelloSwirldDemo/src/main/java/com/swirlds/demo/hello/HelloSwirldDemoMain.java +++ b/platform-sdk/platform-apps/demos/HelloSwirldDemo/src/main/java/com/swirlds/demo/hello/HelloSwirldDemoMain.java @@ -113,7 +113,8 @@ public SwirldState newState() { private void platformStatusChange(final PlatformStatusChangeNotification notification) { final PlatformStatus newStatus = notification.getNewStatus(); if (PlatformStatus.ACTIVE.equals(newStatus)) { - final String myName = platform.getSelfAddress().getSelfName(); + final String myName = + platform.getAddressBook().getAddress(platform.getSelfId()).getSelfName(); console.out.println("Hello Swirld from " + myName); diff --git a/platform-sdk/platform-apps/demos/StatsDemo/build.gradle.kts b/platform-sdk/platform-apps/demos/StatsDemo/build.gradle.kts index e16524aab79f..2a6bd33eb2c3 100644 --- a/platform-sdk/platform-apps/demos/StatsDemo/build.gradle.kts +++ b/platform-sdk/platform-apps/demos/StatsDemo/build.gradle.kts @@ -14,6 +14,6 @@ * limitations under the License. */ -plugins { id("com.hedera.hashgraph.application") } +plugins { id("com.hedera.gradle.application") } application.mainClass.set("com.swirlds.demo.stats.StatsDemoMain") diff --git a/platform-sdk/platform-apps/tests/AddressBookTestingTool/build.gradle.kts b/platform-sdk/platform-apps/tests/AddressBookTestingTool/build.gradle.kts index 4a8b05684151..6bb16a68e867 100644 --- a/platform-sdk/platform-apps/tests/AddressBookTestingTool/build.gradle.kts +++ b/platform-sdk/platform-apps/tests/AddressBookTestingTool/build.gradle.kts @@ -14,6 +14,6 @@ * limitations under the License. */ -plugins { id("com.hedera.hashgraph.application") } +plugins { id("com.hedera.gradle.application") } application.mainClass.set("com.swirlds.demo.addressbook.AddressBookTestingToolMain") diff --git a/platform-sdk/platform-apps/tests/ConsistencyTestingTool/build.gradle.kts b/platform-sdk/platform-apps/tests/ConsistencyTestingTool/build.gradle.kts index f875d92aa051..4efdd4a8ab45 100644 --- a/platform-sdk/platform-apps/tests/ConsistencyTestingTool/build.gradle.kts +++ b/platform-sdk/platform-apps/tests/ConsistencyTestingTool/build.gradle.kts @@ -14,7 +14,7 @@ * limitations under the License. */ -plugins { id("com.hedera.hashgraph.application") } +plugins { id("com.hedera.gradle.application") } application.mainClass.set("com.swirlds.demo.consistency.ConsistencyTestingToolMain") diff --git a/platform-sdk/platform-apps/tests/ISSTestingTool/build.gradle.kts b/platform-sdk/platform-apps/tests/ISSTestingTool/build.gradle.kts index 2c793e2df9c8..0ccf911efacb 100644 --- a/platform-sdk/platform-apps/tests/ISSTestingTool/build.gradle.kts +++ b/platform-sdk/platform-apps/tests/ISSTestingTool/build.gradle.kts @@ -14,7 +14,7 @@ * limitations under the License. */ -plugins { id("com.hedera.hashgraph.application") } +plugins { id("com.hedera.gradle.application") } application.mainClass.set("com.swirlds.demo.iss.ISSTestingToolMain") diff --git a/platform-sdk/platform-apps/tests/MigrationTestingTool/build.gradle.kts b/platform-sdk/platform-apps/tests/MigrationTestingTool/build.gradle.kts index fd123d6d3aec..faf0295e6b51 100644 --- a/platform-sdk/platform-apps/tests/MigrationTestingTool/build.gradle.kts +++ b/platform-sdk/platform-apps/tests/MigrationTestingTool/build.gradle.kts @@ -14,7 +14,7 @@ * limitations under the License. */ -plugins { id("com.hedera.hashgraph.application") } +plugins { id("com.hedera.gradle.application") } application.mainClass.set("com.swirlds.demo.migration.MigrationTestingToolMain") diff --git a/platform-sdk/platform-apps/tests/MigrationTestingTool/src/main/java/com/swirlds/demo/migration/MigrationTestingToolMain.java b/platform-sdk/platform-apps/tests/MigrationTestingTool/src/main/java/com/swirlds/demo/migration/MigrationTestingToolMain.java index 3226566220e6..4cf640c7950e 100644 --- a/platform-sdk/platform-apps/tests/MigrationTestingTool/src/main/java/com/swirlds/demo/migration/MigrationTestingToolMain.java +++ b/platform-sdk/platform-apps/tests/MigrationTestingTool/src/main/java/com/swirlds/demo/migration/MigrationTestingToolMain.java @@ -89,7 +89,8 @@ public void run() { maximumTransactionsPerNode, seed); - final boolean isZeroWeight = platform.getSelfAddress().isZeroWeight(); + final boolean isZeroWeight = + platform.getAddressBook().getAddress(platform.getSelfId()).isZeroWeight(); if (!isZeroWeight) { while (transactionsCreated < maximumTransactionsPerNode) { try { diff --git a/platform-sdk/platform-apps/tests/PlatformTestingTool/build.gradle.kts b/platform-sdk/platform-apps/tests/PlatformTestingTool/build.gradle.kts index 4f4ab74b52cd..eb484be99d97 100644 --- a/platform-sdk/platform-apps/tests/PlatformTestingTool/build.gradle.kts +++ b/platform-sdk/platform-apps/tests/PlatformTestingTool/build.gradle.kts @@ -17,7 +17,7 @@ import com.google.protobuf.gradle.ProtobufExtract plugins { - id("com.hedera.hashgraph.application") + id("com.hedera.gradle.application") id("com.google.protobuf") } diff --git a/platform-sdk/platform-apps/tests/PlatformTestingTool/src/main/java/com/swirlds/demo/platform/PlatformTestingToolMain.java b/platform-sdk/platform-apps/tests/PlatformTestingTool/src/main/java/com/swirlds/demo/platform/PlatformTestingToolMain.java index a585e2b1c4e4..97da6cb39740 100644 --- a/platform-sdk/platform-apps/tests/PlatformTestingTool/src/main/java/com/swirlds/demo/platform/PlatformTestingToolMain.java +++ b/platform-sdk/platform-apps/tests/PlatformTestingTool/src/main/java/com/swirlds/demo/platform/PlatformTestingToolMain.java @@ -78,7 +78,6 @@ import com.swirlds.platform.listeners.PlatformStatusChangeListener; import com.swirlds.platform.listeners.PlatformStatusChangeNotification; import com.swirlds.platform.listeners.ReconnectCompleteListener; -import com.swirlds.platform.listeners.StateLoadedFromDiskCompleteListener; import com.swirlds.platform.listeners.StateWriteToDiskCompleteListener; import com.swirlds.platform.system.BasicSoftwareVersion; import com.swirlds.platform.system.Platform; @@ -546,7 +545,8 @@ public void init(Platform platform, NodeId id) { state.initControlStructures(this::handleMessageQuorum); // FUTURE WORK implement mirrorNode - final String myName = platform.getSelfAddress().getSelfName(); + final String myName = + platform.getAddressBook().getAddress(platform.getSelfId()).getSelfName(); // Parameters[0]: JSON file for test config String jsonFileName = null; @@ -671,9 +671,6 @@ public void init(Platform platform, NodeId id) { initAppStat(); - // register RestartCompleteListener - registerStateLoadedFromDiskCompleteListener(currentConfig); - registerAccountBalanceExportListener(); if (waitForSaveStateDuringFreeze) { @@ -975,21 +972,6 @@ private void rebuildExpirationQueue(Platform platform) { } } - /** - * once restart is completed, rebuild ExpectedMap based on actual MerkleMaps - */ - private void registerStateLoadedFromDiskCompleteListener(final SuperConfig currentConfig) { - // register RestartCompleteListener - platform.getNotificationEngine().register(StateLoadedFromDiskCompleteListener.class, (notification) -> { - logger.info( - LOGM_DEMO_INFO, - "Notification Received: StateLoadedFromDisk Finished. sequence: {}", - notification.getSequence()); - ExpectedMapUtils.buildExpectedMapAfterStateLoad(platform, currentConfig); - rebuildExpirationQueue(platform); - }); - } - /** * Iterates on the {@link com.swirlds.virtualmap.VirtualMap} instances from the state * of the given {@code platform} to compute the next id to be used by account and smart contract entities. diff --git a/platform-sdk/platform-apps/tests/PlatformTestingTool/src/main/java/com/swirlds/demo/virtualmerkle/VirtualMerkleLeafHasher.java b/platform-sdk/platform-apps/tests/PlatformTestingTool/src/main/java/com/swirlds/demo/virtualmerkle/VirtualMerkleLeafHasher.java index 45ced653fda2..c8da1b48ef77 100644 --- a/platform-sdk/platform-apps/tests/PlatformTestingTool/src/main/java/com/swirlds/demo/virtualmerkle/VirtualMerkleLeafHasher.java +++ b/platform-sdk/platform-apps/tests/PlatformTestingTool/src/main/java/com/swirlds/demo/virtualmerkle/VirtualMerkleLeafHasher.java @@ -18,7 +18,6 @@ import static com.swirlds.common.io.utility.FileUtils.getAbsolutePath; import static com.swirlds.common.merkle.iterators.MerkleIterationOrder.BREADTH_FIRST; -import static com.swirlds.common.utility.CommonUtils.hex; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.node.ObjectNode; @@ -109,7 +108,7 @@ public Hash computeNextHash(final Hash prevHash, final VirtualLeafNode lea if (prevHash != null) { // add Previous Hash - out.write(prevHash.getValue()); + prevHash.getBytes().writeTo(out); } // add leaf key leaf.getKey().serialize(out); @@ -211,19 +210,19 @@ public static void main(final String[] args) throws IOException { // add content to json if (accountsHash != null) { - rootNode.put("accounts", hex(accountsHash.getValue())); + rootNode.put("accounts", accountsHash.toHex()); } else { rootNode.put("accounts", "empty"); } if (scHash != null) { - rootNode.put("sc", hex(scHash.getValue())); + rootNode.put("sc", scHash.toHex()); } else { rootNode.put("sc", "empty"); } if (byteCodeHash != null) { - rootNode.put("byteCode", hex(byteCodeHash.getValue())); + rootNode.put("byteCode", byteCodeHash.toHex()); } else { rootNode.put("byteCode", "empty"); } diff --git a/platform-sdk/platform-apps/tests/PlatformTestingTool/src/test/java/com/swirlds/demo/merkle/map/SaveExpectedMapHandlerTest.java b/platform-sdk/platform-apps/tests/PlatformTestingTool/src/test/java/com/swirlds/demo/merkle/map/SaveExpectedMapHandlerTest.java index 3c537292fbce..488bdd0dba7c 100644 --- a/platform-sdk/platform-apps/tests/PlatformTestingTool/src/test/java/com/swirlds/demo/merkle/map/SaveExpectedMapHandlerTest.java +++ b/platform-sdk/platform-apps/tests/PlatformTestingTool/src/test/java/com/swirlds/demo/merkle/map/SaveExpectedMapHandlerTest.java @@ -30,6 +30,7 @@ import com.swirlds.merkle.test.fixtures.map.lifecycle.TransactionState; import com.swirlds.merkle.test.fixtures.map.pta.MapKey; import java.io.File; +import java.io.IOException; import java.time.Instant; import java.util.HashMap; import java.util.Map; @@ -113,7 +114,7 @@ private boolean areEqual(Map actualMap, Map contextMap; @@ -72,7 +74,7 @@ public void clear() { */ @NonNull public static GlobalContext getInstance() { - return INSTANCE; + return InstanceHolder.INSTANCE; } /** @@ -82,6 +84,6 @@ public static GlobalContext getInstance() { */ @NonNull public static Map getContextMap() { - return Collections.unmodifiableMap(INSTANCE.contextMap); + return Collections.unmodifiableMap(InstanceHolder.INSTANCE.contextMap); } } diff --git a/platform-sdk/swirlds-base/src/main/java/com/swirlds/base/context/internal/ThreadLocalContext.java b/platform-sdk/swirlds-base/src/main/java/com/swirlds/base/context/internal/ThreadLocalContext.java index 95f93b126913..53c8e65aedbf 100644 --- a/platform-sdk/swirlds-base/src/main/java/com/swirlds/base/context/internal/ThreadLocalContext.java +++ b/platform-sdk/swirlds-base/src/main/java/com/swirlds/base/context/internal/ThreadLocalContext.java @@ -32,7 +32,9 @@ */ public final class ThreadLocalContext implements Context { - private static final ThreadLocalContext INSTANCE = new ThreadLocalContext(); + private static final class InstanceHolder { + private static final ThreadLocalContext INSTANCE = new ThreadLocalContext(); + } private final ThreadLocal> contextThreadLocal; @@ -83,7 +85,7 @@ public void clear() { */ @NonNull public static ThreadLocalContext getInstance() { - return INSTANCE; + return InstanceHolder.INSTANCE; } /** @@ -94,7 +96,7 @@ public static ThreadLocalContext getInstance() { */ @NonNull public static Map getContextMap() { - final Map current = INSTANCE.contextThreadLocal.get(); + final Map current = InstanceHolder.INSTANCE.contextThreadLocal.get(); if (current != null) { return Collections.unmodifiableMap(current); } diff --git a/platform-sdk/swirlds-base/src/main/java/com/swirlds/base/internal/impl/BaseExecutorFactoryImpl.java b/platform-sdk/swirlds-base/src/main/java/com/swirlds/base/internal/impl/BaseExecutorFactoryImpl.java index a25b8e5564e6..53fca4dd6ae8 100644 --- a/platform-sdk/swirlds-base/src/main/java/com/swirlds/base/internal/impl/BaseExecutorFactoryImpl.java +++ b/platform-sdk/swirlds-base/src/main/java/com/swirlds/base/internal/impl/BaseExecutorFactoryImpl.java @@ -27,10 +27,9 @@ */ public class BaseExecutorFactoryImpl implements BaseExecutorFactory { - /** - * The singleton instance of this factory. - */ - private static final BaseExecutorFactory instance = new BaseExecutorFactoryImpl(); + private static final class InstanceHolder { + private static final BaseExecutorFactory INSTANCE = new BaseExecutorFactoryImpl(); + } /** * Constructs a new factory. @@ -50,6 +49,6 @@ public ScheduledExecutorService getScheduledExecutor() { */ @NonNull public static BaseExecutorFactory getInstance() { - return instance; + return InstanceHolder.INSTANCE; } } diff --git a/platform-sdk/swirlds-base/src/main/java/com/swirlds/base/internal/impl/BaseExecutorThreadFactory.java b/platform-sdk/swirlds-base/src/main/java/com/swirlds/base/internal/impl/BaseExecutorThreadFactory.java index e77e2cb51079..dd9d0adf11ab 100644 --- a/platform-sdk/swirlds-base/src/main/java/com/swirlds/base/internal/impl/BaseExecutorThreadFactory.java +++ b/platform-sdk/swirlds-base/src/main/java/com/swirlds/base/internal/impl/BaseExecutorThreadFactory.java @@ -27,11 +27,6 @@ */ class BaseExecutorThreadFactory implements ThreadFactory { - /** - * The singleton instance of this factory. - */ - private static final BaseExecutorThreadFactory instance = new BaseExecutorThreadFactory(); - /** * The number of threads created by this factory. */ @@ -40,7 +35,7 @@ class BaseExecutorThreadFactory implements ThreadFactory { /** * Constructs a new factory. */ - private BaseExecutorThreadFactory() {} + protected BaseExecutorThreadFactory() {} @Override public Thread newThread(@NonNull final Runnable runnable) { @@ -51,14 +46,4 @@ public Thread newThread(@NonNull final Runnable runnable) { thread.setDaemon(true); return thread; } - - /** - * Returns the singleton instance of this factory. - * - * @return the instance - */ - @NonNull - public static BaseExecutorThreadFactory getInstance() { - return instance; - } } diff --git a/platform-sdk/swirlds-base/src/main/java/com/swirlds/base/internal/impl/BaseScheduledExecutorService.java b/platform-sdk/swirlds-base/src/main/java/com/swirlds/base/internal/impl/BaseScheduledExecutorService.java index 47775546ff71..3e57861c3bfb 100644 --- a/platform-sdk/swirlds-base/src/main/java/com/swirlds/base/internal/impl/BaseScheduledExecutorService.java +++ b/platform-sdk/swirlds-base/src/main/java/com/swirlds/base/internal/impl/BaseScheduledExecutorService.java @@ -50,10 +50,9 @@ public class BaseScheduledExecutorService implements ScheduledExecutorService { */ public static final int CORE_POOL_SIZE = 1; - /** - * The singleton instance of this executor. - */ - private static volatile BaseScheduledExecutorService instance; + private static final class InstanceHolder { + private static final BaseScheduledExecutorService INSTANCE = new BaseScheduledExecutorService(); + } /** * The lock for creating the singleton instance. @@ -74,7 +73,7 @@ public class BaseScheduledExecutorService implements ScheduledExecutorService { * Constructs a new executor. */ private BaseScheduledExecutorService() { - final ThreadFactory threadFactory = BaseExecutorThreadFactory.getInstance(); + final ThreadFactory threadFactory = new BaseExecutorThreadFactory(); this.innerService = Executors.newScheduledThreadPool(CORE_POOL_SIZE, threadFactory); this.observers = new CopyOnWriteArrayList<>(); final Thread shutdownHook = new Thread(() -> innerService.shutdown()); @@ -82,25 +81,6 @@ private BaseScheduledExecutorService() { Runtime.getRuntime().addShutdownHook(shutdownHook); } - /** - * Returns the singleton instance of this executor. - * - * @return the instance - */ - public static BaseScheduledExecutorService getInstance() { - if (instance == null) { - instanceLock.lock(); - try { - if (instance == null) { - instance = new BaseScheduledExecutorService(); - } - } finally { - instanceLock.unlock(); - } - } - return instance; - } - /** * Adds an observer to this executor. * @@ -278,4 +258,14 @@ public void execute(Runnable command) { final Runnable wrapped = wrapOnSubmit(command); innerService.execute(wrapped); } + + /** + * Returns the singleton instance of this executor. + * + * @return the instance + */ + @NonNull + public static BaseScheduledExecutorService getInstance() { + return InstanceHolder.INSTANCE; + } } diff --git a/platform-sdk/swirlds-base/src/main/java/com/swirlds/base/time/internal/OSTime.java b/platform-sdk/swirlds-base/src/main/java/com/swirlds/base/time/internal/OSTime.java index 155236f14eb3..7cd4b61eaa2c 100644 --- a/platform-sdk/swirlds-base/src/main/java/com/swirlds/base/time/internal/OSTime.java +++ b/platform-sdk/swirlds-base/src/main/java/com/swirlds/base/time/internal/OSTime.java @@ -25,7 +25,9 @@ */ public final class OSTime implements Time { - private static final Time instance = new OSTime(); + private static final class InstanceHolder { + private static final Time INSTANCE = new OSTime(); + } private OSTime() {} @@ -34,7 +36,7 @@ private OSTime() {} */ @NonNull public static Time getInstance() { - return instance; + return InstanceHolder.INSTANCE; } /** diff --git a/platform-sdk/swirlds-benchmarks/build.gradle.kts b/platform-sdk/swirlds-benchmarks/build.gradle.kts index 4207a209c02a..4e142178e0cb 100644 --- a/platform-sdk/swirlds-benchmarks/build.gradle.kts +++ b/platform-sdk/swirlds-benchmarks/build.gradle.kts @@ -17,8 +17,8 @@ import me.champeau.jmh.JMHTask plugins { - id("com.hedera.hashgraph.sdk.conventions") - id("com.hedera.hashgraph.benchmark-conventions") + id("com.hedera.gradle.platform") + id("com.hedera.gradle.benchmark") } jmhModuleInfo { @@ -31,7 +31,7 @@ jmhModuleInfo { requires("com.swirlds.fchashmap") requires("com.swirlds.merkledb") requires("com.swirlds.virtualmap") - requires("com.swirlds.platform") + requires("com.swirlds.platform.core") requires("jmh.core") requires("org.apache.logging.log4j") requiresStatic("com.github.spotbugs.annotations") diff --git a/platform-sdk/swirlds-cli/build.gradle.kts b/platform-sdk/swirlds-cli/build.gradle.kts index e8ba36c31aa0..6daacca0a6da 100644 --- a/platform-sdk/swirlds-cli/build.gradle.kts +++ b/platform-sdk/swirlds-cli/build.gradle.kts @@ -15,8 +15,8 @@ */ plugins { - id("com.hedera.hashgraph.sdk.conventions") - id("com.hedera.hashgraph.platform-maven-publish") + id("com.hedera.gradle.platform") + id("com.hedera.gradle.platform-publish") } testModuleInfo { requires("org.junit.jupiter.api") } diff --git a/platform-sdk/swirlds-common/build.gradle.kts b/platform-sdk/swirlds-common/build.gradle.kts index d8f904940f68..0cfd36a840a5 100644 --- a/platform-sdk/swirlds-common/build.gradle.kts +++ b/platform-sdk/swirlds-common/build.gradle.kts @@ -15,9 +15,9 @@ */ plugins { - id("com.hedera.hashgraph.sdk.conventions") - id("com.hedera.hashgraph.platform-maven-publish") - id("com.hedera.hashgraph.java-test-fixtures") + id("com.hedera.gradle.platform") + id("com.hedera.gradle.platform-publish") + id("com.hedera.gradle.java-test-fixtures") } mainModuleInfo { diff --git a/platform-sdk/swirlds-common/src/main/java/com/swirlds/common/concurrent/ExecutorFactory.java b/platform-sdk/swirlds-common/src/main/java/com/swirlds/common/concurrent/ExecutorFactory.java index c5ee135ed093..09b534e67a48 100644 --- a/platform-sdk/swirlds-common/src/main/java/com/swirlds/common/concurrent/ExecutorFactory.java +++ b/platform-sdk/swirlds-common/src/main/java/com/swirlds/common/concurrent/ExecutorFactory.java @@ -16,7 +16,10 @@ package com.swirlds.common.concurrent; +import com.swirlds.common.concurrent.internal.DefaultExecutorFactory; import edu.umd.cs.findbugs.annotations.NonNull; +import edu.umd.cs.findbugs.annotations.Nullable; +import java.lang.Thread.UncaughtExceptionHandler; import java.util.concurrent.ExecutorService; import java.util.concurrent.ForkJoinPool; import java.util.concurrent.ScheduledExecutorService; @@ -62,4 +65,32 @@ public interface ExecutorFactory { @Deprecated @NonNull Thread createThread(@NonNull Runnable runnable); + + /** + * Creates a new instance of {@link ExecutorFactory}. + * + * @param groupName the thread group name + * @param onStartup the on startup runnable + * @param exceptionHandler the exception handler + * @return the new instance of {@link ExecutorFactory} + */ + @NonNull + static ExecutorFactory create( + @NonNull final String groupName, + @Nullable final Runnable onStartup, + @NonNull final UncaughtExceptionHandler exceptionHandler) { + return DefaultExecutorFactory.create(groupName, onStartup, exceptionHandler); + } + + /** + * Creates a new instance of {@link ExecutorFactory}. + * + * @param groupName the thread group name + * @param exceptionHandler the exception handler + * @return the new instance of {@link ExecutorFactory} + */ + static ExecutorFactory create( + @NonNull final String groupName, @NonNull final UncaughtExceptionHandler exceptionHandler) { + return create(groupName, null, exceptionHandler); + } } diff --git a/platform-sdk/swirlds-common/src/main/java/com/swirlds/common/context/PlatformContext.java b/platform-sdk/swirlds-common/src/main/java/com/swirlds/common/context/PlatformContext.java index 8006d0893ee2..78fb1f979651 100644 --- a/platform-sdk/swirlds-common/src/main/java/com/swirlds/common/context/PlatformContext.java +++ b/platform-sdk/swirlds-common/src/main/java/com/swirlds/common/context/PlatformContext.java @@ -17,11 +17,19 @@ package com.swirlds.common.context; import com.swirlds.base.time.Time; +import com.swirlds.common.concurrent.ExecutorFactory; +import com.swirlds.common.context.internal.DefaultPlatformContext; +import com.swirlds.common.context.internal.PlatformUncaughtExceptionHandler; import com.swirlds.common.crypto.Cryptography; +import com.swirlds.common.crypto.CryptographyHolder; import com.swirlds.common.io.filesystem.FileSystemManager; +import com.swirlds.common.io.filesystem.FileSystemManagerFactory; +import com.swirlds.common.io.utility.NoOpRecycleBin; +import com.swirlds.common.metrics.noop.NoOpMetrics; import com.swirlds.config.api.Configuration; import com.swirlds.metrics.api.Metrics; import edu.umd.cs.findbugs.annotations.NonNull; +import java.lang.Thread.UncaughtExceptionHandler; /** * Public interface of the platform context that provides access to all basic services and resources. By using the @@ -33,6 +41,52 @@ */ public interface PlatformContext { + /** + * Creates a new instance of the platform context. The instance uses a {@link NoOpMetrics} implementation for + * metrics and a {@link com.swirlds.common.io.utility.NoOpRecycleBin}. + * The instance uses the static {@link CryptographyHolder#get()} call to get the cryptography. The instance + * uses the static {@link Time#getCurrent()} call to get the time. + * + * @apiNote This method is meant for utilities and testing and not for a node's production operation + * @param configuration the configuration + * @return the platform context + * @deprecated since we need to remove the static {@link CryptographyHolder#get()} call in future. + */ + @Deprecated(forRemoval = true) + @NonNull + static PlatformContext create(@NonNull final Configuration configuration) { + final Metrics metrics = new NoOpMetrics(); + final Cryptography cryptography = CryptographyHolder.get(); + final FileSystemManager fileSystemManager = + FileSystemManagerFactory.getInstance().createFileSystemManager(configuration, new NoOpRecycleBin()); + + return create(configuration, metrics, cryptography, fileSystemManager); + } + + /** + * Creates a new instance of the platform context. + *

    + * The instance uses the static {@link Time#getCurrent()} call to get the time. + * + * @param configuration the configuration + * @param metrics the metrics + * @param cryptography the cryptography + * @param fileSystemManager the fileSystemManager + * @return the platform context + */ + @NonNull + static PlatformContext create( + @NonNull final Configuration configuration, + @NonNull final Metrics metrics, + @NonNull final Cryptography cryptography, + @NonNull final FileSystemManager fileSystemManager) { + final Time time = Time.getCurrent(); + final UncaughtExceptionHandler handler = new PlatformUncaughtExceptionHandler(); + final ExecutorFactory executorFactory = ExecutorFactory.create("platform", null, handler); + return new DefaultPlatformContext( + configuration, metrics, cryptography, time, executorFactory, fileSystemManager); + } + /** * Returns the {@link Configuration} instance for the platform * @@ -72,4 +126,11 @@ public interface PlatformContext { */ @NonNull FileSystemManager getFileSystemManager(); + + /** + * Returns the {@link ExecutorFactory} for this node + * + * @return the {@link ExecutorFactory} for this node + */ + ExecutorFactory getExecutorFactory(); } diff --git a/platform-sdk/swirlds-common/src/main/java/com/swirlds/common/context/DefaultPlatformContext.java b/platform-sdk/swirlds-common/src/main/java/com/swirlds/common/context/internal/DefaultPlatformContext.java similarity index 77% rename from platform-sdk/swirlds-common/src/main/java/com/swirlds/common/context/DefaultPlatformContext.java rename to platform-sdk/swirlds-common/src/main/java/com/swirlds/common/context/internal/DefaultPlatformContext.java index fee1fb95b327..16df863df9fd 100644 --- a/platform-sdk/swirlds-common/src/main/java/com/swirlds/common/context/DefaultPlatformContext.java +++ b/platform-sdk/swirlds-common/src/main/java/com/swirlds/common/context/internal/DefaultPlatformContext.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2018-2024 Hedera Hashgraph, LLC + * Copyright (C) 2024 Hedera Hashgraph, LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,12 +14,13 @@ * limitations under the License. */ -package com.swirlds.common.context; +package com.swirlds.common.context.internal; import com.swirlds.base.time.Time; +import com.swirlds.common.concurrent.ExecutorFactory; +import com.swirlds.common.context.PlatformContext; import com.swirlds.common.crypto.Cryptography; import com.swirlds.common.io.filesystem.FileSystemManager; -import com.swirlds.common.io.filesystem.FileSystemManagerFactory; import com.swirlds.config.api.Configuration; import com.swirlds.metrics.api.Metrics; import edu.umd.cs.findbugs.annotations.NonNull; @@ -35,29 +36,10 @@ public final class DefaultPlatformContext implements PlatformContext { private final Metrics metrics; private final Cryptography cryptography; private final Time time; - private final FileSystemManager fileSystemManager; - /** - * Constructor. - * - * @param configuration the configuration - * @param metrics the metrics - * @param cryptography the cryptography - * @param time the time - */ - public DefaultPlatformContext( - @NonNull final Configuration configuration, - @NonNull final Metrics metrics, - @NonNull final Cryptography cryptography, - @NonNull final Time time) { + private final ExecutorFactory executorFactory; - this( - configuration, - metrics, - cryptography, - time, - FileSystemManagerFactory.getInstance().createFileSystemManager(configuration, metrics)); - } + private final FileSystemManager fileSystemManager; /** * Constructor. @@ -66,6 +48,7 @@ public DefaultPlatformContext( * @param metrics the metrics * @param cryptography the cryptography * @param time the time + * @param executorFactory the executor factory * @param fileSystemManager the fileSystemManager */ public DefaultPlatformContext( @@ -73,12 +56,13 @@ public DefaultPlatformContext( @NonNull final Metrics metrics, @NonNull final Cryptography cryptography, @NonNull final Time time, + @NonNull final ExecutorFactory executorFactory, @NonNull final FileSystemManager fileSystemManager) { - this.configuration = Objects.requireNonNull(configuration); this.metrics = Objects.requireNonNull(metrics); this.cryptography = Objects.requireNonNull(cryptography); this.time = Objects.requireNonNull(time); + this.executorFactory = Objects.requireNonNull(executorFactory); this.fileSystemManager = Objects.requireNonNull(fileSystemManager); } @@ -118,12 +102,15 @@ public Time getTime() { return time; } - /** - * {@inheritDoc} - */ @NonNull @Override public FileSystemManager getFileSystemManager() { return fileSystemManager; } + + @Override + @NonNull + public ExecutorFactory getExecutorFactory() { + return executorFactory; + } } diff --git a/platform-sdk/swirlds-common/src/main/java/com/swirlds/common/context/internal/PlatformUncaughtExceptionHandler.java b/platform-sdk/swirlds-common/src/main/java/com/swirlds/common/context/internal/PlatformUncaughtExceptionHandler.java new file mode 100644 index 000000000000..a8e564da9f28 --- /dev/null +++ b/platform-sdk/swirlds-common/src/main/java/com/swirlds/common/context/internal/PlatformUncaughtExceptionHandler.java @@ -0,0 +1,34 @@ +/* + * Copyright (C) 2024 Hedera Hashgraph, LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.swirlds.common.context.internal; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +/** + * Simple uncaught exception handler that logs the exception and rethrows it. + */ +public class PlatformUncaughtExceptionHandler implements Thread.UncaughtExceptionHandler { + + private static final Logger logger = LogManager.getLogger(PlatformUncaughtExceptionHandler.class); + + @Override + public void uncaughtException(Thread t, Throwable e) { + logger.error("Uncaught exception in thread: " + t.getName(), e); + throw new RuntimeException("Uncaught exception", e); + } +} diff --git a/platform-sdk/swirlds-common/src/main/java/com/swirlds/common/crypto/Cryptography.java b/platform-sdk/swirlds-common/src/main/java/com/swirlds/common/crypto/Cryptography.java index ba8cab1a2938..4b4e76766ddd 100644 --- a/platform-sdk/swirlds-common/src/main/java/com/swirlds/common/crypto/Cryptography.java +++ b/platform-sdk/swirlds-common/src/main/java/com/swirlds/common/crypto/Cryptography.java @@ -17,6 +17,7 @@ package com.swirlds.common.crypto; import com.swirlds.common.io.SelfSerializable; +import edu.umd.cs.findbugs.annotations.NonNull; import java.util.List; public interface Cryptography { @@ -97,6 +98,24 @@ default Hash digestSync(final SerializableHashable serializableHashable, final D */ Hash digestSync(final SerializableHashable serializableHashable, final DigestType digestType, boolean setHash); + /** + * Same as {@link #digestBytesSync(byte[], DigestType)} with DigestType set to {@link DigestType#SHA_384} + */ + default byte[] digestBytesSync(@NonNull final byte[] message) { + return digestBytesSync(message, DEFAULT_DIGEST_TYPE); + } + + /** + * Computes a cryptographic hash (message digest) for the given message. + * + * @param message the message contents to be hashed + * @param digestType the type of digest used to compute the hash + * @return the cryptographic hash for the given message contents + * @throws CryptographyException if an unrecoverable error occurs while computing the digest + */ + @NonNull + byte[] digestBytesSync(@NonNull final byte[] message, @NonNull final DigestType digestType); + /** * @return the hash for a null value. Uses SHA_384. */ diff --git a/platform-sdk/swirlds-common/src/main/java/com/swirlds/common/crypto/EmptyHashValueException.java b/platform-sdk/swirlds-common/src/main/java/com/swirlds/common/crypto/EmptyHashValueException.java deleted file mode 100644 index a7fe2866297f..000000000000 --- a/platform-sdk/swirlds-common/src/main/java/com/swirlds/common/crypto/EmptyHashValueException.java +++ /dev/null @@ -1,44 +0,0 @@ -/* - * Copyright (C) 2016-2024 Hedera Hashgraph, LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.swirlds.common.crypto; - -import com.swirlds.logging.legacy.LogMarker; - -/** - * Exception caused when provided hash value contains all zeros - */ -public class EmptyHashValueException extends CryptographyException { - - private static final String MESSAGE = - "Provided hash value contained all zeros, hashes must contain at least one " + "non-zero byte"; - - public EmptyHashValueException() { - this(MESSAGE); - } - - public EmptyHashValueException(final String message) { - super(message, LogMarker.TESTING_EXCEPTIONS); - } - - public EmptyHashValueException(final String message, final Throwable cause) { - super(message, cause, LogMarker.TESTING_EXCEPTIONS); - } - - public EmptyHashValueException(final Throwable cause) { - this(MESSAGE, cause); - } -} diff --git a/platform-sdk/swirlds-common/src/main/java/com/swirlds/common/crypto/Hash.java b/platform-sdk/swirlds-common/src/main/java/com/swirlds/common/crypto/Hash.java index 43e4acc83baf..fa93fe900bd7 100644 --- a/platform-sdk/swirlds-common/src/main/java/com/swirlds/common/crypto/Hash.java +++ b/platform-sdk/swirlds-common/src/main/java/com/swirlds/common/crypto/Hash.java @@ -26,21 +26,23 @@ import com.swirlds.common.io.streams.AugmentedDataOutputStream; import com.swirlds.common.io.streams.SerializableDataInputStream; import com.swirlds.common.io.streams.SerializableDataOutputStream; +import edu.umd.cs.findbugs.annotations.NonNull; import edu.umd.cs.findbugs.annotations.Nullable; import java.io.IOException; import java.io.Serializable; import java.nio.ByteBuffer; import java.util.Arrays; +import java.util.Objects; /** * A cryptographic hash of some data. */ public class Hash implements Comparable, SerializableWithKnownLength, Serializable { - private static final int SHORT_STRING_BYTES = 4; public static final long CLASS_ID = 0xf422da83a251741eL; public static final int CLASS_VERSION = 1; private byte[] value; + private Bytes bytes; private DigestType digestType; /** @@ -56,8 +58,8 @@ public Hash() { * @param digestType * the digest type */ - public Hash(final DigestType digestType) { - this(new byte[digestType.digestLength()], digestType, true, false); + public Hash(@NonNull final DigestType digestType) { + this(new byte[digestType.digestLength()], digestType); } /** @@ -66,7 +68,7 @@ public Hash(final DigestType digestType) { * @param value * the hash bytes */ - public Hash(final byte[] value) { + public Hash(@NonNull final byte[] value) { this(value, DigestType.SHA_384); } @@ -78,8 +80,17 @@ public Hash(final byte[] value) { * @param digestType * the digest type */ - public Hash(final byte[] value, final DigestType digestType) { - this(value, digestType, false, false); + public Hash(@NonNull final byte[] value, @NonNull final DigestType digestType) { + Objects.requireNonNull(value, "value"); + Objects.requireNonNull(digestType, "digestType"); + + if (value.length != digestType.digestLength()) { + throw new IllegalArgumentException("value: " + value.length); + } + + this.digestType = digestType; + this.value = value; + this.bytes = Bytes.wrap(this.value); } /** @@ -90,7 +101,7 @@ public Hash(final byte[] value, final DigestType digestType) { * @param digestType * the digest type of this hash */ - public Hash(final ByteBuffer byteBuffer, final DigestType digestType) { + public Hash(@NonNull final ByteBuffer byteBuffer, @NonNull final DigestType digestType) { this(digestType); byteBuffer.get(this.value); } @@ -101,50 +112,14 @@ public Hash(final ByteBuffer byteBuffer, final DigestType digestType) { * @param other * the hash to copy */ - public Hash(final Hash other) { + public Hash(@NonNull final Hash other) { if (other == null) { throw new IllegalArgumentException("other"); } this.digestType = other.digestType; this.value = Arrays.copyOf(other.value, other.value.length); - } - - protected Hash( - final byte[] value, - final DigestType digestType, - final boolean bypassSafetyCheck, - final boolean shouldCopy) { - if (value == null) { - throw new IllegalArgumentException("value"); - } - - if (digestType == null) { - throw new IllegalArgumentException("digestType"); - } - - if (value.length != digestType.digestLength()) { - throw new IllegalArgumentException("value: " + value.length); - } - - this.digestType = digestType; - - final byte[] valuePtr = (shouldCopy) ? Arrays.copyOf(value, value.length) : value; - - if (bypassSafetyCheck) { - this.value = valuePtr; - } else { - // Check for all zeros & stop when first non-zero byte has been encountered - for (byte b : value) { - if (b != 0) { - this.value = value; - return; - } - } - - // We throw an exception here because the value array contained all zero bytes - throw new EmptyHashValueException("Hash creation failed, hash is array of zeroes"); - } + this.bytes = other.bytes; } /** @@ -152,15 +127,22 @@ protected Hash( * * @return the hash value */ - public byte[] getValue() { + public @NonNull byte[] getValue() { return value; } + /** + * @return a copy of the hash value as a byte array + */ + public @NonNull byte[] copyToByteArray() { + return bytes.toByteArray(); + } + /** * @return the hash value as an immutable {@link Bytes} object */ - public Bytes getBytes() { - return Bytes.wrap(value); + public @NonNull Bytes getBytes() { + return bytes; } /** @@ -168,7 +150,7 @@ public Bytes getBytes() { * * @return a new hash */ - public Hash copy() { + public @NonNull Hash copy() { return new Hash(this); } @@ -192,7 +174,7 @@ public int getVersion() { * {@inheritDoc} */ @Override - public void serialize(final SerializableDataOutputStream out) throws IOException { + public void serialize(@NonNull final SerializableDataOutputStream out) throws IOException { requireNonNull(digestType, "digestType"); requireNonNull(value, "value"); out.writeInt(digestType.id()); @@ -208,7 +190,7 @@ public int getSerializedLength() { * {@inheritDoc} */ @Override - public void deserialize(final SerializableDataInputStream in, final int version) throws IOException { + public void deserialize(@NonNull final SerializableDataInputStream in, final int version) throws IOException { final DigestType digestType = DigestType.valueOf(in.readInt()); if (digestType == null) { @@ -221,6 +203,7 @@ public void deserialize(final SerializableDataInputStream in, final int version) if (value == null) { throw new BadIOException("Invalid hash value read from the stream"); } + this.bytes = Bytes.wrap(value); } /** @@ -262,7 +245,7 @@ public int hashCode() { * {@inheritDoc} */ @Override - public int compareTo(final Hash that) { + public int compareTo(@NonNull final Hash that) { if (this == that) { return 0; } @@ -271,7 +254,7 @@ public int compareTo(final Hash that) { throw new NullPointerException("that"); } - int ret = Integer.compare(digestType.id(), that.digestType.id()); + final int ret = Integer.compare(digestType.id(), that.digestType.id()); if (ret != 0) { return ret; @@ -284,15 +267,15 @@ public int compareTo(final Hash that) { * {@inheritDoc} */ @Override - public String toString() { - return (value == null) ? null : hex(value); + public @NonNull String toString() { + return (value == null) ? "null" : hex(value); } /** - * Create a short string representation of this hash. + * Create a hexadecimal string representation of this hash. */ - public String toShortString() { - return toShortString(SHORT_STRING_BYTES); + public @NonNull String toHex() { + return toString(); } /** @@ -301,8 +284,8 @@ public String toShortString() { * @param length * the number of characters to include in the short string */ - public String toShortString(final int length) { - return (value == null) ? null : hex(value, length); + public @NonNull String toHex(final int length) { + return (value == null) ? "null" : hex(value, length); } /** @@ -310,7 +293,7 @@ public String toShortString(final int length) { * * @return a mnemonic for this hash */ - public String toMnemonic() { + public @NonNull String toMnemonic() { return generateMnemonic(value, 4); } @@ -319,7 +302,7 @@ public String toMnemonic() { * * @return the digest type */ - public DigestType getDigestType() { + public @NonNull DigestType getDigestType() { return this.digestType; } } diff --git a/platform-sdk/swirlds-common/src/main/java/com/swirlds/common/crypto/HashBuilder.java b/platform-sdk/swirlds-common/src/main/java/com/swirlds/common/crypto/HashBuilder.java index 0115abf524f0..4e6d5820cb02 100644 --- a/platform-sdk/swirlds-common/src/main/java/com/swirlds/common/crypto/HashBuilder.java +++ b/platform-sdk/swirlds-common/src/main/java/com/swirlds/common/crypto/HashBuilder.java @@ -169,7 +169,7 @@ public HashBuilder update(final Hash hash) { throw new IllegalArgumentException("hash"); } - digest.update(hash.getValue()); + hash.getBytes().writeTo(digest); return this; } diff --git a/platform-sdk/swirlds-common/src/main/java/com/swirlds/common/crypto/ImmutableHash.java b/platform-sdk/swirlds-common/src/main/java/com/swirlds/common/crypto/ImmutableHash.java index 6ddfb8c62b0f..958f7e99edd0 100644 --- a/platform-sdk/swirlds-common/src/main/java/com/swirlds/common/crypto/ImmutableHash.java +++ b/platform-sdk/swirlds-common/src/main/java/com/swirlds/common/crypto/ImmutableHash.java @@ -31,14 +31,14 @@ public ImmutableHash() {} * {@inheritDoc} */ public ImmutableHash(final byte[] value) { - super(value, DigestType.SHA_384, true, true); + super(Arrays.copyOf(value, value.length), DigestType.SHA_384); } /** * {@inheritDoc} */ public ImmutableHash(final byte[] value, final DigestType digestType) { - super(value, digestType, true, true); + super(Arrays.copyOf(value, value.length), digestType); } /** @@ -53,7 +53,6 @@ public ImmutableHash(final Hash mutable) { */ @Override public byte[] getValue() { - final byte[] value = super.getValue(); - return Arrays.copyOf(value, value.length); + return copyToByteArray(); } } diff --git a/platform-sdk/swirlds-common/src/main/java/com/swirlds/common/crypto/Signature.java b/platform-sdk/swirlds-common/src/main/java/com/swirlds/common/crypto/Signature.java index b2c013ce8f7c..63e600e58d1a 100644 --- a/platform-sdk/swirlds-common/src/main/java/com/swirlds/common/crypto/Signature.java +++ b/platform-sdk/swirlds-common/src/main/java/com/swirlds/common/crypto/Signature.java @@ -19,10 +19,12 @@ import static com.swirlds.common.crypto.SignatureType.RSA; import static com.swirlds.common.utility.CommonUtils.hex; +import com.hedera.pbj.runtime.io.buffer.Bytes; import com.swirlds.base.utility.ToStringBuilder; import com.swirlds.common.io.SelfSerializable; import com.swirlds.common.io.streams.SerializableDataInputStream; import com.swirlds.common.io.streams.SerializableDataOutputStream; +import edu.umd.cs.findbugs.annotations.NonNull; import java.io.IOException; import java.util.Arrays; import java.util.Objects; @@ -78,6 +80,21 @@ public byte[] getSignatureBytes() { return signatureBytes; } + /** + * @return the bytes of this signature in an immutable instance + */ + public @NonNull Bytes getBytes() { + return Bytes.wrap(signatureBytes); + } + + /** + * Get the type of this signature. + */ + @NonNull + public SignatureType getType() { + return signatureType; + } + /** * {@inheritDoc} */ diff --git a/platform-sdk/swirlds-common/src/main/java/com/swirlds/common/crypto/engine/AsyncDigestHandler.java b/platform-sdk/swirlds-common/src/main/java/com/swirlds/common/crypto/engine/AsyncDigestHandler.java deleted file mode 100644 index 46007f847d2c..000000000000 --- a/platform-sdk/swirlds-common/src/main/java/com/swirlds/common/crypto/engine/AsyncDigestHandler.java +++ /dev/null @@ -1,78 +0,0 @@ -/* - * Copyright (C) 2016-2024 Hedera Hashgraph, LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.swirlds.common.crypto.engine; - -import com.swirlds.common.crypto.Message; -import java.security.NoSuchAlgorithmException; -import java.util.List; - -/** - * A message digest capable {@link AsyncOperationHandler} implementation. - * - * Provides a generic way to process cryptographic transformations for a given {@link List} of work items in a - * asynchronous manner on a background thread. This object also serves as the {@link java.util.concurrent.Future} - * implementation assigned to each item contained in the {@link List}. - */ -public class AsyncDigestHandler extends AsyncOperationHandler { - - /** - * Constructs an {@link AsyncOperationHandler} which will operate on the provided {@link List} of items using the - * specified algorithm provider. This method does not make a copy of the list provided and expects exclusive access - * to the list. - * - * @param workItems - * the list of items to be asynchronously processed by the algorithm provider - * @param provider - * the algorithm provider used to perform cryptographic transformations on each item - */ - public AsyncDigestHandler(final List workItems, final DigestProvider provider) { - super(workItems, provider); - } - - /** - * Constructs an {@link AsyncOperationHandler} which will operate on the provided {@link List} of items using the - * specified algorithm provider. - * - * @param workItems - * the list of items to be asynchronously processed by the algorithm provider - * @param shouldCopy - * if true, then a shallow copy of the provided list will be made; otherwise the original list will be used - * @param provider - * the algorithm provider used to perform cryptographic transformations on each item - */ - public AsyncDigestHandler(final List workItems, final boolean shouldCopy, final DigestProvider provider) { - super(workItems, shouldCopy, provider); - } - - /** - * Called by the {@link #run()} method to process the cryptographic transformation for a single item on the - * background - * thread. - * - * @param provider - * the algorithm provider to use - * @param item - * the input to be transformed - * @throws NoSuchAlgorithmException - * if an implementation of the required algorithm cannot be located or loaded - */ - @Override - protected void handleWorkItem(final DigestProvider provider, final Message item) throws NoSuchAlgorithmException { - item.setFuture(this); - item.setHash(provider.compute(item, item.getDigestType())); - } -} diff --git a/platform-sdk/swirlds-common/src/main/java/com/swirlds/common/crypto/engine/CryptoEngine.java b/platform-sdk/swirlds-common/src/main/java/com/swirlds/common/crypto/engine/CryptoEngine.java index e419c128d525..0fb79bd403af 100644 --- a/platform-sdk/swirlds-common/src/main/java/com/swirlds/common/crypto/engine/CryptoEngine.java +++ b/platform-sdk/swirlds-common/src/main/java/com/swirlds/common/crypto/engine/CryptoEngine.java @@ -192,7 +192,7 @@ public synchronized void setSettings(final CryptoConfig config) { */ @Override public Hash digestSync(final byte[] message, final DigestType digestType) { - return digestSyncInternal(message, digestType, digestProvider); + return new Hash(digestSyncInternal(message, digestType, digestProvider), digestType); } /** @@ -224,6 +224,12 @@ public Hash digestSync( } } + @NonNull + @Override + public byte[] digestBytesSync(@NonNull final byte[] message, @NonNull final DigestType digestType) { + return digestSyncInternal(message, digestType, digestProvider); + } + /** * Compute and store hash for null using different digest types. */ @@ -344,7 +350,10 @@ protected synchronized void applySettings() { * @param provider the underlying provider to be used * @return the cryptographic hash for the given message contents */ - private Hash digestSyncInternal(final byte[] message, final DigestType digestType, final DigestProvider provider) { + private @NonNull byte[] digestSyncInternal( + @NonNull final byte[] message, + @NonNull final DigestType digestType, + @NonNull final DigestProvider provider) { try { return provider.compute(message, digestType); } catch (final NoSuchAlgorithmException ex) { diff --git a/platform-sdk/swirlds-common/src/main/java/com/swirlds/common/crypto/engine/DigestProvider.java b/platform-sdk/swirlds-common/src/main/java/com/swirlds/common/crypto/engine/DigestProvider.java index f8cc6022726e..78b8b5cdc91a 100644 --- a/platform-sdk/swirlds-common/src/main/java/com/swirlds/common/crypto/engine/DigestProvider.java +++ b/platform-sdk/swirlds-common/src/main/java/com/swirlds/common/crypto/engine/DigestProvider.java @@ -17,8 +17,8 @@ package com.swirlds.common.crypto.engine; import com.swirlds.common.crypto.DigestType; -import com.swirlds.common.crypto.Hash; import com.swirlds.common.crypto.Message; +import edu.umd.cs.findbugs.annotations.NonNull; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; @@ -26,7 +26,7 @@ * Implementation of a message digest provider. This implementation depends on the JCE {@link MessageDigest} providers * and supports all algorithms supported by the JVM. */ -public class DigestProvider extends CachingOperationProvider { +public class DigestProvider extends CachingOperationProvider { /** * Default Constructor. @@ -45,7 +45,7 @@ public DigestProvider() { * @throws NoSuchAlgorithmException * if an implementation of the required algorithm cannot be located or loaded */ - protected Hash compute(final byte[] msg) throws NoSuchAlgorithmException { + protected @NonNull byte[] compute(@NonNull final byte[] msg) throws NoSuchAlgorithmException { return compute(msg, DigestType.SHA_384); } @@ -60,7 +60,8 @@ protected Hash compute(final byte[] msg) throws NoSuchAlgorithmException { * @throws NoSuchAlgorithmException * if an implementation of the required algorithm cannot be located or loaded */ - protected Hash compute(final byte[] msg, final DigestType algorithmType) throws NoSuchAlgorithmException { + protected @NonNull byte[] compute(@NonNull final byte[] msg, @NonNull final DigestType algorithmType) + throws NoSuchAlgorithmException { if (msg == null) { throw new IllegalArgumentException("msg"); } @@ -68,24 +69,6 @@ protected Hash compute(final byte[] msg, final DigestType algorithmType) throws return compute(msg, 0, msg.length, algorithmType); } - /** - * Computes the result of the cryptographic transformation using the given subset of bytes from the provided - * message. This implementation defaults to an SHA-384 message digest and is provided for convenience. - * - * @param msg - * the message for which to compute a message digest - * @param offset - * the starting offset to begin reading - * @param length - * the total number of bytes to read - * @return the message digest as an array of the raw bytes - * @throws NoSuchAlgorithmException - * if an implementation of the required algorithm cannot be located or loaded - */ - private Hash compute(final byte[] msg, final int offset, final int length) throws NoSuchAlgorithmException { - return compute(msg, offset, length, DigestType.SHA_384); - } - /** * Computes the result of the cryptographic transformation using the given subset of bytes from the provided * message. This implementation defaults to an SHA-384 message digest and is provided for convenience. @@ -102,17 +85,19 @@ private Hash compute(final byte[] msg, final int offset, final int length) throw * @throws NoSuchAlgorithmException * if an implementation of the required algorithm cannot be located or loaded */ - private Hash compute(final byte[] msg, final int offset, final int length, final DigestType algorithmType) + private @NonNull byte[] compute( + @NonNull final byte[] msg, final int offset, final int length, @NonNull final DigestType algorithmType) throws NoSuchAlgorithmException { final MessageDigest algorithm = loadAlgorithm(algorithmType); - return new Hash(compute(algorithm, msg, offset, length), algorithmType); + return compute(algorithm, msg, offset, length); } /** * {@inheritDoc} */ @Override - protected MessageDigest handleAlgorithmRequired(final DigestType algorithmType) throws NoSuchAlgorithmException { + protected @NonNull MessageDigest handleAlgorithmRequired(@NonNull final DigestType algorithmType) + throws NoSuchAlgorithmException { return MessageDigest.getInstance(algorithmType.algorithmName()); } @@ -120,12 +105,12 @@ protected MessageDigest handleAlgorithmRequired(final DigestType algorithmType) * {@inheritDoc} */ @Override - protected Hash handleItem( - final MessageDigest algorithm, - final DigestType algorithmType, - final Message item, - final Void optionalData) { - return new Hash(compute(algorithm, item.getPayloadDirect(), item.getOffset(), item.getLength()), algorithmType); + protected @NonNull byte[] handleItem( + @NonNull final MessageDigest algorithm, + @NonNull final DigestType algorithmType, + @NonNull final Message item, + @NonNull final Void optionalData) { + return compute(algorithm, item.getPayloadDirect(), item.getOffset(), item.getLength()); } /** @@ -142,7 +127,8 @@ protected Hash handleItem( * the total number of bytes to read * @return the message digest as an array of the raw bytes */ - private byte[] compute(final MessageDigest algorithm, final byte[] msg, final int offset, final int length) { + private @NonNull byte[] compute( + @NonNull final MessageDigest algorithm, @NonNull final byte[] msg, final int offset, final int length) { algorithm.reset(); algorithm.update(msg, offset, length); diff --git a/platform-sdk/swirlds-common/src/main/java/com/swirlds/common/io/config/FileSystemManagerConfig.java b/platform-sdk/swirlds-common/src/main/java/com/swirlds/common/io/config/FileSystemManagerConfig.java index 104f00de1a19..22f631acc7a9 100644 --- a/platform-sdk/swirlds-common/src/main/java/com/swirlds/common/io/config/FileSystemManagerConfig.java +++ b/platform-sdk/swirlds-common/src/main/java/com/swirlds/common/io/config/FileSystemManagerConfig.java @@ -40,5 +40,5 @@ public record FileSystemManagerConfig( public static final String DEFAULT_TMP_DIR_NAME = "tmp"; public static final String DEFAULT_DATA_DIR_NAME = "saved"; - public static final String DEFAULT_RECYCLE_BIN_DIR_NAME = "recycle-bin"; + public static final String DEFAULT_RECYCLE_BIN_DIR_NAME = "saved/swirlds-recycle-bin"; } diff --git a/platform-sdk/swirlds-common/src/main/java/com/swirlds/common/io/filesystem/FileSystemManagerFactory.java b/platform-sdk/swirlds-common/src/main/java/com/swirlds/common/io/filesystem/FileSystemManagerFactory.java index 2e740ebafb66..c1ba0f643f8a 100644 --- a/platform-sdk/swirlds-common/src/main/java/com/swirlds/common/io/filesystem/FileSystemManagerFactory.java +++ b/platform-sdk/swirlds-common/src/main/java/com/swirlds/common/io/filesystem/FileSystemManagerFactory.java @@ -17,6 +17,8 @@ package com.swirlds.common.io.filesystem; import com.swirlds.common.io.filesystem.internal.FileSystemManagerFactoryImpl; +import com.swirlds.common.io.utility.RecycleBin; +import com.swirlds.common.platform.NodeId; import com.swirlds.config.api.Configuration; import com.swirlds.metrics.api.Metrics; import edu.umd.cs.findbugs.annotations.NonNull; @@ -30,12 +32,25 @@ public interface FileSystemManagerFactory { * Creates a {@link FileSystemManager} by searching {@code root} path in the {@link Configuration} class under a * property name indicated in {@code rootLocationPropertyName} * - * @param configuration the configuration instance to retrieve properties from + * @param configuration the configuration instance to retrieve properties from + * @param recycleBin the recycleBin instance to use + * @return a new instance of {@link FileSystemManager} + */ + @NonNull + FileSystemManager createFileSystemManager(@NonNull Configuration configuration, @NonNull RecycleBin recycleBin); + + /** + * Creates a {@link FileSystemManager} by searching {@code root} path in the {@link Configuration} class under a + * property name indicated in {@code rootLocationPropertyName} + * + * @param configuration the configuration instance to retrieve properties from + * @param metrics metrics instance of the platform + * @param nodeId id of the ode configuring this instance * @return a new instance of {@link FileSystemManager} */ @NonNull FileSystemManager createFileSystemManager( - @NonNull final Configuration configuration, @NonNull final Metrics metrics); + @NonNull Configuration configuration, @NonNull Metrics metrics, @NonNull NodeId nodeId); /** * Retrieves the default FileSystemManagerFactory instance diff --git a/platform-sdk/swirlds-common/src/main/java/com/swirlds/common/io/filesystem/internal/FileSystemManagerFactoryImpl.java b/platform-sdk/swirlds-common/src/main/java/com/swirlds/common/io/filesystem/internal/FileSystemManagerFactoryImpl.java index eff6443ddab7..1c1d6c02080a 100644 --- a/platform-sdk/swirlds-common/src/main/java/com/swirlds/common/io/filesystem/internal/FileSystemManagerFactoryImpl.java +++ b/platform-sdk/swirlds-common/src/main/java/com/swirlds/common/io/filesystem/internal/FileSystemManagerFactoryImpl.java @@ -22,11 +22,14 @@ import com.swirlds.common.io.config.FileSystemManagerConfig; import com.swirlds.common.io.filesystem.FileSystemManager; import com.swirlds.common.io.filesystem.FileSystemManagerFactory; +import com.swirlds.common.io.utility.RecycleBin; import com.swirlds.common.io.utility.RecycleBinImpl; +import com.swirlds.common.platform.NodeId; import com.swirlds.config.api.Configuration; import com.swirlds.metrics.api.Metrics; import edu.umd.cs.findbugs.annotations.NonNull; import java.io.UncheckedIOException; +import java.nio.file.Path; public class FileSystemManagerFactoryImpl implements FileSystemManagerFactory { @@ -40,22 +43,40 @@ private static class InstanceHolder { * {@code FileSystemManagerConfig} record * * @param configuration the configuration instance to retrieve properties from - * @param metrics metrics instance + * @param recycleBin the recycle-bin instance to use in the fileSystemManager * @return a new instance of {@link FileSystemManager} * @throws UncheckedIOException if the dir structure to rootLocation cannot be created */ @NonNull @Override public FileSystemManager createFileSystemManager( - @NonNull final Configuration configuration, @NonNull final Metrics metrics) { - + @NonNull final Configuration configuration, @NonNull final RecycleBin recycleBin) { final FileSystemManagerConfig fsmConfig = configuration.getConfigData(FileSystemManagerConfig.class); + return new FileSystemManagerImpl(fsmConfig.rootPath(), fsmConfig.userDataDir(), fsmConfig.tmpDir(), recycleBin); + } + /** + * Creates a {@link FileSystemManager} and a {@link com.swirlds.common.io.utility.RecycleBin} by searching {@code root} + * path in the {@link Configuration} class using + * {@code FileSystemManagerConfig} record + * + * @param configuration the configuration instance to retrieve properties from + * @param metrics metrics instance + * @param selfId the node id for the recycle bin path + * @return a new instance of {@link FileSystemManager} + * @throws UncheckedIOException if the dir structure to rootLocation cannot be created + */ + @NonNull + @Override + public FileSystemManager createFileSystemManager( + @NonNull final Configuration configuration, @NonNull final Metrics metrics, @NonNull final NodeId selfId) { + final FileSystemManagerConfig fsmConfig = configuration.getConfigData(FileSystemManagerConfig.class); + final Path recycleBinDir = Path.of(fsmConfig.recycleBinDir()).resolve(selfId.toString()); return new FileSystemManagerImpl( fsmConfig.rootPath(), fsmConfig.userDataDir(), fsmConfig.tmpDir(), - fsmConfig.recycleBinDir(), + recycleBinDir.toString(), path -> new RecycleBinImpl( metrics, getStaticThreadManager(), diff --git a/platform-sdk/swirlds-common/src/main/java/com/swirlds/common/io/filesystem/internal/FileSystemManagerImpl.java b/platform-sdk/swirlds-common/src/main/java/com/swirlds/common/io/filesystem/internal/FileSystemManagerImpl.java index 954ccada62e0..d6eb94441c59 100644 --- a/platform-sdk/swirlds-common/src/main/java/com/swirlds/common/io/filesystem/internal/FileSystemManagerImpl.java +++ b/platform-sdk/swirlds-common/src/main/java/com/swirlds/common/io/filesystem/internal/FileSystemManagerImpl.java @@ -29,7 +29,6 @@ import java.io.UncheckedIOException; import java.nio.file.Files; import java.nio.file.Path; -import java.nio.file.StandardCopyOption; import java.util.concurrent.atomic.AtomicLong; import java.util.function.Function; @@ -62,10 +61,8 @@ public class FileSystemManagerImpl implements FileSystemManager { private final Path rootPath; private final Path tempPath; private final Path savedPath; - private final Path recycleBinPath; private final RecycleBin bin; private final AtomicLong tmpFileNameIndex = new AtomicLong(0); - /** * Creates a {@link FileSystemManager} and a {@link com.swirlds.common.io.utility.RecycleBin} by searching {@code root} * path in the {@link Configuration} class using @@ -74,16 +71,14 @@ public class FileSystemManagerImpl implements FileSystemManager { * @param rootLocation the location to be used as root path. It should not exist. * @param dataDirName the name of the user data file directory * @param tmpDirName the name of the tmp file directory - * @param recycleBinDirName the name of the recycle bin directory - * @param binSupplier for building the recycle bin. + * @param recycleBin for building the recycle bin. * @throws UncheckedIOException if the dir structure to rootLocation cannot be created */ FileSystemManagerImpl( @NonNull final String rootLocation, final String dataDirName, final String tmpDirName, - final String recycleBinDirName, - @NonNull final Function binSupplier) { + @NonNull final RecycleBin recycleBin) { this.rootPath = Path.of(rootLocation).normalize(); if (!exists(rootPath)) { rethrowIO(() -> Files.createDirectories(rootPath)); @@ -91,7 +86,6 @@ public class FileSystemManagerImpl implements FileSystemManager { this.tempPath = rootPath.resolve(tmpDirName); this.savedPath = rootPath.resolve(dataDirName); - this.recycleBinPath = rootPath.resolve(recycleBinDirName); if (!exists(savedPath)) { rethrowIO(() -> Files.createDirectory(savedPath)); @@ -101,15 +95,46 @@ public class FileSystemManagerImpl implements FileSystemManager { } rethrowIO(() -> Files.createDirectory(tempPath)); - // FUTURE-WORK: --MIGRATION-- Remove this logic after the fs manager was deployed. - // Moves files in the old location of the recycle bin to the new one - final Path oldRecyclePath = savedPath.resolve("swirlds-recycle-bin"); - if (!exists(recycleBinPath) && exists(oldRecyclePath)) { - rethrowIO(() -> Files.move(oldRecyclePath, recycleBinPath, StandardCopyOption.ATOMIC_MOVE)); + this.bin = recycleBin; + } + + /** + * Creates a {@link FileSystemManager} and a {@link com.swirlds.common.io.utility.RecycleBin} by searching {@code root} + * path in the {@link Configuration} class using + * {@code FileSystemManagerConfig} record + * + * @param rootLocation the location to be used as root path. It should not exist. + * @param dataDirName the name of the user data file directory + * @param tmpDirName the name of the tmp file directory + * @param recycleBinDirName the name of the recycle bin directory + * @param binSupplier for building the recycle bin. + * @throws UncheckedIOException if the dir structure to rootLocation cannot be created + */ + FileSystemManagerImpl( + @NonNull final String rootLocation, + @NonNull final String dataDirName, + @NonNull final String tmpDirName, + @NonNull final String recycleBinDirName, + @NonNull final Function binSupplier) { + this.rootPath = Path.of(rootLocation).normalize(); + if (!exists(rootPath)) { + rethrowIO(() -> Files.createDirectories(rootPath)); } + this.tempPath = rootPath.resolve(tmpDirName); + this.savedPath = rootPath.resolve(dataDirName); + final Path recycleBinPath = rootPath.resolve(recycleBinDirName); + + if (!exists(savedPath)) { + rethrowIO(() -> Files.createDirectory(savedPath)); + } + if (exists(tempPath)) { + rethrowIO(() -> FileUtils.deleteDirectory(tempPath)); + } + rethrowIO(() -> Files.createDirectory(tempPath)); + if (!exists(recycleBinPath)) { - rethrowIO(() -> Files.createDirectory(recycleBinPath)); + rethrowIO(() -> Files.createDirectories(recycleBinPath)); } this.bin = binSupplier.apply(recycleBinPath); } diff --git a/platform-sdk/swirlds-common/src/main/java/com/swirlds/common/io/streams/SerializableDataOutputStream.java b/platform-sdk/swirlds-common/src/main/java/com/swirlds/common/io/streams/SerializableDataOutputStream.java index db59be86b7ca..28fb1c480c51 100644 --- a/platform-sdk/swirlds-common/src/main/java/com/swirlds/common/io/streams/SerializableDataOutputStream.java +++ b/platform-sdk/swirlds-common/src/main/java/com/swirlds/common/io/streams/SerializableDataOutputStream.java @@ -25,7 +25,6 @@ import static com.swirlds.common.io.streams.SerializableStreamConstants.VERSION_BYTES; import com.swirlds.common.io.FunctionalSerialize; -import com.swirlds.common.io.OptionalSelfSerializable; import com.swirlds.common.io.SelfSerializable; import com.swirlds.common.io.SerializableDet; import com.swirlds.common.io.SerializableWithKnownLength; @@ -98,11 +97,6 @@ public void writeSerializable(SelfSerializable serializable, boolean writeClassI writeSerializable(serializable, writeClassId, serializable); } - public > void writeOptionalSerializable( - OptionalSelfSerializable serializable, boolean writeClassId, E option) throws IOException { - writeSerializable(serializable, writeClassId, out -> serializable.serialize(out, option)); - } - /** * Writes a list of objects returned by an {@link Iterator} when the size in known ahead of time. If the class is * known at the time of deserialization, the {@code writeClassId} param can be set to false. If the class might be diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/utilops/lifecycle/selectors/SelectAll.java b/platform-sdk/swirlds-common/src/main/java/com/swirlds/common/io/utility/NoOpRecycleBin.java similarity index 61% rename from hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/utilops/lifecycle/selectors/SelectAll.java rename to platform-sdk/swirlds-common/src/main/java/com/swirlds/common/io/utility/NoOpRecycleBin.java index 528f272bf7cb..263280edd40a 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/utilops/lifecycle/selectors/SelectAll.java +++ b/platform-sdk/swirlds-common/src/main/java/com/swirlds/common/io/utility/NoOpRecycleBin.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023-2024 Hedera Hashgraph, LLC + * Copyright (C) 2024 Hedera Hashgraph, LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,22 +14,23 @@ * limitations under the License. */ -package com.hedera.services.bdd.spec.utilops.lifecycle.selectors; +package com.swirlds.common.io.utility; -import com.hedera.services.bdd.junit.HapiTestNode; import edu.umd.cs.findbugs.annotations.NonNull; +import java.io.IOException; +import java.nio.file.Path; /** - * Selects all nodes. + * A no-op {@link RecycleBin} implementation. */ -public class SelectAll implements NodeSelector { +public class NoOpRecycleBin implements RecycleBin { + + @Override + public void recycle(@NonNull Path path) throws IOException {} + @Override - public boolean test(@NonNull final HapiTestNode hapiTestNode) { - return true; - } + public void start() {} @Override - public String toString() { - return "including all nodes"; - } + public void stop() {} } diff --git a/platform-sdk/swirlds-common/src/main/java/com/swirlds/common/merkle/crypto/internal/MerkleInternalDigestProvider.java b/platform-sdk/swirlds-common/src/main/java/com/swirlds/common/merkle/crypto/internal/MerkleInternalDigestProvider.java index 462ba5cb6ec0..e565d39447d7 100644 --- a/platform-sdk/swirlds-common/src/main/java/com/swirlds/common/merkle/crypto/internal/MerkleInternalDigestProvider.java +++ b/platform-sdk/swirlds-common/src/main/java/com/swirlds/common/merkle/crypto/internal/MerkleInternalDigestProvider.java @@ -65,7 +65,7 @@ protected Hash handleItem( for (int index = 0; index < childHashes.size(); index++) { final Hash childHash = childHashes.get(index); - if (childHash == null || childHash.getValue() == null) { + if (childHash == null || childHash.getBytes() == null) { final MerkleNode childNode = node.getChild(index); final String msg = String.format( "Child has an unexpected null hash " diff --git a/platform-sdk/swirlds-common/src/main/java/com/swirlds/common/merkle/synchronization/stats/ReconnectMapMetrics.java b/platform-sdk/swirlds-common/src/main/java/com/swirlds/common/merkle/synchronization/stats/ReconnectMapMetrics.java new file mode 100644 index 000000000000..f9b96dc0ceae --- /dev/null +++ b/platform-sdk/swirlds-common/src/main/java/com/swirlds/common/merkle/synchronization/stats/ReconnectMapMetrics.java @@ -0,0 +1,170 @@ +/* + * Copyright (C) 2024 Hedera Hashgraph, LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.swirlds.common.merkle.synchronization.stats; + +import com.swirlds.metrics.api.Counter; +import com.swirlds.metrics.api.Metrics; +import edu.umd.cs.findbugs.annotations.NonNull; +import edu.umd.cs.findbugs.annotations.Nullable; +import java.util.Objects; + +/** + * An implementation of ReconnectMapStats that emits all the stats as Counter metrics. + */ +public class ReconnectMapMetrics implements ReconnectMapStats { + + private static final String RECONNECT_MAP_CATEGORY = "reconnect_vmap"; + + private final ReconnectMapStats aggregateStats; + + private final Counter transfersFromTeacher; + private final Counter transfersFromLearner; + + private final Counter internalHashes; + private final Counter internalCleanHashes; + private final Counter internalData; + private final Counter internalCleanData; + + private final Counter leafHashes; + private final Counter leafCleanHashes; + private final Counter leafData; + private final Counter leafCleanData; + + /** + * Create an instance of ReconnectMapMetrics. + * @param metrics a non-null Metrics object + * @param originalLabel an optional label, e.g. a VirtualMap name or similar, may be null. + * If specified, the label is added to the names of the metrics. + * @param aggregateStats an optional aggregateStats object to which all ReconnectMapStats calls will delegate + * in addition to emitting metrics for this object, may be null. + */ + public ReconnectMapMetrics( + @NonNull final Metrics metrics, + @Nullable final String originalLabel, + @Nullable final ReconnectMapStats aggregateStats) { + Objects.requireNonNull(metrics, "metrics must not be null"); + this.aggregateStats = aggregateStats; + // Normalize the label + final String label = originalLabel.replace('.', '_'); + + this.transfersFromTeacher = metrics.getOrCreate( + new Counter.Config(RECONNECT_MAP_CATEGORY, formatName("transfersFromTeacher", label)) + .withDescription("number of transfers from teacher to learner")); + this.transfersFromLearner = metrics.getOrCreate( + new Counter.Config(RECONNECT_MAP_CATEGORY, formatName("transfersFromLearner", label)) + .withDescription("number of transfers from learner to teacher")); + + this.internalHashes = + metrics.getOrCreate(new Counter.Config(RECONNECT_MAP_CATEGORY, formatName("internalHashes", label)) + .withDescription("number of internal node hashes transferred")); + this.internalCleanHashes = + metrics.getOrCreate(new Counter.Config(RECONNECT_MAP_CATEGORY, formatName("internalCleanHashes", label)) + .withDescription("number of clean internal node hashes transferred")); + this.internalData = + metrics.getOrCreate(new Counter.Config(RECONNECT_MAP_CATEGORY, formatName("internalData", label)) + .withDescription("number of internal node data transferred")); + this.internalCleanData = + metrics.getOrCreate(new Counter.Config(RECONNECT_MAP_CATEGORY, formatName("internalCleanData", label)) + .withDescription("number of clean internal node data transferred")); + + this.leafHashes = + metrics.getOrCreate(new Counter.Config(RECONNECT_MAP_CATEGORY, formatName("leafHashes", label)) + .withDescription("number of leaf node hashes transferred")); + this.leafCleanHashes = + metrics.getOrCreate(new Counter.Config(RECONNECT_MAP_CATEGORY, formatName("leafCleanHashes", label)) + .withDescription("number of clean leaf node hashes transferred")); + this.leafData = metrics.getOrCreate(new Counter.Config(RECONNECT_MAP_CATEGORY, formatName("leafData", label)) + .withDescription("number of leaf node data transferred")); + this.leafCleanData = + metrics.getOrCreate(new Counter.Config(RECONNECT_MAP_CATEGORY, formatName("leafCleanData", label)) + .withDescription("number of clean leaf node data transferred")); + } + + private static String formatName(final String name, final String label) { + return (label == null || label.isBlank() ? name : (name + "_" + label + "_")) + "Total"; + } + + /** + * {@inheritDoc} + */ + @Override + public void incrementTransfersFromTeacher() { + transfersFromTeacher.increment(); + if (aggregateStats != null) { + aggregateStats.incrementTransfersFromTeacher(); + } + } + + /** + * {@inheritDoc} + */ + @Override + public void incrementTransfersFromLearner() { + transfersFromLearner.increment(); + if (aggregateStats != null) { + aggregateStats.incrementTransfersFromLearner(); + } + } + + /** + * {@inheritDoc} + */ + @Override + public void incrementInternalHashes(final int hashNum, final int cleanHashNum) { + internalHashes.add(hashNum); + internalCleanHashes.add(cleanHashNum); + if (aggregateStats != null) { + aggregateStats.incrementInternalHashes(hashNum, cleanHashNum); + } + } + + /** + * {@inheritDoc} + */ + @Override + public void incrementInternalData(final int dataNum, final int cleanDataNum) { + internalData.add(dataNum); + internalCleanData.add(cleanDataNum); + if (aggregateStats != null) { + aggregateStats.incrementInternalData(dataNum, cleanDataNum); + } + } + + /** + * {@inheritDoc} + */ + @Override + public void incrementLeafHashes(final int hashNum, final int cleanHashNum) { + leafHashes.add(hashNum); + leafCleanHashes.add(cleanHashNum); + if (aggregateStats != null) { + aggregateStats.incrementLeafHashes(hashNum, cleanHashNum); + } + } + + /** + * {@inheritDoc} + */ + @Override + public void incrementLeafData(final int dataNum, final int cleanDataNum) { + leafData.add(dataNum); + leafCleanData.add(cleanDataNum); + if (aggregateStats != null) { + aggregateStats.incrementLeafData(dataNum, cleanDataNum); + } + } +} diff --git a/platform-sdk/swirlds-common/src/main/java/com/swirlds/common/merkle/synchronization/stats/ReconnectMapStats.java b/platform-sdk/swirlds-common/src/main/java/com/swirlds/common/merkle/synchronization/stats/ReconnectMapStats.java new file mode 100644 index 000000000000..dc96b9afc408 --- /dev/null +++ b/platform-sdk/swirlds-common/src/main/java/com/swirlds/common/merkle/synchronization/stats/ReconnectMapStats.java @@ -0,0 +1,75 @@ +/* + * Copyright (C) 2024 Hedera Hashgraph, LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.swirlds.common.merkle.synchronization.stats; + +/** + * An interface that helps gather statistics about the reconnect of tree-like data structures, such as VirtualMaps. + *

    + * An implementation could gather aggregate statistics for all maps, or it could gather the counters for a specific + * map and then also optionally delegate to another instance of the interface that would compute aggregate stats + * for all maps. + *

    + * All the methods have default no-op implementations to help with stubbing of the instances until an implementation + * is ready, or in tests. + */ +public interface ReconnectMapStats { + /** + * Increment a transfers from teacher counter. + *

    + * Different reconnect algorithms may define the term "transfer" differently. Examples of a transfer from teacher:
    + * * a lesson from the teacher,
    + * * a response from the teacher per a prior request from the learner. + */ + default void incrementTransfersFromTeacher() {} + + /** + * Increment a transfers from learner counter. + *

    + * Different reconnect algorithms may define the term "transfer" differently. Examples of a transfer from learner:
    + * * a query response to the teacher for a single hash,
    + * * a request from the learner. + */ + default void incrementTransfersFromLearner() {} + + /** + * Gather stats about internal nodes hashes transfers. + * @param hashNum the number of hashes of internal nodes transferred + * @param cleanHashNum the number of hashes transferred unnecessarily because they were clean + */ + default void incrementInternalHashes(int hashNum, int cleanHashNum) {} + + /** + * Gather stats about internal nodes data transfers. + * @param dataNum the number of data payloads of internal nodes transferred (for non-VirtualMap trees) + * @param cleanDataNum the number of data payloads transferred unnecessarily because they were clean + */ + default void incrementInternalData(int dataNum, int cleanDataNum) {} + + /** + * Gather stats about leaf nodes hashes transfers. + * @param hashNum the number of hashes of leaf nodes transferred + * @param cleanHashNum the number of hashes transferred unnecessarily because they were clean + */ + default void incrementLeafHashes(int hashNum, int cleanHashNum) {} + + /** + * Gather stats about leaf nodes data transfers. + * @param dataNum the number of data payloads of leaf nodes transferred + * @param cleanDataNum the number of data payloads transferred unnecessarily because they were clean + */ + default void incrementLeafData(int dataNum, int cleanDataNum) {} +} diff --git a/platform-sdk/swirlds-common/src/main/java/com/swirlds/common/stream/RunningEventHashOverride.java b/platform-sdk/swirlds-common/src/main/java/com/swirlds/common/stream/RunningEventHashOverride.java index 42d999a20623..db5d963f2e99 100644 --- a/platform-sdk/swirlds-common/src/main/java/com/swirlds/common/stream/RunningEventHashOverride.java +++ b/platform-sdk/swirlds-common/src/main/java/com/swirlds/common/stream/RunningEventHashOverride.java @@ -24,8 +24,6 @@ * reconnect or a restart). * * @param legacyRunningEventHash the legacy running event hash of the loaded state, used by the consensus event stream - * @param runningEventHash the running event hash of the loaded state * @param isReconnect whether or not this is a reconnect state */ -public record RunningEventHashOverride( - @NonNull Hash legacyRunningEventHash, @NonNull Hash runningEventHash, boolean isReconnect) {} +public record RunningEventHashOverride(@NonNull Hash legacyRunningEventHash, boolean isReconnect) {} diff --git a/platform-sdk/swirlds-common/src/main/java/com/swirlds/common/stream/Signer.java b/platform-sdk/swirlds-common/src/main/java/com/swirlds/common/stream/Signer.java index a5d4b716cfae..e92d9ab52a76 100644 --- a/platform-sdk/swirlds-common/src/main/java/com/swirlds/common/stream/Signer.java +++ b/platform-sdk/swirlds-common/src/main/java/com/swirlds/common/stream/Signer.java @@ -17,6 +17,7 @@ package com.swirlds.common.stream; import com.swirlds.common.crypto.Signature; +import edu.umd.cs.findbugs.annotations.NonNull; /** * An object capable of signing data. @@ -27,9 +28,9 @@ public interface Signer { /** * generate signature bytes for given data * - * @param data - * an array of bytes + * @param data an array of bytes * @return signature bytes */ - Signature sign(byte[] data); + @NonNull + Signature sign(@NonNull byte[] data); } diff --git a/platform-sdk/swirlds-common/src/main/java/com/swirlds/common/stream/internal/LinkedObjectStreamValidateUtils.java b/platform-sdk/swirlds-common/src/main/java/com/swirlds/common/stream/internal/LinkedObjectStreamValidateUtils.java index 7b782cbffd26..243ee34a6bfa 100644 --- a/platform-sdk/swirlds-common/src/main/java/com/swirlds/common/stream/internal/LinkedObjectStreamValidateUtils.java +++ b/platform-sdk/swirlds-common/src/main/java/com/swirlds/common/stream/internal/LinkedObjectStreamValidateUtils.java @@ -132,7 +132,7 @@ public static StreamValidationResult validateSignature( Hash metaHashInSig = parsedPairs.right().left(); Signature metaSignature = parsedPairs.right().right(); - if (!verifySignature(entireHash.getValue(), entireSignature, publicKey)) { + if (!verifySignature(entireHash.copyToByteArray(), entireSignature, publicKey)) { result = StreamValidationResult.INVALID_ENTIRE_SIGNATURE; } else if (!verifySignature(metaHashInSig.getValue(), metaSignature, publicKey)) { result = StreamValidationResult.INVALID_META_SIGNATURE; diff --git a/platform-sdk/swirlds-common/src/main/java/com/swirlds/common/utility/LoggingClearables.java b/platform-sdk/swirlds-common/src/main/java/com/swirlds/common/utility/LoggingClearables.java deleted file mode 100644 index 93da37c24122..000000000000 --- a/platform-sdk/swirlds-common/src/main/java/com/swirlds/common/utility/LoggingClearables.java +++ /dev/null @@ -1,52 +0,0 @@ -/* - * Copyright (C) 2016-2024 Hedera Hashgraph, LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.swirlds.common.utility; - -import com.swirlds.base.utility.Pair; -import java.util.List; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; -import org.apache.logging.log4j.Marker; - -/** - * Combine a list of {@link Clearable} instances into a single one with logging. The combined instance will execute clear - * on each of the provided instances sequentially, while logging before each clear - */ -public class LoggingClearables implements Clearable { - private static final Logger logger = LogManager.getLogger(LoggingClearables.class); - private final Marker logMarker; - private final List> list; - - /** - * @param logMarker - * the log marker to use for logging - * @param pairs - * a list of pairs, each clearable is paired up with a string name used for logging - */ - public LoggingClearables(final Marker logMarker, final List> pairs) { - this.logMarker = logMarker; - this.list = pairs; - } - - @Override - public void clear() { - for (final Pair pair : list) { - logger.info(logMarker, "about to clear {}", pair::right); - pair.left().clear(); - } - } -} diff --git a/platform-sdk/swirlds-common/src/main/java/com/swirlds/common/utility/Mnemonics.java b/platform-sdk/swirlds-common/src/main/java/com/swirlds/common/utility/Mnemonics.java index 0c001b8092c6..4aa8a92f33e9 100644 --- a/platform-sdk/swirlds-common/src/main/java/com/swirlds/common/utility/Mnemonics.java +++ b/platform-sdk/swirlds-common/src/main/java/com/swirlds/common/utility/Mnemonics.java @@ -2162,7 +2162,7 @@ public static List generateMnemonicWords(@NonNull final byte[] data, fin while (mnemonics.size() < wordCount) { if (nextIndex + 1 >= entropy.length) { - entropy = CryptographyHolder.get().digestSync(entropy).getValue(); + entropy = CryptographyHolder.get().digestBytesSync(entropy); nextIndex = 0; } diff --git a/platform-sdk/swirlds-common/src/main/java/com/swirlds/common/wiring/component/internal/WiringComponentProxy.java b/platform-sdk/swirlds-common/src/main/java/com/swirlds/common/wiring/component/internal/WiringComponentProxy.java index 79482d0f4f3a..b751b34dd197 100644 --- a/platform-sdk/swirlds-common/src/main/java/com/swirlds/common/wiring/component/internal/WiringComponentProxy.java +++ b/platform-sdk/swirlds-common/src/main/java/com/swirlds/common/wiring/component/internal/WiringComponentProxy.java @@ -35,6 +35,13 @@ public class WiringComponentProxy implements InvocationHandler { @Override public Object invoke(@NonNull final Object proxy, @NonNull final Method method, @NonNull final Object[] args) throws Throwable { + + if (method.getName().equals("toString")) { + // Handle this specially, the debugger likes to call toString() + // on the proxy which disrupts normal behavior when debugging. + return "WiringComponentProxy"; + } + mostRecentlyInvokedMethod = Objects.requireNonNull(method); return null; } diff --git a/platform-sdk/swirlds-common/src/main/java/com/swirlds/common/wiring/model/internal/deterministic/DeterministicTaskScheduler.java b/platform-sdk/swirlds-common/src/main/java/com/swirlds/common/wiring/model/internal/deterministic/DeterministicTaskScheduler.java index 39e4e8c2e0ad..b547d7ece09f 100644 --- a/platform-sdk/swirlds-common/src/main/java/com/swirlds/common/wiring/model/internal/deterministic/DeterministicTaskScheduler.java +++ b/platform-sdk/swirlds-common/src/main/java/com/swirlds/common/wiring/model/internal/deterministic/DeterministicTaskScheduler.java @@ -80,7 +80,9 @@ public long getUnprocessedTaskCount() { */ @Override public void flush() { - throw new UnsupportedOperationException("Flush is not supported on deterministic task schedulers"); + // Future work: flushing is incompatible with deterministic task schedulers. + // This is because flushing currently requires us to block thread A while + // thread B does work, but with turtle there is only a single thread. } /** diff --git a/platform-sdk/swirlds-common/src/main/java/module-info.java b/platform-sdk/swirlds-common/src/main/java/module-info.java index dce8a05d8e72..efed38815dba 100644 --- a/platform-sdk/swirlds-common/src/main/java/module-info.java +++ b/platform-sdk/swirlds-common/src/main/java/module-info.java @@ -143,6 +143,7 @@ exports com.swirlds.common.startup; exports com.swirlds.common.threading.atomic; exports com.swirlds.common.wiring.model.diagram; + exports com.swirlds.common.concurrent; requires transitive com.swirlds.base; requires transitive com.swirlds.config.api; diff --git a/platform-sdk/swirlds-common/src/test/java/com/swirlds/common/context/internal/DefaultPlatformContextTest.java b/platform-sdk/swirlds-common/src/test/java/com/swirlds/common/context/internal/DefaultPlatformContextTest.java index a225855e5904..7ba819b4387f 100644 --- a/platform-sdk/swirlds-common/src/test/java/com/swirlds/common/context/internal/DefaultPlatformContextTest.java +++ b/platform-sdk/swirlds-common/src/test/java/com/swirlds/common/context/internal/DefaultPlatformContextTest.java @@ -19,7 +19,7 @@ import static org.junit.jupiter.api.Assertions.assertNotNull; import com.swirlds.base.time.Time; -import com.swirlds.common.context.DefaultPlatformContext; +import com.swirlds.common.concurrent.ExecutorFactory; import com.swirlds.common.context.PlatformContext; import com.swirlds.common.crypto.CryptographyHolder; import com.swirlds.common.metrics.PlatformMetricsProvider; @@ -47,6 +47,7 @@ void testNoNullServices() { metricsProvider.createPlatformMetrics(nodeId), CryptographyHolder.get(), Time.getCurrent(), + ExecutorFactory.create("test", new PlatformUncaughtExceptionHandler()), new TestFileSystemManager(Path.of("/tmp/test"))); // then @@ -55,5 +56,6 @@ void testNoNullServices() { assertNotNull(context.getCryptography(), "Cryptography must not be null"); assertNotNull(context.getTime(), "Time must not be null"); assertNotNull(context.getFileSystemManager(), "FileSystemManager must not be null"); + assertNotNull(context.getExecutorFactory(), "ExecutorFactory must not be null"); } } diff --git a/platform-sdk/swirlds-common/src/test/java/com/swirlds/common/crypto/CryptographyTests.java b/platform-sdk/swirlds-common/src/test/java/com/swirlds/common/crypto/CryptographyTests.java index be500ce66c85..6bc08e8c7738 100644 --- a/platform-sdk/swirlds-common/src/test/java/com/swirlds/common/crypto/CryptographyTests.java +++ b/platform-sdk/swirlds-common/src/test/java/com/swirlds/common/crypto/CryptographyTests.java @@ -18,7 +18,6 @@ import static com.swirlds.common.test.fixtures.junit.tags.TestQualifierTags.TIME_CONSUMING; import static com.swirlds.common.utility.CommonUtils.unhex; -import static org.junit.jupiter.api.Assertions.assertArrayEquals; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertNotNull; @@ -91,7 +90,7 @@ void digestSyncRawTest(final int count) { for (int i = 0; i < messages.length; i++) { messages[i] = digestPool.next(); final Hash hash = cryptography.digestSync(messages[i].getPayloadDirect(), DigestType.SHA_384); - assertTrue(digestPool.isValid(messages[i], hash.getValue())); + assertTrue(digestPool.isValid(messages[i], hash.copyToByteArray())); } } @@ -104,7 +103,7 @@ void hashableSerializableTest() { final Hash hash = hashable.getHash(); assertEquals(KNOWN_DUMMY_SERIALIZABLE_HASH, hash); - assertArrayEquals(KNOWN_DUMMY_SERIALIZABLE_HASH.getValue(), hash.getValue()); + assertEquals(KNOWN_DUMMY_SERIALIZABLE_HASH.getBytes(), hash.getBytes()); } @ParameterizedTest diff --git a/platform-sdk/swirlds-common/src/test/java/com/swirlds/common/crypto/HashTests.java b/platform-sdk/swirlds-common/src/test/java/com/swirlds/common/crypto/HashTests.java index c1f65c5b9bd5..076a2032fba2 100644 --- a/platform-sdk/swirlds-common/src/test/java/com/swirlds/common/crypto/HashTests.java +++ b/platform-sdk/swirlds-common/src/test/java/com/swirlds/common/crypto/HashTests.java @@ -16,7 +16,6 @@ package com.swirlds.common.crypto; -import static org.junit.jupiter.api.Assertions.assertArrayEquals; import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; @@ -59,17 +58,13 @@ public void exceptionTests() { assertDoesNotThrow(() -> new Hash(DigestType.SHA_512)); assertThrows(NullPointerException.class, () -> new Hash((DigestType) null)); - assertThrows(IllegalArgumentException.class, () -> new Hash((byte[]) null)); + assertThrows(NullPointerException.class, () -> new Hash((byte[]) null)); assertThrows(IllegalArgumentException.class, () -> new Hash((Hash) null)); - assertThrows(EmptyHashValueException.class, () -> new Hash(new byte[48])); - assertThrows(IllegalArgumentException.class, () -> new Hash(nonZeroHashValue, null)); + assertThrows(NullPointerException.class, () -> new Hash(nonZeroHashValue, null)); assertThrows(IllegalArgumentException.class, () -> new Hash(new byte[0], DigestType.SHA_384)); assertThrows(IllegalArgumentException.class, () -> new Hash(new byte[47], DigestType.SHA_384)); assertThrows(IllegalArgumentException.class, () -> new Hash(new byte[71], DigestType.SHA_512)); - assertThrows( - EmptyHashValueException.class, - () -> new Hash(new byte[DigestType.SHA_384.digestLength()], DigestType.SHA_384)); } @Test @@ -110,11 +105,9 @@ public void accessorCorrectness() { assertNotEquals(0, original.compareTo(new Hash(DigestType.SHA_512))); //////// - assertArrayEquals(original.getValue(), copy.getValue()); - assertArrayEquals(original.getValue(), recalculated.getValue()); - assertArrayEquals(copy.getValue(), recalculated.getValue()); - assertFalse(Arrays.equals(original.getValue(), different.getValue())); - assertFalse(Arrays.equals(copy.getValue(), different.getValue())); + assertEquals(original.getBytes(), copy.getBytes()); + assertEquals(original.getBytes(), recalculated.getBytes()); + assertEquals(copy.getBytes(), recalculated.getBytes()); assertEquals(original, copy); assertEquals(original, recalculated); diff --git a/platform-sdk/swirlds-common/src/test/java/com/swirlds/common/io/filesystem/internal/FileSystemManagerImplTest.java b/platform-sdk/swirlds-common/src/test/java/com/swirlds/common/io/filesystem/internal/FileSystemManagerImplTest.java index 148737220e7c..914534aa0969 100644 --- a/platform-sdk/swirlds-common/src/test/java/com/swirlds/common/io/filesystem/internal/FileSystemManagerImplTest.java +++ b/platform-sdk/swirlds-common/src/test/java/com/swirlds/common/io/filesystem/internal/FileSystemManagerImplTest.java @@ -58,8 +58,7 @@ public FileSystemManagerImpl getFileSystemManager() { getTestRootPath(), FileSystemManagerConfig.DEFAULT_DATA_DIR_NAME, FileSystemManagerConfig.DEFAULT_TMP_DIR_NAME, - FileSystemManagerConfig.DEFAULT_RECYCLE_BIN_DIR_NAME, - p -> mockRecycleBin); + mockRecycleBin); } private String getTestRootPath() { @@ -86,8 +85,7 @@ public void testNew_aLargeRootPathIsCreated() { largeRootLocation, FileSystemManagerConfig.DEFAULT_DATA_DIR_NAME, FileSystemManagerConfig.DEFAULT_TMP_DIR_NAME, - FileSystemManagerConfig.DEFAULT_RECYCLE_BIN_DIR_NAME, - p -> mockRecycleBin); + mockRecycleBin); // then assertThat(Path.of(largeRootLocation)).isDirectory().isNotEmptyDirectory(); } @@ -110,8 +108,7 @@ public void testNew_deletesAllInTempFolderIfPathExist() throws IOException { assertThat(dir) .isNotEmptyDirectory() .isDirectoryContaining("glob:**" + FileSystemManagerConfig.DEFAULT_TMP_DIR_NAME) - .isDirectoryContaining("glob:**" + FileSystemManagerConfig.DEFAULT_DATA_DIR_NAME) - .isDirectoryContaining("glob:**" + FileSystemManagerConfig.DEFAULT_RECYCLE_BIN_DIR_NAME); + .isDirectoryContaining("glob:**" + FileSystemManagerConfig.DEFAULT_DATA_DIR_NAME); assertThat(tmpDir).isDirectoryNotContaining("glob:{" + String.join(",", tmpFileNames) + "}"); } diff --git a/platform-sdk/swirlds-common/src/test/java/com/swirlds/common/utility/ClearablesTest.java b/platform-sdk/swirlds-common/src/test/java/com/swirlds/common/utility/ClearablesTest.java deleted file mode 100644 index 0280b4e8316e..000000000000 --- a/platform-sdk/swirlds-common/src/test/java/com/swirlds/common/utility/ClearablesTest.java +++ /dev/null @@ -1,57 +0,0 @@ -/* - * Copyright (C) 2023-2024 Hedera Hashgraph, LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.swirlds.common.utility; - -import com.swirlds.base.utility.Pair; -import com.swirlds.logging.legacy.LogMarker; -import java.util.List; -import java.util.concurrent.atomic.AtomicBoolean; -import java.util.stream.Stream; -import org.junit.jupiter.api.Assertions; -import org.junit.jupiter.api.Test; - -class ClearablesTest { - /** - * Tests whether all clearables have been cleared with a single method call - */ - @Test - void clearAllTest() { - final List list = - Stream.generate(AtomicBoolean::new).limit(10).toList(); - final Clearables clearables = Clearables.of( - list.stream().map(ab -> (Clearable) () -> ab.set(true)).toArray(Clearable[]::new)); - clearables.clear(); - list.forEach(ab -> Assertions.assertTrue(ab.get(), "all booleans should have been set to true")); - } - - /** - * Tests whether all clearables have been cleared with a single method call - */ - @Test - void loggingClearablesTest() { - final List list = - Stream.generate(AtomicBoolean::new).limit(10).toList(); - final Clearable clearables = new LoggingClearables( - LogMarker.STARTUP.getMarker(), - list.stream() - .map(ab -> (Clearable) () -> ab.set(true)) - .map(c -> Pair.of(c, "name")) - .toList()); - clearables.clear(); - list.forEach(ab -> Assertions.assertTrue(ab.get(), "all booleans should have been set to true")); - } -} diff --git a/platform-sdk/swirlds-common/src/test/java/com/swirlds/common/utility/RandotronTests.java b/platform-sdk/swirlds-common/src/test/java/com/swirlds/common/utility/RandotronTests.java index 03f476dd519f..49f068bfa8d6 100644 --- a/platform-sdk/swirlds-common/src/test/java/com/swirlds/common/utility/RandotronTests.java +++ b/platform-sdk/swirlds-common/src/test/java/com/swirlds/common/utility/RandotronTests.java @@ -51,39 +51,39 @@ void setUp() { } @Test - @DisplayName("Test randomString with different seeds") - void randomStringUnique() { - final String randomString1 = random1.randomString(10); - final String randomString2 = random2.randomString(10); - - assertNotEquals(randomString1, randomString2); - assertEquals(10, randomString1.length()); - assertEquals(10, randomString2.length()); + @DisplayName("Test nextString with different seeds") + void nextStringUnique() { + final String nextString1 = random1.nextString(10); + final String nextString2 = random2.nextString(10); + + assertNotEquals(nextString1, nextString2); + assertEquals(10, nextString1.length()); + assertEquals(10, nextString2.length()); } @Test - @DisplayName("Test randomString with same seed") - void randomStringSameSeed() { - final String randomString1 = random1.randomString(10); - final String randomString2 = random1Duplicate.randomString(10); - - assertEquals(randomString1, randomString2); - assertEquals(10, randomString1.length()); - assertEquals(10, randomString2.length()); + @DisplayName("Test nextString with same seed") + void nextStringSameSeed() { + final String nextString1 = random1.nextString(10); + final String nextString2 = random1Duplicate.nextString(10); + + assertEquals(nextString1, nextString2); + assertEquals(10, nextString1.length()); + assertEquals(10, nextString2.length()); } @Test - @DisplayName("Test randomString edge cases") - void randomStringEdgeCases() { - assertEquals("", random1.randomString(0)); - assertThrows(IllegalArgumentException.class, () -> random1.randomString(-1)); + @DisplayName("Test nextString edge cases") + void nextStringEdgeCases() { + assertEquals("", random1.nextString(0)); + assertThrows(IllegalArgumentException.class, () -> random1.nextString(-1)); } @Test - @DisplayName("Test randomIp with different seeds") - void randomIpUnique() { - final String ip1 = random1.randomIp(); - final String ip2 = random2.randomIp(); + @DisplayName("Test nextIp with different seeds") + void nextIpUnique() { + final String ip1 = random1.nextIp(); + final String ip2 = random2.nextIp(); assertNotEquals(ip1, ip2); assertDoesNotThrow(() -> InetAddress.getByName(ip1)); @@ -91,10 +91,10 @@ void randomIpUnique() { } @Test - @DisplayName("Test randomIp with same seed") - void randomIpSameSeed() { - final String ip1 = random1.randomIp(); - final String ip2 = random1Duplicate.randomIp(); + @DisplayName("Test nextIp with same seed") + void nextIpSameSeed() { + final String ip1 = random1.nextIp(); + final String ip2 = random1Duplicate.nextIp(); assertEquals(ip1, ip2); assertDoesNotThrow(() -> InetAddress.getByName(ip1)); @@ -102,10 +102,10 @@ void randomIpSameSeed() { } @Test - @DisplayName("Test bounded randomPositiveLong with different seeds") + @DisplayName("Test bounded nextPositiveLong with different seeds") void randomPositiveBoundedLongUnique() { - final long randomLong1 = random1.randomPositiveLong(10000000); - final long randomLong2 = random2.randomPositiveLong(10000000); + final long randomLong1 = random1.nextPositiveLong(10000000); + final long randomLong2 = random2.nextPositiveLong(10000000); assertNotEquals(randomLong1, randomLong2); assertTrue(randomLong1 < 10000000); @@ -113,68 +113,93 @@ void randomPositiveBoundedLongUnique() { } @Test - @DisplayName("Test bounded randomPositiveLong with same seed") + @DisplayName("Test bounded nextPositiveLong with same seed") void randomPositiveBoundedLongSameSeed() { - final long randomLong1 = random1.randomPositiveLong(10000000); - final long randomLong2 = random1Duplicate.randomPositiveLong(10000000); + final long randomLong1 = random1.nextPositiveLong(10000000); + final long randomLong2 = random1Duplicate.nextPositiveLong(10000000); assertEquals(randomLong1, randomLong2); assertTrue(randomLong1 < 10000000); } @Test - @DisplayName("Test bounded randomPositiveLong edge cases") + @DisplayName("Test bounded nextPositiveLong edge cases") void randomPositiveBoundedLongEdgeCases() { - assertThrows(IllegalArgumentException.class, () -> random1.randomPositiveLong(1)); - assertThrows(IllegalArgumentException.class, () -> random1.randomPositiveLong(0)); - assertThrows(IllegalArgumentException.class, () -> random1.randomPositiveLong(-1)); - assertEquals(1, random1.randomPositiveLong(2)); + assertThrows(IllegalArgumentException.class, () -> random1.nextPositiveLong(1)); + assertThrows(IllegalArgumentException.class, () -> random1.nextPositiveLong(0)); + assertThrows(IllegalArgumentException.class, () -> random1.nextPositiveLong(-1)); + assertEquals(1, random1.nextPositiveLong(2)); } @Test - @DisplayName("Test randomPositiveLong with different seeds") - void randomPositiveLongUnique() { - assertNotEquals(random1.randomPositiveLong(), random2.randomPositiveLong()); + @DisplayName("Test nextPositiveLong with different seeds") + void nextPositiveLongUnique() { + assertNotEquals(random1.nextPositiveLong(), random2.nextPositiveLong()); } @Test - @DisplayName("Test randomPositiveLong with same seed") - void randomPositiveLongSameSeed() { - assertEquals(random1.randomPositiveLong(), random1Duplicate.randomPositiveLong()); + @DisplayName("Test nextPositiveLong with same seed") + void nextPositiveLongSameSeed() { + assertEquals(random1.nextPositiveLong(), random1Duplicate.nextPositiveLong()); } @Test - @DisplayName("Test randomHash with different seeds") - void randomHashUnique() { - final Hash hash1 = random1.randomHash(); - final Hash hash2 = random2.randomHash(); + @DisplayName("Test bounded nextPositiveInt with same seed") + void randomPositiveBoundedIntSameSeed() { + final long randomLong1 = random1.nextPositiveInt(10000000); + final long randomLong2 = random1Duplicate.nextPositiveInt(10000000); + + assertEquals(randomLong1, randomLong2); + assertTrue(randomLong1 < 10000000); + } + + @Test + @DisplayName("Test bounded nextPositiveInt edge cases") + void randomPositiveBoundedIntEdgeCases() { + assertThrows(IllegalArgumentException.class, () -> random1.nextPositiveInt(1)); + assertThrows(IllegalArgumentException.class, () -> random1.nextPositiveInt(0)); + assertThrows(IllegalArgumentException.class, () -> random1.nextPositiveInt(-1)); + assertEquals(1, random1.nextPositiveInt(2)); + } + + @Test + @DisplayName("Test nextPositiveInt with same seed") + void randomPositiveIntSameSeed() { + assertEquals(random1.nextPositiveInt(), random1Duplicate.nextPositiveInt()); + } + + @Test + @DisplayName("Test nextHash with different seeds") + void nextHashUnique() { + final Hash hash1 = random1.nextHash(); + final Hash hash2 = random2.nextHash(); assertNotEquals(hash1, hash2); - assertEquals(48, hash1.getValue().length); - assertEquals(48, hash2.getValue().length); + assertEquals(48, hash1.getBytes().length()); + assertEquals(48, hash2.getBytes().length()); assertEquals(DigestType.SHA_384, hash1.getDigestType()); assertEquals(DigestType.SHA_384, hash2.getDigestType()); } @Test - @DisplayName("Test randomHash with same seed") - void randomHashSameSeed() { - final Hash hash1 = random1.randomHash(); - final Hash hash2 = random1Duplicate.randomHash(); + @DisplayName("Test nextHash with same seed") + void nextHashSameSeed() { + final Hash hash1 = random1.nextHash(); + final Hash hash2 = random1Duplicate.nextHash(); assertEquals(hash1, hash2); - assertEquals(48, hash1.getValue().length); - assertEquals(48, hash2.getValue().length); + assertEquals(48, hash1.getBytes().length()); + assertEquals(48, hash2.getBytes().length()); assertEquals(DigestType.SHA_384, hash1.getDigestType()); assertEquals(DigestType.SHA_384, hash2.getDigestType()); } @Test - @DisplayName("Test randomHashBytes with different seeds") - void randomHashBytesUnique() { - final Bytes hashBytes1 = random1.randomHashBytes(); - final Bytes hashBytes2 = random2.randomHashBytes(); + @DisplayName("Test nextHashBytes with different seeds") + void nextHashBytesUnique() { + final Bytes hashBytes1 = random1.nextHashBytes(); + final Bytes hashBytes2 = random2.nextHashBytes(); assertNotEquals(hashBytes1, hashBytes2); assertEquals(48, hashBytes1.length()); @@ -182,10 +207,10 @@ void randomHashBytesUnique() { } @Test - @DisplayName("Test randomHashBytes with same seed") - void randomHashBytesSameSeed() { - final Bytes hashBytes1 = random1.randomHashBytes(); - final Bytes hashBytes2 = random1Duplicate.randomHashBytes(); + @DisplayName("Test nextHashBytes with same seed") + void nextHashBytesSameSeed() { + final Bytes hashBytes1 = random1.nextHashBytes(); + final Bytes hashBytes2 = random1Duplicate.nextHashBytes(); assertEquals(hashBytes1, hashBytes2); assertEquals(48, hashBytes1.length()); @@ -193,10 +218,10 @@ void randomHashBytesSameSeed() { } @Test - @DisplayName("Test randomSignature with different seeds") - void randomSignatureUnique() { - final Signature signature1 = random1.randomSignature(); - final Signature signature2 = random2.randomSignature(); + @DisplayName("Test nextSignature with different seeds") + void nextSignatureUnique() { + final Signature signature1 = random1.nextSignature(); + final Signature signature2 = random2.nextSignature(); assertNotEquals(signature1, signature2); assertEquals(384, signature1.getSignatureBytes().length); @@ -204,10 +229,10 @@ void randomSignatureUnique() { } @Test - @DisplayName("Test randomSignature with same seed") - void randomSignatureSameSeed() { - final Signature signature1 = random1.randomSignature(); - final Signature signature2 = random1Duplicate.randomSignature(); + @DisplayName("Test nextSignature with same seed") + void nextSignatureSameSeed() { + final Signature signature1 = random1.nextSignature(); + final Signature signature2 = random1Duplicate.nextSignature(); assertEquals(signature1, signature2); assertEquals(384, signature1.getSignatureBytes().length); @@ -215,10 +240,10 @@ void randomSignatureSameSeed() { } @Test - @DisplayName("Test randomSignatureBytes with different seeds") - void randomSignatureBytesUnique() { - final Bytes signatureBytes1 = random1.randomSignatureBytes(); - final Bytes signatureBytes2 = random2.randomSignatureBytes(); + @DisplayName("Test nextSignatureBytes with different seeds") + void nextSignatureBytesUnique() { + final Bytes signatureBytes1 = random1.nextSignatureBytes(); + final Bytes signatureBytes2 = random2.nextSignatureBytes(); assertNotEquals(signatureBytes1, signatureBytes2); assertEquals(384, signatureBytes1.length()); @@ -226,10 +251,10 @@ void randomSignatureBytesUnique() { } @Test - @DisplayName("Test randomSignatureBytes with same seed") - void randomSignatureBytesSameSeed() { - final Bytes signatureBytes1 = random1.randomSignatureBytes(); - final Bytes signatureBytes2 = random1Duplicate.randomSignatureBytes(); + @DisplayName("Test nextSignatureBytes with same seed") + void nextSignatureBytesSameSeed() { + final Bytes signatureBytes1 = random1.nextSignatureBytes(); + final Bytes signatureBytes2 = random1Duplicate.nextSignatureBytes(); assertEquals(signatureBytes1, signatureBytes2); assertEquals(384, signatureBytes1.length()); @@ -237,10 +262,10 @@ void randomSignatureBytesSameSeed() { } @Test - @DisplayName("Test randomByteArray with different seeds") - void randomByteArrayUnique() { - final byte[] randomBytes1 = random1.randomByteArray(10); - final byte[] randomBytes2 = random2.randomByteArray(10); + @DisplayName("Test nextByteArray with different seeds") + void nextByteArray() { + final byte[] randomBytes1 = random1.nextByteArray(10); + final byte[] randomBytes2 = random2.nextByteArray(10); assertFalse(Arrays.equals(randomBytes1, randomBytes2)); @@ -249,34 +274,34 @@ void randomByteArrayUnique() { } @Test - @DisplayName("Test randomByteArray with same seed") - void randomByteArraySameSeed() { - final byte[] randomBytes1 = random1.randomByteArray(10); - final byte[] randomBytes2 = random1Duplicate.randomByteArray(10); + @DisplayName("Test nextByteArray with same seed") + void nextByteArraySameSeed() { + final byte[] randomBytes1 = random1.nextByteArray(10); + final byte[] randomBytes2 = random1Duplicate.nextByteArray(10); - assertArrayEquals(random1.randomByteArray(10), random1Duplicate.randomByteArray(10)); + assertArrayEquals(random1.nextByteArray(10), random1Duplicate.nextByteArray(10)); assertEquals(10, randomBytes1.length); assertEquals(10, randomBytes2.length); } @Test - @DisplayName("Test randomInstant with different seeds") - void randomInstantUnique() { - assertNotEquals(random1.randomInstant(), random2.randomInstant()); + @DisplayName("Test nextInstant with different seeds") + void nextInstantUnique() { + assertNotEquals(random1.nextInstant(), random2.nextInstant()); } @Test - @DisplayName("Test randomInstant with same seed") - void randomInstantSameSeed() { - assertEquals(random1.randomInstant(), random1Duplicate.randomInstant()); + @DisplayName("Test nextInstant with same seed") + void nextInstantSameSeed() { + assertEquals(random1.nextInstant(), random1Duplicate.nextInstant()); } @Test - @DisplayName("Test randomBooleanWithProbability with different seeds") - void randomBooleanWithProbabilityUnique() { + @DisplayName("Test nextBooleanWithProbability with different seeds") + void nextBooleanWithProbabilityUnique() { boolean isDifferent = false; for (int i = 0; i < 100; i++) { - if (random1.randomBooleanWithProbability(0.5) != random2.randomBooleanWithProbability(0.5)) { + if (random1.nextBoolean(0.5) != random2.nextBoolean(0.5)) { isDifferent = true; break; } @@ -286,19 +311,19 @@ void randomBooleanWithProbabilityUnique() { } @Test - @DisplayName("Test randomBooleanWithProbability with same seed") - void randomBooleanWithProbabilitySameSeed() { + @DisplayName("Test nextBooleanWithProbability with same seed") + void nextBooleanWithProbabilitySameSeed() { for (int i = 0; i < 100; i++) { - assertEquals(random1.randomBooleanWithProbability(0.5), random1Duplicate.randomBooleanWithProbability(0.5)); + assertEquals(random1.nextBoolean(0.5), random1Duplicate.nextBoolean(0.5)); } } @Test - @DisplayName("Test randomBooleanWithProbability edge cases") - void randomBooleanWithProbabilityEdgeCases() { - assertThrows(IllegalArgumentException.class, () -> random1.randomBooleanWithProbability(1.1)); - assertThrows(IllegalArgumentException.class, () -> random1.randomBooleanWithProbability(-0.1)); - assertDoesNotThrow(() -> random1.randomBooleanWithProbability(0)); - assertDoesNotThrow(() -> random1.randomBooleanWithProbability(1)); + @DisplayName("Test nextBooleanWithProbability edge cases") + void nextBooleanWithProbabilityEdgeCases() { + assertThrows(IllegalArgumentException.class, () -> random1.nextBoolean(1.1)); + assertThrows(IllegalArgumentException.class, () -> random1.nextBoolean(-0.1)); + assertDoesNotThrow(() -> random1.nextBoolean(0)); + assertDoesNotThrow(() -> random1.nextBoolean(1)); } } diff --git a/platform-sdk/swirlds-common/src/testFixtures/java/com/swirlds/common/test/fixtures/Randotron.java b/platform-sdk/swirlds-common/src/testFixtures/java/com/swirlds/common/test/fixtures/Randotron.java index f1d1b3faaa67..a7209b27e575 100644 --- a/platform-sdk/swirlds-common/src/testFixtures/java/com/swirlds/common/test/fixtures/Randotron.java +++ b/platform-sdk/swirlds-common/src/testFixtures/java/com/swirlds/common/test/fixtures/Randotron.java @@ -37,6 +37,8 @@ public final class Randotron extends Random { * @return a new instance of Randotron */ public static Randotron create() { + final long seed = new Random().nextLong(); + System.out.println("Random seed: " + seed + "L"); return new Randotron(new Random().nextLong()); } @@ -60,7 +62,16 @@ public static Randotron create(final long seed) { private Randotron(final long seed) { super(seed); this.seed = seed; - System.out.println("Random seed: " + seed + "L"); + } + + /** + * Get a copy of this Randotron with the same starting seed. The copy will have the same state as this Randotron at + * the moment it was first created (not the current state!). + * + * @return a copy of this Randotron + */ + public Randotron copyAndReset() { + return new Randotron(seed); } /** @@ -70,7 +81,7 @@ private Randotron(final long seed) { * @return a random string */ @NonNull - public String randomString(final int length) { + public String nextString(final int length) { final int LEFT_LIMIT = 48; // numeral '0' final int RIGHT_LIMIT = 122; // letter 'z' @@ -87,7 +98,7 @@ public String randomString(final int length) { * @return a random IP address */ @NonNull - public String randomIp() { + public String nextIp() { return this.nextInt(256) + "." + this.nextInt(256) + "." + this.nextInt(256) + "." + this.nextInt(256); } @@ -97,7 +108,7 @@ public String randomIp() { * @param maxValue the upper bound, the returned value will be smaller than this * @return the random long */ - public long randomPositiveLong(final long maxValue) { + public long nextPositiveLong(final long maxValue) { return this.longs(1, 1, maxValue).findFirst().orElseThrow(); } @@ -106,8 +117,27 @@ public long randomPositiveLong(final long maxValue) { * * @return the random long */ - public long randomPositiveLong() { - return randomPositiveLong(Long.MAX_VALUE); + public long nextPositiveLong() { + return nextPositiveLong(Long.MAX_VALUE); + } + + /** + * Generates a random positive int that is smaller than the supplied value + * + * @param maxValue the upper bound, the returned value will be smaller than this + * @return the random int + */ + public int nextPositiveInt(final int maxValue) { + return this.ints(1, 1, maxValue).findFirst().orElseThrow(); + } + + /** + * Generates a random positive int + * + * @return the random int + */ + public int nextPositiveInt() { + return nextPositiveInt(Integer.MAX_VALUE); } /** @@ -116,8 +146,8 @@ public long randomPositiveLong() { * @return a random hash */ @NonNull - public Hash randomHash() { - return new Hash(randomByteArray(DigestType.SHA_384.digestLength()), DigestType.SHA_384); + public Hash nextHash() { + return new Hash(nextByteArray(DigestType.SHA_384.digestLength()), DigestType.SHA_384); } /** @@ -126,8 +156,8 @@ public Hash randomHash() { * @return random Bytes */ @NonNull - public Bytes randomHashBytes() { - return Bytes.wrap(randomByteArray(DigestType.SHA_384.digestLength())); + public Bytes nextHashBytes() { + return Bytes.wrap(nextByteArray(DigestType.SHA_384.digestLength())); } /** @@ -136,8 +166,8 @@ public Bytes randomHashBytes() { * @return a random signature */ @NonNull - public Signature randomSignature() { - return new Signature(SignatureType.RSA, randomByteArray(SignatureType.RSA.signatureLength())); + public Signature nextSignature() { + return new Signature(SignatureType.RSA, nextByteArray(SignatureType.RSA.signatureLength())); } /** @@ -146,8 +176,8 @@ public Signature randomSignature() { * @return random signature bytes */ @NonNull - public Bytes randomSignatureBytes() { - return Bytes.wrap(randomByteArray(SignatureType.RSA.signatureLength())); + public Bytes nextSignatureBytes() { + return Bytes.wrap(nextByteArray(SignatureType.RSA.signatureLength())); } /** @@ -157,7 +187,7 @@ public Bytes randomSignatureBytes() { * @return a random byte array */ @NonNull - public byte[] randomByteArray(final int size) { + public byte[] nextByteArray(final int size) { final byte[] bytes = new byte[size]; this.nextBytes(bytes); return bytes; @@ -169,8 +199,8 @@ public byte[] randomByteArray(final int size) { * @return a random instant */ @NonNull - public Instant randomInstant() { - return Instant.ofEpochMilli(randomPositiveLong(2000000000000L)); + public Instant nextInstant() { + return Instant.ofEpochMilli(nextPositiveLong(2000000000000L)); } /** @@ -179,7 +209,7 @@ public Instant randomInstant() { * @param trueProbability the probability of the boolean being true * @return a random boolean */ - public boolean randomBooleanWithProbability(final double trueProbability) { + public boolean nextBoolean(final double trueProbability) { if (trueProbability < 0 || trueProbability > 1) { throw new IllegalArgumentException("Probability must be between 0 and 1"); } diff --git a/platform-sdk/swirlds-common/src/testFixtures/java/com/swirlds/common/test/fixtures/crypto/MessageDigestPool.java b/platform-sdk/swirlds-common/src/testFixtures/java/com/swirlds/common/test/fixtures/crypto/MessageDigestPool.java index 09487cb65ece..451b1ba21d29 100644 --- a/platform-sdk/swirlds-common/src/testFixtures/java/com/swirlds/common/test/fixtures/crypto/MessageDigestPool.java +++ b/platform-sdk/swirlds-common/src/testFixtures/java/com/swirlds/common/test/fixtures/crypto/MessageDigestPool.java @@ -78,7 +78,7 @@ public boolean isValid(final Message message) { KnownDigest kd = messages.get(index); - return (kd != null && kd.isValid(message.getHash().getValue())); + return (kd != null && kd.isValid(message.getHash().copyToByteArray())); } public boolean isValid(final Message message, final byte[] hash) { diff --git a/platform-sdk/swirlds-common/src/testFixtures/java/com/swirlds/common/test/fixtures/platform/TestPlatformContextBuilder.java b/platform-sdk/swirlds-common/src/testFixtures/java/com/swirlds/common/test/fixtures/platform/TestPlatformContextBuilder.java index c65eefefb587..9bf33ce98838 100644 --- a/platform-sdk/swirlds-common/src/testFixtures/java/com/swirlds/common/test/fixtures/platform/TestPlatformContextBuilder.java +++ b/platform-sdk/swirlds-common/src/testFixtures/java/com/swirlds/common/test/fixtures/platform/TestPlatformContextBuilder.java @@ -17,13 +17,14 @@ package com.swirlds.common.test.fixtures.platform; import static com.swirlds.common.io.utility.FileUtils.rethrowIO; +import static org.junit.jupiter.api.Assertions.fail; import com.swirlds.base.time.Time; +import com.swirlds.common.concurrent.ExecutorFactory; import com.swirlds.common.context.PlatformContext; import com.swirlds.common.crypto.Cryptography; import com.swirlds.common.crypto.CryptographyHolder; import com.swirlds.common.io.filesystem.FileSystemManager; -import com.swirlds.common.io.filesystem.FileSystemManagerFactory; import com.swirlds.common.io.utility.RecycleBin; import com.swirlds.common.metrics.noop.NoOpMetrics; import com.swirlds.common.test.fixtures.TestFileSystemManager; @@ -33,6 +34,7 @@ import com.swirlds.metrics.api.Metrics; import edu.umd.cs.findbugs.annotations.NonNull; import edu.umd.cs.findbugs.annotations.Nullable; +import java.lang.Thread.UncaughtExceptionHandler; import java.nio.file.Files; import java.nio.file.Path; import java.util.Objects; @@ -51,7 +53,6 @@ public final class TestPlatformContextBuilder { private Metrics metrics; private Cryptography cryptography; private Time time = Time.getCurrent(); - private boolean buildFileSystemManagerFromConfig = false; private BiFunction fileSystemManagerProvider; private TestPlatformContextBuilder() {} @@ -111,16 +112,9 @@ public TestPlatformContextBuilder withTime(@NonNull final Time time) { return this; } - @NonNull - public TestPlatformContextBuilder withFileSystemManagerFromConfig() { - this.buildFileSystemManagerFromConfig = true; - return this; - } - @NonNull public TestPlatformContextBuilder withFileSystemManager( @NonNull final BiFunction fileSystemManagerProvider) { - this.buildFileSystemManagerFromConfig = false; this.fileSystemManagerProvider = fileSystemManagerProvider; return this; } @@ -128,7 +122,6 @@ public TestPlatformContextBuilder withFileSystemManager( @NonNull public TestPlatformContextBuilder withTestFileSystemManager( @NonNull final Path rootPath, @NonNull final BiFunction recycleBinProvider) { - this.buildFileSystemManagerFromConfig = false; this.fileSystemManagerProvider = (m, t) -> { TestFileSystemManager fsm = new TestFileSystemManager(rootPath); fsm.setBin(recycleBinProvider.apply(m, t)); @@ -137,29 +130,6 @@ public TestPlatformContextBuilder withTestFileSystemManager( return this; } - @NonNull - public TestPlatformContextBuilder withTestFileSystemManager(@NonNull final Path rootPath) { - this.buildFileSystemManagerFromConfig = false; - this.fileSystemManagerProvider = (m, t) -> { - TestFileSystemManager fsm = new TestFileSystemManager(rootPath); - fsm.setBin(TestRecycleBin.getInstance()); - return fsm; - }; - return this; - } - - @NonNull - public TestPlatformContextBuilder withTestFileSystemManager( - @NonNull final Path rootPath, @NonNull final RecycleBin recycleBin) { - this.buildFileSystemManagerFromConfig = false; - this.fileSystemManagerProvider = (m, t) -> { - TestFileSystemManager fsm = new TestFileSystemManager(rootPath); - fsm.setBin(recycleBin); - return fsm; - }; - return this; - } - /** * Returns a new {@link PlatformContext} based on this builder * @@ -177,14 +147,19 @@ public PlatformContext build() { } final FileSystemManager fileSystemManager; - if (buildFileSystemManagerFromConfig) { - fileSystemManager = FileSystemManagerFactory.getInstance().createFileSystemManager(configuration, metrics); - } else if (fileSystemManagerProvider != null) { + if (fileSystemManagerProvider != null) { fileSystemManager = fileSystemManagerProvider.apply(metrics, time); } else { - fileSystemManager = getDefaultManager(); + fileSystemManager = getTestFileSystemManager(); } + final ExecutorFactory executorFactory = ExecutorFactory.create("test", new UncaughtExceptionHandler() { + @Override + public void uncaughtException(Thread t, Throwable e) { + fail("Uncaught exception in thread " + t.getName(), e); + } + }); + return new PlatformContext() { @NonNull @Override @@ -215,10 +190,15 @@ public Time getTime() { public FileSystemManager getFileSystemManager() { return fileSystemManager; } + + @Override + public ExecutorFactory getExecutorFactory() { + return executorFactory; + } }; } - private static TestFileSystemManager getDefaultManager() { + private static TestFileSystemManager getTestFileSystemManager() { Path defaultRootLocation = rethrowIO(() -> Files.createTempDirectory("testRootDir")); TestFileSystemManager fsm = new TestFileSystemManager(defaultRootLocation); fsm.setBin(TestRecycleBin.getInstance()); diff --git a/platform-sdk/swirlds-config-api/build.gradle.kts b/platform-sdk/swirlds-config-api/build.gradle.kts index 47a52cc77ead..4ce38cfa290b 100644 --- a/platform-sdk/swirlds-config-api/build.gradle.kts +++ b/platform-sdk/swirlds-config-api/build.gradle.kts @@ -15,7 +15,7 @@ */ plugins { - id("com.hedera.hashgraph.sdk.conventions") - id("com.hedera.hashgraph.platform-maven-publish") - id("com.hedera.hashgraph.java-test-fixtures") + id("com.hedera.gradle.platform") + id("com.hedera.gradle.platform-publish") + id("com.hedera.gradle.java-test-fixtures") } diff --git a/platform-sdk/swirlds-config-api/src/main/java/com/swirlds/config/api/intern/ConfigurationProvider.java b/platform-sdk/swirlds-config-api/src/main/java/com/swirlds/config/api/intern/ConfigurationProvider.java index 2eb6bd1aeb4e..f893bab996da 100644 --- a/platform-sdk/swirlds-config-api/src/main/java/com/swirlds/config/api/intern/ConfigurationProvider.java +++ b/platform-sdk/swirlds-config-api/src/main/java/com/swirlds/config/api/intern/ConfigurationProvider.java @@ -28,7 +28,9 @@ */ public final class ConfigurationProvider { - private static final ConfigurationProvider INSTANCE = new ConfigurationProvider(); + private static final class InstanceHolder { + private static final ConfigurationProvider INSTANCE = new ConfigurationProvider(); + } private final ConfigurationBuilderFactory factory; @@ -63,6 +65,6 @@ public ConfigurationBuilder createBuilder() { * @return the singleton instance of this class */ public static ConfigurationProvider getInstance() { - return INSTANCE; + return InstanceHolder.INSTANCE; } } diff --git a/platform-sdk/swirlds-config-extensions/build.gradle.kts b/platform-sdk/swirlds-config-extensions/build.gradle.kts index 9e59fdc8bbc7..05e16e0d596f 100644 --- a/platform-sdk/swirlds-config-extensions/build.gradle.kts +++ b/platform-sdk/swirlds-config-extensions/build.gradle.kts @@ -15,9 +15,9 @@ */ plugins { - id("com.hedera.hashgraph.sdk.conventions") - id("com.hedera.hashgraph.platform-maven-publish") - id("com.hedera.hashgraph.java-test-fixtures") + id("com.hedera.gradle.platform") + id("com.hedera.gradle.platform-publish") + id("com.hedera.gradle.java-test-fixtures") } testModuleInfo { diff --git a/platform-sdk/swirlds-config-impl/build.gradle.kts b/platform-sdk/swirlds-config-impl/build.gradle.kts index 4ec44abbee16..b62f14ef6f0e 100644 --- a/platform-sdk/swirlds-config-impl/build.gradle.kts +++ b/platform-sdk/swirlds-config-impl/build.gradle.kts @@ -15,9 +15,9 @@ */ plugins { - id("com.hedera.hashgraph.sdk.conventions") - id("com.hedera.hashgraph.platform-maven-publish") - id("com.hedera.hashgraph.benchmark-conventions") + id("com.hedera.gradle.platform") + id("com.hedera.gradle.platform-publish") + id("com.hedera.gradle.benchmark") } mainModuleInfo { annotationProcessor("com.google.auto.service.processor") } diff --git a/platform-sdk/swirlds-config-processor/build.gradle.kts b/platform-sdk/swirlds-config-processor/build.gradle.kts index 79fc7e804b6e..19b3d4822b37 100644 --- a/platform-sdk/swirlds-config-processor/build.gradle.kts +++ b/platform-sdk/swirlds-config-processor/build.gradle.kts @@ -15,9 +15,8 @@ */ plugins { - id("com.hedera.hashgraph.sdk.conventions") - id("com.hedera.hashgraph.platform-maven-publish") - // antlr + id("com.hedera.gradle.platform") + id("com.hedera.gradle.platform-publish") } mainModuleInfo { annotationProcessor("com.google.auto.service.processor") } diff --git a/platform-sdk/swirlds-config-processor/src/main/java/com/swirlds/config/processor/ConfigDataAnnotationProcessor.java b/platform-sdk/swirlds-config-processor/src/main/java/com/swirlds/config/processor/ConfigDataAnnotationProcessor.java index 67f4cea37830..60a5fdaebffe 100644 --- a/platform-sdk/swirlds-config-processor/src/main/java/com/swirlds/config/processor/ConfigDataAnnotationProcessor.java +++ b/platform-sdk/swirlds-config-processor/src/main/java/com/swirlds/config/processor/ConfigDataAnnotationProcessor.java @@ -78,7 +78,7 @@ public boolean process( .forEach(typeElement -> handleTypeElement(typeElement, configDocumentationFile)); return true; } catch (final Exception e) { - log("Error while processing annotations: " + e.getMessage()); + log(Kind.ERROR, "Error while processing annotations: " + e.getMessage()); e.printStackTrace(); return false; } @@ -104,12 +104,12 @@ private void handleTypeElement( final JavaFileObject constantsSourceFile = getConstantSourceFile(packageName, simpleClassName, typeElement); log("generating config constants file: " + constantsSourceFile.getName()); - ConstantClassFactory.doWork(recordDefinitions.get(0), constantsSourceFile); + ConstantClassFactory.doWork(recordDefinitions.getFirst(), constantsSourceFile); log("generating config doc file: " + configDocumentationFile.getFileName()); - DocumentationFactory.doWork(recordDefinitions.get(0), configDocumentationFile); + DocumentationFactory.doWork(recordDefinitions.getFirst(), configDocumentationFile); } - } catch (final IOException e) { - throw new RuntimeException("Error while handling " + typeElement, e); + } catch (final Exception e) { + throw new RuntimeException("Error handling " + typeElement, e); } } @@ -135,11 +135,15 @@ private FileObject getSource(@NonNull final String fileName, @NonNull final Stri return processingEnv.getFiler().getResource(StandardLocation.SOURCE_PATH, packageName, fileName); } - protected void log(@NonNull final String message) { + private void log(@NonNull final String message) { + log(Kind.OTHER, message); + } + + private void log(@NonNull final Kind kind, @NonNull final String message) { Objects.requireNonNull(message, "message must not be null"); processingEnv .getMessager() - .printMessage(Kind.OTHER, ConfigDataAnnotationProcessor.class.getSimpleName() + ": " + message); + .printMessage(kind, ConfigDataAnnotationProcessor.class.getSimpleName() + ": " + message); } } diff --git a/platform-sdk/swirlds-config-processor/src/main/java/com/swirlds/config/processor/ConstantClassFactory.java b/platform-sdk/swirlds-config-processor/src/main/java/com/swirlds/config/processor/ConstantClassFactory.java index 3a789f94105e..604636d1400d 100644 --- a/platform-sdk/swirlds-config-processor/src/main/java/com/swirlds/config/processor/ConstantClassFactory.java +++ b/platform-sdk/swirlds-config-processor/src/main/java/com/swirlds/config/processor/ConstantClassFactory.java @@ -19,6 +19,7 @@ import com.squareup.javapoet.FieldSpec; import com.squareup.javapoet.JavaFile; import com.squareup.javapoet.TypeSpec; +import com.swirlds.config.api.ConfigProperty; import edu.umd.cs.findbugs.annotations.NonNull; import java.io.IOException; import java.io.Writer; @@ -34,6 +35,12 @@ */ public final class ConstantClassFactory { + /** + * Property name of: + * {@link ConfigProperty#defaultValue()} + */ + public static final String DEFAULT_VALUE = "defaultValue"; + /** * private constructor to prevent instantiation */ @@ -71,17 +78,29 @@ public static void doWork( configDataRecordDefinition.propertyDefinitions().forEach(propertyDefinition -> { final String name = toConstantName( propertyDefinition.name().replace(configDataRecordDefinition.configDataName() + ".", "")); - FieldSpec fieldSpec = FieldSpec.builder( - String.class, name, Modifier.PUBLIC, Modifier.STATIC, Modifier.FINAL) - .initializer("$S", propertyDefinition.name()) - .addJavadoc( - "Name of the {@link $L#$L} property\n@see $L#$L\n", - originalRecordClassName, - propertyDefinition.fieldName(), - originalRecordClassName, - propertyDefinition.fieldName()) - .build(); - constantsClassBuilder.addField(fieldSpec); + + try { + + FieldSpec fieldSpec = FieldSpec.builder( + String.class, name, Modifier.PUBLIC, Modifier.STATIC, Modifier.FINAL) + .initializer("$S", propertyDefinition.name()) + .addJavadoc( + "Name of the {@link $L#$L} property\n@see $L#$L\n", + originalRecordClassName, + propertyDefinition.fieldName(), + originalRecordClassName, + propertyDefinition.fieldName()) + .build(); + constantsClassBuilder.addField(fieldSpec); + } catch (Exception e) { + throw new IllegalArgumentException( + "Error processing record:" + + configDataRecordDefinition.simpleClassName() + " field:" + + propertyDefinition.fieldName() + " annotation value:\"" + propertyDefinition.name() + + "\" cannot be used as a valid constant name. Check if should be a " + DEFAULT_VALUE + + " instead.", + e); + } }); TypeSpec constantsClass = constantsClassBuilder.build(); diff --git a/platform-sdk/swirlds-config-processor/src/main/java/com/swirlds/config/processor/antlr/AntlrConfigRecordParser.java b/platform-sdk/swirlds-config-processor/src/main/java/com/swirlds/config/processor/antlr/AntlrConfigRecordParser.java index e8ed120f30bb..d6ec2de3afcc 100644 --- a/platform-sdk/swirlds-config-processor/src/main/java/com/swirlds/config/processor/antlr/AntlrConfigRecordParser.java +++ b/platform-sdk/swirlds-config-processor/src/main/java/com/swirlds/config/processor/antlr/AntlrConfigRecordParser.java @@ -25,94 +25,111 @@ import com.swirlds.config.processor.antlr.generated.JavaParser.RecordComponentContext; import com.swirlds.config.processor.antlr.generated.JavaParser.RecordDeclarationContext; import edu.umd.cs.findbugs.annotations.NonNull; -import java.io.IOException; +import java.lang.annotation.Annotation; import java.util.List; import java.util.Map; import java.util.Optional; import java.util.Set; import java.util.stream.Collectors; +import org.antlr.v4.runtime.RuleContext; +import org.antlr.v4.runtime.tree.ParseTree; public final class AntlrConfigRecordParser { - private static boolean isAnnotatedWithConfigData( - @NonNull final RecordDeclarationContext ctx, @NonNull String packageName, @NonNull List imports) { + /** + * Property name for: + * {@link ConfigProperty#defaultValue()} + */ + private static final String DEFAULT_VALUE = "defaultValue"; + /** + * Property name for: + * {@link ConfigProperty#value()}} + */ + private static final String VALUE = "value"; + + private static boolean isAnnotatedWith( + @NonNull final RecordDeclarationContext ctx, + @NonNull String packageName, + @NonNull List imports, + @NonNull final Class annotation) { final List allAnnotations = AntlrUtils.getAllAnnotations(ctx); - return AntlrUtils.findAnnotationOfType(ConfigData.class, allAnnotations, packageName, imports) + return AntlrUtils.findAnnotationOfType(annotation, allAnnotations, packageName, imports) .isPresent(); } @NonNull - private static Optional getConfigDataAnnotation( - @NonNull final RecordDeclarationContext ctx, + private static Optional getAnnotation( + @NonNull final List annotations, @NonNull final String packageName, - @NonNull final List imports) { - final List annotations = AntlrUtils.getAllAnnotations(ctx); - return AntlrUtils.findAnnotationOfType(ConfigData.class, annotations, packageName, imports); + @NonNull final List imports, + @NonNull final Class annotation) { + return AntlrUtils.findAnnotationOfType(annotation, annotations, packageName, imports); } @NonNull - private static String getConfigDataAnnotationValue( + private static String getAnnotationValue( @NonNull final RecordDeclarationContext ctx, @NonNull final String packageName, - @NonNull final List imports) { - return getConfigDataAnnotation(ctx, packageName, imports) - .map(annotationContext -> annotationContext.elementValue()) - .map(elementValueContext -> elementValueContext.getText()) + @NonNull final List imports, + @NonNull final Class annotation) { + final List annotations = AntlrUtils.getAllAnnotations(ctx); + return getAnnotation(annotations, packageName, imports, annotation) + .map(AnnotationContext::elementValue) + .map(RuleContext::getText) .map(text -> text.substring(1, text.length() - 1)) // remove quotes .orElse(""); } @NonNull - private static Optional getConfigPropertyAnnotation( + private static String getAnnotationPropertyOrElse( @NonNull final RecordComponentContext ctx, @NonNull final String packageName, - @NonNull final List imports) { - final List annotations = AntlrUtils.getAllAnnotations(ctx); - return AntlrUtils.findAnnotationOfType(ConfigProperty.class, annotations, packageName, imports); - } - - @NonNull - private static String getConfigPropertyAnnotationDefaultValue( - @NonNull final RecordComponentContext ctx, - @NonNull final String packageName, - @NonNull final List imports) { - return getConfigPropertyAnnotation(ctx, packageName, imports) - .flatMap(annotationContext -> AntlrUtils.getAnnotationValue(annotationContext, "defaultValue")) - .orElse(ConfigProperty.UNDEFINED_DEFAULT_VALUE); - } - - @NonNull - private static Optional getConfigPropertyAnnotationName( - @NonNull final RecordComponentContext ctx, - @NonNull final String packageName, - @NonNull final List imports) { - return getConfigPropertyAnnotation(ctx, packageName, imports) - .flatMap(annotationContext -> AntlrUtils.getAnnotationValue(annotationContext)); + @NonNull final List imports, + @NonNull final Class annotation, + @NonNull final String property, + @NonNull final String orElseValue) { + final List allAnnotations = AntlrUtils.getAllAnnotations(ctx); + return getAnnotation(allAnnotations, packageName, imports, annotation) + .flatMap(annotationContext -> AntlrUtils.getAnnotationValue(annotationContext, property)) + .orElse(orElseValue); } @NonNull - private static ConfigDataPropertyDefinition createPropertyDefinition( + private static ConfigDataPropertyDefinition createDefinitionFromConfigProperty( @NonNull final RecordComponentContext ctx, @NonNull final String configPropertyNamePrefix, @NonNull final String packageName, @NonNull final List imports, @NonNull final Map javadocParams) { final String componentName = ctx.identifier().getText(); - final String configPropertyNameSuffix = - getConfigPropertyAnnotationName(ctx, packageName, imports).orElse(componentName); - final String name = createPropertyName(configPropertyNamePrefix, configPropertyNameSuffix); - final String defaultValue = getConfigPropertyAnnotationDefaultValue(ctx, packageName, imports); - final String type = Optional.ofNullable(ctx.typeType().classOrInterfaceType()) - .map(c -> c.getText()) - .map(typeText -> imports.stream() - .filter(importText -> importText.endsWith(typeText)) - .findAny() - .orElse(typeText)) - .map(typeText -> getTypeForJavaLang(typeText)) - .orElseGet(() -> ctx.typeType().primitiveType().getText()); - final String description = - Optional.ofNullable(javadocParams.get(componentName)).orElse(""); - return new ConfigDataPropertyDefinition(componentName, name, type, defaultValue, description); + String name = "not-yet-known"; + try { + final String configPropertyNameSuffix = + getAnnotationPropertyOrElse(ctx, packageName, imports, ConfigProperty.class, VALUE, componentName); + name = createPropertyName(configPropertyNamePrefix, configPropertyNameSuffix); + final String defaultValue = getAnnotationPropertyOrElse( + ctx, + packageName, + imports, + ConfigProperty.class, + DEFAULT_VALUE, + ConfigProperty.UNDEFINED_DEFAULT_VALUE); + final String type = Optional.ofNullable(ctx.typeType().classOrInterfaceType()) + .map(RuleContext::getText) + .map(typeText -> imports.stream() + .filter(importText -> importText.endsWith(typeText)) + .findAny() + .orElse(typeText)) + .map(AntlrConfigRecordParser::getTypeForJavaLang) + .orElseGet(() -> ctx.typeType().primitiveType().getText()); + final String description = + Optional.ofNullable(javadocParams.get(componentName)).orElse(""); + + return new ConfigDataPropertyDefinition(componentName, name, type, defaultValue, description); + } catch (Exception e) { + throw new IllegalArgumentException(ConfigProperty.class.getTypeName() + " is not correctly defined for " + + componentName + " property"); + } } @NonNull @@ -139,7 +156,7 @@ private static List createDefinitions( final String packageName = AntlrUtils.getPackage(unitContext); final List imports = AntlrUtils.getImports(unitContext); return AntlrUtils.getRecordDeclarationContext(unitContext).stream() - .filter(c -> isAnnotatedWithConfigData(c, packageName, imports)) + .filter(c -> isAnnotatedWith(c, packageName, imports, ConfigData.class)) .map(recordContext -> createDefinition(unitContext, recordContext, packageName, imports)) .collect(Collectors.toList()); } @@ -151,22 +168,29 @@ private static ConfigDataRecordDefinition createDefinition( @NonNull final String packageName, @NonNull final List imports) { final String recordName = recordContext.identifier().getText(); - final String configPropertyNamePrefix = getConfigDataAnnotationValue(recordContext, packageName, imports); - final Map javadocParams = unitContext.children.stream() - .filter(c -> AntlrUtils.isJavaDocNode(c)) - .map(c -> c.getText()) - .map(t -> AntlrUtils.getJavaDocParams(t)) - .reduce((m1, m2) -> { - m1.putAll(m2); - return m1; - }) - .orElse(Map.of()); - final Set propertyDefinitions = - recordContext.recordHeader().recordComponentList().recordComponent().stream() - .map(c -> createPropertyDefinition( - c, configPropertyNamePrefix, packageName, imports, javadocParams)) - .collect(Collectors.toSet()); - return new ConfigDataRecordDefinition(packageName, recordName, configPropertyNamePrefix, propertyDefinitions); + + try { + final String configPropertyNamePrefix = + getAnnotationValue(recordContext, packageName, imports, ConfigData.class); + final Map javadocParams = unitContext.children.stream() + .filter(AntlrUtils::isJavaDocNode) + .map(ParseTree::getText) + .map(AntlrUtils::getJavaDocParams) + .reduce((m1, m2) -> { + m1.putAll(m2); + return m1; + }) + .orElse(Map.of()); + final Set propertyDefinitions = + recordContext.recordHeader().recordComponentList().recordComponent().stream() + .map(c -> createDefinitionFromConfigProperty( + c, configPropertyNamePrefix, packageName, imports, javadocParams)) + .collect(Collectors.toSet()); + return new ConfigDataRecordDefinition( + packageName, recordName, configPropertyNamePrefix, propertyDefinitions); + } catch (Exception e) { + throw new IllegalArgumentException("Could not process " + packageName + "." + recordName, e); + } } /** @@ -175,7 +199,7 @@ private static ConfigDataRecordDefinition createDefinition( * @param fileContent the content of the Java source file */ @NonNull - public static List parse(@NonNull final String fileContent) throws IOException { + public static List parse(@NonNull final String fileContent) { final CompilationUnitContext parsedContext = AntlrUtils.parse(fileContent); return createDefinitions(parsedContext); } diff --git a/platform-sdk/swirlds-config-processor/src/main/java/com/swirlds/config/processor/antlr/AntlrUtils.java b/platform-sdk/swirlds-config-processor/src/main/java/com/swirlds/config/processor/antlr/AntlrUtils.java index 276f483dd762..f51d40dffc41 100644 --- a/platform-sdk/swirlds-config-processor/src/main/java/com/swirlds/config/processor/antlr/AntlrUtils.java +++ b/platform-sdk/swirlds-config-processor/src/main/java/com/swirlds/config/processor/antlr/AntlrUtils.java @@ -23,6 +23,7 @@ import com.swirlds.config.processor.antlr.generated.JavaParser.ClassOrInterfaceModifierContext; import com.swirlds.config.processor.antlr.generated.JavaParser.CompilationUnitContext; import com.swirlds.config.processor.antlr.generated.JavaParser.ElementValueContext; +import com.swirlds.config.processor.antlr.generated.JavaParser.ImportDeclarationContext; import com.swirlds.config.processor.antlr.generated.JavaParser.RecordComponentContext; import com.swirlds.config.processor.antlr.generated.JavaParser.RecordDeclarationContext; import com.swirlds.config.processor.antlr.generated.JavaParser.TypeDeclarationContext; @@ -33,7 +34,6 @@ import com.swirlds.config.processor.antlr.generated.JavadocParser.DocumentationContext; import com.swirlds.config.processor.antlr.generated.JavadocParser.TagSectionContext; import edu.umd.cs.findbugs.annotations.NonNull; -import java.io.IOException; import java.lang.annotation.Annotation; import java.util.Arrays; import java.util.Collections; @@ -47,6 +47,7 @@ import org.antlr.v4.runtime.CommonTokenStream; import org.antlr.v4.runtime.Lexer; import org.antlr.v4.runtime.ParserRuleContext; +import org.antlr.v4.runtime.RuleContext; import org.antlr.v4.runtime.TokenStream; import org.antlr.v4.runtime.tree.ParseTree; import org.antlr.v4.runtime.tree.TerminalNode; @@ -57,6 +58,7 @@ public final class AntlrUtils { public static final String JAVADOC_PARAM = "param"; + public static final String ANNOTATION_VALUE_PROPERTY_NAME = "value"; private AntlrUtils() {} @@ -164,8 +166,8 @@ public static CompilationUnitContext getCompilationUnit(@NonNull final ParserRul public static List getImports(@NonNull final ParserRuleContext ctx) { final CompilationUnitContext compilationUnitContext = getCompilationUnit(ctx); return compilationUnitContext.importDeclaration().stream() - .map(context -> context.qualifiedName()) - .map(name -> name.getText()) + .map(ImportDeclarationContext::qualifiedName) + .map(RuleContext::getText) .collect(Collectors.toList()); } @@ -191,6 +193,20 @@ public static String getPackage(@NonNull final ParserRuleContext ctx) { @NonNull public static Optional getAnnotationValue( @NonNull final AnnotationContext annotationContext, @NonNull final String identifier) { + if (ANNOTATION_VALUE_PROPERTY_NAME.equals(identifier)) { + final ElementValueContext elementValueContext = annotationContext.elementValue(); + if (elementValueContext != null) { + return Optional.of(elementValueContext.getText()).map(text -> { + if (text.startsWith("\"") && text.endsWith("\"")) { + return text.substring(1, text.length() - 1); + } + return text; + }); + } + } + if (annotationContext.elementValuePairs() == null) { + return Optional.empty(); + } return annotationContext.elementValuePairs().elementValuePair().stream() .filter(p -> Objects.equals(p.identifier().getText(), identifier)) .map(p -> p.elementValue().getText()) @@ -205,28 +221,11 @@ public static Optional getAnnotationValue( public static boolean isJavaDocNode(@NonNull final ParseTree node) { if (node instanceof TerminalNode terminalNode) { - if (terminalNode.getSymbol().getType() == JavaParser.JAVADOC_COMMENT) { - return true; - } + return terminalNode.getSymbol().getType() == JavaParser.JAVADOC_COMMENT; } return false; } - /** - * Returns the value of an annotation {@code value} attribute - * - * @param annotationContext the annotation context - * @return the value of the {@code value} attribute - */ - @NonNull - public static Optional getAnnotationValue(@NonNull final AnnotationContext annotationContext) { - final ElementValueContext elementValueContext = annotationContext.elementValue(); - if (elementValueContext != null) { - return Optional.of(elementValueContext.getText()); - } - return getAnnotationValue(annotationContext, "value"); - } - public static List getRecordDeclarationContext( @NonNull final CompilationUnitContext compilationUnitContext) { return compilationUnitContext.children.stream() @@ -263,10 +262,10 @@ public static Map getJavaDocParams(@NonNull String rawDocContent .filter(c -> c instanceof TagSectionContext) .map(c -> (TagSectionContext) c) .flatMap(context -> context.children.stream()) - .filter(c -> c instanceof BlockTagContext btc) + .filter(c -> c instanceof BlockTagContext) .map(c -> (BlockTagContext) c) .filter(c -> Objects.equals(c.blockTagName().NAME().getText(), JAVADOC_PARAM)) - .map(c -> extractFullText(c)) + .map(AntlrUtils::extractFullText) .filter(fullText -> !fullText.isBlank()) .filter(fullText -> fullText.contains(" ")) .forEach(fullText -> { @@ -280,8 +279,8 @@ public static Map getJavaDocParams(@NonNull String rawDocContent private static String extractFullText(@NonNull final BlockTagContext c) { final String[] split = c.getText().trim().split("\n \\*"); - final String result = Arrays.asList(split).stream() - .map(s -> s.trim()) + final String result = Arrays.stream(split) + .map(String::trim) .filter(s -> !s.isBlank()) .reduce((a, b) -> a + " " + b) .orElse(""); @@ -297,9 +296,8 @@ private static String extractFullText(@NonNull final BlockTagContext c) { * * @param fileContent the file content to parse * @return the {@link ConfigDataRecordDefinition} object - * @throws IOException if an I/O error occurs */ - public static CompilationUnitContext parse(@NonNull final String fileContent) throws IOException { + public static CompilationUnitContext parse(@NonNull final String fileContent) { final Lexer lexer = new JavaLexer(CharStreams.fromString(fileContent)); final TokenStream tokens = new CommonTokenStream(lexer); final JavaParser parser = new JavaParser(tokens); diff --git a/platform-sdk/swirlds-fchashmap/build.gradle.kts b/platform-sdk/swirlds-fchashmap/build.gradle.kts index 003674d09045..2ea867132dcc 100644 --- a/platform-sdk/swirlds-fchashmap/build.gradle.kts +++ b/platform-sdk/swirlds-fchashmap/build.gradle.kts @@ -15,8 +15,8 @@ */ plugins { - id("com.hedera.hashgraph.sdk.conventions") - id("com.hedera.hashgraph.platform-maven-publish") + id("com.hedera.gradle.platform") + id("com.hedera.gradle.platform-publish") } mainModuleInfo { annotationProcessor("com.swirlds.config.processor") } diff --git a/platform-sdk/swirlds-fcqueue/build.gradle.kts b/platform-sdk/swirlds-fcqueue/build.gradle.kts index 23c3b963ce18..e1675a00ef13 100644 --- a/platform-sdk/swirlds-fcqueue/build.gradle.kts +++ b/platform-sdk/swirlds-fcqueue/build.gradle.kts @@ -15,11 +15,12 @@ */ plugins { - id("com.hedera.hashgraph.sdk.conventions") - id("com.hedera.hashgraph.platform-maven-publish") + id("com.hedera.gradle.platform") + id("com.hedera.gradle.platform-publish") } testModuleInfo { + requires("com.hedera.pbj.runtime") requires("com.swirlds.base") requires("com.swirlds.common.test.fixtures") requires("com.swirlds.config.api") diff --git a/platform-sdk/swirlds-fcqueue/src/main/java/com/swirlds/fcqueue/FCQueue.java b/platform-sdk/swirlds-fcqueue/src/main/java/com/swirlds/fcqueue/FCQueue.java index 3eaf9936d33d..692cdc781762 100644 --- a/platform-sdk/swirlds-fcqueue/src/main/java/com/swirlds/fcqueue/FCQueue.java +++ b/platform-sdk/swirlds-fcqueue/src/main/java/com/swirlds/fcqueue/FCQueue.java @@ -88,8 +88,10 @@ private static class ClassVersion { private static final long HASH_RADIX = 3; + /** The bytes of a NULL_HASH */ + private static final byte[] NULL_HASH_BYTES = new byte[DIGEST_TYPE.digestLength()]; /** A hash value representing a null element or a destroyed queue */ - private static final ImmutableHash NULL_HASH = new ImmutableHash(new byte[DIGEST_TYPE.digestLength()]); + private static final ImmutableHash NULL_HASH = new ImmutableHash(NULL_HASH_BYTES); /** the number of elements in this queue */ private int size; @@ -801,7 +803,7 @@ private void deserializeV3(final SerializableDataInputStream dis) throws IOExcep byte[] getHash(final E element) { // Handle cases where list methods return null if the list is empty if (element == null) { - return NULL_HASH.getValue(); + return NULL_HASH_BYTES; } final Cryptography crypto = CryptographyHolder.get(); // return a hash of a hash, in order to make state proofs smaller in the future diff --git a/platform-sdk/swirlds-fcqueue/src/test/java/com/swirlds/fcqueue/FCQueueTest.java b/platform-sdk/swirlds-fcqueue/src/test/java/com/swirlds/fcqueue/FCQueueTest.java index af2bc154797e..75b3ea8fd5cc 100644 --- a/platform-sdk/swirlds-fcqueue/src/test/java/com/swirlds/fcqueue/FCQueueTest.java +++ b/platform-sdk/swirlds-fcqueue/src/test/java/com/swirlds/fcqueue/FCQueueTest.java @@ -143,7 +143,7 @@ public void hashTest1() { q3.add(new FCInt(rnd.nextInt(NEXT_INT_BOUNDS))); q3.remove(); q3.remove(); - final byte[] hh = q3.getHash().getValue(); + final byte[] hh = q3.getHash().copyToByteArray(); // the hash of {} should be all zeros for (final byte b : hh) { assertEquals(0, b); @@ -225,8 +225,8 @@ public void hashTest3() { h++; // the hash of {} should be all zeros - for (int i = 0; i < hash[0].getValue().length; i++) { - assertEquals(0, hash[0].getValue()[i]); + for (int i = 0; i < hash[0].getBytes().length(); i++) { + assertEquals(0, hash[0].getBytes().getByte(i)); } // 1={a} 2={a,b} 3={a,b,c} diff --git a/platform-sdk/swirlds-fcqueue/src/test/java/com/swirlds/fcqueue/MockFCQueueTest.java b/platform-sdk/swirlds-fcqueue/src/test/java/com/swirlds/fcqueue/MockFCQueueTest.java index ab58ad173e5a..0fce9616f22b 100644 --- a/platform-sdk/swirlds-fcqueue/src/test/java/com/swirlds/fcqueue/MockFCQueueTest.java +++ b/platform-sdk/swirlds-fcqueue/src/test/java/com/swirlds/fcqueue/MockFCQueueTest.java @@ -167,7 +167,7 @@ void hashTest1(final Supplier> supplier) { q3.add(new FCInt(rnd.nextInt(NEXT_INT_BOUNDS))); q3.remove(); q3.remove(); - final byte[] hh = q3.getHash().getValue(); + final byte[] hh = q3.getHash().copyToByteArray(); // the hash of {} should be all zeros for (byte b : hh) { assertEquals(0, b, "hash for an empty queue should be 0"); @@ -251,8 +251,8 @@ void hashTest3(final Supplier> supplier) { h++; // the hash of {} should be all zeros - for (int i = 0; i < hash[0].getValue().length; i++) { - assertEquals(0, hash[0].getValue()[i], "hash for an empty queue should be 0"); + for (int i = 0; i < hash[0].getBytes().length(); i++) { + assertEquals(0, hash[0].getBytes().getByte(i), "hash for an empty queue should be 0"); } // 1={a} 2={a,b} 3={a,b,c} @@ -368,8 +368,8 @@ void hashTest6(final int queueSize, final Supplier> mockSupplier) mfcq.add(elem); } - final byte[] fcqh = fcq.getHash().getValue(); - final byte[] mfcqh = mfcq.getHash().getValue(); + final byte[] fcqh = fcq.getHash().copyToByteArray(); + final byte[] mfcqh = mfcq.getHash().copyToByteArray(); assert (fcqh.length == mfcqh.length); assert (fcqh.length == 48); @@ -439,8 +439,8 @@ void hashTest8(final int queueSize, final Supplier> supplier) { mfcq.add(elem); } // verify hashes - final byte[] fcqh1 = fcq.getHash().getValue(); - final byte[] mfcqh1 = mfcq.getHash().getValue(); + final byte[] fcqh1 = fcq.getHash().copyToByteArray(); + final byte[] mfcqh1 = mfcq.getHash().copyToByteArray(); // hashes should stay identical for (int i1 = 0; i1 < fcqh1.length; i1++) { assertEquals(fcqh1[i1], mfcqh1[i1], "FCQ and Mock FCQ hashes are different"); @@ -453,8 +453,8 @@ void hashTest8(final int queueSize, final Supplier> supplier) { } // verify hashes - final byte[] fcqh2 = fcq.getHash().getValue(); - final byte[] mfcqh2 = mfcq.getHash().getValue(); + final byte[] fcqh2 = fcq.getHash().copyToByteArray(); + final byte[] mfcqh2 = mfcq.getHash().copyToByteArray(); // hashes should stay identical for (int i2 = 0; i2 < fcqh2.length; i2++) { assertEquals(fcqh2[i2], mfcqh2[i2], "FCQ and Mock FCQ hashes are different"); @@ -620,10 +620,10 @@ void multithreadHashTest(int writeThreadsNum, final Supplier> sup } // verify hashes between FCQueue and Mock - final byte[] fcqh = fcq.getHash().getValue(); - final byte[] mfcqh = mfcq.getHash().getValue(); - final byte[] fcqh_single = fcq_single.getHash().getValue(); - final byte[] mfcqh_single = mfcq_single.getHash().getValue(); + final byte[] fcqh = fcq.getHash().copyToByteArray(); + final byte[] mfcqh = mfcq.getHash().copyToByteArray(); + final byte[] fcqh_single = fcq_single.getHash().copyToByteArray(); + final byte[] mfcqh_single = mfcq_single.getHash().copyToByteArray(); // compare FCQ to MFCQ, FCQ_single to MFCQ_single, cross versions for (int i = 0; i < fcqh.length; i++) { assertEquals(fcqh[i], fcqh_single[i], "single and multithreaded hashes are different"); @@ -713,10 +713,10 @@ void multithreadHashTest2(final int removeThreadsNum, final Supplier { - statisticsUpdater.updateStoreFileStats(); - statisticsUpdater.updateOffHeapStats(); + statisticsUpdater.updateStoreFileStats(this); + statisticsUpdater.updateOffHeapStats(this); }; // internal node hashes store, on disk @@ -539,9 +539,9 @@ public void saveRecords( // Report total size on disk as sum of all store files. All metadata and other helper files // are considered small enough to be ignored. If/when we decide to use on-disk long lists // for indices, they should be added here - statisticsUpdater.updateStoreFileStats(); + statisticsUpdater.updateStoreFileStats(this); // update off-heap stats - statisticsUpdater.updateOffHeapStats(); + statisticsUpdater.updateOffHeapStats(this); } } diff --git a/platform-sdk/swirlds-jasperdb/src/main/java/com/swirlds/merkledb/MerkleDbStatisticsUpdater.java b/platform-sdk/swirlds-jasperdb/src/main/java/com/swirlds/merkledb/MerkleDbStatisticsUpdater.java index 2db9f68759f9..b8bf19bfd7b4 100644 --- a/platform-sdk/swirlds-jasperdb/src/main/java/com/swirlds/merkledb/MerkleDbStatisticsUpdater.java +++ b/platform-sdk/swirlds-jasperdb/src/main/java/com/swirlds/merkledb/MerkleDbStatisticsUpdater.java @@ -21,11 +21,11 @@ import com.swirlds.common.metrics.FunctionGauge; import com.swirlds.merkledb.collections.LongList; import com.swirlds.merkledb.collections.OffHeapUser; +import com.swirlds.merkledb.config.MerkleDbConfig; import com.swirlds.merkledb.files.DataFileReader; import com.swirlds.metrics.api.Metrics; import edu.umd.cs.findbugs.annotations.NonNull; import java.util.LongSummaryStatistics; -import java.util.concurrent.atomic.AtomicBoolean; import java.util.function.IntConsumer; /** @@ -33,12 +33,6 @@ */ public class MerkleDbStatisticsUpdater { - /** - * When we register stats for the first instance, also register the global stats. If true then - * this is the first time stats are being registered for an instance. - */ - private static final AtomicBoolean firstStatRegistration = new AtomicBoolean(true); - private static final FunctionGauge.Config COUNT_OF_OPEN_DATABASES_CONFIG = new FunctionGauge.Config<>( MerkleDbStatistics.STAT_CATEGORY, "merkledb_count", @@ -48,11 +42,9 @@ public class MerkleDbStatisticsUpdater { .withFormat("%d"); private final MerkleDbStatistics statistics; - private final MerkleDbDataSource dataSource; - public MerkleDbStatisticsUpdater(@NonNull MerkleDbDataSource dataSource) { - statistics = new MerkleDbStatistics(dataSource.getDatabase().getConfig(), dataSource.getTableName()); - this.dataSource = dataSource; + public MerkleDbStatisticsUpdater(@NonNull final MerkleDbConfig config, @NonNull final String tableName) { + statistics = new MerkleDbStatistics(config, tableName); } /** @@ -60,10 +52,8 @@ public MerkleDbStatisticsUpdater(@NonNull MerkleDbDataSource dataSource) { * @param metrics the metrics to register */ public void registerMetrics(final Metrics metrics) { - if (firstStatRegistration.compareAndSet(true, false)) { - // register static/global statistics - metrics.getOrCreate(COUNT_OF_OPEN_DATABASES_CONFIG); - } + // register static/global statistics + metrics.getOrCreate(COUNT_OF_OPEN_DATABASES_CONFIG); // register instance statistics statistics.registerMetrics(metrics); @@ -93,7 +83,7 @@ void setFlushHashesStoreFileSize(final DataFileReader newHashesFile) { * * @return hashes store file size, Mb */ - int updateHashesStoreFileStats() { + int updateHashesStoreFileStats(final MerkleDbDataSource dataSource) { if (dataSource.getHashStoreDisk() != null) { final LongSummaryStatistics internalHashesFileSizeStats = dataSource.getHashStoreDisk().getFilesSizeStatistics(); @@ -110,7 +100,7 @@ int updateHashesStoreFileStats() { * * @return leaves store file size, Mb */ - private int updateLeavesStoreFileStats() { + private int updateLeavesStoreFileStats(final MerkleDbDataSource dataSource) { final LongSummaryStatistics leafDataFileSizeStats = dataSource.getPathToKeyValue().getFilesSizeStatistics(); statistics.setLeavesStoreFileCount((int) leafDataFileSizeStats.getCount()); @@ -125,7 +115,7 @@ private int updateLeavesStoreFileStats() { * * @return leaf keys store file size, Mb */ - private int updateLeafKeysStoreFileStats() { + private int updateLeafKeysStoreFileStats(final MerkleDbDataSource dataSource) { if (dataSource.getObjectKeyToPath() != null) { final LongSummaryStatistics leafKeyFileSizeStats = dataSource.getObjectKeyToPath().getFilesSizeStatistics(); @@ -138,15 +128,16 @@ private int updateLeafKeysStoreFileStats() { } /** Calculate updates statistics for all the storages and then updates total usage */ - void updateStoreFileStats() { - statistics.setTotalFileSizeMb( - updateHashesStoreFileStats() + updateLeavesStoreFileStats() + updateLeafKeysStoreFileStats()); + void updateStoreFileStats(final MerkleDbDataSource dataSource) { + statistics.setTotalFileSizeMb(updateHashesStoreFileStats(dataSource) + + updateLeavesStoreFileStats(dataSource) + + updateLeafKeysStoreFileStats(dataSource)); } /** * Updates statistics with off-heap memory consumption. */ - void updateOffHeapStats() { + void updateOffHeapStats(final MerkleDbDataSource dataSource) { int totalOffHeapMemoryConsumption = updateOffHeapStat( dataSource.getPathToDiskLocationInternalNodes(), statistics::setOffHeapHashesIndexMb) + updateOffHeapStat(dataSource.getPathToDiskLocationLeafNodes(), statistics::setOffHeapLeavesIndexMb) diff --git a/platform-sdk/swirlds-jasperdb/src/main/java/com/swirlds/merkledb/files/VirtualHashRecordSerializer.java b/platform-sdk/swirlds-jasperdb/src/main/java/com/swirlds/merkledb/files/VirtualHashRecordSerializer.java index 3c60b019c16d..9cd7ac0c1671 100644 --- a/platform-sdk/swirlds-jasperdb/src/main/java/com/swirlds/merkledb/files/VirtualHashRecordSerializer.java +++ b/platform-sdk/swirlds-jasperdb/src/main/java/com/swirlds/merkledb/files/VirtualHashRecordSerializer.java @@ -97,7 +97,7 @@ public int getSerializedSize(VirtualHashRecord data) { + Long.BYTES; } size += ProtoWriterTools.sizeOfDelimited( - FIELD_HASHRECORD_HASH, data.hash().getValue().length); + FIELD_HASHRECORD_HASH, (int) data.hash().getBytes().length()); return size; } @@ -116,8 +116,8 @@ public void serialize(@NonNull final VirtualHashRecord hashRecord, @NonNull fina ProtoWriterTools.writeDelimited( out, FIELD_HASHRECORD_HASH, - hashRecord.hash().getValue().length, - o -> o.writeBytes(hashRecord.hash().getValue())); + (int) hashRecord.hash().getBytes().length(), + o -> hashRecord.hash().getBytes().writeTo(o)); } @Override @@ -151,10 +151,10 @@ private long readPath(final ReadableSequentialData in) { private Hash readHash(final ReadableSequentialData in) { final int hashSize = in.readVarInt(false); - final Hash hash = new Hash(DigestType.SHA_384); - assert hashSize == hash.getValue().length; - in.readBytes(hash.getValue()); - return hash; + assert hashSize == DigestType.SHA_384.digestLength(); + final byte[] hashBytes = new byte[hashSize]; + in.readBytes(hashBytes); + return new Hash(hashBytes, DigestType.SHA_384); } public void extractAndWriteHashBytes(final ReadableSequentialData in, final SerializableDataOutputStream out) diff --git a/platform-sdk/swirlds-jasperdb/src/main/java/com/swirlds/merkledb/utilities/HashTools.java b/platform-sdk/swirlds-jasperdb/src/main/java/com/swirlds/merkledb/utilities/HashTools.java index 971f22883de0..30e8636d9d87 100644 --- a/platform-sdk/swirlds-jasperdb/src/main/java/com/swirlds/merkledb/utilities/HashTools.java +++ b/platform-sdk/swirlds-jasperdb/src/main/java/com/swirlds/merkledb/utilities/HashTools.java @@ -47,7 +47,7 @@ public static int getSerializationVersion() { */ public static ByteBuffer hashToByteBuffer(final Hash hash) { final ByteBuffer buf = ByteBuffer.allocate(HASH_SIZE_BYTES); - buf.put(hash.getValue()); + hash.getBytes().writeTo(buf); return buf.flip(); } @@ -60,7 +60,7 @@ public static ByteBuffer hashToByteBuffer(final Hash hash) { * the byte buffer to receive the digest of the hash */ public static void hashToByteBuffer(final Hash hash, final ByteBuffer buf) { - buf.put(hash.getValue()); + hash.getBytes().writeTo(buf); } /** diff --git a/platform-sdk/swirlds-jasperdb/src/test/java/com/swirlds/merkledb/MerkleDbDataSourceTest.java b/platform-sdk/swirlds-jasperdb/src/test/java/com/swirlds/merkledb/MerkleDbDataSourceTest.java index 7665e0d2fa8e..4374129a6faa 100644 --- a/platform-sdk/swirlds-jasperdb/src/test/java/com/swirlds/merkledb/MerkleDbDataSourceTest.java +++ b/platform-sdk/swirlds-jasperdb/src/test/java/com/swirlds/merkledb/MerkleDbDataSourceTest.java @@ -42,6 +42,9 @@ import com.swirlds.merkledb.serialize.KeyIndexType; import com.swirlds.merkledb.test.fixtures.ExampleByteArrayVirtualValue; import com.swirlds.merkledb.test.fixtures.TestType; +import com.swirlds.metrics.api.IntegerGauge; +import com.swirlds.metrics.api.Metric.ValueType; +import com.swirlds.metrics.api.Metrics; import com.swirlds.virtualmap.VirtualLongKey; import com.swirlds.virtualmap.datasource.VirtualHashRecord; import com.swirlds.virtualmap.datasource.VirtualLeafRecord; @@ -648,6 +651,38 @@ void dirtyDeletedLeavesBetweenFlushesOnReconnect(final TestType testType) throws }); } + @Test + void copyStatisticsTest() throws Exception { + // This test simulates what happens on reconnect and makes sure that MerkleDb stats are reported + // for the copy correctly + final String label = "copyStatisticsTest"; + final TestType testType = TestType.variable_variable; + final Metrics metrics = testType.getMetrics(); + createAndApplyDataSource(testDirectory, label, testType, 1000, dataSource -> { + dataSource.registerMetrics(metrics); + assertEquals( + 1L, + metrics.getMetric(MerkleDbStatistics.STAT_CATEGORY, "merkledb_count") + .get(ValueType.VALUE)); + dataSource.saveRecords(4, 8, Stream.of(), Stream.of(), Stream.of(), false); + final IntegerGauge sourceCounter = (IntegerGauge) + metrics.getMetric(MerkleDbStatistics.STAT_CATEGORY, "ds_files_leavesStoreFileCount_" + label); + assertEquals(1L, sourceCounter.get()); + final var copy = dataSource.getDatabase().copyDataSource(dataSource, true); + try { + assertEquals( + 2L, metrics.getMetric("merkle_db", "merkledb_count").get(ValueType.VALUE)); + copy.copyStatisticsFrom(dataSource); + copy.saveRecords(4, 8, Stream.of(), Stream.of(), Stream.of(), false); + final IntegerGauge copyCounter = (IntegerGauge) + metrics.getMetric(MerkleDbStatistics.STAT_CATEGORY, "ds_files_leavesStoreFileCount_" + label); + assertEquals(2L, copyCounter.get()); + } finally { + copy.close(); + } + }); + } + // ================================================================================================================= // Helper Methods diff --git a/platform-sdk/swirlds-jasperdb/src/test/java/com/swirlds/merkledb/files/VirtualHashRecordSerializerTest.java b/platform-sdk/swirlds-jasperdb/src/test/java/com/swirlds/merkledb/files/VirtualHashRecordSerializerTest.java index b5e7b19163cf..573e35d689c0 100644 --- a/platform-sdk/swirlds-jasperdb/src/test/java/com/swirlds/merkledb/files/VirtualHashRecordSerializerTest.java +++ b/platform-sdk/swirlds-jasperdb/src/test/java/com/swirlds/merkledb/files/VirtualHashRecordSerializerTest.java @@ -46,12 +46,12 @@ void serializedSizeTest() { "Serialized size should be variable"); final VirtualHashRecord data0 = new VirtualHashRecord(0L, nonDefaultHash); assertEquals( - 1 + 1 + nonDefaultHash.getValue().length, // tag + len + hash + 1 + 1 + nonDefaultHash.getBytes().length(), // tag + len + hash subject.getSerializedSize(data0), "Serialized size should be 0 bytes for path and 66 bytes for hash"); final VirtualHashRecord data1 = new VirtualHashRecord(1L, nonDefaultHash); assertEquals( - 1 + 8 + 1 + 1 + nonDefaultHash.getValue().length, // tag + path + tag + len + hash + 1 + 8 + 1 + 1 + nonDefaultHash.getBytes().length(), // tag + path + tag + len + hash subject.getSerializedSize(data1), "Serialized size should be 9 bytes for path and 75 bytes for hash"); assertEquals(1L, subject.getCurrentDataVersion(), "Current version should be 1"); diff --git a/platform-sdk/swirlds-jasperdb/src/test/java/com/swirlds/merkledb/utilities/HashToolsTest.java b/platform-sdk/swirlds-jasperdb/src/test/java/com/swirlds/merkledb/utilities/HashToolsTest.java index a1d4c20e2b3c..ce8f176faf36 100644 --- a/platform-sdk/swirlds-jasperdb/src/test/java/com/swirlds/merkledb/utilities/HashToolsTest.java +++ b/platform-sdk/swirlds-jasperdb/src/test/java/com/swirlds/merkledb/utilities/HashToolsTest.java @@ -32,7 +32,7 @@ void constructsExpectedBuffer() { final ByteBuffer buffer = HashTools.hashToByteBuffer(hash); - assertArrayEquals(hash.getValue(), buffer.array(), "Hash digest should match created buffer"); + assertArrayEquals(hash.copyToByteArray(), buffer.array(), "Hash digest should match created buffer"); } @Test @@ -42,7 +42,7 @@ void putsDigestAsExpected() { HashTools.hashToByteBuffer(hash, buffer); - assertArrayEquals(hash.getValue(), buffer.array(), "Hash digest should matched filled buffer"); + assertArrayEquals(hash.copyToByteArray(), buffer.array(), "Hash digest should matched filled buffer"); } @Test diff --git a/platform-sdk/swirlds-jasperdb/src/testFixtures/java/com/swirlds/merkledb/test/fixtures/MerkleDbTestUtils.java b/platform-sdk/swirlds-jasperdb/src/testFixtures/java/com/swirlds/merkledb/test/fixtures/MerkleDbTestUtils.java index 4ff03886a9d4..f97913d0bf75 100644 --- a/platform-sdk/swirlds-jasperdb/src/testFixtures/java/com/swirlds/merkledb/test/fixtures/MerkleDbTestUtils.java +++ b/platform-sdk/swirlds-jasperdb/src/testFixtures/java/com/swirlds/merkledb/test/fixtures/MerkleDbTestUtils.java @@ -188,9 +188,9 @@ public static Hash hashDirectoryContentsStatus(final Path dir) { } public static String toLongsString(final Hash hash) { - final byte[] bytes = hash.getValue(); - final LongBuffer longBuf = - ByteBuffer.wrap(bytes).order(ByteOrder.BIG_ENDIAN).asLongBuffer(); + final LongBuffer longBuf = ByteBuffer.wrap(hash.copyToByteArray()) + .order(ByteOrder.BIG_ENDIAN) + .asLongBuffer(); final long[] array = new long[longBuf.remaining()]; longBuf.get(array); return Arrays.toString(array); @@ -213,12 +213,11 @@ public static String toLongsString(final ByteBuffer buf) { public static Hash hash(final int value) { final byte[] hardCoded = new byte[] {(byte) (value >>> 24), (byte) (value >>> 16), (byte) (value >>> 8), (byte) value}; - final Hash mutableHash = new Hash(DigestType.SHA_384); - final byte[] digest = mutableHash.getValue(); + final byte[] digest = new byte[DigestType.SHA_384.digestLength()]; for (int i = 0; i < 6; i++) { System.arraycopy(hardCoded, 0, digest, i * 6 + 4, 4); } - return mutableHash; + return new Hash(digest, DigestType.SHA_384); } public static void hexDump(final PrintStream out, final Path file) throws IOException { diff --git a/platform-sdk/swirlds-jasperdb/src/testFixtures/java/com/swirlds/merkledb/test/fixtures/TestType.java b/platform-sdk/swirlds-jasperdb/src/testFixtures/java/com/swirlds/merkledb/test/fixtures/TestType.java index 4f8c31347c17..428364af7371 100644 --- a/platform-sdk/swirlds-jasperdb/src/testFixtures/java/com/swirlds/merkledb/test/fixtures/TestType.java +++ b/platform-sdk/swirlds-jasperdb/src/testFixtures/java/com/swirlds/merkledb/test/fixtures/TestType.java @@ -71,6 +71,8 @@ public enum TestType { public final boolean fixedSize; + private Metrics metrics = null; + TestType(boolean fixedSize) { this.fixedSize = fixedSize; } @@ -79,8 +81,28 @@ public DataTypeConfig dataT return new DataTypeConfig<>(this); } + public Metrics getMetrics() { + if (metrics == null) { + final Configuration configuration = new TestConfigBuilder().getOrCreateConfig(); + MetricsConfig metricsConfig = configuration.getConfigData(MetricsConfig.class); + + final MetricKeyRegistry registry = mock(MetricKeyRegistry.class); + when(registry.register(any(), any(), any())).thenReturn(true); + metrics = new DefaultMetrics( + null, + registry, + mock(ScheduledExecutorService.class), + new DefaultMetricsFactory(metricsConfig), + metricsConfig); + MerkleDbStatistics statistics = + new MerkleDbStatistics(configuration.getConfigData(MerkleDbConfig.class), "test"); + statistics.registerMetrics(metrics); + } + return metrics; + } + @SuppressWarnings({"unchecked", "rawtypes", "unused"}) - public static class DataTypeConfig { + public class DataTypeConfig { private final TestType testType; private final KeySerializer keySerializer; @@ -191,24 +213,6 @@ public boolean hasKeyToPathStore() { return (keySerializer.getSerializedSize() != Long.BYTES); } - private static Metrics createMetrics() { - final Configuration configuration = new TestConfigBuilder().getOrCreateConfig(); - MetricsConfig metricsConfig = configuration.getConfigData(MetricsConfig.class); - - final MetricKeyRegistry registry = mock(MetricKeyRegistry.class); - when(registry.register(any(), any(), any())).thenReturn(true); - Metrics metrics = new DefaultMetrics( - null, - registry, - mock(ScheduledExecutorService.class), - new DefaultMetricsFactory(metricsConfig), - metricsConfig); - MerkleDbStatistics statistics = - new MerkleDbStatistics(configuration.getConfigData(MerkleDbConfig.class), "test"); - statistics.registerMetrics(metrics); - return metrics; - } - public MerkleDbDataSource createDataSource( final Path dbPath, final String name, @@ -228,7 +232,7 @@ public MerkleDbDataSource createDa .hashesRamToDiskThreshold(hashesRamToDiskThreshold); MerkleDbDataSource dataSource = database.createDataSource(name, (MerkleDbTableConfig) tableConfig, enableMerging); - dataSource.registerMetrics(createMetrics()); + dataSource.registerMetrics(getMetrics()); return dataSource; } diff --git a/platform-sdk/swirlds-logging-log4j-appender/build.gradle.kts b/platform-sdk/swirlds-logging-log4j-appender/build.gradle.kts index 4f52d4ee3625..6c7b688ba995 100644 --- a/platform-sdk/swirlds-logging-log4j-appender/build.gradle.kts +++ b/platform-sdk/swirlds-logging-log4j-appender/build.gradle.kts @@ -15,8 +15,8 @@ */ plugins { - id("com.hedera.hashgraph.sdk.conventions") - id("com.hedera.hashgraph.platform-maven-publish") + id("com.hedera.gradle.platform") + id("com.hedera.gradle.platform-publish") } mainModuleInfo { diff --git a/platform-sdk/swirlds-logging/build.gradle.kts b/platform-sdk/swirlds-logging/build.gradle.kts index 1064f64be119..eddcde75994f 100644 --- a/platform-sdk/swirlds-logging/build.gradle.kts +++ b/platform-sdk/swirlds-logging/build.gradle.kts @@ -15,10 +15,10 @@ */ plugins { - id("com.hedera.hashgraph.sdk.conventions") - id("com.hedera.hashgraph.platform-maven-publish") - id("com.hedera.hashgraph.java-test-fixtures") - id("com.hedera.hashgraph.benchmark-conventions") + id("com.hedera.gradle.platform") + id("com.hedera.gradle.platform-publish") + id("com.hedera.gradle.java-test-fixtures") + id("com.hedera.gradle.benchmark") } mainModuleInfo { annotationProcessor("com.google.auto.service.processor") } diff --git a/platform-sdk/swirlds-logging/src/jmh/java/com/swirlds/logging/benchmark/bridged/BridgedSwirldsLogConfig.java b/platform-sdk/swirlds-logging/src/jmh/java/com/swirlds/logging/benchmark/bridged/BridgedSwirldsLogConfig.java index 9cf7d645f602..df1ef067b505 100644 --- a/platform-sdk/swirlds-logging/src/jmh/java/com/swirlds/logging/benchmark/bridged/BridgedSwirldsLogConfig.java +++ b/platform-sdk/swirlds-logging/src/jmh/java/com/swirlds/logging/benchmark/bridged/BridgedSwirldsLogConfig.java @@ -45,7 +45,7 @@ public class BridgedSwirldsLogConfig implements LoggingBenchmarkConfig{@code append} - Determines whether to append to an existing file or overwrite it. *

  • {@code formatTimestamp} - If set to true, epoch values are formatted as human-readable strings.
  • *
  • {@code file-rolling.maxFileSize} - Maximum size of the file for size-based rolling.
  • - *
  • {@code file-rolling.maxRollover} - Maximum number of files used for rolling.
  • + *
  • {@code file-rolling.maxFiles} - Maximum number of files used for rolling.
  • * */ public class FileHandler extends AbstractLogHandler { diff --git a/platform-sdk/swirlds-logging/src/main/java/com/swirlds/logging/io/OutputStreamFactory.java b/platform-sdk/swirlds-logging/src/main/java/com/swirlds/logging/io/OutputStreamFactory.java index aea932e66525..a09afb865f3c 100644 --- a/platform-sdk/swirlds-logging/src/main/java/com/swirlds/logging/io/OutputStreamFactory.java +++ b/platform-sdk/swirlds-logging/src/main/java/com/swirlds/logging/io/OutputStreamFactory.java @@ -36,17 +36,17 @@ public class OutputStreamFactory { private static final String FILE_NAME_PROPERTY = ".file"; private static final String APPEND_PROPERTY = ".append"; private static final String SIZE_PROPERTY = ".file-rolling.maxFileSize"; - private static final String MAX_ROLLOVER = ".file-rolling.maxRollover"; + private static final String MAX_ROLLOVER = ".file-rolling.maxFiles"; private static final String DEFAULT_FILE_NAME = "swirlds-log.log"; private static final int DEFAULT_MAX_ROLLOVER_FILES = 1; private static final int BUFFER_CAPACITY = 8192 * 8; - private static class InstanceHolder { - private static final OutputStreamFactory instance = new OutputStreamFactory(); + private static final class InstanceHolder { + private static final OutputStreamFactory INSTANCE = new OutputStreamFactory(); } public static OutputStreamFactory getInstance() { - return InstanceHolder.instance; + return InstanceHolder.INSTANCE; } /** diff --git a/platform-sdk/swirlds-logging/src/main/java/com/swirlds/logging/io/RolloverFileOutputStream.java b/platform-sdk/swirlds-logging/src/main/java/com/swirlds/logging/io/RolloverFileOutputStream.java index bed34af1c2bd..3c1cd470b360 100644 --- a/platform-sdk/swirlds-logging/src/main/java/com/swirlds/logging/io/RolloverFileOutputStream.java +++ b/platform-sdk/swirlds-logging/src/main/java/com/swirlds/logging/io/RolloverFileOutputStream.java @@ -35,7 +35,7 @@ */ public class RolloverFileOutputStream extends OutputStream { protected final Path logPath; - private final int maxRollover; + private final int maxFiles; private final long maxFileSize; private final boolean append; private long remainingSize; @@ -52,21 +52,21 @@ public class RolloverFileOutputStream extends OutputStream { * Path logPath = Paths.get("logs", "example.log"); * long maxFileSize = 1024 * 1024; // 1 MB * boolean append = true; - * int maxRollover = 5; // Maximum number of rolling files - * RolloverFileOutputStream outputStream = new RolloverFileOutputStream(logPath, maxFileSize, append, maxRollover); + * int maxFiles = 5; // Maximum number of rolling files + * RolloverFileOutputStream outputStream = new RolloverFileOutputStream(logPath, maxFileSize, append, maxFiles); * * * @param logPath path where the logging file is located. Should contain the base dir + the name of the logging * file. * @param maxFileSize maximum size for the file. The limit is checked with best effort * @param append if true and the file exists, appends the content to the file. if not, the file is rolled. - * @param maxRollover Within a rolling period, how many rolling files are allowed. + * @param maxFiles Within a rolling period, how many rolling files are allowed. */ protected RolloverFileOutputStream( - final @NonNull Path logPath, final long maxFileSize, final boolean append, final int maxRollover) { + final @NonNull Path logPath, final long maxFileSize, final boolean append, final int maxFiles) { this.logPath = logPath.toAbsolutePath().getParent(); this.append = append; - this.maxRollover = maxRollover; + this.maxFiles = maxFiles; this.maxFileSize = maxFileSize; final String baseFile = logPath.getFileName().toString(); final int lastDotIndex = baseFile.lastIndexOf("."); @@ -80,7 +80,7 @@ protected RolloverFileOutputStream( } this.index = 0; - this.indexLength = String.valueOf(maxRollover).length(); + this.indexLength = String.valueOf(maxFiles).length(); try { this.outputStream = new FileOutputStream(logPath.toString(), this.append); } catch (FileNotFoundException e) { @@ -162,19 +162,19 @@ protected Path logFilePath() { * Rolls the file */ private void roll() { - final long maxIndex = maxRollover - 1; + final long maxIndex = maxFiles - 1; int currentIndex = index; Path newPath = getPathFor(currentIndex); // Start by searching a new index if current index is in use while (Files.exists(newPath) && currentIndex <= maxIndex) { currentIndex++; - newPath = getPathFor(currentIndex % maxRollover); + newPath = getPathFor(currentIndex % maxFiles); } if (currentIndex > maxIndex) { currentIndex = index; - newPath = getPathFor(currentIndex % maxRollover); + newPath = getPathFor(currentIndex % maxFiles); } final File file = logFilePath().toFile(); diff --git a/platform-sdk/swirlds-merkle/build.gradle.kts b/platform-sdk/swirlds-merkle/build.gradle.kts index f412cebc6dec..047f495bb24e 100644 --- a/platform-sdk/swirlds-merkle/build.gradle.kts +++ b/platform-sdk/swirlds-merkle/build.gradle.kts @@ -15,9 +15,9 @@ */ plugins { - id("com.hedera.hashgraph.sdk.conventions") - id("com.hedera.hashgraph.platform-maven-publish") - id("com.hedera.hashgraph.java-test-fixtures") + id("com.hedera.gradle.platform") + id("com.hedera.gradle.platform-publish") + id("com.hedera.gradle.java-test-fixtures") } testModuleInfo { diff --git a/platform-sdk/swirlds-merkle/src/test/java/com/swirlds/merkle/test/MerkleHashTests.java b/platform-sdk/swirlds-merkle/src/test/java/com/swirlds/merkle/test/MerkleHashTests.java index ccad12c0ac68..5e086b164d0a 100644 --- a/platform-sdk/swirlds-merkle/src/test/java/com/swirlds/merkle/test/MerkleHashTests.java +++ b/platform-sdk/swirlds-merkle/src/test/java/com/swirlds/merkle/test/MerkleHashTests.java @@ -44,8 +44,6 @@ import com.swirlds.common.test.fixtures.merkle.dummy.DummyMerkleNode; import com.swirlds.common.test.fixtures.merkle.dummy.SelfHashingDummyMerkleLeaf; import com.swirlds.common.test.fixtures.merkle.util.MerkleTestUtils; -import java.io.FileOutputStream; -import java.io.IOException; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -184,16 +182,6 @@ void doubleHashingTest() { } }); } - - /** - * Utility function that writes a tree's hash to a file. - */ - void writeTreeToFile(final MerkleNode tree, final String filePath) throws IOException { - final FileOutputStream stream = new FileOutputStream(filePath); - stream.write(cryptography.digestTreeSync(tree).getValue()); - stream.close(); - } - /** * This test verifies that two MerkleInternal nodes with different types * but the same children hash to different values. diff --git a/platform-sdk/swirlds-merkle/src/test/java/com/swirlds/merkle/test/map/MerkleMapTests.java b/platform-sdk/swirlds-merkle/src/test/java/com/swirlds/merkle/test/map/MerkleMapTests.java index 414bfb2a5f4c..743c6ca420cf 100644 --- a/platform-sdk/swirlds-merkle/src/test/java/com/swirlds/merkle/test/map/MerkleMapTests.java +++ b/platform-sdk/swirlds-merkle/src/test/java/com/swirlds/merkle/test/map/MerkleMapTests.java @@ -676,7 +676,7 @@ void hashIsNullAfterModifyWithCopyTest() { final Hash hash = mm.getRootHash(); assertNotNull(hash, "expected non-null hash"); - assertNotNull(hash.getValue(), "expected non-null hash data"); + assertNotNull(hash.getBytes(), "expected non-null hash data"); final MerkleMap copy01 = mm.copy(); final Key key01 = new Key(0, 0, 0); diff --git a/platform-sdk/swirlds-merkle/src/testFixtures/java/com/swirlds/merkle/test/fixtures/map/lifecycle/SaveExpectedMapHandler.java b/platform-sdk/swirlds-merkle/src/testFixtures/java/com/swirlds/merkle/test/fixtures/map/lifecycle/SaveExpectedMapHandler.java index 39530f6cdef4..1cdaff0daf42 100644 --- a/platform-sdk/swirlds-merkle/src/testFixtures/java/com/swirlds/merkle/test/fixtures/map/lifecycle/SaveExpectedMapHandler.java +++ b/platform-sdk/swirlds-merkle/src/testFixtures/java/com/swirlds/merkle/test/fixtures/map/lifecycle/SaveExpectedMapHandler.java @@ -18,6 +18,7 @@ import static com.swirlds.logging.legacy.LogMarker.EXCEPTION; +import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.core.util.DefaultPrettyPrinter; import com.fasterxml.jackson.databind.DeserializationFeature; @@ -25,6 +26,8 @@ import com.fasterxml.jackson.databind.ObjectWriter; import com.fasterxml.jackson.databind.SerializationFeature; import com.fasterxml.jackson.databind.module.SimpleModule; +import com.hedera.pbj.runtime.io.buffer.Bytes; +import com.swirlds.common.crypto.Hash; import com.swirlds.merkle.test.fixtures.map.pta.MapKey; import com.swirlds.merkle.test.fixtures.map.serialization.MapKeyDeserializer; import java.io.BufferedOutputStream; @@ -54,9 +57,17 @@ public class SaveExpectedMapHandler { private static final String JSON_FILE_NAME_TEMPLATE = "Node%04d_ExpectedMap_%d_%d.json"; private static final ObjectMapper objectMapper = - new ObjectMapper().disable(SerializationFeature.FAIL_ON_EMPTY_BEANS); + new ObjectMapper().disable(SerializationFeature.FAIL_ON_EMPTY_BEANS).addMixIn(Hash.class, MixIn.class); private static final ObjectWriter objectWriter = objectMapper.writer(new DefaultPrettyPrinter()); + /** + * MixIn class to ignore Bytes field in Hash class without polluting the Hash class with Jackson annotations + */ + abstract static class MixIn { + @JsonIgnore + abstract Bytes getBytes(); + } + /** * Serialize the expectedMap to JSON * @@ -117,20 +128,17 @@ public static String serialize( * the JSON file * @return ExpectedMap de-serialized from JSON file serialized to disk */ - public static Map deserialize(final File sourceFile) { + public static Map deserialize(final File sourceFile) throws IOException { Map newMap = new ConcurrentHashMap<>(); registerModule(); final File jsonFile = unzipExpectedMap(sourceFile); if (jsonFile == null) { - logger.error(EXCEPTION.getMarker(), "No JSON file found in the zip file: {}", sourceFile); - return newMap; + throw new IllegalArgumentException("No JSON file found in the zip file: %s".formatted(sourceFile)); } try (FileInputStream fileInputStream = new FileInputStream(jsonFile)) { newMap = objectMapper.readValue(fileInputStream, new TypeReference>() {}); - } catch (IOException e) { - logger.error(EXCEPTION.getMarker(), "Error occurred while reading: {}", sourceFile, e); } jsonFile.delete(); diff --git a/platform-sdk/swirlds-merkle/src/testFixtures/java/module-info.java b/platform-sdk/swirlds-merkle/src/testFixtures/java/module-info.java index 1981db09d885..f946cb5a7580 100644 --- a/platform-sdk/swirlds-merkle/src/testFixtures/java/module-info.java +++ b/platform-sdk/swirlds-merkle/src/testFixtures/java/module-info.java @@ -7,6 +7,7 @@ exports com.swirlds.merkle.test.fixtures.map.pta; requires com.fasterxml.jackson.core; + requires com.hedera.pbj.runtime; requires com.swirlds.base; requires com.swirlds.fchashmap; requires com.swirlds.fcqueue; diff --git a/platform-sdk/swirlds-metrics-api/build.gradle.kts b/platform-sdk/swirlds-metrics-api/build.gradle.kts index 15a286733a6f..278ebab31a3a 100644 --- a/platform-sdk/swirlds-metrics-api/build.gradle.kts +++ b/platform-sdk/swirlds-metrics-api/build.gradle.kts @@ -15,8 +15,8 @@ */ plugins { - id("com.hedera.hashgraph.sdk.conventions") - id("com.hedera.hashgraph.platform-maven-publish") + id("com.hedera.gradle.platform") + id("com.hedera.gradle.platform-publish") } testModuleInfo { diff --git a/platform-sdk/swirlds-platform-core/build.gradle.kts b/platform-sdk/swirlds-platform-core/build.gradle.kts index 7a4e5ff3ab4c..1b91fa788cd9 100644 --- a/platform-sdk/swirlds-platform-core/build.gradle.kts +++ b/platform-sdk/swirlds-platform-core/build.gradle.kts @@ -15,10 +15,10 @@ */ plugins { - id("com.hedera.hashgraph.sdk.conventions") - id("com.hedera.hashgraph.platform-maven-publish") - id("com.hedera.hashgraph.benchmark-conventions") - id("com.hedera.hashgraph.java-test-fixtures") + id("com.hedera.gradle.platform") + id("com.hedera.gradle.platform-publish") + id("com.hedera.gradle.benchmark") + id("com.hedera.gradle.java-test-fixtures") } mainModuleInfo { @@ -42,11 +42,15 @@ testModuleInfo { requires("com.swirlds.common.test.fixtures") requires("com.swirlds.logging.test.fixtures") requires("com.swirlds.platform.core") + requires("com.swirlds.platform.core.test.fixtures") requires("com.swirlds.config.extensions.test.fixtures") + requires("com.swirlds.platform.core.test.fixtures") requires("jakarta.inject") requires("org.assertj.core") + requires("io.github.classgraph") requires("org.junit.jupiter.api") requires("org.junit.jupiter.params") requires("org.mockito") + requires("org.mockito.junit.jupiter") requiresStatic("com.github.spotbugs.annotations") } diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/StateSigner.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/StateSigner.java index 8d48e9dda875..2f99f22daf32 100644 --- a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/StateSigner.java +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/StateSigner.java @@ -23,6 +23,8 @@ import com.swirlds.platform.state.signed.ReservedSignedState; import com.swirlds.platform.system.status.PlatformStatus; import com.swirlds.platform.system.status.PlatformStatusNexus; +import com.swirlds.platform.system.transaction.ConsensusTransactionImpl; +import com.swirlds.platform.system.transaction.StateSignatureTransaction; import edu.umd.cs.findbugs.annotations.NonNull; import edu.umd.cs.findbugs.annotations.Nullable; import java.util.Objects; @@ -59,7 +61,7 @@ public StateSigner(@NonNull final PlatformSigner signer, @NonNull final Platform * @param reservedSignedState the state to sign * @return a {@link StateSignaturePayload} containing the signature, or null if the state should not be signed */ - public @Nullable StateSignaturePayload signState(@NonNull final ReservedSignedState reservedSignedState) { + public @Nullable ConsensusTransactionImpl signState(@NonNull final ReservedSignedState reservedSignedState) { try (reservedSignedState) { if (statusNexus.getCurrentStatus() == PlatformStatus.REPLAYING_EVENTS) { // the only time we don't want to submit signatures is during PCES replay @@ -71,11 +73,12 @@ public StateSigner(@NonNull final PlatformSigner signer, @NonNull final Platform final Bytes signature = signer.signImmutable(stateHash); Objects.requireNonNull(signature); - return StateSignaturePayload.newBuilder() + final StateSignaturePayload payload = StateSignaturePayload.newBuilder() .round(reservedSignedState.get().getRound()) .signature(signature) .hash(stateHash.getBytes()) .build(); + return new StateSignatureTransaction(payload); } } } diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/SwirldsPlatform.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/SwirldsPlatform.java index cc381e3ba4a3..ef31abee9d85 100644 --- a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/SwirldsPlatform.java +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/SwirldsPlatform.java @@ -17,7 +17,6 @@ package com.swirlds.platform; import static com.swirlds.common.threading.interrupt.Uninterruptable.abortAndThrowIfInterrupted; -import static com.swirlds.common.threading.manager.AdHocThreadManager.getStaticThreadManager; import static com.swirlds.logging.legacy.LogMarker.EXCEPTION; import static com.swirlds.logging.legacy.LogMarker.RECONNECT; import static com.swirlds.logging.legacy.LogMarker.STARTUP; @@ -25,35 +24,21 @@ import static com.swirlds.platform.event.preconsensus.PcesBirthRoundMigration.migratePcesToBirthRoundMode; import static com.swirlds.platform.state.BirthRoundStateMigration.modifyStateForBirthRoundMigration; import static com.swirlds.platform.state.address.AddressBookMetrics.registerAddressBookMetrics; -import static com.swirlds.platform.state.iss.IssDetector.DO_NOT_IGNORE_ROUNDS; -import static com.swirlds.platform.state.signed.SignedStateFileReader.getSavedStateFiles; +import static com.swirlds.platform.state.snapshot.SignedStateFileReader.getSavedStateFiles; import static com.swirlds.platform.system.InitTrigger.GENESIS; import static com.swirlds.platform.system.InitTrigger.RESTART; import static com.swirlds.platform.system.SoftwareVersion.NO_VERSION; -import static com.swirlds.platform.system.UptimeData.NO_ROUND; -import com.swirlds.base.time.Time; -import com.swirlds.base.utility.Pair; import com.swirlds.common.context.PlatformContext; import com.swirlds.common.crypto.Hash; import com.swirlds.common.crypto.Signature; import com.swirlds.common.io.IOIterator; import com.swirlds.common.merkle.crypto.MerkleCryptoFactory; -import com.swirlds.common.merkle.utility.SerializableLong; import com.swirlds.common.notification.NotificationEngine; import com.swirlds.common.platform.NodeId; -import com.swirlds.common.scratchpad.Scratchpad; import com.swirlds.common.stream.RunningEventHashOverride; -import com.swirlds.common.threading.framework.QueueThread; -import com.swirlds.common.threading.framework.config.QueueThreadConfiguration; -import com.swirlds.common.threading.framework.config.QueueThreadMetricsConfiguration; -import com.swirlds.common.threading.manager.AdHocThreadManager; -import com.swirlds.common.threading.manager.ThreadManager; import com.swirlds.common.utility.AutoCloseableWrapper; -import com.swirlds.common.utility.Clearable; -import com.swirlds.common.utility.LoggingClearables; import com.swirlds.logging.legacy.LogMarker; -import com.swirlds.metrics.api.Metrics; import com.swirlds.platform.builder.PlatformBuildingBlocks; import com.swirlds.platform.builder.PlatformComponentBuilder; import com.swirlds.platform.components.AppNotifier; @@ -66,59 +51,45 @@ import com.swirlds.platform.components.appcomm.LatestCompleteStateNotifier; import com.swirlds.platform.config.StateConfig; import com.swirlds.platform.config.TransactionConfig; -import com.swirlds.platform.consensus.ConsensusSnapshot; import com.swirlds.platform.consensus.EventWindow; import com.swirlds.platform.crypto.KeysAndCerts; import com.swirlds.platform.crypto.PlatformSigner; import com.swirlds.platform.event.AncientMode; import com.swirlds.platform.event.EventCounter; import com.swirlds.platform.event.GossipEvent; -import com.swirlds.platform.event.preconsensus.PcesConfig; import com.swirlds.platform.event.preconsensus.PcesFileTracker; import com.swirlds.platform.event.preconsensus.PcesReplayer; import com.swirlds.platform.event.validation.AddressBookUpdate; import com.swirlds.platform.eventhandling.ConsensusRoundHandler; import com.swirlds.platform.eventhandling.EventConfig; -import com.swirlds.platform.eventhandling.TransactionPool; import com.swirlds.platform.gossip.SyncGossip; -import com.swirlds.platform.gossip.shadowgraph.Shadowgraph; import com.swirlds.platform.listeners.ReconnectCompleteNotification; -import com.swirlds.platform.listeners.StateLoadedFromDiskNotification; import com.swirlds.platform.metrics.RuntimeMetrics; -import com.swirlds.platform.metrics.SwirldStateMetrics; -import com.swirlds.platform.metrics.SyncMetrics; import com.swirlds.platform.metrics.TransactionMetrics; +import com.swirlds.platform.pool.TransactionPoolNexus; import com.swirlds.platform.publisher.DefaultPlatformPublisher; import com.swirlds.platform.publisher.PlatformPublisher; -import com.swirlds.platform.recovery.EmergencyRecoveryManager; import com.swirlds.platform.state.PlatformState; import com.swirlds.platform.state.State; import com.swirlds.platform.state.SwirldStateManager; -import com.swirlds.platform.state.iss.IssDetector; import com.swirlds.platform.state.iss.IssHandler; -import com.swirlds.platform.state.iss.IssScratchpad; import com.swirlds.platform.state.nexus.DefaultLatestCompleteStateNexus; -import com.swirlds.platform.state.nexus.EmergencyStateNexus; import com.swirlds.platform.state.nexus.LatestCompleteStateNexus; import com.swirlds.platform.state.nexus.LockFreeStateNexus; import com.swirlds.platform.state.nexus.SignedStateNexus; -import com.swirlds.platform.state.signed.DefaultSignedStateHasher; +import com.swirlds.platform.state.signed.DefaultStateSignatureCollector; import com.swirlds.platform.state.signed.ReservedSignedState; -import com.swirlds.platform.state.signed.SavedStateInfo; import com.swirlds.platform.state.signed.SignedState; -import com.swirlds.platform.state.signed.SignedStateFileManager; -import com.swirlds.platform.state.signed.SignedStateHasher; import com.swirlds.platform.state.signed.SignedStateMetrics; -import com.swirlds.platform.state.signed.StartupStateUtils; -import com.swirlds.platform.state.signed.StateDumpRequest; import com.swirlds.platform.state.signed.StateSignatureCollector; -import com.swirlds.platform.state.signed.StateToDiskReason; +import com.swirlds.platform.state.snapshot.SavedStateInfo; +import com.swirlds.platform.state.snapshot.StateDumpRequest; +import com.swirlds.platform.state.snapshot.StateToDiskReason; import com.swirlds.platform.system.InitTrigger; import com.swirlds.platform.system.Platform; import com.swirlds.platform.system.SoftwareVersion; import com.swirlds.platform.system.SwirldState; import com.swirlds.platform.system.SystemExitUtils; -import com.swirlds.platform.system.UptimeData; import com.swirlds.platform.system.address.AddressBook; import com.swirlds.platform.system.address.AddressBookUtils; import com.swirlds.platform.system.events.BirthRoundMigrationShim; @@ -129,9 +100,6 @@ import com.swirlds.platform.system.status.actions.ReconnectCompleteAction; import com.swirlds.platform.system.status.actions.StartedReplayingEventsAction; import com.swirlds.platform.system.transaction.SwirldTransaction; -import com.swirlds.platform.util.HashLogger; -import com.swirlds.platform.util.ThingsToStart; -import com.swirlds.platform.wiring.NoInput; import com.swirlds.platform.wiring.PlatformWiring; import edu.umd.cs.findbugs.annotations.NonNull; import edu.umd.cs.findbugs.annotations.Nullable; @@ -140,16 +108,16 @@ import java.util.List; import java.util.Objects; import java.util.concurrent.ExecutionException; -import java.util.concurrent.atomic.AtomicLong; -import java.util.function.Consumer; import java.util.function.LongSupplier; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; +/** + * The swirlds consensus node platform. Responsible for the creation, gossip, and consensus of events. Also manages the + * transaction handling and state management. + */ public class SwirldsPlatform implements Platform { - public static final String PLATFORM_THREAD_POOL_NAME = "platform-core"; - private static final Logger logger = LogManager.getLogger(SwirldsPlatform.class); /** @@ -157,19 +125,11 @@ public class SwirldsPlatform implements Platform { */ private final NodeId selfId; - /** - * The shadow graph manager. This wraps a shadow graph, which is an Event graph that adds child pointers to the - * Hashgraph Event graph. Used for gossiping. - */ - private final Shadowgraph shadowGraph; - /** * the current nodes in the network and their information */ private final AddressBook currentAddressBook; - private final Metrics metrics; - /** * the object that contains all key pairs and CSPRNG state for this member */ @@ -205,16 +165,6 @@ public class SwirldsPlatform implements Platform { */ private final SwirldTransactionSubmitter transactionSubmitter; - /** - * clears all pipelines to prepare for a reconnect - */ - private final Clearable clearAllPipelines; - - /** - * All things that need to be started when the platform is started. - */ - private final ThingsToStart thingsToStart; - /** * For passing notifications between the platform and the application. */ @@ -230,30 +180,6 @@ public class SwirldsPlatform implements Platform { */ private final PcesFileTracker initialPcesFiles; - /** - * A nexus for getting and setting the current platform status. - *

    - * Future work: usage of this member should be replaced by consuming the status updates from - * {@link com.swirlds.platform.system.status.StatusStateMachine} - */ - private final PlatformStatusNexus statusNexus; - - /** - * Responsible for transmitting and receiving events from the network. - */ - private final SyncGossip gossip; - - /** - * The round of the most recent reconnect state received, or {@link UptimeData#NO_ROUND} if no reconnect state has - * been received since startup. - */ - private final AtomicLong latestReconnectRound = new AtomicLong(NO_ROUND); - - /** - * Manages emergency recovery - */ - private final EmergencyRecoveryManager emergencyRecoveryManager; - /** * Controls which states are saved to disk */ @@ -264,8 +190,6 @@ public class SwirldsPlatform implements Platform { */ private final PlatformWiring platformWiring; - private final AncientMode ancientMode; - /** * Constructor. * @@ -276,7 +200,7 @@ public SwirldsPlatform(@NonNull final PlatformComponentBuilder builder) { final PlatformBuildingBlocks blocks = builder.getBuildingBlocks(); platformContext = blocks.platformContext(); - ancientMode = platformContext + final AncientMode ancientMode = platformContext .getConfiguration() .getConfigData(EventConfig.class) .getAncientMode(); @@ -301,128 +225,38 @@ public SwirldsPlatform(@NonNull final PlatformComponentBuilder builder) { } } - emergencyRecoveryManager = blocks.emergencyRecoveryManager(); selfId = blocks.selfId(); initialPcesFiles = blocks.initialPcesFiles(); - - thingsToStart = new ThingsToStart(); - - // FUTURE WORK: use a real thread manager here - final ThreadManager threadManager = getStaticThreadManager(); - - notificationEngine = NotificationEngine.buildEngine(threadManager); - - final StateConfig stateConfig = platformContext.getConfiguration().getConfigData(StateConfig.class); - final String actualMainClassName = stateConfig.getMainClassName(blocks.mainClassName()); + notificationEngine = blocks.notificationEngine(); currentAddressBook = initialState.getAddressBook(); - final EmergencyStateNexus emergencyState = new EmergencyStateNexus(); - if (emergencyRecoveryManager.isEmergencyState(initialState)) { - emergencyState.setState(initialState.reserve("emergency state nexus")); - } - - final Consumer preconsensusEventConsumer = blocks.preconsensusEventConsumer(); - final Consumer snapshotOverrideConsumer = blocks.snapshotOverrideConsumer(); - platformWiring = thingsToStart.add(new PlatformWiring( - platformContext, preconsensusEventConsumer != null, snapshotOverrideConsumer != null)); - - thingsToStart.add(platformContext.getFileSystemManager()); - - metrics = platformContext.getMetrics(); - - registerAddressBookMetrics(metrics, currentAddressBook, selfId); - - final SyncMetrics syncMetrics = new SyncMetrics(metrics); - RuntimeMetrics.setup(metrics); + platformWiring = new PlatformWiring(platformContext, blocks.model(), blocks.applicationCallbacks()); - shadowGraph = new Shadowgraph(platformContext, currentAddressBook, blocks.intakeEventCounter()); + registerAddressBookMetrics(platformContext.getMetrics(), currentAddressBook, selfId); - final EventConfig eventConfig = platformContext.getConfiguration().getConfigData(EventConfig.class); + RuntimeMetrics.setup(platformContext.getMetrics()); keysAndCerts = blocks.keysAndCerts(); - EventCounter.registerEventCounterMetrics(metrics); - - final Hash epochHash; - if (emergencyRecoveryManager.getEmergencyRecoveryFile() != null) { - epochHash = emergencyRecoveryManager.getEmergencyRecoveryFile().hash(); - } else { - epochHash = initialState.getState().getPlatformState().getEpochHash(); - } - - final String swirldName = blocks.swirldName(); - StartupStateUtils.doRecoveryCleanup( - platformContext, selfId, swirldName, actualMainClassName, epochHash, initialState.getRound()); - - // Only validate preconsensus signature transactions if we are not recovering from an ISS. - // ISS round == null means we haven't observed an ISS yet. - // ISS round < current round means there was an ISS prior to the saved state - // that has already been recovered from. - // ISS round >= current round means that the ISS happens in the future relative the initial state, meaning - // we may observe ISS-inducing signature transactions in the preconsensus event stream. - final Scratchpad issScratchpad = - Scratchpad.create(platformContext, selfId, IssScratchpad.class, "platform.iss"); - issScratchpad.logContents(); - final SerializableLong issRound = issScratchpad.get(IssScratchpad.LAST_ISS_ROUND); - - final boolean forceIgnorePcesSignatures = platformContext - .getConfiguration() - .getConfigData(PcesConfig.class) - .forceIgnorePcesSignatures(); - - final boolean ignorePreconsensusSignatures; - if (forceIgnorePcesSignatures) { - // this is used FOR TESTING ONLY - ignorePreconsensusSignatures = true; - } else { - ignorePreconsensusSignatures = issRound != null && issRound.getValue() >= initialState.getRound(); - } - - // A round that we will completely skip ISS detection for. Needed for tests that do janky state modification - // without a software upgrade (in production this feature should not be used). - final long roundToIgnore = stateConfig.validateInitialState() ? DO_NOT_IGNORE_ROUNDS : initialState.getRound(); - - final IssDetector issDetector = new IssDetector( - platformContext, currentAddressBook, appVersion, ignorePreconsensusSignatures, roundToIgnore); - - final SignedStateFileManager signedStateFileManager = new SignedStateFileManager( - platformContext, - new SignedStateMetrics(platformContext.getMetrics()), - Time.getCurrent(), - actualMainClassName, - selfId, - swirldName); + EventCounter.registerEventCounterMetrics(platformContext.getMetrics()); - final LatestCompleteStateNexus latestCompleteStateNexus = - new DefaultLatestCompleteStateNexus(stateConfig, platformContext.getMetrics()); + final LatestCompleteStateNexus latestCompleteStateNexus = new DefaultLatestCompleteStateNexus(platformContext); - final boolean useOldStyleIntakeQueue = eventConfig.useOldStyleIntakeQueue(); - - final QueueThread oldStyleIntakeQueue; - if (useOldStyleIntakeQueue) { - oldStyleIntakeQueue = new QueueThreadConfiguration(AdHocThreadManager.getStaticThreadManager()) - .setCapacity(10_000) - .setThreadName("old_style_intake_queue") - .setComponent("platform") - .setHandler(event -> platformWiring.getGossipEventInput().put(event)) - .setMetricsConfiguration(new QueueThreadMetricsConfiguration(metrics).enableMaxSizeMetric()) - .build(); - thingsToStart.add(oldStyleIntakeQueue); - - } else { - oldStyleIntakeQueue = null; - } - - savedStateController = new DefaultSavedStateController(stateConfig); + savedStateController = new DefaultSavedStateController(platformContext); final SignedStateMetrics signedStateMetrics = new SignedStateMetrics(platformContext.getMetrics()); - final StateSignatureCollector stateSignatureCollector = new StateSignatureCollector( - platformContext.getConfiguration().getConfigData(StateConfig.class), signedStateMetrics); + final StateSignatureCollector stateSignatureCollector = + new DefaultStateSignatureCollector(platformContext, signedStateMetrics); final LatestCompleteStateNotifier latestCompleteStateNotifier = new DefaultLatestCompleteStateNotifier(); - statusNexus = new DefaultPlatformStatusNexus(platformContext); + final PlatformStatusNexus statusNexus = new DefaultPlatformStatusNexus(platformContext); + // Future work: all interaction with platform status should happen over the wiring framework. + // Once this is done, these hacky references can be removed. + blocks.platformStatusSupplierReference().set(statusNexus::getCurrentStatus); + blocks.statusActionSubmitterReference() + .set(x -> platformWiring.getStatusActionSubmitter().submitStatusAction(x)); final StateSigner stateSigner = new StateSigner(new PlatformSigner(keysAndCerts), statusNexus); final PcesReplayer pcesReplayer = new PcesReplayer( @@ -438,79 +272,71 @@ public SwirldsPlatform(@NonNull final PlatformComponentBuilder builder) { platformContext.getConfiguration().getConfigData(TransactionConfig.class); // This object makes a copy of the state. After this point, initialState becomes immutable. - swirldStateManager = new SwirldStateManager( - platformContext, - currentAddressBook, - selfId, - new SwirldStateMetrics(platformContext.getMetrics()), - platformWiring.getStatusActionSubmitter(), - initialState.getState(), - appVersion); + swirldStateManager = blocks.swirldStateManager(); + swirldStateManager.setInitialState(initialState.getState()); final EventWindowManager eventWindowManager = new DefaultEventWindowManager(); final ConsensusRoundHandler consensusRoundHandler = new ConsensusRoundHandler( platformContext, swirldStateManager, platformWiring.getStatusActionSubmitter(), appVersion); - final LongSupplier intakeQueueSizeSupplier = - oldStyleIntakeQueue == null ? platformWiring.getIntakeQueueSizeSupplier() : oldStyleIntakeQueue::size; - blocks.intakeQueueSizeSupplierSupplier().set(intakeQueueSizeSupplier); - blocks.isInFreezePeriodReference().set(swirldStateManager::isInFreezePeriod); - - platformWiring.wireExternalComponents(blocks.transactionPool()); + final boolean useOldStyleIntakeQueue = platformContext + .getConfiguration() + .getConfigData(EventConfig.class) + .useOldStyleIntakeQueue(); - final IssHandler issHandler = - new IssHandler(stateConfig, this::haltRequested, SystemExitUtils::handleFatalError, issScratchpad); + final LongSupplier intakeQueueSizeSupplier; + if (useOldStyleIntakeQueue) { + final SyncGossip gossip = (SyncGossip) builder.buildGossip(); + intakeQueueSizeSupplier = () -> gossip.getOldStyleIntakeQueueSize(); + } else { + intakeQueueSizeSupplier = platformWiring.getIntakeQueueSizeSupplier(); + } - final HashLogger hashLogger = - new HashLogger(platformContext.getConfiguration().getConfigData(StateConfig.class)); + blocks.intakeQueueSizeSupplierSupplier().set(intakeQueueSizeSupplier); + blocks.isInFreezePeriodReference().set(swirldStateManager::isInFreezePeriod); - final BirthRoundMigrationShim birthRoundMigrationShim = buildBirthRoundMigrationShim(initialState); + final IssHandler issHandler = new IssHandler( + platformContext, this::haltRequested, SystemExitUtils::handleFatalError, blocks.issScratchpad()); - final SignedStateHasher signedStateHasher = - new DefaultSignedStateHasher(signedStateMetrics, SystemExitUtils::handleFatalError); + final BirthRoundMigrationShim birthRoundMigrationShim = buildBirthRoundMigrationShim(initialState, ancientMode); - final AppNotifier appNotifier = new DefaultAppNotifier(notificationEngine); + final AppNotifier appNotifier = new DefaultAppNotifier(blocks.notificationEngine()); - final PlatformPublisher publisher = - new DefaultPlatformPublisher(preconsensusEventConsumer, snapshotOverrideConsumer); + final PlatformPublisher publisher = new DefaultPlatformPublisher(blocks.applicationCallbacks()); platformWiring.bind( builder, - signedStateFileManager, stateSigner, pcesReplayer, - shadowGraph, stateSignatureCollector, eventWindowManager, consensusRoundHandler, - issDetector, issHandler, - hashLogger, birthRoundMigrationShim, latestCompleteStateNotifier, latestImmutableStateNexus, latestCompleteStateNexus, savedStateController, - signedStateHasher, appNotifier, publisher, statusNexus); - final Hash runningEventHash = initialState.getState().getPlatformState().getRunningEventHash() == null - ? platformContext.getCryptography().getNullHash() - : initialState.getState().getPlatformState().getRunningEventHash(); final Hash legacyRunningEventHash = initialState.getState().getPlatformState().getLegacyRunningEventHash() == null ? platformContext.getCryptography().getNullHash() : initialState.getState().getPlatformState().getLegacyRunningEventHash(); final RunningEventHashOverride runningEventHashOverride = - new RunningEventHashOverride(legacyRunningEventHash, runningEventHash, false); + new RunningEventHashOverride(legacyRunningEventHash, false); platformWiring.updateRunningHash(runningEventHashOverride); // Load the minimum generation into the pre-consensus event writer + final String actualMainClassName = platformContext + .getConfiguration() + .getConfigData(StateConfig.class) + .getMainClassName(blocks.mainClassName()); final List savedStates = - getSavedStateFiles(platformContext, actualMainClassName, selfId, swirldName); + getSavedStateFiles(platformContext, actualMainClassName, selfId, blocks.swirldName()); if (!savedStates.isEmpty()) { // The minimum generation of non-ancient events for the oldest state snapshot on disk. final long minimumGenerationNonAncientForOldestState = @@ -518,56 +344,21 @@ public SwirldsPlatform(@NonNull final PlatformComponentBuilder builder) { platformWiring.getPcesMinimumGenerationToStoreInput().inject(minimumGenerationNonAncientForOldestState); } - final TransactionPool transactionPool = blocks.transactionPool(); + final TransactionPoolNexus transactionPoolNexus = blocks.transactionPoolNexus(); transactionSubmitter = new SwirldTransactionSubmitter( statusNexus, transactionConfig, - transaction -> transactionPool.submitTransaction(transaction, false), - new TransactionMetrics(metrics)); + transaction -> transactionPoolNexus.submitTransaction(transaction, false), + new TransactionMetrics(platformContext.getMetrics())); final boolean startedFromGenesis = initialState.isGenesisState(); - final Consumer eventFromGossipConsumer = oldStyleIntakeQueue == null - ? platformWiring.getGossipEventInput()::put - : event -> { - try { - oldStyleIntakeQueue.put(event); - } catch (final InterruptedException e) { - logger.error( - EXCEPTION.getMarker(), "Interrupted while adding event to old style intake queue", e); - Thread.currentThread().interrupt(); - } - }; - - gossip = new SyncGossip( - platformContext, - blocks.randomBuilder().buildNonCryptographicRandom(), - threadManager, - keysAndCerts, - notificationEngine, - currentAddressBook, - selfId, - appVersion, - epochHash, - shadowGraph, - emergencyRecoveryManager, - eventFromGossipConsumer, - intakeQueueSizeSupplier, - swirldStateManager, - latestCompleteStateNexus, - syncMetrics, - platformWiring.getStatusActionSubmitter(), - statusNexus, - this::loadReconnectState, - this::clearAllPipelines, - blocks.intakeEventCounter(), - () -> emergencyState.getState("emergency reconnect")) {}; - latestImmutableStateNexus.setState(initialState.reserve("set latest immutable to initial state")); if (startedFromGenesis) { initialAncientThreshold = 0; startingRound = 0; + platformWiring.updateEventWindow(EventWindow.getGenesisEventWindow(ancientMode)); } else { initialAncientThreshold = initialState.getState().getPlatformState().getAncientThreshold(); startingRound = initialState.getRound(); @@ -579,7 +370,8 @@ public SwirldsPlatform(@NonNull final PlatformComponentBuilder builder) { savedStateController.registerSignedStateFromDisk(initialState); - loadStateIntoConsensus(initialState); + platformWiring.consensusSnapshotOverride(Objects.requireNonNull( + initialState.getState().getPlatformState().getSnapshot())); // We only load non-ancient events during start up, so the initial expired threshold will be // equal to the ancient threshold when the system first starts. Over time as we get more events, @@ -589,26 +381,13 @@ public SwirldsPlatform(@NonNull final PlatformComponentBuilder builder) { initialAncientThreshold, initialAncientThreshold, AncientMode.getAncientMode(platformContext))); - platformWiring.getIssDetectorWiring().overridingState().put(initialState.reserve("initialize issDetector")); - - // We don't want to send this notification until after we are starting up. - thingsToStart.add(() -> { - // If we loaded from disk then call the appropriate dispatch. - // Let the app know that a state was loaded. - platformWiring - .getNotifierWiring() - .getInputWire(AppNotifier::sendStateLoadedFromDiskNotification) - .put(new StateLoadedFromDiskNotification()); - }); + platformWiring.overrideIssDetectorState(initialState.reserve("initialize issDetector")); } - clearAllPipelines = new LoggingClearables( - RECONNECT.getMarker(), - List.of( - Pair.of(platformWiring, "platformWiring"), - Pair.of(shadowGraph, "shadowGraph"), - Pair.of(transactionPool, "transactionPool"))); - + blocks.getLatestCompleteStateReference() + .set(() -> latestCompleteStateNexus.getState("get latest complete state for reconnect")); + blocks.loadReconnectStateReference().set(this::loadReconnectState); + blocks.clearAllPipelinesForReconnectReference().set(platformWiring::clear); blocks.latestImmutableStateProviderReference().set(latestImmutableStateNexus::getState); } @@ -616,10 +395,12 @@ public SwirldsPlatform(@NonNull final PlatformComponentBuilder builder) { * Builds the birth round migration shim if necessary. * * @param initialState the initial state + * @param ancientMode the ancient mode * @return the birth round migration shim, or null if it is not needed */ @Nullable - private BirthRoundMigrationShim buildBirthRoundMigrationShim(@NonNull final SignedState initialState) { + private BirthRoundMigrationShim buildBirthRoundMigrationShim( + @NonNull final SignedState initialState, @NonNull final AncientMode ancientMode) { if (ancientMode == AncientMode.GENERATION_THRESHOLD) { // We don't need the shim if we haven't migrated to birth round mode. @@ -636,17 +417,11 @@ private BirthRoundMigrationShim buildBirthRoundMigrationShim(@NonNull final Sign platformState.getLowestJudgeGenerationBeforeBirthRoundMode()); } - /** - * Clears all pipelines in preparation for a reconnect. This method is needed to break a circular dependency. - */ - private void clearAllPipelines() { - clearAllPipelines.clear(); - } - /** * {@inheritDoc} */ @Override + @NonNull public NodeId getSelfId() { return selfId; } @@ -706,27 +481,6 @@ private void initializeState(@NonNull final SignedState signedState) { signedState.getState().getInfoString(stateConfig.debugHashDepth())); } - /** - * Loads the signed state data into consensus - * - * @param signedState the state to get the data from - */ - private void loadStateIntoConsensus(@NonNull final SignedState signedState) { - Objects.requireNonNull(signedState); - - platformWiring.consensusSnapshotOverride( - Objects.requireNonNull(signedState.getState().getPlatformState().getSnapshot())); - - // FUTURE WORK: this needs to be updated for birth round compatibility. - final EventWindow eventWindow = new EventWindow( - signedState.getRound(), - signedState.getState().getPlatformState().getAncientThreshold(), - signedState.getState().getPlatformState().getAncientThreshold(), - ancientMode); - - shadowGraph.startWithEventWindow(eventWindow); - } - /** * Used to load the state received from the sender. * @@ -737,10 +491,7 @@ private void loadReconnectState(final SignedState signedState) { logger.info(LogMarker.STATE_HASH.getMarker(), "RECONNECT: loadReconnectState: reloading state"); logger.debug(RECONNECT.getMarker(), "`loadReconnectState` : reloading state"); try { - platformWiring - .getIssDetectorWiring() - .overridingState() - .put(signedState.reserve("reconnect state to issDetector")); + platformWiring.overrideIssDetectorState(signedState.reserve("reconnect state to issDetector")); // It's important to call init() before loading the signed state. The loading process makes copies // of the state, and we want to be sure that the first state in the chain of copies has been initialized. @@ -763,11 +514,8 @@ private void loadReconnectState(final SignedState signedState) { AddressBookUtils.verifyReconnectAddressBooks(getAddressBook(), signedState.getAddressBook()); swirldStateManager.loadFromSignedState(signedState); - - latestReconnectRound.set(signedState.getRound()); - // kick off transition to RECONNECT_COMPLETE before beginning to save the reconnect state to disk - // this guarantees that the platform status will be RECONNECT_COMPLETE before the state is saved + // this guarantees that the platform statusp will be RECONNECT_COMPLETE before the state is saved platformWiring .getStatusActionSubmitter() .submitStatusAction(new ReconnectCompleteAction(signedState.getRound())); @@ -782,7 +530,8 @@ private void loadReconnectState(final SignedState signedState) { platformWiring .getSignatureCollectorStateInput() .put(signedState.reserve("loading reconnect state into sig collector")); - loadStateIntoConsensus(signedState); + platformWiring.consensusSnapshotOverride(Objects.requireNonNull( + signedState.getState().getPlatformState().getSnapshot())); platformWiring .getAddressBookUpdateInput() @@ -790,6 +539,11 @@ private void loadReconnectState(final SignedState signedState) { signedState.getState().getPlatformState().getPreviousAddressBook(), signedState.getState().getPlatformState().getAddressBook())); + final AncientMode ancientMode = platformContext + .getConfiguration() + .getConfigData(EventConfig.class) + .getAncientMode(); + platformWiring.updateEventWindow(new EventWindow( signedState.getRound(), signedState.getState().getPlatformState().getAncientThreshold(), @@ -797,9 +551,7 @@ private void loadReconnectState(final SignedState signedState) { ancientMode)); final RunningEventHashOverride runningEventHashOverride = new RunningEventHashOverride( - signedState.getState().getPlatformState().getLegacyRunningEventHash(), - signedState.getState().getPlatformState().getRunningEventHash(), - true); + signedState.getState().getPlatformState().getLegacyRunningEventHash(), true); platformWiring.updateRunningHash(runningEventHashOverride); platformWiring.getPcesWriterRegisterDiscontinuityInput().inject(signedState.getRound()); @@ -815,11 +567,9 @@ private void loadReconnectState(final SignedState signedState) { } catch (final RuntimeException e) { logger.debug(RECONNECT.getMarker(), "`loadReconnectState` : FAILED, reason: {}", e.getMessage()); // if the loading fails for whatever reason, we clear all data again in case some of it has been loaded - clearAllPipelines(); + platformWiring.clear(); throw e; } - - gossip.resetFallenBehind(); } /** @@ -829,7 +579,7 @@ private void loadReconnectState(final SignedState signedState) { */ private void haltRequested(final String reason) { logger.error(EXCEPTION.getMarker(), "System halt requested. Reason: {}", reason); - gossip.stop(); + platformWiring.stopGossip(); } /** @@ -840,12 +590,12 @@ public void start() { logger.info(STARTUP.getMarker(), "Starting platform {}", selfId); platformWiring.getModel().preventJvmExit(); - thingsToStart.start(); - - metrics.start(); + platformContext.getFileSystemManager().start(); + platformContext.getMetrics().start(); + platformWiring.start(); replayPreconsensusEvents(); - gossip.start(); + platformWiring.startGossip(); } /** @@ -858,7 +608,9 @@ public void start() { * */ public void performPcesRecovery() { - thingsToStart.start(); + platformContext.getFileSystemManager().start(); + platformContext.getMetrics().start(); + platformWiring.start(); replayPreconsensusEvents(); try (final ReservedSignedState reservedState = latestImmutableStateNexus.getState("Get PCES recovery state")) { @@ -903,33 +655,27 @@ private void logSignedStateHash(@NonNull final SignedState signedState) { private void replayPreconsensusEvents() { platformWiring.getStatusActionSubmitter().submitStatusAction(new StartedReplayingEventsAction()); - final boolean emergencyRecoveryNeeded = emergencyRecoveryManager.isEmergencyStateRequired(); - - // if we need to do an emergency recovery, replaying the PCES could cause issues if the - // minimum generation non-ancient is reversed to a smaller value, so we skip it - if (!emergencyRecoveryNeeded) { - final IOIterator iterator = - initialPcesFiles.getEventIterator(initialAncientThreshold, startingRound); + final IOIterator iterator = + initialPcesFiles.getEventIterator(initialAncientThreshold, startingRound); - logger.info( - STARTUP.getMarker(), - "replaying preconsensus event stream starting at generation {}", - initialAncientThreshold); + logger.info( + STARTUP.getMarker(), + "replaying preconsensus event stream starting at generation {}", + initialAncientThreshold); - platformWiring.getPcesReplayerIteratorInput().inject(iterator); - } + platformWiring.getPcesReplayerIteratorInput().inject(iterator); // We have to wait for all the PCES transactions to reach the ISS detector before telling it that PCES replay is // done. The PCES replay will flush the intake pipeline, but we have to flush the hasher // FUTURE WORK: These flushes can be done by the PCES replayer. platformWiring.flushStateHasher(); - platformWiring.getIssDetectorWiring().endOfPcesReplay().put(NoInput.getInstance()); + platformWiring.signalEndOfPcesReplay(); platformWiring .getStatusActionSubmitter() .submitStatusAction( - new DoneReplayingEventsAction(Time.getCurrent().now())); + new DoneReplayingEventsAction(platformContext.getTime().now())); } /** @@ -944,6 +690,7 @@ public boolean createTransaction(@NonNull final byte[] transaction) { * {@inheritDoc} */ @Override + @NonNull public PlatformContext getContext() { return platformContext; } @@ -952,6 +699,7 @@ public PlatformContext getContext() { * {@inheritDoc} */ @Override + @NonNull public NotificationEngine getNotificationEngine() { return notificationEngine; } @@ -960,7 +708,8 @@ public NotificationEngine getNotificationEngine() { * {@inheritDoc} */ @Override - public Signature sign(final byte[] data) { + @NonNull + public Signature sign(@NonNull final byte[] data) { return new PlatformSigner(keysAndCerts).sign(data); } @@ -970,6 +719,7 @@ public Signature sign(final byte[] data) { * @return AddressBook */ @Override + @NonNull public AddressBook getAddressBook() { return currentAddressBook; } @@ -979,8 +729,8 @@ public AddressBook getAddressBook() { */ @SuppressWarnings("unchecked") @Override - public @NonNull AutoCloseableWrapper getLatestImmutableState( - @NonNull final String reason) { + @NonNull + public AutoCloseableWrapper getLatestImmutableState(@NonNull final String reason) { final ReservedSignedState wrapper = latestImmutableStateNexus.getState(reason); return wrapper == null ? AutoCloseableWrapper.empty() diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/Utilities.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/Utilities.java index fbdfaaded54c..f3e7529722d2 100644 --- a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/Utilities.java +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/Utilities.java @@ -16,6 +16,7 @@ package com.swirlds.platform; +import com.hedera.pbj.runtime.io.buffer.Bytes; import com.swirlds.common.io.streams.SerializableDataInputStream; import com.swirlds.common.io.streams.SerializableDataOutputStream; import com.swirlds.common.platform.NodeId; @@ -24,6 +25,7 @@ import com.swirlds.platform.network.PeerInfo; import com.swirlds.platform.system.address.AddressBook; import edu.umd.cs.findbugs.annotations.NonNull; +import edu.umd.cs.findbugs.annotations.Nullable; import java.io.IOException; import java.net.SocketException; import java.util.Arrays; @@ -95,34 +97,34 @@ public static long[][] deepClone(long[][] original) { * A null array is considered less than a non-null array. * This is the same as Java.Util.Arrays#compar * - * @param sig1 + * @param b1 * first array - * @param sig2 + * @param b2 * second array * @return 1 if first is bigger, -1 if second, 0 otherwise */ - public static int arrayCompare(byte[] sig1, byte[] sig2) { - if (sig1 == null && sig2 == null) { + public static int arrayCompare(@Nullable final Bytes b1, @Nullable final Bytes b2) { + if (b1 == null && b2 == null) { return 0; } - if (sig1 == null && sig2 != null) { + if (b1 == null && b2 != null) { return -1; } - if (sig1 != null && sig2 == null) { + if (b1 != null && b2 == null) { return 1; } - for (int i = 0; i < Math.min(sig1.length, sig2.length); i++) { - if (sig1[i] < sig2[i]) { + for (int i = 0; i < Math.min(b1.length(), b2.length()); i++) { + if (b1.getByte(i) < b2.getByte(i)) { return -1; } - if (sig1[i] > sig2[i]) { + if (b1.getByte(i) > b2.getByte(i)) { return 1; } } - if (sig1.length < sig2.length) { + if (b1.length() < b2.length()) { return -1; } - if (sig1.length > sig2.length) { + if (b1.length() > b2.length()) { return 1; } return 0; @@ -133,34 +135,32 @@ public static int arrayCompare(byte[] sig1, byte[] sig2) { * XORed with whitening before the comparison. The XOR doesn't actually happen, and the arrays are left * unchanged. * - * @param sig1 + * @param a1 * first array - * @param sig2 + * @param a2 * second array * @param whitening * the array virtually XORed with the other two * @return 1 if first is bigger, -1 if second, 0 otherwise */ - public static int arrayCompare(byte[] sig1, byte[] sig2, byte[] whitening) { - int maxLen; - int minLen; - if (sig1 == null && sig2 == null) { + public static int arrayCompare(@Nullable final Bytes a1, @Nullable final Bytes a2, byte[] whitening) { + if (a1 == null && a2 == null) { return 0; } - if (sig1 != null && sig2 == null) { + if (a1 != null && a2 == null) { return 1; } - if (sig1 == null && sig2 != null) { + if (a1 == null && a2 != null) { return -1; } - maxLen = Math.max(sig1.length, sig2.length); - minLen = Math.min(sig1.length, sig2.length); + final int maxLen = (int) Math.max(a1.length(), a2.length()); + final int minLen = (int) Math.min(a1.length(), a2.length()); if (whitening.length < maxLen) { whitening = Arrays.copyOf(whitening, maxLen); } for (int i = 0; i < minLen; i++) { - int b1 = sig1[i] ^ whitening[i]; - int b2 = sig2[i] ^ whitening[i]; + final int b1 = a1.getByte(i) ^ whitening[i]; + final int b2 = a2.getByte(i) ^ whitening[i]; if (b1 > b2) { return 1; } @@ -168,10 +168,10 @@ public static int arrayCompare(byte[] sig1, byte[] sig2, byte[] whitening) { return -1; } } - if (sig1.length > sig2.length) { + if (a1.length() > a2.length()) { return 1; } - if (sig1.length < sig2.length) { + if (a1.length() < a2.length()) { return -1; } return 0; diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/builder/ApplicationCallbacks.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/builder/ApplicationCallbacks.java new file mode 100644 index 000000000000..eeae6799db14 --- /dev/null +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/builder/ApplicationCallbacks.java @@ -0,0 +1,35 @@ +/* + * Copyright (C) 2024 Hedera Hashgraph, LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.swirlds.platform.builder; + +import com.swirlds.platform.consensus.ConsensusSnapshot; +import com.swirlds.platform.event.GossipEvent; +import edu.umd.cs.findbugs.annotations.Nullable; +import java.util.function.Consumer; + +/** + * A collection of callbacks that the application can provide to the platform to be notified of certain events. + * + * @param preconsensusEventConsumer a consumer that will be called on preconsensus events in topological order + * @param snapshotOverrideConsumer a consumer that will be called when the current consensus snapshot is overridden + * (i.e. at reconnect/restart boundaries) + * @param staleEventConsumer a consumer that will be called when a stale self event is detected + */ +public record ApplicationCallbacks( + @Nullable Consumer preconsensusEventConsumer, + @Nullable Consumer snapshotOverrideConsumer, + @Nullable Consumer staleEventConsumer) {} diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/builder/PlatformBuilder.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/builder/PlatformBuilder.java index 88444a919fa9..6a04369f62d6 100644 --- a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/builder/PlatformBuilder.java +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/builder/PlatformBuilder.java @@ -18,6 +18,8 @@ import static com.swirlds.common.io.utility.FileUtils.getAbsolutePath; import static com.swirlds.common.io.utility.FileUtils.rethrowIO; +import static com.swirlds.common.threading.manager.AdHocThreadManager.getStaticThreadManager; +import static com.swirlds.logging.legacy.LogMarker.STARTUP; import static com.swirlds.platform.builder.PlatformBuildConstants.DEFAULT_CONFIG_FILE_NAME; import static com.swirlds.platform.builder.PlatformBuildConstants.DEFAULT_SETTINGS_FILE_NAME; import static com.swirlds.platform.builder.internal.StaticPlatformBuilder.doStaticSetup; @@ -29,23 +31,25 @@ import static com.swirlds.platform.util.BootstrapUtils.checkNodesToRun; import static com.swirlds.platform.util.BootstrapUtils.detectSoftwareUpgrade; -import com.swirlds.base.time.Time; -import com.swirlds.common.context.DefaultPlatformContext; import com.swirlds.common.context.PlatformContext; import com.swirlds.common.crypto.Cryptography; import com.swirlds.common.crypto.CryptographyFactory; import com.swirlds.common.crypto.CryptographyHolder; +import com.swirlds.common.io.filesystem.FileSystemManager; +import com.swirlds.common.io.filesystem.FileSystemManagerFactory; import com.swirlds.common.merkle.crypto.MerkleCryptoFactory; import com.swirlds.common.merkle.crypto.MerkleCryptography; import com.swirlds.common.merkle.crypto.MerkleCryptographyFactory; +import com.swirlds.common.notification.NotificationEngine; import com.swirlds.common.platform.NodeId; +import com.swirlds.common.scratchpad.Scratchpad; +import com.swirlds.common.wiring.model.WiringModel; +import com.swirlds.common.wiring.model.WiringModelBuilder; import com.swirlds.config.api.Configuration; import com.swirlds.config.api.ConfigurationBuilder; import com.swirlds.metrics.api.Metrics; import com.swirlds.platform.ParameterProvider; import com.swirlds.platform.SwirldsPlatform; -import com.swirlds.platform.config.BasicConfig; -import com.swirlds.platform.config.StateConfig; import com.swirlds.platform.config.internal.PlatformConfigUtils; import com.swirlds.platform.config.legacy.LegacyConfigProperties; import com.swirlds.platform.config.legacy.LegacyConfigPropertiesLoader; @@ -56,22 +60,25 @@ import com.swirlds.platform.event.preconsensus.PcesFileReader; import com.swirlds.platform.event.preconsensus.PcesFileTracker; import com.swirlds.platform.eventhandling.EventConfig; -import com.swirlds.platform.eventhandling.TransactionPool; import com.swirlds.platform.gossip.DefaultIntakeEventCounter; import com.swirlds.platform.gossip.IntakeEventCounter; import com.swirlds.platform.gossip.NoOpIntakeEventCounter; import com.swirlds.platform.gossip.sync.config.SyncConfig; -import com.swirlds.platform.recovery.EmergencyRecoveryManager; +import com.swirlds.platform.pool.TransactionPoolNexus; import com.swirlds.platform.state.State; +import com.swirlds.platform.state.SwirldStateManager; import com.swirlds.platform.state.address.AddressBookInitializer; +import com.swirlds.platform.state.iss.IssScratchpad; import com.swirlds.platform.state.signed.ReservedSignedState; import com.swirlds.platform.system.Platform; import com.swirlds.platform.system.SoftwareVersion; import com.swirlds.platform.system.StaticSoftwareVersion; import com.swirlds.platform.system.SwirldState; import com.swirlds.platform.system.address.AddressBook; +import com.swirlds.platform.system.status.StatusActionSubmitter; import com.swirlds.platform.util.BootstrapUtils; import com.swirlds.platform.util.RandomBuilder; +import com.swirlds.platform.wiring.PlatformSchedulersConfig; import edu.umd.cs.findbugs.annotations.NonNull; import edu.umd.cs.findbugs.annotations.Nullable; import java.io.IOException; @@ -79,18 +86,22 @@ import java.nio.file.Path; import java.util.HashSet; import java.util.List; -import java.util.Map; import java.util.Objects; import java.util.Set; +import java.util.concurrent.ForkJoinPool; import java.util.concurrent.atomic.AtomicReference; import java.util.function.Consumer; import java.util.function.Supplier; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; /** * Builds a {@link SwirldsPlatform} instance. */ public final class PlatformBuilder { + private static final Logger logger = LogManager.getLogger(PlatformBuilder.class); + private final String appName; private final SoftwareVersion softwareVersion; private final Supplier genesisStateBuilder; @@ -100,6 +111,16 @@ public final class PlatformBuilder { private PlatformContext platformContext; private ConfigurationBuilder configurationBuilder; + /** + * An address book that is used to bootstrap the system. Traditionally read from config.txt. + */ + private AddressBook bootstrapAddressBook; + + /** + * This node's cryptographic keys. + */ + private KeysAndCerts keysAndCerts; + /** * The path to the configuration file (i.e. the file with the address book). */ @@ -110,8 +131,19 @@ public final class PlatformBuilder { */ private Path settingsPath; + /** + * The wiring model to use for this platform. + */ + private WiringModel model; + + /** + * The source of non-cryptographic randomness for this platform. + */ + private RandomBuilder randomBuilder; + private Consumer preconsensusEventConsumer; private Consumer snapshotOverrideConsumer; + private Consumer staleEventConsumer; /** * False if this builder has not yet been used to build a platform (or platform component builder), true if it has. @@ -308,6 +340,75 @@ public PlatformBuilder withConsensusSnapshotOverrideCallback( return this; } + /** + * Register a callback that is called when a stale self event is detected (i.e. an event that will never reach + * consensus). Depending on the use case, it may be a good idea to resubmit the transactions in the stale event. + *

    + * Stale event detection is guaranteed to catch all stale self events as long as the node remains online. However, + * if the node restarts or reconnects, any event that went stale "in the gap" may not be detected. + * + * @param staleEventConsumer the callback to register + * @return this + */ + @NonNull + public PlatformBuilder withStaleEventCallback(@NonNull final Consumer staleEventConsumer) { + throwIfAlreadyUsed(); + this.staleEventConsumer = Objects.requireNonNull(staleEventConsumer); + return this; + } + + /** + * Provide the address book to use for bootstrapping the system. If not provided then the address book is read from + * the config.txt file. + * + * @param bootstrapAddressBook the address book to use for bootstrapping + * @return this + */ + @NonNull + public PlatformBuilder withBootstrapAddressBook(@NonNull final AddressBook bootstrapAddressBook) { + throwIfAlreadyUsed(); + this.bootstrapAddressBook = Objects.requireNonNull(bootstrapAddressBook); + return this; + } + + /** + * Provide the cryptographic keys to use for this node. + * + * @param keysAndCerts the cryptographic keys to use + * @return this + */ + @NonNull + public PlatformBuilder withKeysAndCerts(@NonNull final KeysAndCerts keysAndCerts) { + throwIfAlreadyUsed(); + this.keysAndCerts = Objects.requireNonNull(keysAndCerts); + return this; + } + + /** + * Provide the wiring model to use for this platform. + * + * @param model the wiring model to use + * @return this + */ + public PlatformBuilder withModel(@NonNull final WiringModel model) { + throwIfAlreadyUsed(); + this.model = Objects.requireNonNull(model); + return this; + } + + /** + * Provide the source of non-cryptographic randomness for this platform. + * + * @param randomBuilder the source of non-cryptographic randomness + * @return this + */ + @NonNull + public PlatformBuilder withRandomBuilder(@NonNull final RandomBuilder randomBuilder) { + throwIfAlreadyUsed(); + this.randomBuilder = Objects.requireNonNull(randomBuilder); + return this; + } + /** * Build the configuration for the node. * @@ -369,7 +470,9 @@ public static PlatformContext buildPlatformContext( setupGlobalMetrics(configuration); final Metrics metrics = getMetricsProvider().createPlatformMetrics(selfId); - return new DefaultPlatformContext(configuration, metrics, cryptography, Time.getCurrent()); + final FileSystemManager fileSystemManager = + FileSystemManagerFactory.getInstance().createFileSystemManager(configuration, metrics, selfId); + return PlatformContext.create(configuration, metrics, cryptography, fileSystemManager); } /** @@ -390,7 +493,6 @@ private void throwIfAlreadyUsed() { */ @NonNull public PlatformComponentBuilder buildComponentBuilder() { - throwIfAlreadyUsed(); used = true; @@ -408,19 +510,17 @@ public PlatformComponentBuilder buildComponentBuilder() { final boolean firstPlatform = doStaticSetup(configuration, configPath); - final AddressBook configAddressBook = loadConfigAddressBook(); + final AddressBook boostrapAddressBook = + this.bootstrapAddressBook == null ? loadConfigAddressBook() : this.bootstrapAddressBook; checkNodesToRun(List.of(selfId)); - final Map keysAndCerts = initNodeSecurity(configAddressBook, configuration); + final KeysAndCerts keysAndCerts = this.keysAndCerts == null + ? initNodeSecurity(boostrapAddressBook, configuration).get(selfId) + : this.keysAndCerts; // the AddressBook is not changed after this point, so we calculate the hash now - platformContext.getCryptography().digestSync(configAddressBook); - - final BasicConfig basicConfig = configuration.getConfigData(BasicConfig.class); - final StateConfig stateConfig = configuration.getConfigData(StateConfig.class); - final EmergencyRecoveryManager emergencyRecoveryManager = - new EmergencyRecoveryManager(stateConfig, basicConfig.getEmergencyRecoveryFileLoadDir()); + platformContext.getCryptography().digestSync(boostrapAddressBook); final ReservedSignedState initialState = getInitialState( platformContext, @@ -429,8 +529,7 @@ public PlatformComponentBuilder buildComponentBuilder() { appName, swirldName, selfId, - configAddressBook, - emergencyRecoveryManager); + boostrapAddressBook); final boolean softwareUpgrade = detectSoftwareUpgrade(softwareVersion, initialState.get()); @@ -440,7 +539,7 @@ public PlatformComponentBuilder buildComponentBuilder() { softwareVersion, softwareUpgrade, initialState.get(), - configAddressBook.copy(), + boostrapAddressBook.copy(), platformContext); if (addressBookInitializer.hasAddressBookChanged()) { @@ -498,24 +597,68 @@ public PlatformComponentBuilder buildComponentBuilder() { throw new UncheckedIOException(e); } + final Scratchpad issScratchpad = + Scratchpad.create(platformContext, selfId, IssScratchpad.class, "platform.iss"); + issScratchpad.logContents(); + + final ApplicationCallbacks callbacks = + new ApplicationCallbacks(preconsensusEventConsumer, snapshotOverrideConsumer, staleEventConsumer); + + final AtomicReference statusActionSubmitterAtomicReference = new AtomicReference<>(); + final SwirldStateManager swirldStateManager = new SwirldStateManager( + platformContext, + initialState.get().getAddressBook(), + selfId, + x -> statusActionSubmitterAtomicReference.get().submitStatusAction(x), + softwareVersion); + + if (model == null) { + final PlatformSchedulersConfig schedulersConfig = + platformContext.getConfiguration().getConfigData(PlatformSchedulersConfig.class); + + final int coreCount = Runtime.getRuntime().availableProcessors(); + final int parallelism = (int) Math.max( + 1, schedulersConfig.defaultPoolMultiplier() * coreCount + schedulersConfig.defaultPoolConstant()); + final ForkJoinPool defaultPool = + platformContext.getExecutorFactory().createForkJoinPool(parallelism); + logger.info(STARTUP.getMarker(), "Default platform pool parallelism: {}", parallelism); + + model = WiringModelBuilder.create(platformContext) + .withDefaultPool(defaultPool) + .build(); + } + + if (randomBuilder == null) { + randomBuilder = new RandomBuilder(); + } + final PlatformBuildingBlocks buildingBlocks = new PlatformBuildingBlocks( platformContext, - keysAndCerts.get(selfId), + model, + keysAndCerts, selfId, appName, swirldName, softwareVersion, initialState, - emergencyRecoveryManager, + callbacks, preconsensusEventConsumer, snapshotOverrideConsumer, intakeEventCounter, - new RandomBuilder(), - new TransactionPool(platformContext), + randomBuilder, + new TransactionPoolNexus(platformContext), new AtomicReference<>(), new AtomicReference<>(), new AtomicReference<>(), initialPcesFiles, + issScratchpad, + NotificationEngine.buildEngine(getStaticThreadManager()), + new AtomicReference<>(), + statusActionSubmitterAtomicReference, + swirldStateManager, + new AtomicReference<>(), + new AtomicReference<>(), + new AtomicReference<>(), firstPlatform); return new PlatformComponentBuilder(buildingBlocks); diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/builder/PlatformBuildingBlocks.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/builder/PlatformBuildingBlocks.java index f528d476533c..c222d129bd2d 100644 --- a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/builder/PlatformBuildingBlocks.java +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/builder/PlatformBuildingBlocks.java @@ -17,16 +17,24 @@ package com.swirlds.platform.builder; import com.swirlds.common.context.PlatformContext; +import com.swirlds.common.notification.NotificationEngine; import com.swirlds.common.platform.NodeId; +import com.swirlds.common.scratchpad.Scratchpad; +import com.swirlds.common.wiring.model.WiringModel; import com.swirlds.platform.consensus.ConsensusSnapshot; import com.swirlds.platform.crypto.KeysAndCerts; import com.swirlds.platform.event.GossipEvent; import com.swirlds.platform.event.preconsensus.PcesFileTracker; -import com.swirlds.platform.eventhandling.TransactionPool; import com.swirlds.platform.gossip.IntakeEventCounter; -import com.swirlds.platform.recovery.EmergencyRecoveryManager; +import com.swirlds.platform.pool.TransactionPoolNexus; +import com.swirlds.platform.state.SwirldStateManager; +import com.swirlds.platform.state.iss.IssScratchpad; import com.swirlds.platform.state.signed.ReservedSignedState; +import com.swirlds.platform.state.signed.SignedState; import com.swirlds.platform.system.SoftwareVersion; +import com.swirlds.platform.system.address.AddressBook; +import com.swirlds.platform.system.status.PlatformStatus; +import com.swirlds.platform.system.status.StatusActionSubmitter; import com.swirlds.platform.util.RandomBuilder; import edu.umd.cs.findbugs.annotations.NonNull; import edu.umd.cs.findbugs.annotations.Nullable; @@ -36,57 +44,96 @@ import java.util.function.Function; import java.util.function.LongSupplier; import java.util.function.Predicate; +import java.util.function.Supplier; /** * This record contains core utilities and basic objects needed to build a platform. It should not contain any platform * components. * - * @param platformContext the context for this platform - * @param keysAndCerts an object holding all the public/private key pairs and the CSPRNG state - * for this member - * @param selfId the ID for this node - * @param mainClassName the name of the app class inheriting from SwirldMain - * @param swirldName the name of the swirld being run - * @param appVersion the current version of the running application - * @param initialState the initial state of the platform - * @param emergencyRecoveryManager used in emergency recovery. - * @param preconsensusEventConsumer the consumer for preconsensus events, null if publishing this data has - * not been enabled - * @param snapshotOverrideConsumer the consumer for snapshot overrides, null if publishing this data has - * not been enabled - * @param intakeEventCounter counts events that have been received by gossip but not yet inserted - * into gossip event storage, per peer - * @param randomBuilder a builder for creating random number generators - * @param transactionPool provides transactions to be added to new events - * @param intakeQueueSizeSupplierSupplier supplies a method which supplies the size of the intake queue. This hack - * is required due to the lack of a platform health monitor. - * @param isInFreezePeriodReference a reference to a predicate that determines if a timestamp is in the - * freeze period, this can be deleted as soon as the CES is retired. - * @param latestImmutableStateProviderReference a reference to a method that supplies the latest immutable state. Input - * argument is a string explaining why we are getting this state (for - * debugging). Return value may be null (implementation detail of - * underlying data source), this indirection can be removed once states are - * passed within the wiring framework - * @param initialPcesFiles the initial set of PCES files present when the node starts - * @param firstPlatform if this is the first platform being built (there is static setup that - * needs to be done, long term plan is to stop using static variables) + * @param platformContext the context for this platform + * @param model the wiring model for this platform + * @param keysAndCerts an object holding all the public/private key pairs and the CSPRNG state + * for this member + * @param selfId the ID for this node + * @param mainClassName the name of the app class inheriting from SwirldMain + * @param swirldName the name of the swirld being run + * @param appVersion the current version of the running application + * @param initialState the initial state of the platform + * @param applicationCallbacks the callbacks that the platform will call when certain events happen + * @param preconsensusEventConsumer the consumer for preconsensus events, null if publishing this data has + * not been enabled + * @param snapshotOverrideConsumer the consumer for snapshot overrides, null if publishing this data has + * not been enabled + * @param intakeEventCounter counts events that have been received by gossip but not yet inserted + * into gossip event storage, per peer + * @param randomBuilder a builder for creating random number generators + * @param transactionPoolNexus provides transactions to be added to new events + * @param intakeQueueSizeSupplierSupplier supplies a method which supplies the size of the intake queue. This + * hack is required due to the lack of a platform health monitor. + * @param isInFreezePeriodReference a reference to a predicate that determines if a timestamp is in the + * freeze period, this can be deleted as soon as the CES is retired. + * @param latestImmutableStateProviderReference a reference to a method that supplies the latest immutable state. Input + * argument is a string explaining why we are getting this state (for + * debugging). Return value may be null (implementation detail of + * underlying data source), this indirection can be removed once states + * are passed within the wiring framework + * @param initialPcesFiles the initial set of PCES files present when the node starts + * @param issScratchpad scratchpad storage for ISS recovery + * @param notificationEngine for sending notifications to the application (legacy pattern) + * @param firstPlatform if this is the first platform being built (there is static setup that + * needs to be done, long term plan is to stop using static variables) + * @param platformStatusSupplierReference a reference to a supplier that supplies the platform status, this can + * be deleted once the distribution of the platform status is fully + * managed by the wiring framework + * @param statusActionSubmitterReference a reference to the status action submitter, this can be deleted once + * platform status management is handled by the wiring framework + * @param getLatestCompleteStateReference a reference to a supplier that supplies the latest immutable state, + * this is exposed here due to reconnect, can be removed once reconnect is + * made compatible with the wiring framework + * @param loadReconnectStateReference a reference to a consumer that loads the state for reconnect, can be + * removed once reconnect is made compatible with the wiring framework + * @param clearAllPipelinesForReconnectReference a reference to a runnable that clears all pipelines for reconnect, can + * be removed once reconnect is made compatible with the wiring framework + * @param swirldStateManager responsible for the mutable state, this is exposed here due to + * reconnect, can be removed once reconnect is made compatible with the + * wiring framework */ public record PlatformBuildingBlocks( @NonNull PlatformContext platformContext, + @NonNull WiringModel model, @NonNull KeysAndCerts keysAndCerts, @NonNull NodeId selfId, @NonNull String mainClassName, @NonNull String swirldName, @NonNull SoftwareVersion appVersion, @NonNull ReservedSignedState initialState, - @NonNull EmergencyRecoveryManager emergencyRecoveryManager, + @NonNull ApplicationCallbacks applicationCallbacks, @Nullable Consumer preconsensusEventConsumer, @Nullable Consumer snapshotOverrideConsumer, @NonNull IntakeEventCounter intakeEventCounter, @NonNull RandomBuilder randomBuilder, - @NonNull TransactionPool transactionPool, + @NonNull TransactionPoolNexus transactionPoolNexus, @NonNull AtomicReference intakeQueueSizeSupplierSupplier, @NonNull AtomicReference> isInFreezePeriodReference, @NonNull AtomicReference> latestImmutableStateProviderReference, @NonNull PcesFileTracker initialPcesFiles, - boolean firstPlatform) {} + @NonNull Scratchpad issScratchpad, + @NonNull NotificationEngine notificationEngine, + @NonNull AtomicReference> platformStatusSupplierReference, + @NonNull AtomicReference statusActionSubmitterReference, + @NonNull SwirldStateManager swirldStateManager, + @NonNull AtomicReference> getLatestCompleteStateReference, + @NonNull AtomicReference> loadReconnectStateReference, + @NonNull AtomicReference clearAllPipelinesForReconnectReference, + boolean firstPlatform) { + + /** + * Get the address book from the initial state. + * + * @return the initial address book + */ + @NonNull + public AddressBook initialAddressBook() { + return initialState.get().getState().getPlatformState().getAddressBook(); + } +} diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/builder/PlatformComponentBuilder.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/builder/PlatformComponentBuilder.java index 411cc3e58f1e..494920b28097 100644 --- a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/builder/PlatformComponentBuilder.java +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/builder/PlatformComponentBuilder.java @@ -19,10 +19,14 @@ import static com.swirlds.platform.builder.internal.StaticPlatformBuilder.getGlobalMetrics; import static com.swirlds.platform.builder.internal.StaticPlatformBuilder.getMetricsProvider; import static com.swirlds.platform.gui.internal.BrowserWindowManager.getPlatforms; +import static com.swirlds.platform.state.iss.IssDetector.DO_NOT_IGNORE_ROUNDS; +import com.swirlds.common.merkle.utility.SerializableLong; +import com.swirlds.common.threading.manager.AdHocThreadManager; import com.swirlds.platform.SwirldsPlatform; import com.swirlds.platform.components.consensus.ConsensusEngine; import com.swirlds.platform.components.consensus.DefaultConsensusEngine; +import com.swirlds.platform.config.StateConfig; import com.swirlds.platform.crypto.CryptoStatic; import com.swirlds.platform.crypto.PlatformSigner; import com.swirlds.platform.event.creation.DefaultEventCreationManager; @@ -33,21 +37,22 @@ import com.swirlds.platform.event.deduplication.StandardEventDeduplicator; import com.swirlds.platform.event.hashing.DefaultEventHasher; import com.swirlds.platform.event.hashing.EventHasher; -import com.swirlds.platform.event.linking.GossipLinker; -import com.swirlds.platform.event.linking.InOrderLinker; import com.swirlds.platform.event.orphan.DefaultOrphanBuffer; import com.swirlds.platform.event.orphan.OrphanBuffer; import com.swirlds.platform.event.preconsensus.DefaultPcesSequencer; import com.swirlds.platform.event.preconsensus.DefaultPcesWriter; +import com.swirlds.platform.event.preconsensus.PcesConfig; import com.swirlds.platform.event.preconsensus.PcesFileManager; import com.swirlds.platform.event.preconsensus.PcesSequencer; import com.swirlds.platform.event.preconsensus.PcesWriter; import com.swirlds.platform.event.preconsensus.durability.DefaultRoundDurabilityBuffer; import com.swirlds.platform.event.preconsensus.durability.RoundDurabilityBuffer; -import com.swirlds.platform.event.runninghash.DefaultRunningEventHasher; -import com.swirlds.platform.event.runninghash.RunningEventHasher; import com.swirlds.platform.event.signing.DefaultSelfEventSigner; import com.swirlds.platform.event.signing.SelfEventSigner; +import com.swirlds.platform.event.stale.DefaultStaleEventDetector; +import com.swirlds.platform.event.stale.DefaultTransactionResubmitter; +import com.swirlds.platform.event.stale.StaleEventDetector; +import com.swirlds.platform.event.stale.TransactionResubmitter; import com.swirlds.platform.event.stream.ConsensusEventStream; import com.swirlds.platform.event.stream.DefaultConsensusEventStream; import com.swirlds.platform.event.validation.DefaultEventSignatureValidator; @@ -56,16 +61,30 @@ import com.swirlds.platform.event.validation.InternalEventValidator; import com.swirlds.platform.eventhandling.DefaultTransactionPrehandler; import com.swirlds.platform.eventhandling.TransactionPrehandler; +import com.swirlds.platform.gossip.SyncGossip; import com.swirlds.platform.internal.EventImpl; +import com.swirlds.platform.pool.DefaultTransactionPool; +import com.swirlds.platform.pool.TransactionPool; +import com.swirlds.platform.state.hasher.DefaultStateHasher; +import com.swirlds.platform.state.hasher.StateHasher; +import com.swirlds.platform.state.hashlogger.DefaultHashLogger; +import com.swirlds.platform.state.hashlogger.HashLogger; +import com.swirlds.platform.state.iss.DefaultIssDetector; +import com.swirlds.platform.state.iss.IssDetector; +import com.swirlds.platform.state.iss.IssScratchpad; import com.swirlds.platform.state.signed.DefaultSignedStateSentinel; import com.swirlds.platform.state.signed.DefaultStateGarbageCollector; import com.swirlds.platform.state.signed.ReservedSignedState; import com.swirlds.platform.state.signed.SignedStateSentinel; import com.swirlds.platform.state.signed.StateGarbageCollector; +import com.swirlds.platform.state.snapshot.DefaultStateSnapshotManager; +import com.swirlds.platform.state.snapshot.StateSnapshotManager; import com.swirlds.platform.system.Platform; +import com.swirlds.platform.system.address.Address; import com.swirlds.platform.system.status.DefaultStatusStateMachine; import com.swirlds.platform.system.status.StatusStateMachine; import com.swirlds.platform.util.MetricsDocUtils; +import com.swirlds.platform.wiring.components.Gossip; import edu.umd.cs.findbugs.annotations.NonNull; import java.io.IOException; import java.io.UncheckedIOException; @@ -100,9 +119,7 @@ public class PlatformComponentBuilder { private SelfEventSigner selfEventSigner; private StateGarbageCollector stateGarbageCollector; private OrphanBuffer orphanBuffer; - private RunningEventHasher runningEventHasher; private EventCreationManager eventCreationManager; - private InOrderLinker inOrderLinker; private ConsensusEngine consensusEngine; private ConsensusEventStream consensusEventStream; private SignedStateSentinel signedStateSentinel; @@ -111,6 +128,16 @@ public class PlatformComponentBuilder { private StatusStateMachine statusStateMachine; private TransactionPrehandler transactionPrehandler; private PcesWriter pcesWriter; + private IssDetector issDetector; + private Gossip gossip; + private StaleEventDetector staleEventDetector; + private TransactionResubmitter transactionResubmitter; + private TransactionPool transactionPool; + private StateHasher stateHasher; + private StateSnapshotManager stateSnapshotManager; + private HashLogger hashLogger; + + private boolean metricsDocumentationEnabled = true; /** * False if this builder has not yet been used to build a platform (or platform component builder), true if it has. @@ -159,18 +186,32 @@ public Platform build() { try (final ReservedSignedState initialState = blocks.initialState()) { return new SwirldsPlatform(this); } finally { - - // Future work: eliminate the static variables that require this code to exist - if (blocks.firstPlatform()) { - MetricsDocUtils.writeMetricsDocumentToFile( - getGlobalMetrics(), - getPlatforms(), - blocks.platformContext().getConfiguration()); - getMetricsProvider().start(); + if (metricsDocumentationEnabled) { + // Future work: eliminate the static variables that require this code to exist + if (blocks.firstPlatform()) { + MetricsDocUtils.writeMetricsDocumentToFile( + getGlobalMetrics(), + getPlatforms(), + blocks.platformContext().getConfiguration()); + getMetricsProvider().start(); + } } } } + /** + * If enabled, building this object will cause a metrics document to be generated. Default is true. + * + * @param metricsDocumentationEnabled whether to generate a metrics document + * @return this builder + */ + @NonNull + public PlatformComponentBuilder withMetricsDocumentationEnabled(final boolean metricsDocumentationEnabled) { + throwIfAlreadyUsed(); + this.metricsDocumentationEnabled = metricsDocumentationEnabled; + return this; + } + /** * Provide an event hasher in place of the platform's default event hasher. * @@ -230,13 +271,7 @@ public PlatformComponentBuilder withInternalEventValidator( @NonNull public InternalEventValidator buildInternalEventValidator() { if (internalEventValidator == null) { - final boolean singleNodeNetwork = blocks.initialState() - .get() - .getState() - .getPlatformState() - .getAddressBook() - .getSize() - == 1; + final boolean singleNodeNetwork = blocks.initialAddressBook().getSize() == 1; internalEventValidator = new DefaultInternalEventValidator( blocks.platformContext(), singleNodeNetwork, blocks.intakeEventCounter()); } @@ -307,7 +342,7 @@ public EventSignatureValidator buildEventSignatureValidator() { CryptoStatic::verifySignature, blocks.appVersion(), blocks.initialState().get().getState().getPlatformState().getPreviousAddressBook(), - blocks.initialState().get().getState().getPlatformState().getAddressBook(), + blocks.initialAddressBook(), blocks.intakeEventCounter()); } return eventSignatureValidator; @@ -410,38 +445,6 @@ public PlatformComponentBuilder withOrphanBuffer(@NonNull final OrphanBuffer orp return this; } - /** - * Provide a running event hasher in place of the platform's default running event hasher. - * - * @param runningEventHasher the running event hasher to use - * @return this builder - */ - @NonNull - public PlatformComponentBuilder withRunningEventHasher(@NonNull final RunningEventHasher runningEventHasher) { - throwIfAlreadyUsed(); - if (this.runningEventHasher != null) { - throw new IllegalStateException("Running event hasher has already been set"); - } - this.runningEventHasher = Objects.requireNonNull(runningEventHasher); - return this; - } - - /** - * Build the running event hasher if it has not yet been built. If one has been provided via - * {@link #withRunningEventHasher(RunningEventHasher)}, that hasher will be used. If this method is called more than - * once, only the first call will build the running event hasher. Otherwise, the default hasher will be created and - * returned. - * - * @return the running event hasher - */ - @NonNull - public RunningEventHasher buildRunningEventHasher() { - if (runningEventHasher == null) { - runningEventHasher = new DefaultRunningEventHasher(); - } - return runningEventHasher; - } - /** * Provide an event creation manager in place of the platform's default event creation manager. * @@ -473,53 +476,20 @@ public EventCreationManager buildEventCreationManager() { blocks.platformContext(), blocks.randomBuilder().buildNonCryptographicRandom(), data -> new PlatformSigner(blocks.keysAndCerts()).sign(data), - blocks.initialState().get().getState().getPlatformState().getAddressBook(), + blocks.initialAddressBook(), blocks.selfId(), blocks.appVersion(), - blocks.transactionPool()); + blocks.transactionPoolNexus()); eventCreationManager = new DefaultEventCreationManager( blocks.platformContext(), - blocks.transactionPool(), + blocks.transactionPoolNexus(), blocks.intakeQueueSizeSupplierSupplier().get(), eventCreator); } return eventCreationManager; } - /** - * Build the in-order linker if it has not yet been built. If one has been provided via - * {@link #withInOrderLinker(InOrderLinker)}, that in-order linker will be used. If this method is called more than - * once, only the first call will build the in-order linker. Otherwise, the default in-order linker will be created - * and returned. - * - * @return the in-order linker - */ - @NonNull - public InOrderLinker buildInOrderLinker() { - if (inOrderLinker == null) { - inOrderLinker = new GossipLinker(blocks.platformContext(), blocks.intakeEventCounter()); - } - return inOrderLinker; - } - - /** - * Provide an in-order linker in place of the platform's default in-order linker. - * - * @param inOrderLinker the in-order linker to use - * @return this builder - */ - @NonNull - public PlatformComponentBuilder withInOrderLinker(@NonNull final InOrderLinker inOrderLinker) { - throwIfAlreadyUsed(); - if (this.inOrderLinker != null) { - throw new IllegalStateException("In-order linker has already been set"); - } - this.inOrderLinker = Objects.requireNonNull(inOrderLinker); - - return this; - } - /** * Provide a consensus engine in place of the platform's default consensus engine. * @@ -579,11 +549,23 @@ public PlatformComponentBuilder withConsensusEventStream(@NonNull final Consensu @NonNull public ConsensusEventStream buildConsensusEventStream() { if (consensusEventStream == null) { + + // Required for conformity with legacy behavior. This sort of funky logic is normally something + // we'd try to move away from, but since we will be removing the CES entirely, it's simpler + // to just wait until the entire component disappears. + final Address address = blocks.initialAddressBook().getAddress(blocks.selfId()); + final String consensusEventStreamName; + if (!address.getMemo().isEmpty()) { + consensusEventStreamName = address.getMemo(); + } else { + consensusEventStreamName = String.valueOf(blocks.selfId()); + } + consensusEventStream = new DefaultConsensusEventStream( blocks.platformContext(), blocks.selfId(), (byte[] data) -> new PlatformSigner(blocks.keysAndCerts()).sign(data), - "" + blocks.selfId().id(), + consensusEventStreamName, (EventImpl event) -> event.isLastInRoundReceived() && blocks.isInFreezePeriodReference().get().test(event.getConsensusTimestamp())); } @@ -794,4 +776,314 @@ public PcesWriter buildPcesWriter() { } return pcesWriter; } + + /** + * Provide an ISS detector in place of the platform's default ISS detector. + * + * @param issDetector the ISS detector to use + * @return this builder + */ + @NonNull + public PlatformComponentBuilder withIssDetector(@NonNull final IssDetector issDetector) { + throwIfAlreadyUsed(); + if (this.issDetector != null) { + throw new IllegalStateException("ISS detector has already been set"); + } + this.issDetector = Objects.requireNonNull(issDetector); + return this; + } + + /** + * Build the ISS detector if it has not yet been built. If one has been provided via + * {@link #withIssDetector(IssDetector)}, that detector will be used. If this method is called more than once, only + * the first call will build the ISS detector. Otherwise, the default detector will be created and returned. + * + * @return the ISS detector + */ + @NonNull + public IssDetector buildIssDetector() { + if (issDetector == null) { + // Only validate preconsensus signature transactions if we are not recovering from an ISS. + // ISS round == null means we haven't observed an ISS yet. + // ISS round < current round means there was an ISS prior to the saved state + // that has already been recovered from. + // ISS round >= current round means that the ISS happens in the future relative the initial state, meaning + // we may observe ISS-inducing signature transactions in the preconsensus event stream. + + final SerializableLong issRound = blocks.issScratchpad().get(IssScratchpad.LAST_ISS_ROUND); + + final boolean forceIgnorePcesSignatures = blocks.platformContext() + .getConfiguration() + .getConfigData(PcesConfig.class) + .forceIgnorePcesSignatures(); + + final long initialStateRound = blocks.initialState().get().getRound(); + + final boolean ignorePreconsensusSignatures; + if (forceIgnorePcesSignatures) { + // this is used FOR TESTING ONLY + ignorePreconsensusSignatures = true; + } else { + ignorePreconsensusSignatures = issRound != null && issRound.getValue() >= initialStateRound; + } + + // A round that we will completely skip ISS detection for. Needed for tests that do janky state modification + // without a software upgrade (in production this feature should not be used). + final long roundToIgnore = blocks.platformContext() + .getConfiguration() + .getConfigData(StateConfig.class) + .validateInitialState() + ? DO_NOT_IGNORE_ROUNDS + : initialStateRound; + + issDetector = new DefaultIssDetector( + blocks.platformContext(), + blocks.initialState().get().getState().getPlatformState().getAddressBook(), + blocks.appVersion(), + ignorePreconsensusSignatures, + roundToIgnore); + } + return issDetector; + } + + /** + * Provide a stale event detector in place of the platform's default stale event detector. + * + * @param staleEventDetector the stale event detector to use + * @return this builder + */ + @NonNull + public PlatformComponentBuilder withStaleEventDetector(@NonNull final StaleEventDetector staleEventDetector) { + throwIfAlreadyUsed(); + if (this.staleEventDetector != null) { + throw new IllegalStateException("Stale event detector has already been set"); + } + this.staleEventDetector = Objects.requireNonNull(staleEventDetector); + return this; + } + + /** + * Build the stale event detector if it has not yet been built. If one has been provided via + * {@link #withStaleEventDetector(StaleEventDetector)}, that detector will be used. If this method is called more + * than once, only the first call will build the stale event detector. Otherwise, the default detector will be + * created and returned. + * + * @return the stale event detector + */ + @NonNull + public StaleEventDetector buildStaleEventDetector() { + if (staleEventDetector == null) { + staleEventDetector = new DefaultStaleEventDetector(blocks.platformContext(), blocks.selfId()); + } + return staleEventDetector; + } + + /** + * Provide a transaction resubmitter in place of the platform's default transaction resubmitter. + * + * @param transactionResubmitter the transaction resubmitter to use + * @return this builder + */ + @NonNull + public PlatformComponentBuilder withTransactionResubmitter( + @NonNull final TransactionResubmitter transactionResubmitter) { + throwIfAlreadyUsed(); + if (this.transactionResubmitter != null) { + throw new IllegalStateException("Transaction resubmitter has already been set"); + } + this.transactionResubmitter = Objects.requireNonNull(transactionResubmitter); + return this; + } + + /** + * Build the transaction resubmitter if it has not yet been built. If one has been provided via + * {@link #withTransactionResubmitter(TransactionResubmitter)}, that resubmitter will be used. If this method is + * called more than once, only the first call will build the transaction resubmitter. Otherwise, the default + * resubmitter will be created and returned. + * + * @return the transaction resubmitter + */ + @NonNull + public TransactionResubmitter buildTransactionResubmitter() { + if (transactionResubmitter == null) { + transactionResubmitter = new DefaultTransactionResubmitter(); + } + return transactionResubmitter; + } + + /** + * Provide a transaction pool in place of the platform's default transaction pool. + * + * @param transactionPool the transaction pool to use + * @return this builder + */ + @NonNull + public PlatformComponentBuilder withTransactionPool(@NonNull final TransactionPool transactionPool) { + throwIfAlreadyUsed(); + if (this.transactionPool != null) { + throw new IllegalStateException("Transaction pool has already been set"); + } + this.transactionPool = Objects.requireNonNull(transactionPool); + return this; + } + + /** + * Build the transaction pool if it has not yet been built. If one has been provided via + * {@link #withTransactionPool(TransactionPool)}, that pool will be used. If this method is called more than once, + * only the first call will build the transaction pool. Otherwise, the default pool will be created and returned. + * + * @return the transaction pool + */ + @NonNull + public TransactionPool buildTransactionPool() { + if (transactionPool == null) { + transactionPool = new DefaultTransactionPool(blocks.transactionPoolNexus()); + } + return transactionPool; + } + + /** + * Provide a gossip in place of the platform's default gossip. + * + * @param gossip the gossip to use + * @return this builder + */ + @NonNull + public PlatformComponentBuilder withGossip(@NonNull final Gossip gossip) { + throwIfAlreadyUsed(); + if (this.gossip != null) { + throw new IllegalStateException("Gossip has already been set"); + } + this.gossip = Objects.requireNonNull(gossip); + return this; + } + + /** + * Build the gossip if it has not yet been built. If one has been provided via {@link #withGossip(Gossip)}, that + * gossip will be used. If this method is called more than once, only the first call will build the gossip. + * Otherwise, the default gossip will be created and returned. + * + * @return the gossip + */ + @NonNull + public Gossip buildGossip() { + if (gossip == null) { + gossip = new SyncGossip( + blocks.platformContext(), + blocks.randomBuilder().buildNonCryptographicRandom(), + AdHocThreadManager.getStaticThreadManager(), + blocks.keysAndCerts(), + blocks.initialAddressBook(), + blocks.selfId(), + blocks.appVersion(), + () -> blocks.intakeQueueSizeSupplierSupplier().get().getAsLong(), + blocks.swirldStateManager(), + () -> blocks.getLatestCompleteStateReference().get().get(), + x -> blocks.statusActionSubmitterReference().get().submitStatusAction(x), + () -> blocks.platformStatusSupplierReference().get().get(), + state -> blocks.loadReconnectStateReference().get().accept(state), + () -> blocks.clearAllPipelinesForReconnectReference().get().run(), + blocks.intakeEventCounter()); + } + return gossip; + } + + /** + * Provide a state hasher in place of the platform's default state hasher. + * + * @param stateHasher the state hasher to use + * @return this builder + */ + @NonNull + public PlatformComponentBuilder withStateHasher(@NonNull final StateHasher stateHasher) { + throwIfAlreadyUsed(); + if (this.stateHasher != null) { + throw new IllegalStateException("Signed state hasher has already been set"); + } + this.stateHasher = Objects.requireNonNull(stateHasher); + return this; + } + + /** + * Build the state hasher if it has not yet been built. If one has been provided via + * {@link #withStateHasher(StateHasher)}, that hasher will be used. If this method is called more than once, only + * the first call will build the state hasher. Otherwise, the default hasher will be created and returned. + * + * @return the signed state hasher + */ + @NonNull + public StateHasher buildStateHasher() { + if (stateHasher == null) { + stateHasher = new DefaultStateHasher(blocks.platformContext()); + } + return stateHasher; + } + + /** + * Provide a state snapshot manager in place of the platform's default state snapshot manager. + * + * @param stateSnapshotManager the state snapshot manager to use + * @return this builder + */ + @NonNull + public PlatformComponentBuilder withStateSnapshotManager(@NonNull final StateSnapshotManager stateSnapshotManager) { + throwIfAlreadyUsed(); + if (this.stateSnapshotManager != null) { + throw new IllegalStateException("State snapshot manager has already been set"); + } + this.stateSnapshotManager = Objects.requireNonNull(stateSnapshotManager); + return this; + } + + /** + * Build the state snapshot manager if it has not yet been built. If one has been provided via + * {@link #withStateSnapshotManager(StateSnapshotManager)}, that manager will be used. If this method is called more + * than once, only the first call will build the state snapshot manager. Otherwise, the default manager will be + * created and returned. + * + * @return the state snapshot manager + */ + @NonNull + public StateSnapshotManager buildStateSnapshotManager() { + if (stateSnapshotManager == null) { + final StateConfig stateConfig = + blocks.platformContext().getConfiguration().getConfigData(StateConfig.class); + final String actualMainClassName = stateConfig.getMainClassName(blocks.mainClassName()); + + stateSnapshotManager = new DefaultStateSnapshotManager( + blocks.platformContext(), actualMainClassName, blocks.selfId(), blocks.swirldName()); + } + return stateSnapshotManager; + } + + /** + * Provide a hash logger in place of the platform's default hash logger. + * + * @param hashLogger the hash logger to use + * @return this builder + */ + @NonNull + public PlatformComponentBuilder withHashLogger(@NonNull final HashLogger hashLogger) { + throwIfAlreadyUsed(); + if (this.hashLogger != null) { + throw new IllegalStateException("Hash logger has already been set"); + } + this.hashLogger = Objects.requireNonNull(hashLogger); + return this; + } + + /** + * Build the hash logger if it has not yet been built. If one has been provided via + * {@link #withHashLogger(HashLogger)}, that logger will be used. If this method is called more than once, only the + * first call will build the hash logger. Otherwise, the default logger will be created and returned. + * + * @return the hash logger + */ + @NonNull + public HashLogger buildHashLogger() { + if (hashLogger == null) { + hashLogger = new DefaultHashLogger(blocks.platformContext()); + } + return hashLogger; + } } diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/cli/CompareStatesCommand.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/cli/CompareStatesCommand.java index 8d44fda9003a..22ccc36fd08f 100644 --- a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/cli/CompareStatesCommand.java +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/cli/CompareStatesCommand.java @@ -18,22 +18,18 @@ import static com.swirlds.common.io.utility.FileUtils.getAbsolutePath; -import com.swirlds.base.time.Time; import com.swirlds.cli.commands.StateCommand; import com.swirlds.cli.utility.AbstractCommand; import com.swirlds.cli.utility.SubcommandOf; -import com.swirlds.common.context.DefaultPlatformContext; import com.swirlds.common.context.PlatformContext; -import com.swirlds.common.crypto.CryptographyHolder; import com.swirlds.common.merkle.crypto.MerkleCryptoFactory; -import com.swirlds.common.metrics.noop.NoOpMetrics; import com.swirlds.config.api.Configuration; import com.swirlds.config.api.ConfigurationBuilder; import com.swirlds.logging.legacy.LogMarker; import com.swirlds.platform.config.DefaultConfiguration; import com.swirlds.platform.state.signed.ReservedSignedState; import com.swirlds.platform.state.signed.SignedStateComparison; -import com.swirlds.platform.state.signed.SignedStateFileReader; +import com.swirlds.platform.state.snapshot.SignedStateFileReader; import com.swirlds.platform.util.BootstrapUtils; import edu.umd.cs.findbugs.annotations.NonNull; import java.io.IOException; @@ -164,8 +160,7 @@ public Integer call() throws IOException { final Configuration configuration = DefaultConfiguration.buildBasicConfiguration( ConfigurationBuilder.create(), getAbsolutePath("settings.txt"), configurationPaths); - final PlatformContext platformContext = new DefaultPlatformContext( - configuration, new NoOpMetrics(), CryptographyHolder.get(), Time.getCurrent()); + final PlatformContext platformContext = PlatformContext.create(configuration); try (final ReservedSignedState stateA = loadAndHashState(platformContext, stateAPath)) { try (final ReservedSignedState stateB = loadAndHashState(platformContext, stateBPath)) { diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/cli/DiagramCommand.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/cli/DiagramCommand.java index 1a8cbf5fdb5e..51b49dbff621 100644 --- a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/cli/DiagramCommand.java +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/cli/DiagramCommand.java @@ -16,21 +16,19 @@ package com.swirlds.platform.cli; -import com.swirlds.base.time.Time; import com.swirlds.cli.PlatformCli; import com.swirlds.cli.utility.AbstractCommand; import com.swirlds.cli.utility.SubcommandOf; -import com.swirlds.common.context.DefaultPlatformContext; import com.swirlds.common.context.PlatformContext; -import com.swirlds.common.crypto.CryptographyHolder; -import com.swirlds.common.metrics.noop.NoOpMetrics; +import com.swirlds.common.wiring.model.WiringModel; +import com.swirlds.common.wiring.model.WiringModelBuilder; import com.swirlds.common.wiring.model.diagram.ModelEdgeSubstitution; import com.swirlds.common.wiring.model.diagram.ModelGroup; import com.swirlds.common.wiring.model.diagram.ModelManualLink; import com.swirlds.config.api.Configuration; import com.swirlds.config.api.ConfigurationBuilder; +import com.swirlds.platform.builder.ApplicationCallbacks; import com.swirlds.platform.config.DefaultConfiguration; -import com.swirlds.platform.eventhandling.TransactionPool; import com.swirlds.platform.util.VirtualTerminal; import com.swirlds.platform.wiring.PlatformWiring; import edu.umd.cs.findbugs.annotations.NonNull; @@ -116,12 +114,13 @@ private void setLessMystery(final boolean lessMystery) { @Override public Integer call() throws IOException { final Configuration configuration = DefaultConfiguration.buildBasicConfiguration(ConfigurationBuilder.create()); - final PlatformContext platformContext = new DefaultPlatformContext( - configuration, new NoOpMetrics(), CryptographyHolder.get(), Time.getCurrent()); + final PlatformContext platformContext = PlatformContext.create(configuration); - final PlatformWiring platformWiring = new PlatformWiring(platformContext, true, true); + final ApplicationCallbacks callbacks = new ApplicationCallbacks(x -> {}, x -> {}, x -> {}); - platformWiring.wireExternalComponents(new TransactionPool(platformContext)); + final WiringModel model = WiringModelBuilder.create(platformContext).build(); + + final PlatformWiring platformWiring = new PlatformWiring(platformContext, model, callbacks); final String diagramString = platformWiring .getModel() diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/cli/DiagramLegendCommand.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/cli/DiagramLegendCommand.java index 04403fd1e30e..3e2c598b4c35 100644 --- a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/cli/DiagramLegendCommand.java +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/cli/DiagramLegendCommand.java @@ -16,13 +16,9 @@ package com.swirlds.platform.cli; -import com.swirlds.base.time.Time; import com.swirlds.cli.utility.AbstractCommand; import com.swirlds.cli.utility.SubcommandOf; -import com.swirlds.common.context.DefaultPlatformContext; import com.swirlds.common.context.PlatformContext; -import com.swirlds.common.crypto.CryptographyHolder; -import com.swirlds.common.metrics.noop.NoOpMetrics; import com.swirlds.common.wiring.model.WiringModel; import com.swirlds.common.wiring.model.WiringModelBuilder; import com.swirlds.common.wiring.model.diagram.ModelEdgeSubstitution; @@ -58,8 +54,7 @@ public Integer call() throws IOException { final Configuration configuration = DefaultConfiguration.buildBasicConfiguration(ConfigurationBuilder.create()); BootstrapUtils.setupConstructableRegistry(); - final PlatformContext platformContext = new DefaultPlatformContext( - configuration, new NoOpMetrics(), CryptographyHolder.get(), Time.getCurrent()); + final PlatformContext platformContext = PlatformContext.create(configuration); final WiringModel model = WiringModelBuilder.create(platformContext).build(); diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/cli/EventStreamInfoCommand.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/cli/EventStreamInfoCommand.java new file mode 100644 index 000000000000..242fd7ea78fd --- /dev/null +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/cli/EventStreamInfoCommand.java @@ -0,0 +1,249 @@ +/* + * Copyright (C) 2016-2024 Hedera Hashgraph, LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.swirlds.platform.cli; + +import static com.swirlds.logging.legacy.LogMarker.EXCEPTION; +import static com.swirlds.platform.util.BootstrapUtils.setupConstructableRegistry; + +import com.swirlds.cli.commands.EventStreamCommand; +import com.swirlds.cli.utility.AbstractCommand; +import com.swirlds.cli.utility.SubcommandOf; +import com.swirlds.platform.event.report.EventStreamMultiNodeReport; +import com.swirlds.platform.event.report.EventStreamReport; +import com.swirlds.platform.event.report.EventStreamScanner; +import com.swirlds.platform.recovery.internal.EventStreamLowerBound; +import com.swirlds.platform.recovery.internal.EventStreamRoundLowerBound; +import com.swirlds.platform.recovery.internal.EventStreamTimestampLowerBound; +import edu.umd.cs.findbugs.annotations.NonNull; +import edu.umd.cs.findbugs.annotations.Nullable; +import java.io.IOException; +import java.io.UncheckedIOException; +import java.nio.file.DirectoryStream; +import java.nio.file.Files; +import java.nio.file.Path; +import java.time.Duration; +import java.time.Instant; +import java.time.format.DateTimeFormatter; +import java.time.format.DateTimeParseException; +import java.util.NoSuchElementException; +import java.util.Objects; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import picocli.CommandLine; + +@CommandLine.Command( + name = "info", + mixinStandardHelpOptions = true, + description = "Read event stream files and print an informational report.") +@SubcommandOf(EventStreamCommand.class) +public final class EventStreamInfoCommand extends AbstractCommand { + private static final Logger logger = LogManager.getLogger(EventStreamInfoCommand.class); + + /** a format for timestamps */ + private static final String TIMESTAMP_FORMAT = "yyyy-MM-dd HH:mm:ss"; + + /** a formatter for timestamps */ + private static final DateTimeFormatter formatter = DateTimeFormatter.ofPattern(TIMESTAMP_FORMAT); + + /** the directory containing the event stream files */ + private Path eventStreamDirectory; + + /** the timestamp of the lower bound */ + private Instant timestampBound = Instant.MIN; + + /** the round of the lower bound */ + private long roundBound = -1; + + /** the default temporal granularity of the data report */ + private Duration granularity = Duration.ofSeconds(10); + + /** + * If true, a separate report will be generated for each child directory contained in the specified parent. + *

    + * Each individual report will be written to a file in the respective child directory + *

    + * In addition to individual reports, a summary report will be generated and printed to stdout. + */ + private boolean multiNodeReport = false; + + @CommandLine.Parameters(description = "The path to a directory tree containing event stream files.") + private void setEventStreamDirectory(final Path eventStreamDirectory) { + this.eventStreamDirectory = pathMustExist(eventStreamDirectory.toAbsolutePath()); + } + + @CommandLine.Option( + names = {"-f", "--first-round"}, + description = "The first round to be considered in the event stream.") + private void setFirstRound(final long firstRound) { + roundBound = firstRound; + } + + @CommandLine.Option( + names = {"-t", "--timestamp"}, + description = "The minimum timestamp to be considered in the event stream. The format is \"" + + TIMESTAMP_FORMAT + "\".") + private void setTimestamp(@NonNull final String timestamp) { + Objects.requireNonNull(timestamp, "timestamp must not be null"); + try { + // the format used by log4j2 + timestampBound = formatter.parse(timestamp, Instant::from); + } catch (final DateTimeParseException e) { + // the format used by Instant.toString() + timestampBound = Instant.parse(timestamp); + } + } + + @CommandLine.Option( + names = {"-g", "--granularity"}, + description = "The temporal granularity of the data report, in seconds.") + private void setGranularityInSeconds(final long granularityInSeconds) { + if (granularityInSeconds < 1) { + throw buildParameterException("Granularity must be strictly greater than 1"); + } + this.granularity = Duration.ofSeconds(granularityInSeconds); + } + + @CommandLine.Option( + names = {"-m", "--multi-node-report"}, + description = + "Generate a separate report for each direct child of the specified directory, as well as a summary report.") + private void requestMultiNodeReport(final boolean multiNodeReport) { + this.multiNodeReport = multiNodeReport; + } + + private EventStreamInfoCommand() {} + + /** + * Generate an {@link EventStreamReport} for the event stream files contained in the specified directory + * + * @param directory the directory containing the event stream files + * @param bound the lower bound to use when generating the reports + * @return the report, or null if the directory does not contain any event stream files + */ + @Nullable + private EventStreamReport generateReport( + @NonNull final Path directory, @NonNull final EventStreamLowerBound bound) { + + Objects.requireNonNull(directory); + Objects.requireNonNull(bound); + + try { + return new EventStreamScanner(directory, bound, granularity, true).createReport(); + } catch (final IOException e) { + throw new UncheckedIOException("Failed to generate event stream report", e); + } catch (final IllegalStateException e) { + // the directory does not contain any event stream files. return null and let the caller sort it out + return null; + } + } + + /** + * Write an {@link EventStreamReport} to a file in the specified directory + * + * @param nodeDirectory the directory to write the report to + * @param nodeReport the report to write to file + */ + private void writeNodeReportToFile(@NonNull final Path nodeDirectory, @NonNull final EventStreamReport nodeReport) { + final Path reportFile = nodeDirectory.resolve("event-stream-report.txt"); + + try { + Files.writeString(reportFile, nodeReport.toString()); + } catch (final IOException e) { + throw new UncheckedIOException("Failed to write report to file", e); + } + } + + /** + * Generate an {@link EventStreamReport} for each child directory contained in {@link #eventStreamDirectory}, as + * well as a summary of these individual reports + *

    + * The individual reports will be written to files in the respective child directories, and the summary report will + * be printed to stdout + * + * @param bound the lower bound to use when generating the reports + * @throws IOException if the directory stream cannot be opened + */ + @SuppressWarnings("java:S106") + private void generateMultiNodeReport(@NonNull final EventStreamLowerBound bound) throws IOException { + Objects.requireNonNull(bound); + + final EventStreamMultiNodeReport multiReport = new EventStreamMultiNodeReport(); + + try (final DirectoryStream stream = Files.newDirectoryStream(eventStreamDirectory)) { + stream.forEach(streamElement -> { + // child elements that aren't directories are ignored + if (!Files.isDirectory(streamElement)) { + return; + } + + final Path directory = streamElement.normalize(); + + final EventStreamReport individualReport = generateReport(directory, bound); + + if (individualReport == null) { + logger.error(EXCEPTION.getMarker(), "No event stream files found in `{}`", directory); + return; + } + + multiReport.addIndividualReport(directory, individualReport); + writeNodeReportToFile(directory, individualReport); + }); + } + try { + System.out.println(multiReport); + } catch (final NoSuchElementException e) { + // the multi report is empty + logger.error( + EXCEPTION.getMarker(), + "No event stream files found in any child directory of `{}`", + eventStreamDirectory); + } + } + + @Override + @SuppressWarnings("java:S106") + public Integer call() throws Exception { + setupConstructableRegistry(); + if (roundBound > 0 && !Instant.MIN.equals(timestampBound)) { + throw buildParameterException("Cannot set both round and timestamp"); + } + + final EventStreamLowerBound bound; + if (roundBound > 0) { + bound = new EventStreamRoundLowerBound(roundBound); + } else if (!Instant.MIN.equals(timestampBound)) { + bound = new EventStreamTimestampLowerBound(timestampBound); + } else { + bound = EventStreamLowerBound.UNBOUNDED; + } + + if (multiNodeReport) { + generateMultiNodeReport(bound); + } else { + final EventStreamReport report = generateReport(eventStreamDirectory, bound); + + if (report == null) { + logger.error(EXCEPTION.getMarker(), "No event stream files found in `{}`", eventStreamDirectory); + } else { + // an individual report was requested. Simply print the report to stdout. + System.out.println(report); + } + } + + return 0; + } +} diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/cli/EventStreamRecoverCommand.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/cli/EventStreamRecoverCommand.java index e9b7509e673e..83de30215324 100644 --- a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/cli/EventStreamRecoverCommand.java +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/cli/EventStreamRecoverCommand.java @@ -19,14 +19,10 @@ import static com.swirlds.common.io.utility.FileUtils.getAbsolutePath; import static com.swirlds.platform.recovery.EventRecoveryWorkflow.recoverState; -import com.swirlds.base.time.Time; import com.swirlds.cli.commands.EventStreamCommand; import com.swirlds.cli.utility.AbstractCommand; import com.swirlds.cli.utility.SubcommandOf; -import com.swirlds.common.context.DefaultPlatformContext; import com.swirlds.common.context.PlatformContext; -import com.swirlds.common.crypto.CryptographyHolder; -import com.swirlds.common.metrics.noop.NoOpMetrics; import com.swirlds.common.platform.NodeId; import com.swirlds.config.api.Configuration; import com.swirlds.config.api.ConfigurationBuilder; @@ -128,8 +124,7 @@ private void setLoadSigningKeys(final boolean loadSigningKeys) { public Integer call() throws Exception { final Configuration configuration = DefaultConfiguration.buildBasicConfiguration( ConfigurationBuilder.create(), getAbsolutePath("settings.txt"), configurationPaths); - final PlatformContext platformContext = new DefaultPlatformContext( - configuration, new NoOpMetrics(), CryptographyHolder.get(), Time.getCurrent()); + final PlatformContext platformContext = PlatformContext.create(configuration); recoverState( platformContext, diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/cli/GenesisPlatformStateCommand.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/cli/GenesisPlatformStateCommand.java index dc8113504929..152106f36169 100644 --- a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/cli/GenesisPlatformStateCommand.java +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/cli/GenesisPlatformStateCommand.java @@ -16,26 +16,22 @@ package com.swirlds.platform.cli; -import static com.swirlds.platform.state.signed.SavedStateMetadata.NO_NODE_ID; -import static com.swirlds.platform.state.signed.SignedStateFileWriter.writeSignedStateFilesToDirectory; +import static com.swirlds.platform.state.snapshot.SavedStateMetadata.NO_NODE_ID; +import static com.swirlds.platform.state.snapshot.SignedStateFileWriter.writeSignedStateFilesToDirectory; -import com.swirlds.base.time.Time; import com.swirlds.cli.commands.StateCommand; import com.swirlds.cli.utility.AbstractCommand; import com.swirlds.cli.utility.SubcommandOf; -import com.swirlds.common.context.DefaultPlatformContext; import com.swirlds.common.context.PlatformContext; -import com.swirlds.common.crypto.CryptographyHolder; import com.swirlds.common.merkle.crypto.MerkleCryptoFactory; -import com.swirlds.common.metrics.noop.NoOpMetrics; import com.swirlds.config.api.Configuration; import com.swirlds.config.api.ConfigurationBuilder; import com.swirlds.platform.config.DefaultConfiguration; import com.swirlds.platform.consensus.SyntheticSnapshot; import com.swirlds.platform.state.PlatformState; -import com.swirlds.platform.state.signed.DeserializedSignedState; import com.swirlds.platform.state.signed.ReservedSignedState; -import com.swirlds.platform.state.signed.SignedStateFileReader; +import com.swirlds.platform.state.snapshot.DeserializedSignedState; +import com.swirlds.platform.state.snapshot.SignedStateFileReader; import com.swirlds.platform.util.BootstrapUtils; import java.io.IOException; import java.nio.file.Path; @@ -72,8 +68,7 @@ public Integer call() throws IOException, ExecutionException, InterruptedExcepti final Configuration configuration = DefaultConfiguration.buildBasicConfiguration(ConfigurationBuilder.create()); BootstrapUtils.setupConstructableRegistry(); - final PlatformContext platformContext = new DefaultPlatformContext( - configuration, new NoOpMetrics(), CryptographyHolder.get(), Time.getCurrent()); + final PlatformContext platformContext = PlatformContext.create(configuration); System.out.printf("Reading from %s %n", statePath.toAbsolutePath()); final DeserializedSignedState deserializedSignedState = diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/cli/ValidateAddressBookStateCommand.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/cli/ValidateAddressBookStateCommand.java index c3e9e6e68a53..fa7d677b6ed2 100644 --- a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/cli/ValidateAddressBookStateCommand.java +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/cli/ValidateAddressBookStateCommand.java @@ -16,21 +16,17 @@ package com.swirlds.platform.cli; -import com.swirlds.base.time.Time; import com.swirlds.cli.commands.StateCommand; import com.swirlds.cli.utility.AbstractCommand; import com.swirlds.cli.utility.SubcommandOf; -import com.swirlds.common.context.DefaultPlatformContext; import com.swirlds.common.context.PlatformContext; -import com.swirlds.common.crypto.CryptographyHolder; -import com.swirlds.common.metrics.noop.NoOpMetrics; import com.swirlds.config.api.Configuration; import com.swirlds.config.api.ConfigurationBuilder; import com.swirlds.platform.config.DefaultConfiguration; import com.swirlds.platform.state.PlatformState; -import com.swirlds.platform.state.signed.DeserializedSignedState; import com.swirlds.platform.state.signed.ReservedSignedState; -import com.swirlds.platform.state.signed.SignedStateFileReader; +import com.swirlds.platform.state.snapshot.DeserializedSignedState; +import com.swirlds.platform.state.snapshot.SignedStateFileReader; import com.swirlds.platform.system.address.AddressBook; import com.swirlds.platform.system.address.AddressBookUtils; import com.swirlds.platform.system.address.AddressBookValidator; @@ -72,8 +68,7 @@ public Integer call() throws IOException, ExecutionException, InterruptedExcepti final Configuration configuration = DefaultConfiguration.buildBasicConfiguration(ConfigurationBuilder.create()); BootstrapUtils.setupConstructableRegistry(); - final PlatformContext platformContext = new DefaultPlatformContext( - configuration, new NoOpMetrics(), CryptographyHolder.get(), Time.getCurrent()); + final PlatformContext platformContext = PlatformContext.create(configuration); System.out.printf("Reading state from %s %n", statePath.toAbsolutePath()); final DeserializedSignedState deserializedSignedState = diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/components/AppNotifier.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/components/AppNotifier.java index 1e77c28e4bf6..c279fb439ad0 100644 --- a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/components/AppNotifier.java +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/components/AppNotifier.java @@ -19,7 +19,6 @@ import com.swirlds.common.wiring.component.InputWireLabel; import com.swirlds.platform.components.appcomm.CompleteStateNotificationWithCleanup; import com.swirlds.platform.listeners.ReconnectCompleteNotification; -import com.swirlds.platform.listeners.StateLoadedFromDiskNotification; import com.swirlds.platform.listeners.StateWriteToDiskCompleteNotification; import com.swirlds.platform.system.state.notifications.IssNotification; import com.swirlds.platform.system.status.PlatformStatus; @@ -37,14 +36,6 @@ public interface AppNotifier { @InputWireLabel("state written notification") void sendStateWrittenToDiskNotification(@NonNull final StateWriteToDiskCompleteNotification notification); - /** - * Send a notification to the app that the state has been loaded from disk. - * - * @param notification the notification - */ - @InputWireLabel("state loaded notification") - void sendStateLoadedFromDiskNotification(@NonNull final StateLoadedFromDiskNotification notification); - /** * Send a notification to the app that a reconnect has completed. * diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/components/DefaultAppNotifier.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/components/DefaultAppNotifier.java index ce73ad8aba6a..af4582bb4195 100644 --- a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/components/DefaultAppNotifier.java +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/components/DefaultAppNotifier.java @@ -22,8 +22,6 @@ import com.swirlds.platform.listeners.PlatformStatusChangeNotification; import com.swirlds.platform.listeners.ReconnectCompleteListener; import com.swirlds.platform.listeners.ReconnectCompleteNotification; -import com.swirlds.platform.listeners.StateLoadedFromDiskCompleteListener; -import com.swirlds.platform.listeners.StateLoadedFromDiskNotification; import com.swirlds.platform.listeners.StateWriteToDiskCompleteListener; import com.swirlds.platform.listeners.StateWriteToDiskCompleteNotification; import com.swirlds.platform.system.state.notifications.IssListener; @@ -44,14 +42,6 @@ public void sendStateWrittenToDiskNotification(@NonNull final StateWriteToDiskCo notificationEngine.dispatch(StateWriteToDiskCompleteListener.class, notification); } - /** - * {@inheritDoc} - */ - @Override - public void sendStateLoadedFromDiskNotification(@NonNull final StateLoadedFromDiskNotification notification) { - notificationEngine.dispatch(StateLoadedFromDiskCompleteListener.class, notification); - } - /** * {@inheritDoc} */ diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/components/DefaultSavedStateController.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/components/DefaultSavedStateController.java index db8af192fc09..893fbc33e67e 100644 --- a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/components/DefaultSavedStateController.java +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/components/DefaultSavedStateController.java @@ -17,20 +17,20 @@ package com.swirlds.platform.components; import static com.swirlds.logging.legacy.LogMarker.STATE_TO_DISK; -import static com.swirlds.platform.state.signed.StateToDiskReason.FIRST_ROUND_AFTER_GENESIS; -import static com.swirlds.platform.state.signed.StateToDiskReason.FREEZE_STATE; -import static com.swirlds.platform.state.signed.StateToDiskReason.PERIODIC_SNAPSHOT; -import static com.swirlds.platform.state.signed.StateToDiskReason.RECONNECT; +import static com.swirlds.platform.state.snapshot.StateToDiskReason.FIRST_ROUND_AFTER_GENESIS; +import static com.swirlds.platform.state.snapshot.StateToDiskReason.FREEZE_STATE; +import static com.swirlds.platform.state.snapshot.StateToDiskReason.PERIODIC_SNAPSHOT; +import static com.swirlds.platform.state.snapshot.StateToDiskReason.RECONNECT; +import com.swirlds.common.context.PlatformContext; import com.swirlds.platform.config.StateConfig; import com.swirlds.platform.state.signed.ReservedSignedState; import com.swirlds.platform.state.signed.SignedState; -import com.swirlds.platform.state.signed.StateToDiskReason; +import com.swirlds.platform.state.snapshot.StateToDiskReason; import com.swirlds.platform.wiring.components.StateAndRound; import edu.umd.cs.findbugs.annotations.NonNull; import edu.umd.cs.findbugs.annotations.Nullable; import java.time.Instant; -import java.util.Objects; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; @@ -50,10 +50,10 @@ public class DefaultSavedStateController implements SavedStateController { /** * Constructor * - * @param stateConfig the state config + * @param platformContext the platform context */ - public DefaultSavedStateController(@NonNull final StateConfig stateConfig) { - this.stateConfig = Objects.requireNonNull(stateConfig); + public DefaultSavedStateController(@NonNull final PlatformContext platformContext) { + this.stateConfig = platformContext.getConfiguration().getConfigData(StateConfig.class); } /** diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/components/consensus/DefaultConsensusEngine.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/components/consensus/DefaultConsensusEngine.java index ea08075be51f..e0324619e5cc 100644 --- a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/components/consensus/DefaultConsensusEngine.java +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/components/consensus/DefaultConsensusEngine.java @@ -34,7 +34,6 @@ import com.swirlds.platform.metrics.ConsensusMetrics; import com.swirlds.platform.metrics.ConsensusMetricsImpl; import com.swirlds.platform.system.address.AddressBook; -import com.swirlds.platform.wiring.NoInput; import edu.umd.cs.findbugs.annotations.NonNull; import java.util.List; import java.util.Objects; @@ -122,7 +121,7 @@ public void outOfBandSnapshotUpdate(@NonNull final ConsensusSnapshot snapshot) { final EventWindow eventWindow = new EventWindow(snapshot.round(), ancientThreshold, ancientThreshold, ancientMode); - linker.clear(NoInput.getInstance()); + linker.clear(); linker.setEventWindow(eventWindow); consensus.loadSnapshot(snapshot); } diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/consensus/ConsensusSorter.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/consensus/ConsensusSorter.java index 7097ddbc6186..d8cb56c44271 100644 --- a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/consensus/ConsensusSorter.java +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/consensus/ConsensusSorter.java @@ -71,6 +71,7 @@ public int compare(@NonNull final EventImpl e1, @NonNull final EventImpl e2) { } // subsort ties by whitened signature - return Utilities.arrayCompare(e1.getSignature(), e2.getSignature(), whitening); + return Utilities.arrayCompare( + e1.getBaseEvent().getSignature(), e2.getBaseEvent().getSignature(), whitening); } } diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/consensus/ConsensusUtils.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/consensus/ConsensusUtils.java index 54d20b91ef46..165dfe1a5806 100644 --- a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/consensus/ConsensusUtils.java +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/consensus/ConsensusUtils.java @@ -18,6 +18,7 @@ import static com.swirlds.platform.consensus.ConsensusConstants.MIN_TRANS_TIMESTAMP_INCR_NANOS; +import com.hedera.pbj.runtime.io.buffer.Bytes; import com.swirlds.common.crypto.Hash; import com.swirlds.platform.crypto.CryptoConstants; import com.swirlds.platform.internal.EventImpl; @@ -43,7 +44,8 @@ private ConsensusUtils() {} */ public static boolean coin(@NonNull final EventImpl event) { // coin is one bit from signature (LSB of second of two middle bytes) - return ((event.getSignature()[(event.getSignature().length / 2)] & 1) == 1); + final int sigLen = (int) event.getBaseEvent().getSignature().length(); + return ((event.getBaseEvent().getSignature().getByte((sigLen / 2)) & 1) == 1); } /** @@ -72,9 +74,10 @@ public static boolean coin(@NonNull final EventImpl event) { // find whitening for round for (final EventImpl w : judges) { // calculate the whitening byte array if (w != null) { - final int mn = Math.min(whitening.length, w.getSignature().length); + final Bytes sig = w.getBaseEvent().getSignature(); + final int mn = Math.min(whitening.length, (int) sig.length()); for (int i = 0; i < mn; i++) { - whitening[i] ^= w.getSignature()[i]; + whitening[i] ^= sig.getByte(i); } } } diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/consensus/RoundElections.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/consensus/RoundElections.java index 3030084cf6d4..e04fd1447278 100644 --- a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/consensus/RoundElections.java +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/consensus/RoundElections.java @@ -172,7 +172,7 @@ public long getMinGeneration() { // if this creator forked, then the judge is the "unique" famous witness, which is the one // with minimum hash // (where "minimum" is the lexicographically-least signed byte array) - if (Utilities.arrayCompare(e1.getBaseHash().getValue(), e2.getBaseHash().getValue()) < 0) { + if (Utilities.arrayCompare(e1.getBaseHash().getBytes(), e2.getBaseHash().getBytes()) < 0) { return e1; } return e2; diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/crypto/CryptoStatic.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/crypto/CryptoStatic.java index 1c1772a1556d..d6efe6edda76 100644 --- a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/crypto/CryptoStatic.java +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/crypto/CryptoStatic.java @@ -24,6 +24,7 @@ import static com.swirlds.platform.crypto.CryptoConstants.PUBLIC_KEYS_FILE; import static com.swirlds.platform.crypto.KeyCertPurpose.SIGNING; +import com.hedera.pbj.runtime.io.buffer.Bytes; import com.swirlds.common.crypto.CryptographyException; import com.swirlds.common.crypto.config.CryptoConfig; import com.swirlds.common.platform.NodeId; @@ -254,23 +255,23 @@ static KeyStore createEmptyTrustStore() throws KeyStoreException { } /** - * See {@link SignatureVerifier#verifySignature(byte[], byte[], PublicKey)} + * See {@link SignatureVerifier#verifySignature(Bytes, Bytes, PublicKey)} */ public static boolean verifySignature( - @NonNull byte[] data, @NonNull byte[] signature, @NonNull PublicKey publicKey) { + @NonNull final Bytes data, @NonNull final Bytes signature, @NonNull final PublicKey publicKey) { Objects.requireNonNull(data); Objects.requireNonNull(signature); Objects.requireNonNull(publicKey); try { final Signature sig = Signature.getInstance(CryptoConstants.SIG_TYPE2, CryptoConstants.SIG_PROVIDER); sig.initVerify(publicKey); - sig.update(data); - return sig.verify(signature); - } catch (NoSuchAlgorithmException | NoSuchProviderException e) { + data.updateSignature(sig); + return signature.verifySignature(sig); + } catch (final NoSuchAlgorithmException | NoSuchProviderException e) { // should never happen - throw new CryptographyException(e); - } catch (InvalidKeyException | SignatureException e) { - logger.error(LogMarker.EXCEPTION.getMarker(), "", e); + throw new CryptographyException("Exception occurred while validating a signature:", e, LogMarker.EXCEPTION); + } catch (final InvalidKeyException | SignatureException e) { + logger.error(LogMarker.EXCEPTION.getMarker(), "Exception occurred while validating a signature:", e); return false; } } diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/crypto/SignatureVerifier.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/crypto/SignatureVerifier.java index 41656e68476f..cd4e497103b1 100644 --- a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/crypto/SignatureVerifier.java +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/crypto/SignatureVerifier.java @@ -16,6 +16,7 @@ package com.swirlds.platform.crypto; +import com.hedera.pbj.runtime.io.buffer.Bytes; import edu.umd.cs.findbugs.annotations.NonNull; import java.security.PublicKey; @@ -35,5 +36,6 @@ public interface SignatureVerifier { * the claimed public key used to generate that signature * @return true if the signature is valid */ - boolean verifySignature(@NonNull byte[] data, @NonNull byte[] signature, @NonNull PublicKey publicKey); + boolean verifySignature( + @NonNull final Bytes data, @NonNull final Bytes signature, @NonNull final PublicKey publicKey); } diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/event/GossipEvent.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/event/GossipEvent.java index 1105c266bfc2..0482e657bf91 100644 --- a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/event/GossipEvent.java +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/event/GossipEvent.java @@ -18,30 +18,33 @@ import static com.swirlds.common.threading.interrupt.Uninterruptable.abortAndLogIfInterrupted; +import com.hedera.pbj.runtime.io.buffer.Bytes; +import com.swirlds.common.crypto.SignatureType; import com.swirlds.common.io.SelfSerializable; import com.swirlds.common.io.streams.SerializableDataInputStream; import com.swirlds.common.io.streams.SerializableDataOutputStream; import com.swirlds.common.platform.NodeId; +import com.swirlds.platform.system.SoftwareVersion; import com.swirlds.platform.system.events.BaseEventHashedData; -import com.swirlds.platform.system.events.BaseEventUnhashedData; +import com.swirlds.platform.system.events.Event; import com.swirlds.platform.system.events.EventDescriptor; +import com.swirlds.platform.system.transaction.Transaction; import edu.umd.cs.findbugs.annotations.NonNull; import edu.umd.cs.findbugs.annotations.Nullable; import java.io.IOException; import java.time.Instant; +import java.util.Arrays; +import java.util.Iterator; import java.util.Objects; import java.util.concurrent.CountDownLatch; /** * A class used to hold information about an event transferred through gossip */ -public class GossipEvent implements SelfSerializable { +public class GossipEvent implements Event, SelfSerializable { private static final long CLASS_ID = 0xfe16b46795bfb8dcL; - private static final int MAX_SIG_LENGTH = 384; private static final class ClassVersion { - public static final int ORIGINAL = 1; - public static final int REMOVED_ROUND = 2; /** * Event serialization changes * @@ -50,9 +53,10 @@ private static final class ClassVersion { public static final int BIRTH_ROUND = 3; } - private int serializedVersion = ClassVersion.BIRTH_ROUND; private BaseEventHashedData hashedData; - private BaseEventUnhashedData unhashedData; + /** creator's signature for this event */ + private Bytes signature; + private Instant timeReceived; /** @@ -84,11 +88,19 @@ public GossipEvent() {} /** * @param hashedData the hashed data for the event - * @param unhashedData the unhashed data for the event + * @param signature the signature for the event */ - public GossipEvent(final BaseEventHashedData hashedData, final BaseEventUnhashedData unhashedData) { + public GossipEvent(final BaseEventHashedData hashedData, final byte[] signature) { + this(hashedData, Bytes.wrap(signature)); + } + + /** + * @param hashedData the hashed data for the event + * @param signature the signature for the event + */ + public GossipEvent(final BaseEventHashedData hashedData, final Bytes signature) { this.hashedData = hashedData; - this.unhashedData = unhashedData; + this.signature = signature; this.timeReceived = Instant.now(); this.senderId = null; } @@ -122,13 +134,9 @@ public long getStreamSequenceNumber() { */ @Override public void serialize(final SerializableDataOutputStream out) throws IOException { - if (serializedVersion < ClassVersion.BIRTH_ROUND) { - out.writeSerializable(hashedData, false); - out.writeSerializable(unhashedData, false); - } else { - out.writeSerializable(hashedData, false); - out.writeByteArray(unhashedData.getSignature()); - } + out.writeSerializable(hashedData, false); + out.writeInt((int) signature.length()); + signature.writeTo(out); } /** @@ -136,15 +144,9 @@ public void serialize(final SerializableDataOutputStream out) throws IOException */ @Override public void deserialize(final SerializableDataInputStream in, final int version) throws IOException { - serializedVersion = version; - if (version < ClassVersion.BIRTH_ROUND) { - hashedData = in.readSerializable(false, BaseEventHashedData::new); - unhashedData = in.readSerializable(false, BaseEventUnhashedData::new); - } else { - hashedData = in.readSerializable(false, BaseEventHashedData::new); - final byte[] signature = in.readByteArray(MAX_SIG_LENGTH); - unhashedData = new BaseEventUnhashedData(signature); - } + hashedData = in.readSerializable(false, BaseEventHashedData::new); + final byte[] signature = in.readByteArray(SignatureType.RSA.signatureLength()); + this.signature = Bytes.wrap(signature); timeReceived = Instant.now(); } @@ -156,10 +158,10 @@ public BaseEventHashedData getHashedData() { } /** - * Get the unhashed data for the event. + * @return the signature for the event */ - public BaseEventUnhashedData getUnhashedData() { - return unhashedData; + public @NonNull Bytes getSignature() { + return signature; } /** @@ -171,6 +173,28 @@ public EventDescriptor getDescriptor() { return hashedData.getDescriptor(); } + @Override + public Iterator transactionIterator() { + return Arrays.asList((Transaction[]) hashedData.getTransactions()).iterator(); + } + + @Override + public Instant getTimeCreated() { + return hashedData.getTimeCreated(); + } + + @Nullable + @Override + public SoftwareVersion getSoftwareVersion() { + return hashedData.getSoftwareVersion(); + } + + @NonNull + @Override + public NodeId getCreatorId() { + return hashedData.getCreatorId(); + } + /** * Get the generation of the event. * @@ -180,6 +204,13 @@ public long getGeneration() { return hashedData.getGeneration(); } + /** + * @return the number of payloads this event contains + */ + public int getPayloadCount() { + return hashedData.getTransactions().length; + } + /** * Get the time this event was received via gossip * @@ -244,12 +275,12 @@ public long getClassId() { */ @Override public int getVersion() { - return serializedVersion; + return ClassVersion.BIRTH_ROUND; } @Override public int getMinimumSupportedVersion() { - return ClassVersion.REMOVED_ROUND; + return ClassVersion.BIRTH_ROUND; } /** diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/event/creation/DefaultEventCreationManager.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/event/creation/DefaultEventCreationManager.java index f4933dd5a0b1..0bcaa7de5848 100644 --- a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/event/creation/DefaultEventCreationManager.java +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/event/creation/DefaultEventCreationManager.java @@ -31,7 +31,7 @@ import com.swirlds.platform.event.creation.rules.EventCreationRule; import com.swirlds.platform.event.creation.rules.MaximumRateRule; import com.swirlds.platform.event.creation.rules.PlatformStatusRule; -import com.swirlds.platform.eventhandling.TransactionPool; +import com.swirlds.platform.pool.TransactionPoolNexus; import com.swirlds.platform.system.events.BaseEventHashedData; import com.swirlds.platform.system.status.PlatformStatus; import edu.umd.cs.findbugs.annotations.NonNull; @@ -65,13 +65,13 @@ public class DefaultEventCreationManager implements EventCreationManager { * Constructor. * * @param platformContext the platform context - * @param transactionPool provides transactions to be added to new events + * @param transactionPoolNexus provides transactions to be added to new events * @param eventIntakeQueueSize supplies the size of the event intake queue * @param creator creates events */ public DefaultEventCreationManager( @NonNull final PlatformContext platformContext, - @NonNull final TransactionPool transactionPool, + @NonNull final TransactionPoolNexus transactionPoolNexus, @NonNull final LongSupplier eventIntakeQueueSize, @NonNull final EventCreator creator) { @@ -80,7 +80,7 @@ public DefaultEventCreationManager( this.eventCreationRules = AggregateEventCreationRules.of( new MaximumRateRule(platformContext), new BackpressureRule(platformContext, eventIntakeQueueSize), - new PlatformStatusRule(this::getPlatformStatus, transactionPool)); + new PlatformStatusRule(this::getPlatformStatus, transactionPoolNexus)); phase = new PhaseTimerBuilder<>( platformContext, platformContext.getTime(), "platform", EventCreationStatus.class) diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/event/creation/rules/PlatformStatusRule.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/event/creation/rules/PlatformStatusRule.java index 711e5c6638b6..1fb0795ebaf6 100644 --- a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/event/creation/rules/PlatformStatusRule.java +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/event/creation/rules/PlatformStatusRule.java @@ -19,7 +19,7 @@ import static com.swirlds.platform.event.creation.EventCreationStatus.PLATFORM_STATUS; import com.swirlds.platform.event.creation.EventCreationStatus; -import com.swirlds.platform.eventhandling.TransactionPool; +import com.swirlds.platform.pool.TransactionPoolNexus; import com.swirlds.platform.system.status.PlatformStatus; import edu.umd.cs.findbugs.annotations.NonNull; import java.util.Objects; @@ -31,20 +31,20 @@ public class PlatformStatusRule implements EventCreationRule { private final Supplier platformStatusSupplier; - private final TransactionPool transactionPool; + private final TransactionPoolNexus transactionPoolNexus; /** * Constructor. * - * @param platformStatusSupplier provides the current platform status - * @param transactionPool provides transactions to be added to new events + * @param platformStatusSupplier provides the current platform status + * @param transactionPoolNexus provides transactions to be added to new events */ public PlatformStatusRule( @NonNull final Supplier platformStatusSupplier, - @NonNull final TransactionPool transactionPool) { + @NonNull final TransactionPoolNexus transactionPoolNexus) { this.platformStatusSupplier = Objects.requireNonNull(platformStatusSupplier); - this.transactionPool = Objects.requireNonNull(transactionPool); + this.transactionPoolNexus = Objects.requireNonNull(transactionPoolNexus); } /** @@ -55,7 +55,7 @@ public boolean isEventCreationPermitted() { final PlatformStatus currentStatus = platformStatusSupplier.get(); if (currentStatus == PlatformStatus.FREEZING) { - return transactionPool.hasBufferedSignatureTransactions(); + return transactionPoolNexus.hasBufferedSignatureTransactions(); } if (currentStatus != PlatformStatus.ACTIVE && currentStatus != PlatformStatus.CHECKING) { diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/event/deduplication/StandardEventDeduplicator.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/event/deduplication/StandardEventDeduplicator.java index 3c977bab3a3a..cf7150458fc1 100644 --- a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/event/deduplication/StandardEventDeduplicator.java +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/event/deduplication/StandardEventDeduplicator.java @@ -19,6 +19,7 @@ import static com.swirlds.metrics.api.FloatFormats.FORMAT_10_2; import static com.swirlds.metrics.api.Metrics.PLATFORM_CATEGORY; +import com.hedera.pbj.runtime.io.buffer.Bytes; import com.swirlds.common.context.PlatformContext; import com.swirlds.common.metrics.RunningAverageMetric; import com.swirlds.common.metrics.extensions.CountPerSecond; @@ -35,7 +36,6 @@ import com.swirlds.platform.wiring.NoInput; import edu.umd.cs.findbugs.annotations.NonNull; import edu.umd.cs.findbugs.annotations.Nullable; -import java.nio.ByteBuffer; import java.util.HashSet; import java.util.Objects; import java.util.Set; @@ -48,7 +48,7 @@ public class StandardEventDeduplicator implements EventDeduplicator { /** * Avoid the creation of lambdas for Map.computeIfAbsent() by reusing this lambda. */ - private static final Function> NEW_HASH_SET = ignored -> new HashSet<>(); + private static final Function> NEW_HASH_SET = ignored -> new HashSet<>(); /** * Initial capacity of {@link #observedEvents}. @@ -68,7 +68,7 @@ public class StandardEventDeduplicator implements EventDeduplicator { /** * A map from event descriptor to a set of signatures that have been received for that event. */ - private final SequenceMap> observedEvents; + private final SequenceMap> observedEvents; private static final LongAccumulator.Config DISPARATE_SIGNATURE_CONFIG = new LongAccumulator.Config( PLATFORM_CATEGORY, "eventsWithDisparateSignature") @@ -130,8 +130,8 @@ public GossipEvent handleEvent(@NonNull final GossipEvent event) { return null; } - final Set signatures = observedEvents.computeIfAbsent(event.getDescriptor(), NEW_HASH_SET); - if (signatures.add(ByteBuffer.wrap(event.getUnhashedData().getSignature()))) { + final Set signatures = observedEvents.computeIfAbsent(event.getDescriptor(), NEW_HASH_SET); + if (signatures.add(event.getSignature())) { if (signatures.size() != 1) { // signature is unique, but descriptor is not disparateSignatureAccumulator.update(1); diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/event/linking/AbstractInOrderLinker.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/event/linking/AbstractInOrderLinker.java index fe96ecf0c059..58fb5a362ccf 100644 --- a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/event/linking/AbstractInOrderLinker.java +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/event/linking/AbstractInOrderLinker.java @@ -31,7 +31,6 @@ import com.swirlds.platform.internal.EventImpl; import com.swirlds.platform.system.events.BaseEventHashedData; import com.swirlds.platform.system.events.EventDescriptor; -import com.swirlds.platform.wiring.NoInput; import edu.umd.cs.findbugs.annotations.NonNull; import edu.umd.cs.findbugs.annotations.Nullable; import java.time.Duration; @@ -54,7 +53,6 @@ * */ abstract class AbstractInOrderLinker implements InOrderLinker { - private static final Logger logger = LogManager.getLogger(AbstractInOrderLinker.class); /** * The initial capacity of the {@link #parentDescriptorMap} and {@link #parentHashMap} @@ -98,6 +96,12 @@ abstract class AbstractInOrderLinker implements InOrderLinker { * @param platformContext the platform context */ public AbstractInOrderLinker(@NonNull final PlatformContext platformContext) { + // We use a non-static logger here so that we can scope the logger to the concrete + // implementation of this class, not to the abstract class. Once instantiated, a + // linker has the same life span as a node, so it is not inefficient to + // have a non-static logger. + final Logger logger = LogManager.getLogger(this.getClass()); + this.missingParentLogger = new RateLimitedLogger(logger, platformContext.getTime(), MINIMUM_LOG_PERIOD); this.generationMismatchLogger = new RateLimitedLogger(logger, platformContext.getTime(), MINIMUM_LOG_PERIOD); this.birthRoundMismatchLogger = new RateLimitedLogger(logger, platformContext.getTime(), MINIMUM_LOG_PERIOD); @@ -165,10 +169,9 @@ public void setEventWindow(@NonNull final EventWindow eventWindow) { /** * Clear the internal state of this linker. - * - * @param ignored ignored trigger object */ - public void clear(@NonNull final NoInput ignored) { + @Override + public void clear() { parentDescriptorMap.clear(); parentHashMap.clear(); } @@ -265,6 +268,7 @@ protected void ancientEventAdded(@NonNull final GossipEvent event) { /** * This method is called when this data structure stops tracking an event because it has become ancient. + * * @param event the event that has become ancient */ protected void eventHasBecomeAncient(@NonNull final EventImpl event) { diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/event/linking/ConsensusLinker.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/event/linking/ConsensusLinker.java index 0036332744d2..ac8ec1e1dd04 100644 --- a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/event/linking/ConsensusLinker.java +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/event/linking/ConsensusLinker.java @@ -23,7 +23,6 @@ import com.swirlds.metrics.api.LongAccumulator; import com.swirlds.platform.event.GossipEvent; import com.swirlds.platform.internal.EventImpl; -import com.swirlds.platform.metrics.StaleMetrics; import com.swirlds.platform.system.events.EventDescriptor; import edu.umd.cs.findbugs.annotations.NonNull; import java.time.Instant; @@ -40,8 +39,6 @@ public class ConsensusLinker extends AbstractInOrderLinker { private final LongAccumulator birthRoundMismatchAccumulator; private final LongAccumulator timeCreatedMismatchAccumulator; - private final StaleMetrics staleMetrics; - /** * Constructor * @@ -73,8 +70,6 @@ public ConsensusLinker(@NonNull final PlatformContext platformContext, @NonNull new LongAccumulator.Config(PLATFORM_CATEGORY, "timeCreatedMismatch") .withDescription( "Parent child relationships where child time created wasn't strictly after parent time created")); - - staleMetrics = new StaleMetrics(platformContext, selfId); } /** @@ -82,9 +77,6 @@ public ConsensusLinker(@NonNull final PlatformContext platformContext, @NonNull */ @Override protected void eventHasBecomeAncient(@NonNull final EventImpl event) { - if (!event.isConsensus()) { - staleMetrics.staleEvent(event); - } event.clear(); } diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/event/linking/InOrderLinker.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/event/linking/InOrderLinker.java index 9285f9ee835e..3d95daf83049 100644 --- a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/event/linking/InOrderLinker.java +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/event/linking/InOrderLinker.java @@ -21,7 +21,6 @@ import com.swirlds.platform.event.GossipEvent; import com.swirlds.platform.gossip.shadowgraph.Shadowgraph; import com.swirlds.platform.internal.EventImpl; -import com.swirlds.platform.wiring.NoInput; import edu.umd.cs.findbugs.annotations.NonNull; import edu.umd.cs.findbugs.annotations.Nullable; @@ -59,8 +58,6 @@ public interface InOrderLinker { /** * Clear the internal state of this linker. - * - * @param ignored ignored trigger object */ - void clear(@NonNull final NoInput ignored); + void clear(); } diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/event/report/EventStreamInfo.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/event/report/EventStreamInfo.java new file mode 100644 index 000000000000..7476466ce3ca --- /dev/null +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/event/report/EventStreamInfo.java @@ -0,0 +1,60 @@ +/* + * Copyright (C) 2016-2024 Hedera Hashgraph, LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.swirlds.platform.event.report; + +import com.swirlds.platform.system.events.DetailedConsensusEvent; +import java.time.Instant; + +/** + * Information about an event stream. + * + * @param start + * the timestamp at the start of the period reported + * @param end + * the timestamp at the end of the period reported + * @param eventCount + * the number of events in this period + * @param transactionCount + * the total number of transactions in this period + * @param systemTransactionCount + * the number of system transactions in this period + * @param applicationTransactionCount + * the number of application transactions in this period + * @param fileCount + * the number of files in this period + * @param byteCount + * the byte count of + * @param firstEvent + * the first event in the time period + * @param lastEvent + * the last event in the time period + * @param damagedFileCount + * the number of damaged files + */ +public record EventStreamInfo( + Instant start, + Instant end, + long roundCount, + long eventCount, + long transactionCount, + long systemTransactionCount, + long applicationTransactionCount, + long fileCount, + long byteCount, + DetailedConsensusEvent firstEvent, + DetailedConsensusEvent lastEvent, + long damagedFileCount) {} diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/event/report/EventStreamMultiNodeReport.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/event/report/EventStreamMultiNodeReport.java new file mode 100644 index 000000000000..31ba27b4f6c4 --- /dev/null +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/event/report/EventStreamMultiNodeReport.java @@ -0,0 +1,94 @@ +/* + * Copyright (C) 2023-2024 Hedera Hashgraph, LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.swirlds.platform.event.report; + +import static com.swirlds.common.formatting.TextEffect.BRIGHT_RED; +import static com.swirlds.common.formatting.TextEffect.BRIGHT_YELLOW; + +import com.swirlds.common.formatting.TextTable; +import edu.umd.cs.findbugs.annotations.NonNull; +import java.nio.file.Path; +import java.time.Instant; +import java.util.Comparator; +import java.util.HashMap; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Objects; + +/** + * A report summarizing a collection of event stream files from multiple nodes + */ +public class EventStreamMultiNodeReport { + /** + * A map from a directory to an {@link EventStreamInfo} which summarizes that event stream files in that directory + */ + private final Map individualReports = new HashMap<>(); + + /** + * Add an individual node report to this multi-node report + * + * @param directory the directory containing the event stream files of the individual report + * @param individualReport the individual report + */ + public void addIndividualReport(@NonNull final Path directory, @NonNull final EventStreamReport individualReport) { + Objects.requireNonNull(directory); + Objects.requireNonNull(individualReport); + + individualReports.put(directory.toString(), individualReport.summary()); + } + + @Override + @NonNull + public String toString() { + final StringBuilder sb = new StringBuilder(); + sb.append("\n"); + + final Entry entryWithLatestEvent = individualReports.entrySet().stream() + .max(Map.Entry.comparingByValue(((o1, o2) -> { + final Instant o1Timestamp = + o1.lastEvent().getConsensusData().getConsensusTimestamp(); + final Instant o2Timestamp = + o2.lastEvent().getConsensusData().getConsensusTimestamp(); + + return o1Timestamp.compareTo(o2Timestamp); + }))) + .orElseThrow(); + + final Entry entryWithMostEvents = individualReports.entrySet().stream() + .max(Map.Entry.comparingByValue((Comparator.comparingLong(EventStreamInfo::eventCount)))) + .orElseThrow(); + + new TextTable() + .setTitle("Multi-node Event Stream Summary") + .addRow(BRIGHT_RED.apply("Directory with latest event"), entryWithLatestEvent.getKey()) + .addRow( + BRIGHT_YELLOW.apply("Latest event consensus timestamp"), + entryWithLatestEvent + .getValue() + .lastEvent() + .getConsensusData() + .getConsensusTimestamp()) + .addRow(BRIGHT_RED.apply("Directory with most events"), entryWithMostEvents.getKey()) + .addRow( + BRIGHT_YELLOW.apply("Number of events"), + entryWithMostEvents.getValue().eventCount()) + .render(sb); + sb.append("\n\n"); + + return sb.toString(); + } +} diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/event/report/EventStreamReport.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/event/report/EventStreamReport.java new file mode 100644 index 000000000000..2baa9d453ca8 --- /dev/null +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/event/report/EventStreamReport.java @@ -0,0 +1,184 @@ +/* + * Copyright (C) 2022-2024 Hedera Hashgraph, LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.swirlds.platform.event.report; + +import static com.swirlds.common.formatting.StringFormattingUtils.commaSeparatedNumber; +import static com.swirlds.common.formatting.TextEffect.BRIGHT_CYAN; +import static com.swirlds.common.formatting.TextEffect.BRIGHT_RED; +import static com.swirlds.common.formatting.TextEffect.BRIGHT_YELLOW; +import static com.swirlds.common.units.DataUnit.UNIT_BYTES; + +import com.swirlds.common.formatting.HorizontalAlignment; +import com.swirlds.common.formatting.TextHistogram; +import com.swirlds.common.formatting.TextTable; +import com.swirlds.common.formatting.UnitFormatter; +import com.swirlds.platform.system.events.DetailedConsensusEvent; +import java.time.Duration; +import java.util.List; + +/** + * Useful information about an event stream + * + * @param granularInfo + * information about the event stream broken down into small time periods + * @param summary + * a summary about the entire event stream + */ +public record EventStreamReport(List granularInfo, EventStreamInfo summary) { + + private static final int HASH_STRING_LENGTH = 12; + + @Override + public String toString() { + final StringBuilder sb = new StringBuilder(); + sb.append("\n"); + + sb.append("--- Event Count ---\n"); + new TextHistogram<>(granularInfo, EventStreamInfo::eventCount) + .setTimestampExtractor(EventStreamInfo::start) + .render(sb); + sb.append("\n"); + + sb.append("--- Rounds ---\n"); + new TextHistogram<>(granularInfo, EventStreamInfo::roundCount) + .setTimestampExtractor(EventStreamInfo::start) + .render(sb); + sb.append("\n"); + + sb.append("--- Transaction Count ---\n"); + new TextHistogram<>(granularInfo, EventStreamInfo::transactionCount) + .setTimestampExtractor(EventStreamInfo::start) + .render(sb); + sb.append("\n"); + + sb.append("--- System Transaction Count ---\n"); + new TextHistogram<>(granularInfo, EventStreamInfo::systemTransactionCount) + .setTimestampExtractor(EventStreamInfo::start) + .render(sb); + sb.append("\n"); + + sb.append("--- Application Transaction Count ---\n"); + new TextHistogram<>(granularInfo, EventStreamInfo::applicationTransactionCount) + .setTimestampExtractor(EventStreamInfo::start) + .render(sb); + sb.append("\n"); + + sb.append("--- File Count ---\n"); + new TextHistogram<>(granularInfo, EventStreamInfo::fileCount) + .setTimestampExtractor(EventStreamInfo::start) + .render(sb); + sb.append("\n"); + + sb.append("--- Damaged File Count ---\n"); + new TextHistogram<>(granularInfo, EventStreamInfo::damagedFileCount) + .setTimestampExtractor(EventStreamInfo::start) + .render(sb); + sb.append("\n"); + + sb.append("--- Byte Count ---\n"); + new TextHistogram<>(granularInfo, EventStreamInfo::byteCount) + .setTimestampExtractor(EventStreamInfo::start) + .setValueUnit(UNIT_BYTES) + .render(sb); + sb.append("\n"); + + final DetailedConsensusEvent firstEvent = summary.firstEvent(); + final DetailedConsensusEvent lastEvent = summary.lastEvent(); + new TextTable() + .setTitle("First/Last Event Info") + .addTitleEffects(BRIGHT_CYAN) + .addColumnEffects(0, BRIGHT_RED) + .addRowEffects(0, BRIGHT_RED) + .setRowHorizontalAlignment(0, HorizontalAlignment.ALIGNED_CENTER) + .addRow("", "first event", "last event") + .addRow( + "round", + commaSeparatedNumber(firstEvent.getConsensusData().getRoundReceived()), + commaSeparatedNumber(lastEvent.getConsensusData().getRoundReceived())) + .addRow( + "timestamp", + firstEvent.getConsensusData().getConsensusTimestamp(), + lastEvent.getConsensusData().getConsensusTimestamp()) + .addRow( + "hash", + firstEvent.getHash().toHex(HASH_STRING_LENGTH), + lastEvent.getHash().toHex(HASH_STRING_LENGTH)) + .addRow( + "running hash", + firstEvent.getRunningHash().getHash().toHex(HASH_STRING_LENGTH), + lastEvent.getRunningHash().getHash().toHex(HASH_STRING_LENGTH)) + .addRow( + "consensus order", + commaSeparatedNumber(firstEvent.getConsensusData().getConsensusOrder()), + commaSeparatedNumber(lastEvent.getConsensusData().getConsensusOrder())) + .addRow( + "generation", + commaSeparatedNumber(firstEvent.getGossipEvent().getGeneration()), + commaSeparatedNumber(lastEvent.getGossipEvent().getGeneration())) + .addRow( + "creator ID", + firstEvent.getGossipEvent().getCreatorId(), + lastEvent.getGossipEvent().getCreatorId()) + .addRow( + "last in round", + firstEvent.getConsensusData().isLastInRoundReceived() ? "yes" : "no", + lastEvent.getConsensusData().isLastInRoundReceived() ? "yes" : "no") + .addRow( + "transaction count", + commaSeparatedNumber(firstEvent.getGossipEvent().getPayloadCount()), + commaSeparatedNumber(lastEvent.getGossipEvent().getPayloadCount())) + .render(sb); + + sb.append("\n\n"); + new TextTable() + .setBordersEnabled(false) + .addRow( + BRIGHT_RED.apply("first event full hash"), + firstEvent.getHash().toString()) + .addRow( + BRIGHT_YELLOW.apply("first event full running hash"), + firstEvent.getRunningHash().getHash().toString()) + .addRow( + BRIGHT_RED.apply("last event full hash"), + lastEvent.getHash().toString()) + .addRow( + BRIGHT_YELLOW.apply("last event full running hash"), + lastEvent.getRunningHash().getHash().toString()) + .render(sb); + sb.append("\n\n"); + + new TextTable() + .setTitle(BRIGHT_CYAN.apply("Stream Info")) + .addTitleEffects(BRIGHT_CYAN) + .addColumnEffects(0, BRIGHT_RED) + .addRow("rounds", commaSeparatedNumber(summary.roundCount())) + .addRow( + "time", + commaSeparatedNumber(Duration.between(summary.start(), summary.end()) + .toSeconds()) + "s") + .addRow("events", commaSeparatedNumber(summary.eventCount())) + .addRow("transactions", commaSeparatedNumber(summary.transactionCount())) + .addRow("system transactions", commaSeparatedNumber(summary.systemTransactionCount())) + .addRow("application transactions", commaSeparatedNumber(summary.applicationTransactionCount())) + .addRow("files", commaSeparatedNumber(summary.fileCount())) + .addRow("bytes", new UnitFormatter(summary.byteCount(), UNIT_BYTES).render()) + .addRow("damaged file count", commaSeparatedNumber(summary.damagedFileCount())) + .render(sb); + + return sb.toString(); + } +} diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/event/report/EventStreamScanner.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/event/report/EventStreamScanner.java new file mode 100644 index 000000000000..d9bf75bacabd --- /dev/null +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/event/report/EventStreamScanner.java @@ -0,0 +1,222 @@ +/* + * Copyright (C) 2023-2024 Hedera Hashgraph, LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.swirlds.platform.event.report; + +import static com.swirlds.common.formatting.StringFormattingUtils.commaSeparatedNumber; +import static com.swirlds.common.formatting.TextEffect.BRIGHT_RED; +import static com.swirlds.common.formatting.TextEffect.BRIGHT_YELLOW; +import static com.swirlds.common.utility.CompareTo.isGreaterThan; + +import com.swirlds.common.formatting.UnitFormatter; +import com.swirlds.common.io.IOIterator; +import com.swirlds.common.units.TimeUnit; +import com.swirlds.platform.recovery.internal.EventStreamLowerBound; +import com.swirlds.platform.recovery.internal.EventStreamMultiFileIterator; +import com.swirlds.platform.recovery.internal.MultiFileRunningHashIterator; +import com.swirlds.platform.system.events.DetailedConsensusEvent; +import edu.umd.cs.findbugs.annotations.NonNull; +import java.io.IOException; +import java.nio.file.Path; +import java.time.Duration; +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; + +/** + * Scans an event stream and generates a report. + */ +public class EventStreamScanner { + + private static final int PROGRESS_INTERVAL = 10_000; + + private long eventCount = 0; + private long transactionCount = 0; + private long systemTransactionCount = 0; + private long applicationTransactionCount = 0; + + // These variables store data for the higher granularity reports + private final List granularInfo = new ArrayList<>(); + private DetailedConsensusEvent granularFirstEvent; + private long granularStartingFileCount = 0; + private long previousFileCount = 0; + private long previousDamagedFileCount = 0; + private long previousByteCount = 0; + private long granularStartingDamagedFileCount = 0; + private long granularEventCount = 0; + private long granularTransactionCount = 0; + private long granularSystemTransactionCount = 0; + private long granularApplicationTransactionCount = 0; + + private final Duration reportPeriod; + private final boolean enableProgressReport; + + private final EventStreamMultiFileIterator fileIterator; + private final IOIterator eventIterator; + + public EventStreamScanner( + @NonNull final Path eventStreamDirectory, + @NonNull final EventStreamLowerBound lowerBound, + @NonNull final Duration reportPeriod, + final boolean enableProgressReport) + throws IOException { + Objects.requireNonNull(eventStreamDirectory, "the event stream directory must not be null"); + Objects.requireNonNull(lowerBound, "the lower bound must not be null"); + this.reportPeriod = Objects.requireNonNull(reportPeriod, "the report period must not be null"); + this.enableProgressReport = enableProgressReport; + + fileIterator = new EventStreamMultiFileIterator(eventStreamDirectory, lowerBound); + eventIterator = new MultiFileRunningHashIterator(fileIterator); + } + + /** + * Time is split into "chunks". For each chunk of time we generate a mini-report. When this method is called + * we gather all the data from a single chunk of time. + */ + private void reportGranularData(final DetailedConsensusEvent lastEventInPeriod) { + final long granularRoundCount = lastEventInPeriod.getConsensusData().getRoundReceived() + - granularFirstEvent.getConsensusData().getRoundReceived(); + + granularInfo.add(new EventStreamInfo( + granularFirstEvent.getConsensusData().getConsensusTimestamp(), + lastEventInPeriod.getConsensusData().getConsensusTimestamp(), + granularRoundCount, + granularEventCount, + granularTransactionCount, + granularSystemTransactionCount, + granularApplicationTransactionCount, + previousFileCount - granularStartingFileCount, + fileIterator.getBytesRead() - previousByteCount, + granularFirstEvent, + lastEventInPeriod, + previousDamagedFileCount - granularStartingDamagedFileCount)); + } + + /** + * This should be called between each "chunk" of time for which we collect granular data. + */ + private void resetGranularData(final DetailedConsensusEvent mostRecentEvent) { + granularFirstEvent = mostRecentEvent; + granularEventCount = 0; + granularTransactionCount = 0; + granularSystemTransactionCount = 0; + granularApplicationTransactionCount = 0; + granularStartingFileCount = fileIterator.getFileCount(); + granularStartingDamagedFileCount = fileIterator.getDamagedFileCount(); + previousByteCount = fileIterator.getBytesRead(); + } + + /** + * Collect data from an event. + */ + private void collectEventData(final DetailedConsensusEvent mostRecentEvent) { + eventCount++; + granularEventCount++; + mostRecentEvent.getGossipEvent().transactionIterator().forEachRemaining(transaction -> { + transactionCount++; + granularTransactionCount++; + if (transaction.isSystem()) { + systemTransactionCount++; + granularSystemTransactionCount++; + } else { + applicationTransactionCount++; + granularApplicationTransactionCount++; + } + }); + } + + /** + * Write information to the console that let's a human know of the progress of the scan. + */ + private void writeConsoleSummary( + final DetailedConsensusEvent firstEvent, final DetailedConsensusEvent mostRecentEvent) { + + if (enableProgressReport && eventCount % PROGRESS_INTERVAL == 0) { + // This is intended to be used in a terminal with a human in the loop, intentionally not logged. + final Duration consensusTimeProcessed = Duration.between( + firstEvent.getConsensusData().getConsensusTimestamp(), + mostRecentEvent.getConsensusData().getConsensusTimestamp()); + + final UnitFormatter formatter = TimeUnit.UNIT_MILLISECONDS + .buildFormatter() + .setQuantity(consensusTimeProcessed.toMillis()) + .setDecimalPlaces(2) + .setAbbreviate(false); + + System.out.print(" > " + BRIGHT_RED.apply(commaSeparatedNumber(eventCount)) + + " events have been parsed from a consensus timespan of " + + BRIGHT_YELLOW.apply(formatter.render()) + + " \r"); + System.out.flush(); + } + } + + public EventStreamReport createReport() throws IOException { + + if (!eventIterator.hasNext()) { + throw new IllegalStateException("No events found in the event stream"); + } + + final DetailedConsensusEvent firstEvent = eventIterator.peek(); + granularFirstEvent = firstEvent; + + DetailedConsensusEvent mostRecentEvent = null; + DetailedConsensusEvent previousEvent; + while (eventIterator.hasNext()) { + previousEvent = mostRecentEvent; + mostRecentEvent = eventIterator.next(); + + final Duration elapsedGranularTime = Duration.between( + granularFirstEvent.getConsensusData().getConsensusTimestamp(), + mostRecentEvent.getConsensusData().getConsensusTimestamp()); + if (previousEvent != null && isGreaterThan(elapsedGranularTime, reportPeriod)) { + // The previous granular period has ended. Start a new period. + reportGranularData(previousEvent); + resetGranularData(mostRecentEvent); + } + + collectEventData(mostRecentEvent); + + previousFileCount = fileIterator.getFileCount(); + previousDamagedFileCount = fileIterator.getDamagedFileCount(); + + if (!eventIterator.hasNext() && granularEventCount > 0) { + reportGranularData(mostRecentEvent); + } + + writeConsoleSummary(firstEvent, mostRecentEvent); + } + + final long rounds = mostRecentEvent.getConsensusData().getRoundReceived() + - firstEvent.getConsensusData().getRoundReceived(); + + return new EventStreamReport( + granularInfo, + new EventStreamInfo( + firstEvent.getConsensusData().getConsensusTimestamp(), + mostRecentEvent.getConsensusData().getConsensusTimestamp(), + rounds, + eventCount, + transactionCount, + systemTransactionCount, + applicationTransactionCount, + fileIterator.getFileCount(), + fileIterator.getBytesRead(), + firstEvent, + mostRecentEvent, + fileIterator.getDamagedFileCount())); + } +} diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/signed/LoadableFromSignedState.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/event/report/RoundRunningHash.java similarity index 63% rename from platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/signed/LoadableFromSignedState.java rename to platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/event/report/RoundRunningHash.java index cc16984ea09c..69a7eb261cd0 100644 --- a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/signed/LoadableFromSignedState.java +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/event/report/RoundRunningHash.java @@ -14,18 +14,16 @@ * limitations under the License. */ -package com.swirlds.platform.state.signed; +package com.swirlds.platform.event.report; + +import com.swirlds.common.crypto.Hash; /** - * A class that can be loaded from a signed state. + * The running hash of the event stream at the end of a round + * + * @param round + * the round in question + * @param runningHash + * the hash */ -public interface LoadableFromSignedState { - - /** - * Loads all necessary data from the {@code reservedSignedState}. - * - * @param signedState - * the signed state to load - */ - void loadFromSignedState(final SignedState signedState); -} +public record RoundRunningHash(long round, Hash runningHash) {} diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/event/runninghash/DefaultRunningEventHasher.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/event/runninghash/DefaultRunningEventHasher.java deleted file mode 100644 index eb4664e7283d..000000000000 --- a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/event/runninghash/DefaultRunningEventHasher.java +++ /dev/null @@ -1,99 +0,0 @@ -/* - * Copyright (C) 2024 Hedera Hashgraph, LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.swirlds.platform.event.runninghash; - -import static com.swirlds.common.utility.ByteUtils.intToByteArray; -import static com.swirlds.common.utility.ByteUtils.longToByteArray; - -import com.swirlds.common.crypto.DigestType; -import com.swirlds.common.crypto.Hash; -import com.swirlds.common.crypto.HashingOutputStream; -import com.swirlds.common.stream.RunningEventHashOverride; -import com.swirlds.platform.internal.ConsensusRound; -import com.swirlds.platform.internal.EventImpl; -import edu.umd.cs.findbugs.annotations.NonNull; -import java.io.IOException; -import java.security.MessageDigest; -import java.security.NoSuchAlgorithmException; -import java.time.Instant; - -/** - * A standard implementation of the {@link RunningEventHasher}. - */ -public class DefaultRunningEventHasher implements RunningEventHasher { - - private static final DigestType DIGEST_TYPE = DigestType.SHA_384; - private final HashingOutputStream hashingOutputStream; - - private Hash runningEventHash; - - /** - * Constructor. - */ - public DefaultRunningEventHasher() { - final MessageDigest digest; - try { - digest = MessageDigest.getInstance(DIGEST_TYPE.algorithmName()); - } catch (final NoSuchAlgorithmException e) { - throw new RuntimeException(e); - } - this.hashingOutputStream = new HashingOutputStream(digest); - } - - /** - * {@inheritDoc} - */ - @Override - public void computeRunningEventHash(@NonNull final ConsensusRound round) { - final long roundNumber = round.getRoundNum(); - if (runningEventHash == null) { - throw new IllegalStateException( - "Prior running event hash must be set before computing the running event hash for round " - + roundNumber); - } - - try { - hashingOutputStream.resetDigest(); - hashingOutputStream.write(runningEventHash.getBytes().toByteArray()); - hashingOutputStream.write(longToByteArray(roundNumber)); - - for (final EventImpl event : round.getConsensusEvents()) { - final Hash eventHash = event.getBaseEvent().getHashedData().getHash(); - final Instant consensusTimestamp = event.getConsensusData().getConsensusTimestamp(); - - hashingOutputStream.write(eventHash.getBytes().toByteArray()); - hashingOutputStream.write(longToByteArray(event.getConsensusOrder())); - hashingOutputStream.write(longToByteArray(consensusTimestamp.getEpochSecond())); - hashingOutputStream.write(intToByteArray(consensusTimestamp.getNano())); - } - - runningEventHash = new Hash(hashingOutputStream.getDigest(), DIGEST_TYPE); - - round.setRunningEventHash(runningEventHash); - } catch (final IOException e) { - throw new RuntimeException(e); - } - } - - /** - * {@inheritDoc} - */ - @Override - public void overrideRunningEventHash(@NonNull final RunningEventHashOverride runningEventHashOverride) { - runningEventHash = runningEventHashOverride.runningEventHash(); - } -} diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/event/runninghash/RunningEventHasher.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/event/runninghash/RunningEventHasher.java deleted file mode 100644 index d59734c982cc..000000000000 --- a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/event/runninghash/RunningEventHasher.java +++ /dev/null @@ -1,44 +0,0 @@ -/* - * Copyright (C) 2024 Hedera Hashgraph, LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.swirlds.platform.event.runninghash; - -import com.swirlds.common.stream.RunningEventHashOverride; -import com.swirlds.common.wiring.component.InputWireLabel; -import com.swirlds.platform.internal.ConsensusRound; -import edu.umd.cs.findbugs.annotations.NonNull; - -/** - * Computes the running event hash of events that have reached consensus. - */ -public interface RunningEventHasher { - - /** - * Compute the running event hash for the given round. When computed, write the hash to the round. - * - * @param round the round - */ - @InputWireLabel("rounds") - void computeRunningEventHash(@NonNull final ConsensusRound round); - - /** - * Override the running event hash of the previous round. This must be called at restart and reconnect boundaries. - * - * @param runningEventHashOverride the running event hash override - */ - @InputWireLabel("hash override") - void overrideRunningEventHash(@NonNull final RunningEventHashOverride runningEventHashOverride); -} diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/event/signing/DefaultSelfEventSigner.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/event/signing/DefaultSelfEventSigner.java index 557720f7b288..a565ba03e0b4 100644 --- a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/event/signing/DefaultSelfEventSigner.java +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/event/signing/DefaultSelfEventSigner.java @@ -21,7 +21,6 @@ import com.swirlds.platform.crypto.PlatformSigner; import com.swirlds.platform.event.GossipEvent; import com.swirlds.platform.system.events.BaseEventHashedData; -import com.swirlds.platform.system.events.BaseEventUnhashedData; import edu.umd.cs.findbugs.annotations.NonNull; import java.util.Objects; @@ -48,7 +47,6 @@ public DefaultSelfEventSigner(@NonNull final KeysAndCerts keysAndCerts) { @Override public GossipEvent signEvent(@NonNull final BaseEventHashedData event) { final Signature signature = new PlatformSigner(keysAndCerts).sign(event.getHash()); - final BaseEventUnhashedData unhashedData = new BaseEventUnhashedData(signature.getSignatureBytes()); - return new GossipEvent(event, unhashedData); + return new GossipEvent(event, signature.getSignatureBytes()); } } diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/event/signing/SelfEventSigner.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/event/signing/SelfEventSigner.java index 33f40c5e1274..a37ce639f577 100644 --- a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/event/signing/SelfEventSigner.java +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/event/signing/SelfEventSigner.java @@ -32,7 +32,7 @@ public interface SelfEventSigner { * @param event the event to sign * @return the signed event */ - @InputWireLabel("self event") + @InputWireLabel("self events") @NonNull GossipEvent signEvent(@NonNull BaseEventHashedData event); } diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/event/stale/DefaultStaleEventDetector.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/event/stale/DefaultStaleEventDetector.java new file mode 100644 index 000000000000..8500f9d3f1b0 --- /dev/null +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/event/stale/DefaultStaleEventDetector.java @@ -0,0 +1,168 @@ +/* + * Copyright (C) 2024 Hedera Hashgraph, LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.swirlds.platform.event.stale; + +import static com.swirlds.platform.event.AncientMode.BIRTH_ROUND_THRESHOLD; + +import com.swirlds.common.context.PlatformContext; +import com.swirlds.common.platform.NodeId; +import com.swirlds.common.sequence.map.StandardSequenceMap; +import com.swirlds.common.wiring.transformers.RoutableData; +import com.swirlds.platform.consensus.EventWindow; +import com.swirlds.platform.event.AncientMode; +import com.swirlds.platform.event.GossipEvent; +import com.swirlds.platform.eventhandling.EventConfig; +import com.swirlds.platform.internal.ConsensusRound; +import com.swirlds.platform.internal.EventImpl; +import com.swirlds.platform.system.events.EventDescriptor; +import edu.umd.cs.findbugs.annotations.NonNull; +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; +import java.util.function.ToLongFunction; + +/** + * Detects when a self event becomes stale. Note that this detection may not observe a self event go stale if the node + * needs to reconnect or restart. + */ +public class DefaultStaleEventDetector implements StaleEventDetector { + + /** + * The ID of this node. + */ + private final NodeId selfId; + + /** + * Self events that have not yet reached consensus. + */ + private final StandardSequenceMap selfEvents; + + /** + * The most recent event window we know about. + */ + private EventWindow currentEventWindow; + + /** + * Metrics for the stale event detector. + */ + private final StaleEventDetectorMetrics metrics; + + /** + * Constructor. + * + * @param platformContext the platform context + * @param selfId the ID of this node + */ + public DefaultStaleEventDetector(@NonNull final PlatformContext platformContext, @NonNull final NodeId selfId) { + + this.selfId = Objects.requireNonNull(selfId); + + final AncientMode ancientMode = platformContext + .getConfiguration() + .getConfigData(EventConfig.class) + .getAncientMode(); + + final ToLongFunction getAncientIdentifier; + if (ancientMode == BIRTH_ROUND_THRESHOLD) { + getAncientIdentifier = EventDescriptor::getBirthRound; + } else { + getAncientIdentifier = EventDescriptor::getGeneration; + } + selfEvents = new StandardSequenceMap<>(0, 1024, true, getAncientIdentifier); + + metrics = new StaleEventDetectorMetrics(platformContext); + } + + /** + * {@inheritDoc} + */ + @NonNull + @Override + public List> addSelfEvent(@NonNull final GossipEvent event) { + if (currentEventWindow == null) { + throw new IllegalStateException("Event window must be set before adding self events"); + } + + final RoutableData selfEvent = + new RoutableData<>(StaleEventDetectorOutput.SELF_EVENT, event); + + if (currentEventWindow.isAncient(event)) { + // Although unlikely, it is plausible for an event to go stale before it is added to the detector. + handleStaleEvent(event); + + final RoutableData staleEvent = + new RoutableData<>(StaleEventDetectorOutput.STALE_SELF_EVENT, event); + return List.of(selfEvent, staleEvent); + } + + selfEvents.put(event.getDescriptor(), event); + return List.of(selfEvent); + } + + /** + * {@inheritDoc} + */ + @NonNull + @Override + public List> addConsensusRound( + @NonNull final ConsensusRound consensusRound) { + for (final EventImpl event : consensusRound.getConsensusEvents()) { + if (event.getCreatorId().equals(selfId)) { + selfEvents.remove(event.getBaseEvent().getDescriptor()); + } + } + + final List staleEvents = new ArrayList<>(); + currentEventWindow = consensusRound.getEventWindow(); + selfEvents.shiftWindow(currentEventWindow.getAncientThreshold(), (descriptor, event) -> staleEvents.add(event)); + + final List> output = new ArrayList<>(staleEvents.size()); + for (final GossipEvent event : staleEvents) { + handleStaleEvent(event); + output.add(new RoutableData<>(StaleEventDetectorOutput.STALE_SELF_EVENT, event)); + } + + return output; + } + + /** + * {@inheritDoc} + */ + @Override + public void setInitialEventWindow(@NonNull final EventWindow initialEventWindow) { + this.currentEventWindow = Objects.requireNonNull(initialEventWindow); + selfEvents.shiftWindow(currentEventWindow.getAncientThreshold()); + } + + /** + * {@inheritDoc} + */ + @Override + public void clear() { + selfEvents.clear(); + currentEventWindow = null; + } + + /** + * Handle a stale event. + * + * @param event the stale event + */ + private void handleStaleEvent(@NonNull final GossipEvent event) { + metrics.reportStaleEvent(event); + } +} diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/event/stale/DefaultTransactionResubmitter.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/event/stale/DefaultTransactionResubmitter.java new file mode 100644 index 000000000000..b19cb6cec959 --- /dev/null +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/event/stale/DefaultTransactionResubmitter.java @@ -0,0 +1,49 @@ +/* + * Copyright (C) 2024 Hedera Hashgraph, LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.swirlds.platform.event.stale; + +import com.swirlds.platform.event.GossipEvent; +import com.swirlds.platform.system.transaction.ConsensusTransactionImpl; +import edu.umd.cs.findbugs.annotations.NonNull; +import java.util.ArrayList; +import java.util.List; + +/** + * A default implementation of {@link TransactionResubmitter}. + */ +public class DefaultTransactionResubmitter implements TransactionResubmitter { + + /** + * Constructor. + */ + public DefaultTransactionResubmitter() {} + + /** + * {@inheritDoc} + */ + @Override + @NonNull + public List resubmitStaleTransactions(@NonNull final GossipEvent event) { + final List transactionsToResubmit = new ArrayList<>(); + for (final ConsensusTransactionImpl transaction : event.getHashedData().getTransactions()) { + if (transaction.isSystem()) { + transactionsToResubmit.add(transaction); + } + } + return transactionsToResubmit; + } +} diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/event/stale/StaleEventDetector.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/event/stale/StaleEventDetector.java new file mode 100644 index 000000000000..6b69ccd8ffe2 --- /dev/null +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/event/stale/StaleEventDetector.java @@ -0,0 +1,73 @@ +/* + * Copyright (C) 2024 Hedera Hashgraph, LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.swirlds.platform.event.stale; + +import com.swirlds.common.wiring.component.InputWireLabel; +import com.swirlds.common.wiring.transformers.RoutableData; +import com.swirlds.platform.consensus.EventWindow; +import com.swirlds.platform.event.GossipEvent; +import com.swirlds.platform.internal.ConsensusRound; +import edu.umd.cs.findbugs.annotations.NonNull; +import java.util.List; + +/** + * Detects when a self event becomes stale. This utility does not pay attention to events created by other nodes. This + * utility may not observe a self event go stale if the node needs to reconnect or restart. + */ +public interface StaleEventDetector { + + /** + * Add a newly created self event to the detector. If this event goes stale without this node restarting or + * reconnecting, then the detector will observe the event go stale and take appropriate action. + *

    + * Events added to this method "pass through" and are always returned as part of the output (even if stale). + * + * @param event the self event to add + * @return a list of routable data, produces two output wires which can be split by a router. The first stream + * corresponds to the tag {@link StaleEventDetectorOutput#SELF_EVENT} and contains all self events added to this + * component. The second stream corresponds to the tag {@link StaleEventDetectorOutput#STALE_SELF_EVENT} and + * contains all self events that have gone stale. + */ + @InputWireLabel("self events") + @NonNull + List> addSelfEvent(@NonNull GossipEvent event); + + /** + * Add a round that has just reached consensus. + * + * @param consensusRound a round that has just reached consensus + * @return a list of routable data, produces two output wires which can be split by a router. The first stream + * corresponds to the tag {@link StaleEventDetectorOutput#SELF_EVENT} and contains all self events added to this + * component. The second stream corresponds to the tag {@link StaleEventDetectorOutput#STALE_SELF_EVENT} and + * contains all self events that have gone stale. + */ + @InputWireLabel("rounds") + @NonNull + List> addConsensusRound(@NonNull ConsensusRound consensusRound); + + /** + * Set the initial event window for the detector. Must be called after restart and reconnect. + * + * @param initialEventWindow the initial event window + */ + void setInitialEventWindow(@NonNull EventWindow initialEventWindow); + + /** + * Clear the internal state of the detector. + */ + void clear(); +} diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/metrics/StaleMetrics.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/event/stale/StaleEventDetectorMetrics.java similarity index 51% rename from platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/metrics/StaleMetrics.java rename to platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/event/stale/StaleEventDetectorMetrics.java index 734e62c71766..e3751ee6935f 100644 --- a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/metrics/StaleMetrics.java +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/event/stale/StaleEventDetectorMetrics.java @@ -14,77 +14,51 @@ * limitations under the License. */ -package com.swirlds.platform.metrics; +package com.swirlds.platform.event.stale; import static com.swirlds.metrics.api.Metrics.INTERNAL_CATEGORY; import com.swirlds.common.context.PlatformContext; -import com.swirlds.common.platform.NodeId; import com.swirlds.metrics.api.LongAccumulator; import com.swirlds.metrics.api.Metrics; -import com.swirlds.platform.internal.EventImpl; +import com.swirlds.platform.event.GossipEvent; +import com.swirlds.platform.system.transaction.ConsensusTransactionImpl; import edu.umd.cs.findbugs.annotations.NonNull; -import java.util.Objects; /** * Collection of metrics related to stale events and transactions */ -public class StaleMetrics { +public class StaleEventDetectorMetrics { private static final LongAccumulator.Config STALE_EVENTS_CONFIG = new LongAccumulator.Config( INTERNAL_CATEGORY, "staleEvents") .withAccumulator(Long::sum) - .withDescription("number of stale events"); - private final LongAccumulator staleEventCount; - - private static final LongAccumulator.Config STALE_SELF_EVENTS_CONFIG = new LongAccumulator.Config( - INTERNAL_CATEGORY, "staleSelfEvents") - .withAccumulator(Long::sum) .withDescription("number of stale self events"); - private final LongAccumulator staleSelfEventCount; + private final LongAccumulator staleEventCount; private static final LongAccumulator.Config STALE_APP_TRANSACTIONS_CONFIG = new LongAccumulator.Config( INTERNAL_CATEGORY, "staleAppTransactions") .withAccumulator(Long::sum) - .withDescription("number of application transactions in stale events"); - private final LongAccumulator staleAppTransactionCount; - - private static final LongAccumulator.Config STALE_SELF_APP_TRANSACTIONS_CONFIG = new LongAccumulator.Config( - INTERNAL_CATEGORY, "staleSelfAppTransactions") - .withAccumulator(Long::sum) .withDescription("number of application transactions in stale self events"); - private final LongAccumulator staleSelfAppTransactionCount; + private final LongAccumulator staleAppTransactionCount; private static final LongAccumulator.Config STALE_SYSTEM_TRANSACTIONS_CONFIG = new LongAccumulator.Config( INTERNAL_CATEGORY, "staleSystemTransactions") .withAccumulator(Long::sum) - .withDescription("number of system transactions in stale events"); - private final LongAccumulator staleSystemTransactionCount; - - private static final LongAccumulator.Config STALE_SELF_SYSTEM_TRANSACTIONS_CONFIG = new LongAccumulator.Config( - INTERNAL_CATEGORY, "staleSelfSystemTransactions") - .withAccumulator(Long::sum) .withDescription("number of system transactions in stale self events"); - private final LongAccumulator staleSelfSystemTransactionCount; - - private final NodeId selfId; + private final LongAccumulator staleSystemTransactionCount; /** * Constructor * * @param platformContext the platform context - * @param selfId the ID of the node */ - public StaleMetrics(@NonNull final PlatformContext platformContext, @NonNull final NodeId selfId) { + public StaleEventDetectorMetrics(@NonNull final PlatformContext platformContext) { final Metrics metrics = platformContext.getMetrics(); - this.selfId = Objects.requireNonNull(selfId); staleEventCount = metrics.getOrCreate(STALE_EVENTS_CONFIG); - staleSelfEventCount = metrics.getOrCreate(STALE_SELF_EVENTS_CONFIG); staleAppTransactionCount = metrics.getOrCreate(STALE_APP_TRANSACTIONS_CONFIG); - staleSelfAppTransactionCount = metrics.getOrCreate(STALE_SELF_APP_TRANSACTIONS_CONFIG); staleSystemTransactionCount = metrics.getOrCreate(STALE_SYSTEM_TRANSACTIONS_CONFIG); - staleSelfSystemTransactionCount = metrics.getOrCreate(STALE_SELF_SYSTEM_TRANSACTIONS_CONFIG); } /** @@ -92,18 +66,20 @@ public StaleMetrics(@NonNull final PlatformContext platformContext, @NonNull fin * * @param event the stale event */ - public void staleEvent(@NonNull final EventImpl event) { - final int applicationTransactionCount = event.getNumAppTransactions(); - final int systemTransactionCount = event.getNumTransactions() - applicationTransactionCount; + public void reportStaleEvent(@NonNull final GossipEvent event) { + int systemTransactions = 0; + int appTransactions = 0; + + for (final ConsensusTransactionImpl transaction : event.getHashedData().getTransactions()) { + if (transaction.isSystem()) { + systemTransactions++; + } else { + appTransactions++; + } + } staleEventCount.update(1); - staleAppTransactionCount.update(applicationTransactionCount); - staleSystemTransactionCount.update(systemTransactionCount); - - if (event.getCreatorId().equals(selfId)) { - staleSelfEventCount.update(1); - staleSelfAppTransactionCount.update(applicationTransactionCount); - staleSelfSystemTransactionCount.update(systemTransactionCount); - } + staleSystemTransactionCount.update(systemTransactions); + staleAppTransactionCount.update(appTransactions); } } diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/signed/SourceOfSignedState.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/event/stale/StaleEventDetectorOutput.java similarity index 54% rename from platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/signed/SourceOfSignedState.java rename to platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/event/stale/StaleEventDetectorOutput.java index 875172648b04..f77d405659bf 100644 --- a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/signed/SourceOfSignedState.java +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/event/stale/StaleEventDetectorOutput.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2016-2024 Hedera Hashgraph, LLC + * Copyright (C) 2024 Hedera Hashgraph, LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,22 +14,18 @@ * limitations under the License. */ -package com.swirlds.platform.state.signed; +package com.swirlds.platform.event.stale; /** - * Various ways that {@link com.swirlds.platform.state.signed.SignedState SignedState}s get created. + * Describes the different types of output that the stale event detector produces. */ -public enum SourceOfSignedState { +public enum StaleEventDetectorOutput { /** - * This signed state was read from the disk. + * A self event that was just created. Type is {@link com.swirlds.platform.event.GossipEvent}. */ - DISK, + SELF_EVENT, /** - * This signed state was obtained through a reconnect. + * A self event that has gone stale. Type is {@link com.swirlds.platform.event.GossipEvent}. */ - RECONNECT, - /** - * This signed state was the product of applying consensus transactions to the state (standard pathway). - */ - TRANSACTIONS + STALE_SELF_EVENT } diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/event/stale/TransactionResubmitter.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/event/stale/TransactionResubmitter.java new file mode 100644 index 000000000000..eb6afa54fafb --- /dev/null +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/event/stale/TransactionResubmitter.java @@ -0,0 +1,39 @@ +/* + * Copyright (C) 2024 Hedera Hashgraph, LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.swirlds.platform.event.stale; + +import com.swirlds.common.wiring.component.InputWireLabel; +import com.swirlds.platform.event.GossipEvent; +import com.swirlds.platform.system.transaction.ConsensusTransactionImpl; +import edu.umd.cs.findbugs.annotations.NonNull; +import java.util.List; + +/** + * A simple utility responsible for resubmitting stale transactions. + */ +public interface TransactionResubmitter { + + /** + * Resubmit transactions that have gone stale. + * + * @param event the event that has gone stale + * @return a list of transactions that should be resubmitted + */ + @InputWireLabel("stale events") + @NonNull + List resubmitStaleTransactions(@NonNull GossipEvent event); +} diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/event/validation/DefaultEventSignatureValidator.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/event/validation/DefaultEventSignatureValidator.java index 4223c04529d2..085089eb8e91 100644 --- a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/event/validation/DefaultEventSignatureValidator.java +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/event/validation/DefaultEventSignatureValidator.java @@ -21,7 +21,6 @@ import com.swirlds.common.context.PlatformContext; import com.swirlds.common.platform.NodeId; -import com.swirlds.common.utility.CommonUtils; import com.swirlds.common.utility.throttle.RateLimitedLogger; import com.swirlds.metrics.api.LongAccumulator; import com.swirlds.platform.consensus.EventWindow; @@ -197,16 +196,14 @@ private boolean isSignatureValid(@NonNull final GossipEvent event) { } final boolean isSignatureValid = signatureVerifier.verifySignature( - event.getHashedData().getHash().getValue(), - event.getUnhashedData().getSignature(), - publicKey); + event.getHashedData().getHash().getBytes(), event.getSignature(), publicKey); if (!isSignatureValid) { rateLimitedLogger.error( EXCEPTION.getMarker(), "Event failed signature check. Event: {}, Signature: {}, Hash: {}", event, - CommonUtils.hex(event.getUnhashedData().getSignature()), + event.getSignature().toHex(), event.getHashedData().getHash()); } diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/event/validation/DefaultInternalEventValidator.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/event/validation/DefaultInternalEventValidator.java index 33e0e8f9999a..986888befbc1 100644 --- a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/event/validation/DefaultInternalEventValidator.java +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/event/validation/DefaultInternalEventValidator.java @@ -171,7 +171,7 @@ private boolean areRequiredFieldsNonNull(@NonNull final GossipEvent event) { return false; } - if (event.getUnhashedData() == null) { + if (event.getSignature() == null) { // do not log the event itself, since toString would throw a NullPointerException nullUnhashedDataLogger.error(EXCEPTION.getMarker(), "Event has null unhashed data"); nullUnhashedDataAccumulator.update(1); diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/eventhandling/ConsensusRoundHandler.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/eventhandling/ConsensusRoundHandler.java index f20cec2a830e..1175f392906b 100644 --- a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/eventhandling/ConsensusRoundHandler.java +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/eventhandling/ConsensusRoundHandler.java @@ -101,7 +101,7 @@ public class ConsensusRoundHandler { /** * If true then write the legacy running event hash each round. */ - private boolean writeLegacyRunningEventHash; + private final boolean writeLegacyRunningEventHash; /** * If true then wait for application transactions to be prehandled before handling the consensus round. @@ -251,8 +251,6 @@ private void updateRunningEventHash(@NonNull final ConsensusRound round) throws final PlatformState platformState = swirldStateManager.getConsensusState().getPlatformState(); - platformState.setRunningEventHash(round.getRunningEventHash()); - if (writeLegacyRunningEventHash) { // Update the running hash object. If there are no events, the running hash does not change. // Future work: this is a redundant check, since empty rounds are currently ignored entirely. The check is diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/gossip/SyncGossip.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/gossip/SyncGossip.java index 8de8bfda1549..2dd0501649e0 100644 --- a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/gossip/SyncGossip.java +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/gossip/SyncGossip.java @@ -16,32 +16,38 @@ package com.swirlds.platform.gossip; -import static com.swirlds.platform.SwirldsPlatform.PLATFORM_THREAD_POOL_NAME; +import static com.swirlds.platform.consensus.ConsensusConstants.ROUND_UNDEFINED; -import com.swirlds.base.state.Lifecycle; -import com.swirlds.base.state.LifecyclePhase; import com.swirlds.base.state.Startable; import com.swirlds.common.context.PlatformContext; -import com.swirlds.common.crypto.Hash; import com.swirlds.common.merkle.synchronization.config.ReconnectConfig; -import com.swirlds.common.notification.NotificationEngine; import com.swirlds.common.platform.NodeId; +import com.swirlds.common.threading.framework.QueueThread; import com.swirlds.common.threading.framework.StoppableThread; +import com.swirlds.common.threading.framework.config.QueueThreadConfiguration; +import com.swirlds.common.threading.framework.config.QueueThreadMetricsConfiguration; import com.swirlds.common.threading.framework.config.StoppableThreadConfiguration; import com.swirlds.common.threading.manager.ThreadManager; import com.swirlds.common.threading.pool.CachedPoolParallelExecutor; import com.swirlds.common.threading.pool.ParallelExecutor; +import com.swirlds.common.wiring.model.WiringModel; +import com.swirlds.common.wiring.wires.input.BindableInputWire; +import com.swirlds.common.wiring.wires.output.StandardOutputWire; import com.swirlds.platform.Utilities; import com.swirlds.platform.config.BasicConfig; import com.swirlds.platform.config.StateConfig; import com.swirlds.platform.config.ThreadConfig; +import com.swirlds.platform.consensus.EventWindow; import com.swirlds.platform.crypto.KeysAndCerts; import com.swirlds.platform.event.GossipEvent; +import com.swirlds.platform.event.linking.GossipLinker; +import com.swirlds.platform.event.linking.InOrderLinker; import com.swirlds.platform.eventhandling.EventConfig; import com.swirlds.platform.gossip.shadowgraph.Shadowgraph; import com.swirlds.platform.gossip.shadowgraph.ShadowgraphSynchronizer; import com.swirlds.platform.gossip.sync.SyncManagerImpl; import com.swirlds.platform.gossip.sync.config.SyncConfig; +import com.swirlds.platform.internal.EventImpl; import com.swirlds.platform.metrics.ReconnectMetrics; import com.swirlds.platform.metrics.SyncMetrics; import com.swirlds.platform.network.Connection; @@ -52,13 +58,11 @@ import com.swirlds.platform.network.PeerInfo; import com.swirlds.platform.network.communication.NegotiationProtocols; import com.swirlds.platform.network.communication.ProtocolNegotiatorThread; -import com.swirlds.platform.network.communication.handshake.HashCompareHandshake; import com.swirlds.platform.network.communication.handshake.VersionCompareHandshake; import com.swirlds.platform.network.connectivity.ConnectionServer; import com.swirlds.platform.network.connectivity.InboundConnectionHandler; import com.swirlds.platform.network.connectivity.OutboundConnectionCreator; import com.swirlds.platform.network.connectivity.SocketFactory; -import com.swirlds.platform.network.protocol.EmergencyReconnectProtocolFactory; import com.swirlds.platform.network.protocol.HeartbeatProtocolFactory; import com.swirlds.platform.network.protocol.ProtocolFactory; import com.swirlds.platform.network.protocol.ProtocolRunnable; @@ -73,18 +77,17 @@ import com.swirlds.platform.reconnect.ReconnectLearnerFactory; import com.swirlds.platform.reconnect.ReconnectLearnerThrottle; import com.swirlds.platform.reconnect.ReconnectThrottle; -import com.swirlds.platform.recovery.EmergencyRecoveryManager; import com.swirlds.platform.state.SwirldStateManager; -import com.swirlds.platform.state.nexus.SignedStateNexus; import com.swirlds.platform.state.signed.ReservedSignedState; import com.swirlds.platform.state.signed.SignedState; import com.swirlds.platform.system.SoftwareVersion; import com.swirlds.platform.system.address.Address; import com.swirlds.platform.system.address.AddressBook; -import com.swirlds.platform.system.status.PlatformStatusNexus; +import com.swirlds.platform.system.status.PlatformStatus; import com.swirlds.platform.system.status.StatusActionSubmitter; +import com.swirlds.platform.wiring.NoInput; +import com.swirlds.platform.wiring.components.Gossip; import edu.umd.cs.findbugs.annotations.NonNull; -import edu.umd.cs.findbugs.annotations.Nullable; import java.time.Duration; import java.util.ArrayList; import java.util.List; @@ -98,15 +101,20 @@ /** * Boilerplate code for gossip. */ -public class SyncGossip implements ConnectionTracker, Lifecycle { - private LifecyclePhase lifecyclePhase = LifecyclePhase.NOT_STARTED; +public class SyncGossip implements ConnectionTracker, Gossip { + public static final String PLATFORM_THREAD_POOL_NAME = "platform-core"; + private boolean started = false; + + private final PlatformContext platformContext; private final ReconnectController reconnectController; private final AtomicBoolean gossipHalted = new AtomicBoolean(false); private final SyncPermitProvider syncPermitProvider; - protected final SyncConfig syncConfig; - protected final ShadowgraphSynchronizer syncShadowgraphSynchronizer; + private final SyncConfig syncConfig; + private final InOrderLinker inOrderLinker; + private final Shadowgraph shadowgraph; + private final ShadowgraphSynchronizer syncShadowgraphSynchronizer; /** * Keeps track of the number of events in the intake pipeline from each peer @@ -118,21 +126,28 @@ public class SyncGossip implements ConnectionTracker, Lifecycle { */ private final List syncProtocolThreads = new ArrayList<>(); - protected final PlatformContext platformContext; - protected final AddressBook addressBook; - protected final NodeId selfId; - protected final NetworkTopology topology; - protected final NetworkMetrics networkMetrics; - protected final ReconnectHelper reconnectHelper; - protected final StaticConnectionManagers connectionManagers; - protected final FallenBehindManagerImpl fallenBehindManager; - protected final SyncManagerImpl syncManager; - protected final ReconnectThrottle reconnectThrottle; - protected final ReconnectMetrics reconnectMetrics; + private final NetworkTopology topology; + private final NetworkMetrics networkMetrics; + private final ReconnectHelper reconnectHelper; + private final StaticConnectionManagers connectionManagers; + private final FallenBehindManagerImpl fallenBehindManager; + private final SyncManagerImpl syncManager; + private final ReconnectThrottle reconnectThrottle; + private final ReconnectMetrics reconnectMetrics; + protected final StatusActionSubmitter statusActionSubmitter; - protected final PlatformStatusNexus statusNexus; + protected final Supplier platformStatusSupplier; + + private final List thingsToStart = new ArrayList<>(); - protected final List thingsToStart = new ArrayList<>(); + private Consumer receivedEventHandler; + + /** + * The old style intake queue (if enabled), null if not enabled. + */ + private QueueThread oldStyleIntakeQueue; + + private final ThreadManager threadManager; /** * Builds the gossip engine, depending on which flavor is requested in the configuration. @@ -141,54 +156,45 @@ public class SyncGossip implements ConnectionTracker, Lifecycle { * @param random a source of randomness, does not need to be cryptographically secure * @param threadManager the thread manager * @param keysAndCerts private keys and public certificates - * @param notificationEngine used to send notifications to the app * @param addressBook the current address book * @param selfId this node's ID * @param appVersion the version of the app - * @param epochHash the epoch hash of the initial state - * @param shadowGraph contains non-expired events - * @param emergencyRecoveryManager handles emergency recovery - * @param receivedEventHandler handles events received from other nodes * @param intakeQueueSizeSupplier a supplier for the size of the event intake queue * @param swirldStateManager manages the mutable state * @param latestCompleteState holds the latest signed state that has enough signatures to be verifiable - * @param syncMetrics metrics for sync * @param statusActionSubmitter submits status actions - * @param statusNexus the platform status nexus + * @param platformStatusSupplier provides the current platform status * @param loadReconnectState a method that should be called when a state from reconnect is obtained * @param clearAllPipelinesForReconnect this method should be called to clear all pipelines prior to a reconnect * @param intakeEventCounter keeps track of the number of events in the intake pipeline from each peer - * @param emergencyStateSupplier returns the emergency state if available + * @param statusActionSubmitter for submitting updates to the platform status manager */ - protected SyncGossip( + public SyncGossip( @NonNull final PlatformContext platformContext, @NonNull final Random random, @NonNull final ThreadManager threadManager, @NonNull final KeysAndCerts keysAndCerts, - @NonNull final NotificationEngine notificationEngine, @NonNull final AddressBook addressBook, @NonNull final NodeId selfId, @NonNull final SoftwareVersion appVersion, - @Nullable final Hash epochHash, - @NonNull final Shadowgraph shadowGraph, - @NonNull final EmergencyRecoveryManager emergencyRecoveryManager, - @NonNull final Consumer receivedEventHandler, @NonNull final LongSupplier intakeQueueSizeSupplier, @NonNull final SwirldStateManager swirldStateManager, - @NonNull final SignedStateNexus latestCompleteState, - @NonNull final SyncMetrics syncMetrics, + @NonNull final Supplier latestCompleteState, @NonNull final StatusActionSubmitter statusActionSubmitter, - @NonNull final PlatformStatusNexus statusNexus, + @NonNull final Supplier platformStatusSupplier, @NonNull final Consumer loadReconnectState, @NonNull final Runnable clearAllPipelinesForReconnect, - @NonNull final IntakeEventCounter intakeEventCounter, - @NonNull final Supplier emergencyStateSupplier) { + @NonNull final IntakeEventCounter intakeEventCounter) { this.platformContext = Objects.requireNonNull(platformContext); - this.addressBook = Objects.requireNonNull(addressBook); - this.selfId = Objects.requireNonNull(selfId); + + this.threadManager = Objects.requireNonNull(threadManager); + + inOrderLinker = new GossipLinker(platformContext, intakeEventCounter); + shadowgraph = new Shadowgraph(platformContext, addressBook, intakeEventCounter); + this.statusActionSubmitter = Objects.requireNonNull(statusActionSubmitter); - this.statusNexus = Objects.requireNonNull(statusNexus); + this.platformStatusSupplier = Objects.requireNonNull(platformStatusSupplier); final ThreadConfig threadConfig = platformContext.getConfiguration().getConfigData(ThreadConfig.class); @@ -222,7 +228,13 @@ protected SyncGossip( .setWork(connectionServer) .build()); - fallenBehindManager = buildFallenBehindManager(); + fallenBehindManager = new FallenBehindManagerImpl( + addressBook, + selfId, + topology.getConnectionGraph(), + statusActionSubmitter, + () -> getReconnectController().start(), + platformContext.getConfiguration().getConfigData(ReconnectConfig.class)); syncManager = new SyncManagerImpl( platformContext, @@ -241,13 +253,27 @@ protected SyncGossip( reconnectMetrics = new ReconnectMetrics(platformContext.getMetrics(), addressBook); final StateConfig stateConfig = platformContext.getConfiguration().getConfigData(StateConfig.class); + + final LongSupplier getRoundSupplier = () -> { + try (final ReservedSignedState reservedState = latestCompleteState.get()) { + if (reservedState == null || reservedState.isNull()) { + return ROUND_UNDEFINED; + } + + return reservedState.get().getRound(); + } + }; + reconnectHelper = new ReconnectHelper( this::pause, clearAllPipelinesForReconnect::run, swirldStateManager::getConsensusState, - latestCompleteState::getRound, + getRoundSupplier, new ReconnectLearnerThrottle(platformContext.getTime(), selfId, reconnectConfig), - loadReconnectState, + state -> { + loadReconnectState.accept(state); + syncManager.resetFallenBehind(); + }, new ReconnectLearnerFactory( platformContext, threadManager, @@ -263,12 +289,13 @@ protected SyncGossip( final ParallelExecutor shadowgraphExecutor = new CachedPoolParallelExecutor(threadManager, "node-sync"); thingsToStart.add(shadowgraphExecutor); + final SyncMetrics syncMetrics = new SyncMetrics(platformContext.getMetrics()); syncShadowgraphSynchronizer = new ShadowgraphSynchronizer( platformContext, - shadowGraph, + shadowgraph, addressBook.getSize(), syncMetrics, - receivedEventHandler, + event -> receivedEventHandler.accept(event), syncManager, intakeEventCounter, shadowgraphExecutor); @@ -288,26 +315,15 @@ protected SyncGossip( syncPermitProvider = new SyncPermitProvider(permitCount, intakeEventCounter); - if (emergencyRecoveryManager.isEmergencyStateRequired()) { - // If we still need an emergency recovery state, we need it via emergency reconnect. - // Start the helper first so that it is ready to receive a connection to perform reconnect with when the - // protocol is initiated. - thingsToStart.addFirst(reconnectController::start); - } - buildSyncProtocolThreads( platformContext, threadManager, - notificationEngine, selfId, appVersion, - epochHash, - emergencyRecoveryManager, intakeQueueSizeSupplier, latestCompleteState, syncMetrics, - statusNexus, - emergencyStateSupplier, + platformStatusSupplier, hangingThreadDuration, protocolConfig, reconnectConfig, @@ -319,20 +335,17 @@ protected SyncGossip( private void buildSyncProtocolThreads( final PlatformContext platformContext, final ThreadManager threadManager, - final NotificationEngine notificationEngine, final NodeId selfId, final SoftwareVersion appVersion, - final Hash epochHash, - final EmergencyRecoveryManager emergencyRecoveryManager, final LongSupplier intakeQueueSizeSupplier, - final SignedStateNexus latestCompleteState, + final Supplier getLatestCompleteState, final SyncMetrics syncMetrics, - final PlatformStatusNexus statusNexus, - final Supplier emergencyStateSupplier, + final Supplier platformStatusSupplier, final Duration hangingThreadDuration, final ProtocolConfig protocolConfig, final ReconnectConfig reconnectConfig, final EventConfig eventConfig) { + final ProtocolFactory syncProtocolFactory = new SyncProtocolFactory( platformContext, syncShadowgraphSynchronizer, @@ -342,38 +355,26 @@ private void buildSyncProtocolThreads( () -> intakeQueueSizeSupplier.getAsLong() >= eventConfig.eventIntakeQueueThrottleSize(), Duration.ZERO, syncMetrics, - statusNexus); + platformStatusSupplier); + final ProtocolFactory reconnectProtocolFactory = new ReconnectProtocolFactory( platformContext, threadManager, reconnectThrottle, - () -> latestCompleteState.getState("SwirldsPlatform: ReconnectProtocol"), + getLatestCompleteState, reconnectConfig.asyncStreamTimeout(), reconnectMetrics, reconnectController, new DefaultSignedStateValidator(platformContext), fallenBehindManager, - statusNexus, - platformContext.getConfiguration()); - final ProtocolFactory emergencyReconnectProtocolFactory = new EmergencyReconnectProtocolFactory( - platformContext, - threadManager, - notificationEngine, - emergencyRecoveryManager, - reconnectThrottle, - emergencyStateSupplier, - reconnectConfig.asyncStreamTimeout(), - reconnectMetrics, - reconnectController, - statusActionSubmitter, + platformStatusSupplier, platformContext.getConfiguration()); + final ProtocolFactory heartbeatProtocolFactory = new HeartbeatProtocolFactory( Duration.ofMillis(syncConfig.syncProtocolHeartbeatPeriod()), networkMetrics, platformContext.getTime()); - final HashCompareHandshake hashCompareHandshake = - new HashCompareHandshake(epochHash, !protocolConfig.tolerateMismatchedEpochHash()); final VersionCompareHandshake versionCompareHandshake = new VersionCompareHandshake(appVersion, !protocolConfig.tolerateMismatchedVersion()); - final List handshakeProtocols = List.of(hashCompareHandshake, versionCompareHandshake); + final List handshakeProtocols = List.of(versionCompareHandshake); for (final NodeId otherId : topology.getNeighbors()) { syncProtocolThreads.add(new StoppableThreadConfiguration<>(threadManager) .setPriority(Thread.NORM_PRIORITY) @@ -388,30 +389,12 @@ private void buildSyncProtocolThreads( handshakeProtocols, new NegotiationProtocols(List.of( heartbeatProtocolFactory.build(otherId), - emergencyReconnectProtocolFactory.build(otherId), reconnectProtocolFactory.build(otherId), syncProtocolFactory.build(otherId))))) .build()); } } - /** - * Build the fallen behind manager. - */ - @NonNull - protected FallenBehindManagerImpl buildFallenBehindManager() { - return new FallenBehindManagerImpl( - addressBook, - selfId, - topology.getConnectionGraph(), - statusActionSubmitter, - // this fallen behind impl is different from that of - // SingleNodeSyncGossip which was a no-op. Same for the pause/resume impls - // which only logged (but they do more here) - () -> getReconnectController().start(), - platformContext.getConfiguration().getConfigData(ReconnectConfig.class)); - } - /** * Get the reconnect controller. This method is needed to break a circular dependency. */ @@ -420,31 +403,23 @@ private ReconnectController getReconnectController() { } /** - * {@inheritDoc} + * Start gossiping. */ - @NonNull - @Override - public LifecyclePhase getLifecyclePhase() { - return lifecyclePhase; - } - - /** - * {@inheritDoc} - */ - @Override - public void start() { - throwIfNotInPhase(LifecyclePhase.NOT_STARTED); - lifecyclePhase = LifecyclePhase.STARTED; + private void start() { + if (started) { + throw new IllegalStateException("Gossip already started"); + } + started = true; thingsToStart.forEach(Startable::start); } /** - * {@inheritDoc} + * Stop gossiping. */ - @Override - public void stop() { - throwIfNotInPhase(LifecyclePhase.STARTED); - lifecyclePhase = LifecyclePhase.STOPPED; + private void stop() { + if (!started) { + throw new IllegalStateException("Gossip not started"); + } syncManager.haltRequestedObserver("stopping gossip"); gossipHalted.set(true); // wait for all existing syncs to stop. no new ones will be started, since gossip has been halted, and @@ -455,20 +430,6 @@ public void stop() { } } - /** - * This method is called when the node has finished a reconnect - */ - public void resetFallenBehind() { - syncManager.resetFallenBehind(); - } - - /** - * Check if we have fallen behind. - */ - public boolean hasFallenBehind() { - return syncManager.hasFallenBehind(); - } - /** * {@inheritDoc} */ @@ -490,8 +451,10 @@ public void connectionClosed(final boolean outbound, @NonNull final Connection c /** * Stop gossiping until {@link #resume()} is called. If called when already paused then this has no effect. */ - protected void pause() { - throwIfNotInPhase(LifecyclePhase.STARTED); + private void pause() { + if (!started) { + throw new IllegalStateException("Gossip not started"); + } gossipHalted.set(true); syncPermitProvider.waitForAllSyncsToFinish(); } @@ -499,9 +462,87 @@ protected void pause() { /** * Resume gossiping. If called when already running then this has no effect. */ - protected void resume() { - throwIfNotInPhase(LifecyclePhase.STARTED); + private void resume() { + if (!started) { + throw new IllegalStateException("Gossip not started"); + } intakeEventCounter.reset(); gossipHalted.set(false); } + + /** + * Clear the internal state of the gossip engine. + */ + private void clear() { + inOrderLinker.clear(); + shadowgraph.clear(); + } + + /** + * {@inheritDoc} + */ + @Override + public void bind( + @NonNull final WiringModel model, + @NonNull final BindableInputWire eventInput, + @NonNull final BindableInputWire eventWindowInput, + @NonNull final StandardOutputWire eventOutput, + @NonNull final BindableInputWire startInput, + @NonNull final BindableInputWire stopInput, + @NonNull final BindableInputWire clearInput) { + + startInput.bindConsumer(ignored -> start()); + stopInput.bindConsumer(ignored -> stop()); + clearInput.bindConsumer(ignored -> clear()); + + eventInput.bindConsumer(event -> { + final EventImpl linkedEvent = inOrderLinker.linkEvent(event); + if (linkedEvent != null) { + shadowgraph.addEvent(linkedEvent); + } + }); + + eventWindowInput.bindConsumer(eventWindow -> { + inOrderLinker.setEventWindow(eventWindow); + shadowgraph.updateEventWindow(eventWindow); + }); + + final boolean useOldStyleIntakeQueue = platformContext + .getConfiguration() + .getConfigData(EventConfig.class) + .useOldStyleIntakeQueue(); + + if (useOldStyleIntakeQueue) { + oldStyleIntakeQueue = new QueueThreadConfiguration(threadManager) + .setCapacity(10_000) + .setThreadName("old_style_intake_queue") + .setComponent("platform") + .setHandler(eventOutput::forward) + .setMetricsConfiguration( + new QueueThreadMetricsConfiguration(platformContext.getMetrics()).enableMaxSizeMetric()) + .build(); + thingsToStart.add(oldStyleIntakeQueue); + + receivedEventHandler = event -> { + try { + oldStyleIntakeQueue.put(event); + } catch (final InterruptedException e) { + Thread.currentThread().interrupt(); + throw new RuntimeException("interrupted while attempting to enqueue event from gossip", e); + } + }; + + } else { + receivedEventHandler = eventOutput::forward; + } + } + + /** + * Get the size of the old style intake queue. + * + * @return the size of the old style intake queue + */ + public int getOldStyleIntakeQueueSize() { + return oldStyleIntakeQueue.size(); + } } diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/gossip/shadowgraph/Shadowgraph.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/gossip/shadowgraph/Shadowgraph.java index dcf717cc4c4c..09c0e5dbcdd7 100644 --- a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/gossip/shadowgraph/Shadowgraph.java +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/gossip/shadowgraph/Shadowgraph.java @@ -132,8 +132,6 @@ public Shadowgraph( this.metrics = new ShadowgraphMetrics(platformContext); this.numberOfNodes = addressBook.getSize(); this.intakeEventCounter = Objects.requireNonNull(intakeEventCounter); - eventWindow = EventWindow.getGenesisEventWindow(ancientMode); - oldestUnexpiredIndicator = ancientMode.getGenesisIndicator(); tips = new HashSet<>(); hashToShadowEvent = new HashMap<>(); indicatorToShadowEvent = new HashMap<>(); @@ -145,7 +143,7 @@ public Shadowgraph( * * @param eventWindow the starting event window */ - public synchronized void startWithEventWindow(@NonNull final EventWindow eventWindow) { + private void startWithEventWindow(@NonNull final EventWindow eventWindow) { this.eventWindow = eventWindow; oldestUnexpiredIndicator = eventWindow.getExpiredThreshold(); logger.info( @@ -158,7 +156,7 @@ public synchronized void startWithEventWindow(@NonNull final EventWindow eventWi * Reset the shadowgraph manager to its constructed state. */ public synchronized void clear() { - eventWindow = EventWindow.getGenesisEventWindow(ancientMode); + eventWindow = null; oldestUnexpiredIndicator = ancientMode.getGenesisIndicator(); disconnectShadowEvents(); tips.clear(); @@ -352,6 +350,11 @@ public synchronized Collection findByAncientIndicator( * @param eventWindow describes the current window of non-expired events */ public synchronized void updateEventWindow(@NonNull final EventWindow eventWindow) { + if (this.eventWindow == null) { + startWithEventWindow(eventWindow); + return; + } + final long expiredThreshold = eventWindow.getExpiredThreshold(); if (expiredThreshold < eventWindow.getExpiredThreshold()) { @@ -508,6 +511,10 @@ public synchronized List getTips() { * @throws ShadowgraphInsertionException if the event was unable to be added to the shadowgraph */ public synchronized boolean addEvent(@NonNull final EventImpl e) throws ShadowgraphInsertionException { + if (eventWindow == null) { + throw new IllegalStateException("Initial event window not set"); + } + Objects.requireNonNull(e); try { final InsertableStatus status = insertable(e); diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/gossip/shadowgraph/SyncLogging.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/gossip/shadowgraph/SyncLogging.java index 72e472dd196f..6fe051b1ee03 100644 --- a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/gossip/shadowgraph/SyncLogging.java +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/gossip/shadowgraph/SyncLogging.java @@ -17,7 +17,6 @@ package com.swirlds.platform.gossip.shadowgraph; import com.swirlds.common.crypto.Hash; -import com.swirlds.common.utility.CommonUtils; import java.util.Collection; import java.util.List; import java.util.stream.Collectors; @@ -46,9 +45,7 @@ public static String toShortHashes(List hashes) { if (hashes == null) { return "null"; } - return hashes.stream() - .map(h -> CommonUtils.hex(h.getValue(), BRIEF_HASH_LENGTH)) - .collect(Collectors.joining(",")); + return hashes.stream().map(h -> h.toHex(BRIEF_HASH_LENGTH)).collect(Collectors.joining(",")); } public static String toShortBooleans(List booleans) { diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/gossip/sync/protocol/SyncProtocol.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/gossip/sync/protocol/SyncProtocol.java index 35a9002e39b2..2f29e55bd0a8 100644 --- a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/gossip/sync/protocol/SyncProtocol.java +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/gossip/sync/protocol/SyncProtocol.java @@ -31,13 +31,14 @@ import com.swirlds.platform.network.Connection; import com.swirlds.platform.network.NetworkProtocolException; import com.swirlds.platform.network.protocol.Protocol; -import com.swirlds.platform.system.status.PlatformStatusNexus; +import com.swirlds.platform.system.status.PlatformStatus; import edu.umd.cs.findbugs.annotations.NonNull; import java.io.IOException; import java.time.Duration; import java.time.Instant; import java.util.Objects; import java.util.function.BooleanSupplier; +import java.util.function.Supplier; /** * Executes the sync protocol where events are exchanged with a peer and all events are sent and received in topological @@ -93,21 +94,21 @@ public class SyncProtocol implements Protocol { private final PlatformContext platformContext; - private final PlatformStatusNexus statusNexus; + private final Supplier platformStatusSupplier; /** * Constructs a new sync protocol * - * @param platformContext the platform context - * @param peerId the id of the peer being synced with in this protocol - * @param synchronizer the shadow graph synchronizer, responsible for actually doing the sync - * @param fallenBehindManager manager to determine whether this node has fallen behind - * @param permitProvider provides permits to sync - * @param gossipHalted returns true if gossip is halted, false otherwise - * @param intakeIsTooFull returns true if the intake queue is too full to continue syncing, false otherwise - * @param sleepAfterSync the amount of time to sleep after a sync - * @param syncMetrics metrics tracking syncing - * @param statusNexus provides the current platform status + * @param platformContext the platform context + * @param peerId the id of the peer being synced with in this protocol + * @param synchronizer the shadow graph synchronizer, responsible for actually doing the sync + * @param fallenBehindManager manager to determine whether this node has fallen behind + * @param permitProvider provides permits to sync + * @param gossipHalted returns true if gossip is halted, false otherwise + * @param intakeIsTooFull returns true if the intake queue is too full to continue syncing, false otherwise + * @param sleepAfterSync the amount of time to sleep after a sync + * @param syncMetrics metrics tracking syncing + * @param platformStatusSupplier provides the current platform status */ public SyncProtocol( @NonNull final PlatformContext platformContext, @@ -119,7 +120,7 @@ public SyncProtocol( @NonNull final BooleanSupplier intakeIsTooFull, @NonNull final Duration sleepAfterSync, @NonNull final SyncMetrics syncMetrics, - @NonNull final PlatformStatusNexus statusNexus) { + @NonNull final Supplier platformStatusSupplier) { this.platformContext = Objects.requireNonNull(platformContext); this.peerId = Objects.requireNonNull(peerId); @@ -130,7 +131,7 @@ public SyncProtocol( this.intakeIsTooFull = Objects.requireNonNull(intakeIsTooFull); this.sleepAfterSync = Objects.requireNonNull(sleepAfterSync); this.syncMetrics = Objects.requireNonNull(syncMetrics); - this.statusNexus = Objects.requireNonNull(statusNexus); + this.platformStatusSupplier = Objects.requireNonNull(platformStatusSupplier); } /** @@ -149,7 +150,7 @@ private boolean syncCooldownComplete() { * @return true if the node should sync, false otherwise */ private boolean shouldSync() { - if (!SyncStatusChecker.doesStatusPermitSync(statusNexus.getCurrentStatus())) { + if (!SyncStatusChecker.doesStatusPermitSync(platformStatusSupplier.get())) { syncMetrics.doNotSyncPlatformStatus(); return false; } diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/gui/GuiEventStorage.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/gui/GuiEventStorage.java index 38be1dede790..3002013be6ac 100644 --- a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/gui/GuiEventStorage.java +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/gui/GuiEventStorage.java @@ -19,11 +19,7 @@ import static com.swirlds.platform.event.AncientMode.GENERATION_THRESHOLD; import static com.swirlds.platform.system.events.EventConstants.FIRST_GENERATION; -import com.swirlds.base.time.Time; -import com.swirlds.common.context.DefaultPlatformContext; import com.swirlds.common.context.PlatformContext; -import com.swirlds.common.crypto.CryptographyHolder; -import com.swirlds.common.metrics.noop.NoOpMetrics; import com.swirlds.config.api.Configuration; import com.swirlds.platform.Consensus; import com.swirlds.platform.ConsensusImpl; @@ -61,8 +57,7 @@ public class GuiEventStorage { public GuiEventStorage(@NonNull final Configuration configuration, @NonNull final AddressBook addressBook) { this.configuration = Objects.requireNonNull(configuration); - final PlatformContext platformContext = new DefaultPlatformContext( - configuration, new NoOpMetrics(), CryptographyHolder.get(), Time.getCurrent()); + final PlatformContext platformContext = PlatformContext.create(configuration); this.consensus = new ConsensusImpl(platformContext, new NoOpConsensusMetrics(), addressBook); // Future work: birth round compatibility for GUI diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/gui/internal/WinTabAddresses.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/gui/internal/WinTabAddresses.java index 4a998fa06168..45e626a1729a 100644 --- a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/gui/internal/WinTabAddresses.java +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/gui/internal/WinTabAddresses.java @@ -51,8 +51,8 @@ public void prePaint() { redoWindow = false; String s = ""; synchronized (getPlatforms()) { - for (Platform p : getPlatforms()) { - final Address address = p.getSelfAddress(); + for (final Platform p : getPlatforms()) { + final Address address = p.getAddressBook().getAddress(p.getSelfId()); s += "\n" + address.getNodeId().id() + " " + address.getNickname() + " " + address.getSelfName() + " " + address.getHostnameInternal() diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/internal/EventImpl.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/internal/EventImpl.java index c58e4f63da3c..9af2b5bf9d8a 100644 --- a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/internal/EventImpl.java +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/internal/EventImpl.java @@ -21,7 +21,7 @@ import com.swirlds.common.crypto.RunningHash; import com.swirlds.common.crypto.RunningHashable; import com.swirlds.common.crypto.SerializableHashable; -import com.swirlds.common.io.OptionalSelfSerializable; +import com.swirlds.common.io.SelfSerializable; import com.swirlds.common.io.streams.SerializableDataInputStream; import com.swirlds.common.io.streams.SerializableDataOutputStream; import com.swirlds.common.platform.NodeId; @@ -31,11 +31,9 @@ import com.swirlds.platform.event.GossipEvent; import com.swirlds.platform.system.SoftwareVersion; import com.swirlds.platform.system.events.BaseEventHashedData; -import com.swirlds.platform.system.events.BaseEventUnhashedData; import com.swirlds.platform.system.events.ConsensusData; import com.swirlds.platform.system.events.ConsensusEvent; import com.swirlds.platform.system.events.DetailedConsensusEvent; -import com.swirlds.platform.system.events.EventSerializationOptions; import com.swirlds.platform.system.transaction.ConsensusTransaction; import com.swirlds.platform.system.transaction.ConsensusTransactionImpl; import com.swirlds.platform.system.transaction.Transaction; @@ -59,7 +57,7 @@ public class EventImpl extends EventMetadata implements Comparable, ConsensusEvent, SerializableHashable, - OptionalSelfSerializable, + SelfSerializable, RunningHashable, StreamAligned, Timestamped { @@ -95,36 +93,12 @@ public class EventImpl extends EventMetadata public EventImpl() {} - public EventImpl(final BaseEventHashedData baseEventHashedData, final BaseEventUnhashedData baseEventUnhashedData) { - this(baseEventHashedData, baseEventUnhashedData, new ConsensusData(), null, null); - } - - public EventImpl( - final BaseEventHashedData baseEventHashedData, - final BaseEventUnhashedData baseEventUnhashedData, - final ConsensusData consensusData) { - this(baseEventHashedData, baseEventUnhashedData, consensusData, null, null); - } - - public EventImpl( - final BaseEventHashedData baseEventHashedData, - final BaseEventUnhashedData baseEventUnhashedData, - final EventImpl selfParent, - final EventImpl otherParent) { - this(baseEventHashedData, baseEventUnhashedData, new ConsensusData(), selfParent, otherParent); - } - public EventImpl(final GossipEvent gossipEvent, final EventImpl selfParent, final EventImpl otherParent) { this(gossipEvent, new ConsensusData(), selfParent, otherParent); } - public EventImpl( - final BaseEventHashedData baseEventHashedData, - final BaseEventUnhashedData baseEventUnhashedData, - final ConsensusData consensusData, - final EventImpl selfParent, - final EventImpl otherParent) { - this(new GossipEvent(baseEventHashedData, baseEventUnhashedData), consensusData, selfParent, otherParent); + public EventImpl(@NonNull final GossipEvent gossipEvent) { + this(gossipEvent, new ConsensusData(), null, null); } public EventImpl( @@ -135,7 +109,7 @@ public EventImpl( super(selfParent, otherParent); Objects.requireNonNull(baseEvent, "baseEvent"); Objects.requireNonNull(baseEvent.getHashedData(), "baseEventDataHashed"); - Objects.requireNonNull(baseEvent.getUnhashedData(), "baseEventDataNotHashed"); + Objects.requireNonNull(baseEvent.getSignature(), "signature"); Objects.requireNonNull(consensusData, "consensusData"); this.baseEvent = baseEvent; @@ -146,6 +120,14 @@ public EventImpl( findSystemTransactions(); } + /** + * Create an instance based on the given {@link DetailedConsensusEvent} + * @param detailedConsensusEvent the detailed consensus event to build from + */ + public EventImpl(final DetailedConsensusEvent detailedConsensusEvent) { + buildFromConsensusEvent(detailedConsensusEvent); + } + /** * initialize RunningHash instance */ @@ -235,17 +217,7 @@ public synchronized int compareTo(final EventImpl other) { */ @Override public void serialize(final SerializableDataOutputStream out) throws IOException { - serialize(out, EventSerializationOptions.FULL); - } - - /** - * {@inheritDoc} - */ - @Override - public void serialize(final SerializableDataOutputStream out, final EventSerializationOptions option) - throws IOException { - DetailedConsensusEvent.serialize( - out, baseEvent.getHashedData(), baseEvent.getUnhashedData(), consensusData, option); + DetailedConsensusEvent.serialize(out, baseEvent, consensusData); } /** @@ -264,7 +236,7 @@ public void deserialize(final SerializableDataInputStream in, final int version) * @param consensusEvent the consensus event to build from */ void buildFromConsensusEvent(final DetailedConsensusEvent consensusEvent) { - baseEvent = new GossipEvent(consensusEvent.getBaseEventHashedData(), consensusEvent.getBaseEventUnhashedData()); + baseEvent = consensusEvent.getGossipEvent(); consensusData = consensusEvent.getConsensusData(); // clears metadata in case there is any super.clear(); @@ -375,13 +347,6 @@ public BaseEventHashedData getHashedData() { return baseEvent.getHashedData(); } - /** - * @return The part of a base event which is not hashed - */ - public BaseEventUnhashedData getUnhashedData() { - return baseEvent.getUnhashedData(); - } - /** * @return Consensus data calculated for an event */ @@ -436,14 +401,6 @@ public boolean isCreatedBy(final NodeId id) { return Objects.equals(getCreatorId(), id); } - ////////////////////////////////////////// - // BaseEventUnhashedData - ////////////////////////////////////////// - - public byte[] getSignature() { - return baseEvent.getUnhashedData().getSignature(); - } - ////////////////////////////////////////// // ConsensusData ////////////////////////////////////////// diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/network/protocol/ReconnectProtocolFactory.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/network/protocol/ReconnectProtocolFactory.java index 7cb479ad03f4..90dde3a2269d 100644 --- a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/network/protocol/ReconnectProtocolFactory.java +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/network/protocol/ReconnectProtocolFactory.java @@ -28,7 +28,7 @@ import com.swirlds.platform.reconnect.ReconnectThrottle; import com.swirlds.platform.state.signed.ReservedSignedState; import com.swirlds.platform.state.signed.SignedStateValidator; -import com.swirlds.platform.system.status.PlatformStatusNexus; +import com.swirlds.platform.system.status.PlatformStatus; import edu.umd.cs.findbugs.annotations.NonNull; import java.time.Duration; import java.util.Objects; @@ -51,7 +51,7 @@ public class ReconnectProtocolFactory implements ProtocolFactory { /** * Provides the platform status. */ - private final PlatformStatusNexus statusNexus; + private final Supplier platformStatusSupplier; private final Configuration configuration; @@ -68,7 +68,7 @@ public ReconnectProtocolFactory( @NonNull final ReconnectController reconnectController, @NonNull final SignedStateValidator validator, @NonNull final FallenBehindManager fallenBehindManager, - @NonNull final PlatformStatusNexus statusNexus, + final Supplier platformStatusSupplier, @NonNull final Configuration configuration) { this.platformContext = Objects.requireNonNull(platformContext); @@ -80,7 +80,7 @@ public ReconnectProtocolFactory( this.reconnectController = Objects.requireNonNull(reconnectController); this.validator = Objects.requireNonNull(validator); this.fallenBehindManager = Objects.requireNonNull(fallenBehindManager); - this.statusNexus = Objects.requireNonNull(statusNexus); + this.platformStatusSupplier = Objects.requireNonNull(platformStatusSupplier); this.configuration = Objects.requireNonNull(configuration); this.time = Objects.requireNonNull(platformContext.getTime()); } @@ -102,7 +102,7 @@ public ReconnectProtocol build(@NonNull final NodeId peerId) { reconnectController, validator, fallenBehindManager, - statusNexus, + platformStatusSupplier, configuration, time); } diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/network/protocol/SyncProtocolFactory.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/network/protocol/SyncProtocolFactory.java index 58b9ddc8e5f6..2478d4edf1bf 100644 --- a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/network/protocol/SyncProtocolFactory.java +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/network/protocol/SyncProtocolFactory.java @@ -23,11 +23,12 @@ import com.swirlds.platform.gossip.shadowgraph.ShadowgraphSynchronizer; import com.swirlds.platform.gossip.sync.protocol.SyncProtocol; import com.swirlds.platform.metrics.SyncMetrics; -import com.swirlds.platform.system.status.PlatformStatusNexus; +import com.swirlds.platform.system.status.PlatformStatus; import edu.umd.cs.findbugs.annotations.NonNull; import java.time.Duration; import java.util.Objects; import java.util.function.BooleanSupplier; +import java.util.function.Supplier; /** * Implementation of a factory for sync protocol @@ -42,20 +43,20 @@ public class SyncProtocolFactory implements ProtocolFactory { private final BooleanSupplier intakeIsTooFull; private final Duration sleepAfterSync; private final SyncMetrics syncMetrics; - private final PlatformStatusNexus statusNexus; + private final Supplier platformStatusSupplier; /** * Constructs a new sync protocol * - * @param platformContext the platform context - * @param synchronizer the shadow graph synchronizer, responsible for actually doing the sync - * @param fallenBehindManager manager to determine whether this node has fallen behind - * @param permitProvider provides permits to sync - * @param gossipHalted returns true if gossip is halted, false otherwise - * @param intakeIsTooFull returns true if the intake queue is too full to continue syncing, false otherwise - * @param sleepAfterSync the amount of time to sleep after a sync - * @param syncMetrics metrics tracking syncing - * @param statusNexus provides the current platform status + * @param platformContext the platform context + * @param synchronizer the shadow graph synchronizer, responsible for actually doing the sync + * @param fallenBehindManager manager to determine whether this node has fallen behind + * @param permitProvider provides permits to sync + * @param gossipHalted returns true if gossip is halted, false otherwise + * @param intakeIsTooFull returns true if the intake queue is too full to continue syncing, false otherwise + * @param sleepAfterSync the amount of time to sleep after a sync + * @param syncMetrics metrics tracking syncing + * @param platformStatusSupplier provides the current platform status */ public SyncProtocolFactory( @NonNull final PlatformContext platformContext, @@ -66,7 +67,7 @@ public SyncProtocolFactory( @NonNull final BooleanSupplier intakeIsTooFull, @NonNull final Duration sleepAfterSync, @NonNull final SyncMetrics syncMetrics, - @NonNull final PlatformStatusNexus statusNexus) { + @NonNull final Supplier platformStatusSupplier) { this.platformContext = Objects.requireNonNull(platformContext); this.synchronizer = Objects.requireNonNull(synchronizer); @@ -76,7 +77,7 @@ public SyncProtocolFactory( this.intakeIsTooFull = Objects.requireNonNull(intakeIsTooFull); this.sleepAfterSync = Objects.requireNonNull(sleepAfterSync); this.syncMetrics = Objects.requireNonNull(syncMetrics); - this.statusNexus = Objects.requireNonNull(statusNexus); + this.platformStatusSupplier = Objects.requireNonNull(platformStatusSupplier); } /** @@ -95,6 +96,6 @@ public SyncProtocol build(@NonNull final NodeId peerId) { intakeIsTooFull, sleepAfterSync, syncMetrics, - statusNexus); + platformStatusSupplier); } } diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/pool/DefaultTransactionPool.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/pool/DefaultTransactionPool.java new file mode 100644 index 000000000000..ae6192354d1d --- /dev/null +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/pool/DefaultTransactionPool.java @@ -0,0 +1,55 @@ +/* + * Copyright (C) 2024 Hedera Hashgraph, LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.swirlds.platform.pool; + +import com.swirlds.platform.system.transaction.ConsensusTransactionImpl; +import edu.umd.cs.findbugs.annotations.NonNull; +import java.util.Objects; + +/** + * A default implementation of a {@link TransactionPool}. + */ +public class DefaultTransactionPool implements TransactionPool { + + private final TransactionPoolNexus transactionPoolNexus; + + /** + * Constructor. + * + * @param transactionPoolNexus the transaction pool nexus + */ + public DefaultTransactionPool(@NonNull final TransactionPoolNexus transactionPoolNexus) { + this.transactionPoolNexus = Objects.requireNonNull(transactionPoolNexus); + } + + /** + * {@inheritDoc} + */ + @Override + public void submitSystemTransaction(@NonNull final ConsensusTransactionImpl transaction) { + Objects.requireNonNull(transaction); + transactionPoolNexus.submitTransaction(transaction, true); + } + + /** + * {@inheritDoc} + */ + @Override + public void clear() { + transactionPoolNexus.clear(); + } +} diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/pool/TransactionPool.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/pool/TransactionPool.java new file mode 100644 index 000000000000..f66a6bfd50ba --- /dev/null +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/pool/TransactionPool.java @@ -0,0 +1,41 @@ +/* + * Copyright (C) 2024 Hedera Hashgraph, LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.swirlds.platform.pool; + +import com.swirlds.common.wiring.component.InputWireLabel; +import com.swirlds.platform.system.transaction.ConsensusTransactionImpl; +import edu.umd.cs.findbugs.annotations.NonNull; + +/** + * Coordinates and manages a pool of transactions waiting to be submitted. + */ +public interface TransactionPool { + + /** + * Submit a system transaction to the transaction pool. Transaction will be included in a future event, if + * possible. + * + * @param transaction the system transaction to submit + */ + @InputWireLabel("submit transaction") + void submitSystemTransaction(@NonNull ConsensusTransactionImpl transaction); + + /** + * Clear the transaction pool. + */ + void clear(); +} diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/eventhandling/TransactionPool.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/pool/TransactionPoolNexus.java similarity index 91% rename from platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/eventhandling/TransactionPool.java rename to platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/pool/TransactionPoolNexus.java index fd2245e3ec10..91475fddc30c 100644 --- a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/eventhandling/TransactionPool.java +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/pool/TransactionPoolNexus.java @@ -14,13 +14,12 @@ * limitations under the License. */ -package com.swirlds.platform.eventhandling; +package com.swirlds.platform.pool; -import com.hedera.hapi.platform.event.StateSignaturePayload; import com.swirlds.common.context.PlatformContext; -import com.swirlds.common.utility.Clearable; import com.swirlds.platform.components.transaction.TransactionSupplier; import com.swirlds.platform.config.TransactionConfig; +import com.swirlds.platform.eventhandling.TransactionPoolMetrics; import com.swirlds.platform.system.transaction.ConsensusTransaction; import com.swirlds.platform.system.transaction.ConsensusTransactionImpl; import com.swirlds.platform.system.transaction.StateSignatureTransaction; @@ -35,7 +34,7 @@ * Store a list of transactions created by self, both system and non-system, for wrapping in the next event to be * created. */ -public class TransactionPool implements TransactionSupplier, Clearable { +public class TransactionPoolNexus implements TransactionSupplier { /** * A list of transactions created by this node waiting to be put into a self-event. @@ -74,7 +73,7 @@ public class TransactionPool implements TransactionSupplier, Clearable { * * @param platformContext the platform context */ - public TransactionPool(@NonNull final PlatformContext platformContext) { + public TransactionPoolNexus(@NonNull final PlatformContext platformContext) { Objects.requireNonNull(platformContext); final TransactionConfig transactionConfig = @@ -207,15 +206,6 @@ public synchronized boolean submitTransaction( return true; } - /** - * This method takes a StateSignaturePayload protobuf record as an argument and submits it to the pool. - * Same as {@link #submitTransaction(ConsensusTransactionImpl, boolean)} but with priority set to true. - * This method has no return since system transactions are never rejected. - */ - public synchronized void submitPayload(@NonNull final StateSignaturePayload transaction) { - submitTransaction(new StateSignatureTransaction(transaction), true); - } - /** * get the number of buffered transactions * @@ -237,8 +227,7 @@ private synchronized int getPriorityBufferedTransactionCount() { /** * Clear all the transactions */ - @Override - public synchronized void clear() { + synchronized void clear() { bufferedTransactions.clear(); priorityBufferedTransactions.clear(); bufferedSignatureTransactionCount = 0; diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/proof/StateProof.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/proof/StateProof.java index 2cc678939270..d8ce36293b24 100644 --- a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/proof/StateProof.java +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/proof/StateProof.java @@ -16,6 +16,7 @@ package com.swirlds.platform.proof; +import com.hedera.pbj.runtime.io.buffer.Bytes; import com.swirlds.common.crypto.Cryptography; import com.swirlds.common.crypto.Signature; import com.swirlds.common.io.SelfSerializable; @@ -110,7 +111,7 @@ public boolean isValid( addressBook, threshold, (signature, bytes, publicKey) -> - CryptoStatic.verifySignature(bytes, signature.getSignatureBytes(), publicKey)); + CryptoStatic.verifySignature(Bytes.wrap(bytes), signature.getBytes(), publicKey)); } /** diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/proof/algorithms/StateProofTreeBuilder.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/proof/algorithms/StateProofTreeBuilder.java index b8f2f03b469b..910c0eb14daf 100644 --- a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/proof/algorithms/StateProofTreeBuilder.java +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/proof/algorithms/StateProofTreeBuilder.java @@ -216,7 +216,7 @@ private static StateProofNode buildStateProofInternalNode( if (child == null) { // If the child is null, append the null hash. - byteSegments.add(cryptography.getNullHash().getValue()); + byteSegments.add(cryptography.getNullHash().copyToByteArray()); } else if (nodeRoutes.contains(child.getRoute())) { // If the child is in the node list, then we add its state proof node. @@ -231,7 +231,7 @@ private static StateProofNode buildStateProofInternalNode( selfChildren.add(children.pop()); } else { // If the child is not in the node list, we append its hash. - byteSegments.add(child.getHash().getValue()); + byteSegments.add(child.getHash().copyToByteArray()); } } diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/proof/tree/StateProofPayload.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/proof/tree/StateProofPayload.java index c6eb7cca0f8c..9acab11a8d59 100644 --- a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/proof/tree/StateProofPayload.java +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/proof/tree/StateProofPayload.java @@ -80,7 +80,7 @@ public void computeHashableBytes(@NonNull final Cryptography cryptography, @NonN if (payload.getHash() == null) { cryptography.digestSync(payload); } - setHashableBytes(payload.getHash().getValue()); + setHashableBytes(payload.getHash().copyToByteArray()); } /** diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/publisher/DefaultPlatformPublisher.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/publisher/DefaultPlatformPublisher.java index 59212a3e750d..3c680f82c16d 100644 --- a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/publisher/DefaultPlatformPublisher.java +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/publisher/DefaultPlatformPublisher.java @@ -18,10 +18,10 @@ import static com.swirlds.logging.legacy.LogMarker.EXCEPTION; +import com.swirlds.platform.builder.ApplicationCallbacks; import com.swirlds.platform.consensus.ConsensusSnapshot; import com.swirlds.platform.event.GossipEvent; import edu.umd.cs.findbugs.annotations.NonNull; -import edu.umd.cs.findbugs.annotations.Nullable; import java.util.function.Consumer; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; @@ -40,20 +40,18 @@ public class DefaultPlatformPublisher implements PlatformPublisher { private final Consumer snapshotOverrideConsumer; private boolean snapshotOverrideConsumerErrorLogged = false; + private final Consumer staleEventConsumer; + private boolean staleEventConsumerErrorLogged = false; + /** * Constructor. * - * @param preconsensusEventConsumer the handler for preconsensus events, if null then it is expected that no - * preconsensus events will be sent to this publisher - * @param snapshotOverrideConsumer the handler for snapshot overrides, if null then it is expected that no snapshot - * overrides will be sent to this publisher + * @param applicationCallbacks the application callbacks */ - public DefaultPlatformPublisher( - @Nullable final Consumer preconsensusEventConsumer, - @Nullable final Consumer snapshotOverrideConsumer) { - - this.preconsensusEventConsumer = preconsensusEventConsumer; - this.snapshotOverrideConsumer = snapshotOverrideConsumer; + public DefaultPlatformPublisher(@NonNull final ApplicationCallbacks applicationCallbacks) { + this.preconsensusEventConsumer = applicationCallbacks.preconsensusEventConsumer(); + this.snapshotOverrideConsumer = applicationCallbacks.snapshotOverrideConsumer(); + this.staleEventConsumer = applicationCallbacks.staleEventConsumer(); } /** @@ -87,4 +85,20 @@ public void publishSnapshotOverride(@NonNull final ConsensusSnapshot snapshot) { } snapshotOverrideConsumer.accept(snapshot); } + + /** + * {@inheritDoc} + */ + @Override + public void publishStaleEvent(@NonNull final GossipEvent event) { + if (staleEventConsumer == null) { + if (!staleEventConsumerErrorLogged) { + // One log is sufficient to alert test validators, no need generate spam beyond the first log. + logger.error(EXCEPTION.getMarker(), "No stale event consumer is registered"); + staleEventConsumerErrorLogged = true; + } + return; + } + staleEventConsumer.accept(event); + } } diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/publisher/PlatformPublisher.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/publisher/PlatformPublisher.java index 16a295d3bd8b..b8debea83af0 100644 --- a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/publisher/PlatformPublisher.java +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/publisher/PlatformPublisher.java @@ -43,4 +43,11 @@ public interface PlatformPublisher { */ @InputWireLabel("ConsensusSnapshot") void publishSnapshotOverride(@NonNull final ConsensusSnapshot snapshot); + + /** + * Publish a stale event. + * + * @param event the event to publish + */ + void publishStaleEvent(@NonNull final GossipEvent event); } diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/reconnect/ReconnectProtocol.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/reconnect/ReconnectProtocol.java index 2efce6d47de3..1f21f3b57e4c 100644 --- a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/reconnect/ReconnectProtocol.java +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/reconnect/ReconnectProtocol.java @@ -34,7 +34,6 @@ import com.swirlds.platform.state.signed.ReservedSignedState; import com.swirlds.platform.state.signed.SignedStateValidator; import com.swirlds.platform.system.status.PlatformStatus; -import com.swirlds.platform.system.status.PlatformStatusNexus; import edu.umd.cs.findbugs.annotations.NonNull; import java.io.IOException; import java.time.Duration; @@ -64,7 +63,7 @@ public class ReconnectProtocol implements Protocol { /** * Provides the platform status. */ - private final PlatformStatusNexus statusNexus; + private final Supplier platformStatusSupplier; private final Configuration configuration; private ReservedSignedState teacherState; @@ -97,7 +96,7 @@ public class ReconnectProtocol implements Protocol { * @param reconnectMetrics tracks reconnect metrics * @param reconnectController controls reconnecting as a learner * @param fallenBehindManager maintains this node's behind status - * @param statusNexus provides the platform status + * @param platformStatusSupplier provides the platform status * @param configuration platform configuration * @param time the time object to use */ @@ -112,7 +111,7 @@ public ReconnectProtocol( @NonNull final ReconnectController reconnectController, @NonNull final SignedStateValidator validator, @NonNull final FallenBehindManager fallenBehindManager, - @NonNull final PlatformStatusNexus statusNexus, + @NonNull final Supplier platformStatusSupplier, @NonNull final Configuration configuration, @NonNull final Time time) { @@ -126,7 +125,7 @@ public ReconnectProtocol( this.reconnectController = Objects.requireNonNull(reconnectController); this.validator = Objects.requireNonNull(validator); this.fallenBehindManager = Objects.requireNonNull(fallenBehindManager); - this.statusNexus = Objects.requireNonNull(statusNexus); + this.platformStatusSupplier = Objects.requireNonNull(platformStatusSupplier); this.configuration = Objects.requireNonNull(configuration); Objects.requireNonNull(time); @@ -183,7 +182,7 @@ public boolean shouldAccept() { } // only teach if the platform is active - if (statusNexus.getCurrentStatus() != PlatformStatus.ACTIVE) { + if (platformStatusSupplier.get() != PlatformStatus.ACTIVE) { notActiveLogger.info( RECONNECT.getMarker(), "Rejecting reconnect request from node {} because this node isn't ACTIVE", diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/recovery/EventRecoveryWorkflow.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/recovery/EventRecoveryWorkflow.java index d03a157185dd..1591971e9797 100644 --- a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/recovery/EventRecoveryWorkflow.java +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/recovery/EventRecoveryWorkflow.java @@ -55,8 +55,8 @@ import com.swirlds.platform.state.State; import com.swirlds.platform.state.signed.ReservedSignedState; import com.swirlds.platform.state.signed.SignedState; -import com.swirlds.platform.state.signed.SignedStateFileReader; -import com.swirlds.platform.state.signed.SignedStateFileWriter; +import com.swirlds.platform.state.snapshot.SignedStateFileReader; +import com.swirlds.platform.state.snapshot.SignedStateFileWriter; import com.swirlds.platform.system.InitTrigger; import com.swirlds.platform.system.Round; import com.swirlds.platform.system.StaticSoftwareVersion; @@ -383,7 +383,6 @@ private static ReservedSignedState handleNextRound( platformState.setRound(round.getRoundNum()); platformState.setLegacyRunningEventHash(getHashEventsCons( previousState.get().getState().getPlatformState().getLegacyRunningEventHash(), round)); - platformState.setRunningEventHash(platformContext.getCryptography().getNullHash()); platformState.setConsensusTimestamp(currentRoundTimestamp); platformState.setSnapshot(SyntheticSnapshot.generateSyntheticSnapshot( round.getRoundNum(), diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/recovery/internal/EventStreamRoundIterator.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/recovery/internal/EventStreamRoundIterator.java index 319e43cd44b8..d60ce456b4cb 100644 --- a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/recovery/internal/EventStreamRoundIterator.java +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/recovery/internal/EventStreamRoundIterator.java @@ -87,8 +87,7 @@ public EventStreamRoundIterator( * @return an event impl with the same data as the detailed consensus event */ private static EventImpl convertToEventImpl(final DetailedConsensusEvent event) { - return new EventImpl( - event.getBaseEventHashedData(), event.getBaseEventUnhashedData(), event.getConsensusData()); + return new EventImpl(event); } /** diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/recovery/internal/MultiFileRunningHashIterator.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/recovery/internal/MultiFileRunningHashIterator.java new file mode 100644 index 000000000000..b9c39e16312e --- /dev/null +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/recovery/internal/MultiFileRunningHashIterator.java @@ -0,0 +1,83 @@ +/* + * Copyright (C) 2022-2024 Hedera Hashgraph, LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.swirlds.platform.recovery.internal; + +import com.swirlds.common.io.IOIterator; +import com.swirlds.common.stream.RunningHashCalculatorForStream; +import com.swirlds.platform.system.events.DetailedConsensusEvent; +import java.io.IOException; +import java.util.Objects; + +/** + * A wrapper around {@link EventStreamMultiFileIterator} + * that computes a running hash of the iterated events. + */ +public class MultiFileRunningHashIterator implements IOIterator { + private final EventStreamMultiFileIterator iterator; + private final RunningHashCalculatorForStream runningHashCalculator; + + /** + * @param iterator + * the iterator that reads event stream files + * @throws NullPointerException in case {@code iterator} parameter is {@code null} + */ + public MultiFileRunningHashIterator(final EventStreamMultiFileIterator iterator) { + this.iterator = Objects.requireNonNull(iterator, "iterator must not be null"); + this.runningHashCalculator = new RunningHashCalculatorForStream<>(); + + runningHashCalculator.setRunningHash(iterator.getStartHash()); + for (final DetailedConsensusEvent skippedEvent : iterator.getSkippedEvents()) { + runningHashCalculator.addObject(skippedEvent); + } + } + + @Override + public boolean hasNext() throws IOException { + return iterator.hasNext(); + } + + @Override + public DetailedConsensusEvent next() throws IOException { + final DetailedConsensusEvent next = iterator.next(); + runningHashCalculator.addObject(next); + return next; + } + + @Override + public DetailedConsensusEvent peek() throws IOException { + return iterator.peek(); + } + + @Override + public void close() { + iterator.close(); + } + + /** + * Get the number of bytes read from the disk. + */ + public long getBytesRead() { + return iterator.getBytesRead(); + } + + /** + * Get the number of files that are damaged. + */ + public long getDamagedFileCount() { + return iterator.getDamagedFileCount(); + } +} diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/recovery/internal/RecoveryPlatform.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/recovery/internal/RecoveryPlatform.java index 9e3bb1066875..d7fc22c322e0 100644 --- a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/recovery/internal/RecoveryPlatform.java +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/recovery/internal/RecoveryPlatform.java @@ -19,18 +19,13 @@ import static com.swirlds.common.threading.manager.AdHocThreadManager.getStaticThreadManager; import static com.swirlds.platform.crypto.CryptoStatic.initNodeSecurity; -import com.swirlds.base.time.Time; import com.swirlds.common.AutoCloseableNonThrowing; -import com.swirlds.common.context.DefaultPlatformContext; import com.swirlds.common.context.PlatformContext; -import com.swirlds.common.crypto.CryptographyHolder; import com.swirlds.common.crypto.Signature; -import com.swirlds.common.metrics.noop.NoOpMetrics; import com.swirlds.common.notification.NotificationEngine; import com.swirlds.common.platform.NodeId; import com.swirlds.common.utility.AutoCloseableWrapper; import com.swirlds.config.api.Configuration; -import com.swirlds.metrics.api.Metrics; import com.swirlds.platform.crypto.KeysAndCerts; import com.swirlds.platform.crypto.PlatformSigner; import com.swirlds.platform.state.signed.ReservedSignedState; @@ -84,11 +79,9 @@ public RecoveryPlatform( keysAndCerts = null; } - final Metrics metrics = new NoOpMetrics(); - notificationEngine = NotificationEngine.buildEngine(getStaticThreadManager()); - context = new DefaultPlatformContext(configuration, metrics, CryptographyHolder.get(), Time.getCurrent()); + context = PlatformContext.create(configuration); setLatestState(initialState); } @@ -118,6 +111,7 @@ public Signature sign(final byte[] data) { * {@inheritDoc} */ @Override + @NonNull public PlatformContext getContext() { return context; } @@ -126,6 +120,7 @@ public PlatformContext getContext() { * {@inheritDoc} */ @Override + @NonNull public NotificationEngine getNotificationEngine() { return notificationEngine; } @@ -134,6 +129,7 @@ public NotificationEngine getNotificationEngine() { * {@inheritDoc} */ @Override + @NonNull public AddressBook getAddressBook() { return addressBook; } @@ -142,6 +138,7 @@ public AddressBook getAddressBook() { * {@inheritDoc} */ @Override + @NonNull public NodeId getSelfId() { return selfId; } @@ -151,6 +148,7 @@ public NodeId getSelfId() { */ @SuppressWarnings("unchecked") @Override + @NonNull public synchronized AutoCloseableWrapper getLatestImmutableState( @NonNull final String reason) { final ReservedSignedState reservedSignedState = immutableState.getAndReserve(reason); diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/GenesisStateBuilder.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/GenesisStateBuilder.java index 9a253a5c3199..f9842fb543ff 100644 --- a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/GenesisStateBuilder.java +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/GenesisStateBuilder.java @@ -40,9 +40,7 @@ private GenesisStateBuilder() {} * @return a genesis platform state */ private static PlatformState buildGenesisPlatformState( - @NonNull final PlatformContext platformContext, - final AddressBook addressBook, - final SoftwareVersion appVersion) { + final AddressBook addressBook, final SoftwareVersion appVersion) { final PlatformState platformState = new PlatformState(); platformState.setAddressBook(addressBook.copy()); @@ -50,7 +48,6 @@ private static PlatformState buildGenesisPlatformState( platformState.setCreationSoftwareVersion(appVersion); platformState.setRound(0); platformState.setLegacyRunningEventHash(null); - platformState.setRunningEventHash(platformContext.getCryptography().getNullHash()); platformState.setEpochHash(null); platformState.setConsensusTimestamp(Instant.ofEpochSecond(0L)); @@ -74,7 +71,7 @@ public static ReservedSignedState buildGenesisState( final BasicConfig basicConfig = platformContext.getConfiguration().getConfigData(BasicConfig.class); final State state = new State(); - state.setPlatformState(buildGenesisPlatformState(platformContext, addressBook, appVersion)); + state.setPlatformState(buildGenesisPlatformState(addressBook, appVersion)); state.setSwirldState(swirldState); final long genesisFreezeTime = basicConfig.genesisFreezeTime(); diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/PlatformState.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/PlatformState.java index c262c6629dea..9f8cd5cc7bf2 100644 --- a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/PlatformState.java +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/PlatformState.java @@ -55,6 +55,10 @@ private static final class ClassVersion { * Added the new running event hash algorithm. */ public static final int RUNNING_EVENT_HASH = 3; + /** + * Removed the running event hash algorithm. + */ + public static final int REMOVED_EVENT_HASH = 4; } /** @@ -81,13 +85,6 @@ private static final class ClassVersion { */ private Hash legacyRunningEventHash; - /** - * The running event hash of all events that have been applied to this state since the beginning of time (or, for - * networks that were around prior to the introduction of this running hash, since the introduction of the running - * event hash). - */ - private Hash runningEventHash; - /** * the consensus timestamp for this signed state */ @@ -164,7 +161,6 @@ private PlatformState(final PlatformState that) { this.previousAddressBook = that.previousAddressBook == null ? null : that.previousAddressBook.copy(); this.round = that.round; this.legacyRunningEventHash = that.legacyRunningEventHash; - this.runningEventHash = that.runningEventHash; this.consensusTimestamp = that.consensusTimestamp; this.creationSoftwareVersion = that.creationSoftwareVersion; this.epochHash = that.epochHash; @@ -223,7 +219,6 @@ public void serialize(final SerializableDataOutputStream out) throws IOException out.writeSerializable(firstVersionInBirthRoundMode, true); out.writeLong(lastRoundBeforeBirthRoundMode); out.writeLong(lowestJudgeGenerationBeforeBirthRoundMode); - out.writeSerializable(runningEventHash, false); } /** @@ -248,8 +243,8 @@ public void deserialize(final SerializableDataInputStream in, final int version) lastRoundBeforeBirthRoundMode = in.readLong(); lowestJudgeGenerationBeforeBirthRoundMode = in.readLong(); } - if (version >= ClassVersion.RUNNING_EVENT_HASH) { - runningEventHash = in.readSerializable(false, Hash::new); + if (version == ClassVersion.RUNNING_EVENT_HASH) { + in.readSerializable(false, Hash::new); } } @@ -258,7 +253,7 @@ public void deserialize(final SerializableDataInputStream in, final int version) */ @Override public int getVersion() { - return ClassVersion.RUNNING_EVENT_HASH; + return ClassVersion.REMOVED_EVENT_HASH; } /** @@ -359,26 +354,6 @@ public void setLegacyRunningEventHash(@Nullable final Hash legacyRunningEventHas this.legacyRunningEventHash = legacyRunningEventHash; } - /** - * Get the running hash of all events that have been applied to this state since the beginning of time (or if - * this network has been around since before this feature was added, the running event hash since that time). - * - * @return a running hash of events - */ - @Nullable - public Hash getRunningEventHash() { - return runningEventHash; - } - - /** - * Set the running hash of all events that have been applied to this state since the beginning of time. - * - * @param runningEventHash a running hash of events - */ - public void setRunningEventHash(@NonNull final Hash runningEventHash) { - this.runningEventHash = Objects.requireNonNull(runningEventHash); - } - /** * Get the consensus timestamp for this state, defined as the timestamp of the first transaction that was applied in * the round that created the state. diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/State.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/State.java index 9a65a23b12ca..50e16edfe0f6 100644 --- a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/State.java +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/State.java @@ -237,12 +237,6 @@ public String getInfoString(final int hashDepth) { .addRow("Next consensus number:", snapshot == null ? "null" : snapshot.nextConsensusNumber()) .addRow("Legacy running event hash:", hashEventsCons) .addRow("Legacy running event mnemonic:", hashEventsCons == null ? "null" : hashEventsCons.toMnemonic()) - .addRow("Running Event Hash: ", platformState.getRunningEventHash()) - .addRow( - "Running Event Mnemonic: ", - platformState.getRunningEventHash() == null - ? "null" - : platformState.getRunningEventHash().toMnemonic()) .addRow("Rounds non-ancient:", platformState.getRoundsNonAncient()) .addRow("Creation version:", platformState.getCreationSoftwareVersion()) .addRow("Epoch mnemonic:", epochHash == null ? "null" : epochHash.toMnemonic()) diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/SwirldStateManager.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/SwirldStateManager.java index 05b5f32aae2b..e95a5e7e4abd 100644 --- a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/SwirldStateManager.java +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/SwirldStateManager.java @@ -24,7 +24,6 @@ import com.swirlds.platform.FreezePeriodChecker; import com.swirlds.platform.internal.ConsensusRound; import com.swirlds.platform.metrics.SwirldStateMetrics; -import com.swirlds.platform.state.signed.LoadableFromSignedState; import com.swirlds.platform.state.signed.SignedState; import com.swirlds.platform.system.Round; import com.swirlds.platform.system.SoftwareVersion; @@ -40,7 +39,7 @@ /** * Manages all interactions with the state object required by {@link SwirldState}. */ -public class SwirldStateManager implements FreezePeriodChecker, LoadableFromSignedState { +public class SwirldStateManager implements FreezePeriodChecker { /** * Stats relevant to SwirldState operations. @@ -73,37 +72,50 @@ public class SwirldStateManager implements FreezePeriodChecker, LoadableFromSign private final SoftwareVersion softwareVersion; /** - * Creates a new instance with the provided state. + * Constructor. * * @param platformContext the platform context * @param addressBook the address book * @param selfId this node's id - * @param swirldStateMetrics metrics related to SwirldState * @param statusActionSubmitter enables submitting platform status actions - * @param state the genesis state * @param softwareVersion the current software version */ public SwirldStateManager( @NonNull final PlatformContext platformContext, @NonNull final AddressBook addressBook, @NonNull final NodeId selfId, - @NonNull final SwirldStateMetrics swirldStateMetrics, @NonNull final StatusActionSubmitter statusActionSubmitter, - @NonNull final State state, @NonNull final SoftwareVersion softwareVersion) { Objects.requireNonNull(platformContext); Objects.requireNonNull(addressBook); Objects.requireNonNull(selfId); - this.stats = Objects.requireNonNull(swirldStateMetrics); + this.stats = new SwirldStateMetrics(platformContext.getMetrics()); Objects.requireNonNull(statusActionSubmitter); - Objects.requireNonNull(state); this.softwareVersion = Objects.requireNonNull(softwareVersion); this.transactionHandler = new TransactionHandler(selfId, stats); this.uptimeTracker = new UptimeTracker(platformContext, addressBook, statusActionSubmitter, selfId, Time.getCurrent()); - initialState(state); + } + + /** + * Set the initial state for the platform. This method should only be called once. + * + * @param state the initial state + */ + public void setInitialState(@NonNull final State state) { + Objects.requireNonNull(state); + state.throwIfDestroyed("state must not be destroyed"); + state.throwIfImmutable("state must be mutable"); + + if (stateRef.get() != null) { + throw new IllegalStateException("Attempt to set initial state when there is already a state reference."); + } + + // Create a fast copy so there is always an immutable state to + // invoke handleTransaction on for pre-consensus transactions + fastCopyAndUpdateRefs(state); } /** @@ -146,10 +158,11 @@ public void savedStateInFreezePeriod() { } /** - * {@inheritDoc} + * Loads all necessary data from the {@code reservedSignedState}. + * + * @param signedState the signed state to load */ - @Override - public void loadFromSignedState(final SignedState signedState) { + public void loadFromSignedState(@NonNull final SignedState signedState) { final State state = signedState.getState(); state.throwIfDestroyed("state must not be destroyed"); @@ -158,19 +171,6 @@ public void loadFromSignedState(final SignedState signedState) { fastCopyAndUpdateRefs(state); } - private void initialState(final State state) { - state.throwIfDestroyed("state must not be destroyed"); - state.throwIfImmutable("state must be mutable"); - - if (stateRef.get() != null) { - throw new IllegalStateException("Attempt to set initial state when there is already a state reference."); - } - - // Create a fast copy so there is always an immutable state to - // invoke handleTransaction on for pre-consensus transactions - fastCopyAndUpdateRefs(state); - } - private void fastCopyAndUpdateRefs(final State state) { final State consState = fastCopy(state, stats, softwareVersion); diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/editor/StateEditor.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/editor/StateEditor.java index 7e5407e91c87..88769d173db2 100644 --- a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/editor/StateEditor.java +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/editor/StateEditor.java @@ -19,27 +19,23 @@ import static com.swirlds.platform.state.editor.StateEditorUtils.formatNodeType; import static com.swirlds.platform.state.editor.StateEditorUtils.formatRoute; -import com.swirlds.base.time.Time; import com.swirlds.cli.utility.CommandBuilder; -import com.swirlds.common.context.DefaultPlatformContext; import com.swirlds.common.context.PlatformContext; -import com.swirlds.common.crypto.CryptographyHolder; import com.swirlds.common.merkle.MerkleInternal; import com.swirlds.common.merkle.MerkleNode; import com.swirlds.common.merkle.crypto.MerkleCryptoFactory; import com.swirlds.common.merkle.route.MerkleRoute; import com.swirlds.common.merkle.route.MerkleRouteFactory; import com.swirlds.common.merkle.route.MerkleRouteUtils; -import com.swirlds.common.metrics.noop.NoOpMetrics; import com.swirlds.config.api.Configuration; import com.swirlds.config.api.ConfigurationBuilder; import com.swirlds.platform.config.DefaultConfiguration; import com.swirlds.platform.crypto.CryptoStatic; -import com.swirlds.platform.state.signed.DeserializedSignedState; import com.swirlds.platform.state.signed.ReservedSignedState; import com.swirlds.platform.state.signed.SignedState; -import com.swirlds.platform.state.signed.SignedStateFileReader; import com.swirlds.platform.state.signed.SignedStateReference; +import com.swirlds.platform.state.snapshot.DeserializedSignedState; +import com.swirlds.platform.state.snapshot.SignedStateFileReader; import java.io.IOException; import java.nio.file.Path; import java.util.NoSuchElementException; @@ -67,8 +63,7 @@ public StateEditor(final Path statePath) throws IOException { final Configuration configuration = DefaultConfiguration.buildBasicConfiguration(ConfigurationBuilder.create()); - platformContext = new DefaultPlatformContext( - configuration, new NoOpMetrics(), CryptographyHolder.get(), Time.getCurrent()); + platformContext = PlatformContext.create(configuration); final DeserializedSignedState deserializedSignedState = SignedStateFileReader.readStateFile(platformContext, statePath); diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/editor/StateEditorSave.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/editor/StateEditorSave.java index e4c167ee3019..b9b8ef0a2d9e 100644 --- a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/editor/StateEditorSave.java +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/editor/StateEditorSave.java @@ -18,16 +18,12 @@ import static com.swirlds.common.io.utility.FileUtils.getAbsolutePath; import static com.swirlds.platform.state.editor.StateEditorUtils.formatFile; -import static com.swirlds.platform.state.signed.SavedStateMetadata.NO_NODE_ID; -import static com.swirlds.platform.state.signed.SignedStateFileWriter.writeSignedStateFilesToDirectory; +import static com.swirlds.platform.state.snapshot.SavedStateMetadata.NO_NODE_ID; +import static com.swirlds.platform.state.snapshot.SignedStateFileWriter.writeSignedStateFilesToDirectory; -import com.swirlds.base.time.Time; import com.swirlds.cli.utility.SubcommandOf; -import com.swirlds.common.context.DefaultPlatformContext; import com.swirlds.common.context.PlatformContext; -import com.swirlds.common.crypto.CryptographyHolder; import com.swirlds.common.merkle.crypto.MerkleCryptoFactory; -import com.swirlds.common.metrics.noop.NoOpMetrics; import com.swirlds.config.api.Configuration; import com.swirlds.config.api.ConfigurationBuilder; import com.swirlds.logging.legacy.LogMarker; @@ -77,8 +73,7 @@ public void run() { final Configuration configuration = DefaultConfiguration.buildBasicConfiguration(ConfigurationBuilder.create()); - final PlatformContext platformContext = new DefaultPlatformContext( - configuration, new NoOpMetrics(), CryptographyHolder.get(), Time.getCurrent()); + final PlatformContext platformContext = PlatformContext.create(configuration); try (final ReservedSignedState signedState = getStateEditor().getSignedStateCopy()) { writeSignedStateFilesToDirectory(platformContext, NO_NODE_ID, directory, signedState.get()); diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/signed/DefaultSignedStateHasher.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/hasher/DefaultStateHasher.java similarity index 57% rename from platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/signed/DefaultSignedStateHasher.java rename to platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/hasher/DefaultStateHasher.java index 495b12e300b0..ad26d54aae4a 100644 --- a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/signed/DefaultSignedStateHasher.java +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/hasher/DefaultStateHasher.java @@ -14,19 +14,17 @@ * limitations under the License. */ -package com.swirlds.platform.state.signed; +package com.swirlds.platform.state.hasher; import static com.swirlds.logging.legacy.LogMarker.EXCEPTION; -import static com.swirlds.platform.system.SystemExitCode.FATAL_ERROR; +import com.swirlds.common.context.PlatformContext; import com.swirlds.common.merkle.crypto.MerkleCryptoFactory; -import com.swirlds.platform.components.common.output.FatalErrorConsumer; import com.swirlds.platform.wiring.components.StateAndRound; import edu.umd.cs.findbugs.annotations.NonNull; import edu.umd.cs.findbugs.annotations.Nullable; import java.time.Duration; import java.time.Instant; -import java.util.Objects; import java.util.concurrent.ExecutionException; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; @@ -34,35 +32,21 @@ /** * Hashes signed states after all modifications for a round have been completed. */ -public class DefaultSignedStateHasher implements SignedStateHasher { - /** - * The logger for the SignedStateHasher class. - */ - private static final Logger logger = LogManager.getLogger(DefaultSignedStateHasher.class); - /** - * The SignedStateMetrics object to record time spent hashing. May be null. - */ - private final SignedStateMetrics signedStateMetrics; +public class DefaultStateHasher implements StateHasher { - /** - * The FatalErrorConsumer to notify with any fatal errors that occur during hashing. - */ - private final FatalErrorConsumer fatalErrorConsumer; + private static final Logger logger = LogManager.getLogger(DefaultStateHasher.class); + private final StateHasherMetrics metrics; /** * Constructs a SignedStateHasher to hash SignedStates. If the signedStateMetrics object is not null, the time * spent hashing is recorded. Any fatal errors that occur are passed to the provided FatalErrorConsumer. The hash is * dispatched to the provided StateHashedTrigger. * - * @param signedStateMetrics the SignedStateMetrics instance to record time spent hashing. - * @param fatalErrorConsumer the FatalErrorConsumer to consume any fatal errors during hashing. - * @throws NullPointerException if any of the {@code fatalErrorConsumer} parameter is {@code null}. + * @param platformContext the platform context */ - public DefaultSignedStateHasher( - @Nullable final SignedStateMetrics signedStateMetrics, - @NonNull final FatalErrorConsumer fatalErrorConsumer) { - this.fatalErrorConsumer = Objects.requireNonNull(fatalErrorConsumer, "fatalErrorConsumer must not be null"); - this.signedStateMetrics = signedStateMetrics; + public DefaultStateHasher(@NonNull final PlatformContext platformContext) { + + metrics = new StateHasherMetrics(platformContext.getMetrics()); } /** @@ -77,15 +61,11 @@ public StateAndRound hashState(@NonNull final StateAndRound stateAndRound) { .digestTreeAsync(stateAndRound.reservedSignedState().get().getState()) .get(); - if (signedStateMetrics != null) { - signedStateMetrics - .getSignedStateHashingTimeMetric() - .update(Duration.between(start, Instant.now()).toMillis()); - } + metrics.reportHashingTime(Duration.between(start, Instant.now())); return stateAndRound; } catch (final ExecutionException e) { - fatalErrorConsumer.fatalError("Exception occurred during SignedState hashing", e, FATAL_ERROR); + logger.fatal(EXCEPTION.getMarker(), "Exception occurred during SignedState hashing", e); } catch (final InterruptedException e) { logger.error(EXCEPTION.getMarker(), "Interrupted while hashing state. Expect buggy behavior."); Thread.currentThread().interrupt(); diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/signed/SignedStateHasher.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/hasher/StateHasher.java similarity index 93% rename from platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/signed/SignedStateHasher.java rename to platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/hasher/StateHasher.java index f96833409483..63c72d1ef328 100644 --- a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/signed/SignedStateHasher.java +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/hasher/StateHasher.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.swirlds.platform.state.signed; +package com.swirlds.platform.state.hasher; import com.swirlds.common.wiring.component.InputWireLabel; import com.swirlds.platform.wiring.components.StateAndRound; @@ -25,7 +25,7 @@ * Hashes signed states */ @FunctionalInterface -public interface SignedStateHasher { +public interface StateHasher { /** * Hashes a SignedState. * diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/hasher/StateHasherMetrics.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/hasher/StateHasherMetrics.java new file mode 100644 index 000000000000..97c21f02bd1e --- /dev/null +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/hasher/StateHasherMetrics.java @@ -0,0 +1,51 @@ +/* + * Copyright (C) 2024 Hedera Hashgraph, LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.swirlds.platform.state.hasher; + +import com.swirlds.common.metrics.RunningAverageMetric; +import com.swirlds.metrics.api.Metrics; +import edu.umd.cs.findbugs.annotations.NonNull; +import java.time.Duration; + +/** + * Encapsulates metrics for state hashing. + */ +public class StateHasherMetrics { + + private static final RunningAverageMetric.Config STATE_HASHING_TIME_CONFIG = new RunningAverageMetric.Config( + "platform", "sigStateHash") + .withDescription("average time it takes to hash a SignedState (in milliseconds)") + .withUnit("ms"); + private final RunningAverageMetric stateHashingTime; + + /** + * Constructor. + * + * @param metrics the metrics object + */ + public StateHasherMetrics(@NonNull final Metrics metrics) { + stateHashingTime = metrics.getOrCreate(STATE_HASHING_TIME_CONFIG); + } + /** + * Report the time taken to hash a state. + * + * @param hashingTime the time taken to hash a state + */ + public void reportHashingTime(@NonNull final Duration hashingTime) { + stateHashingTime.update(hashingTime.toMillis()); + } +} diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/util/HashLogger.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/hashlogger/DefaultHashLogger.java similarity index 50% rename from platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/util/HashLogger.java rename to platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/hashlogger/DefaultHashLogger.java index 2e139bf5bd20..360615cce7ec 100644 --- a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/util/HashLogger.java +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/hashlogger/DefaultHashLogger.java @@ -14,10 +14,11 @@ * limitations under the License. */ -package com.swirlds.platform.util; +package com.swirlds.platform.state.hashlogger; import static com.swirlds.logging.legacy.LogMarker.STATE_HASH; +import com.swirlds.common.context.PlatformContext; import com.swirlds.platform.config.StateConfig; import com.swirlds.platform.state.State; import com.swirlds.platform.state.signed.ReservedSignedState; @@ -32,72 +33,82 @@ import org.apache.logging.log4j.message.ParameterizedMessageFactory; /** - * Logger responsible for logging node hashes to the swirlds-hashstream file. - * - *

    HashLogger will offer tasks to generate and log hashes from the provided signed states ... to a task scheduler. - * The implementation makes use of log4j and will make use of log4j configuration to configure how the log entries are - * outputted.

    + * A default implementation of a {@link HashLogger}. */ -public class HashLogger { - private static final Logger logger = LogManager.getLogger(HashLogger.class); +public class DefaultHashLogger implements HashLogger { + + private static final Logger logger = LogManager.getLogger(DefaultHashLogger.class); + private static final MessageFactory MESSAGE_FACTORY = ParameterizedMessageFactory.INSTANCE; private final AtomicLong lastRoundLogged = new AtomicLong(-1); - private final StateConfig stateConfig; + private final int depth; private final Logger logOutput; // NOSONAR: selected logger to output to. private final boolean isEnabled; /** * Construct a HashLogger. * - * @param stateConfig configuration for the current state. + * @param platformContext the platform context */ - public HashLogger(@NonNull final StateConfig stateConfig) { - this(stateConfig, logger); + public DefaultHashLogger(@NonNull final PlatformContext platformContext) { + this(platformContext, logger); } - // Visible for testing - HashLogger(@NonNull final StateConfig stateConfig, @NonNull final Logger logOutput) { - this.stateConfig = Objects.requireNonNull(stateConfig); + /** + * Internal constructor visible for testing. + * + * @param platformContext the platform context + * @param logOutput the logger to write to + */ + DefaultHashLogger(@NonNull final PlatformContext platformContext, @NonNull final Logger logOutput) { + final StateConfig stateConfig = platformContext.getConfiguration().getConfigData(StateConfig.class); + depth = stateConfig.debugHashDepth(); isEnabled = stateConfig.enableHashStreamLogging(); this.logOutput = Objects.requireNonNull(logOutput); } - private void log(final SignedState signedState) { - final long currentRound = signedState.getRound(); - final long prevRound = lastRoundLogged.getAndUpdate(value -> Math.max(value, currentRound)); - - if (prevRound >= 0 && currentRound - prevRound > 1) { - // One or more rounds skipped. - logOutput.info( - STATE_HASH.getMarker(), - () -> MESSAGE_FACTORY.newMessage( - "*** Several rounds skipped. Round received {}. Previously received {}.", - currentRound, - prevRound)); - } - - if (currentRound > prevRound) { - logOutput.info(STATE_HASH.getMarker(), () -> generateLogMessage(signedState)); - } - } - /** * Delegates extraction and logging of the signed state's hashes * - * @param signedState the signed state to retrieve hash information from and log. + * @param reservedState the signed state to retrieve hash information from and log. */ - public void logHashes(@NonNull final ReservedSignedState signedState) { - try (signedState) { + public void logHashes(@NonNull final ReservedSignedState reservedState) { + try (reservedState) { if (!isEnabled) { return; } - log(signedState.get()); + + final SignedState signedState = reservedState.get(); + + final long currentRound = signedState.getRound(); + final long prevRound = lastRoundLogged.getAndUpdate(value -> Math.max(value, currentRound)); + + if (prevRound >= 0 && currentRound - prevRound > 1) { + // One or more rounds skipped. + logOutput.info( + STATE_HASH.getMarker(), + () -> MESSAGE_FACTORY.newMessage( + "*** Several rounds skipped. Round received {}. Previously received {}.", + currentRound, + prevRound)); + } + + if (currentRound > prevRound) { + logOutput.info(STATE_HASH.getMarker(), () -> generateLogMessage(signedState)); + } } } + /** + * Generate the actual log message. Packaged in a lambda in case the logging framework decides not to log it. + * + * @param signedState the signed state to log + * @return the log message + */ + @NonNull private Message generateLogMessage(@NonNull final SignedState signedState) { final State state = signedState.getState(); - final String platformInfo = state.getInfoString(stateConfig.debugHashDepth()); + final String platformInfo = state.getInfoString(depth); return MESSAGE_FACTORY.newMessage( """ diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/hashlogger/HashLogger.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/hashlogger/HashLogger.java new file mode 100644 index 000000000000..78055ed0d746 --- /dev/null +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/hashlogger/HashLogger.java @@ -0,0 +1,35 @@ +/* + * Copyright (C) 2022-2024 Hedera Hashgraph, LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.swirlds.platform.state.hashlogger; + +import com.swirlds.common.wiring.component.InputWireLabel; +import com.swirlds.platform.state.signed.ReservedSignedState; +import edu.umd.cs.findbugs.annotations.NonNull; + +/** + * This component is responsible for logging the hash of the state each round (for debugging). + */ +public interface HashLogger { + + /** + * Log the hashes of the state. + * + * @param reservedState the state to retrieve hash information from and log. + */ + @InputWireLabel("hashed states") + void logHashes(@NonNull ReservedSignedState reservedState); +} diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/iss/DefaultIssDetector.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/iss/DefaultIssDetector.java new file mode 100644 index 000000000000..ff49f4da865a --- /dev/null +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/iss/DefaultIssDetector.java @@ -0,0 +1,555 @@ +/* + * Copyright (C) 2022-2024 Hedera Hashgraph, LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.swirlds.platform.state.iss; + +import static com.swirlds.logging.legacy.LogMarker.EXCEPTION; +import static com.swirlds.logging.legacy.LogMarker.STARTUP; +import static com.swirlds.logging.legacy.LogMarker.STATE_HASH; + +import com.hedera.hapi.platform.event.StateSignaturePayload; +import com.swirlds.common.context.PlatformContext; +import com.swirlds.common.crypto.Hash; +import com.swirlds.common.platform.NodeId; +import com.swirlds.common.sequence.map.ConcurrentSequenceMap; +import com.swirlds.common.sequence.map.SequenceMap; +import com.swirlds.common.utility.throttle.RateLimiter; +import com.swirlds.logging.legacy.payload.IssPayload; +import com.swirlds.platform.components.transaction.system.ScopedSystemTransaction; +import com.swirlds.platform.components.transaction.system.SystemTransactionExtractionUtils; +import com.swirlds.platform.config.StateConfig; +import com.swirlds.platform.consensus.ConsensusConfig; +import com.swirlds.platform.internal.ConsensusRound; +import com.swirlds.platform.metrics.IssMetrics; +import com.swirlds.platform.state.iss.internal.ConsensusHashFinder; +import com.swirlds.platform.state.iss.internal.HashValidityStatus; +import com.swirlds.platform.state.iss.internal.RoundHashValidator; +import com.swirlds.platform.state.signed.ReservedSignedState; +import com.swirlds.platform.system.SoftwareVersion; +import com.swirlds.platform.system.address.AddressBook; +import com.swirlds.platform.system.state.notifications.IssNotification; +import com.swirlds.platform.system.state.notifications.IssNotification.IssType; +import com.swirlds.platform.util.MarkerFileWriter; +import com.swirlds.platform.wiring.components.StateAndRound; +import edu.umd.cs.findbugs.annotations.NonNull; +import edu.umd.cs.findbugs.annotations.Nullable; +import java.time.Duration; +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +/** + * A default implementation of the {@link IssDetector}. + */ +public class DefaultIssDetector implements IssDetector { + + private static final Logger logger = LogManager.getLogger(DefaultIssDetector.class); + + private final SequenceMap roundData; + + private long previousRound = -1; + + /** + * The address book of this network. + */ + private final AddressBook addressBook; + + /** + * The current software version + */ + private final SoftwareVersion currentSoftwareVersion; + + /** + * Prevent log messages about a lack of signatures from spamming the logs. + */ + private final RateLimiter lackingSignaturesRateLimiter; + + /** + * Prevent log messages about self ISS events from spamming the logs. + */ + private final RateLimiter selfIssRateLimiter; + + /** + * Prevent log messages about catastrophic ISS events from spamming the logs. + */ + private final RateLimiter catastrophicIssRateLimiter; + + /** + * If true, ignore signatures from the preconsensus event stream, otherwise validate them like normal. + */ + private final boolean ignorePreconsensusSignatures; + + /** + * Set to false once all preconsensus events have been replayed. + */ + private boolean replayingPreconsensusStream = true; + + /** + * A round that should not be validated. Set to {@link #DO_NOT_IGNORE_ROUNDS} if all rounds should be validated. + */ + private final long ignoredRound; + + /** + * ISS related metrics + */ + private final IssMetrics issMetrics; + + /** + * Writes marker files to disk. + */ + private final MarkerFileWriter markerFileWriter; + + /** + * Create an object that tracks reported hashes and detects ISS events. + * + * @param platformContext the platform context + * @param addressBook the address book for the network + * @param currentSoftwareVersion the current software version + * @param ignorePreconsensusSignatures If true, ignore signatures from the preconsensus event stream, otherwise + * validate them like normal. + * @param ignoredRound a round that should not be validated. Set to {@link #DO_NOT_IGNORE_ROUNDS} if + * all rounds should be validated. + */ + public DefaultIssDetector( + @NonNull final PlatformContext platformContext, + @NonNull final AddressBook addressBook, + @NonNull final SoftwareVersion currentSoftwareVersion, + final boolean ignorePreconsensusSignatures, + final long ignoredRound) { + Objects.requireNonNull(platformContext); + markerFileWriter = new MarkerFileWriter(platformContext); + + final ConsensusConfig consensusConfig = + platformContext.getConfiguration().getConfigData(ConsensusConfig.class); + final StateConfig stateConfig = platformContext.getConfiguration().getConfigData(StateConfig.class); + + final Duration timeBetweenIssLogs = Duration.ofSeconds(stateConfig.secondsBetweenIssLogs()); + lackingSignaturesRateLimiter = new RateLimiter(platformContext.getTime(), timeBetweenIssLogs); + selfIssRateLimiter = new RateLimiter(platformContext.getTime(), timeBetweenIssLogs); + catastrophicIssRateLimiter = new RateLimiter(platformContext.getTime(), timeBetweenIssLogs); + + this.addressBook = Objects.requireNonNull(addressBook); + this.currentSoftwareVersion = Objects.requireNonNull(currentSoftwareVersion); + + this.roundData = new ConcurrentSequenceMap<>( + -consensusConfig.roundsNonAncient(), consensusConfig.roundsNonAncient(), x -> x); + + this.ignorePreconsensusSignatures = ignorePreconsensusSignatures; + if (ignorePreconsensusSignatures) { + logger.info(STARTUP.getMarker(), "State signatures from the preconsensus event stream will be ignored."); + } + + this.ignoredRound = ignoredRound; + if (ignoredRound != DO_NOT_IGNORE_ROUNDS) { + logger.warn(STARTUP.getMarker(), "No ISS detection will be performed for round {}", ignoredRound); + } + this.issMetrics = new IssMetrics(platformContext.getMetrics(), addressBook); + } + + /** + * This method is called once all preconsensus events have been replayed. + */ + @Override + public void signalEndOfPreconsensusReplay() { + replayingPreconsensusStream = false; + } + + /** + * Create an ISS notification if the round shouldn't be ignored + * + * @param roundNumber the round number of the ISS + * @param issType the type of the ISS + * @return an ISS notification, or null if the round of the ISS should be ignored + */ + @Nullable + private IssNotification maybeCreateIssNotification(final long roundNumber, @NonNull final IssType issType) { + if (roundNumber == ignoredRound) { + return null; + } + markerFileWriter.writeMarkerFile(issType.toString()); + return new IssNotification(roundNumber, issType); + } + + /** + * Shift the round data window when a new round is completed. + *

    + * If any round that is removed by shifting the window hasn't already had its hash decided, then this method will + * force a decision on the hash, and handle any ISS events that result. + * + * @param roundNumber the round that was just completed + * @return a list of ISS notifications, which may be empty, but will not contain null + */ + @NonNull + private List shiftRoundDataWindow(final long roundNumber) { + if (roundNumber <= previousRound) { + throw new IllegalArgumentException( + "previous round was " + previousRound + ", can't decrease round to " + roundNumber); + } + + final long oldestRoundToValidate = roundNumber - roundData.getSequenceNumberCapacity() + 1; + + final List removedRounds = new ArrayList<>(); + if (roundNumber != previousRound + 1) { + // We are either loading the first state at boot time, or we had a reconnect that caused us to skip some + // rounds. Rounds that have not yet been validated at this point in time should not be considered + // evidence of a catastrophic ISS. + roundData.shiftWindow(oldestRoundToValidate); + } else { + roundData.shiftWindow(oldestRoundToValidate, (k, v) -> removedRounds.add(v)); + } + + previousRound = roundNumber; + + roundData.put(roundNumber, new RoundHashValidator(roundNumber, addressBook.getTotalWeight(), issMetrics)); + + return removedRounds.stream() + .map(this::handleRemovedRound) + .filter(Objects::nonNull) + .toList(); + } + + /** + * Called when a round has been completed. + *

    + * Expects the contained state to have been reserved by the caller for this method. This method will release the + * state reservation when it is done with it. + * + * @param stateAndRound the round and state to be handled + * @return a list of ISS notifications, or null if no ISS occurred + */ + @Override + @Nullable + public List handleStateAndRound(@NonNull final StateAndRound stateAndRound) { + try (final ReservedSignedState state = stateAndRound.reservedSignedState()) { + final long roundNumber = stateAndRound.round().getRoundNum(); + + final List issNotifications = new ArrayList<>(shiftRoundDataWindow(roundNumber)); + + final IssNotification selfHashCheckResult = + checkSelfStateHash(roundNumber, state.get().getState().getHash()); + if (selfHashCheckResult != null) { + issNotifications.add(selfHashCheckResult); + } + + issNotifications.addAll(handlePostconsensusSignatures(stateAndRound.round())); + + return issNotifications.isEmpty() ? null : issNotifications; + } + } + + /** + * Handle a round that has become old enough that we want to stop tracking data on it. + * + * @param roundHashValidator the hash validator for the round + * @return an ISS notification, or null if no ISS occurred + */ + @Nullable + private IssNotification handleRemovedRound(@NonNull final RoundHashValidator roundHashValidator) { + final boolean justDecided = roundHashValidator.outOfTime(); + + final StringBuilder sb = new StringBuilder(); + roundHashValidator.getHashFinder().writePartitionData(sb); + logger.info(STATE_HASH.getMarker(), sb); + + if (justDecided) { + final HashValidityStatus status = roundHashValidator.getStatus(); + if (status == HashValidityStatus.CATASTROPHIC_ISS + || status == HashValidityStatus.CATASTROPHIC_LACK_OF_DATA) { + + final IssNotification notification = + maybeCreateIssNotification(roundHashValidator.getRound(), IssType.CATASTROPHIC_ISS); + if (notification != null) { + handleCatastrophic(roundHashValidator); + } + + return notification; + } else if (status == HashValidityStatus.LACK_OF_DATA) { + handleLackOfData(roundHashValidator); + } else { + throw new IllegalStateException( + "Unexpected hash validation status " + status + ", should have decided prior to now"); + } + } + return null; + } + + /** + * Handle postconsensus state signatures. + * + * @param round the round that may contain state signatures + * @return a list of ISS notifications, which may be empty, but will not contain null + */ + @NonNull + private List handlePostconsensusSignatures(@NonNull final ConsensusRound round) { + final List> stateSignatureTransactions = + SystemTransactionExtractionUtils.extractFromRound(round, StateSignaturePayload.class); + + if (stateSignatureTransactions == null) { + return List.of(); + } + + return stateSignatureTransactions.stream() + .map(this::handlePostconsensusSignature) + .filter(Objects::nonNull) + .toList(); + } + + /** + *

    + * Observes post-consensus state signature transactions. + *

    + * + *

    + * Since it is only possible to sign a round after it has reached consensus, it is guaranteed that any valid + * signature transaction observed here (post consensus) will be for a round in the past. + *

    + * + * @param transaction the transaction to handle + * @return an ISS notification, or null if no ISS occurred + */ + @Nullable + private IssNotification handlePostconsensusSignature( + @NonNull final ScopedSystemTransaction transaction) { + final NodeId signerId = transaction.submitterId(); + final StateSignaturePayload signaturePayload = transaction.transaction(); + final SoftwareVersion eventVersion = transaction.softwareVersion(); + + if (eventVersion == null) { + // Illegal event version, ignore. + return null; + } + + if (ignorePreconsensusSignatures && replayingPreconsensusStream) { + // We are still replaying preconsensus events, and we are configured to ignore signatures during replay + return null; + } + + if (currentSoftwareVersion.compareTo(eventVersion) != 0) { + // this is a signature from a different software version, ignore it + return null; + } + + if (!addressBook.contains(signerId)) { + // we don't care about nodes not in the address book + return null; + } + + if (signaturePayload.round() == ignoredRound) { + // This round is intentionally ignored. + return null; + } + + final long nodeWeight = addressBook.getAddress(signerId).getWeight(); + + final RoundHashValidator roundValidator = roundData.get(signaturePayload.round()); + if (roundValidator == null) { + // We are being asked to validate a signature from the far future or far past, or a round that has already + // been decided. + return null; + } + + final boolean decided = roundValidator.reportHashFromNetwork( + signerId, nodeWeight, new Hash(signaturePayload.hash().toByteArray())); + if (decided) { + return checkValidity(roundValidator); + } + return null; + } + + /** + * Checks the validity of the self state hash for a round. + * + * @param round the round of the state + * @param hash the hash of the state + * @return an ISS notification, or null if no ISS occurred + */ + @Nullable + private IssNotification checkSelfStateHash(final long round, @NonNull final Hash hash) { + final RoundHashValidator roundHashValidator = roundData.get(round); + if (roundHashValidator == null) { + throw new IllegalStateException( + "Hash reported for round " + round + ", but that round is not being tracked"); + } + + final boolean decided = roundHashValidator.reportSelfHash(hash); + if (decided) { + return checkValidity(roundHashValidator); + } + return null; + } + + /** + * Called when an overriding state is obtained, i.e. via reconnect or state loading. + *

    + * Expects the input state to have been reserved by the caller for this method. This method will release the state + * reservation when it is done with it. + * + * @param state the state that was loaded + * @return a list of ISS notifications, or null if no ISS occurred + */ + @Override + @Nullable + public List overridingState(@NonNull final ReservedSignedState state) { + try (state) { + final long roundNumber = state.get().getRound(); + // this is not practically possible for this to happen. Even if it were to happen, on a reconnect, + // we are receiving a new state that is fully signed, so any ISSs in the past should be ignored. + // so we will ignore any ISSs from removed rounds + shiftRoundDataWindow(roundNumber); + + final Hash stateHash = state.get().getState().getHash(); + final IssNotification issNotification = checkSelfStateHash(roundNumber, stateHash); + + return issNotification == null ? null : List.of(issNotification); + } + } + + /** + * Called once the validity has been decided. Take action based on the validity status. + * + * @param roundValidator the validator for the round + * @return an ISS notification, or null if no ISS occurred + */ + @Nullable + private IssNotification checkValidity(@NonNull final RoundHashValidator roundValidator) { + final long round = roundValidator.getRound(); + + return switch (roundValidator.getStatus()) { + case VALID -> { + if (roundValidator.hasDisagreement()) { + yield maybeCreateIssNotification(round, IssType.OTHER_ISS); + } + yield null; + } + case SELF_ISS -> { + final IssNotification notification = maybeCreateIssNotification(round, IssType.SELF_ISS); + if (notification != null) { + handleSelfIss(roundValidator); + } + yield notification; + } + case CATASTROPHIC_ISS -> { + final IssNotification notification = maybeCreateIssNotification(round, IssType.CATASTROPHIC_ISS); + if (notification != null) { + handleCatastrophic(roundValidator); + } + yield notification; + } + case UNDECIDED -> throw new IllegalStateException( + "status is undecided, but method reported a decision, round = " + round); + case LACK_OF_DATA -> throw new IllegalStateException( + "a decision that we lack data should only be possible once time runs out, round = " + round); + default -> throw new IllegalStateException( + "unhandled case " + roundValidator.getStatus() + ", round = " + round); + }; + } + + /** + * This node doesn't agree with the consensus hash. + * + * @param roundHashValidator the validator responsible for validating the round with a self ISS + */ + private void handleSelfIss(@NonNull final RoundHashValidator roundHashValidator) { + final long round = roundHashValidator.getRound(); + final Hash selfHash = roundHashValidator.getSelfStateHash(); + final Hash consensusHash = roundHashValidator.getConsensusHash(); + + final long skipCount = selfIssRateLimiter.getDeniedRequests(); + if (selfIssRateLimiter.requestAndTrigger()) { + + final StringBuilder sb = new StringBuilder(); + sb.append("Invalid State Signature (ISS): this node has the wrong hash for round ") + .append(round) + .append(".\n"); + + roundHashValidator.getHashFinder().writePartitionData(sb); + writeSkippedLogCount(sb, skipCount); + + logger.fatal( + EXCEPTION.getMarker(), + new IssPayload(sb.toString(), round, selfHash.toMnemonic(), consensusHash.toMnemonic(), false)); + } + } + + /** + * There has been a catastrophic ISS or a catastrophic lack of data. + * + * @param roundHashValidator information about the round, including the signatures that were gathered + */ + private void handleCatastrophic(@NonNull final RoundHashValidator roundHashValidator) { + + final long round = roundHashValidator.getRound(); + final ConsensusHashFinder hashFinder = roundHashValidator.getHashFinder(); + final Hash selfHash = roundHashValidator.getSelfStateHash(); + + final long skipCount = catastrophicIssRateLimiter.getDeniedRequests(); + if (catastrophicIssRateLimiter.requestAndTrigger()) { + + final StringBuilder sb = new StringBuilder(); + sb.append("Catastrophic Invalid State Signature (ISS)\n"); + sb.append("Due to divergence in state hash between many network members, " + + "this network is incapable of continued operation without human intervention.\n"); + + hashFinder.writePartitionData(sb); + writeSkippedLogCount(sb, skipCount); + + logger.fatal(EXCEPTION.getMarker(), new IssPayload(sb.toString(), round, selfHash.toMnemonic(), "", true)); + } + } + + /** + * We are not getting the signatures we need to be getting. ISS events may be going undetected. + * + * @param roundHashValidator information about the round + */ + private void handleLackOfData(@NonNull final RoundHashValidator roundHashValidator) { + final long skipCount = lackingSignaturesRateLimiter.getDeniedRequests(); + if (!lackingSignaturesRateLimiter.requestAndTrigger()) { + return; + } + + final long round = roundHashValidator.getRound(); + final ConsensusHashFinder hashFinder = roundHashValidator.getHashFinder(); + final Hash selfHash = roundHashValidator.getSelfStateHash(); + + final StringBuilder sb = new StringBuilder(); + sb.append("Unable to collect enough data to determine the consensus hash for round ") + .append(round) + .append(".\n"); + if (selfHash == null) { + sb.append("No self hash was computed. This is highly unusual.\n"); + } + hashFinder.writePartitionData(sb); + writeSkippedLogCount(sb, skipCount); + + logger.warn(STATE_HASH.getMarker(), sb); + } + + /** + * Write the number of times a log has been skipped. + */ + private static void writeSkippedLogCount(@NonNull final StringBuilder sb, final long skipCount) { + if (skipCount > 0) { + sb.append("This condition has been triggered ") + .append(skipCount) + .append(" time(s) over the last ") + .append(Duration.ofMinutes(1).toSeconds()) + .append("seconds."); + } + } +} diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/iss/IssDetector.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/iss/IssDetector.java index 9890e45e9e05..f85a2b503269 100644 --- a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/iss/IssDetector.java +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/iss/IssDetector.java @@ -16,212 +16,30 @@ package com.swirlds.platform.state.iss; -import static com.swirlds.logging.legacy.LogMarker.EXCEPTION; -import static com.swirlds.logging.legacy.LogMarker.STARTUP; -import static com.swirlds.logging.legacy.LogMarker.STATE_HASH; - -import com.hedera.hapi.platform.event.StateSignaturePayload; -import com.swirlds.common.context.PlatformContext; -import com.swirlds.common.crypto.Hash; -import com.swirlds.common.platform.NodeId; -import com.swirlds.common.sequence.map.ConcurrentSequenceMap; -import com.swirlds.common.sequence.map.SequenceMap; -import com.swirlds.common.utility.throttle.RateLimiter; -import com.swirlds.logging.legacy.payload.IssPayload; -import com.swirlds.platform.components.transaction.system.ScopedSystemTransaction; -import com.swirlds.platform.components.transaction.system.SystemTransactionExtractionUtils; -import com.swirlds.platform.config.StateConfig; -import com.swirlds.platform.consensus.ConsensusConfig; -import com.swirlds.platform.internal.ConsensusRound; -import com.swirlds.platform.metrics.IssMetrics; -import com.swirlds.platform.state.iss.internal.ConsensusHashFinder; -import com.swirlds.platform.state.iss.internal.HashValidityStatus; -import com.swirlds.platform.state.iss.internal.RoundHashValidator; import com.swirlds.platform.state.signed.ReservedSignedState; -import com.swirlds.platform.system.SoftwareVersion; -import com.swirlds.platform.system.address.AddressBook; import com.swirlds.platform.system.state.notifications.IssNotification; -import com.swirlds.platform.system.state.notifications.IssNotification.IssType; -import com.swirlds.platform.util.MarkerFileWriter; +import com.swirlds.platform.system.status.actions.CatastrophicFailureAction; +import com.swirlds.platform.system.status.actions.PlatformStatusAction; import com.swirlds.platform.wiring.components.StateAndRound; import edu.umd.cs.findbugs.annotations.NonNull; import edu.umd.cs.findbugs.annotations.Nullable; -import java.time.Duration; -import java.util.ArrayList; import java.util.List; -import java.util.Objects; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; +import java.util.Set; /** * Keeps track of the state hashes reported by all network nodes. Responsible for detecting ISS events. */ -public class IssDetector { - - private static final Logger logger = LogManager.getLogger(IssDetector.class); - - private final SequenceMap roundData; - - private long previousRound = -1; - - /** - * The address book of this network. - */ - private final AddressBook addressBook; - /** - * The current software version - */ - private final SoftwareVersion currentSoftwareVersion; - - /** - * Prevent log messages about a lack of signatures from spamming the logs. - */ - private final RateLimiter lackingSignaturesRateLimiter; - - /** - * Prevent log messages about self ISS events from spamming the logs. - */ - private final RateLimiter selfIssRateLimiter; - - /** - * Prevent log messages about catastrophic ISS events from spamming the logs. - */ - private final RateLimiter catastrophicIssRateLimiter; - - /** - * If true, ignore signatures from the preconsensus event stream, otherwise validate them like normal. - */ - private final boolean ignorePreconsensusSignatures; - - /** - * Set to false once all preconsensus events have been replayed. - */ - private boolean replayingPreconsensusStream = true; +public interface IssDetector { /** * Use this constant if the consensus hash manager should not ignore any rounds. */ - public static final int DO_NOT_IGNORE_ROUNDS = -1; - - /** - * A round that should not be validated. Set to {@link #DO_NOT_IGNORE_ROUNDS} if all rounds should be validated. - */ - private final long ignoredRound; - /** - * ISS related metrics - */ - private final IssMetrics issMetrics; - /** - * Writes marker files to disk. - */ - private final MarkerFileWriter markerFileWriter; - - /** - * Create an object that tracks reported hashes and detects ISS events. - * - * @param platformContext the platform context - * @param addressBook the address book for the network - * @param currentSoftwareVersion the current software version - * @param ignorePreconsensusSignatures If true, ignore signatures from the preconsensus event stream, otherwise - * validate them like normal. - * @param ignoredRound a round that should not be validated. Set to {@link #DO_NOT_IGNORE_ROUNDS} if - * all rounds should be validated. - */ - public IssDetector( - @NonNull final PlatformContext platformContext, - @NonNull final AddressBook addressBook, - @NonNull final SoftwareVersion currentSoftwareVersion, - final boolean ignorePreconsensusSignatures, - final long ignoredRound) { - Objects.requireNonNull(platformContext); - markerFileWriter = new MarkerFileWriter(platformContext); - - final ConsensusConfig consensusConfig = - platformContext.getConfiguration().getConfigData(ConsensusConfig.class); - final StateConfig stateConfig = platformContext.getConfiguration().getConfigData(StateConfig.class); - - final Duration timeBetweenIssLogs = Duration.ofSeconds(stateConfig.secondsBetweenIssLogs()); - lackingSignaturesRateLimiter = new RateLimiter(platformContext.getTime(), timeBetweenIssLogs); - selfIssRateLimiter = new RateLimiter(platformContext.getTime(), timeBetweenIssLogs); - catastrophicIssRateLimiter = new RateLimiter(platformContext.getTime(), timeBetweenIssLogs); - - this.addressBook = Objects.requireNonNull(addressBook); - this.currentSoftwareVersion = Objects.requireNonNull(currentSoftwareVersion); - - this.roundData = new ConcurrentSequenceMap<>( - -consensusConfig.roundsNonAncient(), consensusConfig.roundsNonAncient(), x -> x); - - this.ignorePreconsensusSignatures = ignorePreconsensusSignatures; - if (ignorePreconsensusSignatures) { - logger.info(STARTUP.getMarker(), "State signatures from the preconsensus event stream will be ignored."); - } - - this.ignoredRound = ignoredRound; - if (ignoredRound != DO_NOT_IGNORE_ROUNDS) { - logger.warn(STARTUP.getMarker(), "No ISS detection will be performed for round {}", ignoredRound); - } - this.issMetrics = new IssMetrics(platformContext.getMetrics(), addressBook); - } + int DO_NOT_IGNORE_ROUNDS = -1; /** * This method is called once all preconsensus events have been replayed. */ - public void signalEndOfPreconsensusReplay(@Nullable final Object ignored) { - replayingPreconsensusStream = false; - } - - /** - * Create an ISS notification if the round shouldn't be ignored - * - * @param roundNumber the round number of the ISS - * @param issType the type of the ISS - * @return an ISS notification, or null if the round of the ISS should be ignored - */ - private @Nullable IssNotification maybeCreateIssNotification( - final long roundNumber, @NonNull final IssType issType) { - if (roundNumber == ignoredRound) { - return null; - } - markerFileWriter.writeMarkerFile(issType.toString()); - return new IssNotification(roundNumber, issType); - } - - /** - * Shift the round data window when a new round is completed. - *

    - * If any round that is removed by shifting the window hasn't already had its hash decided, then this method will - * force a decision on the hash, and handle any ISS events that result. - * - * @param roundNumber the round that was just completed - * @return a list of ISS notifications, which may be empty, but will not contain null - */ - private @NonNull List shiftRoundDataWindow(final long roundNumber) { - if (roundNumber <= previousRound) { - throw new IllegalArgumentException( - "previous round was " + previousRound + ", can't decrease round to " + roundNumber); - } - - final long oldestRoundToValidate = roundNumber - roundData.getSequenceNumberCapacity() + 1; - - final List removedRounds = new ArrayList<>(); - if (roundNumber != previousRound + 1) { - // We are either loading the first state at boot time, or we had a reconnect that caused us to skip some - // rounds. Rounds that have not yet been validated at this point in time should not be considered - // evidence of a catastrophic ISS. - roundData.shiftWindow(oldestRoundToValidate); - } else { - roundData.shiftWindow(oldestRoundToValidate, (k, v) -> removedRounds.add(v)); - } - - previousRound = roundNumber; - - roundData.put(roundNumber, new RoundHashValidator(roundNumber, addressBook.getTotalWeight(), issMetrics)); - - return removedRounds.stream() - .map(this::handleRemovedRound) - .filter(Objects::nonNull) - .toList(); - } + void signalEndOfPreconsensusReplay(); /** * Called when a round has been completed. @@ -232,160 +50,8 @@ public void signalEndOfPreconsensusReplay(@Nullable final Object ignored) { * @param stateAndRound the round and state to be handled * @return a list of ISS notifications, or null if no ISS occurred */ - public @Nullable List handleStateAndRound(@NonNull final StateAndRound stateAndRound) { - try (final ReservedSignedState state = stateAndRound.reservedSignedState()) { - final long roundNumber = stateAndRound.round().getRoundNum(); - - final List issNotifications = new ArrayList<>(shiftRoundDataWindow(roundNumber)); - - final IssNotification selfHashCheckResult = - checkSelfStateHash(roundNumber, state.get().getState().getHash()); - if (selfHashCheckResult != null) { - issNotifications.add(selfHashCheckResult); - } - - issNotifications.addAll(handlePostconsensusSignatures(stateAndRound.round())); - - return issNotifications.isEmpty() ? null : issNotifications; - } - } - - /** - * Handle a round that has become old enough that we want to stop tracking data on it. - * - * @param roundHashValidator the hash validator for the round - * @return an ISS notification, or null if no ISS occurred - */ - private @Nullable IssNotification handleRemovedRound(@NonNull final RoundHashValidator roundHashValidator) { - final boolean justDecided = roundHashValidator.outOfTime(); - - final StringBuilder sb = new StringBuilder(); - roundHashValidator.getHashFinder().writePartitionData(sb); - logger.info(STATE_HASH.getMarker(), sb); - - if (justDecided) { - final HashValidityStatus status = roundHashValidator.getStatus(); - if (status == HashValidityStatus.CATASTROPHIC_ISS - || status == HashValidityStatus.CATASTROPHIC_LACK_OF_DATA) { - - final IssNotification notification = - maybeCreateIssNotification(roundHashValidator.getRound(), IssType.CATASTROPHIC_ISS); - if (notification != null) { - handleCatastrophic(roundHashValidator); - } - - return notification; - } else if (status == HashValidityStatus.LACK_OF_DATA) { - handleLackOfData(roundHashValidator); - } else { - throw new IllegalStateException( - "Unexpected hash validation status " + status + ", should have decided prior to now"); - } - } - return null; - } - - /** - * Handle postconsensus state signatures. - * - * @param round the round that may contain state signatures - * @return a list of ISS notifications, which may be empty, but will not contain null - */ - private @NonNull List handlePostconsensusSignatures(@NonNull final ConsensusRound round) { - final List> stateSignatureTransactions = - SystemTransactionExtractionUtils.extractFromRound(round, StateSignaturePayload.class); - - if (stateSignatureTransactions == null) { - return List.of(); - } - - return stateSignatureTransactions.stream() - .map(this::handlePostconsensusSignature) - .filter(Objects::nonNull) - .toList(); - } - - /** - *

    - * Observes post-consensus state signature transactions. - *

    - * - *

    - * Since it is only possible to sign a round after it has reached consensus, it is guaranteed that any valid - * signature transaction observed here (post consensus) will be for a round in the past. - *

    - * - * @param transaction the transaction to handle - * @return an ISS notification, or null if no ISS occurred - */ - private @Nullable IssNotification handlePostconsensusSignature( - @NonNull final ScopedSystemTransaction transaction) { - final NodeId signerId = transaction.submitterId(); - final StateSignaturePayload signaturePayload = transaction.transaction(); - final SoftwareVersion eventVersion = transaction.softwareVersion(); - - if (eventVersion == null) { - // Illegal event version, ignore. - return null; - } - - if (ignorePreconsensusSignatures && replayingPreconsensusStream) { - // We are still replaying preconsensus events, and we are configured to ignore signatures during replay - return null; - } - - if (currentSoftwareVersion.compareTo(eventVersion) != 0) { - // this is a signature from a different software version, ignore it - return null; - } - - if (!addressBook.contains(signerId)) { - // we don't care about nodes not in the address book - return null; - } - - if (signaturePayload.round() == ignoredRound) { - // This round is intentionally ignored. - return null; - } - - final long nodeWeight = addressBook.getAddress(signerId).getWeight(); - - final RoundHashValidator roundValidator = roundData.get(signaturePayload.round()); - if (roundValidator == null) { - // We are being asked to validate a signature from the far future or far past, or a round that has already - // been decided. - return null; - } - - final boolean decided = roundValidator.reportHashFromNetwork( - signerId, nodeWeight, new Hash(signaturePayload.hash().toByteArray())); - if (decided) { - return checkValidity(roundValidator); - } - return null; - } - - /** - * Checks the validity of the self state hash for a round. - * - * @param round the round of the state - * @param hash the hash of the state - * @return an ISS notification, or null if no ISS occurred - */ - private @Nullable IssNotification checkSelfStateHash(final long round, @NonNull final Hash hash) { - final RoundHashValidator roundHashValidator = roundData.get(round); - if (roundHashValidator == null) { - throw new IllegalStateException( - "Hash reported for round " + round + ", but that round is not being tracked"); - } - - final boolean decided = roundHashValidator.reportSelfHash(hash); - if (decided) { - return checkValidity(roundHashValidator); - } - return null; - } + @Nullable + List handleStateAndRound(@NonNull StateAndRound stateAndRound); /** * Called when an overriding state is obtained, i.e. via reconnect or state loading. @@ -396,151 +62,22 @@ public void signalEndOfPreconsensusReplay(@Nullable final Object ignored) { * @param state the state that was loaded * @return a list of ISS notifications, or null if no ISS occurred */ - public @Nullable List overridingState(@NonNull final ReservedSignedState state) { - try (state) { - final long roundNumber = state.get().getRound(); - // this is not practically possible for this to happen. Even if it were to happen, on a reconnect, - // we are receiving a new state that is fully signed, so any ISSs in the past should be ignored. - // so we will ignore any ISSs from removed rounds - shiftRoundDataWindow(roundNumber); - - final Hash stateHash = state.get().getState().getHash(); - final IssNotification issNotification = checkSelfStateHash(roundNumber, stateHash); - - return issNotification == null ? null : List.of(issNotification); - } - } - - /** - * Called once the validity has been decided. Take action based on the validity status. - * - * @param roundValidator the validator for the round - * @return an ISS notification, or null if no ISS occurred - */ - private @Nullable IssNotification checkValidity(@NonNull final RoundHashValidator roundValidator) { - final long round = roundValidator.getRound(); - - return switch (roundValidator.getStatus()) { - case VALID -> { - if (roundValidator.hasDisagreement()) { - yield maybeCreateIssNotification(round, IssType.OTHER_ISS); - } - yield null; - } - case SELF_ISS -> { - final IssNotification notification = maybeCreateIssNotification(round, IssType.SELF_ISS); - if (notification != null) { - handleSelfIss(roundValidator); - } - yield notification; - } - case CATASTROPHIC_ISS -> { - final IssNotification notification = maybeCreateIssNotification(round, IssType.CATASTROPHIC_ISS); - if (notification != null) { - handleCatastrophic(roundValidator); - } - yield notification; - } - case UNDECIDED -> throw new IllegalStateException( - "status is undecided, but method reported a decision, round = " + round); - case LACK_OF_DATA -> throw new IllegalStateException( - "a decision that we lack data should only be possible once time runs out, round = " + round); - default -> throw new IllegalStateException( - "unhandled case " + roundValidator.getStatus() + ", round = " + round); - }; - } - - /** - * This node doesn't agree with the consensus hash. - * - * @param roundHashValidator the validator responsible for validating the round with a self ISS - */ - private void handleSelfIss(@NonNull final RoundHashValidator roundHashValidator) { - final long round = roundHashValidator.getRound(); - final Hash selfHash = roundHashValidator.getSelfStateHash(); - final Hash consensusHash = roundHashValidator.getConsensusHash(); - - final long skipCount = selfIssRateLimiter.getDeniedRequests(); - if (selfIssRateLimiter.requestAndTrigger()) { - - final StringBuilder sb = new StringBuilder(); - sb.append("Invalid State Signature (ISS): this node has the wrong hash for round ") - .append(round) - .append(".\n"); - - roundHashValidator.getHashFinder().writePartitionData(sb); - writeSkippedLogCount(sb, skipCount); - - logger.fatal( - EXCEPTION.getMarker(), - new IssPayload(sb.toString(), round, selfHash.toMnemonic(), consensusHash.toMnemonic(), false)); - } - } - - /** - * There has been a catastrophic ISS or a catastrophic lack of data. - * - * @param roundHashValidator information about the round, including the signatures that were gathered - */ - private void handleCatastrophic(@NonNull final RoundHashValidator roundHashValidator) { - - final long round = roundHashValidator.getRound(); - final ConsensusHashFinder hashFinder = roundHashValidator.getHashFinder(); - final Hash selfHash = roundHashValidator.getSelfStateHash(); - - final long skipCount = catastrophicIssRateLimiter.getDeniedRequests(); - if (catastrophicIssRateLimiter.requestAndTrigger()) { - - final StringBuilder sb = new StringBuilder(); - sb.append("Catastrophic Invalid State Signature (ISS)\n"); - sb.append("Due to divergence in state hash between many network members, " - + "this network is incapable of continued operation without human intervention.\n"); - - hashFinder.writePartitionData(sb); - writeSkippedLogCount(sb, skipCount); - - logger.fatal(EXCEPTION.getMarker(), new IssPayload(sb.toString(), round, selfHash.toMnemonic(), "", true)); - } - } + @Nullable + List overridingState(@NonNull ReservedSignedState state); /** - * We are not getting the signatures we need to be getting. ISS events may be going undetected. + * Given an ISS notification, produce the appropriate status action. * - * @param roundHashValidator information about the round + * @param notification the ISS notification + * @return the status action, or null if no action is needed */ - private void handleLackOfData(@NonNull final RoundHashValidator roundHashValidator) { - final long skipCount = lackingSignaturesRateLimiter.getDeniedRequests(); - if (!lackingSignaturesRateLimiter.requestAndTrigger()) { - return; - } - - final long round = roundHashValidator.getRound(); - final ConsensusHashFinder hashFinder = roundHashValidator.getHashFinder(); - final Hash selfHash = roundHashValidator.getSelfStateHash(); - - final StringBuilder sb = new StringBuilder(); - sb.append("Unable to collect enough data to determine the consensus hash for round ") - .append(round) - .append(".\n"); - if (selfHash == null) { - sb.append("No self hash was computed. This is highly unusual.\n"); - } - hashFinder.writePartitionData(sb); - writeSkippedLogCount(sb, skipCount); - - logger.warn(STATE_HASH.getMarker(), sb); - } - - /** - * Write the number of times a log has been skipped. - */ - private static void writeSkippedLogCount(@NonNull final StringBuilder sb, final long skipCount) { - if (skipCount > 0) { - sb.append("This condition has been triggered ") - .append(skipCount) - .append(" time(s) over the last ") - .append(Duration.ofMinutes(1).toSeconds()) - .append("seconds."); + @Nullable + default PlatformStatusAction getStatusAction(final IssNotification notification) { + if (Set.of(IssNotification.IssType.SELF_ISS, IssNotification.IssType.CATASTROPHIC_ISS) + .contains(notification.getIssType())) { + return new CatastrophicFailureAction(); } + // don't change status for other types of ISS + return null; } } diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/iss/IssHandler.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/iss/IssHandler.java index e187e3589d15..2addbf8b57e5 100644 --- a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/iss/IssHandler.java +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/iss/IssHandler.java @@ -16,6 +16,7 @@ package com.swirlds.platform.state.iss; +import com.swirlds.common.context.PlatformContext; import com.swirlds.common.merkle.utility.SerializableLong; import com.swirlds.common.scratchpad.Scratchpad; import com.swirlds.platform.components.common.output.FatalErrorConsumer; @@ -40,20 +41,19 @@ public class IssHandler { /** * Create an object responsible for handling ISS events. * - * @param stateConfig settings for the state + * @param platformContext the platform context * @param haltRequestedConsumer consumer to invoke when a system halt is desired * @param fatalErrorConsumer consumer to invoke if a fatal error occurs * @param issScratchpad scratchpad for ISS data, is persistent across restarts */ public IssHandler( - @NonNull final StateConfig stateConfig, + @NonNull final PlatformContext platformContext, @NonNull final Consumer haltRequestedConsumer, @NonNull final FatalErrorConsumer fatalErrorConsumer, @NonNull final Scratchpad issScratchpad) { - this.haltRequestedConsumer = - Objects.requireNonNull(haltRequestedConsumer, "haltRequestedConsumer must not be null"); - this.fatalErrorConsumer = Objects.requireNonNull(fatalErrorConsumer, "fatalErrorConsumer must not be null"); - this.stateConfig = Objects.requireNonNull(stateConfig, "stateConfig must not be null"); + this.haltRequestedConsumer = Objects.requireNonNull(haltRequestedConsumer); + this.fatalErrorConsumer = Objects.requireNonNull(fatalErrorConsumer); + this.stateConfig = platformContext.getConfiguration().getConfigData(StateConfig.class); this.issScratchpad = Objects.requireNonNull(issScratchpad); } @@ -108,7 +108,7 @@ private void updateIssRoundInScratchpad(final long issRound) { /** * This method is called when there is a self ISS. * - * @param round the round of the ISS + * @param round the round of the ISS */ private void selfIssObserver(@NonNull final Long round) { diff --git a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/state/merkle/StateUtils.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/merkle/StateUtils.java similarity index 93% rename from hedera-node/hedera-app/src/main/java/com/hedera/node/app/state/merkle/StateUtils.java rename to platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/merkle/StateUtils.java index 2c8a3eb3f000..e740a2f2f2fa 100644 --- a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/state/merkle/StateUtils.java +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/merkle/StateUtils.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.hedera.node.app.state.merkle; +package com.swirlds.platform.state.merkle; import static com.swirlds.common.utility.CommonUtils.getNormalisedStringBytes; @@ -91,7 +91,7 @@ public static T readFromStream(@NonNull final InputStream in, @NonNull final * @throws IllegalArgumentException if any other validation criteria fails */ @NonNull - static String validateServiceName(@NonNull final String serviceName) { + public static String validateServiceName(@NonNull final String serviceName) { if (Objects.requireNonNull(serviceName).isEmpty()) { throw new IllegalArgumentException("The service name must have characters"); } @@ -108,7 +108,7 @@ static String validateServiceName(@NonNull final String serviceName) { * @throws IllegalArgumentException if any other validation criteria fails */ @NonNull - static String validateStateKey(@NonNull final String stateKey) { + public static String validateStateKey(@NonNull final String stateKey) { if (Objects.requireNonNull(stateKey).isEmpty()) { throw new IllegalArgumentException("The state key must have characters"); } @@ -157,18 +157,7 @@ public static String computeLabel(@NonNull final String serviceName, @NonNull fi * @param extra An extra string to bake into the class id * @return the class id */ - static long computeClassId(@NonNull final StateMetadata md, @NonNull final String extra) { - final var def = md.stateDefinition(); - return computeClassId(md.serviceName(), def.stateKey(), md.schema().getVersion(), extra); - } - - /** - * Given the inputs, compute the corresponding class ID. - * - * @param extra An extra string to bake into the class id - * @return the class id - */ - static long computeClassId( + public static long computeClassId( @NonNull final String serviceName, @NonNull final String stateKey, @NonNull final SemanticVersion version, diff --git a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/state/merkle/disk/OnDiskKey.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/merkle/disk/OnDiskKey.java similarity index 76% rename from hedera-node/hedera-app/src/main/java/com/hedera/node/app/state/merkle/disk/OnDiskKey.java rename to platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/merkle/disk/OnDiskKey.java index 7abc27def131..8d29e1ed7bc0 100644 --- a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/state/merkle/disk/OnDiskKey.java +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/merkle/disk/OnDiskKey.java @@ -14,18 +14,19 @@ * limitations under the License. */ -package com.hedera.node.app.state.merkle.disk; +package com.swirlds.platform.state.merkle.disk; -import static com.hedera.node.app.state.merkle.StateUtils.readFromStream; -import static com.hedera.node.app.state.merkle.StateUtils.writeToStream; +import static com.swirlds.platform.state.merkle.StateUtils.readFromStream; +import static com.swirlds.platform.state.merkle.StateUtils.writeToStream; +import static java.util.Objects.requireNonNull; -import com.hedera.node.app.state.merkle.StateMetadata; import com.hedera.pbj.runtime.Codec; import com.swirlds.common.io.streams.SerializableDataInputStream; import com.swirlds.common.io.streams.SerializableDataOutputStream; import com.swirlds.virtualmap.VirtualKey; import com.swirlds.virtualmap.VirtualMap; import edu.umd.cs.findbugs.annotations.NonNull; +import edu.umd.cs.findbugs.annotations.Nullable; import java.io.IOException; import java.util.Objects; @@ -50,8 +51,11 @@ public final class OnDiskKey implements VirtualKey { static final int VERSION = 1; - /** The metadata */ - private final StateMetadata md; + /** Key codec. */ + private final Codec codec; + + /** Key class id. */ + private final Long classId; /** The "real" key, such as AccountID. */ private K key; @@ -59,27 +63,31 @@ public final class OnDiskKey implements VirtualKey { // Default constructor provided for ConstructableRegistry, TO BE REMOVED ASAP @Deprecated(forRemoval = true) public OnDiskKey() { - md = null; + this.classId = CLASS_ID; + this.codec = null; } /** * Creates a new OnDiskKey. Used by {@link OnDiskKeySerializer}. * - * @param md The state metadata + * @param classId The class ID for the key + * @param codec The codec for the key */ - public OnDiskKey(@NonNull final StateMetadata md) { - this.md = Objects.requireNonNull(md); + public OnDiskKey(final long classId, @Nullable final Codec codec) { + this.classId = classId; + this.codec = codec; } /** * Creates a new OnDiskKey. * - * @param md The state metadata - * @param key The "real" key + * @param classId The class ID for the key + * @param codec The codec for the key + * @param key The "real" key */ - public OnDiskKey(@NonNull final StateMetadata md, @NonNull final K key) { - this(md); - this.key = Objects.requireNonNull(key); + public OnDiskKey(final long classId, @Nullable final Codec codec, @NonNull final K key) { + this(classId, codec); + this.key = requireNonNull(key); } @NonNull @@ -89,8 +97,7 @@ public K getKey() { @Override public long getClassId() { - // SHOULD NOT ALLOW md TO BE NULL, but ConstructableRegistry has foiled me. - return md == null ? CLASS_ID : md.onDiskKeyClassId(); + return classId; } @Override @@ -101,8 +108,7 @@ public int getVersion() { /** Writes the "real" key to the given stream. {@inheritDoc} */ @Override public void serialize(@NonNull final SerializableDataOutputStream out) throws IOException { - final Codec codec; - if ((md == null) || ((codec = md.stateDefinition().keyCodec()) == null)) { + if (codec == null) { throw new IllegalStateException("Cannot serialize on-disk key, null metadata / codec"); } writeToStream(out, codec, key); @@ -110,8 +116,7 @@ public void serialize(@NonNull final SerializableDataOutputStream out) throws IO @Override public void deserialize(@NonNull final SerializableDataInputStream in, int ignored) throws IOException { - final Codec codec; - if ((md == null) || ((codec = md.stateDefinition().keyCodec()) == null)) { + if (codec == null) { throw new IllegalStateException("Cannot deserialize on-disk key, null metadata / codec"); } key = readFromStream(in, codec); diff --git a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/state/merkle/disk/OnDiskKeySerializer.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/merkle/disk/OnDiskKeySerializer.java similarity index 89% rename from hedera-node/hedera-app/src/main/java/com/hedera/node/app/state/merkle/disk/OnDiskKeySerializer.java rename to platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/merkle/disk/OnDiskKeySerializer.java index 9f25a7a3580e..f4c94474bffc 100644 --- a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/state/merkle/disk/OnDiskKeySerializer.java +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/merkle/disk/OnDiskKeySerializer.java @@ -14,9 +14,8 @@ * limitations under the License. */ -package com.hedera.node.app.state.merkle.disk; +package com.swirlds.platform.state.merkle.disk; -import com.hedera.node.app.state.merkle.StateMetadata; import com.hedera.pbj.runtime.Codec; import com.hedera.pbj.runtime.ParseException; import com.hedera.pbj.runtime.io.ReadableSequentialData; @@ -26,6 +25,7 @@ import com.swirlds.merkledb.serialize.KeySerializer; import com.swirlds.virtualmap.VirtualMap; import edu.umd.cs.findbugs.annotations.NonNull; +import edu.umd.cs.findbugs.annotations.Nullable; import java.io.IOException; import java.util.Objects; @@ -54,29 +54,29 @@ public final class OnDiskKeySerializer implements KeySerializer> // Serializer version private static final int VERSION = 1; - private final long classId; + private final long serializerClassId; + private final long keyClassId; private final Codec codec; - private final StateMetadata md; // Default constructor provided for ConstructableRegistry, TO BE REMOVED ASAP @Deprecated(forRemoval = true) public OnDiskKeySerializer() { - classId = CLASS_ID; // BAD!! + serializerClassId = CLASS_ID; // BAD!! + keyClassId = 0; // invalid class id codec = null; - md = null; } - public OnDiskKeySerializer(@NonNull final StateMetadata md) { - this.classId = md.onDiskKeySerializerClassId(); - this.md = Objects.requireNonNull(md); - this.codec = md.stateDefinition().keyCodec(); + public OnDiskKeySerializer(long serializerClassId, long keyClassId, @Nullable final Codec codec) { + this.serializerClassId = serializerClassId; + this.keyClassId = keyClassId; + this.codec = codec; } // Serializer info @Override public long getClassId() { - return classId; + return serializerClassId; } @Override @@ -122,6 +122,7 @@ public int getSerializedSize() { @Override public int getSerializedSize(@NonNull final OnDiskKey key) { + assert codec != null; return codec.measureRecord(key.getKey()); } @@ -134,6 +135,7 @@ public int getTypicalSerializedSize() { @Override public void serialize(@NonNull final OnDiskKey key, final @NonNull WritableSequentialData out) { + assert codec != null; // Future work: https://github.com/hashgraph/pbj/issues/73 try { codec.write(key.getKey(), out); @@ -146,11 +148,12 @@ public void serialize(@NonNull final OnDiskKey key, final @NonNull WritableSe @Override public OnDiskKey deserialize(@NonNull final ReadableSequentialData in) { + assert codec != null; // Future work: https://github.com/hashgraph/pbj/issues/73 try { final K k = codec.parse(in); Objects.requireNonNull(k); - return new OnDiskKey<>(md, k); + return new OnDiskKey<>(keyClassId, codec, k); } catch (final ParseException e) { throw new RuntimeException(e); } @@ -158,6 +161,7 @@ public OnDiskKey deserialize(@NonNull final ReadableSequentialData in) { @Override public boolean equals(@NonNull final BufferedData bufferedData, @NonNull final OnDiskKey keyToCompare) { + assert codec != null; // Future work: https://github.com/hashgraph/pbj/issues/73 try { return codec.fastEquals(keyToCompare.getKey(), bufferedData); diff --git a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/state/merkle/disk/OnDiskReadableKVState.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/merkle/disk/OnDiskReadableKVState.java similarity index 74% rename from hedera-node/hedera-app/src/main/java/com/hedera/node/app/state/merkle/disk/OnDiskReadableKVState.java rename to platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/merkle/disk/OnDiskReadableKVState.java index c972b571d54f..6427c30b51f0 100644 --- a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/state/merkle/disk/OnDiskReadableKVState.java +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/merkle/disk/OnDiskReadableKVState.java @@ -14,21 +14,22 @@ * limitations under the License. */ -package com.hedera.node.app.state.merkle.disk; +package com.swirlds.platform.state.merkle.disk; -import static com.hedera.node.app.state.logging.TransactionStateLogger.logMapGet; -import static com.hedera.node.app.state.logging.TransactionStateLogger.logMapGetSize; -import static com.hedera.node.app.state.logging.TransactionStateLogger.logMapIterate; +import static com.swirlds.platform.state.merkle.logging.StateLogger.logMapGet; +import static com.swirlds.platform.state.merkle.logging.StateLogger.logMapGetSize; +import static com.swirlds.platform.state.merkle.logging.StateLogger.logMapIterate; +import static java.util.Objects.requireNonNull; -import com.hedera.node.app.spi.state.ReadableKVState; -import com.hedera.node.app.spi.state.ReadableKVStateBase; -import com.hedera.node.app.state.merkle.StateMetadata; +import com.hedera.pbj.runtime.Codec; +import com.swirlds.platform.state.spi.ReadableKVStateBase; +import com.swirlds.state.spi.ReadableKVState; import com.swirlds.virtualmap.VirtualMap; import com.swirlds.virtualmap.internal.merkle.VirtualLeafNode; import edu.umd.cs.findbugs.annotations.NonNull; +import edu.umd.cs.findbugs.annotations.Nullable; import java.util.Iterator; import java.util.NoSuchElementException; -import java.util.Objects; /** * An implementation of {@link ReadableKVState} backed by a {@link VirtualMap}, resulting in a state @@ -42,25 +43,32 @@ public final class OnDiskReadableKVState extends ReadableKVStateBase /** The backing merkle data structure to use */ private final VirtualMap, OnDiskValue> virtualMap; - private final StateMetadata md; + private final long keyClassId; + private final Codec keyCodec; /** * Create a new instance * - * @param md the state metadata + * @param stateKey + * @param keyClassId + * @param keyCodec * @param virtualMap the backing merkle structure to use */ public OnDiskReadableKVState( - @NonNull final StateMetadata md, @NonNull final VirtualMap, OnDiskValue> virtualMap) { - super(md.stateDefinition().stateKey()); - this.md = md; - this.virtualMap = Objects.requireNonNull(virtualMap); + String stateKey, + final long keyClassId, + @Nullable final Codec keyCodec, + @NonNull final VirtualMap, OnDiskValue> virtualMap) { + super(stateKey); + this.keyClassId = keyClassId; + this.keyCodec = keyCodec; + this.virtualMap = requireNonNull(virtualMap); } /** {@inheritDoc} */ @Override protected V readFromDataSource(@NonNull K key) { - final var k = new OnDiskKey<>(md, key); + final var k = new OnDiskKey<>(keyClassId, keyCodec, key); final var v = virtualMap.get(k); final var value = v == null ? null : v.getValue(); // Log to transaction state log, what was read @@ -119,7 +127,7 @@ public long size() { @Override public void warm(@NonNull final K key) { - final var k = new OnDiskKey<>(md, key); + final var k = new OnDiskKey<>(keyClassId, keyCodec, key); virtualMap.warm(k); } } diff --git a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/state/merkle/disk/OnDiskValue.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/merkle/disk/OnDiskValue.java similarity index 73% rename from hedera-node/hedera-app/src/main/java/com/hedera/node/app/state/merkle/disk/OnDiskValue.java rename to platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/merkle/disk/OnDiskValue.java index 2e49697880cb..5af819b76fe2 100644 --- a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/state/merkle/disk/OnDiskValue.java +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/merkle/disk/OnDiskValue.java @@ -14,19 +14,19 @@ * limitations under the License. */ -package com.hedera.node.app.state.merkle.disk; +package com.swirlds.platform.state.merkle.disk; -import static com.hedera.node.app.state.merkle.StateUtils.readFromStream; -import static com.hedera.node.app.state.merkle.StateUtils.writeToStream; +import static com.swirlds.platform.state.merkle.StateUtils.readFromStream; +import static com.swirlds.platform.state.merkle.StateUtils.writeToStream; +import static java.util.Objects.requireNonNull; -import com.hedera.node.app.state.merkle.StateMetadata; +import com.hedera.pbj.runtime.Codec; import com.swirlds.common.io.streams.SerializableDataInputStream; import com.swirlds.common.io.streams.SerializableDataOutputStream; import com.swirlds.virtualmap.VirtualValue; import edu.umd.cs.findbugs.annotations.NonNull; import edu.umd.cs.findbugs.annotations.Nullable; import java.io.IOException; -import java.util.Objects; /** * A {@link VirtualValue} used for storing the actual value. In our system, a state might have @@ -45,29 +45,31 @@ public class OnDiskValue implements VirtualValue { static final int VERSION = 1; - private final StateMetadata md; + private final long classId; + private final Codec codec; private V value; private boolean immutable = false; // Default constructor is for deserialization public OnDiskValue() { - this.md = null; + this.codec = null; + this.classId = CLASS_ID; } - public OnDiskValue(@NonNull final StateMetadata md) { - this.md = Objects.requireNonNull(md); - Objects.requireNonNull(md.stateDefinition().valueCodec()); + public OnDiskValue(final long classId, @NonNull final Codec codec) { + this.codec = requireNonNull(codec); + this.classId = classId; } - public OnDiskValue(@NonNull final StateMetadata md, @NonNull final V value) { - this(md); - this.value = Objects.requireNonNull(value); + public OnDiskValue(final long classId, @NonNull final Codec codec, @NonNull final V value) { + this(classId, codec); + this.value = requireNonNull(value); } /** {@inheritDoc} */ @Override public VirtualValue copy() { - final var copy = new OnDiskValue<>(md, value); + final var copy = new OnDiskValue<>(classId, requireNonNull(codec), value); this.immutable = true; return copy; } @@ -84,7 +86,7 @@ public VirtualValue asReadOnly() { if (isImmutable()) { return this; } else { - final var copy = new OnDiskValue<>(md, value); + final var copy = new OnDiskValue<>(classId, requireNonNull(codec), value); copy.immutable = true; return copy; } @@ -93,26 +95,25 @@ public VirtualValue asReadOnly() { /** {@inheritDoc} */ @Override public void serialize(@NonNull final SerializableDataOutputStream out) throws IOException { - if (md == null) { + if (codec == null) { throw new IllegalStateException("Cannot serialize on-disk value, null metadata / codec"); } - writeToStream(out, md.stateDefinition().valueCodec(), value); + writeToStream(out, codec, value); } /** {@inheritDoc} */ @Override public void deserialize(@NonNull final SerializableDataInputStream in, int ignored) throws IOException { - if (md == null) { + if (codec == null) { throw new IllegalStateException("Cannot deserialize on-disk value, null metadata / codec"); } - value = readFromStream(in, md.stateDefinition().valueCodec()); + value = readFromStream(in, codec); } /** {@inheritDoc} */ @Override public long getClassId() { - // SHOULD NOT ALLOW md TO BE NULL, but ConstructableRegistry has foiled me. - return md == null ? CLASS_ID : md.onDiskValueClassId(); + return classId; } /** {@inheritDoc} */ @@ -138,6 +139,6 @@ public V getValue() { */ public void setValue(@Nullable final V value) { throwIfImmutable(); - this.value = Objects.requireNonNull(value); + this.value = requireNonNull(value); } } diff --git a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/state/merkle/disk/OnDiskValueSerializer.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/merkle/disk/OnDiskValueSerializer.java similarity index 80% rename from hedera-node/hedera-app/src/main/java/com/hedera/node/app/state/merkle/disk/OnDiskValueSerializer.java rename to platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/merkle/disk/OnDiskValueSerializer.java index e9169ae17674..379bac08e26d 100644 --- a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/state/merkle/disk/OnDiskValueSerializer.java +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/merkle/disk/OnDiskValueSerializer.java @@ -14,9 +14,8 @@ * limitations under the License. */ -package com.hedera.node.app.state.merkle.disk; +package com.swirlds.platform.state.merkle.disk; -import com.hedera.node.app.state.merkle.StateMetadata; import com.hedera.pbj.runtime.Codec; import com.hedera.pbj.runtime.ParseException; import com.hedera.pbj.runtime.io.ReadableSequentialData; @@ -41,28 +40,33 @@ public final class OnDiskValueSerializer implements ValueSerializer md; + private final long serializerClassId; + private final long valueClassId; + private final Codec codec; // Default constructor provided for ConstructableRegistry, TO BE REMOVED ASAP @Deprecated(forRemoval = true) public OnDiskValueSerializer() { - md = null; + codec = null; + serializerClassId = CLASS_ID; + valueClassId = 0; // invalid class id } /** * Create a new instance. This is created at registration time, it doesn't need to serialize * anything to disk. */ - public OnDiskValueSerializer(@NonNull final StateMetadata md) { - this.md = Objects.requireNonNull(md); + public OnDiskValueSerializer(final long serializerClassId, final long valueClassId, @NonNull final Codec codec) { + this.codec = Objects.requireNonNull(codec); + this.serializerClassId = serializerClassId; + this.valueClassId = valueClassId; } // Serializer info @Override public long getClassId() { - // SHOULD NOT ALLOW md TO BE NULL, but ConstructableRegistry has foiled me. - return md == null ? CLASS_ID : md.onDiskValueSerializerClassId(); + return serializerClassId; } @Override @@ -86,8 +90,7 @@ public int getSerializedSize() { @Override public int getSerializedSize(OnDiskValue value) { - assert md != null; - final Codec codec = md.stateDefinition().valueCodec(); + assert codec != null; return codec.measureRecord(value.getValue()); } @@ -98,8 +101,7 @@ public int getTypicalSerializedSize() { @Override public void serialize(@NonNull final OnDiskValue value, @NonNull final WritableSequentialData out) { - assert md != null; - final Codec codec = md.stateDefinition().valueCodec(); + assert codec != null; // Future work: https://github.com/hashgraph/pbj/issues/73 try { codec.write(value.getValue(), out); @@ -112,12 +114,11 @@ public void serialize(@NonNull final OnDiskValue value, @NonNull final Writab @Override public OnDiskValue deserialize(@NonNull final ReadableSequentialData in) { - assert md != null; - final Codec codec = md.stateDefinition().valueCodec(); + assert codec != null; // Future work: https://github.com/hashgraph/pbj/issues/73 try { final V value = codec.parse(in); - return new OnDiskValue<>(md, value); + return new OnDiskValue<>(valueClassId, codec, value); } catch (final ParseException e) { throw new RuntimeException(e); } diff --git a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/state/merkle/disk/OnDiskWritableKVState.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/merkle/disk/OnDiskWritableKVState.java similarity index 63% rename from hedera-node/hedera-app/src/main/java/com/hedera/node/app/state/merkle/disk/OnDiskWritableKVState.java rename to platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/merkle/disk/OnDiskWritableKVState.java index 4f5bcfe17e53..cdeb7bb8df6f 100644 --- a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/state/merkle/disk/OnDiskWritableKVState.java +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/merkle/disk/OnDiskWritableKVState.java @@ -14,21 +14,22 @@ * limitations under the License. */ -package com.hedera.node.app.state.merkle.disk; +package com.swirlds.platform.state.merkle.disk; -import static com.hedera.node.app.state.logging.TransactionStateLogger.logMapGet; -import static com.hedera.node.app.state.logging.TransactionStateLogger.logMapGetForModify; -import static com.hedera.node.app.state.logging.TransactionStateLogger.logMapGetSize; -import static com.hedera.node.app.state.logging.TransactionStateLogger.logMapPut; -import static com.hedera.node.app.state.logging.TransactionStateLogger.logMapRemove; +import static com.swirlds.platform.state.merkle.logging.StateLogger.logMapGet; +import static com.swirlds.platform.state.merkle.logging.StateLogger.logMapGetForModify; +import static com.swirlds.platform.state.merkle.logging.StateLogger.logMapGetSize; +import static com.swirlds.platform.state.merkle.logging.StateLogger.logMapPut; +import static com.swirlds.platform.state.merkle.logging.StateLogger.logMapRemove; import static java.util.Objects.requireNonNull; -import com.hedera.node.app.spi.metrics.StoreMetrics; -import com.hedera.node.app.spi.state.WritableKVState; -import com.hedera.node.app.spi.state.WritableKVStateBase; -import com.hedera.node.app.state.merkle.StateMetadata; +import com.hedera.pbj.runtime.Codec; +import com.swirlds.platform.state.spi.WritableKVStateBase; +import com.swirlds.state.spi.WritableKVState; +import com.swirlds.state.spi.metrics.StoreMetrics; import com.swirlds.virtualmap.VirtualMap; import edu.umd.cs.findbugs.annotations.NonNull; +import edu.umd.cs.findbugs.annotations.Nullable; import java.util.Iterator; /** @@ -42,27 +43,42 @@ public final class OnDiskWritableKVState extends WritableKVStateBase /** The backing merkle data structure */ private final VirtualMap, OnDiskValue> virtualMap; - private final StateMetadata md; + private final Codec keyCodec; + private final long keyClassId; + private final Codec valueCodec; + private final long valueClassId; private StoreMetrics storeMetrics; /** * Create a new instance * - * @param md the state metadata - * @param virtualMap the backing merkle data structure to use + * @param stateKey the state key + * @param keyClassId the class ID for the key + * @param keyCodec the codec for the key + * @param valueClassId the class ID for the value + * @param valueCodec the codec for the value + * @param virtualMap the backing merkle data structure to use */ public OnDiskWritableKVState( - @NonNull final StateMetadata md, @NonNull final VirtualMap, OnDiskValue> virtualMap) { - super(md.stateDefinition().stateKey()); - this.md = md; + String stateKey, + final long keyClassId, + @Nullable final Codec keyCodec, + final long valueClassId, + @NonNull final Codec valueCodec, + @NonNull final VirtualMap, OnDiskValue> virtualMap) { + super(stateKey); + this.keyClassId = keyClassId; + this.keyCodec = keyCodec; + this.valueClassId = valueClassId; + this.valueCodec = valueCodec; this.virtualMap = requireNonNull(virtualMap); } /** {@inheritDoc} */ @Override protected V readFromDataSource(@NonNull K key) { - final var k = new OnDiskKey<>(md, key); + final var k = new OnDiskKey<>(keyClassId, keyCodec, key); final var v = virtualMap.get(k); final var value = v == null ? null : v.getValue(); // Log to transaction state log, what was read @@ -80,7 +96,7 @@ protected Iterator iterateFromDataSource() { /** {@inheritDoc} */ @Override protected V getForModifyFromDataSource(@NonNull K key) { - final var k = new OnDiskKey<>(md, key); + final var k = new OnDiskKey<>(keyClassId, keyCodec, key); final var v = virtualMap.getForModify(k); final var value = v == null ? null : v.getValue(); // Log to transaction state log, what was read @@ -91,12 +107,12 @@ protected V getForModifyFromDataSource(@NonNull K key) { /** {@inheritDoc} */ @Override protected void putIntoDataSource(@NonNull K key, @NonNull V value) { - final var k = new OnDiskKey<>(md, key); + final var k = new OnDiskKey<>(keyClassId, keyCodec, key); final var existing = virtualMap.getForModify(k); if (existing != null) { existing.setValue(value); } else { - virtualMap.put(k, new OnDiskValue<>(md, value)); + virtualMap.put(k, new OnDiskValue<>(valueClassId, valueCodec, value)); } // Log to transaction state log, what was put logMapPut(getStateKey(), key, value); @@ -105,7 +121,7 @@ protected void putIntoDataSource(@NonNull K key, @NonNull V value) { /** {@inheritDoc} */ @Override protected void removeFromDataSource(@NonNull K key) { - final var k = new OnDiskKey<>(md, key); + final var k = new OnDiskKey<>(keyClassId, keyCodec, key); final var removed = virtualMap.remove(k); // Log to transaction state log, what was removed logMapRemove(getStateKey(), key, removed); diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/merkle/logging/StateLogger.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/merkle/logging/StateLogger.java new file mode 100644 index 000000000000..4282a2d81b65 --- /dev/null +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/merkle/logging/StateLogger.java @@ -0,0 +1,329 @@ +/* + * Copyright (C) 2024 Hedera Hashgraph, LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.swirlds.platform.state.merkle.logging; + +import static com.swirlds.platform.eventhandling.ConsensusRoundHandler.TRANSACTION_HANDLING_THREAD_NAME; + +import com.hedera.hapi.node.base.AccountID; +import com.hedera.hapi.node.base.FileID; +import com.hedera.hapi.node.base.ScheduleID; +import com.hedera.hapi.node.base.TokenID; +import com.hedera.hapi.node.base.TopicID; +import com.swirlds.fcqueue.FCQueue; +import com.swirlds.platform.state.merkle.disk.OnDiskKey; +import com.swirlds.platform.state.merkle.disk.OnDiskValue; +import com.swirlds.platform.state.merkle.memory.InMemoryKey; +import com.swirlds.platform.state.merkle.memory.InMemoryValue; +import com.swirlds.platform.state.merkle.singleton.ValueLeaf; +import com.swirlds.virtualmap.VirtualMap; +import com.swirlds.virtualmap.internal.merkle.VirtualLeafNode; +import edu.umd.cs.findbugs.annotations.NonNull; +import edu.umd.cs.findbugs.annotations.Nullable; +import java.util.Set; +import java.util.Spliterator; +import java.util.Spliterators; +import java.util.stream.Collectors; +import java.util.stream.StreamSupport; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +/** + * This utility class provides convenient methods for logging state operations for different types of state types. + */ +public class StateLogger { + /** The logger we are using for the State log */ + private static final Logger logger = LogManager.getLogger(StateLogger.class); + + /** + * Log the read of a singleton. + * + * @param label The label of the singleton + * @param value The value of the singleton + * @param The type of the singleton + */ + public static void logSingletonRead(@NonNull final String label, @Nullable final ValueLeaf value) { + if (logger.isDebugEnabled() && Thread.currentThread().getName().equals(TRANSACTION_HANDLING_THREAD_NAME)) { + logger.debug(" READ singleton {} value {}", label, value == null ? "null" : value.getValue()); + } + } + + /** + * Log when the value of a singleton is written. + * + * @param label The label of the singleton + * @param value The value of the singleton + */ + public static void logSingletonWrite(@NonNull final String label, @Nullable final Object value) { + if (logger.isDebugEnabled() && Thread.currentThread().getName().equals(TRANSACTION_HANDLING_THREAD_NAME)) { + logger.debug(" WRITTEN singleton {} value {}", label, value == null ? "null" : value.toString()); + } + } + + /** + * Log when a value is added to a queue. + * + * @param label The label of the queue + * @param value The value added to the queue + */ + public static void logQueueAdd(@NonNull final String label, @Nullable final Object value) { + if (logger.isDebugEnabled() && Thread.currentThread().getName().equals(TRANSACTION_HANDLING_THREAD_NAME)) { + logger.debug(" ADD to queue {} value {}", label, value == null ? "null" : value.toString()); + } + } + + /** + * Log when a value is removed from a queue. + * + * @param label The label of the queue + * @param value The value removed from the queue + */ + public static void logQueueRemove(@NonNull final String label, @Nullable final Object value) { + if (logger.isDebugEnabled() && Thread.currentThread().getName().equals(TRANSACTION_HANDLING_THREAD_NAME)) { + logger.debug(" REMOVE from queue {} value {}", label, value == null ? "null" : value.toString()); + } + } + + /** + * Log when queue value is peeked. + * + * @param label The label of the queue + * @param value The value peeked from the queue + */ + public static void logQueuePeek(@NonNull final String label, @Nullable final Object value) { + if (logger.isDebugEnabled() && Thread.currentThread().getName().equals(TRANSACTION_HANDLING_THREAD_NAME)) { + logger.debug(" PEEK on queue {} value {}", label, value == null ? "null" : value.toString()); + } + } + + /** + * Log the iteration over a queue. + * + * @param label The label of the queue + * @param queue The queue that was iterated + * @param The type of the queue values + */ + public static void logQueueIterate(@NonNull final String label, @NonNull final FCQueue> queue) { + if (logger.isDebugEnabled() && Thread.currentThread().getName().equals(TRANSACTION_HANDLING_THREAD_NAME)) { + if (queue.isEmpty()) { + logger.debug(" ITERATE queue {} size 0 values:EMPTY", label); + } else { + logger.debug( + " ITERATE queue {} size {} values:\n{}", + label, + queue.size(), + queue.stream() + .map(leaf -> leaf == null ? "null" : leaf.toString()) + .collect(Collectors.joining(",\n"))); + } + } + } + + /** + * Log the put of an entry in to a map. + * + * @param label The label of the map + * @param key The key added to the map + * @param value The value added to the map + * @param The type of the key + * @param The type of the value + */ + public static void logMapPut(@NonNull final String label, @NonNull final K key, @Nullable final V value) { + if (logger.isDebugEnabled() && Thread.currentThread().getName().equals(TRANSACTION_HANDLING_THREAD_NAME)) { + logger.debug( + " PUT into map {} key {} value {}", + label, + formatKey(key), + value == null ? "null" : value.toString()); + } + } + + /** + * Log the removal of an entry from a map. + * + * @param label The label of the map + * @param key The key removed to the map + * @param value The value removed to the map + * @param The type of the key + * @param The type of the value + */ + public static void logMapRemove( + @NonNull final String label, @NonNull final K key, @Nullable final InMemoryValue value) { + if (logger.isDebugEnabled() && Thread.currentThread().getName().equals(TRANSACTION_HANDLING_THREAD_NAME)) { + logger.debug( + " REMOVE from map {} key {} removed value {}", + label, + formatKey(key), + value == null ? "null" : value.toString()); + } + } + + /** + * Log the removal of an entry from a map. + * + * @param label The label of the map + * @param key The key removed to the map + * @param value The value removed to the map + * @param The type of the key + * @param The type of the value + */ + public static void logMapRemove( + @NonNull final String label, @NonNull final K key, @Nullable final OnDiskValue value) { + if (logger.isDebugEnabled() && Thread.currentThread().getName().equals(TRANSACTION_HANDLING_THREAD_NAME)) { + logger.debug( + " REMOVE from map {} key {} removed value {}", + label, + formatKey(key), + value == null ? "null" : value.toString()); + } + } + + /** + * Log the fetching of the size of a map. + * + * @param label The label of the map + * @param size The size of the map + */ + public static void logMapGetSize(@NonNull final String label, final long size) { + if (logger.isDebugEnabled() && Thread.currentThread().getName().equals(TRANSACTION_HANDLING_THREAD_NAME)) { + logger.debug(" GET_SIZE on map {} size {}", label, size); + } + } + + /** + * Log the get of an entry from a map. + * + * @param label The label of the map + * @param key The key fetched to the map + * @param value The value fetched to the map + * @param The type of the key + * @param The type of the value + */ + public static void logMapGet(@NonNull final String label, @NonNull final K key, @Nullable final V value) { + if (logger.isDebugEnabled() && Thread.currentThread().getName().equals(TRANSACTION_HANDLING_THREAD_NAME)) { + logger.debug( + " GET on map {} key {} value {}", + label, + formatKey(key), + value == null ? "null" : value.toString()); + } + } + + /** + * Log the get of an entry from a map for modification. + * + * @param label The label of the map + * @param key The key fetched to the map + * @param value The value fetched to the map + * @param The type of the key + * @param The type of the value + */ + public static void logMapGetForModify( + @NonNull final String label, @NonNull final K key, @Nullable final V value) { + if (logger.isDebugEnabled() && Thread.currentThread().getName().equals(TRANSACTION_HANDLING_THREAD_NAME)) { + logger.debug( + " GET_FOR_MODIFY on map {} key {} value {}", + label, + formatKey(key), + value == null ? "null" : value.toString()); + } + } + + /** + * Log the iteration of keys of a map. + * + * @param label The label of the map + * @param keySet The set of keys of the map + * @param The type of the key + */ + public static void logMapIterate(@NonNull final String label, @NonNull final Set> keySet) { + if (logger.isDebugEnabled() && Thread.currentThread().getName().equals(TRANSACTION_HANDLING_THREAD_NAME)) { + final long size = keySet.size(); + if (size == 0) { + logger.debug(" ITERATE map {} size 0 keys:EMPTY", label); + } else { + logger.debug( + " ITERATE map {} size {} keys:\n{}", + label, + size, + keySet.stream() + .map(InMemoryKey::key) + .map(StateLogger::formatKey) + .collect(Collectors.joining(",\n"))); + } + } + } + + /** + * Log the iteration of values of a map. + * + * @param label The label of the map + * @param virtualMap The map that was iterated + * @param The type of the key + * @param The type of the value + */ + public static void logMapIterate( + @NonNull final String label, @NonNull final VirtualMap, OnDiskValue> virtualMap) { + if (logger.isDebugEnabled()) { + final var spliterator = Spliterators.spliterator( + virtualMap.treeIterator(), virtualMap.size(), Spliterator.SIZED & Spliterator.ORDERED); + final long size = virtualMap.size(); + if (size == 0) { + logger.debug(" ITERATE map {} size 0 keys:EMPTY", label); + } else { + logger.debug( + " ITERATE map {} size {} keys:\n{}", + label, + size, + StreamSupport.stream(spliterator, false) + .map(merkleNode -> { + if (merkleNode instanceof VirtualLeafNode leaf) { + final var k = leaf.getKey(); + if (k instanceof OnDiskKey onDiskKey) { + return onDiskKey.getKey().toString(); + } + } + return "Unknown_Type"; + }) + .collect(Collectors.joining(",\n"))); + } + } + } + + /** + * Format entity id keys in form 0.0.123 rather than default toString() long form. + * + * @param key The key to format + * @return The formatted key + * @param The type of the key + */ + public static String formatKey(@Nullable final K key) { + if (key == null) { + return "null"; + } else if (key instanceof AccountID accountID) { + return accountID.shardNum() + "." + accountID.realmNum() + '.' + accountID.accountNum(); + } else if (key instanceof FileID fileID) { + return fileID.shardNum() + "." + fileID.realmNum() + '.' + fileID.fileNum(); + } else if (key instanceof TokenID tokenID) { + return tokenID.shardNum() + "." + tokenID.realmNum() + '.' + tokenID.tokenNum(); + } else if (key instanceof TopicID topicID) { + return topicID.shardNum() + "." + topicID.realmNum() + '.' + topicID.topicNum(); + } else if (key instanceof ScheduleID scheduleID) { + return scheduleID.shardNum() + "." + scheduleID.realmNum() + '.' + scheduleID.scheduleNum(); + } + return key.toString(); + } +} diff --git a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/state/merkle/memory/InMemoryKey.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/merkle/memory/InMemoryKey.java similarity index 94% rename from hedera-node/hedera-app/src/main/java/com/hedera/node/app/state/merkle/memory/InMemoryKey.java rename to platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/merkle/memory/InMemoryKey.java index df60732f9ea5..09cbe6f0e21f 100644 --- a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/state/merkle/memory/InMemoryKey.java +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/merkle/memory/InMemoryKey.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.hedera.node.app.state.merkle.memory; +package com.swirlds.platform.state.merkle.memory; import com.swirlds.merkle.map.MerkleMap; import edu.umd.cs.findbugs.annotations.NonNull; diff --git a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/state/merkle/memory/InMemoryReadableKVState.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/merkle/memory/InMemoryReadableKVState.java similarity index 80% rename from hedera-node/hedera-app/src/main/java/com/hedera/node/app/state/merkle/memory/InMemoryReadableKVState.java rename to platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/merkle/memory/InMemoryReadableKVState.java index 8d95f642d00a..29714964c1b8 100644 --- a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/state/merkle/memory/InMemoryReadableKVState.java +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/merkle/memory/InMemoryReadableKVState.java @@ -14,14 +14,15 @@ * limitations under the License. */ -package com.hedera.node.app.state.merkle.memory; +package com.swirlds.platform.state.merkle.memory; -import static com.hedera.node.app.state.logging.TransactionStateLogger.*; +import static com.swirlds.platform.state.merkle.logging.StateLogger.logMapGet; +import static com.swirlds.platform.state.merkle.logging.StateLogger.logMapGetSize; +import static com.swirlds.platform.state.merkle.logging.StateLogger.logMapIterate; -import com.hedera.node.app.spi.state.ReadableKVState; -import com.hedera.node.app.spi.state.ReadableKVStateBase; -import com.hedera.node.app.state.merkle.StateMetadata; import com.swirlds.merkle.map.MerkleMap; +import com.swirlds.platform.state.spi.ReadableKVStateBase; +import com.swirlds.state.spi.ReadableKVState; import edu.umd.cs.findbugs.annotations.NonNull; import java.util.Iterator; import java.util.Objects; @@ -42,12 +43,12 @@ public final class InMemoryReadableKVState extends ReadableKVStateBase md, @NonNull MerkleMap, InMemoryValue> merkleMap) { - super(md.stateDefinition().stateKey()); + @NonNull final String stateKey, @NonNull MerkleMap, InMemoryValue> merkleMap) { + super(stateKey); this.merkle = Objects.requireNonNull(merkleMap); } diff --git a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/state/merkle/memory/InMemoryValue.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/merkle/memory/InMemoryValue.java similarity index 72% rename from hedera-node/hedera-app/src/main/java/com/hedera/node/app/state/merkle/memory/InMemoryValue.java rename to platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/merkle/memory/InMemoryValue.java index 893a3a81c81a..07c6ca589eed 100644 --- a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/state/merkle/memory/InMemoryValue.java +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/merkle/memory/InMemoryValue.java @@ -14,12 +14,12 @@ * limitations under the License. */ -package com.hedera.node.app.state.merkle.memory; +package com.swirlds.platform.state.merkle.memory; -import static com.hedera.node.app.state.merkle.StateUtils.readFromStream; -import static com.hedera.node.app.state.merkle.StateUtils.writeToStream; +import static com.swirlds.platform.state.merkle.StateUtils.readFromStream; +import static com.swirlds.platform.state.merkle.StateUtils.writeToStream; -import com.hedera.node.app.state.merkle.StateMetadata; +import com.hedera.pbj.runtime.Codec; import com.swirlds.common.io.SelfSerializable; import com.swirlds.common.io.streams.SerializableDataInputStream; import com.swirlds.common.io.streams.SerializableDataOutputStream; @@ -43,36 +43,51 @@ public final class InMemoryValue extends PartialMerkleLeaf /** The key associated with this value. {@link MerkleMap} requires we do this. */ private InMemoryKey key; - private final StateMetadata md; + private final Codec keyCodec; + private final Codec valueCodec; + + private final long classId; /** The actual value. For example, it could be an Account or SmartContract. */ private V val; // Default constructor provided for ConstructableRegistry, TO BE REMOVED ASAP @Deprecated(forRemoval = true) public InMemoryValue() { - md = null; + classId = CLASS_ID; + keyCodec = null; + valueCodec = null; } /** * Used by the deserialization system to create an {@link InMemoryValue} that does not yet have * a value. Normally this should not be used. * - * @param md The state metadata + * @param classId + * @param keyCodec + * @param valueCodec */ - public InMemoryValue(@NonNull final StateMetadata md) { - this.md = Objects.requireNonNull(md); + public InMemoryValue(final long classId, @Nullable Codec keyCodec, @NonNull Codec valueCodec) { + this.classId = classId; + this.keyCodec = keyCodec; + this.valueCodec = valueCodec; } /** * Create a new instance with the given value. * - * @param md The state metadata + * @param classId value class ID + * @param keyCodec key codec + * @param valueCodec value codec * @param key The associated key. * @param value The value. */ public InMemoryValue( - @NonNull final StateMetadata md, @NonNull final InMemoryKey key, @NonNull final V value) { - this(md); + final long classId, + @Nullable Codec keyCodec, + @NonNull Codec valueCodec, + @NonNull final InMemoryKey key, + @NonNull final V value) { + this(classId, keyCodec, valueCodec); this.key = Objects.requireNonNull(key); this.val = Objects.requireNonNull(value); } @@ -83,7 +98,7 @@ public InMemoryValue copy() { throwIfImmutable(); throwIfDestroyed(); - final var cp = new InMemoryValue<>(md, key, val); + final var cp = new InMemoryValue<>(classId, keyCodec, valueCodec, key, val); setImmutable(true); return cp; } @@ -91,8 +106,7 @@ public InMemoryValue copy() { /** {@inheritDoc} */ @Override public long getClassId() { - // Null `md` for ConstructableRegistry, TO BE REMOVED ASAP - return md == null ? CLASS_ID : md.inMemoryValueClassId(); + return classId; } /** {@inheritDoc} */ @@ -137,19 +151,16 @@ public void setValue(@Nullable final V value) { /** {@inheritDoc} */ @Override public void deserialize(@NonNull final SerializableDataInputStream in, final int ignored) throws IOException { - final var keySerdes = md.stateDefinition().keyCodec(); - final var valueSerdes = md.stateDefinition().valueCodec(); - final var k = readFromStream(in, keySerdes); + assert keyCodec != null; + final var k = readFromStream(in, keyCodec); this.key = new InMemoryKey<>(k); - this.val = readFromStream(in, valueSerdes); + this.val = readFromStream(in, valueCodec); } /** {@inheritDoc} */ @Override public void serialize(@NonNull final SerializableDataOutputStream out) throws IOException { - final var keySerdes = md.stateDefinition().keyCodec(); - final var valueSerdes = md.stateDefinition().valueCodec(); - writeToStream(out, keySerdes, key.key()); - writeToStream(out, valueSerdes, val); + writeToStream(out, keyCodec, key.key()); + writeToStream(out, valueCodec, val); } } diff --git a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/state/merkle/memory/InMemoryWritableKVState.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/merkle/memory/InMemoryWritableKVState.java similarity index 68% rename from hedera-node/hedera-app/src/main/java/com/hedera/node/app/state/merkle/memory/InMemoryWritableKVState.java rename to platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/merkle/memory/InMemoryWritableKVState.java index c09c30c5a393..d3b8c11cc7c6 100644 --- a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/state/merkle/memory/InMemoryWritableKVState.java +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/merkle/memory/InMemoryWritableKVState.java @@ -14,15 +14,21 @@ * limitations under the License. */ -package com.hedera.node.app.state.merkle.memory; +package com.swirlds.platform.state.merkle.memory; -import static com.hedera.node.app.state.logging.TransactionStateLogger.*; +import static com.swirlds.platform.state.merkle.logging.StateLogger.logMapGet; +import static com.swirlds.platform.state.merkle.logging.StateLogger.logMapGetForModify; +import static com.swirlds.platform.state.merkle.logging.StateLogger.logMapGetSize; +import static com.swirlds.platform.state.merkle.logging.StateLogger.logMapIterate; +import static com.swirlds.platform.state.merkle.logging.StateLogger.logMapPut; +import static com.swirlds.platform.state.merkle.logging.StateLogger.logMapRemove; -import com.hedera.node.app.spi.state.WritableKVState; -import com.hedera.node.app.spi.state.WritableKVStateBase; -import com.hedera.node.app.state.merkle.StateMetadata; +import com.hedera.pbj.runtime.Codec; import com.swirlds.merkle.map.MerkleMap; +import com.swirlds.platform.state.spi.WritableKVStateBase; +import com.swirlds.state.spi.WritableKVState; import edu.umd.cs.findbugs.annotations.NonNull; +import edu.umd.cs.findbugs.annotations.Nullable; import java.util.Iterator; import java.util.Objects; @@ -38,18 +44,30 @@ public final class InMemoryWritableKVState extends WritableKVStateBase, InMemoryValue> merkle; - private final StateMetadata md; + private final Codec keyCodec; + private final Codec valueCodec; + + private final long inMemoryValueClassId; /** * Create a new instance. * - * @param md the state metadata + * @param stateKey the state key + * @param inMemoryValueClassId the class ID for the value + * @param keyCodec the codec for the key + * @param valueCodec the codec for the value * @param merkleMap The backing merkle map */ public InMemoryWritableKVState( - @NonNull final StateMetadata md, @NonNull MerkleMap, InMemoryValue> merkleMap) { - super(md.stateDefinition().stateKey()); - this.md = md; + @NonNull final String stateKey, + final long inMemoryValueClassId, + @Nullable Codec keyCodec, + @NonNull Codec valueCodec, + @NonNull MerkleMap, InMemoryValue> merkleMap) { + super(stateKey); + this.keyCodec = keyCodec; + this.valueCodec = valueCodec; + this.inMemoryValueClassId = inMemoryValueClassId; this.merkle = Objects.requireNonNull(merkleMap); } @@ -89,7 +107,7 @@ protected void putIntoDataSource(@NonNull K key, @NonNull V value) { if (existing != null) { existing.setValue(value); } else { - merkle.put(k, new InMemoryValue<>(md, k, value)); + merkle.put(k, new InMemoryValue<>(inMemoryValueClassId, keyCodec, valueCodec, k, value)); } // Log to transaction state log, what was put logMapPut(getStateKey(), key, value); diff --git a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/state/merkle/queue/QueueNode.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/merkle/queue/QueueNode.java similarity index 69% rename from hedera-node/hedera-app/src/main/java/com/hedera/node/app/state/merkle/queue/QueueNode.java rename to platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/merkle/queue/QueueNode.java index 1951e17d9899..58ce88e004c9 100644 --- a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/state/merkle/queue/QueueNode.java +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/merkle/queue/QueueNode.java @@ -14,19 +14,23 @@ * limitations under the License. */ -package com.hedera.node.app.state.merkle.queue; +package com.swirlds.platform.state.merkle.queue; -import static com.hedera.node.app.state.logging.TransactionStateLogger.*; +import static com.swirlds.platform.state.merkle.logging.StateLogger.logQueueAdd; +import static com.swirlds.platform.state.merkle.logging.StateLogger.logQueueIterate; +import static com.swirlds.platform.state.merkle.logging.StateLogger.logQueuePeek; +import static com.swirlds.platform.state.merkle.logging.StateLogger.logQueueRemove; import static java.util.Objects.requireNonNull; -import com.hedera.node.app.state.merkle.StateMetadata; -import com.hedera.node.app.state.merkle.StateUtils; -import com.hedera.node.app.state.merkle.singleton.StringLeaf; -import com.hedera.node.app.state.merkle.singleton.ValueLeaf; +import com.hedera.pbj.runtime.Codec; import com.swirlds.common.merkle.MerkleInternal; import com.swirlds.common.merkle.impl.PartialBinaryMerkleInternal; +import com.swirlds.common.merkle.utility.DebugIterationEndpoint; import com.swirlds.common.utility.Labeled; import com.swirlds.fcqueue.FCQueue; +import com.swirlds.platform.state.merkle.StateUtils; +import com.swirlds.platform.state.merkle.singleton.StringLeaf; +import com.swirlds.platform.state.merkle.singleton.ValueLeaf; import edu.umd.cs.findbugs.annotations.NonNull; import java.util.Iterator; @@ -37,12 +41,18 @@ * * @param The element type */ +@DebugIterationEndpoint public class QueueNode extends PartialBinaryMerkleInternal implements Labeled, MerkleInternal { private static final long CLASS_ID = 0x990FF87AD2691DCL; public static final int CLASS_VERSION = 1; - /** The state metadata, needed for adding new elements */ - private final StateMetadata md; + /** Key codec. */ + private final Codec codec; + + /** Key class id. */ + private final Long queueNodeClassId; + + private final Long leafClassId; /** * @deprecated Only exists for constructable registry as it works today. Remove ASAP! @@ -51,26 +61,36 @@ public class QueueNode extends PartialBinaryMerkleInternal implements Labeled public QueueNode() { setLeft(new StringLeaf()); setRight(null); - this.md = null; + this.codec = null; + this.queueNodeClassId = CLASS_ID; + this.leafClassId = ValueLeaf.CLASS_ID; } /** * Create a new instance. * - * @param md The metadata + * */ - public QueueNode(@NonNull final StateMetadata md) { - setLeft(new StringLeaf( - StateUtils.computeLabel(md.serviceName(), md.stateDefinition().stateKey()))); + public QueueNode( + @NonNull String serviceName, + @NonNull String stateKey, + final long queueNodeClassId, + final long leafClassId, + @NonNull final Codec codec) { + setLeft(new StringLeaf(StateUtils.computeLabel(serviceName, stateKey))); setRight(new FCQueue>()); - this.md = requireNonNull(md); + this.codec = requireNonNull(codec); + this.queueNodeClassId = queueNodeClassId; + this.leafClassId = leafClassId; } /** Copy constructor */ private QueueNode(@NonNull final QueueNode other) { this.setLeft(other.getLeft().copy()); this.setRight(other.getRight().copy()); - this.md = other.md; + this.codec = other.codec; + this.queueNodeClassId = other.queueNodeClassId; + this.leafClassId = other.leafClassId; } @Override @@ -80,7 +100,7 @@ public QueueNode copy() { @Override public long getClassId() { - return md == null ? CLASS_ID : md.queueNodeClassId(); + return queueNodeClassId; } @Override @@ -96,7 +116,7 @@ public String getLabel() { /** Adds an element to this queue. */ public void add(E element) { - getQueue().add(new ValueLeaf<>(md, element)); + getQueue().add(new ValueLeaf<>(leafClassId, codec, element)); // Log to transaction state log, what was added logQueueAdd(getLabel(), element); } diff --git a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/state/merkle/queue/ReadableQueueStateImpl.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/merkle/queue/ReadableQueueStateImpl.java similarity index 78% rename from hedera-node/hedera-app/src/main/java/com/hedera/node/app/state/merkle/queue/ReadableQueueStateImpl.java rename to platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/merkle/queue/ReadableQueueStateImpl.java index b9bd5207f73f..3acb23a026c0 100644 --- a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/state/merkle/queue/ReadableQueueStateImpl.java +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/merkle/queue/ReadableQueueStateImpl.java @@ -14,13 +14,12 @@ * limitations under the License. */ -package com.hedera.node.app.state.merkle.queue; +package com.swirlds.platform.state.merkle.queue; import static java.util.Objects.requireNonNull; -import com.hedera.node.app.spi.state.ReadableQueueState; -import com.hedera.node.app.spi.state.ReadableQueueStateBase; -import com.hedera.node.app.state.merkle.StateMetadata; +import com.swirlds.platform.state.spi.ReadableQueueStateBase; +import com.swirlds.state.spi.ReadableQueueState; import edu.umd.cs.findbugs.annotations.NonNull; import edu.umd.cs.findbugs.annotations.Nullable; import java.util.Iterator; @@ -33,8 +32,8 @@ public class ReadableQueueStateImpl extends ReadableQueueStateBase { private final QueueNode dataSource; /** Create a new instance */ - public ReadableQueueStateImpl(@NonNull final StateMetadata md, @NonNull final QueueNode node) { - super(md.stateDefinition().stateKey()); + public ReadableQueueStateImpl(@NonNull final String stateKey, @NonNull final QueueNode node) { + super(stateKey); this.dataSource = requireNonNull(node); } diff --git a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/state/merkle/queue/WritableQueueStateImpl.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/merkle/queue/WritableQueueStateImpl.java similarity index 75% rename from hedera-node/hedera-app/src/main/java/com/hedera/node/app/state/merkle/queue/WritableQueueStateImpl.java rename to platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/merkle/queue/WritableQueueStateImpl.java index e561c0986ca1..91bbbd6fdf2d 100644 --- a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/state/merkle/queue/WritableQueueStateImpl.java +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/merkle/queue/WritableQueueStateImpl.java @@ -14,24 +14,23 @@ * limitations under the License. */ -package com.hedera.node.app.state.merkle.queue; +package com.swirlds.platform.state.merkle.queue; import static java.util.Objects.requireNonNull; -import com.hedera.node.app.spi.state.WritableQueueStateBase; -import com.hedera.node.app.state.merkle.StateMetadata; +import com.swirlds.platform.state.spi.WritableQueueStateBase; import edu.umd.cs.findbugs.annotations.NonNull; import java.util.Iterator; /** - * An implementation of {@link com.hedera.node.app.spi.state.WritableQueueState} based on {@link QueueNode}. + * An implementation of {@link com.swirlds.state.spi.WritableQueueState} based on {@link QueueNode}. * @param The type of element in the queue */ public class WritableQueueStateImpl extends WritableQueueStateBase { private final QueueNode dataSource; - public WritableQueueStateImpl(@NonNull final StateMetadata md, @NonNull final QueueNode node) { - super(md.stateDefinition().stateKey()); + public WritableQueueStateImpl(@NonNull final String stateKey, @NonNull final QueueNode node) { + super(stateKey); this.dataSource = requireNonNull(node); } diff --git a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/state/merkle/singleton/ReadableSingletonStateImpl.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/merkle/singleton/ReadableSingletonStateImpl.java similarity index 68% rename from hedera-node/hedera-app/src/main/java/com/hedera/node/app/state/merkle/singleton/ReadableSingletonStateImpl.java rename to platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/merkle/singleton/ReadableSingletonStateImpl.java index 02203c8ef0ef..786dd2528919 100644 --- a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/state/merkle/singleton/ReadableSingletonStateImpl.java +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/merkle/singleton/ReadableSingletonStateImpl.java @@ -14,14 +14,13 @@ * limitations under the License. */ -package com.hedera.node.app.state.merkle.singleton; +package com.swirlds.platform.state.merkle.singleton; -import com.hedera.node.app.spi.state.ReadableSingletonStateBase; -import com.hedera.node.app.state.merkle.StateMetadata; +import com.swirlds.platform.state.spi.ReadableSingletonStateBase; import edu.umd.cs.findbugs.annotations.NonNull; public class ReadableSingletonStateImpl extends ReadableSingletonStateBase { - public ReadableSingletonStateImpl(@NonNull final StateMetadata md, @NonNull final SingletonNode node) { - super(md.stateDefinition().stateKey(), node::getValue); + public ReadableSingletonStateImpl(@NonNull final String stateKey, @NonNull final SingletonNode node) { + super(stateKey, node::getValue); } } diff --git a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/state/merkle/singleton/SingletonNode.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/merkle/singleton/SingletonNode.java similarity index 75% rename from hedera-node/hedera-app/src/main/java/com/hedera/node/app/state/merkle/singleton/SingletonNode.java rename to platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/merkle/singleton/SingletonNode.java index 96a4be8297e4..70f24ce5833b 100644 --- a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/state/merkle/singleton/SingletonNode.java +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/merkle/singleton/SingletonNode.java @@ -14,17 +14,19 @@ * limitations under the License. */ -package com.hedera.node.app.state.merkle.singleton; +package com.swirlds.platform.state.merkle.singleton; -import static com.hedera.node.app.state.logging.TransactionStateLogger.logSingletonRead; -import static com.hedera.node.app.state.logging.TransactionStateLogger.logSingletonWrite; +import static com.swirlds.platform.state.merkle.logging.StateLogger.logSingletonRead; +import static com.swirlds.platform.state.merkle.logging.StateLogger.logSingletonWrite; -import com.hedera.node.app.state.merkle.StateMetadata; -import com.hedera.node.app.state.merkle.StateUtils; +import com.hedera.pbj.runtime.Codec; import com.swirlds.common.merkle.MerkleInternal; import com.swirlds.common.merkle.impl.PartialBinaryMerkleInternal; +import com.swirlds.common.merkle.utility.DebugIterationEndpoint; import com.swirlds.common.utility.Labeled; +import com.swirlds.platform.state.merkle.StateUtils; import edu.umd.cs.findbugs.annotations.NonNull; +import edu.umd.cs.findbugs.annotations.Nullable; /** * A merkle node with a string (the label) as the left child, and the merkle node value as the right @@ -33,6 +35,7 @@ * * @param The value type */ +@DebugIterationEndpoint public class SingletonNode extends PartialBinaryMerkleInternal implements Labeled, MerkleInternal { private static final long CLASS_ID = 0x3832CC837AB77BFL; public static final int CLASS_VERSION = 1; @@ -46,10 +49,14 @@ public SingletonNode() { setRight(null); } - public SingletonNode(@NonNull final StateMetadata md, @NonNull final T value) { - setLeft(new StringLeaf( - StateUtils.computeLabel(md.serviceName(), md.stateDefinition().stateKey()))); - setRight(new ValueLeaf(md, value)); + public SingletonNode( + @NonNull final String serviceName, + @NonNull final String stateKey, + long classId, + @NonNull final Codec codec, + @Nullable final T value) { + setLeft(new StringLeaf(StateUtils.computeLabel(serviceName, stateKey))); + setRight(new ValueLeaf<>(classId, codec, value)); } private SingletonNode(@NonNull final SingletonNode other) { diff --git a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/state/merkle/singleton/StringLeaf.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/merkle/singleton/StringLeaf.java similarity index 98% rename from hedera-node/hedera-app/src/main/java/com/hedera/node/app/state/merkle/singleton/StringLeaf.java rename to platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/merkle/singleton/StringLeaf.java index b49943ddb724..eeaeea8be903 100644 --- a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/state/merkle/singleton/StringLeaf.java +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/merkle/singleton/StringLeaf.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.hedera.node.app.state.merkle.singleton; +package com.swirlds.platform.state.merkle.singleton; import com.swirlds.common.io.streams.SerializableDataInputStream; import com.swirlds.common.io.streams.SerializableDataOutputStream; diff --git a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/state/merkle/singleton/ValueLeaf.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/merkle/singleton/ValueLeaf.java similarity index 76% rename from hedera-node/hedera-app/src/main/java/com/hedera/node/app/state/merkle/singleton/ValueLeaf.java rename to platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/merkle/singleton/ValueLeaf.java index fedc33e3edc3..937dc6afda26 100644 --- a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/state/merkle/singleton/ValueLeaf.java +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/merkle/singleton/ValueLeaf.java @@ -14,13 +14,12 @@ * limitations under the License. */ -package com.hedera.node.app.state.merkle.singleton; +package com.swirlds.platform.state.merkle.singleton; -import static com.hedera.node.app.state.merkle.StateUtils.readFromStream; -import static com.hedera.node.app.state.merkle.StateUtils.writeToStream; +import static com.swirlds.platform.state.merkle.StateUtils.readFromStream; +import static com.swirlds.platform.state.merkle.StateUtils.writeToStream; import static java.util.Objects.requireNonNull; -import com.hedera.node.app.state.merkle.StateMetadata; import com.hedera.pbj.runtime.Codec; import com.swirlds.common.io.streams.SerializableDataInputStream; import com.swirlds.common.io.streams.SerializableDataOutputStream; @@ -32,16 +31,16 @@ /** * A Merkle leaf that stores an arbitrary value with delegated serialization based on the {@link - * StateMetadata}. + * #classId}. */ public class ValueLeaf extends PartialMerkleLeaf implements MerkleLeaf { /** * {@deprecated} Needed for ConstructableRegistry, TO BE REMOVED ASAP */ @Deprecated(forRemoval = true) - private static final long CLASS_ID = 0x65A48B28C563D72EL; + public static final long CLASS_ID = 0x65A48B28C563D72EL; - private final StateMetadata md; + private final long classId; private final Codec codec; /** The actual value. For example, it could be an Account or SmartContract. */ private T val; @@ -51,29 +50,31 @@ public class ValueLeaf extends PartialMerkleLeaf implements MerkleLeaf { */ @Deprecated(forRemoval = true) public ValueLeaf() { - md = null; codec = null; + classId = CLASS_ID; } /** * Used by the deserialization system to create an {@link ValueLeaf} that does not yet have a * value. Normally this should not be used. * - * @param md The state metadata + * @param singletonClassId The class ID of the object + * @param codec The codec to use for serialization */ - public ValueLeaf(@NonNull final StateMetadata md) { - this.md = requireNonNull(md); - this.codec = md.stateDefinition().valueCodec(); + public ValueLeaf(final long singletonClassId, @NonNull Codec codec) { + this.codec = requireNonNull(codec); + this.classId = singletonClassId; } /** * Create a new instance with the given value. * - * @param md The state metadata + * @param singletonClassId The class ID of the object + * @param codec The codec to use for serialization * @param value The value. */ - public ValueLeaf(@NonNull final StateMetadata md, @Nullable final T value) { - this(md); + public ValueLeaf(final long singletonClassId, @NonNull Codec codec, @Nullable final T value) { + this(singletonClassId, codec); this.val = value; } @@ -83,7 +84,7 @@ public ValueLeaf copy() { throwIfImmutable(); throwIfDestroyed(); - final var cp = new ValueLeaf<>(md, val); + final var cp = new ValueLeaf<>(classId, codec, val); setImmutable(true); return cp; } @@ -91,8 +92,7 @@ public ValueLeaf copy() { /** {@inheritDoc} */ @Override public long getClassId() { - // Null `md` for ConstructableRegistry, TO BE REMOVED ASAP - return md == null ? CLASS_ID : md.singletonClassId(); + return classId; } /** {@inheritDoc} */ @@ -104,7 +104,7 @@ public int getVersion() { /** {@inheritDoc} */ @Override public void serialize(final SerializableDataOutputStream out) throws IOException { - if (md == null) { + if (codec == null) { throw new IllegalStateException("Metadata is null, meaning this is not a proper object"); } @@ -114,7 +114,7 @@ public void serialize(final SerializableDataOutputStream out) throws IOException /** {@inheritDoc} */ @Override public void deserialize(final SerializableDataInputStream in, final int version) throws IOException { - if (md == null) { + if (codec == null) { throw new IllegalStateException("Metadata is null, meaning this is not a proper object"); } diff --git a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/state/merkle/singleton/WritableSingletonStateImpl.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/merkle/singleton/WritableSingletonStateImpl.java similarity index 67% rename from hedera-node/hedera-app/src/main/java/com/hedera/node/app/state/merkle/singleton/WritableSingletonStateImpl.java rename to platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/merkle/singleton/WritableSingletonStateImpl.java index ef259285c804..c3179c8c84d8 100644 --- a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/state/merkle/singleton/WritableSingletonStateImpl.java +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/merkle/singleton/WritableSingletonStateImpl.java @@ -14,14 +14,13 @@ * limitations under the License. */ -package com.hedera.node.app.state.merkle.singleton; +package com.swirlds.platform.state.merkle.singleton; -import com.hedera.node.app.spi.state.WritableSingletonStateBase; -import com.hedera.node.app.state.merkle.StateMetadata; +import com.swirlds.platform.state.spi.WritableSingletonStateBase; import edu.umd.cs.findbugs.annotations.NonNull; public class WritableSingletonStateImpl extends WritableSingletonStateBase { - public WritableSingletonStateImpl(@NonNull final StateMetadata md, @NonNull final SingletonNode node) { - super(md.stateDefinition().stateKey(), node::getValue, node::setValue); + public WritableSingletonStateImpl(@NonNull final String stateKey, @NonNull final SingletonNode node) { + super(stateKey, node::getValue, node::setValue); } } diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/nexus/DefaultLatestCompleteStateNexus.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/nexus/DefaultLatestCompleteStateNexus.java index 0844b76635f7..b7324f4f2faf 100644 --- a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/nexus/DefaultLatestCompleteStateNexus.java +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/nexus/DefaultLatestCompleteStateNexus.java @@ -18,6 +18,7 @@ import static com.swirlds.metrics.api.Metrics.PLATFORM_CATEGORY; +import com.swirlds.common.context.PlatformContext; import com.swirlds.common.metrics.RunningAverageMetric; import com.swirlds.metrics.api.Metrics; import com.swirlds.platform.config.StateConfig; @@ -26,7 +27,6 @@ import com.swirlds.platform.state.signed.ReservedSignedState; import edu.umd.cs.findbugs.annotations.NonNull; import edu.umd.cs.findbugs.annotations.Nullable; -import java.util.Objects; /** * The default implementation of {@link LatestCompleteStateNexus}. @@ -43,13 +43,12 @@ public class DefaultLatestCompleteStateNexus implements LatestCompleteStateNexus /** * Create a new nexus that holds the latest complete signed state. * - * @param stateConfig the state configuration - * @param metrics the metrics object to update + * @param platformContext the platform context */ - public DefaultLatestCompleteStateNexus(@NonNull final StateConfig stateConfig, @NonNull final Metrics metrics) { - this.stateConfig = Objects.requireNonNull(stateConfig); - Objects.requireNonNull(metrics); + public DefaultLatestCompleteStateNexus(@NonNull final PlatformContext platformContext) { + this.stateConfig = platformContext.getConfiguration().getConfigData(StateConfig.class); + final Metrics metrics = platformContext.getMetrics(); final RunningAverageMetric avgRoundSupermajority = metrics.getOrCreate(AVG_ROUND_SUPERMAJORITY_CONFIG); metrics.addUpdater(() -> avgRoundSupermajority.update(getRound())); } diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/signed/DefaultStateSignatureCollector.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/signed/DefaultStateSignatureCollector.java new file mode 100644 index 000000000000..250c00d8c46c --- /dev/null +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/signed/DefaultStateSignatureCollector.java @@ -0,0 +1,295 @@ +/* + * Copyright (C) 2017-2024 Hedera Hashgraph, LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.swirlds.platform.state.signed; + +import static java.util.stream.Collectors.collectingAndThen; +import static java.util.stream.Collectors.toList; + +import com.hedera.hapi.platform.event.StateSignaturePayload; +import com.swirlds.common.context.PlatformContext; +import com.swirlds.common.crypto.Signature; +import com.swirlds.common.crypto.SignatureType; +import com.swirlds.common.platform.NodeId; +import com.swirlds.common.sequence.set.SequenceSet; +import com.swirlds.common.sequence.set.StandardSequenceSet; +import com.swirlds.logging.legacy.LogMarker; +import com.swirlds.platform.components.transaction.system.ScopedSystemTransaction; +import com.swirlds.platform.config.StateConfig; +import com.swirlds.platform.consensus.ConsensusConstants; +import edu.umd.cs.findbugs.annotations.NonNull; +import edu.umd.cs.findbugs.annotations.Nullable; +import java.time.Duration; +import java.time.Instant; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Optional; +import java.util.stream.Stream; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +/** + * Collects signatures for signed states. This class ensures that all the non-ancient states that are not fully signed + * (as long as they are not too old) are kept so that signatures can be collected for it. This class returns states once + * they are either: + *
      + *
    • fully signed
    • + *
    • too old
    • + *
    + */ +public class DefaultStateSignatureCollector implements StateSignatureCollector { + private static final Logger logger = LogManager.getLogger(DefaultStateSignatureCollector.class); + /** The latest signed state round */ + private long lastStateRound = ConsensusConstants.ROUND_UNDEFINED; + /** Signed states awaiting signatures */ + private final Map incompleteStates = new HashMap<>(); + /** State config */ + private final StateConfig stateConfig; + /** Signatures for rounds in the future */ + private final SequenceSet savedSignatures; + /** A collection of signed state metrics */ + private final SignedStateMetrics signedStateMetrics; + + /** + * Start empty, with no known signed states. A signed state is considered completed when it has signatures from a + * sufficient threshold of nodes. + * + * @param platformContext the platform context + * @param signedStateMetrics a collection of signed state metrics + */ + public DefaultStateSignatureCollector( + @NonNull final PlatformContext platformContext, @NonNull final SignedStateMetrics signedStateMetrics) { + this.stateConfig = platformContext.getConfiguration().getConfigData(StateConfig.class); + this.signedStateMetrics = Objects.requireNonNull(signedStateMetrics); + + this.savedSignatures = + new StandardSequenceSet<>(0, stateConfig.maxAgeOfFutureStateSignatures(), SavedSignature::round); + } + + /** + * {@inheritDoc} + */ + @Override + public @Nullable List addReservedState( + @NonNull final ReservedSignedState reservedSignedState) { + Objects.requireNonNull(reservedSignedState, "reservedSignedState"); + final SignedState signedState = reservedSignedState.get(); + + if (signedState.getState().getHash() == null) { + throw new IllegalArgumentException( + "Unhashed state for round " + signedState.getRound() + " added to the signed state manager"); + } + + // Double check that the signatures on this state are valid. + // They may no longer be valid if we have done a data migration. + signedState.pruneInvalidSignatures(); + + // find any signatures that have been saved + final List signatures = savedSignatures.getEntriesWithSequenceNumber(signedState.getRound()); + savedSignatures.removeSequenceNumber(signedState.getRound()); + signatures.forEach(ss -> addSignature(reservedSignedState, ss.memberId, ss.signature)); + + lastStateRound = Math.max(lastStateRound, signedState.getRound()); + adjustSavedSignaturesWindow(signedState.getRound()); + + // complete states and freeze states will be written immediately to disk + // incomplete non-freeze states will be kept until they are complete or too old + if (!signedState.isComplete() && !signedState.isFreezeState()) { + final ReservedSignedState previousState = incompleteStates.put(signedState.getRound(), reservedSignedState); + if (previousState != null) { + previousState.close(); + logger.warn( + LogMarker.EXCEPTION.getMarker(), + "Two states with the same round ({}) have been added to the signature collector", + signedState.getRound()); + } + return Optional.of(purgeOldStates()).filter(l -> !l.isEmpty()).orElse(null); + } + return Stream.concat(Stream.of(reservedSignedState), purgeOldStates().stream()) + .filter(Objects::nonNull) + .collect(collectingAndThen(toList(), l -> l.isEmpty() ? null : l)); + } + + /** + * {@inheritDoc} + */ + @Override + public @Nullable List handlePreconsensusSignatures( + @NonNull final List> transactions) { + Objects.requireNonNull(transactions, "transactions"); + return transactions.stream() + .map(this::handlePreconsensusSignature) + .filter(Objects::nonNull) + .collect(collectingAndThen(toList(), l -> l.isEmpty() ? null : l)); + } + + private @Nullable ReservedSignedState handlePreconsensusSignature( + @NonNull final ScopedSystemTransaction scopedTransaction) { + + final long round = scopedTransaction.transaction().round(); + final Signature signature = new Signature( + SignatureType.RSA, scopedTransaction.transaction().signature().toByteArray()); + + signedStateMetrics.getStateSignaturesGatheredPerSecondMetric().cycle(); + + if (lastStateRound != -1) { + final long signatureAge = round - lastStateRound; + signedStateMetrics.getStateSignatureAge().update(signatureAge); + } + + final ReservedSignedState reservedState = incompleteStates.get(round); + if (reservedState == null) { + // This round has already been completed, or it is really old or in the future + savedSignatures.add(new SavedSignature(round, scopedTransaction.submitterId(), signature)); + return null; + } + return addSignature(reservedState, scopedTransaction.submitterId(), signature); + } + + /** + * {@inheritDoc} + */ + @Override + public @Nullable List handlePostconsensusSignatures( + @NonNull final List> transactions) { + Objects.requireNonNull(transactions, "transactions"); + return transactions.stream() + .map(this::handlePostconsensusSignature) + .filter(Objects::nonNull) + .collect(collectingAndThen(toList(), l -> l.isEmpty() ? null : l)); + } + + private @Nullable ReservedSignedState handlePostconsensusSignature( + @NonNull final ScopedSystemTransaction scopedTransaction) { + final long round = scopedTransaction.transaction().round(); + + final ReservedSignedState reservedState = incompleteStates.get(round); + // it isn't possible to receive a postconsensus signature transaction for a future round, + // and if we don't have the state for an old round, we never will. + // in both cases, the signature can be ignored + if (reservedState == null) { + return null; + } + + return addSignature( + reservedState, + scopedTransaction.submitterId(), + new Signature( + SignatureType.RSA, + scopedTransaction.transaction().signature().toByteArray())); + } + + /** + * Add a new signature to a signed state. + * + * @param reservedSignedState the state being signed + * @param nodeId the ID of the signer + * @param signature the signature on the state + * @return the signed state if it is now complete, otherwise null + */ + private @Nullable ReservedSignedState addSignature( + @NonNull final ReservedSignedState reservedSignedState, + @NonNull final NodeId nodeId, + @NonNull final Signature signature) { + final SignedState signedState = reservedSignedState.get(); + + if (signedState.addSignature(nodeId, signature)) { + // at this point the signed state is complete for the first time + signedStateMetrics.getStatesSignedPerSecondMetric().cycle(); + signedStateMetrics + .getAverageTimeToFullySignStateMetric() + .update(Duration.between(signedState.getCreationTimestamp(), Instant.now()) + .toMillis()); + + return incompleteStates.remove(signedState.getRound()); + } + return null; + } + + /** + * Get the earliest round that is permitted to be stored in this data structure. + * + * @return the earliest round permitted to be stored + */ + private long getEarliestPermittedRound() { + return lastStateRound - stateConfig.roundsToKeepForSigning() + 1; + } + + /** + * Get rid of old states. + * + * @return a list of states that were purged + */ + private @NonNull List purgeOldStates() { + final List purgedStates = new ArrayList<>(); + + // Any state older than this is unconditionally removed. + final long earliestPermittedRound = getEarliestPermittedRound(); + for (final Iterator iterator = + incompleteStates.values().iterator(); + iterator.hasNext(); ) { + final ReservedSignedState reservedSignedState = iterator.next(); + final SignedState signedState = reservedSignedState.get(); + if (signedState.getRound() < earliestPermittedRound) { + signedStateMetrics.getTotalUnsignedStatesMetric().increment(); + purgedStates.add(reservedSignedState); + iterator.remove(); + } + } + + signedStateMetrics.getUnsignedStatesMetric().update(incompleteStates.size()); + return purgedStates; + } + + /** + * Adjust the window where we are willing to save future signatures. + * + * @param currentRound the round of the most recently signed state + */ + private void adjustSavedSignaturesWindow(final long currentRound) { + // Only save signatures for round N+1 and after. + // Any rounds behind this one will either have already had a SignedState + // added to this manager, or will never have a SignedState added to this manager. + if (savedSignatures.getFirstSequenceNumberInWindow() < currentRound + 1) { + savedSignatures.shiftWindow(currentRound + 1); + } + } + + /** + * {@inheritDoc} + */ + @Override + public void clear(@NonNull final Object ignored) { + for (final Iterator iterator = + incompleteStates.values().iterator(); + iterator.hasNext(); ) { + final ReservedSignedState state = iterator.next(); + state.close(); + iterator.remove(); + } + savedSignatures.clear(); + lastStateRound = ConsensusConstants.ROUND_UNDEFINED; + } + + /** + * A signature that was received when there was no state with a matching round. + */ + private record SavedSignature(long round, @NonNull NodeId memberId, @NonNull Signature signature) {} +} diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/signed/MismatchedNodes.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/signed/MismatchedNodes.java index a62db36213ba..0249000bd0aa 100644 --- a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/signed/MismatchedNodes.java +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/signed/MismatchedNodes.java @@ -81,8 +81,8 @@ public void appendNodeDescriptions(final TextTable table) { nodeAString = nodeA.getClass().getSimpleName(); nodeBString = nodeB.getClass().getSimpleName(); } else { - nodeAString = nodeA.getHash().toShortString(12); - nodeBString = nodeB.getHash().toShortString(12); + nodeAString = nodeA.getHash().toHex(12); + nodeBString = nodeB.getHash().toHex(12); } final String formattedNodeAString = GRAY.apply(nodeAString); diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/signed/SignatureSummary.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/signed/SignatureSummary.java deleted file mode 100644 index 9b478c61b608..000000000000 --- a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/signed/SignatureSummary.java +++ /dev/null @@ -1,29 +0,0 @@ -/* - * Copyright (C) 2016-2024 Hedera Hashgraph, LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.swirlds.platform.state.signed; - -/** - * Summary level data about the signatures on a signed state - * - * @param numTotalSigs - * the total number of signatures - * @param numValidSigs - * the number of valid signatures - * @param validWeight - * the total amount of weight from the valid signatures - */ -public record SignatureSummary(int numTotalSigs, int numValidSigs, long validWeight) {} diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/signed/SignedState.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/signed/SignedState.java index 3041f81bd54c..f99d398f1be2 100644 --- a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/signed/SignedState.java +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/signed/SignedState.java @@ -36,6 +36,7 @@ import com.swirlds.platform.crypto.SignatureVerifier; import com.swirlds.platform.state.State; import com.swirlds.platform.state.signed.SignedStateHistory.SignedStateAction; +import com.swirlds.platform.state.snapshot.StateToDiskReason; import com.swirlds.platform.system.SwirldState; import com.swirlds.platform.system.address.Address; import com.swirlds.platform.system.address.AddressBook; @@ -590,7 +591,7 @@ private boolean isSignatureValid(@Nullable final Address address, @NonNull final } return signatureVerifier.verifySignature( - state.getHash().getValue(), signature.getSignatureBytes(), address.getSigPublicKey()); + state.getHash().getBytes(), signature.getBytes(), address.getSigPublicKey()); } /** diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/signed/SignedStateMap.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/signed/SignedStateMap.java deleted file mode 100644 index 010c3a27a1f4..000000000000 --- a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/signed/SignedStateMap.java +++ /dev/null @@ -1,215 +0,0 @@ -/* - * Copyright (C) 2022-2024 Hedera Hashgraph, LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.swirlds.platform.state.signed; - -import static com.swirlds.platform.state.signed.ReservedSignedState.createNullReservation; - -import com.swirlds.common.threading.locks.AutoClosableLock; -import com.swirlds.common.threading.locks.Locks; -import com.swirlds.common.threading.locks.locked.Locked; -import edu.umd.cs.findbugs.annotations.NonNull; -import java.util.Iterator; -import java.util.Objects; -import java.util.SortedMap; -import java.util.TreeMap; -import java.util.function.Consumer; - -/** - * A thread safe map-like object for storing a number of states. This object automatically manages reservations. - */ -public class SignedStateMap { - - private final SortedMap map = new TreeMap<>(); - private final AutoClosableLock lock = Locks.createAutoLock(); - - /** - * The round returned if there are no states in this map. - */ - public static final long NO_STATE_ROUND = -1; - - /** - * Create a new map for signed states. - */ - public SignedStateMap() { - // Empty Constructor. - } - - /** - * Get a signed state. A reservation is taken on the state before this method returns. - * - * @param round the round to get - * @param reason a short description of why this SignedState is being reserved. Each location where a SignedState is - * reserved should attempt to use a unique reason, as this makes debugging reservation bugs easier. - * @return an auto-closable object that wraps a signed state. May point to a null state if there is no state for the - * given round. Will automatically release the state when closed. - */ - public @NonNull ReservedSignedState getAndReserve(final long round, @NonNull final String reason) { - try (final Locked l = lock.lock()) { - final ReservedSignedState reservedSignedState = map.get(round); - if (reservedSignedState == null) { - return createNullReservation(); - } - return reservedSignedState.getAndReserve(reason); - } - } - - /** - * Get the latest state in this map. A reservation is taken on the state before this method returns. - * - * @param reason a short description of why this SignedState is being reserved. Each location where a SignedState is - * reserved should attempt to use a unique reason, as this makes debugging reservation bugs easier. - * @return an auto-closable object that wraps a signed state. May point to a null state if there is no state for the - * given round. Will automatically release the state when closed. - */ - public @NonNull ReservedSignedState getLatestAndReserve(@NonNull final String reason) { - try (final Locked l = lock.lock()) { - if (map.isEmpty()) { - return createNullReservation(); - } - - final ReservedSignedState reservedSignedState = map.get(map.lastKey()); - if (reservedSignedState == null) { - return createNullReservation(); - } - return reservedSignedState.getAndReserve(reason); - } - } - - /** - * Get the latest round in this map. - * - * @return the latest round in this map, or {@link #NO_STATE_ROUND} if this map is empty - */ - public long getLatestRound() { - try (final Locked l = lock.lock()) { - if (map.isEmpty()) { - return NO_STATE_ROUND; - } - return map.lastKey(); - } - } - - /** - * Check if the map is empty. - * - * @return true if the map is empty, otherwise false. - */ - public boolean isEmpty() { - try (final Locked l = lock.lock()) { - return map.isEmpty(); - } - } - - /** - * Add a signed state to the map. - * - * @param signedState the signed state to add - * @param reason a short description of why this SignedState is being reserved. Each location where a - * SignedState is reserved should attempt to use a unique reason, as this makes debugging - * reservation bugs easier. - */ - public void put(@NonNull final SignedState signedState, @NonNull final String reason) { - Objects.requireNonNull(signedState); - Objects.requireNonNull(reason); - - try (final Locked l = lock.lock()) { - final ReservedSignedState previousState = - map.put(signedState.getRound(), ReservedSignedState.createAndReserve(signedState, reason)); - if (previousState != null) { - previousState.close(); - } - } - } - - /** - * Remove a signed state from the map if it is present. - * - * @param round the round to remove - */ - public void remove(final long round) { - try (final Locked l = lock.lock()) { - final ReservedSignedState reservedSignedState = map.remove(round); - if (reservedSignedState != null) { - reservedSignedState.close(); - } - } - } - - /** - * Remove all signed states from this collection. - */ - public void clear() { - try (final Locked l = lock.lock()) { - for (final ReservedSignedState reservedSignedState : map.values()) { - reservedSignedState.close(); - } - map.clear(); - } - } - - /** - *

    - * While holding a lock, execute a function that operates on an iterator of states in this map. The iterator is - * permitted to remove elements from the map. - *

    - * - *

    - * Using the iterator after this method returns is strictly prohibited. - *

    - * - * @param operation an operation that will use an iterator - */ - public void atomicIteration(@NonNull final Consumer> operation) { - try (final Locked l = lock.lock()) { - final Iterator baseIterator = map.values().iterator(); - - final Iterator iterator = new Iterator<>() { - private ReservedSignedState previous; - - @Override - public boolean hasNext() { - return baseIterator.hasNext(); - } - - @Override - public SignedState next() { - previous = baseIterator.next(); - return previous.get(); - } - - @Override - public void remove() { - baseIterator.remove(); - if (previous != null) { - previous.close(); - } - } - }; - - operation.accept(iterator); - } - } - - /** - * Get the number of states in this map. - */ - public int getSize() { - try (final Locked l = lock.lock()) { - return map.size(); - } - } -} diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/signed/SignedStateMetrics.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/signed/SignedStateMetrics.java index b8ba7038cc9d..524eadde43b4 100644 --- a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/signed/SignedStateMetrics.java +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/signed/SignedStateMetrics.java @@ -18,7 +18,6 @@ import static com.swirlds.metrics.api.FloatFormats.FORMAT_10_2; import static com.swirlds.metrics.api.FloatFormats.FORMAT_10_3; -import static com.swirlds.metrics.api.FloatFormats.FORMAT_15_3; import static com.swirlds.metrics.api.FloatFormats.FORMAT_16_2; import com.swirlds.common.metrics.RunningAverageMetric; @@ -55,13 +54,6 @@ public class SignedStateMetrics { .withUnit("count"); private final Counter totalNeverSignedStates; - private static final Counter.Config TOTAL_NEVER_SIGNED_DISK_STATES_CONFIG = new Counter.Config( - CATEGORY, "totalNeverSignedDiskStates") - .withDescription( - "total number of disk-bound states that did not receive enough signatures in the allowed time") - .withUnit("count"); - private final Counter totalNeverSignedDiskStates; - private static final SpeedometerMetric.Config STATES_SIGNED_PER_SECOND_CONFIG = new SpeedometerMetric.Config( CATEGORY, "sstatesSigned_per_sec") .withDescription("the number of states completely signed per second") @@ -86,36 +78,6 @@ public class SignedStateMetrics { .withUnit("rounds"); private final RunningAverageMetric stateSignatureAge; - private static final RunningAverageMetric.Config STATE_ARCHIVAL_TIME_AVG_CONFIG = new RunningAverageMetric.Config( - CATEGORY, "stateArchivalTimeAvg") - .withDescription("avg time to archive a signed state (in milliseconds)") - .withUnit(MILLISECONDS) - .withFormat(FORMAT_15_3); - private final RunningAverageMetric stateArchivalTimeAvg; - - private static final RunningAverageMetric.Config STATE_HASHING_TIME_CONFIG = new RunningAverageMetric.Config( - CATEGORY, "sigStateHash") - .withDescription("average time it takes to hash a SignedState (in milliseconds)") - .withUnit(MILLISECONDS) - .withFormat(FORMAT_10_3); - private final RunningAverageMetric stateHashingTime; - - private static final RunningAverageMetric.Config WRITE_STATE_TO_DISK_TIME_CONFIG = new RunningAverageMetric.Config( - CATEGORY, "writeStateToDisk") - .withDescription("average time it takes to write a SignedState to disk (in milliseconds)") - .withUnit(MILLISECONDS) - .withFormat(FORMAT_10_3); - - private final RunningAverageMetric writeStateToDiskTime; - - private static final RunningAverageMetric.Config STATE_TO_DISK_TIME_CONFIG = new RunningAverageMetric.Config( - CATEGORY, "stateToDisk") - .withDescription("average time it takes to do perform all actions when writing a SignedState to disk " - + "(in milliseconds)") - .withUnit(MILLISECONDS) - .withFormat(FORMAT_10_3); - private final RunningAverageMetric stateToDiskTime; - /** * Get a metric tracking unsigned states. */ @@ -137,13 +99,6 @@ public Counter getTotalUnsignedStatesMetric() { return totalNeverSignedStates; } - /** - * Get a metric tracking the total number of unsigned states written to disk that were skipped. - */ - public Counter getTotalUnsignedDiskStatesMetric() { - return totalNeverSignedDiskStates; - } - /** * Get a metric tracking the total number of states signed per second. */ @@ -159,37 +114,8 @@ public SpeedometerMetric getStateSignaturesGatheredPerSecondMetric() { } /** - * Get a metric tracking the average time required to archive a state. - */ - public RunningAverageMetric getStateArchivalTimeAvgMetric() { - return stateArchivalTimeAvg; - } - - /** - * Get a metric tracking the average time required to hash a state. - */ - public RunningAverageMetric getSignedStateHashingTimeMetric() { - return stateHashingTime; - } - - /** - * Get a metric tracking the average time required to write a state to disk. - */ - public RunningAverageMetric getWriteStateToDiskTimeMetric() { - return writeStateToDiskTime; - } - - /** - * Get a metric tracking the average time required to perform all actions when saving a state to disk, i.e. - * notifying listeners and cleaning up old states on disk. - */ - public RunningAverageMetric getStateToDiskTimeMetric() { - return stateToDiskTime; - } - - /** - * Get a metric tracking the average difference in round number between signature transactions and - * the most recent immutable state. + * Get a metric tracking the average difference in round number between signature transactions and the most recent + * immutable state. */ public RunningAverageMetric getStateSignatureAge() { return stateSignatureAge; @@ -198,20 +124,14 @@ public RunningAverageMetric getStateSignatureAge() { /** * Register all metrics with a registry. * - * @param metrics - * a reference to the metrics-system + * @param metrics a reference to the metrics-system */ public SignedStateMetrics(final Metrics metrics) { unsignedStates = metrics.getOrCreate(UNSIGNED_STATES_CONFIG); averageTimeToFullySignState = metrics.getOrCreate(AVERAGE_TIME_TO_FULLY_SIGN_STATE); totalNeverSignedStates = metrics.getOrCreate(TOTAL_NEVER_SIGNED_STATES_CONFIG); - totalNeverSignedDiskStates = metrics.getOrCreate(TOTAL_NEVER_SIGNED_DISK_STATES_CONFIG); statesSignedPerSecond = metrics.getOrCreate(STATES_SIGNED_PER_SECOND_CONFIG); stateSignaturesGatheredPerSecond = metrics.getOrCreate(STATE_SIGNATURES_GATHERED_PER_SECOND_CONFIG); - stateArchivalTimeAvg = metrics.getOrCreate(STATE_ARCHIVAL_TIME_AVG_CONFIG); - stateHashingTime = metrics.getOrCreate(STATE_HASHING_TIME_CONFIG); - stateToDiskTime = metrics.getOrCreate(STATE_TO_DISK_TIME_CONFIG); - writeStateToDiskTime = metrics.getOrCreate(WRITE_STATE_TO_DISK_TIME_CONFIG); stateSignatureAge = metrics.getOrCreate(STATE_SIGNATURE_AGE_CONFIG); } } diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/signed/StartupStateUtils.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/signed/StartupStateUtils.java index 00d838dea7aa..35e04a6fc06f 100644 --- a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/signed/StartupStateUtils.java +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/signed/StartupStateUtils.java @@ -21,22 +21,21 @@ import static com.swirlds.logging.legacy.LogMarker.STARTUP; import static com.swirlds.platform.state.GenesisStateBuilder.buildGenesisState; import static com.swirlds.platform.state.signed.ReservedSignedState.createNullReservation; -import static com.swirlds.platform.state.signed.SignedStateFileReader.readStateFile; +import static com.swirlds.platform.state.snapshot.SignedStateFileReader.readStateFile; import com.swirlds.common.config.StateCommonConfig; import com.swirlds.common.context.PlatformContext; import com.swirlds.common.crypto.Hash; import com.swirlds.common.io.filesystem.FileSystemManager; import com.swirlds.common.platform.NodeId; -import com.swirlds.common.scratchpad.Scratchpad; import com.swirlds.logging.legacy.payload.SavedStateLoadedPayload; import com.swirlds.platform.config.StateConfig; import com.swirlds.platform.crypto.CryptoStatic; import com.swirlds.platform.internal.SignedStateLoadingException; -import com.swirlds.platform.recovery.EmergencyRecoveryManager; -import com.swirlds.platform.recovery.RecoveryScratchpad; -import com.swirlds.platform.recovery.emergencyfile.EmergencyRecoveryFile; import com.swirlds.platform.state.State; +import com.swirlds.platform.state.snapshot.DeserializedSignedState; +import com.swirlds.platform.state.snapshot.SavedStateInfo; +import com.swirlds.platform.state.snapshot.SignedStateFilePath; import com.swirlds.platform.system.SoftwareVersion; import com.swirlds.platform.system.SwirldState; import com.swirlds.platform.system.address.AddressBook; @@ -59,68 +58,17 @@ public final class StartupStateUtils { private StartupStateUtils() {} - /** - * If necessary, perform cleanup in preparation for emergency recovery. - * - * @param platformContext the platform context - * @param selfId the ID of this node - * @param swirldName the name of this swirld - * @param actualMainClassName the name of the app's SwirldMain class (may be a value provided by configuration) - * @param epoch the epoch that the platform wants to be in (defined either by the emergency recovery - * file or the state loaded from disk if it is not overridden by a recovery file) - * @param initialStateRound the round number of the initial state - */ - public static void doRecoveryCleanup( - @NonNull final PlatformContext platformContext, - @NonNull final NodeId selfId, - @NonNull final String swirldName, - @NonNull final String actualMainClassName, - @Nullable final Hash epoch, - final long initialStateRound) { - - final Scratchpad recoveryScratchpad = - Scratchpad.create(platformContext, selfId, RecoveryScratchpad.class, RecoveryScratchpad.SCRATCHPAD_ID); - recoveryScratchpad.logContents(); - - final Hash previousEpoch = recoveryScratchpad.get(RecoveryScratchpad.EPOCH_HASH); - - if (Objects.equals(epoch, previousEpoch)) { - // We are in the same epoch as when we were the last time he platform was shut down. - return; - } - - logger.info( - STARTUP.getMarker(), - "Entering new epoch, cleaning up file system in preparation for emergency recovery. " - + "Any states with a round number higher than {} will be recycled.", - initialStateRound); - - final List savedStateFiles = new SignedStateFilePath( - platformContext.getConfiguration().getConfigData(StateCommonConfig.class)) - .getSavedStateFiles(actualMainClassName, selfId, swirldName); - for (final SavedStateInfo stateInfo : savedStateFiles) { - if (stateInfo.metadata().round() > initialStateRound) { - recycleState(platformContext.getFileSystemManager(), stateInfo); - } - } - - // Write the current epoch into the scratchpad. Once this completes, the platform will not do cleanup - // the next time it boots with this epoch hash. - recoveryScratchpad.set(RecoveryScratchpad.EPOCH_HASH, epoch); - } - /** * Get the initial state to be used by this node. May return a state loaded from disk, or may return a genesis state * if no valid state is found on disk. * - * @param platformContext the platform context - * @param softwareVersion the software version of the app - * @param genesisStateBuilder a supplier that can build a genesis state - * @param mainClassName the name of the app's SwirldMain class - * @param swirldName the name of this swirld - * @param selfId the node id of this node - * @param configAddressBook the address book from config.txt - * @param emergencyRecoveryManager the emergency recovery manager + * @param platformContext the platform context + * @param softwareVersion the software version of the app + * @param genesisStateBuilder a supplier that can build a genesis state + * @param mainClassName the name of the app's SwirldMain class + * @param swirldName the name of this swirld + * @param selfId the node id of this node + * @param configAddressBook the address book from config.txt * @return the initial state to be used by this node * @throws SignedStateLoadingException if there was a problem parsing states on disk and we are not configured to * delete malformed states @@ -133,8 +81,7 @@ public static ReservedSignedState getInitialState( @NonNull final String mainClassName, @NonNull final String swirldName, @NonNull final NodeId selfId, - @NonNull final AddressBook configAddressBook, - @NonNull final EmergencyRecoveryManager emergencyRecoveryManager) + @NonNull final AddressBook configAddressBook) throws SignedStateLoadingException { Objects.requireNonNull(platformContext); @@ -142,10 +89,9 @@ public static ReservedSignedState getInitialState( Objects.requireNonNull(swirldName); Objects.requireNonNull(selfId); Objects.requireNonNull(configAddressBook); - Objects.requireNonNull(emergencyRecoveryManager); - final ReservedSignedState loadedState = StartupStateUtils.loadStateFile( - platformContext, selfId, mainClassName, swirldName, softwareVersion, emergencyRecoveryManager); + final ReservedSignedState loadedState = + StartupStateUtils.loadStateFile(platformContext, selfId, mainClassName, swirldName, softwareVersion); try (loadedState) { if (loadedState.isNotNull()) { @@ -174,7 +120,6 @@ public static ReservedSignedState getInitialState( * @param mainClassName the name of the main class * @param swirldName the name of the swirld * @param currentSoftwareVersion the current software version - * @param emergencyRecoveryManager the emergency recovery manager * @return a reserved signed state (wrapped state will be null if no state could be loaded) * @throws SignedStateLoadingException if there was a problem parsing states on disk and we are not configured to * delete malformed states @@ -185,8 +130,7 @@ static ReservedSignedState loadStateFile( @NonNull final NodeId selfId, @NonNull final String mainClassName, @NonNull final String swirldName, - @NonNull final SoftwareVersion currentSoftwareVersion, - @NonNull final EmergencyRecoveryManager emergencyRecoveryManager) { + @NonNull final SoftwareVersion currentSoftwareVersion) { final StateConfig stateConfig = platformContext.getConfiguration().getConfigData(StateConfig.class); final String actualMainClassName = stateConfig.getMainClassName(mainClassName); @@ -201,16 +145,7 @@ static ReservedSignedState loadStateFile( return createNullReservation(); } - final boolean emergencyStateRequired = emergencyRecoveryManager.isEmergencyStateRequired(); - - final ReservedSignedState state; - if (emergencyStateRequired) { - state = loadEmergencyState( - platformContext, currentSoftwareVersion, savedStateFiles, emergencyRecoveryManager); - } else { - state = loadLatestState(platformContext, currentSoftwareVersion, savedStateFiles); - } - + final ReservedSignedState state = loadLatestState(platformContext, currentSoftwareVersion, savedStateFiles); return state; } @@ -258,188 +193,6 @@ private static void logStatesFound(@NonNull final List savedStat logger.info(STARTUP.getMarker(), sb.toString()); } - /** - * Load the latest state that is compatible with the emergency recovery file. - * - * @param platformContext the platform context - * @param currentSoftwareVersion the current software version - * @param savedStateFiles the saved states to try - * @param emergencyRecoveryManager the emergency recovery manager - * @return the loaded state - */ - @NonNull - private static ReservedSignedState loadEmergencyState( - @NonNull final PlatformContext platformContext, - @NonNull final SoftwareVersion currentSoftwareVersion, - @NonNull final List savedStateFiles, - @NonNull final EmergencyRecoveryManager emergencyRecoveryManager) - throws SignedStateLoadingException { - - final EmergencyRecoveryFile recoveryFile = emergencyRecoveryManager.getEmergencyRecoveryFile(); - logger.info( - STARTUP.getMarker(), - """ - Loading state in emergency recovery mode. The emergency recovery file specifies the following: - Epoch hash: {} - Epoch hash mnemonic: {} - Round: {}""", - recoveryFile.hash(), - recoveryFile.hash().toMnemonic(), - recoveryFile.round()); - - ReservedSignedState state = null; - for (final SavedStateInfo savedStateFile : savedStateFiles) { - if (!isSuitableInitialRecoveryState(emergencyRecoveryManager, savedStateFile)) { - continue; - } - - state = loadStateFile(platformContext, currentSoftwareVersion, savedStateFile); - if (state != null) { - break; - } - } - - return processRecoveryState(emergencyRecoveryManager, state); - } - - /** - * Check if a state is a suitable initial state for emergency recovery. A suitable state satisfies at least one of - * these conditions: - *
      - *
    • The state's root hash matches the exact epoch hash
    • - *
    • The state has a matching epoch hash
    • - *
    • The state's round is less than the recovery round
    • - *
    - * - * @param emergencyRecoveryManager decides if a state is suitable for emergency recovery - * @param savedStateFile the state to check - * @return true if the state is suitable to be an initial state for emergency recovery, false otherwise - */ - private static boolean isSuitableInitialRecoveryState( - @NonNull final EmergencyRecoveryManager emergencyRecoveryManager, - @NonNull final SavedStateInfo savedStateFile) { - - if (savedStateFile.metadata().hash() == null) { - // This state was created with an old version of the metadata, do not consider it. - // Any state written with the current software version will have a non-null value for this field. - return false; - } - - final Hash targetEpoch = - emergencyRecoveryManager.getEmergencyRecoveryFile().hash(); - final long targetRound = - emergencyRecoveryManager.getEmergencyRecoveryFile().round(); - - final Hash stateHash = savedStateFile.metadata().hash(); - final Hash stateEpoch = savedStateFile.metadata().epochHash(); - final long stateRound = savedStateFile.metadata().round(); - - final boolean isStateSuitable = isInEpoch(targetEpoch, stateHash, stateEpoch) || stateRound < targetRound; - - if (isStateSuitable) { - logger.info( - STARTUP.getMarker(), - """ - The following state meets the emergency recovery criteria: - File path: {} - Hash: {} - Hash Mnemonic: {} - Round: {}""", - savedStateFile.stateFile(), - savedStateFile.metadata().hash(), - savedStateFile.metadata().hashMnemonic(), - savedStateFile.metadata().round()); - } else { - logger.warn( - STARTUP.getMarker(), - """ - The following state does not meet the emergency recovery criteria: - File path: {} - Hash: {} - Hash Mnemonic: {} - Round: {}""", - savedStateFile.stateFile(), - savedStateFile.metadata().hash(), - savedStateFile.metadata().hashMnemonic(), - savedStateFile.metadata().round()); - } - - return isStateSuitable; - } - - /** - * Check if a provided state is in the hash epoch that is specified by the emergency recovery file. - * - * @param targetEpoch the hash epoch specified by the emergency recovery file - * @param stateHash the hash of the state - * @param stateEpoch the epoch hash of the state - * @return true if the state is in the hash epoch, false otherwise. Null states are not in the hash epoch. - */ - private static boolean isInEpoch( - @Nullable final Hash targetEpoch, @Nullable final Hash stateHash, @Nullable final Hash stateEpoch) { - - if (stateHash == null) { - // State is from an old version of the code that did not store state hash in metadata - return false; - } - - return stateHash.equals(targetEpoch) || Objects.equals(stateEpoch, targetEpoch); - } - - /** - * Once we have decided which state will be our initial state, do some additional logging and processing. - * - * @param emergencyRecoveryManager the emergency recovery manager - * @param state the state that will be our initial state (null if we are starting from genesis) - * @return the state that will be our initial state (converts null genesis state to a non-null wrapper) - */ - @NonNull - private static ReservedSignedState processRecoveryState( - @NonNull final EmergencyRecoveryManager emergencyRecoveryManager, - @Nullable final ReservedSignedState state) { - - final Hash targetEpoch = - emergencyRecoveryManager.getEmergencyRecoveryFile().hash(); - final Hash stateHash = state == null ? null : state.get().getState().getHash(); - final Hash stateEpoch = - state == null ? null : state.get().getState().getPlatformState().getEpochHash(); - - final boolean inEpoch = isInEpoch(targetEpoch, stateHash, stateEpoch); - - if (state == null) { - logger.warn( - STARTUP.getMarker(), - "No state on disk met the criteria for emergency recovery, starting from genesis. " - + "This node will need to receive a state through an emergency reconnect."); - return createNullReservation(); - } else if (inEpoch) { - logger.info( - STARTUP.getMarker(), - "Loaded state is in the correct hash epoch, " - + "this node will not need to receive a state through an emergency reconnect."); - - state.get().markAsRecoveryState(); - - // Ensure that the next round created has the proper epoch hash. - state.get() - .getState() - .getPlatformState() - .setNextEpochHash( - emergencyRecoveryManager.getEmergencyRecoveryFile().hash()); - - // Signal that an emergency reconnect is not needed. - emergencyRecoveryManager.emergencyStateLoaded(); - - return state; - } else { - logger.warn( - STARTUP.getMarker(), - "Loaded state is not in the correct hash epoch, " - + "this node will need to receive a state through an emergency reconnect."); - return state; - } - } - /** * Load the latest state. If the latest state is invalid, try to load the next latest state. Repeat until a valid * state is found or there are no more states to try. diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/signed/StateSignatureCollector.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/signed/StateSignatureCollector.java index 4a6118543f98..9b226c25dac1 100644 --- a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/signed/StateSignatureCollector.java +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/signed/StateSignatureCollector.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2017-2024 Hedera Hashgraph, LLC + * Copyright (C) 2024 Hedera Hashgraph, LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,33 +16,12 @@ package com.swirlds.platform.state.signed; -import static java.util.stream.Collectors.collectingAndThen; -import static java.util.stream.Collectors.toList; - import com.hedera.hapi.platform.event.StateSignaturePayload; -import com.swirlds.common.crypto.Signature; -import com.swirlds.common.crypto.SignatureType; -import com.swirlds.common.platform.NodeId; -import com.swirlds.common.sequence.set.SequenceSet; -import com.swirlds.common.sequence.set.StandardSequenceSet; -import com.swirlds.logging.legacy.LogMarker; +import com.swirlds.common.wiring.component.InputWireLabel; import com.swirlds.platform.components.transaction.system.ScopedSystemTransaction; -import com.swirlds.platform.config.StateConfig; -import com.swirlds.platform.consensus.ConsensusConstants; import edu.umd.cs.findbugs.annotations.NonNull; import edu.umd.cs.findbugs.annotations.Nullable; -import java.time.Duration; -import java.time.Instant; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.Iterator; import java.util.List; -import java.util.Map; -import java.util.Objects; -import java.util.Optional; -import java.util.stream.Stream; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; /** * Collects signatures for signed states. This class ensures that all the non-ancient states that are not fully signed @@ -53,34 +32,7 @@ *
  • too old
  • * */ -public class StateSignatureCollector { - private static final Logger logger = LogManager.getLogger(StateSignatureCollector.class); - /** The latest signed state round */ - private long lastStateRound = ConsensusConstants.ROUND_UNDEFINED; - /** Signed states awaiting signatures */ - private final Map incompleteStates = new HashMap<>(); - /** State config */ - private final StateConfig stateConfig; - /** Signatures for rounds in the future */ - private final SequenceSet savedSignatures; - /** A collection of signed state metrics */ - private final SignedStateMetrics signedStateMetrics; - - /** - * Start empty, with no known signed states. A signed state is considered completed when it has signatures from a - * sufficient threshold of nodes. - * - * @param stateConfig configuration for state - * @param signedStateMetrics a collection of signed state metrics - */ - public StateSignatureCollector( - @NonNull final StateConfig stateConfig, @NonNull final SignedStateMetrics signedStateMetrics) { - this.stateConfig = Objects.requireNonNull(stateConfig, "stateConfig"); - this.signedStateMetrics = Objects.requireNonNull(signedStateMetrics, "signedStateMetrics"); - - this.savedSignatures = - new StandardSequenceSet<>(0, stateConfig.maxAgeOfFutureStateSignatures(), SavedSignature::round); - } +public interface StateSignatureCollector { /** * Add a state. It could be a complete state, in which case it will be returned immediately. @@ -88,45 +40,9 @@ public StateSignatureCollector( * @param reservedSignedState the signed state to add * @return a list of signed states that are now complete or too old, or null if there are none */ - public @Nullable List addReservedState( - @NonNull final ReservedSignedState reservedSignedState) { - Objects.requireNonNull(reservedSignedState, "reservedSignedState"); - final SignedState signedState = reservedSignedState.get(); - - if (signedState.getState().getHash() == null) { - throw new IllegalArgumentException( - "Unhashed state for round " + signedState.getRound() + " added to the signed state manager"); - } - - // Double check that the signatures on this state are valid. - // They may no longer be valid if we have done a data migration. - signedState.pruneInvalidSignatures(); - - // find any signatures that have been saved - final List signatures = savedSignatures.getEntriesWithSequenceNumber(signedState.getRound()); - savedSignatures.removeSequenceNumber(signedState.getRound()); - signatures.forEach(ss -> addSignature(reservedSignedState, ss.memberId, ss.signature)); - - lastStateRound = Math.max(lastStateRound, signedState.getRound()); - adjustSavedSignaturesWindow(signedState.getRound()); - - // complete states and freeze states will be written immediately to disk - // incomplete non-freeze states will be kept until they are complete or too old - if (!signedState.isComplete() && !signedState.isFreezeState()) { - final ReservedSignedState previousState = incompleteStates.put(signedState.getRound(), reservedSignedState); - if (previousState != null) { - previousState.close(); - logger.warn( - LogMarker.EXCEPTION.getMarker(), - "Two states with the same round ({}) have been added to the signature collector", - signedState.getRound()); - } - return Optional.of(purgeOldStates()).filter(l -> !l.isEmpty()).orElse(null); - } - return Stream.concat(Stream.of(reservedSignedState), purgeOldStates().stream()) - .filter(Objects::nonNull) - .collect(collectingAndThen(toList(), l -> l.isEmpty() ? null : l)); - } + @InputWireLabel("hashed states") + @Nullable + List addReservedState(@NonNull ReservedSignedState reservedSignedState); /** * Handle preconsensus state signatures. @@ -134,37 +50,10 @@ public StateSignatureCollector( * @param transactions the signature transactions to handle * @return a list of signed states that are now complete or too old, or null if there are none */ - public @Nullable List handlePreconsensusSignatures( - @NonNull final List> transactions) { - Objects.requireNonNull(transactions, "transactions"); - return transactions.stream() - .map(this::handlePreconsensusSignature) - .filter(Objects::nonNull) - .collect(collectingAndThen(toList(), l -> l.isEmpty() ? null : l)); - } - - private @Nullable ReservedSignedState handlePreconsensusSignature( - @NonNull final ScopedSystemTransaction scopedTransaction) { - - final long round = scopedTransaction.transaction().round(); - final Signature signature = new Signature( - SignatureType.RSA, scopedTransaction.transaction().signature().toByteArray()); - - signedStateMetrics.getStateSignaturesGatheredPerSecondMetric().cycle(); - - if (lastStateRound != -1) { - final long signatureAge = round - lastStateRound; - signedStateMetrics.getStateSignatureAge().update(signatureAge); - } - - final ReservedSignedState reservedState = incompleteStates.get(round); - if (reservedState == null) { - // This round has already been completed, or it is really old or in the future - savedSignatures.add(new SavedSignature(round, scopedTransaction.submitterId(), signature)); - return null; - } - return addSignature(reservedState, scopedTransaction.submitterId(), signature); - } + @InputWireLabel("preconsensus state signatures") + @Nullable + List handlePreconsensusSignatures( + @NonNull List> transactions); /** * Handle postconsensus state signatures. @@ -172,130 +61,16 @@ public StateSignatureCollector( * @param transactions the signature transactions to handle * @return a list of signed states that are now complete or too old, or null if there are none */ - public @Nullable List handlePostconsensusSignatures( - @NonNull final List> transactions) { - Objects.requireNonNull(transactions, "transactions"); - return transactions.stream() - .map(this::handlePostconsensusSignature) - .filter(Objects::nonNull) - .collect(collectingAndThen(toList(), l -> l.isEmpty() ? null : l)); - } - - private @Nullable ReservedSignedState handlePostconsensusSignature( - @NonNull final ScopedSystemTransaction scopedTransaction) { - final long round = scopedTransaction.transaction().round(); - - final ReservedSignedState reservedState = incompleteStates.get(round); - // it isn't possible to receive a postconsensus signature transaction for a future round, - // and if we don't have the state for an old round, we never will. - // in both cases, the signature can be ignored - if (reservedState == null) { - return null; - } - - return addSignature( - reservedState, - scopedTransaction.submitterId(), - new Signature( - SignatureType.RSA, - scopedTransaction.transaction().signature().toByteArray())); - } - - /** - * Add a new signature to a signed state. - * - * @param reservedSignedState the state being signed - * @param nodeId the ID of the signer - * @param signature the signature on the state - * @return the signed state if it is now complete, otherwise null - */ - private @Nullable ReservedSignedState addSignature( - @NonNull final ReservedSignedState reservedSignedState, - @NonNull final NodeId nodeId, - @NonNull final Signature signature) { - final SignedState signedState = reservedSignedState.get(); - - if (signedState.addSignature(nodeId, signature)) { - // at this point the signed state is complete for the first time - signedStateMetrics.getStatesSignedPerSecondMetric().cycle(); - signedStateMetrics - .getAverageTimeToFullySignStateMetric() - .update(Duration.between(signedState.getCreationTimestamp(), Instant.now()) - .toMillis()); - - return incompleteStates.remove(signedState.getRound()); - } - return null; - } - - /** - * Get the earliest round that is permitted to be stored in this data structure. - * - * @return the earliest round permitted to be stored - */ - private long getEarliestPermittedRound() { - return lastStateRound - stateConfig.roundsToKeepForSigning() + 1; - } - - /** - * Get rid of old states. - * - * @return a list of states that were purged - */ - private @NonNull List purgeOldStates() { - final List purgedStates = new ArrayList<>(); - - // Any state older than this is unconditionally removed. - final long earliestPermittedRound = getEarliestPermittedRound(); - for (final Iterator iterator = - incompleteStates.values().iterator(); - iterator.hasNext(); ) { - final ReservedSignedState reservedSignedState = iterator.next(); - final SignedState signedState = reservedSignedState.get(); - if (signedState.getRound() < earliestPermittedRound) { - signedStateMetrics.getTotalUnsignedStatesMetric().increment(); - purgedStates.add(reservedSignedState); - iterator.remove(); - } - } - - signedStateMetrics.getUnsignedStatesMetric().update(incompleteStates.size()); - return purgedStates; - } - - /** - * Adjust the window where we are willing to save future signatures. - * - * @param currentRound the round of the most recently signed state - */ - private void adjustSavedSignaturesWindow(final long currentRound) { - // Only save signatures for round N+1 and after. - // Any rounds behind this one will either have already had a SignedState - // added to this manager, or will never have a SignedState added to this manager. - if (savedSignatures.getFirstSequenceNumberInWindow() < currentRound + 1) { - savedSignatures.shiftWindow(currentRound + 1); - } - } + @InputWireLabel("post consensus state signatures") + @Nullable + List handlePostconsensusSignatures( + @NonNull List> transactions); /** * Clear the internal state of this collector. * * @param ignored ignored trigger object */ - public void clear(@NonNull final Object ignored) { - for (final Iterator iterator = - incompleteStates.values().iterator(); - iterator.hasNext(); ) { - final ReservedSignedState state = iterator.next(); - state.close(); - iterator.remove(); - } - savedSignatures.clear(); - lastStateRound = ConsensusConstants.ROUND_UNDEFINED; - } - - /** - * A signature that was received when there was no state with a matching round. - */ - private record SavedSignature(long round, @NonNull NodeId memberId, @NonNull Signature signature) {} + @InputWireLabel("clear") + void clear(@NonNull Object ignored); } diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/signed/SignedStateFileManager.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/snapshot/DefaultStateSnapshotManager.java similarity index 76% rename from platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/signed/SignedStateFileManager.java rename to platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/snapshot/DefaultStateSnapshotManager.java index 7f85237e0dc8..37e9bd2faab4 100644 --- a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/signed/SignedStateFileManager.java +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/snapshot/DefaultStateSnapshotManager.java @@ -14,12 +14,12 @@ * limitations under the License. */ -package com.swirlds.platform.state.signed; +package com.swirlds.platform.state.snapshot; import static com.swirlds.common.io.utility.FileUtils.deleteDirectoryAndLog; import static com.swirlds.logging.legacy.LogMarker.EXCEPTION; import static com.swirlds.logging.legacy.LogMarker.STATE_TO_DISK; -import static com.swirlds.platform.state.signed.StateToDiskReason.UNKNOWN; +import static com.swirlds.platform.state.snapshot.StateToDiskReason.UNKNOWN; import com.swirlds.base.time.Time; import com.swirlds.common.config.StateCommonConfig; @@ -29,6 +29,8 @@ import com.swirlds.config.api.Configuration; import com.swirlds.logging.legacy.payload.InsufficientSignaturesPayload; import com.swirlds.platform.config.StateConfig; +import com.swirlds.platform.state.signed.ReservedSignedState; +import com.swirlds.platform.state.signed.SignedState; import com.swirlds.platform.system.events.EventConstants; import edu.umd.cs.findbugs.annotations.NonNull; import edu.umd.cs.findbugs.annotations.Nullable; @@ -42,11 +44,11 @@ import org.apache.logging.log4j.Logger; /** - * This class is responsible for managing the signed state writing pipeline. + * This class is responsible for managing the state writing pipeline. */ -public class SignedStateFileManager { +public class DefaultStateSnapshotManager implements StateSnapshotManager { - private static final Logger logger = LogManager.getLogger(SignedStateFileManager.class); + private static final Logger logger = LogManager.getLogger(DefaultStateSnapshotManager.class); /** * The ID of this node. @@ -66,58 +68,58 @@ public class SignedStateFileManager { /** * Metrics provider */ - private final SignedStateMetrics metrics; - /** the configuration */ + private final StateSnapshotManagerMetrics metrics; + + /** + * the configuration + */ private final Configuration configuration; - /** the platform context */ + + /** + * the platform context + */ private final PlatformContext platformContext; /** * Provides system time */ private final Time time; - /** Used to determine the path of a signed state */ + + /** + * Used to determine the path of a signed state + */ private final SignedStateFilePath signedStateFilePath; /** * Creates a new instance. * - * @param context the platform context - * @param metrics metrics provider - * @param time provides time + * @param platformContext the platform context * @param mainClassName the main class name of this node * @param selfId the ID of this node * @param swirldName the name of the swirld */ - public SignedStateFileManager( - @NonNull final PlatformContext context, - @NonNull final SignedStateMetrics metrics, - @NonNull final Time time, + public DefaultStateSnapshotManager( + @NonNull final PlatformContext platformContext, @NonNull final String mainClassName, @NonNull final NodeId selfId, @NonNull final String swirldName) { - this.metrics = Objects.requireNonNull(metrics, "metrics must not be null"); - this.time = Objects.requireNonNull(time); + this.platformContext = Objects.requireNonNull(platformContext); + this.time = platformContext.getTime(); this.selfId = Objects.requireNonNull(selfId); this.mainClassName = Objects.requireNonNull(mainClassName); this.swirldName = Objects.requireNonNull(swirldName); - this.platformContext = Objects.requireNonNull(context); - this.configuration = Objects.requireNonNull(context.getConfiguration()); - this.signedStateFilePath = new SignedStateFilePath(configuration.getConfigData(StateCommonConfig.class)); + configuration = platformContext.getConfiguration(); + signedStateFilePath = new SignedStateFilePath(configuration.getConfigData(StateCommonConfig.class)); + metrics = new StateSnapshotManagerMetrics(platformContext); } /** - * Method to be called when a state needs to be written to disk in-band. An "in-band" write is part of normal - * platform operations, whereas an out-of-band write is triggered due to a fault, or for debug purposes. - *

    - * This method shouldn't be called if the state was written out-of-band. - * - * @param reservedSignedState the state to be written to disk. it is expected that the state is reserved prior to - * this method call and this method will release the reservation when it is done - * @return the result of the state saving operation, or null if the state was not saved + * {@inheritDoc} */ - public @Nullable StateSavingResult saveStateTask(@NonNull final ReservedSignedState reservedSignedState) { + @Override + @Nullable + public StateSavingResult saveStateTask(@NonNull final ReservedSignedState reservedSignedState) { final long start = time.nanoTime(); final StateSavingResult stateSavingResult; @@ -148,12 +150,9 @@ public SignedStateFileManager( } /** - * Method to be called when a state needs to be written to disk out-of-band. An "in-band" write is part of normal - * platform operations, whereas an out-of-band write is triggered due to a fault, or for debug purposes. - * - * @param request a request to dump a state to disk. it is expected that the state inside the request is reserved - * prior to this method call and this method will release the reservation when it is done + * {@inheritDoc} */ + @Override public void dumpStateTask(@NonNull final StateDumpRequest request) { // the state is reserved before it is handed to this method, and it is released when we are done try (final ReservedSignedState reservedSignedState = request.reservedSignedState()) { @@ -169,7 +168,8 @@ public void dumpStateTask(@NonNull final StateDumpRequest request) { request.finishedCallback().run(); } - private static @NonNull StateToDiskReason getReason(@NonNull final SignedState state) { + @NonNull + private static StateToDiskReason getReason(@NonNull final SignedState state) { return Optional.ofNullable(state.getStateToDiskReason()).orElse(UNKNOWN); } @@ -212,9 +212,9 @@ private void checkSignatures(@NonNull final SignedState reservedState) { logger.info( STATE_TO_DISK.getMarker(), """ - Freeze state written to disk for round {} was not fully signed. This is expected. - Collected signatures representing {}/{} ({}%) weight. - """, + Freeze state written to disk for round {} was not fully signed. This is expected. + Collected signatures representing {}/{} ({}%) weight. + """, reservedState.getRound(), reservedState.getSigningWeight(), reservedState.getAddressBook().getTotalWeight(), @@ -226,10 +226,10 @@ private void checkSignatures(@NonNull final SignedState reservedState) { EXCEPTION.getMarker(), new InsufficientSignaturesPayload( (""" - State written to disk for round %d did not have enough signatures. - This log adds debug information for #11422. - Pre-check weight: %d/%d (%f%%) Post-check weight: %d/%d (%f%%) - Pre-check threshold: %s Post-check threshold: %s""" + State written to disk for round %d did not have enough signatures. + This log adds debug information for #11422. + Pre-check weight: %d/%d (%f%%) Post-check weight: %d/%d (%f%%) + Pre-check threshold: %s Post-check threshold: %s""" .formatted( reservedState.getRound(), signingWeight1, @@ -249,12 +249,14 @@ private void checkSignatures(@NonNull final SignedState reservedState) { * @param round the round number for the signed state * @return the File that represents the directory of the signed state for the particular round */ + @NonNull private Path getSignedStateDir(final long round) { return signedStateFilePath.getSignedStateDirectory(mainClassName, selfId, swirldName, round); } /** * Purge old states on the disk. + * * @return the minimum generation non-ancient of the oldest state that was not deleted */ private long deleteOldStates() { diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/signed/DeserializedSignedState.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/snapshot/DeserializedSignedState.java similarity index 90% rename from platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/signed/DeserializedSignedState.java rename to platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/snapshot/DeserializedSignedState.java index 19aad6fd18f9..1285c257b6d4 100644 --- a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/signed/DeserializedSignedState.java +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/snapshot/DeserializedSignedState.java @@ -14,9 +14,10 @@ * limitations under the License. */ -package com.swirlds.platform.state.signed; +package com.swirlds.platform.state.snapshot; import com.swirlds.common.crypto.Hash; +import com.swirlds.platform.state.signed.ReservedSignedState; /** * This record encapsulates the data read from a new signed state. diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/signed/SavedStateInfo.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/snapshot/SavedStateInfo.java similarity index 96% rename from platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/signed/SavedStateInfo.java rename to platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/snapshot/SavedStateInfo.java index 179b532509a0..29e68d883bfd 100644 --- a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/signed/SavedStateInfo.java +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/snapshot/SavedStateInfo.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.swirlds.platform.state.signed; +package com.swirlds.platform.state.snapshot; import edu.umd.cs.findbugs.annotations.NonNull; import java.nio.file.Path; diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/signed/SavedStateMetadata.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/snapshot/SavedStateMetadata.java similarity index 89% rename from platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/signed/SavedStateMetadata.java rename to platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/snapshot/SavedStateMetadata.java index a821c3f0e8be..ced83ad0af80 100644 --- a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/signed/SavedStateMetadata.java +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/snapshot/SavedStateMetadata.java @@ -14,34 +14,33 @@ * limitations under the License. */ -package com.swirlds.platform.state.signed; +package com.swirlds.platform.state.snapshot; import static com.swirlds.common.formatting.StringFormattingUtils.formattedList; import static com.swirlds.common.utility.CommonUtils.unhex; import static com.swirlds.logging.legacy.LogMarker.STARTUP; -import static com.swirlds.platform.state.signed.SavedStateMetadataField.CONSENSUS_TIMESTAMP; -import static com.swirlds.platform.state.signed.SavedStateMetadataField.EPOCH_HASH; -import static com.swirlds.platform.state.signed.SavedStateMetadataField.EPOCH_HASH_MNEMONIC; -import static com.swirlds.platform.state.signed.SavedStateMetadataField.HASH; -import static com.swirlds.platform.state.signed.SavedStateMetadataField.HASH_MNEMONIC; -import static com.swirlds.platform.state.signed.SavedStateMetadataField.LEGACY_RUNNING_EVENT_HASH; -import static com.swirlds.platform.state.signed.SavedStateMetadataField.LEGACY_RUNNING_EVENT_HASH_MNEMONIC; -import static com.swirlds.platform.state.signed.SavedStateMetadataField.MINIMUM_GENERATION_NON_ANCIENT; -import static com.swirlds.platform.state.signed.SavedStateMetadataField.NODE_ID; -import static com.swirlds.platform.state.signed.SavedStateMetadataField.NUMBER_OF_CONSENSUS_EVENTS; -import static com.swirlds.platform.state.signed.SavedStateMetadataField.ROUND; -import static com.swirlds.platform.state.signed.SavedStateMetadataField.RUNNING_EVENT_HASH; -import static com.swirlds.platform.state.signed.SavedStateMetadataField.RUNNING_EVENT_HASH_MNEMONIC; -import static com.swirlds.platform.state.signed.SavedStateMetadataField.SIGNING_NODES; -import static com.swirlds.platform.state.signed.SavedStateMetadataField.SIGNING_WEIGHT_SUM; -import static com.swirlds.platform.state.signed.SavedStateMetadataField.SOFTWARE_VERSION; -import static com.swirlds.platform.state.signed.SavedStateMetadataField.TOTAL_WEIGHT; -import static com.swirlds.platform.state.signed.SavedStateMetadataField.WALL_CLOCK_TIME; +import static com.swirlds.platform.state.snapshot.SavedStateMetadataField.CONSENSUS_TIMESTAMP; +import static com.swirlds.platform.state.snapshot.SavedStateMetadataField.EPOCH_HASH; +import static com.swirlds.platform.state.snapshot.SavedStateMetadataField.EPOCH_HASH_MNEMONIC; +import static com.swirlds.platform.state.snapshot.SavedStateMetadataField.HASH; +import static com.swirlds.platform.state.snapshot.SavedStateMetadataField.HASH_MNEMONIC; +import static com.swirlds.platform.state.snapshot.SavedStateMetadataField.LEGACY_RUNNING_EVENT_HASH; +import static com.swirlds.platform.state.snapshot.SavedStateMetadataField.LEGACY_RUNNING_EVENT_HASH_MNEMONIC; +import static com.swirlds.platform.state.snapshot.SavedStateMetadataField.MINIMUM_GENERATION_NON_ANCIENT; +import static com.swirlds.platform.state.snapshot.SavedStateMetadataField.NODE_ID; +import static com.swirlds.platform.state.snapshot.SavedStateMetadataField.NUMBER_OF_CONSENSUS_EVENTS; +import static com.swirlds.platform.state.snapshot.SavedStateMetadataField.ROUND; +import static com.swirlds.platform.state.snapshot.SavedStateMetadataField.SIGNING_NODES; +import static com.swirlds.platform.state.snapshot.SavedStateMetadataField.SIGNING_WEIGHT_SUM; +import static com.swirlds.platform.state.snapshot.SavedStateMetadataField.SOFTWARE_VERSION; +import static com.swirlds.platform.state.snapshot.SavedStateMetadataField.TOTAL_WEIGHT; +import static com.swirlds.platform.state.snapshot.SavedStateMetadataField.WALL_CLOCK_TIME; import com.swirlds.common.crypto.Hash; import com.swirlds.common.formatting.TextTable; import com.swirlds.common.platform.NodeId; import com.swirlds.platform.state.PlatformState; +import com.swirlds.platform.state.signed.SignedState; import edu.umd.cs.findbugs.annotations.NonNull; import edu.umd.cs.findbugs.annotations.Nullable; import java.io.BufferedReader; @@ -75,13 +74,6 @@ * {@link SavedStateMetadataField#NUMBER_OF_CONSENSUS_EVENTS} * @param consensusTimestamp the consensus timestamp of this state, corresponds to * {@link SavedStateMetadataField#CONSENSUS_TIMESTAMP} - * @param runningEventHash the running hash of all events, starting from genesis, that have been handled - * to create this state, corresponds to - * {@link SavedStateMetadataField#RUNNING_EVENT_HASH}. (For networks that were - * running prior to the running hash being introduced, this will be the running - * hash of events since the introduction of the running hash.) - * @param runningEventHashMnemonic the mnemonic for the {@link #runningEventHash}. Corresponds to - * {@link SavedStateMetadataField#RUNNING_EVENT_HASH_MNEMONIC}. * @param legacyRunningEventHash the legacy running event hash used by the consensus event stream, corresponds * to {@link SavedStateMetadataField#LEGACY_RUNNING_EVENT_HASH}. * @param legacyRunningEventHashMnemonic the mnemonic for the {@link #legacyRunningEventHash}, corresponds to @@ -110,8 +102,6 @@ public record SavedStateMetadata( @NonNull String hashMnemonic, long numberOfConsensusEvents, @NonNull Instant consensusTimestamp, - @Nullable Hash runningEventHash, - @Nullable String runningEventHashMnemonic, @Nullable Hash legacyRunningEventHash, @Nullable String legacyRunningEventHashMnemonic, long minimumGenerationNonAncient, @@ -158,8 +148,6 @@ public static SavedStateMetadata parse(final Path metadataFile) throws IOExcepti parseNonNullString(data, HASH_MNEMONIC), parsePrimitiveLong(data, NUMBER_OF_CONSENSUS_EVENTS), parseNonNullInstant(data, CONSENSUS_TIMESTAMP), - parseHash(data, RUNNING_EVENT_HASH), - parseString(data, RUNNING_EVENT_HASH_MNEMONIC), parseHash(data, LEGACY_RUNNING_EVENT_HASH), parseString(data, LEGACY_RUNNING_EVENT_HASH_MNEMONIC), parsePrimitiveLong(data, MINIMUM_GENERATION_NON_ANCIENT), @@ -200,10 +188,6 @@ public static SavedStateMetadata create( signedState.getState().getHash().toMnemonic(), platformState.getSnapshot().nextConsensusNumber(), signedState.getConsensusTimestamp(), - platformState.getRunningEventHash(), - platformState.getRunningEventHash() == null - ? null - : platformState.getRunningEventHash().toMnemonic(), platformState.getLegacyRunningEventHash(), platformState.getLegacyRunningEventHash().toMnemonic(), platformState.getAncientThreshold(), @@ -268,7 +252,7 @@ private static Map parseStringMap(@NonNull fina final SavedStateMetadataField key = SavedStateMetadataField.valueOf(keyString); map.put(key, valueString); } catch (final IllegalArgumentException e) { - logger.warn(STARTUP.getMarker(), "Invalid key in metadata file: {}", keyString, e); + logger.warn(STARTUP.getMarker(), "Unrecognized key in metadata file: {}", keyString); } } } @@ -633,8 +617,6 @@ private Map buildStringMap() { putRequireNonNull(map, HASH_MNEMONIC, hashMnemonic); putRequireNonNull(map, NUMBER_OF_CONSENSUS_EVENTS, numberOfConsensusEvents); putRequireNonNull(map, CONSENSUS_TIMESTAMP, consensusTimestamp); - putRequireNonNull(map, RUNNING_EVENT_HASH, runningEventHash); - putRequireNonNull(map, RUNNING_EVENT_HASH_MNEMONIC, runningEventHashMnemonic); putRequireNonNull(map, LEGACY_RUNNING_EVENT_HASH, legacyRunningEventHash); putRequireNonNull(map, LEGACY_RUNNING_EVENT_HASH_MNEMONIC, legacyRunningEventHashMnemonic); putRequireNonNull(map, MINIMUM_GENERATION_NON_ANCIENT, minimumGenerationNonAncient); diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/signed/SavedStateMetadataField.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/snapshot/SavedStateMetadataField.java similarity index 75% rename from platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/signed/SavedStateMetadataField.java rename to platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/snapshot/SavedStateMetadataField.java index e55a208140f8..178fb668bcc8 100644 --- a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/signed/SavedStateMetadataField.java +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/snapshot/SavedStateMetadataField.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.swirlds.platform.state.signed; +package com.swirlds.platform.state.snapshot; /** * Fields written to the signed state metadata file. @@ -40,18 +40,6 @@ public enum SavedStateMetadataField { * The consensus timestamp of this state. */ CONSENSUS_TIMESTAMP, - /** - * The running hash of all events, starting from genesis, that have been handled to create this state. (If this is - * on a network that was created before the running event hash was computed in the current way, then this will be - * the running event hash since the current running event hash algorithm was introduced.) - */ - RUNNING_EVENT_HASH, - /** - * The running hash of all events, starting from genesis, that have been handled to create this state, in mnemonic - * form. (If this is on a network that was created before the running event hash was computed in the current way, - * then this will be the running event hash since the current running event hash algorithm was introduced.) - */ - RUNNING_EVENT_HASH_MNEMONIC, /** * The legacy running event hash used by the consensus event stream. */ diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/signed/SignedStateFilePath.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/snapshot/SignedStateFilePath.java similarity index 98% rename from platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/signed/SignedStateFilePath.java rename to platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/snapshot/SignedStateFilePath.java index 89b84e6a4c72..bcebf35b2ba7 100644 --- a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/signed/SignedStateFilePath.java +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/snapshot/SignedStateFilePath.java @@ -14,11 +14,11 @@ * limitations under the License. */ -package com.swirlds.platform.state.signed; +package com.swirlds.platform.state.snapshot; import static com.swirlds.common.io.utility.FileUtils.getAbsolutePath; import static com.swirlds.logging.legacy.LogMarker.EXCEPTION; -import static com.swirlds.platform.state.signed.SignedStateFileUtils.SIGNED_STATE_FILE_NAME; +import static com.swirlds.platform.state.snapshot.SignedStateFileUtils.SIGNED_STATE_FILE_NAME; import static java.nio.file.Files.exists; import static java.nio.file.Files.isDirectory; diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/signed/SignedStateFileReader.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/snapshot/SignedStateFileReader.java similarity index 94% rename from platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/signed/SignedStateFileReader.java rename to platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/snapshot/SignedStateFileReader.java index 3df70f31c0dd..20b3b04fb8b9 100644 --- a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/signed/SignedStateFileReader.java +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/snapshot/SignedStateFileReader.java @@ -14,11 +14,11 @@ * limitations under the License. */ -package com.swirlds.platform.state.signed; +package com.swirlds.platform.state.snapshot; import static com.swirlds.common.io.streams.StreamDebugUtils.deserializeAndDebugOnFailure; -import static com.swirlds.platform.state.signed.SignedStateFileUtils.MAX_MERKLE_NODES_IN_STATE; -import static com.swirlds.platform.state.signed.SignedStateFileUtils.VERSIONED_FILE_BYTE; +import static com.swirlds.platform.state.snapshot.SignedStateFileUtils.MAX_MERKLE_NODES_IN_STATE; +import static com.swirlds.platform.state.snapshot.SignedStateFileUtils.VERSIONED_FILE_BYTE; import static java.nio.file.Files.exists; import com.swirlds.common.config.StateCommonConfig; @@ -29,6 +29,8 @@ import com.swirlds.common.platform.NodeId; import com.swirlds.platform.crypto.CryptoStatic; import com.swirlds.platform.state.State; +import com.swirlds.platform.state.signed.SigSet; +import com.swirlds.platform.state.signed.SignedState; import edu.umd.cs.findbugs.annotations.NonNull; import java.io.BufferedInputStream; import java.io.FileInputStream; diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/signed/SignedStateFileUtils.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/snapshot/SignedStateFileUtils.java similarity index 97% rename from platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/signed/SignedStateFileUtils.java rename to platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/snapshot/SignedStateFileUtils.java index aa0e32ce3744..0d10c2fec6cc 100644 --- a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/signed/SignedStateFileUtils.java +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/snapshot/SignedStateFileUtils.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.swirlds.platform.state.signed; +package com.swirlds.platform.state.snapshot; /** * Utility methods for dealing with signed states on disk. diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/signed/SignedStateFileWriter.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/snapshot/SignedStateFileWriter.java similarity index 94% rename from platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/signed/SignedStateFileWriter.java rename to platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/snapshot/SignedStateFileWriter.java index daf74bfcd893..0bbd6d2c7e1d 100644 --- a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/signed/SignedStateFileWriter.java +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/snapshot/SignedStateFileWriter.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.swirlds.platform.state.signed; +package com.swirlds.platform.state.snapshot; import static com.swirlds.common.io.utility.FileUtils.executeAndRename; import static com.swirlds.common.io.utility.FileUtils.writeAndFlush; @@ -22,11 +22,11 @@ import static com.swirlds.logging.legacy.LogMarker.STATE_TO_DISK; import static com.swirlds.platform.config.internal.PlatformConfigUtils.writeSettingsUsed; import static com.swirlds.platform.event.preconsensus.BestEffortPcesFileCopy.copyPcesFilesRetryOnFailure; -import static com.swirlds.platform.state.signed.SignedStateFileUtils.CURRENT_ADDRESS_BOOK_FILE_NAME; -import static com.swirlds.platform.state.signed.SignedStateFileUtils.FILE_VERSION; -import static com.swirlds.platform.state.signed.SignedStateFileUtils.HASH_INFO_FILE_NAME; -import static com.swirlds.platform.state.signed.SignedStateFileUtils.SIGNED_STATE_FILE_NAME; -import static com.swirlds.platform.state.signed.SignedStateFileUtils.VERSIONED_FILE_BYTE; +import static com.swirlds.platform.state.snapshot.SignedStateFileUtils.CURRENT_ADDRESS_BOOK_FILE_NAME; +import static com.swirlds.platform.state.snapshot.SignedStateFileUtils.FILE_VERSION; +import static com.swirlds.platform.state.snapshot.SignedStateFileUtils.HASH_INFO_FILE_NAME; +import static com.swirlds.platform.state.snapshot.SignedStateFileUtils.SIGNED_STATE_FILE_NAME; +import static com.swirlds.platform.state.snapshot.SignedStateFileUtils.VERSIONED_FILE_BYTE; import com.swirlds.common.context.PlatformContext; import com.swirlds.common.io.streams.MerkleDataOutputStream; @@ -36,6 +36,7 @@ import com.swirlds.platform.config.StateConfig; import com.swirlds.platform.recovery.emergencyfile.EmergencyRecoveryFile; import com.swirlds.platform.state.State; +import com.swirlds.platform.state.signed.SignedState; import com.swirlds.platform.system.address.AddressBook; import edu.umd.cs.findbugs.annotations.NonNull; import edu.umd.cs.findbugs.annotations.Nullable; diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/signed/StateDumpRequest.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/snapshot/StateDumpRequest.java similarity index 95% rename from platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/signed/StateDumpRequest.java rename to platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/snapshot/StateDumpRequest.java index ab594d2406c8..15bbd7ccc415 100644 --- a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/signed/StateDumpRequest.java +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/snapshot/StateDumpRequest.java @@ -14,10 +14,11 @@ * limitations under the License. */ -package com.swirlds.platform.state.signed; +package com.swirlds.platform.state.snapshot; import com.swirlds.common.threading.interrupt.InterruptableRunnable; import com.swirlds.common.threading.interrupt.Uninterruptable; +import com.swirlds.platform.state.signed.ReservedSignedState; import edu.umd.cs.findbugs.annotations.NonNull; import java.util.concurrent.CountDownLatch; diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/signed/StateSavingResult.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/snapshot/StateSavingResult.java similarity index 96% rename from platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/signed/StateSavingResult.java rename to platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/snapshot/StateSavingResult.java index bd271f4d4423..668d2b67d670 100644 --- a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/signed/StateSavingResult.java +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/snapshot/StateSavingResult.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.swirlds.platform.state.signed; +package com.swirlds.platform.state.snapshot; import edu.umd.cs.findbugs.annotations.NonNull; import java.time.Instant; diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/snapshot/StateSnapshotManager.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/snapshot/StateSnapshotManager.java new file mode 100644 index 000000000000..e35a403b496c --- /dev/null +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/snapshot/StateSnapshotManager.java @@ -0,0 +1,86 @@ +/* + * Copyright (C) 2018-2024 Hedera Hashgraph, LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.swirlds.platform.state.snapshot; + +import com.swirlds.platform.listeners.StateWriteToDiskCompleteNotification; +import com.swirlds.platform.state.signed.ReservedSignedState; +import com.swirlds.platform.system.status.actions.PlatformStatusAction; +import com.swirlds.platform.system.status.actions.StateWrittenToDiskAction; +import edu.umd.cs.findbugs.annotations.NonNull; +import edu.umd.cs.findbugs.annotations.Nullable; + +/** + * This class is responsible for managing the signed state writing pipeline. + */ +public interface StateSnapshotManager { + + /** + * Method to be called when a state needs to be written to disk in-band. An "in-band" write is part of normal + * platform operations, whereas an out-of-band write is triggered due to a fault, or for debug purposes. + *

    + * This method shouldn't be called if the state was written out-of-band. + * + * @param reservedSignedState the state to be written to disk. it is expected that the state is reserved prior to + * this method call and this method will release the reservation when it is done + * @return the result of the state saving operation, or null if the state was not saved + */ + @Nullable + StateSavingResult saveStateTask(@NonNull ReservedSignedState reservedSignedState); + + /** + * Method to be called when a state needs to be written to disk out-of-band. An "in-band" write is part of normal + * platform operations, whereas an out-of-band write is triggered due to a fault, or for debug purposes. + * + * @param request a request to dump a state to disk. it is expected that the state inside the request is reserved + * prior to this method call and this method will release the reservation when it is done + */ + void dumpStateTask(@NonNull StateDumpRequest request); + + /** + * Convert a {@link StateSavingResult} to a {@link StateWriteToDiskCompleteNotification}. + * + * @param result the result of the state saving operation + * @return the notification + */ + @NonNull + default StateWriteToDiskCompleteNotification toNotification(@NonNull final StateSavingResult result) { + return new StateWriteToDiskCompleteNotification( + result.round(), result.consensusTimestamp(), result.freezeState()); + } + + /** + * Extract the oldest minimum generation on disk from a {@link StateSavingResult}. + * + * @param result the result of the state saving operation + * @return the oldest minimum generation on disk + */ + @NonNull + default Long extractOldestMinimumGenerationOnDisk(@NonNull final StateSavingResult result) { + return result.oldestMinimumGenerationOnDisk(); + } + + /** + * Convert a {@link StateSavingResult} to a {@link PlatformStatusAction}. + * + * @param result the result of the state saving operation + * @return the action + */ + @NonNull + default PlatformStatusAction toStateWrittenToDiskAction(@NonNull final StateSavingResult result) { + return new StateWrittenToDiskAction(result.round(), result.freezeState()); + } +} diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/snapshot/StateSnapshotManagerMetrics.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/snapshot/StateSnapshotManagerMetrics.java new file mode 100644 index 000000000000..85359e2be7bc --- /dev/null +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/snapshot/StateSnapshotManagerMetrics.java @@ -0,0 +1,94 @@ +/* + * Copyright (C) 2024 Hedera Hashgraph, LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.swirlds.platform.state.snapshot; + +import com.swirlds.common.context.PlatformContext; +import com.swirlds.common.metrics.RunningAverageMetric; +import com.swirlds.metrics.api.Counter; +import com.swirlds.metrics.api.Metrics; +import edu.umd.cs.findbugs.annotations.NonNull; + +/** + * Encapsulates metrics for the state snapshot manager. + */ +public class StateSnapshotManagerMetrics { + + private static final RunningAverageMetric.Config WRITE_STATE_TO_DISK_TIME_CONFIG = new RunningAverageMetric.Config( + "platform", "writeStateToDisk") + .withDescription("average time it takes to write a SignedState to disk (in milliseconds)") + .withUnit("ms"); + + private final RunningAverageMetric writeStateToDiskTime; + + private static final RunningAverageMetric.Config STATE_TO_DISK_TIME_CONFIG = new RunningAverageMetric.Config( + "platform", "stateToDisk") + .withDescription("average time it takes to do perform all actions when writing a SignedState to disk " + + "(in milliseconds)") + .withUnit("ms"); + private final RunningAverageMetric stateToDiskTime; + + private static final Counter.Config TOTAL_NEVER_SIGNED_DISK_STATES_CONFIG = new Counter.Config( + "platform", "totalNeverSignedDiskStates") + .withDescription( + "total number of disk-bound states that did not receive enough signatures in the allowed time") + .withUnit("count"); + private final Counter totalNeverSignedDiskStates; + + /** + * Constructor. + * + * @param platformContext the platform context + */ + public StateSnapshotManagerMetrics(@NonNull final PlatformContext platformContext) { + final Metrics metrics = platformContext.getMetrics(); + + stateToDiskTime = metrics.getOrCreate(STATE_TO_DISK_TIME_CONFIG); + writeStateToDiskTime = metrics.getOrCreate(WRITE_STATE_TO_DISK_TIME_CONFIG); + totalNeverSignedDiskStates = metrics.getOrCreate(TOTAL_NEVER_SIGNED_DISK_STATES_CONFIG); + } + + /** + * Get a metric tracking the average time required to write a state to disk. + * + * @return the metric tracking the average time required to write a state to disk + */ + @NonNull + public RunningAverageMetric getWriteStateToDiskTimeMetric() { + return writeStateToDiskTime; + } + + /** + * Get a metric tracking the average time required to perform all actions when saving a state to disk, i.e. + * notifying listeners and cleaning up old states on disk. + * + * @return the metric tracking the average time required to perform all actions when saving a state to disk + */ + @NonNull + public RunningAverageMetric getStateToDiskTimeMetric() { + return stateToDiskTime; + } + + /** + * Get a metric tracking the total number of unsigned states written to disk. + * + * @return the metric tracking the total number of unsigned states written to disk + */ + @NonNull + public Counter getTotalUnsignedDiskStatesMetric() { + return totalNeverSignedDiskStates; + } +} diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/signed/StateToDiskReason.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/snapshot/StateToDiskReason.java similarity index 98% rename from platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/signed/StateToDiskReason.java rename to platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/snapshot/StateToDiskReason.java index 26130752afca..cc46f8dee21e 100644 --- a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/signed/StateToDiskReason.java +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/snapshot/StateToDiskReason.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.swirlds.platform.state.signed; +package com.swirlds.platform.state.snapshot; import edu.umd.cs.findbugs.annotations.NonNull; import java.util.Objects; diff --git a/hedera-node/hedera-app-spi/src/main/java/com/hedera/node/app/spi/state/ReadableKVStateBase.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/spi/ReadableKVStateBase.java similarity index 97% rename from hedera-node/hedera-app-spi/src/main/java/com/hedera/node/app/spi/state/ReadableKVStateBase.java rename to platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/spi/ReadableKVStateBase.java index 023d8a145a43..e97c47312876 100644 --- a/hedera-node/hedera-app-spi/src/main/java/com/hedera/node/app/spi/state/ReadableKVStateBase.java +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/spi/ReadableKVStateBase.java @@ -14,8 +14,10 @@ * limitations under the License. */ -package com.hedera.node.app.spi.state; +package com.swirlds.platform.state.spi; +import com.swirlds.state.spi.ReadableKVState; +import com.swirlds.state.spi.WritableKVState; import edu.umd.cs.findbugs.annotations.NonNull; import edu.umd.cs.findbugs.annotations.Nullable; import java.util.*; diff --git a/hedera-node/hedera-app-spi/src/main/java/com/hedera/node/app/spi/state/ReadableQueueStateBase.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/spi/ReadableQueueStateBase.java similarity index 95% rename from hedera-node/hedera-app-spi/src/main/java/com/hedera/node/app/spi/state/ReadableQueueStateBase.java rename to platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/spi/ReadableQueueStateBase.java index 3057747ae5c6..ce8f18f8ae21 100644 --- a/hedera-node/hedera-app-spi/src/main/java/com/hedera/node/app/spi/state/ReadableQueueStateBase.java +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/spi/ReadableQueueStateBase.java @@ -14,10 +14,11 @@ * limitations under the License. */ -package com.hedera.node.app.spi.state; +package com.swirlds.platform.state.spi; import static java.util.Objects.requireNonNull; +import com.swirlds.state.spi.ReadableQueueState; import edu.umd.cs.findbugs.annotations.NonNull; import edu.umd.cs.findbugs.annotations.Nullable; import java.util.Iterator; diff --git a/hedera-node/hedera-app-spi/src/main/java/com/hedera/node/app/spi/state/ReadableSingletonStateBase.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/spi/ReadableSingletonStateBase.java similarity index 95% rename from hedera-node/hedera-app-spi/src/main/java/com/hedera/node/app/spi/state/ReadableSingletonStateBase.java rename to platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/spi/ReadableSingletonStateBase.java index 8464cb54bba9..9c8abd63b28e 100644 --- a/hedera-node/hedera-app-spi/src/main/java/com/hedera/node/app/spi/state/ReadableSingletonStateBase.java +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/spi/ReadableSingletonStateBase.java @@ -14,8 +14,9 @@ * limitations under the License. */ -package com.hedera.node.app.spi.state; +package com.swirlds.platform.state.spi; +import com.swirlds.state.spi.ReadableSingletonState; import edu.umd.cs.findbugs.annotations.NonNull; import java.util.Objects; import java.util.function.Supplier; diff --git a/hedera-node/hedera-app-spi/src/main/java/com/hedera/node/app/spi/state/WritableKVStateBase.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/spi/WritableKVStateBase.java similarity index 99% rename from hedera-node/hedera-app-spi/src/main/java/com/hedera/node/app/spi/state/WritableKVStateBase.java rename to platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/spi/WritableKVStateBase.java index 36f3b2baf544..8331c41b41d2 100644 --- a/hedera-node/hedera-app-spi/src/main/java/com/hedera/node/app/spi/state/WritableKVStateBase.java +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/spi/WritableKVStateBase.java @@ -14,8 +14,9 @@ * limitations under the License. */ -package com.hedera.node.app.spi.state; +package com.swirlds.platform.state.spi; +import com.swirlds.state.spi.WritableKVState; import edu.umd.cs.findbugs.annotations.NonNull; import edu.umd.cs.findbugs.annotations.Nullable; import java.util.*; diff --git a/hedera-node/hedera-app-spi/src/main/java/com/hedera/node/app/spi/state/WritableQueueStateBase.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/spi/WritableQueueStateBase.java similarity index 98% rename from hedera-node/hedera-app-spi/src/main/java/com/hedera/node/app/spi/state/WritableQueueStateBase.java rename to platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/spi/WritableQueueStateBase.java index 988635ae9417..4ae1212476c1 100644 --- a/hedera-node/hedera-app-spi/src/main/java/com/hedera/node/app/spi/state/WritableQueueStateBase.java +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/spi/WritableQueueStateBase.java @@ -14,10 +14,11 @@ * limitations under the License. */ -package com.hedera.node.app.spi.state; +package com.swirlds.platform.state.spi; import static java.util.Objects.requireNonNull; +import com.swirlds.state.spi.WritableQueueState; import edu.umd.cs.findbugs.annotations.NonNull; import edu.umd.cs.findbugs.annotations.Nullable; import java.util.ArrayList; diff --git a/hedera-node/hedera-app-spi/src/main/java/com/hedera/node/app/spi/state/WritableSingletonStateBase.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/spi/WritableSingletonStateBase.java similarity index 96% rename from hedera-node/hedera-app-spi/src/main/java/com/hedera/node/app/spi/state/WritableSingletonStateBase.java rename to platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/spi/WritableSingletonStateBase.java index f2bfb20b4909..292e4a574797 100644 --- a/hedera-node/hedera-app-spi/src/main/java/com/hedera/node/app/spi/state/WritableSingletonStateBase.java +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/spi/WritableSingletonStateBase.java @@ -14,8 +14,9 @@ * limitations under the License. */ -package com.hedera.node.app.spi.state; +package com.swirlds.platform.state.spi; +import com.swirlds.state.spi.WritableSingletonState; import edu.umd.cs.findbugs.annotations.NonNull; import java.util.Objects; import java.util.function.Consumer; diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/system/Platform.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/system/Platform.java index 26bfb6d12dbd..92734027cbf8 100644 --- a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/system/Platform.java +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/system/Platform.java @@ -16,21 +16,25 @@ package com.swirlds.platform.system; -import com.swirlds.base.state.Startable; import com.swirlds.common.context.PlatformContext; +import com.swirlds.common.crypto.Signature; import com.swirlds.common.notification.NotificationEngine; -import com.swirlds.common.stream.Signer; +import com.swirlds.common.platform.NodeId; +import com.swirlds.common.utility.AutoCloseableWrapper; +import com.swirlds.platform.system.address.AddressBook; +import edu.umd.cs.findbugs.annotations.NonNull; /** * An interface for Swirlds Platform. */ -public interface Platform extends PlatformIdentity, Startable, StateAccessor, Signer, TransactionSubmitter { +public interface Platform { /** * Get the platform context, which contains various utilities and services provided by the platform. * * @return this node's platform context */ + @NonNull PlatformContext getContext(); /** @@ -38,5 +42,76 @@ public interface Platform extends PlatformIdentity, Startable, StateAccessor, Si * * @return a notification engine */ + @NonNull NotificationEngine getNotificationEngine(); + + /** + * Get the Address Book + * + * @return AddressBook + */ + @NonNull + AddressBook getAddressBook(); + + /** + * Get the ID of current node + * + * @return node ID + */ + @NonNull + NodeId getSelfId(); + + /** + * Get the most recent immutable state. This state may or may not be hashed when it is returned. Wrapper must be + * closed when use of the state is no longer needed else resources may be leaked. + * + * @param reason a short description of why this SignedState is being reserved. Each location where a SignedState is + * reserved should attempt to use a unique reason, as this makes debugging reservation bugs easier. + * @param the type of the state + * @return a wrapper around the most recent immutable state + */ + @NonNull + AutoCloseableWrapper getLatestImmutableState(@NonNull final String reason); + + /** + * This method can be called to create a new transaction. If accepted by this method, the newly-created transaction + * will eventually be embedded inside a newly-created event as long as the node does not shut down before getting + * around to it. As long as this node is healthy, with high probability the transaction will reach consensus, but + * this is not a hard guarantee. + *

    + * This method will sometimes reject new transactions. Some (but not necessarily all) causes for a transaction to be + * rejected by this method: + * + *

      + *
    • the node is starting up
    • + *
    • the node is performing a reconnect
    • + *
    • the node is preparing for an upgrade
    • + *
    • the node is unable to submit transactions fast enough and has built up a backlog
    • + *
    • the node is having health problems
    • + *
    + * + *

    + * Transactions have a maximum size defined by the setting "transactionMaxBytes". If a transaction larger than + * this is submitted, this method will always reject it. + * + * @param transaction the transaction to handle in binary format (format used is up to the application) + * @return true if the transaction is accepted, false if it is rejected. Being accepted does not guarantee that the + * transaction will ever reach consensus, only that this node will make a best-effort attempt to make that happen. + */ + boolean createTransaction(@NonNull byte[] transaction); + + /** + * generate signature bytes for given data + * + * @param data + * an array of bytes + * @return signature bytes + */ + @NonNull + Signature sign(@NonNull byte[] data); + + /** + * Start this platform. + */ + void start(); } diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/system/PlatformIdentity.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/system/PlatformIdentity.java deleted file mode 100644 index 596bac6ed29f..000000000000 --- a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/system/PlatformIdentity.java +++ /dev/null @@ -1,50 +0,0 @@ -/* - * Copyright (C) 2018-2024 Hedera Hashgraph, LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.swirlds.platform.system; - -import com.swirlds.common.platform.NodeId; -import com.swirlds.platform.system.address.Address; -import com.swirlds.platform.system.address.AddressBook; - -/** - * Identification information about a platform. - */ -public interface PlatformIdentity { - - /** - * Get the Address Book - * - * @return AddressBook - */ - AddressBook getAddressBook(); - - /** - * Get the ID of current node - * - * @return node ID - */ - NodeId getSelfId(); - - /** - * Get the address of the current node. - * - * @return this node's address - */ - default Address getSelfAddress() { - return getAddressBook().getAddress(getSelfId()); - } -} diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/system/StateAccessor.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/system/StateAccessor.java deleted file mode 100644 index 2ce5e2010dfe..000000000000 --- a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/system/StateAccessor.java +++ /dev/null @@ -1,37 +0,0 @@ -/* - * Copyright (C) 2018-2024 Hedera Hashgraph, LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.swirlds.platform.system; - -import com.swirlds.common.utility.AutoCloseableWrapper; -import edu.umd.cs.findbugs.annotations.NonNull; - -/** - * Provides access to recent copies of the state. - */ -public interface StateAccessor { - - /** - * Get the most recent immutable state. This state may or may not be hashed when it is returned. Wrapper must be - * closed when use of the state is no longer needed else resources may be leaked. - * - * @param reason a short description of why this SignedState is being reserved. Each location where a SignedState is - * reserved should attempt to use a unique reason, as this makes debugging reservation bugs easier. - * @param the type of the state - * @return a wrapper around the most recent immutable state - */ - AutoCloseableWrapper getLatestImmutableState(@NonNull final String reason); -} diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/system/TransactionSubmitter.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/system/TransactionSubmitter.java deleted file mode 100644 index f0940f7647d4..000000000000 --- a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/system/TransactionSubmitter.java +++ /dev/null @@ -1,52 +0,0 @@ -/* - * Copyright (C) 2022-2024 Hedera Hashgraph, LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.swirlds.platform.system; - -import edu.umd.cs.findbugs.annotations.NonNull; - -/** - * An object that can be used to submit transactions. - */ -public interface TransactionSubmitter { - - /** - * This method can be called to create a new transaction. If accepted by this method, the newly-created transaction - * will eventually be embedded inside a newly-created event as long as the node does not shut down before getting - * around to it. As long as this node is healthy, with high probability the transaction will reach consensus, but - * this is not a hard guarantee. - *

    - * This method will sometimes reject new transactions. Some (but not necessarily all) causes for a transaction to be - * rejected by this method: - * - *

      - *
    • the node is starting up
    • - *
    • the node is performing a reconnect
    • - *
    • the node is preparing for an upgrade
    • - *
    • the node is unable to submit transactions fast enough and has built up a backlog
    • - *
    • the node is having health problems
    • - *
    - * - *

    - * Transactions have a maximum size defined by the setting "transactionMaxBytes". If a transaction larger than - * this is submitted, this method will always reject it. - * - * @param transaction the transaction to handle in binary format (format used is up to the application) - * @return true if the transaction is accepted, false if it is rejected. Being accepted does not guarantee that the - * transaction will ever reach consensus, only that this node will make a best-effort attempt to make that happen. - */ - boolean createTransaction(@NonNull byte[] transaction); -} diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/system/address/Address.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/system/address/Address.java index 0d60b5619038..950e2446478c 100644 --- a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/system/address/Address.java +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/system/address/Address.java @@ -113,34 +113,6 @@ public Address() { this(NodeId.FIRST_NODE_ID, "", "", 1, null, -1, null, -1, null, null, ""); } - public Address( - @NonNull final NodeId id, - @NonNull final String nickname, - @NonNull final String selfName, - final long weight, - @Nullable final String hostnameInternal, - final int portInternal, - @Nullable final String hostnameExternal, - final int portExternal, - @NonNull final String memo) { - this( - id, - nickname, - selfName, - weight, // weight - hostnameInternal, - portInternal, - hostnameExternal, - portExternal, - null, - null, - memo); - } - - private byte[] clone(byte[] x) { - return x == null ? x : x.clone(); - } - /** * constructor for a mutable address for one member. * diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/system/address/AddressBookUtils.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/system/address/AddressBookUtils.java index bde2a4b60798..314ff2cc7ab7 100644 --- a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/system/address/AddressBookUtils.java +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/system/address/AddressBookUtils.java @@ -247,6 +247,8 @@ public static Address parseAddressText(@NonNull final String addressText) throws internalPort, externalHostname, externalPort, + null, + null, memoToUse); } diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/system/events/BaseEventHashedData.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/system/events/BaseEventHashedData.java index 63c874bc6dec..cd705f5f9c14 100644 --- a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/system/events/BaseEventHashedData.java +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/system/events/BaseEventHashedData.java @@ -22,11 +22,10 @@ import com.swirlds.common.config.singleton.ConfigurationHolder; import com.swirlds.common.crypto.AbstractSerializableHashable; import com.swirlds.common.crypto.Hash; -import com.swirlds.common.io.OptionalSelfSerializable; +import com.swirlds.common.io.SelfSerializable; import com.swirlds.common.io.streams.SerializableDataInputStream; import com.swirlds.common.io.streams.SerializableDataOutputStream; import com.swirlds.common.platform.NodeId; -import com.swirlds.common.utility.CommonUtils; import com.swirlds.platform.config.TransactionConfig; import com.swirlds.platform.system.SoftwareVersion; import com.swirlds.platform.system.StaticSoftwareVersion; @@ -48,31 +47,13 @@ *

    * A base event is a set of data describing an event at the point when it is created, before it is added to the * hashgraph and before its consensus can be determined. Some of this data is used to create a hash of an event and some - * data is additional and does not affect the hash. This data is split into 2 classes: {@link BaseEventHashedData} and - * {@link BaseEventUnhashedData}. + * data is additional and does not affect the hash. */ -public class BaseEventHashedData extends AbstractSerializableHashable - implements OptionalSelfSerializable { +public class BaseEventHashedData extends AbstractSerializableHashable implements SelfSerializable { public static final int TO_STRING_BYTE_ARRAY_LENGTH = 5; private static final long CLASS_ID = 0x21c2620e9b6a2243L; public static class ClassVersion { - /** - * In this version, the transactions contained by this event are encoded using LegacyTransaction class. No - * longer supported. - */ - public static final int ORIGINAL = 1; - /** - * In this version, the transactions contained by this event are encoded using a newer version Transaction class - * with different subclasses to support internal system transactions and application transactions - */ - public static final int TRANSACTION_SUBCLASSES = 2; - - /** - * In this version, the software version of the node that created this event is included in the event. - */ - public static final int SOFTWARE_VERSION = 3; - /** * Event descriptors replace the hashes and generation of the parents in the event. Multiple otherParents are * supported. birthRound is added for lookup of the effective roster at the time of event creation. @@ -82,13 +63,6 @@ public static class ClassVersion { public static final int BIRTH_ROUND = 4; } - /** - * The version of the serialization to use. May be overridden by the version encountered when deserializing. - *

    - * DEPRECATED: remove after 0.46.0 goes to mainnet. - */ - private int serializedVersion = ClassVersion.BIRTH_ROUND; - /////////////////////////////////////// // immutable, sent during normal syncs, affects the hash that is signed: /////////////////////////////////////// @@ -184,43 +158,20 @@ public int getMinimumSupportedVersion() { } @Override - public void serialize( - @NonNull final SerializableDataOutputStream out, @NonNull final EventSerializationOptions option) - throws IOException { + public void serialize(final SerializableDataOutputStream out) throws IOException { out.writeSerializable(softwareVersion, true); - if (serializedVersion < ClassVersion.BIRTH_ROUND) { - out.writeLong(creatorId.id()); - out.writeLong(selfParent != null ? selfParent.getGeneration() : EventConstants.GENERATION_UNDEFINED); - out.writeLong( - !otherParents.isEmpty() - ? otherParents.get(0).getGeneration() - : EventConstants.GENERATION_UNDEFINED); - out.writeSerializable(selfParent != null ? selfParent.getHash() : null, false); - out.writeSerializable(!otherParents.isEmpty() ? otherParents.get(0).getHash() : null, false); - } else { - out.writeSerializable(creatorId, false); - out.writeSerializable(selfParent, false); - out.writeSerializableList(otherParents, false, true); - out.writeLong(birthRound); - } + out.writeSerializable(creatorId, false); + out.writeSerializable(selfParent, false); + out.writeSerializableList(otherParents, false, true); + out.writeLong(birthRound); out.writeInstant(timeCreated); // write serialized length of transaction array first, so during the deserialization proces // it is possible to skip transaction array and move on to the next object - if (option == EventSerializationOptions.OMIT_TRANSACTIONS) { - out.writeInt(getSerializedLength(null, true, false)); - out.writeSerializableArray(null, true, false); - } else { - out.writeInt(getSerializedLength(transactions, true, false)); - // transactions may include both system transactions and application transactions - // so writeClassId set to true and allSameClass set to false - out.writeSerializableArray(transactions, true, false); - } - } - - @Override - public void serialize(final SerializableDataOutputStream out) throws IOException { - serialize(out, EventSerializationOptions.FULL); + out.writeInt(getSerializedLength(transactions, true, false)); + // transactions may include both system transactions and application transactions + // so writeClassId set to true and allSameClass set to false + out.writeSerializableArray(transactions, true, false); } @Override @@ -229,11 +180,10 @@ public void deserialize(final SerializableDataInputStream in, final int version) deserialize(in, version, transactionConfig.maxTransactionCountPerEvent()); } - public void deserialize( + private void deserialize( @NonNull final SerializableDataInputStream in, final int version, final int maxTransactionCount) throws IOException { Objects.requireNonNull(in, "The input stream must not be null"); - serializedVersion = version; softwareVersion = in.readSerializable(StaticSoftwareVersion.getSoftwareVersionClassIdSet()); creatorId = in.readSerializable(false, NodeId::new); @@ -289,15 +239,10 @@ public String toString() { .append("birthRound", birthRound) .append("timeCreated", timeCreated) .append("transactions size", transactions == null ? "null" : transactions.length) - .append("hash", CommonUtils.hex(valueOrNull(getHash()), TO_STRING_BYTE_ARRAY_LENGTH)) + .append("hash", getHash() == null ? "null" : getHash().toHex(TO_STRING_BYTE_ARRAY_LENGTH)) .toString(); } - @Nullable - private byte[] valueOrNull(final Hash hash) { - return hash == null ? null : hash.getValue(); - } - @Override public long getClassId() { return CLASS_ID; @@ -305,7 +250,7 @@ public long getClassId() { @Override public int getVersion() { - return serializedVersion; + return ClassVersion.BIRTH_ROUND; } /** diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/system/events/BaseEventUnhashedData.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/system/events/BaseEventUnhashedData.java deleted file mode 100644 index 70a092803b4d..000000000000 --- a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/system/events/BaseEventUnhashedData.java +++ /dev/null @@ -1,165 +0,0 @@ -/* - * Copyright (C) 2016-2024 Hedera Hashgraph, LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.swirlds.platform.system.events; - -import com.swirlds.base.utility.ToStringBuilder; -import com.swirlds.common.io.SelfSerializable; -import com.swirlds.common.io.streams.SerializableDataInputStream; -import com.swirlds.common.io.streams.SerializableDataOutputStream; -import com.swirlds.common.platform.NodeId; -import com.swirlds.common.utility.CommonUtils; -import edu.umd.cs.findbugs.annotations.NonNull; -import java.io.IOException; -import java.util.Arrays; -import java.util.Objects; - -/** - * A class used to store base event data that does not affect the hash of that event. - *

    - * A base event is a set of data describing an event at the point when it is created, before it is added to the - * hashgraph and before its consensus can be determined. Some of this data is used to create a hash of an event that is - * signed, and some data is additional and does not affect that hash. This data is split into 2 classes: - * {@link BaseEventHashedData} and {@link BaseEventUnhashedData}. - */ -public class BaseEventUnhashedData implements SelfSerializable { - private static final long CLASS_ID = 0x33cb9d4ae38c9e91L; - public static final int MAX_SIG_LENGTH = 384; - private static final long SEQUENCE_UNUSED = -1; - - public static class ClassVersion { - /** - * The original version of the BaseEventUnhashedData class. - */ - public static final int ORIGINAL = 1; - - /** - * Removes the serialization of the sequence information and other parent creator id. - * - * @since 0.46.0 - */ - public static final int BIRTH_ROUND = 2; - } - - /** - * The version of the software discovered during deserialization that needs to be preserved in serialization. - *

    - * DEPRECATED: Remove after 0.46.0 is delivered to mainnet. - */ - private int serializedVersion = ClassVersion.BIRTH_ROUND; - - /////////////////////////////////////// - // immutable, sent during normal syncs, does NOT affect the hash that is signed: - /////////////////////////////////////// - - // --------------- NOTE ------------------------------------------------------------------------------------------- - // Sequence number fields are no longer in use, so when an object is constructed, they are set to SEQUENCE_UNUSED - // These fields are still kept because there are a lot of downstream consequences of changing the event stream - // format, so they will be removed along with other fields that should not be part of the stream. - // otherId is also probably not needed anymore, so at some point this class can be replaced with just a Signature - // ---------------------------------------------------------------------------------------------------------------- - - /** ID of otherParent (translate before sending) */ - private NodeId otherId; - /** creator's sig for this */ - private byte[] signature; - - public BaseEventUnhashedData() {} - - public BaseEventUnhashedData(@NonNull final byte[] signature) { - this.signature = Objects.requireNonNull(signature, "signature must not be null"); - } - - @Override - public void serialize(@NonNull final SerializableDataOutputStream out) throws IOException { - if (serializedVersion < ClassVersion.BIRTH_ROUND) { - out.writeLong(SEQUENCE_UNUSED); - out.writeLong(otherId == null ? -1 : otherId.id()); - out.writeLong(SEQUENCE_UNUSED); - out.writeByteArray(signature); - } else { - out.writeByteArray(signature); - } - } - - @Override - public void deserialize(@NonNull final SerializableDataInputStream in, final int version) throws IOException { - serializedVersion = version; - if (version < ClassVersion.BIRTH_ROUND) { - in.readLong(); // unused - otherId = NodeId.deserializeLong(in, true); - in.readLong(); // unused - signature = in.readByteArray(MAX_SIG_LENGTH); - } else { - signature = in.readByteArray(MAX_SIG_LENGTH); - // initialize unused fields (can be removed when the unused fields are removed) - otherId = null; - } - } - - @Override - public boolean equals(final Object o) { - if (this == o) { - return true; - } - - if (o == null || getClass() != o.getClass()) { - return false; - } - - final BaseEventUnhashedData that = (BaseEventUnhashedData) o; - - return Objects.equals(otherId, that.otherId) && Arrays.equals(signature, that.signature); - } - - @Override - public int hashCode() { - return Arrays.hashCode(signature); - } - - @Override - public String toString() { - final int signatureLength = signature == null ? 0 : signature.length; - return new ToStringBuilder(this) - .append("otherId", otherId) - .append("signature", CommonUtils.hex(signature, signatureLength)) - .toString(); - } - - @Override - public long getClassId() { - return CLASS_ID; - } - - @Override - public int getVersion() { - return serializedVersion; - } - - @Override - public int getMinimumSupportedVersion() { - return ClassVersion.ORIGINAL; - } - - /** - * Get the signature - * - * @return the signature - */ - public byte[] getSignature() { - return signature; - } -} diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/system/events/DetailedConsensusEvent.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/system/events/DetailedConsensusEvent.java index 193228ca3944..85428e1338fe 100644 --- a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/system/events/DetailedConsensusEvent.java +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/system/events/DetailedConsensusEvent.java @@ -16,14 +16,15 @@ package com.swirlds.platform.system.events; +import com.hedera.pbj.runtime.io.buffer.Bytes; import com.swirlds.base.utility.ToStringBuilder; import com.swirlds.common.crypto.AbstractSerializableHashable; import com.swirlds.common.crypto.RunningHash; import com.swirlds.common.crypto.RunningHashable; -import com.swirlds.common.io.OptionalSelfSerializable; +import com.swirlds.common.io.SelfSerializable; import com.swirlds.common.io.streams.SerializableDataInputStream; import com.swirlds.common.io.streams.SerializableDataOutputStream; -import com.swirlds.platform.system.events.BaseEventHashedData.ClassVersion; +import com.swirlds.platform.event.GossipEvent; import java.io.IOException; import java.util.Objects; @@ -31,16 +32,13 @@ * An event that may or may not have reached consensus. If it has reached consensus, provides detailed consensus * information. */ -public class DetailedConsensusEvent extends AbstractSerializableHashable - implements OptionalSelfSerializable, RunningHashable { +public class DetailedConsensusEvent extends AbstractSerializableHashable implements SelfSerializable, RunningHashable { public static final long CLASS_ID = 0xe250a9fbdcc4b1baL; public static final int CLASS_VERSION = 1; - /** The hashed part of a base event */ - private BaseEventHashedData baseEventHashedData; - /** The part of a base event which is not hashed */ - private BaseEventUnhashedData baseEventUnhashedData; + /** the pre-consensus event */ + private GossipEvent gossipEvent; /** Consensus data calculated for an event */ private ConsensusData consensusData; /** the running hash of this event */ @@ -54,44 +52,18 @@ public DetailedConsensusEvent() {} /** * Create a new instance with the provided data. * - * @param baseEventHashedData - * event data that is part of the event's hash - * @param baseEventUnhashedData - * event data that is not part of the event's hash - * @param consensusData - * the consensus data for this event + * @param gossipEvent the pre-consensus event + * @param consensusData the consensus data for this event */ - public DetailedConsensusEvent( - final BaseEventHashedData baseEventHashedData, - final BaseEventUnhashedData baseEventUnhashedData, - final ConsensusData consensusData) { - this.baseEventHashedData = baseEventHashedData; - this.baseEventUnhashedData = baseEventUnhashedData; + public DetailedConsensusEvent(final GossipEvent gossipEvent, final ConsensusData consensusData) { + this.gossipEvent = gossipEvent; this.consensusData = consensusData; } - /** - * {@inheritDoc} - */ - @Override - public void serialize(final SerializableDataOutputStream out, final EventSerializationOptions option) - throws IOException { - serialize(out, baseEventHashedData, baseEventUnhashedData, consensusData, option); - } - public static void serialize( - final SerializableDataOutputStream out, - final BaseEventHashedData baseEventHashedData, - final BaseEventUnhashedData baseEventUnhashedData, - final ConsensusData consensusData, - final EventSerializationOptions option) + final SerializableDataOutputStream out, final GossipEvent gossipEvent, final ConsensusData consensusData) throws IOException { - out.writeOptionalSerializable(baseEventHashedData, false, option); - if (baseEventHashedData.getVersion() < ClassVersion.BIRTH_ROUND) { - out.writeSerializable(baseEventUnhashedData, false); - } else { - out.writeByteArray(baseEventUnhashedData.getSignature()); - } + gossipEvent.serialize(out); out.writeSerializable(consensusData, false); } @@ -100,7 +72,7 @@ public static void serialize( */ @Override public void serialize(final SerializableDataOutputStream out) throws IOException { - serialize(out, baseEventHashedData, baseEventUnhashedData, consensusData, EventSerializationOptions.FULL); + serialize(out, gossipEvent, consensusData); } /** @@ -108,13 +80,8 @@ public void serialize(final SerializableDataOutputStream out) throws IOException */ @Override public void deserialize(final SerializableDataInputStream in, final int version) throws IOException { - baseEventHashedData = in.readSerializable(false, BaseEventHashedData::new); - if (baseEventHashedData.getVersion() < ClassVersion.BIRTH_ROUND) { - baseEventUnhashedData = in.readSerializable(false, BaseEventUnhashedData::new); - } else { - final byte[] signature = in.readByteArray(BaseEventUnhashedData.MAX_SIG_LENGTH); - baseEventUnhashedData = new BaseEventUnhashedData(signature); - } + this.gossipEvent = new GossipEvent(); + this.gossipEvent.deserialize(in, gossipEvent.getVersion()); consensusData = in.readSerializable(false, ConsensusData::new); } @@ -124,17 +91,17 @@ public RunningHash getRunningHash() { } /** - * Returns the event data that is part of this event's hash. + * @return the pre-consensus event */ - public BaseEventHashedData getBaseEventHashedData() { - return baseEventHashedData; + public GossipEvent getGossipEvent() { + return gossipEvent; } /** - * Returns the event data that is not part of this event's hash. + * @return the signature for the event */ - public BaseEventUnhashedData getBaseEventUnhashedData() { - return baseEventUnhashedData; + public Bytes getSignature() { + return gossipEvent.getSignature(); } /** @@ -165,7 +132,7 @@ public int getVersion() { */ @Override public int hashCode() { - return Objects.hash(baseEventHashedData, baseEventUnhashedData, consensusData); + return Objects.hash(gossipEvent, consensusData); } /** @@ -180,9 +147,7 @@ public boolean equals(final Object other) { return false; } final DetailedConsensusEvent that = (DetailedConsensusEvent) other; - return Objects.equals(baseEventHashedData, that.baseEventHashedData) - && Objects.equals(baseEventUnhashedData, that.baseEventUnhashedData) - && Objects.equals(consensusData, that.consensusData); + return Objects.equals(gossipEvent, that.gossipEvent) && Objects.equals(consensusData, that.consensusData); } /** @@ -191,8 +156,7 @@ public boolean equals(final Object other) { @Override public String toString() { return new ToStringBuilder(this) - .append("baseEventHashedData", baseEventHashedData) - .append("baseEventUnhashedData", baseEventUnhashedData) + .append("gossipEvent", gossipEvent) .append("consensusData", consensusData) .toString(); } diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/system/transaction/StateSignatureTransaction.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/system/transaction/StateSignatureTransaction.java index 5f5e4579e49c..89031fcf558b 100644 --- a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/system/transaction/StateSignatureTransaction.java +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/system/transaction/StateSignatureTransaction.java @@ -165,14 +165,6 @@ public int hashCode() { return Objects.hash(payload); } - /** - * This method just returns null. It is a temporary method that will be removed once we switch to StateSignaturePayload. - */ - @Override - public byte[] getContents() { - return null; - } - @Override public @NonNull OneOf getPayload() { return payload; diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/system/transaction/SwirldTransaction.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/system/transaction/SwirldTransaction.java index 6b014e2a3190..10295eb94b47 100644 --- a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/system/transaction/SwirldTransaction.java +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/system/transaction/SwirldTransaction.java @@ -16,20 +16,17 @@ package com.swirlds.platform.system.transaction; -import static com.swirlds.common.io.streams.AugmentedDataOutputStream.getArraySerializedLength; - import com.hedera.hapi.platform.event.EventPayload.PayloadOneOfType; import com.hedera.pbj.runtime.OneOf; import com.hedera.pbj.runtime.io.buffer.Bytes; -import com.swirlds.base.utility.ToStringBuilder; import com.swirlds.common.config.singleton.ConfigurationHolder; import com.swirlds.common.io.streams.SerializableDataInputStream; import com.swirlds.common.io.streams.SerializableDataOutputStream; import com.swirlds.platform.config.TransactionConfig; import edu.umd.cs.findbugs.annotations.NonNull; import java.io.IOException; -import java.util.Arrays; import java.util.HashMap; +import java.util.Objects; /** * A container for an application transaction that contains extra information. @@ -39,6 +36,9 @@ * application as one after it does reach consensus. */ public class SwirldTransaction extends ConsensusTransactionImpl implements Comparable { + /** ensures that payload is never null even when constructed with the no-args constructor */ + private static final OneOf DEFAULT_PAYLOAD = + new OneOf<>(PayloadOneOfType.APPLICATION_PAYLOAD, Bytes.EMPTY); /** class identifier for the purposes of serialization */ public static final long CLASS_ID = 0x9ff79186f4c4db97L; /** current class version */ @@ -46,10 +46,8 @@ public class SwirldTransaction extends ConsensusTransactionImpl implements Compa private static final String CONTENT_ERROR = "content is null or length is 0"; - /** The content (payload) of the transaction */ - private byte[] contents; /** The data stored as protobuf */ - private OneOf payload; + private OneOf payload = DEFAULT_PAYLOAD; public SwirldTransaction() {} @@ -65,8 +63,7 @@ public SwirldTransaction(final byte[] contents) { if (contents == null || contents.length == 0) { throw new IllegalArgumentException(CONTENT_ERROR); } - this.contents = contents.clone(); - this.payload = new OneOf<>(PayloadOneOfType.APPLICATION_PAYLOAD, Bytes.wrap(this.contents)); + this.payload = new OneOf<>(PayloadOneOfType.APPLICATION_PAYLOAD, Bytes.wrap(contents.clone())); } /** @@ -74,7 +71,9 @@ public SwirldTransaction(final byte[] contents) { */ @Override public void serialize(final SerializableDataOutputStream out) throws IOException { - out.writeByteArray(contents); + final Bytes bytes = getBytes(); + out.writeInt((int) bytes.length()); // array length + bytes.writeTo(out); } /** @@ -83,38 +82,8 @@ public void serialize(final SerializableDataOutputStream out) throws IOException @Override public void deserialize(final SerializableDataInputStream in, final int version) throws IOException { final TransactionConfig transactionConfig = ConfigurationHolder.getConfigData(TransactionConfig.class); - this.contents = in.readByteArray(transactionConfig.transactionMaxBytes()); - this.payload = new OneOf<>(PayloadOneOfType.APPLICATION_PAYLOAD, Bytes.wrap(this.contents)); - } - - /** - * {@inheritDoc} - */ - @Override - public byte[] getContents() { - return contents; - } - - /** - * Returns the size of the transaction content/payload. - * - * This method is thread-safe and guaranteed to be atomic in nature. - * - * @return the length of the transaction content - */ - public int getLength() { - return (contents != null) ? contents.length : 0; - } - - /** - * Returns the size of the transaction content/payload. - * - * This method is thread-safe and guaranteed to be atomic in nature. - * - * @return the length of the transaction content - */ - public int size() { - return getLength(); + final byte[] contents = in.readByteArray(transactionConfig.transactionMaxBytes()); + this.payload = new OneOf<>(PayloadOneOfType.APPLICATION_PAYLOAD, Bytes.wrap(contents)); } /** @@ -145,7 +114,7 @@ public int size() { */ @Override public int hashCode() { - return Arrays.hashCode(contents); + return getBytes().hashCode(); } /** @@ -186,11 +155,10 @@ public boolean equals(final Object obj) { if (this == obj) { return true; } - if (!(obj instanceof SwirldTransaction)) { + if (!(obj instanceof final SwirldTransaction that)) { return false; } - final SwirldTransaction that = (SwirldTransaction) obj; - return Arrays.equals(contents, that.contents); + return Objects.equals(payload, that.payload); } /** @@ -244,7 +212,7 @@ public int compareTo(final SwirldTransaction that) { throw new IllegalArgumentException(); } - return Arrays.compare(contents, that.contents); + return this.getBytes().compareTo(that.getBytes()); } /** @@ -252,7 +220,7 @@ public int compareTo(final SwirldTransaction that) { */ @Override public String toString() { - return new ToStringBuilder(this).append("contents", contents).toString(); + return "payload: " + getBytes().toHex(); } /** @@ -276,7 +244,8 @@ public int getVersion() { */ @Override public int getSerializedLength() { - return getArraySerializedLength(contents); + return Integer.BYTES // add the the size of array length field + + getSize(); // add the size of the array } /** @@ -284,7 +253,11 @@ public int getSerializedLength() { */ @Override public int getSize() { - return contents == null ? 0 : contents.length; + return (int) getBytes().length(); + } + + private Bytes getBytes() { + return payload.as(); } @Override diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/system/transaction/Transaction.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/system/transaction/Transaction.java index f948c1e02b8a..03de193ea0ea 100644 --- a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/system/transaction/Transaction.java +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/system/transaction/Transaction.java @@ -30,24 +30,9 @@ * objects. The list of signatures features controlled mutability with a thread-safe and atomic implementation. The * transaction internally uses a {@link ReadWriteLock} to provide atomic reads and writes to the underlying list of * signatures. - *

    - * The contents provided by this class via {@link #getContents()} must never be mutated. Providing the direct (mutable) - * reference improves performance by eliminating the need to create copies. - *

    */ public sealed interface Transaction extends SerializableWithKnownLength permits ConsensusTransaction { - /** - * Returns a direct (mutable) reference to the transaction contents/payload. Care must be - * taken to never modify the array returned by this accessor. Modifying the array will result in undefined - * behaviors. - * - * @return a direct reference to the transaction content/payload - * @deprecated this method will be removed once the migration to protobuf is complete. Use {@link #getPayload()} instead - */ - @Deprecated - byte[] getContents(); - /** * Returns the payload as a PBJ record * @return the payload diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/threading/PauseAndClear.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/threading/PauseAndClear.java deleted file mode 100644 index 6605e80464f5..000000000000 --- a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/threading/PauseAndClear.java +++ /dev/null @@ -1,50 +0,0 @@ -/* - * Copyright (C) 2018-2024 Hedera Hashgraph, LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.swirlds.platform.threading; - -import com.swirlds.common.threading.framework.StoppableThread; -import com.swirlds.common.utility.Clearable; - -/** - * Pauses a thread while a {@link Clearable} is cleared. This is useful for externally clearing instances that are not - * thread-safe - */ -public class PauseAndClear implements Clearable { - private final StoppableThread thread; - private final Clearable clearable; - - /** - * @param thread - * the thread to pause - * @param clearable - * the {@link Clearable} to clear - */ - public PauseAndClear(final StoppableThread thread, final Clearable clearable) { - this.thread = thread; - this.clearable = clearable; - } - - @Override - public void clear() { - thread.pause(); - try { - clearable.clear(); - } finally { - thread.resume(); - } - } -} diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/threading/PauseAndLoad.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/threading/PauseAndLoad.java deleted file mode 100644 index 17a6ab2d8975..000000000000 --- a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/threading/PauseAndLoad.java +++ /dev/null @@ -1,51 +0,0 @@ -/* - * Copyright (C) 2016-2024 Hedera Hashgraph, LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.swirlds.platform.threading; - -import com.swirlds.common.threading.framework.StoppableThread; -import com.swirlds.platform.state.signed.LoadableFromSignedState; -import com.swirlds.platform.state.signed.SignedState; - -/** - * Pauses a thread while {@link LoadableFromSignedState} loads a state. This is useful for externally loading instances - * that are not thread-safe - */ -public class PauseAndLoad implements LoadableFromSignedState { - private final StoppableThread thread; - private final LoadableFromSignedState loadable; - - /** - * @param thread - * the thread to pause - * @param loadable - * the instance to load the state into - */ - public PauseAndLoad(final StoppableThread thread, final LoadableFromSignedState loadable) { - this.thread = thread; - this.loadable = loadable; - } - - @Override - public void loadFromSignedState(final SignedState signedState) { - thread.pause(); - try { - loadable.loadFromSignedState(signedState); - } finally { - thread.resume(); - } - } -} diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/util/ThingsToStart.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/util/ThingsToStart.java deleted file mode 100644 index 916d9e7664ad..000000000000 --- a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/util/ThingsToStart.java +++ /dev/null @@ -1,76 +0,0 @@ -/* - * Copyright (C) 2018-2024 Hedera Hashgraph, LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.swirlds.platform.util; - -import com.swirlds.base.state.Mutable; -import com.swirlds.base.state.Startable; -import edu.umd.cs.findbugs.annotations.NonNull; -import java.util.LinkedList; -import java.util.List; -import java.util.Objects; - -/** - * A helper class for holding things we want to start. - */ -public class ThingsToStart implements Mutable, Startable { - - private final List components = new LinkedList<>(); - - private boolean immutable = false; - - /** - * Create a new container for platform components. - */ - public ThingsToStart() {} - - /** - * Add a platform component that needs to be wired and/or started. - * - * @param component the component - * @param the type of the component - * @return the component - */ - @NonNull - public T add(@NonNull final T component) { - throwIfImmutable(); - Objects.requireNonNull(component); - components.add(component); - return component; - } - - /** - * {@inheritDoc} - */ - @Override - public void start() { - throwIfImmutable(); - immutable = true; - for (final Object component : components) { - if (component instanceof final Startable startable) { - startable.start(); - } - } - } - - /** - * {@inheritDoc} - */ - @Override - public boolean isImmutable() { - return immutable; - } -} diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/wiring/PlatformCoordinator.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/wiring/PlatformCoordinator.java index 923ce0011a42..2f025436c06c 100644 --- a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/wiring/PlatformCoordinator.java +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/wiring/PlatformCoordinator.java @@ -18,23 +18,29 @@ import com.swirlds.common.wiring.component.ComponentWiring; import com.swirlds.common.wiring.counters.ObjectCounter; +import com.swirlds.common.wiring.transformers.RoutableData; import com.swirlds.platform.components.consensus.ConsensusEngine; import com.swirlds.platform.event.GossipEvent; import com.swirlds.platform.event.creation.EventCreationManager; import com.swirlds.platform.event.deduplication.EventDeduplicator; -import com.swirlds.platform.event.linking.InOrderLinker; import com.swirlds.platform.event.orphan.OrphanBuffer; import com.swirlds.platform.event.preconsensus.durability.RoundDurabilityBuffer; +import com.swirlds.platform.event.stale.StaleEventDetector; +import com.swirlds.platform.event.stale.StaleEventDetectorOutput; import com.swirlds.platform.event.validation.EventSignatureValidator; import com.swirlds.platform.event.validation.InternalEventValidator; import com.swirlds.platform.eventhandling.TransactionPrehandler; import com.swirlds.platform.internal.ConsensusRound; -import com.swirlds.platform.internal.EventImpl; +import com.swirlds.platform.pool.TransactionPool; +import com.swirlds.platform.state.hasher.StateHasher; +import com.swirlds.platform.state.signed.ReservedSignedState; +import com.swirlds.platform.state.signed.StateSignatureCollector; import com.swirlds.platform.system.events.BaseEventHashedData; +import com.swirlds.platform.system.status.PlatformStatus; +import com.swirlds.platform.system.status.StatusStateMachine; import com.swirlds.platform.wiring.components.ConsensusRoundHandlerWiring; -import com.swirlds.platform.wiring.components.ShadowgraphWiring; -import com.swirlds.platform.wiring.components.StateHasherWiring; -import com.swirlds.platform.wiring.components.StateSignatureCollectorWiring; +import com.swirlds.platform.wiring.components.GossipWiring; +import com.swirlds.platform.wiring.components.StateAndRound; import edu.umd.cs.findbugs.annotations.NonNull; import java.util.List; import java.util.Objects; @@ -55,15 +61,18 @@ public class PlatformCoordinator { private final ComponentWiring eventDeduplicatorWiring; private final ComponentWiring eventSignatureValidatorWiring; private final ComponentWiring> orphanBufferWiring; - private final ComponentWiring inOrderLinkerWiring; - private final ShadowgraphWiring shadowgraphWiring; + private final GossipWiring gossipWiring; private final ComponentWiring> consensusEngineWiring; private final ComponentWiring eventCreationManagerWiring; private final ComponentWiring applicationTransactionPrehandlerWiring; - private final StateSignatureCollectorWiring stateSignatureCollectorWiring; + private final ComponentWiring> stateSignatureCollectorWiring; private final ConsensusRoundHandlerWiring consensusRoundHandlerWiring; private final ComponentWiring> roundDurabilityBufferWiring; - private final StateHasherWiring stateHasherWiring; + private final ComponentWiring stateHasherWiring; + private final ComponentWiring>> + staleEventDetectorWiring; + private final ComponentWiring transactionPoolWiring; + private final ComponentWiring statusStateMachineWiring; /** * Constructor @@ -73,8 +82,7 @@ public class PlatformCoordinator { * @param eventDeduplicatorWiring the event deduplicator wiring * @param eventSignatureValidatorWiring the event signature validator wiring * @param orphanBufferWiring the orphan buffer wiring - * @param inOrderLinkerWiring the in order linker wiring - * @param shadowgraphWiring the shadowgraph wiring + * @param gossipWiring gossip wiring * @param consensusEngineWiring the consensus engine wiring * @param eventCreationManagerWiring the event creation manager wiring * @param applicationTransactionPrehandlerWiring the application transaction prehandler wiring @@ -82,6 +90,9 @@ public class PlatformCoordinator { * @param consensusRoundHandlerWiring the consensus round handler wiring * @param roundDurabilityBufferWiring the round durability buffer wiring * @param stateHasherWiring the state hasher wiring + * @param staleEventDetectorWiring the stale event detector wiring + * @param transactionPoolWiring the transaction pool wiring + * @param statusStateMachineWiring the status state machine wiring */ public PlatformCoordinator( @NonNull final ObjectCounter hashingObjectCounter, @@ -89,23 +100,28 @@ public PlatformCoordinator( @NonNull final ComponentWiring eventDeduplicatorWiring, @NonNull final ComponentWiring eventSignatureValidatorWiring, @NonNull final ComponentWiring> orphanBufferWiring, - @NonNull final ComponentWiring inOrderLinkerWiring, - @NonNull final ShadowgraphWiring shadowgraphWiring, + @NonNull final GossipWiring gossipWiring, @NonNull final ComponentWiring> consensusEngineWiring, @NonNull final ComponentWiring eventCreationManagerWiring, @NonNull final ComponentWiring applicationTransactionPrehandlerWiring, - @NonNull final StateSignatureCollectorWiring stateSignatureCollectorWiring, + @NonNull + final ComponentWiring> + stateSignatureCollectorWiring, @NonNull final ConsensusRoundHandlerWiring consensusRoundHandlerWiring, @NonNull final ComponentWiring> roundDurabilityBufferWiring, - @NonNull final StateHasherWiring stateHasherWiring) { + @NonNull final ComponentWiring stateHasherWiring, + @NonNull + final ComponentWiring>> + staleEventDetectorWiring, + @NonNull final ComponentWiring transactionPoolWiring, + @NonNull final ComponentWiring statusStateMachineWiring) { this.hashingObjectCounter = Objects.requireNonNull(hashingObjectCounter); this.internalEventValidatorWiring = Objects.requireNonNull(internalEventValidatorWiring); this.eventDeduplicatorWiring = Objects.requireNonNull(eventDeduplicatorWiring); this.eventSignatureValidatorWiring = Objects.requireNonNull(eventSignatureValidatorWiring); this.orphanBufferWiring = Objects.requireNonNull(orphanBufferWiring); - this.inOrderLinkerWiring = Objects.requireNonNull(inOrderLinkerWiring); - this.shadowgraphWiring = Objects.requireNonNull(shadowgraphWiring); + this.gossipWiring = Objects.requireNonNull(gossipWiring); this.consensusEngineWiring = Objects.requireNonNull(consensusEngineWiring); this.eventCreationManagerWiring = Objects.requireNonNull(eventCreationManagerWiring); this.applicationTransactionPrehandlerWiring = Objects.requireNonNull(applicationTransactionPrehandlerWiring); @@ -113,6 +129,9 @@ public PlatformCoordinator( this.consensusRoundHandlerWiring = Objects.requireNonNull(consensusRoundHandlerWiring); this.roundDurabilityBufferWiring = Objects.requireNonNull(roundDurabilityBufferWiring); this.stateHasherWiring = Objects.requireNonNull(stateHasherWiring); + this.staleEventDetectorWiring = Objects.requireNonNull(staleEventDetectorWiring); + this.transactionPoolWiring = Objects.requireNonNull(transactionPoolWiring); + this.statusStateMachineWiring = Objects.requireNonNull(statusStateMachineWiring); } /** @@ -135,8 +154,7 @@ public void flushIntakePipeline() { eventDeduplicatorWiring.flush(); eventSignatureValidatorWiring.flush(); orphanBufferWiring.flush(); - inOrderLinkerWiring.flush(); - shadowgraphWiring.flushRunnable().run(); + gossipWiring.flush(); consensusEngineWiring.flush(); applicationTransactionPrehandlerWiring.flush(); eventCreationManagerWiring.flush(); @@ -152,6 +170,10 @@ public void clear() { // lines without understanding the implications of doing so. Consult the wiring diagram when deciding // whether to change the order of these lines. + // Phase 0: flush the status state machine. + // When reconnecting, this will force us to adopt a status that will halt event creation and gossip. + statusStateMachineWiring.flush(); + // Phase 1: squelch // Break cycles in the system. Flush squelched components just in case there is a task being executed when // squelch is activated. @@ -159,6 +181,7 @@ public void clear() { consensusEngineWiring.flush(); eventCreationManagerWiring.startSquelching(); eventCreationManagerWiring.flush(); + staleEventDetectorWiring.startSquelching(); // Also squelch the consensus round handler. It isn't strictly necessary to do this to prevent dataflow through // the system, but it prevents the consensus round handler from wasting time handling rounds that don't need to @@ -169,24 +192,30 @@ public void clear() { // Phase 2: flush // All cycles have been broken via squelching, so now it's time to flush everything out of the system. flushIntakePipeline(); - stateHasherWiring.flushRunnable().run(); + stateHasherWiring.flush(); stateSignatureCollectorWiring.flush(); roundDurabilityBufferWiring.flush(); consensusRoundHandlerWiring.flushRunnable().run(); + staleEventDetectorWiring.flush(); // Phase 3: stop squelching // Once everything has been flushed out of the system, it's safe to stop squelching. consensusEngineWiring.stopSquelching(); eventCreationManagerWiring.stopSquelching(); consensusRoundHandlerWiring.stopSquelchingRunnable().run(); + staleEventDetectorWiring.stopSquelching(); // Phase 4: clear // Data is no longer moving through the system. Clear all the internal data structures in the wiring objects. eventDeduplicatorWiring.getInputWire(EventDeduplicator::clear).inject(NoInput.getInstance()); orphanBufferWiring.getInputWire(OrphanBuffer::clear).inject(NoInput.getInstance()); - inOrderLinkerWiring.getInputWire(InOrderLinker::clear).inject(NoInput.getInstance()); - stateSignatureCollectorWiring.getClearInput().inject(NoInput.getInstance()); + gossipWiring.getClearInput().inject(NoInput.getInstance()); + stateSignatureCollectorWiring + .getInputWire(StateSignatureCollector::clear) + .inject(NoInput.getInstance()); eventCreationManagerWiring.getInputWire(EventCreationManager::clear).inject(NoInput.getInstance()); roundDurabilityBufferWiring.getInputWire(RoundDurabilityBuffer::clear).inject(NoInput.getInstance()); + staleEventDetectorWiring.getInputWire(StaleEventDetector::clear).inject(NoInput.getInstance()); + transactionPoolWiring.getInputWire(TransactionPool::clear).inject(NoInput.getInstance()); } } diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/wiring/PlatformSchedulers.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/wiring/PlatformSchedulers.java index 1e2c2ffd4963..109edf81cf8d 100644 --- a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/wiring/PlatformSchedulers.java +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/wiring/PlatformSchedulers.java @@ -17,111 +17,52 @@ package com.swirlds.platform.wiring; import static com.swirlds.common.wiring.model.diagram.HyperlinkBuilder.platformCoreHyperlink; -import static com.swirlds.common.wiring.schedulers.builders.TaskSchedulerBuilder.UNLIMITED_CAPACITY; -import com.hedera.hapi.platform.event.StateSignaturePayload; import com.swirlds.common.context.PlatformContext; import com.swirlds.common.stream.RunningEventHashOverride; -import com.swirlds.common.wiring.counters.ObjectCounter; import com.swirlds.common.wiring.model.WiringModel; import com.swirlds.common.wiring.schedulers.TaskScheduler; import com.swirlds.common.wiring.schedulers.builders.TaskSchedulerType; import com.swirlds.platform.StateSigner; -import com.swirlds.platform.event.GossipEvent; -import com.swirlds.platform.event.hashing.EventHasher; import com.swirlds.platform.event.preconsensus.PcesReplayer; import com.swirlds.platform.eventhandling.ConsensusRoundHandler; -import com.swirlds.platform.gossip.shadowgraph.Shadowgraph; -import com.swirlds.platform.state.iss.IssDetector; import com.swirlds.platform.state.iss.IssHandler; -import com.swirlds.platform.state.signed.ReservedSignedState; -import com.swirlds.platform.state.signed.SignedStateFileManager; -import com.swirlds.platform.state.signed.SignedStateHasher; -import com.swirlds.platform.state.signed.StateSavingResult; -import com.swirlds.platform.state.signed.StateSignatureCollector; -import com.swirlds.platform.system.state.notifications.IssNotification; -import com.swirlds.platform.util.HashLogger; +import com.swirlds.platform.system.transaction.ConsensusTransactionImpl; import com.swirlds.platform.wiring.components.StateAndRound; import edu.umd.cs.findbugs.annotations.NonNull; -import java.util.List; /** * The {@link TaskScheduler}s used by the platform. *

    * This class is being phased out. Do not add additional schedulers to this class! * - * @param eventHasherScheduler the scheduler for the event hasher - * @param postHashCollectorScheduler the scheduler for the post hash collector - * @param signedStateFileManagerScheduler the scheduler for the signed state file manager * @param stateSignerScheduler the scheduler for the state signer * @param pcesReplayerScheduler the scheduler for the pces replayer - * @param stateSignatureCollectorScheduler the scheduler for the state signature collector - * @param shadowgraphScheduler the scheduler for the shadowgraph * @param consensusRoundHandlerScheduler the scheduler for the consensus round handler * @param runningHashUpdateScheduler the scheduler for the running hash updater - * @param issDetectorScheduler the scheduler for the iss detector * @param issHandlerScheduler the scheduler for the iss handler - * @param hashLoggerScheduler the scheduler for the hash logger * @param latestCompleteStateNotifierScheduler the scheduler for the latest complete state notifier - * @param stateHasherScheduler the scheduler for the state hasher */ public record PlatformSchedulers( - @NonNull TaskScheduler eventHasherScheduler, - @NonNull TaskScheduler postHashCollectorScheduler, - @NonNull TaskScheduler signedStateFileManagerScheduler, - @NonNull TaskScheduler stateSignerScheduler, + @NonNull TaskScheduler stateSignerScheduler, @NonNull TaskScheduler pcesReplayerScheduler, - @NonNull TaskScheduler> stateSignatureCollectorScheduler, - @NonNull TaskScheduler shadowgraphScheduler, @NonNull TaskScheduler consensusRoundHandlerScheduler, @NonNull TaskScheduler runningHashUpdateScheduler, - @NonNull TaskScheduler> issDetectorScheduler, @NonNull TaskScheduler issHandlerScheduler, - @NonNull TaskScheduler hashLoggerScheduler, - @NonNull TaskScheduler latestCompleteStateNotifierScheduler, - @NonNull TaskScheduler stateHasherScheduler) { + @NonNull TaskScheduler latestCompleteStateNotifierScheduler) { /** * Instantiate the schedulers for the platform, for the given wiring model * * @param context the platform context * @param model the wiring model - * @param hashingObjectCounter the object counter for the event hasher and post hash collector * @return the instantiated platform schedulers */ - public static PlatformSchedulers create( - @NonNull final PlatformContext context, - @NonNull final WiringModel model, - @NonNull final ObjectCounter hashingObjectCounter) { + public static PlatformSchedulers create(@NonNull final PlatformContext context, @NonNull final WiringModel model) { final PlatformSchedulersConfig config = context.getConfiguration().getConfigData(PlatformSchedulersConfig.class); return new PlatformSchedulers( - model.schedulerBuilder("eventHasher") - .withType(TaskSchedulerType.CONCURRENT) - .withOnRamp(hashingObjectCounter) - .withExternalBackPressure(true) - .withUnhandledTaskMetricEnabled(true) - .withHyperlink(platformCoreHyperlink(EventHasher.class)) - .build() - .cast(), - // don't define a capacity for the postHashCollector, so that the postHashCollector will not apply - // backpressure to the hasher - model.schedulerBuilder("postHashCollector") - .withType(TaskSchedulerType.SEQUENTIAL) - .withOffRamp(hashingObjectCounter) - .withExternalBackPressure(true) - .withUnhandledTaskMetricEnabled(true) - .withUnhandledTaskCapacity(UNLIMITED_CAPACITY) - .build() - .cast(), - model.schedulerBuilder("signedStateFileManager") - .withType(config.signedStateFileManagerSchedulerType()) - .withUnhandledTaskCapacity(config.signedStateFileManagerUnhandledCapacity()) - .withUnhandledTaskMetricEnabled(true) - .withHyperlink(platformCoreHyperlink(SignedStateFileManager.class)) - .build() - .cast(), model.schedulerBuilder("stateSigner") .withType(config.stateSignerSchedulerType()) .withUnhandledTaskCapacity(config.stateSignerUnhandledCapacity()) @@ -134,22 +75,6 @@ public static PlatformSchedulers create( .withHyperlink(platformCoreHyperlink(PcesReplayer.class)) .build() .cast(), - model.schedulerBuilder("stateSignatureCollector") - .withType(config.stateSignatureCollectorSchedulerType()) - .withUnhandledTaskCapacity(config.stateSignatureCollectorUnhandledCapacity()) - .withUnhandledTaskMetricEnabled(true) - .withHyperlink(platformCoreHyperlink(StateSignatureCollector.class)) - .withFlushingEnabled(true) - .build() - .cast(), - model.schedulerBuilder("shadowgraph") - .withType(config.shadowgraphSchedulerType()) - .withUnhandledTaskCapacity(config.shadowgraphUnhandledCapacity()) - .withUnhandledTaskMetricEnabled(true) - .withHyperlink(platformCoreHyperlink(Shadowgraph.class)) - .withFlushingEnabled(true) - .build() - .cast(), // the literal "consensusRoundHandler" is used by the app to log on the transaction handling thread. // Do not modify, unless you also change the TRANSACTION_HANDLING_THREAD_NAME constant model.schedulerBuilder("consensusRoundHandler") @@ -166,38 +91,16 @@ public static PlatformSchedulers create( .withType(TaskSchedulerType.DIRECT_THREADSAFE) .build() .cast(), - model.schedulerBuilder("issDetector") - .withType(config.issDetectorSchedulerType()) - .withUnhandledTaskCapacity(config.issDetectorUnhandledCapacity()) - .withUnhandledTaskMetricEnabled(true) - .withHyperlink(platformCoreHyperlink(IssDetector.class)) - .build() - .cast(), model.schedulerBuilder("issHandler") .withType(TaskSchedulerType.DIRECT) .withHyperlink(platformCoreHyperlink(IssHandler.class)) .build() .cast(), - model.schedulerBuilder("hashLogger") - .withType(config.hashLoggerSchedulerType()) - .withUnhandledTaskCapacity(config.hashLoggerUnhandledTaskCapacity()) - .withUnhandledTaskMetricEnabled(true) - .withHyperlink(platformCoreHyperlink(HashLogger.class)) - .build() - .cast(), model.schedulerBuilder("latestCompleteStateNotifier") .withType(TaskSchedulerType.SEQUENTIAL_THREAD) .withUnhandledTaskCapacity(config.completeStateNotifierUnhandledCapacity()) .withUnhandledTaskMetricEnabled(true) .build() - .cast(), - model.schedulerBuilder("stateHasher") - .withType(config.stateHasherSchedulerType()) - .withUnhandledTaskCapacity(config.stateHasherUnhandledCapacity()) - .withUnhandledTaskMetricEnabled(true) - .withHyperlink(platformCoreHyperlink(SignedStateHasher.class)) - .withFlushingEnabled(true) - .build() .cast()); } } diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/wiring/PlatformSchedulersConfig.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/wiring/PlatformSchedulersConfig.java index 24dd8d8ae07d..ad2e9d625715 100644 --- a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/wiring/PlatformSchedulersConfig.java +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/wiring/PlatformSchedulersConfig.java @@ -25,58 +25,53 @@ /** * Contains configuration values for the platform schedulers. * - * @param defaultPoolMultiplier used when calculating the size of the default platform fork join - * pool. Maximum parallelism in this pool is calculated as max(1, - * (defaultPoolMultipler * [number of processors] + - * defaultPoolConstant)). - * @param defaultPoolConstant used when calculating the size of the default platform fork join - * pool. Maximum parallelism in this pool is calculated as max(1, - * (defaultPoolMultipler * [number of processors] + - * defaultPoolConstant)). It is legal for this constant to be a negative - * number. - * @param eventHasherUnhandledCapacity number of unhandled tasks allowed in the event hasher scheduler - * @param internalEventValidator configuration for the internal event validator scheduler - * @param eventDeduplicator configuration for the event deduplicator scheduler - * @param eventSignatureValidator configuration for the event signature validator scheduler - * @param orphanBuffer configuration for the orphan buffer scheduler - * @param consensusEngine configuration for the consensus engine scheduler - * @param inOrderLinker configuration for the in order linker scheduler - * @param eventCreationManager configuration for the event creation manager scheduler - * @param selfEventSigner configuration for the self event signer scheduler - * @param signedStateFileManagerSchedulerType the signed state file manager scheduler type - * @param signedStateFileManagerUnhandledCapacity number of unhandled tasks allowed in the signed state file manager - * scheduler - * @param stateSignerSchedulerType the state signer scheduler type - * @param stateSignerUnhandledCapacity number of unhandled tasks allowed in the state signer scheduler, - * default is -1 (unlimited) - * @param pcesWriter configuration for the preconsensus event writer scheduler - * @param pcesSequencer configuration for the preconsensus event sequencer scheduler - * @param applicationTransactionPrehandler configuration for the application transaction prehandler scheduler - * @param stateSignatureCollectorSchedulerType the state signature collector scheduler type - * @param stateSignatureCollectorUnhandledCapacity number of unhandled tasks allowed for the state signature collector - * @param shadowgraphSchedulerType the shadowgraph scheduler type - * @param shadowgraphUnhandledCapacity number of unhandled tasks allowed for the shadowgraph - * @param consensusRoundHandlerSchedulerType the consensus round handler scheduler type - * @param consensusRoundHandlerUnhandledCapacity number of unhandled tasks allowed for the consensus round handler - * @param runningEventHasher configuration for the running event hasher scheduler - * @param issDetectorSchedulerType the ISS detector scheduler type - * @param issDetectorUnhandledCapacity number of unhandled tasks allowed for the ISS detector - * @param hashLoggerSchedulerType the hash logger scheduler type - * @param hashLoggerUnhandledTaskCapacity number of unhandled tasks allowed in the hash logger task scheduler - * @param completeStateNotifierUnhandledCapacity number of unhandled tasks allowed for the state completion notifier - * @param stateHasherSchedulerType the state hasher scheduler type - * @param stateHasherUnhandledCapacity number of unhandled tasks allowed for the state hasher - * @param stateGarbageCollector configuration for the state garbage collector scheduler - * @param stateGarbageCollectorHeartbeatPeriod the frequency that heartbeats should be sent to the state garbage - * collector - * @param platformPublisher configuration for the platform publisher scheduler - * @param consensusEventStream configuration for the consensus event stream scheduler - * @param roundDurabilityBuffer configuration for the round durability buffer scheduler - * @param statusStateMachine configuration for the status state machine scheduler - * @param platformStatusNexus configuration for the status nexus scheduler - * @param signedStateSentinel configuration for the signed state sentinel scheduler - * @param signedStateSentinelHeartbeatPeriod the frequency that heartbeats should be sent to the signed state - * sentinel + * @param defaultPoolMultiplier used when calculating the size of the default platform fork join pool. + * Maximum parallelism in this pool is calculated as max(1, + * (defaultPoolMultipler * [number of processors] + + * defaultPoolConstant)). + * @param defaultPoolConstant used when calculating the size of the default platform fork join pool. + * Maximum parallelism in this pool is calculated as max(1, + * (defaultPoolMultipler * [number of processors] + + * defaultPoolConstant)). It is legal for this constant to be a negative + * number. + * @param eventHasherUnhandledCapacity number of unhandled tasks allowed in the event hasher scheduler + * @param internalEventValidator configuration for the internal event validator scheduler + * @param eventDeduplicator configuration for the event deduplicator scheduler + * @param eventSignatureValidator configuration for the event signature validator scheduler + * @param orphanBuffer configuration for the orphan buffer scheduler + * @param consensusEngine configuration for the consensus engine scheduler + * @param eventCreationManager configuration for the event creation manager scheduler + * @param selfEventSigner configuration for the self event signer scheduler + * @param stateSignerSchedulerType the state signer scheduler type + * @param stateSignerUnhandledCapacity number of unhandled tasks allowed in the state signer scheduler, + * default is -1 (unlimited) + * @param pcesWriter configuration for the preconsensus event writer scheduler + * @param pcesSequencer configuration for the preconsensus event sequencer scheduler + * @param applicationTransactionPrehandler configuration for the application transaction prehandler scheduler + * @param stateSignatureCollector configuration for the state signature collector scheduler + * @param consensusRoundHandlerSchedulerType the consensus round handler scheduler type + * @param consensusRoundHandlerUnhandledCapacity number of unhandled tasks allowed for the consensus round handler + * @param issDetector configuration for the ISS detector scheduler + * @param hashLogger configuration for the hash logger scheduler + * @param completeStateNotifierUnhandledCapacity number of unhandled tasks allowed for the state completion notifier + * @param stateHasher configuration for the state hasher scheduler + * @param stateGarbageCollector configuration for the state garbage collector scheduler + * @param stateGarbageCollectorHeartbeatPeriod the frequency that heartbeats should be sent to the state garbage + * collector + * @param platformPublisher configuration for the platform publisher scheduler + * @param consensusEventStream configuration for the consensus event stream scheduler + * @param roundDurabilityBuffer configuration for the round durability buffer scheduler + * @param signedStateSentinel configuration for the signed state sentinel scheduler + * @param signedStateSentinelHeartbeatPeriod the frequency that heartbeats should be sent to the signed state + * sentinel + * @param statusStateMachine configuration for the status state machine scheduler + * @param platformStatusNexus configuration for the status nexus scheduler + * @param staleEventDetector configuration for the stale event detector scheduler + * @param transactionResubmitter configuration for the transaction resubmitter scheduler + * @param transactionPool configuration for the transaction pool scheduler + * @param gossip configuration for the gossip scheduler + * @param eventHasher configuration for the event hasher scheduler + * @param postHashCollector configuration for the post hash collector scheduler */ @ConfigData("platformSchedulers") public record PlatformSchedulersConfig( @@ -95,13 +90,11 @@ public record PlatformSchedulersConfig( defaultValue = "SEQUENTIAL_THREAD CAPACITY(500) FLUSHABLE SQUELCHABLE UNHANDLED_TASK_METRIC BUSY_FRACTION_METRIC") TaskSchedulerConfiguration consensusEngine, - @ConfigProperty(defaultValue = "SEQUENTIAL CAPACITY(500) FLUSHABLE UNHANDLED_TASK_METRIC") - TaskSchedulerConfiguration inOrderLinker, @ConfigProperty(defaultValue = "SEQUENTIAL CAPACITY(500) FLUSHABLE SQUELCHABLE UNHANDLED_TASK_METRIC") TaskSchedulerConfiguration eventCreationManager, @ConfigProperty(defaultValue = "DIRECT") TaskSchedulerConfiguration selfEventSigner, - @ConfigProperty(defaultValue = "SEQUENTIAL_THREAD") TaskSchedulerType signedStateFileManagerSchedulerType, - @ConfigProperty(defaultValue = "20") int signedStateFileManagerUnhandledCapacity, + @ConfigProperty(defaultValue = "SEQUENTIAL_THREAD CAPACITY(20) UNHANDLED_TASK_METRIC") + TaskSchedulerConfiguration stateSnapshotManager, @ConfigProperty(defaultValue = "SEQUENTIAL_THREAD") TaskSchedulerType stateSignerSchedulerType, @ConfigProperty(defaultValue = "-1") int stateSignerUnhandledCapacity, @ConfigProperty(defaultValue = "SEQUENTIAL_THREAD CAPACITY(500) UNHANDLED_TASK_METRIC") @@ -109,21 +102,19 @@ public record PlatformSchedulersConfig( @ConfigProperty(defaultValue = "DIRECT") TaskSchedulerConfiguration pcesSequencer, @ConfigProperty(defaultValue = "CONCURRENT CAPACITY(500) FLUSHABLE UNHANDLED_TASK_METRIC") TaskSchedulerConfiguration applicationTransactionPrehandler, - @ConfigProperty(defaultValue = "SEQUENTIAL") TaskSchedulerType stateSignatureCollectorSchedulerType, - @ConfigProperty(defaultValue = "500") int stateSignatureCollectorUnhandledCapacity, - @ConfigProperty(defaultValue = "SEQUENTIAL") TaskSchedulerType shadowgraphSchedulerType, - @ConfigProperty(defaultValue = "500") int shadowgraphUnhandledCapacity, + @ConfigProperty(defaultValue = "SEQUENTIAL CAPACITY(500) FLUSHABLE UNHANDLED_TASK_METRIC") + TaskSchedulerConfiguration stateSignatureCollector, @ConfigProperty(defaultValue = "SEQUENTIAL_THREAD") TaskSchedulerType consensusRoundHandlerSchedulerType, @ConfigProperty(defaultValue = "5") int consensusRoundHandlerUnhandledCapacity, - @ConfigProperty(defaultValue = "SEQUENTIAL CAPACITY(5) UNHANDLED_TASK_METRIC BUSY_FRACTION_METRIC") - TaskSchedulerConfiguration runningEventHasher, - @ConfigProperty(defaultValue = "SEQUENTIAL") TaskSchedulerType issDetectorSchedulerType, - @ConfigProperty(defaultValue = "500") int issDetectorUnhandledCapacity, - @ConfigProperty(defaultValue = "SEQUENTIAL_THREAD") TaskSchedulerType hashLoggerSchedulerType, - @ConfigProperty(defaultValue = "100") int hashLoggerUnhandledTaskCapacity, + @ConfigProperty(defaultValue = "SEQUENTIAL CAPACITY(500) UNHANDLED_TASK_METRIC") + TaskSchedulerConfiguration issDetector, + @ConfigProperty(defaultValue = "SEQUENTIAL CAPACITY(100) UNHANDLED_TASK_METRIC") + TaskSchedulerConfiguration hashLogger, @ConfigProperty(defaultValue = "1000") int completeStateNotifierUnhandledCapacity, - @ConfigProperty(defaultValue = "SEQUENTIAL_THREAD") TaskSchedulerType stateHasherSchedulerType, - @ConfigProperty(defaultValue = "2") int stateHasherUnhandledCapacity, + @ConfigProperty( + defaultValue = + "SEQUENTIAL_THREAD CAPACITY(2) FLUSHABLE UNHANDLED_TASK_METRIC BUSY_FRACTION_METRIC") + TaskSchedulerConfiguration stateHasher, @ConfigProperty(defaultValue = "SEQUENTIAL CAPACITY(60) UNHANDLED_TASK_METRIC") TaskSchedulerConfiguration stateGarbageCollector, @ConfigProperty(defaultValue = "200ms") Duration stateGarbageCollectorHeartbeatPeriod, @@ -135,6 +126,16 @@ public record PlatformSchedulersConfig( @ConfigProperty(defaultValue = "DIRECT_THREADSAFE") TaskSchedulerConfiguration consensusEventStream, @ConfigProperty(defaultValue = "SEQUENTIAL CAPACITY(5) FLUSHABLE UNHANDLED_TASK_METRIC") TaskSchedulerConfiguration roundDurabilityBuffer, - @ConfigProperty(defaultValue = "SEQUENTIAL CAPACITY(500) UNHANDLED_TASK_METRIC") + @ConfigProperty(defaultValue = "SEQUENTIAL CAPACITY(500) FLUSHABLE UNHANDLED_TASK_METRIC") TaskSchedulerConfiguration statusStateMachine, - @ConfigProperty(defaultValue = "DIRECT_THREADSAFE") TaskSchedulerConfiguration platformStatusNexus) {} + @ConfigProperty(defaultValue = "DIRECT_THREADSAFE") TaskSchedulerConfiguration platformStatusNexus, + @ConfigProperty(defaultValue = "SEQUENTIAL CAPACITY(500) FLUSHABLE SQUELCHABLE UNHANDLED_TASK_METRIC") + TaskSchedulerConfiguration staleEventDetector, + @ConfigProperty(defaultValue = "DIRECT_THREADSAFE") TaskSchedulerConfiguration transactionResubmitter, + @ConfigProperty(defaultValue = "DIRECT_THREADSAFE") TaskSchedulerConfiguration transactionPool, + @ConfigProperty(defaultValue = "SEQUENTIAL CAPACITY(500) FLUSHABLE UNHANDLED_TASK_METRIC") + TaskSchedulerConfiguration gossip, + @ConfigProperty(defaultValue = "CONCURRENT CAPACITY(-1) UNHANDLED_TASK_METRIC") + TaskSchedulerConfiguration eventHasher, + @ConfigProperty(defaultValue = "CONCURRENT CAPACITY(-1) UNHANDLED_TASK_METRIC") + TaskSchedulerConfiguration postHashCollector) {} diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/wiring/PlatformWiring.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/wiring/PlatformWiring.java index 0dcaf4e166e6..99003096881c 100644 --- a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/wiring/PlatformWiring.java +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/wiring/PlatformWiring.java @@ -20,26 +20,28 @@ import static com.swirlds.common.wiring.schedulers.builders.TaskSchedulerConfiguration.NO_OP_CONFIGURATION; import static com.swirlds.common.wiring.wires.SolderType.INJECT; import static com.swirlds.common.wiring.wires.SolderType.OFFER; -import static com.swirlds.logging.legacy.LogMarker.STARTUP; +import static com.swirlds.platform.event.stale.StaleEventDetectorOutput.SELF_EVENT; +import static com.swirlds.platform.event.stale.StaleEventDetectorOutput.STALE_SELF_EVENT; -import com.swirlds.base.state.Startable; -import com.swirlds.base.state.Stoppable; +import com.hedera.hapi.platform.event.StateSignaturePayload; import com.swirlds.common.context.PlatformContext; import com.swirlds.common.io.IOIterator; import com.swirlds.common.stream.RunningEventHashOverride; -import com.swirlds.common.utility.Clearable; import com.swirlds.common.wiring.component.ComponentWiring; import com.swirlds.common.wiring.counters.BackpressureObjectCounter; import com.swirlds.common.wiring.counters.ObjectCounter; import com.swirlds.common.wiring.model.WiringModel; -import com.swirlds.common.wiring.model.WiringModelBuilder; +import com.swirlds.common.wiring.schedulers.TaskScheduler; import com.swirlds.common.wiring.schedulers.builders.TaskSchedulerConfiguration; import com.swirlds.common.wiring.schedulers.builders.TaskSchedulerType; +import com.swirlds.common.wiring.transformers.RoutableData; +import com.swirlds.common.wiring.transformers.WireFilter; import com.swirlds.common.wiring.transformers.WireTransformer; import com.swirlds.common.wiring.wires.input.InputWire; import com.swirlds.common.wiring.wires.output.OutputWire; import com.swirlds.common.wiring.wires.output.StandardOutputWire; import com.swirlds.platform.StateSigner; +import com.swirlds.platform.builder.ApplicationCallbacks; import com.swirlds.platform.builder.PlatformComponentBuilder; import com.swirlds.platform.components.AppNotifier; import com.swirlds.platform.components.EventWindowManager; @@ -47,6 +49,8 @@ import com.swirlds.platform.components.appcomm.CompleteStateNotificationWithCleanup; import com.swirlds.platform.components.appcomm.LatestCompleteStateNotifier; import com.swirlds.platform.components.consensus.ConsensusEngine; +import com.swirlds.platform.components.transaction.system.ScopedSystemTransaction; +import com.swirlds.platform.components.transaction.system.SystemTransactionExtractionUtils; import com.swirlds.platform.consensus.ConsensusSnapshot; import com.swirlds.platform.consensus.EventWindow; import com.swirlds.platform.event.AncientMode; @@ -55,64 +59,60 @@ import com.swirlds.platform.event.creation.EventCreationManager; import com.swirlds.platform.event.deduplication.EventDeduplicator; import com.swirlds.platform.event.hashing.EventHasher; -import com.swirlds.platform.event.linking.InOrderLinker; import com.swirlds.platform.event.orphan.OrphanBuffer; import com.swirlds.platform.event.preconsensus.PcesConfig; import com.swirlds.platform.event.preconsensus.PcesReplayer; import com.swirlds.platform.event.preconsensus.PcesSequencer; import com.swirlds.platform.event.preconsensus.PcesWriter; import com.swirlds.platform.event.preconsensus.durability.RoundDurabilityBuffer; -import com.swirlds.platform.event.runninghash.RunningEventHasher; import com.swirlds.platform.event.signing.SelfEventSigner; +import com.swirlds.platform.event.stale.StaleEventDetector; +import com.swirlds.platform.event.stale.StaleEventDetectorOutput; +import com.swirlds.platform.event.stale.TransactionResubmitter; import com.swirlds.platform.event.stream.ConsensusEventStream; import com.swirlds.platform.event.validation.AddressBookUpdate; import com.swirlds.platform.event.validation.EventSignatureValidator; import com.swirlds.platform.event.validation.InternalEventValidator; import com.swirlds.platform.eventhandling.ConsensusRoundHandler; import com.swirlds.platform.eventhandling.EventConfig; -import com.swirlds.platform.eventhandling.TransactionPool; import com.swirlds.platform.eventhandling.TransactionPrehandler; -import com.swirlds.platform.gossip.shadowgraph.Shadowgraph; import com.swirlds.platform.internal.ConsensusRound; -import com.swirlds.platform.internal.EventImpl; +import com.swirlds.platform.pool.TransactionPool; import com.swirlds.platform.publisher.PlatformPublisher; +import com.swirlds.platform.state.hasher.StateHasher; +import com.swirlds.platform.state.hashlogger.HashLogger; import com.swirlds.platform.state.iss.IssDetector; import com.swirlds.platform.state.iss.IssHandler; import com.swirlds.platform.state.nexus.LatestCompleteStateNexus; import com.swirlds.platform.state.nexus.SignedStateNexus; import com.swirlds.platform.state.signed.ReservedSignedState; -import com.swirlds.platform.state.signed.SignedStateFileManager; -import com.swirlds.platform.state.signed.SignedStateHasher; import com.swirlds.platform.state.signed.SignedStateSentinel; -import com.swirlds.platform.state.signed.StateDumpRequest; import com.swirlds.platform.state.signed.StateGarbageCollector; import com.swirlds.platform.state.signed.StateSignatureCollector; +import com.swirlds.platform.state.snapshot.StateDumpRequest; +import com.swirlds.platform.state.snapshot.StateSavingResult; +import com.swirlds.platform.state.snapshot.StateSnapshotManager; import com.swirlds.platform.system.events.BaseEventHashedData; import com.swirlds.platform.system.events.BirthRoundMigrationShim; +import com.swirlds.platform.system.state.notifications.IssNotification; import com.swirlds.platform.system.status.PlatformStatus; import com.swirlds.platform.system.status.PlatformStatusConfig; import com.swirlds.platform.system.status.PlatformStatusNexus; import com.swirlds.platform.system.status.StatusActionSubmitter; import com.swirlds.platform.system.status.StatusStateMachine; -import com.swirlds.platform.util.HashLogger; +import com.swirlds.platform.system.transaction.ConsensusTransactionImpl; import com.swirlds.platform.wiring.components.ConsensusRoundHandlerWiring; import com.swirlds.platform.wiring.components.GossipWiring; -import com.swirlds.platform.wiring.components.HashLoggerWiring; -import com.swirlds.platform.wiring.components.IssDetectorWiring; import com.swirlds.platform.wiring.components.IssHandlerWiring; import com.swirlds.platform.wiring.components.PassThroughWiring; import com.swirlds.platform.wiring.components.PcesReplayerWiring; import com.swirlds.platform.wiring.components.RunningEventHashOverrideWiring; -import com.swirlds.platform.wiring.components.ShadowgraphWiring; import com.swirlds.platform.wiring.components.StateAndRound; -import com.swirlds.platform.wiring.components.StateHasherWiring; -import com.swirlds.platform.wiring.components.StateSignatureCollectorWiring; import edu.umd.cs.findbugs.annotations.NonNull; import edu.umd.cs.findbugs.annotations.Nullable; import java.time.Duration; import java.util.List; import java.util.Objects; -import java.util.concurrent.ForkJoinPool; import java.util.function.LongSupplier; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; @@ -120,7 +120,7 @@ /** * Encapsulates wiring for {@link com.swirlds.platform.SwirldsPlatform}. */ -public class PlatformWiring implements Startable, Stoppable, Clearable { +public class PlatformWiring { private static final Logger logger = LogManager.getLogger(PlatformWiring.class); @@ -135,86 +135,66 @@ public class PlatformWiring implements Startable, Stoppable, Clearable { private final ComponentWiring eventDeduplicatorWiring; private final ComponentWiring eventSignatureValidatorWiring; private final ComponentWiring> orphanBufferWiring; - private final ComponentWiring inOrderLinkerWiring; private final ComponentWiring> consensusEngineWiring; private final ComponentWiring eventCreationManagerWiring; private final ComponentWiring selfEventSignerWiring; - private final SignedStateFileManagerWiring signedStateFileManagerWiring; + private final ComponentWiring stateSnapshotManagerWiring; private final StateSignerWiring stateSignerWiring; private final PcesReplayerWiring pcesReplayerWiring; private final ComponentWiring pcesWriterWiring; private final ComponentWiring> roundDurabilityBufferWiring; private final ComponentWiring pcesSequencerWiring; private final ComponentWiring applicationTransactionPrehandlerWiring; - private final StateSignatureCollectorWiring stateSignatureCollectorWiring; - private final ShadowgraphWiring shadowgraphWiring; + private final ComponentWiring> stateSignatureCollectorWiring; private final GossipWiring gossipWiring; private final ComponentWiring eventWindowManagerWiring; private final ConsensusRoundHandlerWiring consensusRoundHandlerWiring; private final ComponentWiring consensusEventStreamWiring; private final RunningEventHashOverrideWiring runningEventHashOverrideWiring; - private final IssDetectorWiring issDetectorWiring; + private final ComponentWiring> issDetectorWiring; private final IssHandlerWiring issHandlerWiring; - private final HashLoggerWiring hashLoggerWiring; + private final ComponentWiring hashLoggerWiring; private final ComponentWiring latestCompleteStateNotifierWiring; private final ComponentWiring latestImmutableStateNexusWiring; private final ComponentWiring latestCompleteStateNexusWiring; private final ComponentWiring savedStateControllerWiring; - private final StateHasherWiring signedStateHasherWiring; + private final ComponentWiring stateHasherWiring; private final PlatformCoordinator platformCoordinator; private final ComponentWiring birthRoundMigrationShimWiring; private final ComponentWiring notifierWiring; private final ComponentWiring stateGarbageCollectorWiring; private final ComponentWiring signedStateSentinelWiring; - private final ComponentWiring platformPublisherWiring; private final boolean publishPreconsensusEvents; private final boolean publishSnapshotOverrides; - private final ComponentWiring runningEventHasherWiring; + private final boolean publishStaleEvents; + private final ComponentWiring>> + staleEventDetectorWiring; + private final ComponentWiring> transactionResubmitterWiring; + private final ComponentWiring transactionPoolWiring; private final ComponentWiring statusStateMachineWiring; private final ComponentWiring statusNexusWiring; /** * Constructor. * - * @param platformContext the platform context - * @param publishPreconsensusEvents whether to publish preconsensus events (i.e. if a handler is registered). Extra - * things need to be wired together if we are publishing preconsensus events. - * @param publishSnapshotOverrides whether to publish snapshot overrides. Extra things need to be wired together if - * we are publishing snapshot overrides. + * @param platformContext the platform context + * @param model the wiring model + * @param applicationCallbacks the application callbacks (some wires are only created if the application wants a + * callback for something) */ public PlatformWiring( @NonNull final PlatformContext platformContext, - final boolean publishPreconsensusEvents, - final boolean publishSnapshotOverrides) { + @NonNull final WiringModel model, + @NonNull final ApplicationCallbacks applicationCallbacks) { this.platformContext = Objects.requireNonNull(platformContext); + this.model = Objects.requireNonNull(model); config = platformContext.getConfiguration().getConfigData(PlatformSchedulersConfig.class); - final int coreCount = Runtime.getRuntime().availableProcessors(); - final int parallelism = - (int) Math.max(1, config.defaultPoolMultiplier() * coreCount + config.defaultPoolConstant()); - final ForkJoinPool defaultPool = new ForkJoinPool(parallelism); - logger.info(STARTUP.getMarker(), "Default platform pool parallelism: {}", parallelism); - - model = WiringModelBuilder.create(platformContext) - .withDefaultPool(defaultPool) - .build(); - - // This counter spans both the event hasher and the post hash collector. This is a workaround for the current - // inability of concurrent schedulers to handle backpressure from an immediately subsequent scheduler. - // This counter is the on-ramp for the event hasher, and the off-ramp for the post hash collector. - final ObjectCounter hashingObjectCounter = new BackpressureObjectCounter( - "hashingObjectCounter", - platformContext - .getConfiguration() - .getConfigData(PlatformSchedulersConfig.class) - .eventHasherUnhandledCapacity(), - Duration.ofNanos(100)); - - final PlatformSchedulers schedulers = PlatformSchedulers.create(platformContext, model, hashingObjectCounter); + final PlatformSchedulers schedulers = PlatformSchedulers.create(platformContext, model); final AncientMode ancientMode = platformContext .getConfiguration() @@ -232,16 +212,26 @@ public PlatformWiring( birthRoundMigrationShimWiring = null; } - eventHasherWiring = new ComponentWiring<>(model, EventHasher.class, schedulers.eventHasherScheduler()); + // Provides back pressure across both the event hasher and the post hash collector + final ObjectCounter hashingObjectCounter = new BackpressureObjectCounter( + "hashingObjectCounter", + platformContext + .getConfiguration() + .getConfigData(PlatformSchedulersConfig.class) + .eventHasherUnhandledCapacity(), + Duration.ofNanos(100)); + + eventHasherWiring = + new ComponentWiring<>(model, EventHasher.class, buildEventHasherScheduler(hashingObjectCounter)); postHashCollectorWiring = - new PassThroughWiring<>(model, "GossipEvent", schedulers.postHashCollectorScheduler()); + new PassThroughWiring<>(model, "GossipEvent", buildPostHashCollectorScheduler(hashingObjectCounter)); + internalEventValidatorWiring = new ComponentWiring<>(model, InternalEventValidator.class, config.internalEventValidator()); eventDeduplicatorWiring = new ComponentWiring<>(model, EventDeduplicator.class, config.eventDeduplicator()); eventSignatureValidatorWiring = new ComponentWiring<>(model, EventSignatureValidator.class, config.eventSignatureValidator()); orphanBufferWiring = new ComponentWiring<>(model, OrphanBuffer.class, config.orphanBuffer()); - inOrderLinkerWiring = new ComponentWiring<>(model, InOrderLinker.class, config.inOrderLinker()); consensusEngineWiring = new ComponentWiring<>(model, ConsensusEngine.class, config.consensusEngine()); eventCreationManagerWiring = @@ -252,17 +242,18 @@ public PlatformWiring( applicationTransactionPrehandlerWiring = new ComponentWiring<>(model, TransactionPrehandler.class, config.applicationTransactionPrehandler()); stateSignatureCollectorWiring = - StateSignatureCollectorWiring.create(model, schedulers.stateSignatureCollectorScheduler()); - signedStateFileManagerWiring = - SignedStateFileManagerWiring.create(model, schedulers.signedStateFileManagerScheduler()); + new ComponentWiring<>(model, StateSignatureCollector.class, config.stateSignatureCollector()); + stateSnapshotManagerWiring = + new ComponentWiring<>(model, StateSnapshotManager.class, config.stateSnapshotManager()); stateSignerWiring = StateSignerWiring.create(schedulers.stateSignerScheduler()); - shadowgraphWiring = ShadowgraphWiring.create(schedulers.shadowgraphScheduler()); consensusRoundHandlerWiring = ConsensusRoundHandlerWiring.create(schedulers.consensusRoundHandlerScheduler()); consensusEventStreamWiring = new ComponentWiring<>(model, ConsensusEventStream.class, config.consensusEventStream()); runningEventHashOverrideWiring = RunningEventHashOverrideWiring.create(schedulers.runningHashUpdateScheduler()); - signedStateHasherWiring = StateHasherWiring.create(schedulers.stateHasherScheduler()); + stateHasherWiring = new ComponentWiring<>(model, StateHasher.class, config.stateHasher()); + + gossipWiring = new GossipWiring(platformContext, model); pcesReplayerWiring = PcesReplayerWiring.create(schedulers.pcesReplayerScheduler()); @@ -270,7 +261,6 @@ public PlatformWiring( roundDurabilityBufferWiring = new ComponentWiring<>(model, RoundDurabilityBuffer.class, config.roundDurabilityBuffer()); - gossipWiring = GossipWiring.create(model); eventWindowManagerWiring = new ComponentWiring<>( model, EventWindowManager.class, @@ -280,9 +270,9 @@ public PlatformWiring( .build() .cast()); - issDetectorWiring = IssDetectorWiring.create(schedulers.issDetectorScheduler()); + issDetectorWiring = new ComponentWiring<>(model, IssDetector.class, config.issDetector()); issHandlerWiring = IssHandlerWiring.create(schedulers.issHandlerScheduler()); - hashLoggerWiring = HashLoggerWiring.create(schedulers.hashLoggerScheduler()); + hashLoggerWiring = new ComponentWiring<>(model, HashLogger.class, config.hashLogger()); latestCompleteStateNotifierWiring = new ComponentWiring<>( model, @@ -319,11 +309,12 @@ public PlatformWiring( .build() .cast()); - this.publishPreconsensusEvents = publishPreconsensusEvents; - this.publishSnapshotOverrides = publishSnapshotOverrides; + this.publishPreconsensusEvents = applicationCallbacks.preconsensusEventConsumer() != null; + this.publishSnapshotOverrides = applicationCallbacks.snapshotOverrideConsumer() != null; + this.publishStaleEvents = applicationCallbacks.staleEventConsumer() != null; final TaskSchedulerConfiguration publisherConfiguration; - if (publishPreconsensusEvents || publishSnapshotOverrides) { + if (publishPreconsensusEvents || publishSnapshotOverrides || publishStaleEvents) { publisherConfiguration = config.platformPublisher(); } else { publisherConfiguration = NO_OP_CONFIGURATION; @@ -334,29 +325,73 @@ public PlatformWiring( new ComponentWiring<>(model, StateGarbageCollector.class, config.stateGarbageCollector()); signedStateSentinelWiring = new ComponentWiring<>(model, SignedStateSentinel.class, config.signedStateSentinel()); - runningEventHasherWiring = new ComponentWiring<>(model, RunningEventHasher.class, config.runningEventHasher()); statusStateMachineWiring = new ComponentWiring<>(model, StatusStateMachine.class, config.statusStateMachine()); statusNexusWiring = new ComponentWiring<>(model, PlatformStatusNexus.class, config.platformStatusNexus()); + staleEventDetectorWiring = new ComponentWiring<>(model, StaleEventDetector.class, config.staleEventDetector()); + transactionResubmitterWiring = + new ComponentWiring<>(model, TransactionResubmitter.class, config.transactionResubmitter()); + transactionPoolWiring = new ComponentWiring<>(model, TransactionPool.class, config.transactionPool()); + platformCoordinator = new PlatformCoordinator( hashingObjectCounter, internalEventValidatorWiring, eventDeduplicatorWiring, eventSignatureValidatorWiring, orphanBufferWiring, - inOrderLinkerWiring, - shadowgraphWiring, + gossipWiring, consensusEngineWiring, eventCreationManagerWiring, applicationTransactionPrehandlerWiring, stateSignatureCollectorWiring, consensusRoundHandlerWiring, roundDurabilityBufferWiring, - signedStateHasherWiring); + stateHasherWiring, + staleEventDetectorWiring, + transactionPoolWiring, + statusStateMachineWiring); wire(); } + /** + * Build the event hasher scheduler. Normally we don't build schedulers in this class, but a special exception is + * made here because for back pressure reasons. Will be removed from this class when we implement a platform health + * monitor. + * + * @param hashingObjectCounter the object counter to use for back pressure + * @return the event hasher scheduler + */ + @NonNull + private TaskScheduler buildEventHasherScheduler(@NonNull final ObjectCounter hashingObjectCounter) { + return model.schedulerBuilder("EventHasher") + .configure(config.eventHasher()) + .withOnRamp(hashingObjectCounter) + .withExternalBackPressure(true) + .withHyperlink(platformCoreHyperlink(EventHasher.class)) + .build() + .cast(); + } + + /** + * Build the post hash collector scheduler. Normally we don't build schedulers in this class, but a special + * exception is made here because for back pressure reasons. Will be removed from this class when we implement a + * platform health monitor. + * + * @param hashingObjectCounter the object counter to use for back pressure + * @return the post hash collector scheduler + */ + @NonNull + private TaskScheduler buildPostHashCollectorScheduler( + @NonNull final ObjectCounter hashingObjectCounter) { + return model.schedulerBuilder("PostHashCollector") + .configure(config.postHashCollector()) + .withOffRamp(hashingObjectCounter) + .withExternalBackPressure(true) + .build() + .cast(); + } + /** * Get the wiring model. * @@ -377,12 +412,11 @@ private void solderEventWindow() { eventWindowOutputWire.solderTo( eventSignatureValidatorWiring.getInputWire(EventSignatureValidator::setEventWindow), INJECT); eventWindowOutputWire.solderTo(orphanBufferWiring.getInputWire(OrphanBuffer::setEventWindow), INJECT); - eventWindowOutputWire.solderTo(inOrderLinkerWiring.getInputWire(InOrderLinker::setEventWindow), INJECT); + eventWindowOutputWire.solderTo(gossipWiring.getEventWindowInput(), INJECT); eventWindowOutputWire.solderTo( pcesWriterWiring.getInputWire(PcesWriter::updateNonAncientEventBoundary), INJECT); eventWindowOutputWire.solderTo( eventCreationManagerWiring.getInputWire(EventCreationManager::setEventWindow), INJECT); - eventWindowOutputWire.solderTo(shadowgraphWiring.eventWindowInput(), INJECT); eventWindowOutputWire.solderTo( latestCompleteStateNexusWiring.getInputWire(LatestCompleteStateNexus::updateEventWindow)); } @@ -394,12 +428,12 @@ private void solderNotifier() { latestCompleteStateNotifierWiring .getOutputWire() .solderTo(notifierWiring.getInputWire(AppNotifier::sendLatestCompleteStateNotification)); - signedStateFileManagerWiring - .stateWrittenToDiskNotificationOutput() + stateSnapshotManagerWiring + .getTransformedOutput(StateSnapshotManager::toNotification) .solderTo(notifierWiring.getInputWire(AppNotifier::sendStateWrittenToDiskNotification)); - issDetectorWiring - .issNotificationOutput() - .solderTo(notifierWiring.getInputWire(AppNotifier::sendIssNotification)); + + final OutputWire issNotificationOutputWire = issDetectorWiring.getSplitOutput(); + issNotificationOutputWire.solderTo(notifierWiring.getInputWire(AppNotifier::sendIssNotification)); statusStateMachineWiring .getOutputWire() .solderTo(notifierWiring.getInputWire(AppNotifier::sendPlatformStatusChangeNotification)); @@ -419,7 +453,7 @@ private void wire() { pipelineInputWire = eventHasherWiring.getInputWire(EventHasher::hashEvent); } - gossipWiring.eventOutput().solderTo(pipelineInputWire); + gossipWiring.getEventOutput().solderTo(pipelineInputWire); eventHasherWiring.getOutputWire().solderTo(postHashCollectorWiring.getInputWire()); postHashCollectorWiring .getOutputWire() @@ -439,11 +473,11 @@ private void wire() { pcesSequencerWiring.getOutputWire().solderTo(consensusEngineWiring.getInputWire(ConsensusEngine::addEvent)); - inOrderLinkerWiring.getOutputWire().solderTo(shadowgraphWiring.eventInput()); - splitOrphanBufferOutput.solderTo(eventCreationManagerWiring.getInputWire(EventCreationManager::registerEvent)); - splitOrphanBufferOutput.solderTo(inOrderLinkerWiring.getInputWire(InOrderLinker::linkEvent)); + // This must use injection to avoid cyclical back pressure. There is a risk of OOM if gossip can't ingest + // events fast enough, but we have no other choice until we implement the platform health monitor. + splitOrphanBufferOutput.solderTo(gossipWiring.getEventInput(), INJECT); final double eventCreationHeartbeatFrequency = platformContext .getConfiguration() @@ -462,14 +496,82 @@ private void wire() { .solderTo(selfEventSignerWiring.getInputWire(SelfEventSigner::signEvent)); selfEventSignerWiring .getOutputWire() - .solderTo(internalEventValidatorWiring.getInputWire(InternalEventValidator::validateEvent), INJECT); + .solderTo(staleEventDetectorWiring.getInputWire(StaleEventDetector::addSelfEvent)); + + final OutputWire staleEventsFromStaleEventDetector = + staleEventDetectorWiring.getSplitAndRoutedOutput(STALE_SELF_EVENT); + final OutputWire selfEventsFromStaleEventDetector = + staleEventDetectorWiring.getSplitAndRoutedOutput(SELF_EVENT); + + selfEventsFromStaleEventDetector.solderTo( + internalEventValidatorWiring.getInputWire(InternalEventValidator::validateEvent), INJECT); + + staleEventsFromStaleEventDetector.solderTo( + transactionResubmitterWiring.getInputWire(TransactionResubmitter::resubmitStaleTransactions)); + final OutputWire splitTransactionResubmitterOutput = + transactionResubmitterWiring.getSplitOutput(); + splitTransactionResubmitterOutput.solderTo( + transactionPoolWiring.getInputWire(TransactionPool::submitSystemTransaction)); + + if (publishStaleEvents) { + staleEventsFromStaleEventDetector.solderTo( + platformPublisherWiring.getInputWire(PlatformPublisher::publishStaleEvent)); + } + splitOrphanBufferOutput.solderTo(applicationTransactionPrehandlerWiring.getInputWire( TransactionPrehandler::prehandleApplicationTransactions)); - splitOrphanBufferOutput.solderTo(stateSignatureCollectorWiring.preConsensusEventInput()); - stateSignatureCollectorWiring.getAllStatesOutput().solderTo(signedStateFileManagerWiring.saveToDiskFilter()); - stateSignatureCollectorWiring - .getCompleteStatesOutput() - .solderTo(latestCompleteStateNexusWiring.getInputWire(LatestCompleteStateNexus::setStateIfNewer)); + + // From the orphan buffer, extract signatures from preconsensus events for input to the StateSignatureCollector. + final WireTransformer>> + preConsensusTransformer = new WireTransformer<>( + model, + "extractPreconsensusSignatureTransactions", + "preconsensus signatures", + event -> SystemTransactionExtractionUtils.extractFromEvent(event, StateSignaturePayload.class)); + splitOrphanBufferOutput.solderTo(preConsensusTransformer.getInputWire()); + preConsensusTransformer + .getOutputWire() + .solderTo(stateSignatureCollectorWiring.getInputWire( + StateSignatureCollector::handlePreconsensusSignatures)); + + // Split output of StateSignatureCollector into single ReservedSignedStates. + final OutputWire splitReservedSignedStateWire = stateSignatureCollectorWiring + .getOutputWire() + .buildSplitter("reservedStateSplitter", "reserved state lists"); + // Add another reservation to the signed states since we are soldering to two different input wires + final OutputWire allReservedSignedStatesWire = + splitReservedSignedStateWire.buildAdvancedTransformer(new SignedStateReserver("allStatesReserver")); + + // Future work: this should be a full component in its own right or folded in with the state file manager. + final WireFilter saveToDiskFilter = + new WireFilter<>(model, "saveToDiskFilter", "states", state -> { + if (state.get().isStateToSave()) { + return true; + } + state.close(); + return false; + }); + + allReservedSignedStatesWire.solderTo(saveToDiskFilter.getInputWire()); + + saveToDiskFilter + .getOutputWire() + .solderTo(stateSnapshotManagerWiring.getInputWire(StateSnapshotManager::saveStateTask)); + + // Filter to complete states only and add a 3rd reservation since completes states are used in two input wires. + final OutputWire completeReservedSignedStatesWire = allReservedSignedStatesWire + .buildFilter("completeStateFilter", "states", rs -> { + if (rs.get().isComplete()) { + return true; + } else { + // close the second reservation on states that are not passed on. + rs.close(); + return false; + } + }) + .buildAdvancedTransformer(new SignedStateReserver("completeStatesReserver")); + completeReservedSignedStatesWire.solderTo( + latestCompleteStateNexusWiring.getInputWire(LatestCompleteStateNexus::setStateIfNewer)); solderEventWindow(); @@ -491,6 +593,8 @@ private void wire() { final OutputWire consensusRoundOutputWire = consensusEngineWiring.getSplitOutput(); + consensusRoundOutputWire.solderTo(staleEventDetectorWiring.getInputWire(StaleEventDetector::addConsensusRound)); + // The request to flush the keystone event for a round must be sent to the PCES writer before the consensus // round is passed to the round handler. This prevents a deadlock scenario where the consensus round // handler has a full queue and won't accept additional rounds, and is waiting on a keystone event to be @@ -510,16 +614,14 @@ private void wire() { .getSplitAndTransformedOutput(ConsensusEngine::getConsensusEvents) .solderTo(consensusEventStreamWiring.getInputWire(ConsensusEventStream::addEvents)); - consensusRoundOutputWire.solderTo( - runningEventHasherWiring.getInputWire(RunningEventHasher::computeRunningEventHash)); - consensusRoundHandlerWiring .stateOutput() .solderTo(latestImmutableStateNexusWiring.getInputWire(SignedStateNexus::setState)); consensusRoundHandlerWiring .stateAndRoundOutput() .solderTo(savedStateControllerWiring.getInputWire(SavedStateController::markSavedState)); - savedStateControllerWiring.getOutputWire().solderTo(signedStateHasherWiring.stateAndRoundInput()); + + savedStateControllerWiring.getOutputWire().solderTo(stateHasherWiring.getInputWire(StateHasher::hashState)); consensusRoundHandlerWiring .stateAndRoundOutput() @@ -529,13 +631,43 @@ private void wire() { model.buildHeartbeatWire(config.signedStateSentinelHeartbeatPeriod()) .solderTo(signedStateSentinelWiring.getInputWire(SignedStateSentinel::checkSignedStates), OFFER); - signedStateHasherWiring.stateOutput().solderTo(hashLoggerWiring.hashLoggerInputWire()); - signedStateHasherWiring.stateOutput().solderTo(stateSignerWiring.signState()); - signedStateHasherWiring.stateAndRoundOutput().solderTo(issDetectorWiring.stateAndRoundInput()); + // The state hasher needs to pass its data through a bunch of transformers. Construct those here. + final OutputWire hashedStateAndRoundOutputWire = stateHasherWiring + .getOutputWire() + .buildAdvancedTransformer(new StateAndRoundReserver("postHasher_stateAndRoundReserver")); + final OutputWire hashedStateOutputWire = + hashedStateAndRoundOutputWire.buildAdvancedTransformer( + new StateAndRoundToStateReserver("postHasher_stateReserver")); + final OutputWire hashedConsensusRoundOutput = stateHasherWiring + .getOutputWire() + .buildTransformer("postHasher_getConsensusRound", "stateAndRound", StateAndRound::round); + + hashedStateOutputWire.solderTo(hashLoggerWiring.getInputWire(HashLogger::logHashes)); + hashedStateOutputWire.solderTo(stateSignerWiring.signState()); + hashedStateAndRoundOutputWire.solderTo(issDetectorWiring.getInputWire(IssDetector::handleStateAndRound)); - // FUTURE WORK: combine these two methods into a single input method, which accepts a StateAndRound object - signedStateHasherWiring.stateOutput().solderTo(stateSignatureCollectorWiring.getReservedStateInput()); - signedStateHasherWiring.roundOutput().solderTo(stateSignatureCollectorWiring.getConsensusRoundInput()); + stateSignerWiring + .stateSignature() + .solderTo(transactionPoolWiring.getInputWire(TransactionPool::submitSystemTransaction)); + + // FUTURE WORK: combine the signedStateHasherWiring State and Round outputs into a single StateAndRound output. + // FUTURE WORK: Split the single StateAndRound output into separate State and Round wires. + + // Extract signatures from post-consensus events for input to the StateSignatureCollector. + final WireTransformer>> + postConsensusTransformer = new WireTransformer<>( + model, + "extractConsensusSignatureTransactions", + "consensus events", + round -> SystemTransactionExtractionUtils.extractFromRound(round, StateSignaturePayload.class)); + hashedConsensusRoundOutput.solderTo(postConsensusTransformer.getInputWire()); + postConsensusTransformer + .getOutputWire() + .solderTo(stateSignatureCollectorWiring.getInputWire( + StateSignatureCollector::handlePostconsensusSignatures)); + // Solder the state output as input to the state signature collector. + hashedStateOutputWire.solderTo( + stateSignatureCollectorWiring.getInputWire(StateSignatureCollector::addReservedState)); pcesWriterWiring .getOutputWire() @@ -548,11 +680,11 @@ private void wire() { .roundDurabilityBufferHeartbeatPeriod()) .solderTo(roundDurabilityBufferWiring.getInputWire(RoundDurabilityBuffer::checkForStaleRounds)); - signedStateFileManagerWiring - .oldestMinimumGenerationOnDiskOutputWire() + stateSnapshotManagerWiring + .getTransformedOutput(StateSnapshotManager::extractOldestMinimumGenerationOnDisk) .solderTo(pcesWriterWiring.getInputWire(PcesWriter::setMinimumAncientIdentifierToStore), INJECT); - signedStateFileManagerWiring - .stateWrittenToDiskOutputWire() + stateSnapshotManagerWiring + .getTransformedOutput(StateSnapshotManager::toStateWrittenToDiskAction) .solderTo(statusStateMachineWiring.getInputWire(StatusStateMachine::submitStatusAction)); runningEventHashOverrideWiring @@ -561,19 +693,15 @@ private void wire() { runningEventHashOverrideWiring .runningHashUpdateOutput() .solderTo(consensusEventStreamWiring.getInputWire(ConsensusEventStream::legacyHashOverride)); - runningEventHashOverrideWiring - .runningHashUpdateOutput() - .solderTo(runningEventHasherWiring.getInputWire(RunningEventHasher::overrideRunningEventHash)); - issDetectorWiring.issNotificationOutput().solderTo(issHandlerWiring.issNotificationInput()); + final OutputWire splitIssDetectorOutput = issDetectorWiring.getSplitOutput(); + splitIssDetectorOutput.solderTo(issHandlerWiring.issNotificationInput()); issDetectorWiring - .statusActionOutput() + .getSplitAndTransformedOutput(IssDetector::getStatusAction) .solderTo(statusStateMachineWiring.getInputWire(StatusStateMachine::submitStatusAction)); - stateSignatureCollectorWiring - .getCompleteStatesOutput() - .solderTo(latestCompleteStateNotifierWiring.getInputWire( - LatestCompleteStateNotifier::latestCompleteStateHandler)); + completeReservedSignedStatesWire.solderTo(latestCompleteStateNotifierWiring.getInputWire( + LatestCompleteStateNotifier::latestCompleteStateHandler)); statusStateMachineWiring .getOutputWire() @@ -604,74 +732,55 @@ private void buildUnsolderedWires() { platformPublisherWiring.getInputWire(PlatformPublisher::publishSnapshotOverride); } eventCreationManagerWiring.getInputWire(EventCreationManager::clear); - notifierWiring.getInputWire(AppNotifier::sendStateLoadedFromDiskNotification); notifierWiring.getInputWire(AppNotifier::sendReconnectCompleteNotification); notifierWiring.getInputWire(AppNotifier::sendPlatformStatusChangeNotification); eventSignatureValidatorWiring.getInputWire(EventSignatureValidator::updateAddressBooks); eventWindowManagerWiring.getInputWire(EventWindowManager::updateEventWindow); orphanBufferWiring.getInputWire(OrphanBuffer::clear); - inOrderLinkerWiring.getInputWire(InOrderLinker::clear); roundDurabilityBufferWiring.getInputWire(RoundDurabilityBuffer::clear); pcesWriterWiring.getInputWire(PcesWriter::registerDiscontinuity); - } - - /** - * Wire components that adhere to the framework to components that don't - *

    - * Future work: as more components are moved to the framework, this method should shrink, and eventually be - * removed. - * - * @param transactionPool the transaction pool to wire - */ - public void wireExternalComponents(@NonNull final TransactionPool transactionPool) { - stateSignerWiring - .stateSignature() - .solderTo("transactionPool", "signature transactions", transactionPool::submitPayload); + stateSignatureCollectorWiring.getInputWire(StateSignatureCollector::clear); + issDetectorWiring.getInputWire(IssDetector::overridingState); + issDetectorWiring.getInputWire(IssDetector::signalEndOfPreconsensusReplay); + staleEventDetectorWiring.getInputWire(StaleEventDetector::setInitialEventWindow); + staleEventDetectorWiring.getInputWire(StaleEventDetector::clear); + transactionPoolWiring.getInputWire(TransactionPool::clear); + stateSnapshotManagerWiring.getInputWire(StateSnapshotManager::dumpStateTask); } /** * Bind components to the wiring. * * @param builder builds platform components that need to be bound to wires - * @param signedStateFileManager the signed state file manager to bind * @param stateSigner the state signer to bind * @param pcesReplayer the PCES replayer to bind - * @param shadowgraph the shadowgraph to bind * @param stateSignatureCollector the signed state manager to bind * @param eventWindowManager the event window manager to bind * @param consensusRoundHandler the consensus round handler to bind - * @param issDetector the ISS detector to bind * @param issHandler the ISS handler to bind - * @param hashLogger the hash logger to bind * @param birthRoundMigrationShim the birth round migration shim to bind, ignored if birth round migration has not * yet happened, must not be null if birth round migration has happened * @param completeStateNotifier the latest complete state notifier to bind * @param latestImmutableStateNexus the latest immutable state nexus to bind * @param latestCompleteStateNexus the latest complete state nexus to bind * @param savedStateController the saved state controller to bind - * @param signedStateHasher the signed state hasher to bind * @param notifier the notifier to bind * @param platformPublisher the platform publisher to bind * @param platformStatusNexus the platform status nexus to bind */ public void bind( @NonNull final PlatformComponentBuilder builder, - @NonNull final SignedStateFileManager signedStateFileManager, @NonNull final StateSigner stateSigner, @NonNull final PcesReplayer pcesReplayer, - @NonNull final Shadowgraph shadowgraph, @NonNull final StateSignatureCollector stateSignatureCollector, @NonNull final EventWindowManager eventWindowManager, @NonNull final ConsensusRoundHandler consensusRoundHandler, - @NonNull final IssDetector issDetector, @NonNull final IssHandler issHandler, - @NonNull final HashLogger hashLogger, @Nullable final BirthRoundMigrationShim birthRoundMigrationShim, @NonNull final LatestCompleteStateNotifier completeStateNotifier, @NonNull final SignedStateNexus latestImmutableStateNexus, @NonNull final LatestCompleteStateNexus latestCompleteStateNexus, @NonNull final SavedStateController savedStateController, - @NonNull final SignedStateHasher signedStateHasher, @NonNull final AppNotifier notifier, @NonNull final PlatformPublisher platformPublisher, @NonNull final PlatformStatusNexus platformStatusNexus) { @@ -681,14 +790,12 @@ public void bind( eventDeduplicatorWiring.bind(builder::buildEventDeduplicator); eventSignatureValidatorWiring.bind(builder::buildEventSignatureValidator); orphanBufferWiring.bind(builder::buildOrphanBuffer); - inOrderLinkerWiring.bind(builder::buildInOrderLinker); consensusEngineWiring.bind(builder::buildConsensusEngine); - signedStateFileManagerWiring.bind(signedStateFileManager); + stateSnapshotManagerWiring.bind(builder::buildStateSnapshotManager); stateSignerWiring.bind(stateSigner); pcesReplayerWiring.bind(pcesReplayer); pcesWriterWiring.bind(builder::buildPcesWriter); roundDurabilityBufferWiring.bind(builder::buildRoundDurabilityBuffer); - shadowgraphWiring.bind(shadowgraph); pcesSequencerWiring.bind(builder::buildPcesSequencer); eventCreationManagerWiring.bind(builder::buildEventCreationManager); selfEventSignerWiring.bind(builder::buildSelfEventSigner); @@ -696,11 +803,10 @@ public void bind( eventWindowManagerWiring.bind(eventWindowManager); applicationTransactionPrehandlerWiring.bind(builder::buildTransactionPrehandler); consensusRoundHandlerWiring.bind(consensusRoundHandler); - runningEventHasherWiring.bind(builder::buildRunningEventHasher); consensusEventStreamWiring.bind(builder::buildConsensusEventStream); - issDetectorWiring.bind(issDetector); + issDetectorWiring.bind(builder::buildIssDetector); issHandlerWiring.bind(issHandler); - hashLoggerWiring.bind(hashLogger); + hashLoggerWiring.bind(builder::buildHashLogger); if (birthRoundMigrationShimWiring != null) { birthRoundMigrationShimWiring.bind(Objects.requireNonNull(birthRoundMigrationShim)); } @@ -708,23 +814,31 @@ public void bind( latestImmutableStateNexusWiring.bind(latestImmutableStateNexus); latestCompleteStateNexusWiring.bind(latestCompleteStateNexus); savedStateControllerWiring.bind(savedStateController); - signedStateHasherWiring.bind(signedStateHasher); + stateHasherWiring.bind(builder::buildStateHasher); notifierWiring.bind(notifier); platformPublisherWiring.bind(platformPublisher); stateGarbageCollectorWiring.bind(builder::buildStateGarbageCollector); statusStateMachineWiring.bind(builder::buildStatusStateMachine); statusNexusWiring.bind(platformStatusNexus); signedStateSentinelWiring.bind(builder::buildSignedStateSentinel); + staleEventDetectorWiring.bind(builder::buildStaleEventDetector); + transactionResubmitterWiring.bind(builder::buildTransactionResubmitter); + transactionPoolWiring.bind(builder::buildTransactionPool); + gossipWiring.bind(builder.buildGossip()); } /** - * Get the input wire gossip. All events received from peers during should be passed to this wire. - * - * @return the wire where all events from gossip should be passed + * Start gossiping. */ - @NonNull - public InputWire getGossipEventInput() { - return gossipWiring.eventInput(); + public void startGossip() { + gossipWiring.getStartInput().inject(NoInput.getInstance()); + } + + /** + * Stop gossiping (permanently). + */ + public void stopGossip() { + gossipWiring.getStopInput().inject(NoInput.getInstance()); } /** @@ -749,7 +863,7 @@ public InputWire getAddressBookUpdateInput() { */ @NonNull public InputWire getDumpStateToDiskInput() { - return signedStateFileManagerWiring.dumpStateToDisk(); + return stateSnapshotManagerWiring.getInputWire(StateSnapshotManager::dumpStateTask); } /** @@ -757,7 +871,7 @@ public InputWire getDumpStateToDiskInput() { */ @NonNull public InputWire getSignatureCollectorStateInput() { - return stateSignatureCollectorWiring.getReservedStateInput(); + return stateSignatureCollectorWiring.getInputWire(StateSignatureCollector::addReservedState); } /** @@ -787,7 +901,7 @@ public StandardOutputWire getPcesReplayerEventOutput() { */ @NonNull public InputWire getHashLoggerInput() { - return hashLoggerWiring.hashLoggerInputWire(); + return hashLoggerWiring.getInputWire(HashLogger::logHashes); } /** @@ -844,10 +958,22 @@ public void updateRunningHash(@NonNull final RunningEventHashOverride runningHas } /** - * @return the wiring wrapper for the ISS detector + * Pass an overriding state to the ISS detector. + * + * @param state the overriding state */ - public @NonNull IssDetectorWiring getIssDetectorWiring() { - return issDetectorWiring; + @NonNull + public void overrideIssDetectorState(@NonNull final ReservedSignedState state) { + issDetectorWiring.getInputWire(IssDetector::overridingState).put(state); + } + + /** + * Signal the end of the preconsensus replay to the ISS detector. + */ + public void signalEndOfPcesReplay() { + issDetectorWiring + .getInputWire(IssDetector::signalEndOfPreconsensusReplay) + .put(NoInput.getInstance()); } /** @@ -873,9 +999,13 @@ public void updateEventWindow(@NonNull final EventWindow eventWindow) { .getInputWire(EventWindowManager::updateEventWindow) .inject(eventWindow); + staleEventDetectorWiring + .getInputWire(StaleEventDetector::setInitialEventWindow) + .inject(eventWindow); + // Since there is asynchronous access to the shadowgraph, it's important to ensure that // it has fully ingested the new event window before continuing. - shadowgraphWiring.flushRunnable().run(); + gossipWiring.flush(); } /** @@ -914,21 +1044,19 @@ public void flushConsensusRoundHandler() { * Flush the state hasher. */ public void flushStateHasher() { - signedStateHasherWiring.flushRunnable().run(); + stateHasherWiring.flush(); } /** - * {@inheritDoc} + * Start the wiring framework. */ - @Override public void start() { model.start(); } /** - * {@inheritDoc} + * Stop the wiring framework. */ - @Override public void stop() { model.stop(); } @@ -936,7 +1064,6 @@ public void stop() { /** * Clear all the wiring objects. */ - @Override public void clear() { platformCoordinator.clear(); } diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/wiring/SignedStateFileManagerWiring.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/wiring/SignedStateFileManagerWiring.java deleted file mode 100644 index dca7c4633b03..000000000000 --- a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/wiring/SignedStateFileManagerWiring.java +++ /dev/null @@ -1,110 +0,0 @@ -/* - * Copyright (C) 2023-2024 Hedera Hashgraph, LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.swirlds.platform.wiring; - -import com.swirlds.common.wiring.model.WiringModel; -import com.swirlds.common.wiring.schedulers.TaskScheduler; -import com.swirlds.common.wiring.transformers.WireFilter; -import com.swirlds.common.wiring.wires.input.BindableInputWire; -import com.swirlds.common.wiring.wires.input.InputWire; -import com.swirlds.common.wiring.wires.output.OutputWire; -import com.swirlds.platform.listeners.StateWriteToDiskCompleteNotification; -import com.swirlds.platform.state.signed.ReservedSignedState; -import com.swirlds.platform.state.signed.SignedStateFileManager; -import com.swirlds.platform.state.signed.StateDumpRequest; -import com.swirlds.platform.state.signed.StateSavingResult; -import com.swirlds.platform.system.status.actions.PlatformStatusAction; -import com.swirlds.platform.system.status.actions.StateWrittenToDiskAction; -import edu.umd.cs.findbugs.annotations.NonNull; - -/** - * The wiring for the {@link SignedStateFileManager} - * - * @param saveStateToDisk the input wire for saving the state to disk - * @param saveToDiskFilter the input wire that filters out states that should not be saved - * @param dumpStateToDisk the input wire for dumping the state to disk - * @param stateSavingResultOutputWire the output wire for the state saving result - * @param oldestMinimumGenerationOnDiskOutputWire the output wire for the oldest minimum generation on disk - * @param stateWrittenToDiskOutputWire the output wire for the state written to disk action - * @param stateWrittenToDiskNotificationOutput the output wire for the state written to disk notification - */ -public record SignedStateFileManagerWiring( - @NonNull BindableInputWire saveStateToDisk, - @NonNull InputWire saveToDiskFilter, - @NonNull InputWire dumpStateToDisk, - @NonNull OutputWire stateSavingResultOutputWire, - @NonNull OutputWire oldestMinimumGenerationOnDiskOutputWire, - @NonNull OutputWire stateWrittenToDiskOutputWire, - @NonNull OutputWire stateWrittenToDiskNotificationOutput) { - /** - * Create a new instance of this wiring - * - * @param scheduler the task scheduler for this wiring - * @return the new wiring instance - */ - public static SignedStateFileManagerWiring create( - @NonNull final WiringModel model, @NonNull final TaskScheduler scheduler) { - final WireFilter saveToDiskFilter = - new WireFilter<>(model, "saveToDiskFilter", "states", rs -> { - if (rs.get().isStateToSave()) { - return true; - } - rs.close(); - return false; - }); - final BindableInputWire saveStateToDisk = - scheduler.buildInputWire("states to save"); - saveToDiskFilter.getOutputWire().solderTo(saveStateToDisk); - return new SignedStateFileManagerWiring( - saveStateToDisk, - saveToDiskFilter.getInputWire(), - scheduler.buildInputWire("dump state to disk"), - scheduler.getOutputWire(), - scheduler - .getOutputWire() - .buildTransformer( - "extractOldestMinimumGenerationOnDisk", - "state saving result", - StateSavingResult::oldestMinimumGenerationOnDisk), - scheduler - .getOutputWire() - .buildTransformer( - "toStateWrittenToDiskAction", - "state saving result", - ssr -> new StateWrittenToDiskAction(ssr.round(), ssr.freezeState())), - scheduler - .getOutputWire() - .buildTransformer( - "toNotification", - "StateSavingResult", - stateSavingResult -> new StateWriteToDiskCompleteNotification( - stateSavingResult.round(), - stateSavingResult.consensusTimestamp(), - stateSavingResult.freezeState()))); - } - - /** - * Bind the wires to the {@link SignedStateFileManager} - * - * @param signedStateFileManager the signed state file manager - */ - public void bind(@NonNull final SignedStateFileManager signedStateFileManager) { - saveStateToDisk.bind(signedStateFileManager::saveStateTask); - ((BindableInputWire) dumpStateToDisk) - .bindConsumer(signedStateFileManager::dumpStateTask); - } -} diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/wiring/StateSignerWiring.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/wiring/StateSignerWiring.java index 776e7afd88d9..c0b2dcc05978 100644 --- a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/wiring/StateSignerWiring.java +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/wiring/StateSignerWiring.java @@ -16,13 +16,13 @@ package com.swirlds.platform.wiring; -import com.hedera.hapi.platform.event.StateSignaturePayload; import com.swirlds.common.wiring.schedulers.TaskScheduler; import com.swirlds.common.wiring.wires.input.BindableInputWire; import com.swirlds.common.wiring.wires.input.InputWire; import com.swirlds.common.wiring.wires.output.OutputWire; import com.swirlds.platform.StateSigner; import com.swirlds.platform.state.signed.ReservedSignedState; +import com.swirlds.platform.system.transaction.ConsensusTransactionImpl; import edu.umd.cs.findbugs.annotations.NonNull; /** @@ -32,7 +32,8 @@ * @param stateSignature the output wire for the state signature */ public record StateSignerWiring( - @NonNull InputWire signState, @NonNull OutputWire stateSignature) { + @NonNull InputWire signState, + @NonNull OutputWire stateSignature) { /** * Create a new instance of this wiring @@ -40,7 +41,7 @@ public record StateSignerWiring( * @param scheduler the task scheduler for this wiring * @return the new wiring instance */ - public static StateSignerWiring create(@NonNull final TaskScheduler scheduler) { + public static StateSignerWiring create(@NonNull final TaskScheduler scheduler) { return new StateSignerWiring(scheduler.buildInputWire("state to sign"), scheduler.getOutputWire()); } @@ -50,6 +51,6 @@ public static StateSignerWiring create(@NonNull final TaskScheduler) signState).bind(stateSigner::signState); + ((BindableInputWire) signState).bind(stateSigner::signState); } } diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/wiring/components/Gossip.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/wiring/components/Gossip.java new file mode 100644 index 000000000000..876178d52e21 --- /dev/null +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/wiring/components/Gossip.java @@ -0,0 +1,51 @@ +/* + * Copyright (C) 2024 Hedera Hashgraph, LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.swirlds.platform.wiring.components; + +import com.swirlds.common.wiring.model.WiringModel; +import com.swirlds.common.wiring.wires.input.BindableInputWire; +import com.swirlds.common.wiring.wires.output.StandardOutputWire; +import com.swirlds.platform.consensus.EventWindow; +import com.swirlds.platform.event.GossipEvent; +import com.swirlds.platform.wiring.NoInput; +import edu.umd.cs.findbugs.annotations.NonNull; + +/** + * Implements gossip with network peers. + */ +public interface Gossip { + + /** + * Bind the input wires to the gossip implementation. + * + * @param model the wiring model for this node + * @param eventInput the input wire for events, events sent here should be gossiped to the network + * @param eventWindowInput the input wire for the current event window + * @param eventOutput the output wire for events received from peers during gossip + * @param startInput used to tell gossip to start + * @param stopInput used to tell gossip to stop + * @param clearInput used to tell gossip to clear its internal state + */ + void bind( + @NonNull WiringModel model, + @NonNull BindableInputWire eventInput, + @NonNull BindableInputWire eventWindowInput, + @NonNull StandardOutputWire eventOutput, + @NonNull BindableInputWire startInput, + @NonNull BindableInputWire stopInput, + @NonNull BindableInputWire clearInput); +} diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/wiring/components/GossipWiring.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/wiring/components/GossipWiring.java index 2e89b952976d..478bdd6acd4b 100644 --- a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/wiring/components/GossipWiring.java +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/wiring/components/GossipWiring.java @@ -16,41 +16,157 @@ package com.swirlds.platform.wiring.components; +import com.swirlds.common.context.PlatformContext; import com.swirlds.common.wiring.model.WiringModel; import com.swirlds.common.wiring.schedulers.TaskScheduler; -import com.swirlds.common.wiring.schedulers.builders.TaskSchedulerType; import com.swirlds.common.wiring.wires.input.BindableInputWire; import com.swirlds.common.wiring.wires.input.InputWire; import com.swirlds.common.wiring.wires.output.OutputWire; +import com.swirlds.common.wiring.wires.output.StandardOutputWire; +import com.swirlds.platform.consensus.EventWindow; import com.swirlds.platform.event.GossipEvent; +import com.swirlds.platform.wiring.NoInput; +import com.swirlds.platform.wiring.PlatformSchedulersConfig; import edu.umd.cs.findbugs.annotations.NonNull; /** * Wiring for gossip. - * - * @param eventInput the input wire for events received from peers during gossip - * @param eventOutput the output wire for events received from peers during gossip */ -public record GossipWiring(@NonNull InputWire eventInput, @NonNull OutputWire eventOutput) { +public class GossipWiring { /** - * Create a new instance of {@link GossipWiring}. - * - * @param model the wiring model - * @return the new instance + * The wiring model for this node. */ - @NonNull - public static GossipWiring create(@NonNull final WiringModel model) { - final TaskScheduler scheduler = model.schedulerBuilder("gossip") - .withType(TaskSchedulerType.DIRECT_THREADSAFE) + private final WiringModel model; + + /** + * The task scheduler for the gossip component. + */ + private final TaskScheduler scheduler; + + /** + * Events to be gossiped are sent here. + */ + private final BindableInputWire eventInput; + + /** + * Event window updates are sent here. + */ + private final BindableInputWire eventWindowInput; + + /** + * Events received through gossip are sent out over this wire. + */ + private final StandardOutputWire eventOutput; + + /** + * This wire is used to start gossip. + */ + private final BindableInputWire startInput; + + /** + * This wire is used to stop gossip. + */ + private final BindableInputWire stopInput; + + /** + * This wire is used to clear internal gossip state. + */ + private final BindableInputWire clearInput; + + public GossipWiring(@NonNull final PlatformContext platformContext, @NonNull final WiringModel model) { + this.model = model; + + scheduler = model.schedulerBuilder("gossip") + .configure(platformContext + .getConfiguration() + .getConfigData(PlatformSchedulersConfig.class) + .gossip()) .build() .cast(); - final BindableInputWire inputWire = scheduler.buildInputWire("received events"); - inputWire.bind(x -> x); + eventInput = scheduler.buildInputWire("events to gossip"); + eventWindowInput = scheduler.buildInputWire("event window"); + eventOutput = scheduler.buildSecondaryOutputWire(); + + startInput = scheduler.buildInputWire("start"); + stopInput = scheduler.buildInputWire("stop"); + clearInput = scheduler.buildInputWire("clear"); + } + + /** + * Bind the wiring to a gossip implementation. + * + * @param gossip the gossip implementation + */ + public void bind(@NonNull final Gossip gossip) { + gossip.bind(model, eventInput, eventWindowInput, eventOutput, startInput, stopInput, clearInput); + } + + /** + * Get the input wire for events to be gossiped to the network. + * + * @return the input wire for events + */ + @NonNull + public InputWire getEventInput() { + return eventInput; + } + + /** + * Get the input wire for the current event window. + * + * @return the input wire for the event window + */ + @NonNull + public InputWire getEventWindowInput() { + return eventWindowInput; + } - final OutputWire outputWire = scheduler.getOutputWire(); + /** + * Get the output wire for events received from peers during gossip. + * + * @return the output wire for events + */ + @NonNull + public OutputWire getEventOutput() { + return eventOutput; + } - return new GossipWiring(inputWire, outputWire); + /** + * Get the input wire to start gossip. + * + * @return the input wire to start gossip + */ + @NonNull + public InputWire getStartInput() { + return startInput; + } + + /** + * Get the input wire to stop gossip. + * + * @return the input wire to stop gossip + */ + @NonNull + public InputWire getStopInput() { + return stopInput; + } + + /** + * Get the input wire to clear the gossip state. + * + * @return the input wire to clear the gossip state + */ + @NonNull + public InputWire getClearInput() { + return clearInput; + } + + /** + * Flush the gossip scheduler. + */ + public void flush() { + scheduler.flush(); } } diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/wiring/components/HashLoggerWiring.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/wiring/components/HashLoggerWiring.java deleted file mode 100644 index c95fed5df342..000000000000 --- a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/wiring/components/HashLoggerWiring.java +++ /dev/null @@ -1,50 +0,0 @@ -/* - * Copyright (C) 2024 Hedera Hashgraph, LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.swirlds.platform.wiring.components; - -import com.swirlds.common.wiring.schedulers.TaskScheduler; -import com.swirlds.common.wiring.wires.input.BindableInputWire; -import com.swirlds.common.wiring.wires.input.InputWire; -import com.swirlds.platform.state.signed.ReservedSignedState; -import com.swirlds.platform.util.HashLogger; -import edu.umd.cs.findbugs.annotations.NonNull; - -/** - * Wiring for the {@link com.swirlds.platform.util.HashLogger}. - * - * @param hashLoggerInputWire the input wire for hash logger's reserved signed state to be logged - */ -public record HashLoggerWiring(@NonNull InputWire hashLoggerInputWire) { - /** - * Create a new instance of this wiring. - * - * @param taskScheduler the task scheduler for this wiring - * @return the new wiring instance - */ - public static HashLoggerWiring create(@NonNull final TaskScheduler taskScheduler) { - return new HashLoggerWiring(taskScheduler.buildInputWire("state")); - } - - /** - * Bind a hash logger to this wiring. - * - * @param hashLogger the hash logger to bind - */ - public void bind(@NonNull final HashLogger hashLogger) { - ((BindableInputWire) hashLoggerInputWire).bindConsumer(hashLogger::logHashes); - } -} diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/wiring/components/IssDetectorWiring.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/wiring/components/IssDetectorWiring.java deleted file mode 100644 index a590a2728475..000000000000 --- a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/wiring/components/IssDetectorWiring.java +++ /dev/null @@ -1,88 +0,0 @@ -/* - * Copyright (C) 2024 Hedera Hashgraph, LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.swirlds.platform.wiring.components; - -import com.swirlds.common.wiring.schedulers.TaskScheduler; -import com.swirlds.common.wiring.wires.input.BindableInputWire; -import com.swirlds.common.wiring.wires.input.InputWire; -import com.swirlds.common.wiring.wires.output.OutputWire; -import com.swirlds.platform.state.iss.IssDetector; -import com.swirlds.platform.state.signed.ReservedSignedState; -import com.swirlds.platform.system.state.notifications.IssNotification; -import com.swirlds.platform.system.status.actions.CatastrophicFailureAction; -import com.swirlds.platform.system.status.actions.PlatformStatusAction; -import com.swirlds.platform.wiring.NoInput; -import edu.umd.cs.findbugs.annotations.NonNull; -import java.util.List; -import java.util.Set; - -/** - * Wiring for the {@link IssDetector}. - * - * @param endOfPcesReplay the input wire for the end of the PCES replay - * @param stateAndRoundInput the input wire for completed rounds and their corresponding states - * @param overridingState the input wire for overriding states - * @param issNotificationOutput the output wire for ISS notifications - * @param statusActionOutput the output wire for catastrophic failure status actions - */ -public record IssDetectorWiring( - @NonNull InputWire endOfPcesReplay, - @NonNull InputWire stateAndRoundInput, - @NonNull InputWire overridingState, - @NonNull OutputWire issNotificationOutput, - @NonNull OutputWire statusActionOutput) { - /** - * Create a new instance of this wiring. - * - * @param taskScheduler the task scheduler that will detect ISSs - * @return the new wiring instance - */ - @NonNull - public static IssDetectorWiring create(@NonNull final TaskScheduler> taskScheduler) { - final OutputWire notificationOutputWire = - taskScheduler.getOutputWire().buildSplitter("issNotificationSplitter", "iss notifications"); - final OutputWire catastrophicFailureWire = - notificationOutputWire.buildTransformer("toStatusAction", "notification", notification -> { - if (Set.of(IssNotification.IssType.SELF_ISS, IssNotification.IssType.CATASTROPHIC_ISS) - .contains(notification.getIssType())) { - return new CatastrophicFailureAction(); - } - // don't change status for other types of ISS - return null; - }); - - return new IssDetectorWiring( - taskScheduler.buildInputWire("end of PCES replay"), - taskScheduler.buildInputWire("stateAndRound"), - taskScheduler.buildInputWire("overriding state"), - notificationOutputWire, - catastrophicFailureWire); - } - - /** - * Bind the given ISS detector to this wiring. - * - * @param issDetector the ISS detector - */ - public void bind(@NonNull final IssDetector issDetector) { - ((BindableInputWire) endOfPcesReplay).bindConsumer(issDetector::signalEndOfPreconsensusReplay); - ((BindableInputWire>) stateAndRoundInput) - .bind(issDetector::handleStateAndRound); - ((BindableInputWire>) overridingState) - .bind(issDetector::overridingState); - } -} diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/wiring/components/StateHasherWiring.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/wiring/components/StateHasherWiring.java deleted file mode 100644 index 266a459a5303..000000000000 --- a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/wiring/components/StateHasherWiring.java +++ /dev/null @@ -1,77 +0,0 @@ -/* - * Copyright (C) 2024 Hedera Hashgraph, LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.swirlds.platform.wiring.components; - -import com.swirlds.common.wiring.schedulers.TaskScheduler; -import com.swirlds.common.wiring.wires.input.BindableInputWire; -import com.swirlds.common.wiring.wires.input.InputWire; -import com.swirlds.common.wiring.wires.output.OutputWire; -import com.swirlds.platform.internal.ConsensusRound; -import com.swirlds.platform.state.signed.ReservedSignedState; -import com.swirlds.platform.state.signed.SignedStateHasher; -import com.swirlds.platform.wiring.StateAndRoundReserver; -import com.swirlds.platform.wiring.StateAndRoundToStateReserver; -import edu.umd.cs.findbugs.annotations.NonNull; - -/** - * Wiring for the {@link com.swirlds.platform.state.signed.SignedStateHasher} - * - * @param stateAndRoundInput the input wire for the state to hash, with the corresponding round - * @param stateAndRoundOutput the output wire for the hashed state, with the corresponding round - * @param stateOutput the output wire for the hashed state - * @param roundOutput the output wire for the consensus round - * @param flushRunnable the runnable to flush the task scheduler - */ -public record StateHasherWiring( - @NonNull InputWire stateAndRoundInput, - @NonNull OutputWire stateAndRoundOutput, - @NonNull OutputWire stateOutput, - @NonNull OutputWire roundOutput, - @NonNull Runnable flushRunnable) { - - /** - * Create a new instance of this wiring. - * - * @param taskScheduler the task scheduler for this wiring object - * @return the new wiring instance - */ - public static StateHasherWiring create(@NonNull final TaskScheduler taskScheduler) { - final OutputWire stateAndRoundOutput = taskScheduler - .getOutputWire() - .buildAdvancedTransformer(new StateAndRoundReserver("postHasher_stateAndRoundReserver")); - final OutputWire stateOutput = stateAndRoundOutput.buildAdvancedTransformer( - new StateAndRoundToStateReserver("postHasher_stateReserver")); - - return new StateHasherWiring( - taskScheduler.buildInputWire("state and round"), - stateAndRoundOutput, - stateOutput, - taskScheduler - .getOutputWire() - .buildTransformer("postHasher_getConsensusRound", "stateAndRound", StateAndRound::round), - taskScheduler::flush); - } - - /** - * Bind the given state hasher to this wiring. - * - * @param stateHasher the state hasher - */ - public void bind(@NonNull final SignedStateHasher stateHasher) { - ((BindableInputWire) stateAndRoundInput).bind(stateHasher::hashState); - } -} diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/wiring/components/StateSignatureCollectorWiring.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/wiring/components/StateSignatureCollectorWiring.java deleted file mode 100644 index d0b9bd341d6c..000000000000 --- a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/wiring/components/StateSignatureCollectorWiring.java +++ /dev/null @@ -1,176 +0,0 @@ -/* - * Copyright (C) 2023-2024 Hedera Hashgraph, LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.swirlds.platform.wiring.components; - -import com.hedera.hapi.platform.event.StateSignaturePayload; -import com.swirlds.common.wiring.model.WiringModel; -import com.swirlds.common.wiring.schedulers.TaskScheduler; -import com.swirlds.common.wiring.transformers.WireTransformer; -import com.swirlds.common.wiring.wires.input.BindableInputWire; -import com.swirlds.common.wiring.wires.input.InputWire; -import com.swirlds.common.wiring.wires.output.OutputWire; -import com.swirlds.platform.components.transaction.system.ScopedSystemTransaction; -import com.swirlds.platform.components.transaction.system.SystemTransactionExtractionUtils; -import com.swirlds.platform.event.GossipEvent; -import com.swirlds.platform.internal.ConsensusRound; -import com.swirlds.platform.state.signed.ReservedSignedState; -import com.swirlds.platform.state.signed.StateSignatureCollector; -import com.swirlds.platform.wiring.NoInput; -import com.swirlds.platform.wiring.SignedStateReserver; -import edu.umd.cs.findbugs.annotations.NonNull; -import java.util.List; -import java.util.Objects; - -/** - * Wiring for the state signature collector. - */ -public class StateSignatureCollectorWiring { - - private final TaskScheduler> taskScheduler; - private final BindableInputWire>, List> - preConsSigInput; - private final BindableInputWire>, List> - postConsSigInput; - private final BindableInputWire> reservedStateInput; - private final BindableInputWire> clearInput; - private final InputWire preConsensusEventInput; - private final InputWire postConsensusEventInput; - private final OutputWire allStatesOutput; - private final OutputWire completeStatesOutput; - - /** - * Constructor. - * - * @param model the wiring model for the platform - * @param taskScheduler the task scheduler that will perform the prehandling - */ - private StateSignatureCollectorWiring( - @NonNull final WiringModel model, @NonNull final TaskScheduler> taskScheduler) { - - this.taskScheduler = Objects.requireNonNull(taskScheduler); - // Create the outputs - final OutputWire stateSplitter = - taskScheduler.getOutputWire().buildSplitter("reservedStateSplitter", "reserved state lists"); - this.allStatesOutput = stateSplitter.buildAdvancedTransformer(new SignedStateReserver("allStatesReserver")); - this.completeStatesOutput = allStatesOutput - .buildFilter("completeStateFilter", "states", StateSignatureCollectorWiring::completeStates) - .buildAdvancedTransformer(new SignedStateReserver("completeStatesReserver")); - - // Create input for preconsensus signatures - final WireTransformer>> - preConsensusTransformer = new WireTransformer<>( - model, - "extractPreconsensusSignatureTransactions", - "preconsensus signatures", - event -> SystemTransactionExtractionUtils.extractFromEvent(event, StateSignaturePayload.class)); - preConsensusEventInput = preConsensusTransformer.getInputWire(); - preConsSigInput = taskScheduler.buildInputWire("preconsensus signature transactions"); - preConsensusTransformer.getOutputWire().solderTo(preConsSigInput); - - // Create input for consensus signatures - final WireTransformer>> - postConsensusTransformer = new WireTransformer<>( - model, - "extractConsensusSignatureTransactions", - "consensus events", - round -> SystemTransactionExtractionUtils.extractFromRound(round, StateSignaturePayload.class)); - postConsensusEventInput = postConsensusTransformer.getInputWire(); - postConsSigInput = taskScheduler.buildInputWire("consensus signature transactions"); - postConsensusTransformer.getOutputWire().solderTo(postConsSigInput); - - // Create input for signed states - reservedStateInput = taskScheduler.buildInputWire("state"); - - // Create clear input - clearInput = taskScheduler.buildInputWire("clear"); - } - - private static boolean completeStates(@NonNull final ReservedSignedState rs) { - if (rs.get().isComplete()) { - return true; - } - rs.close(); - return false; - } - - /** - * Create a new instance of this wiring. - * - * @param model the wiring model - * @param taskScheduler the task scheduler that will perform the prehandling - * @return the new wiring instance - */ - @NonNull - public static StateSignatureCollectorWiring create( - @NonNull final WiringModel model, @NonNull final TaskScheduler> taskScheduler) { - return new StateSignatureCollectorWiring(model, taskScheduler); - } - - /** - * Bind the preconsensus event handler to the input wire. - * - * @param stateSignatureCollector collects and manages state signatures - */ - public void bind(@NonNull final StateSignatureCollector stateSignatureCollector) { - Objects.requireNonNull(stateSignatureCollector); - preConsSigInput.bind(stateSignatureCollector::handlePreconsensusSignatures); - postConsSigInput.bind(stateSignatureCollector::handlePostconsensusSignatures); - reservedStateInput.bind(stateSignatureCollector::addReservedState); - clearInput.bindConsumer(stateSignatureCollector::clear); - } - - /** @return the input wire for the pre-consensus events (which contain signatures) */ - @NonNull - public InputWire preConsensusEventInput() { - return preConsensusEventInput; - } - - /** @return the input wire for the states (that we need to collect signatures for) */ - @NonNull - public InputWire getReservedStateInput() { - return reservedStateInput; - } - - /** @return the input wire for consensus rounds (which contain signatures) */ - @NonNull - public InputWire getConsensusRoundInput() { - return postConsensusEventInput; - } - - /** @return the output wire all states returned by the collector (complete, and old incomplete) */ - @NonNull - public OutputWire getAllStatesOutput() { - return allStatesOutput; - } - - /** @return the output wire complete states returned by the collector */ - @NonNull - public OutputWire getCompleteStatesOutput() { - return completeStatesOutput; - } - - /** @return the input wire that clears the collector */ - @NonNull - public InputWire getClearInput() { - return clearInput; - } - - /** Flush the task scheduler. */ - public void flush() { - taskScheduler.flush(); - } -} diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/wiring/generate-platform-diagram.sh b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/wiring/generate-platform-diagram.sh index 1c3e8e9ff058..1b0d9412b6e9 100755 --- a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/wiring/generate-platform-diagram.sh +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/wiring/generate-platform-diagram.sh @@ -10,9 +10,8 @@ SCRIPT_PATH="$(dirname "$(readlink -f "$0")")" pcli diagram \ -l 'TransactionPrehandler:futures:consensusRoundHandler' \ - -l 'gossip:get events:shadowgraph' \ - -l 'EventCreationManager:get transactions:transactionPool' \ - -l 'RunningEventHasher:future hash:consensusRoundHandler' \ + -l 'EventCreationManager:get transactions:TransactionPool' \ + -l 'ConsensusEventStream:future hash:consensusRoundHandler' \ -s 'eventWindowManager:event window:🌀' \ -s 'heartbeat:heartbeat:â¤ï¸' \ -s 'TransactionPrehandler:futures:🔮' \ @@ -20,44 +19,51 @@ pcli diagram \ -s 'OrphanBufferSplitter:events to gossip:📬' \ -s 'getKeystoneEventSequenceNumber:flush request:🚽' \ -s 'extractOldestMinimumGenerationOnDisk:minimum identifier to store:📀' \ - -s 'SelfEventSigner:non-validated events:ðŸŽ' \ + -s 'StaleEventDetectorRouter:non-validated events:ðŸŽ' \ -s 'Mystery Input:mystery data:â”' \ + -s 'stateSigner:submit transaction:🖋ï¸' \ -s 'stateSigner:signature transactions:🖋ï¸' \ - -s 'issNotificationSplitter:IssNotification:💥' \ - -s 'toStatusAction:PlatformStatusAction:💀' \ + -s 'IssDetectorSplitter:IssNotification:💥' \ + -s 'getStatusAction:PlatformStatusAction:💀' \ -s 'toNotification:state written notification:📦' \ -s 'latestCompleteStateNotifier:complete state notification:💢' \ -s 'OrphanBufferSplitter:preconsensus signatures:🔰' \ -s 'RunningEventHashOverride:hash override:💨' \ + -s 'TransactionResubmitterSplitter:submit transaction:â™»ï¸' \ + -s 'StaleEventDetectorRouter:publishStaleEvent:âš°ï¸' \ -s 'toStateWrittenToDiskAction:PlatformStatusAction:💾' \ -s 'StatusStateMachine:PlatformStatus:🚦' \ -g 'Event Validation:InternalEventValidator,EventDeduplicator,EventSignatureValidator' \ - -g 'Event Hashing:eventHasher,postHashCollector' \ + -g 'Event Hashing:EventHasher,PostHashCollector' \ -g 'Orphan Buffer:OrphanBuffer,OrphanBufferSplitter' \ -g 'Consensus Engine:ConsensusEngine,ConsensusEngineSplitter,eventWindowManager,getKeystoneEventSequenceNumber,getConsensusEvents' \ - -g 'State File Manager:saveToDiskFilter,signedStateFileManager,extractOldestMinimumGenerationOnDisk,toStateWrittenToDiskAction,toNotification' \ - -g 'State File Management:State File Manager,📦,📀,💾' \ - -g 'State Signature Collector:stateSignatureCollector,reservedStateSplitter,allStatesReserver,completeStateFilter,completeStatesReserver,extractConsensusSignatureTransactions,extractPreconsensusSignatureTransactions,latestCompleteStateNotifier' \ + -g 'State Snapshot Manager:saveToDiskFilter,StateSnapshotManager,extractOldestMinimumGenerationOnDisk,toStateWrittenToDiskAction,toNotification' \ + -g 'State File Management:State Snapshot Manager,📦,📀,💾' \ + -g 'State Signature Collector:StateSignatureCollector,reservedStateSplitter,allStatesReserver,completeStateFilter,completeStatesReserver,extractConsensusSignatureTransactions,extractPreconsensusSignatureTransactions,latestCompleteStateNotifier' \ -g 'State Signature Collection:State Signature Collector,latestCompleteStateNexus,💢' \ -g 'Preconsensus Event Stream:PcesSequencer,PcesWriter' \ - -g 'Event Creation:EventCreationManager,transactionPool,SelfEventSigner,ðŸŽ' \ - -g 'Gossip:gossip,shadowgraph,InOrderLinker' \ - -g 'ISS Detector:issDetector,issNotificationSplitter,issHandler,toStatusAction' \ + -g 'Event Creation:EventCreationManager,TransactionPool,SelfEventSigner' \ + -g 'ISS Detector:IssDetector,IssDetectorSplitter,issHandler,getStatusAction' \ -g 'Heartbeat:heartbeat,â¤ï¸' \ -g 'PCES Replay:pcesReplayer,✅' \ -g 'Consensus Round Handler:consensusRoundHandler,postHandler_stateAndRoundReserver,getState,savedStateController' \ - -g 'State Hasher:stateHasher,postHasher_stateAndRoundReserver,postHasher_getConsensusRound,postHasher_stateReserver' \ + -g 'State Hasher:StateHasher,postHasher_stateAndRoundReserver,postHasher_getConsensusRound,postHasher_stateReserver' \ -g 'Consensus:Consensus Engine,🚽,🌀' \ - -g 'State Verification:stateSigner,hashLogger,ISS Detector,🖋ï¸,💥,💀' \ + -g 'State Verification:stateSigner,HashLogger,ISS Detector,🖋ï¸,💥,💀' \ -g 'Transaction Handling:Consensus Round Handler,latestImmutableStateNexus' \ -g 'Round Durability Buffer:RoundDurabilityBuffer,RoundDurabilityBufferSplitter' \ + -g 'Stale Event Detector:StaleEventDetector,StaleEventDetectorSplitter,StaleEventDetectorRouter' \ + -g 'Transaction Resubmitter:TransactionResubmitter,TransactionResubmitterSplitter' \ + -g 'Stale Events:Stale Event Detector,Transaction Resubmitter,âš°ï¸,â™»ï¸,ðŸŽ' \ -c 'Orphan Buffer' \ -c 'Consensus Engine' \ -c 'State Signature Collector' \ - -c 'State File Manager' \ + -c 'State Snapshot Manager' \ -c 'Consensus Round Handler' \ -c 'State Hasher' \ -c 'ISS Detector' \ -c 'Round Durability Buffer' \ -c 'Wait For Crash Durability' \ + -c 'Stale Event Detector' \ + -c 'Transaction Resubmitter' \ -o "${SCRIPT_PATH}/../../../../../../../../docs/core/wiring-diagram.svg" diff --git a/platform-sdk/swirlds-platform-core/src/main/java/module-info.java b/platform-sdk/swirlds-platform-core/src/main/java/module-info.java index 64b86268eb5c..f93b23af52da 100644 --- a/platform-sdk/swirlds-platform-core/src/main/java/module-info.java +++ b/platform-sdk/swirlds-platform-core/src/main/java/module-info.java @@ -17,6 +17,7 @@ exports com.swirlds.platform.components.state.output; exports com.swirlds.platform.config; exports com.swirlds.platform.config.legacy; + exports com.swirlds.platform.event.report; exports com.swirlds.platform.gui.hashgraph; exports com.swirlds.platform.gui.hashgraph.internal; exports com.swirlds.platform.network.connection; @@ -52,7 +53,6 @@ exports com.swirlds.platform.system.state.notifications; exports com.swirlds.platform.system.status; exports com.swirlds.platform.system.status.actions; - exports com.swirlds.platform.threading; exports com.swirlds.platform.util; /* Targeted Exports to External Libraries */ @@ -114,14 +114,28 @@ exports com.swirlds.platform.wiring.components; exports com.swirlds.platform.event.hashing; exports com.swirlds.platform.event.orphan; + exports com.swirlds.platform.state.merkle; + exports com.swirlds.platform.state.merkle.logging; + exports com.swirlds.platform.state.merkle.disk; + exports com.swirlds.platform.state.merkle.singleton; + exports com.swirlds.platform.state.merkle.memory; + exports com.swirlds.platform.state.merkle.queue; + exports com.swirlds.platform.state.spi; exports com.swirlds.platform.publisher; exports com.swirlds.platform.components.consensus; + exports com.swirlds.platform.pool; + exports com.swirlds.platform.state.snapshot; requires transitive com.swirlds.base; requires transitive com.swirlds.cli; requires transitive com.swirlds.common; requires transitive com.swirlds.config.api; + requires transitive com.swirlds.fcqueue; + requires transitive com.swirlds.merkle; + requires transitive com.swirlds.merkledb; requires transitive com.swirlds.metrics.api; + requires transitive com.swirlds.state.api; + requires transitive com.swirlds.virtualmap; requires transitive com.fasterxml.jackson.annotation; requires transitive com.fasterxml.jackson.databind; requires transitive com.hedera.node.hapi; @@ -130,8 +144,6 @@ requires transitive org.apache.logging.log4j; requires com.swirlds.config.extensions; requires com.swirlds.logging; - requires com.swirlds.merkledb; - requires com.swirlds.virtualmap; requires com.fasterxml.jackson.core; requires com.fasterxml.jackson.dataformat.yaml; requires java.desktop; diff --git a/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/AddressBookInitializerTest.java b/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/AddressBookInitializerTest.java index 1643a234b100..d65aa2bc4ce6 100644 --- a/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/AddressBookInitializerTest.java +++ b/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/AddressBookInitializerTest.java @@ -34,6 +34,7 @@ import com.swirlds.common.context.PlatformContext; import com.swirlds.common.io.utility.FileUtils; import com.swirlds.common.platform.NodeId; +import com.swirlds.common.test.fixtures.Randotron; import com.swirlds.common.test.fixtures.platform.TestPlatformContextBuilder; import com.swirlds.config.extensions.test.fixtures.TestConfigBuilder; import com.swirlds.platform.config.AddressBookConfig_; @@ -45,7 +46,7 @@ import com.swirlds.platform.system.SwirldState; import com.swirlds.platform.system.address.Address; import com.swirlds.platform.system.address.AddressBook; -import com.swirlds.platform.test.fixtures.addressbook.RandomAddressBookGenerator; +import com.swirlds.platform.test.fixtures.addressbook.RandomAddressBookBuilder; import edu.umd.cs.findbugs.annotations.NonNull; import edu.umd.cs.findbugs.annotations.Nullable; import java.io.File; @@ -54,6 +55,7 @@ import java.nio.file.Path; import java.util.Arrays; import java.util.Objects; +import java.util.Random; import java.util.concurrent.atomic.AtomicReference; import java.util.function.Supplier; import org.junit.jupiter.api.DisplayName; @@ -69,9 +71,10 @@ class AddressBookInitializerTest { @Test @DisplayName("Force the use of the config address book") void forceUseOfConfigAddressBook() throws IOException { + final Randotron randotron = Randotron.create(); clearTestDirectory(); - final AddressBook configAddressBook = getRandomAddressBook(); - final SignedState signedState = getMockSignedState7WeightRandomAddressBook(); + final AddressBook configAddressBook = getRandomAddressBook(randotron); + final SignedState signedState = getMockSignedState7WeightRandomAddressBook(randotron); final AddressBookInitializer initializer = new AddressBookInitializer( new NodeId(0), getMockSoftwareVersion(2), @@ -97,8 +100,9 @@ void forceUseOfConfigAddressBook() throws IOException { @Test @DisplayName("Genesis. Config.txt Initializes Address Book.") void noStateLoadedFromDisk() throws IOException { + final Randotron randotron = Randotron.create(); clearTestDirectory(); - final AddressBook configAddressBook = getRandomAddressBook(); + final AddressBook configAddressBook = getRandomAddressBook(randotron); // initial state has no address books set. final SignedState signedState = getMockSignedState(10, null, null, true); final AddressBookInitializer initializer = new AddressBookInitializer( @@ -123,8 +127,9 @@ void noStateLoadedFromDisk() throws IOException { @Test @DisplayName("No state loaded from disk. Genesis State set 0 weight.") void noStateLoadedFromDiskGenesisStateSetZeroWeight() throws IOException { + final Randotron randotron = Randotron.create(); clearTestDirectory(); - final AddressBook configAddressBook = getRandomAddressBook(); + final AddressBook configAddressBook = getRandomAddressBook(randotron); // genesis state address book is set to null to test the code path where it may be null. final SignedState signedState = getMockSignedState(10, null, null, true); final AddressBookInitializer initializer = new AddressBookInitializer( @@ -149,8 +154,9 @@ void noStateLoadedFromDiskGenesisStateSetZeroWeight() throws IOException { @Test @DisplayName("No state loaded from disk. Genesis State modifies address book entries.") void noStateLoadedFromDiskGenesisStateChangedAddressBook() throws IOException { + final Randotron randotron = Randotron.create(); clearTestDirectory(); - final AddressBook configAddressBook = getRandomAddressBook(); + final AddressBook configAddressBook = getRandomAddressBook(randotron); final SignedState signedState = getMockSignedState(7, configAddressBook, null, true); final AddressBookInitializer initializer = new AddressBookInitializer( new NodeId(0), @@ -176,9 +182,11 @@ void noStateLoadedFromDiskGenesisStateChangedAddressBook() throws IOException { @Test @DisplayName("Current software version is equal to state software version.") void currentVersionEqualsStateVersion() throws IOException { + final Randotron randotron = Randotron.create(); clearTestDirectory(); // start state with previous address book - final SignedState signedState = getMockSignedState(2, getRandomAddressBook(), getRandomAddressBook(), false); + final SignedState signedState = + getMockSignedState(2, getRandomAddressBook(randotron), getRandomAddressBook(randotron), false); final AddressBook configAddressBook = copyWithWeightChanges(signedState.getAddressBook(), 10); final AddressBookInitializer initializer = new AddressBookInitializer( new NodeId(0), @@ -206,8 +214,10 @@ void currentVersionEqualsStateVersion() throws IOException { @Test @DisplayName("Version upgrade, SwirldState set 0 weight.") void versionUpgradeSwirldStateZeroWeight() throws IOException { + final Randotron randotron = Randotron.create(); clearTestDirectory(); - final SignedState signedState = getMockSignedState(0, getRandomAddressBook(), getRandomAddressBook(), false); + final SignedState signedState = + getMockSignedState(0, getRandomAddressBook(randotron), getRandomAddressBook(randotron), false); final AddressBook configAddressBook = copyWithWeightChanges(signedState.getAddressBook(), 10); final AddressBookInitializer initializer = new AddressBookInitializer( new NodeId(0), @@ -234,8 +244,10 @@ void versionUpgradeSwirldStateZeroWeight() throws IOException { @Test @DisplayName("Version upgrade, Swirld State modified the address book.") void versionUpgradeSwirldStateModifiedAddressBook() throws IOException { + final Randotron randotron = Randotron.create(); clearTestDirectory(); - final SignedState signedState = getMockSignedState(2, getRandomAddressBook(), getRandomAddressBook(), false); + final SignedState signedState = + getMockSignedState(2, getRandomAddressBook(randotron), getRandomAddressBook(randotron), false); final AddressBook configAddressBook = copyWithWeightChanges(signedState.getAddressBook(), 3); final AddressBookInitializer initializer = new AddressBookInitializer( new NodeId(0), @@ -262,8 +274,9 @@ void versionUpgradeSwirldStateModifiedAddressBook() throws IOException { @Test @DisplayName("Version upgrade, Swirld State updates weight successfully.") void versionUpgradeSwirldStateWeightUpdateWorks() throws IOException { + final Randotron randotron = Randotron.create(); clearTestDirectory(); - final SignedState signedState = getMockSignedState7WeightRandomAddressBook(); + final SignedState signedState = getMockSignedState7WeightRandomAddressBook(randotron); final AddressBook configAddressBook = copyWithWeightChanges(signedState.getAddressBook(), 5); final AddressBookInitializer initializer = new AddressBookInitializer( new NodeId(0), @@ -336,8 +349,8 @@ private SoftwareVersion getMockSoftwareVersion(int version) { * * @return The mock SignedState. */ - private SignedState getMockSignedState7WeightRandomAddressBook() { - return getMockSignedState(7, getRandomAddressBook(), getRandomAddressBook(), false); + private SignedState getMockSignedState7WeightRandomAddressBook(@NonNull final Randotron randotron) { + return getMockSignedState(7, getRandomAddressBook(randotron), getRandomAddressBook(randotron), false); } /** @@ -422,11 +435,8 @@ private Supplier getMockSwirldStateSupplier(int scenario) { * @return the address book created. */ @NonNull - private AddressBook getRandomAddressBook() { - return new RandomAddressBookGenerator() - .setSize(5) - .setCustomWeightGenerator(i -> i.id()) - .build(); + private AddressBook getRandomAddressBook(@NonNull final Random random) { + return RandomAddressBookBuilder.create(random).withSize(5).build(); } /** diff --git a/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/DummyHashgraph.java b/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/DummyHashgraph.java index ec6b95f05a86..96009fdb2c2a 100644 --- a/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/DummyHashgraph.java +++ b/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/DummyHashgraph.java @@ -18,7 +18,7 @@ import com.swirlds.common.platform.NodeId; import com.swirlds.platform.system.address.AddressBook; -import com.swirlds.platform.test.fixtures.addressbook.RandomAddressBookGenerator; +import com.swirlds.platform.test.fixtures.addressbook.RandomAddressBookBuilder; import edu.umd.cs.findbugs.annotations.NonNull; import java.util.HashMap; import java.util.Random; @@ -37,7 +37,7 @@ public class DummyHashgraph { isInCriticalQuorum = new HashMap<>(); numUserTransEvents = 0; lastRoundReceivedAllTransCons = 0; - addressBook = new RandomAddressBookGenerator(random).setSize(100).build(); + addressBook = RandomAddressBookBuilder.create(random).withSize(100).build(); this.selfId = addressBook.getNodeId(selfIndex); } diff --git a/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/SavedStateMetadataTests.java b/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/SavedStateMetadataTests.java index 19a917828b08..a3c49a391a12 100644 --- a/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/SavedStateMetadataTests.java +++ b/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/SavedStateMetadataTests.java @@ -18,23 +18,21 @@ import static com.swirlds.common.test.fixtures.RandomUtils.getRandomPrintSeed; import static com.swirlds.common.test.fixtures.RandomUtils.randomHash; -import static com.swirlds.platform.state.signed.SavedStateMetadataField.CONSENSUS_TIMESTAMP; -import static com.swirlds.platform.state.signed.SavedStateMetadataField.EPOCH_HASH; -import static com.swirlds.platform.state.signed.SavedStateMetadataField.HASH; -import static com.swirlds.platform.state.signed.SavedStateMetadataField.HASH_MNEMONIC; -import static com.swirlds.platform.state.signed.SavedStateMetadataField.LEGACY_RUNNING_EVENT_HASH; -import static com.swirlds.platform.state.signed.SavedStateMetadataField.LEGACY_RUNNING_EVENT_HASH_MNEMONIC; -import static com.swirlds.platform.state.signed.SavedStateMetadataField.MINIMUM_GENERATION_NON_ANCIENT; -import static com.swirlds.platform.state.signed.SavedStateMetadataField.NODE_ID; -import static com.swirlds.platform.state.signed.SavedStateMetadataField.NUMBER_OF_CONSENSUS_EVENTS; -import static com.swirlds.platform.state.signed.SavedStateMetadataField.ROUND; -import static com.swirlds.platform.state.signed.SavedStateMetadataField.RUNNING_EVENT_HASH; -import static com.swirlds.platform.state.signed.SavedStateMetadataField.RUNNING_EVENT_HASH_MNEMONIC; -import static com.swirlds.platform.state.signed.SavedStateMetadataField.SIGNING_NODES; -import static com.swirlds.platform.state.signed.SavedStateMetadataField.SIGNING_WEIGHT_SUM; -import static com.swirlds.platform.state.signed.SavedStateMetadataField.SOFTWARE_VERSION; -import static com.swirlds.platform.state.signed.SavedStateMetadataField.TOTAL_WEIGHT; -import static com.swirlds.platform.state.signed.SavedStateMetadataField.WALL_CLOCK_TIME; +import static com.swirlds.platform.state.snapshot.SavedStateMetadataField.CONSENSUS_TIMESTAMP; +import static com.swirlds.platform.state.snapshot.SavedStateMetadataField.EPOCH_HASH; +import static com.swirlds.platform.state.snapshot.SavedStateMetadataField.HASH; +import static com.swirlds.platform.state.snapshot.SavedStateMetadataField.HASH_MNEMONIC; +import static com.swirlds.platform.state.snapshot.SavedStateMetadataField.LEGACY_RUNNING_EVENT_HASH; +import static com.swirlds.platform.state.snapshot.SavedStateMetadataField.LEGACY_RUNNING_EVENT_HASH_MNEMONIC; +import static com.swirlds.platform.state.snapshot.SavedStateMetadataField.MINIMUM_GENERATION_NON_ANCIENT; +import static com.swirlds.platform.state.snapshot.SavedStateMetadataField.NODE_ID; +import static com.swirlds.platform.state.snapshot.SavedStateMetadataField.NUMBER_OF_CONSENSUS_EVENTS; +import static com.swirlds.platform.state.snapshot.SavedStateMetadataField.ROUND; +import static com.swirlds.platform.state.snapshot.SavedStateMetadataField.SIGNING_NODES; +import static com.swirlds.platform.state.snapshot.SavedStateMetadataField.SIGNING_WEIGHT_SUM; +import static com.swirlds.platform.state.snapshot.SavedStateMetadataField.SOFTWARE_VERSION; +import static com.swirlds.platform.state.snapshot.SavedStateMetadataField.TOTAL_WEIGHT; +import static com.swirlds.platform.state.snapshot.SavedStateMetadataField.WALL_CLOCK_TIME; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertThrows; @@ -47,10 +45,10 @@ import com.swirlds.platform.consensus.ConsensusSnapshot; import com.swirlds.platform.state.PlatformState; import com.swirlds.platform.state.State; -import com.swirlds.platform.state.signed.SavedStateMetadata; -import com.swirlds.platform.state.signed.SavedStateMetadataField; import com.swirlds.platform.state.signed.SigSet; import com.swirlds.platform.state.signed.SignedState; +import com.swirlds.platform.state.snapshot.SavedStateMetadata; +import com.swirlds.platform.state.snapshot.SavedStateMetadataField; import com.swirlds.platform.system.BasicSoftwareVersion; import com.swirlds.platform.system.SoftwareVersion; import com.swirlds.platform.system.address.AddressBook; @@ -105,7 +103,6 @@ void randomDataTest() throws IOException { final Hash hash = randomHash(random); final long numberOfConsensusEvents = random.nextLong(); final Instant timestamp = RandomUtils.randomInstant(random); - final Hash runningEventHash = randomHash(random); final Hash legacyRunningEventHash = randomHash(random); final long minimumGenerationNonAncient = random.nextLong(); final SoftwareVersion softwareVersion = new BasicSoftwareVersion(random.nextLong()); @@ -126,8 +123,6 @@ void randomDataTest() throws IOException { hash.toMnemonic(), numberOfConsensusEvents, timestamp, - runningEventHash, - runningEventHash.toMnemonic(), legacyRunningEventHash, legacyRunningEventHash.toMnemonic(), minimumGenerationNonAncient, @@ -147,8 +142,6 @@ void randomDataTest() throws IOException { assertEquals(hash.toMnemonic(), deserialized.hashMnemonic()); assertEquals(numberOfConsensusEvents, deserialized.numberOfConsensusEvents()); assertEquals(timestamp, deserialized.consensusTimestamp()); - assertEquals(runningEventHash, deserialized.runningEventHash()); - assertEquals(runningEventHash.toMnemonic(), deserialized.runningEventHashMnemonic()); assertEquals(legacyRunningEventHash, deserialized.legacyRunningEventHash()); assertEquals(legacyRunningEventHash.toMnemonic(), deserialized.legacyRunningEventHashMnemonic()); assertEquals(minimumGenerationNonAncient, deserialized.minimumGenerationNonAncient()); @@ -171,7 +164,6 @@ void randomDataEmptyListTest() throws IOException { final Hash hash = randomHash(random); final long numberOfConsensusEvents = random.nextLong(); final Instant timestamp = RandomUtils.randomInstant(random); - final Hash runningEventHash = randomHash(random); final Hash legacyRunningEventHash = randomHash(random); final long minimumGenerationNonAncient = random.nextLong(); final SoftwareVersion softwareVersion = new BasicSoftwareVersion(random.nextLong()); @@ -189,8 +181,6 @@ void randomDataEmptyListTest() throws IOException { hash.toMnemonic(), numberOfConsensusEvents, timestamp, - runningEventHash, - runningEventHash.toMnemonic(), legacyRunningEventHash, legacyRunningEventHash.toMnemonic(), minimumGenerationNonAncient, @@ -210,8 +200,6 @@ void randomDataEmptyListTest() throws IOException { assertEquals(hash.toMnemonic(), deserialized.hashMnemonic()); assertEquals(numberOfConsensusEvents, deserialized.numberOfConsensusEvents()); assertEquals(timestamp, deserialized.consensusTimestamp()); - assertEquals(runningEventHash, deserialized.runningEventHash()); - assertEquals(runningEventHash.toMnemonic(), deserialized.runningEventHashMnemonic()); assertEquals(minimumGenerationNonAncient, deserialized.minimumGenerationNonAncient()); assertEquals(softwareVersion.toString(), deserialized.softwareVersion()); assertEquals(wallClockTime, deserialized.wallClockTime()); @@ -259,8 +247,6 @@ void handleNewlinesElegantlyTest() throws IOException { final String hashMnemonic = hash.toMnemonic(); final long numberOfConsensusEvents = random.nextLong(); final Instant timestamp = RandomUtils.randomInstant(random); - final Hash runningEventHash = randomHash(random); - final String runningEventHashMnemonic = runningEventHash.toMnemonic(); final Hash legacyRunningEventHash = randomHash(random); final String legacyRunningEventHashMnemonic = legacyRunningEventHash.toMnemonic(); final long minimumGenerationNonAncient = random.nextLong(); @@ -281,8 +267,6 @@ void handleNewlinesElegantlyTest() throws IOException { hashMnemonic, numberOfConsensusEvents, timestamp, - runningEventHash, - runningEventHashMnemonic, legacyRunningEventHash, legacyRunningEventHashMnemonic, minimumGenerationNonAncient, @@ -302,8 +286,6 @@ void handleNewlinesElegantlyTest() throws IOException { assertEquals(hashMnemonic, deserialized.hashMnemonic()); assertEquals(numberOfConsensusEvents, deserialized.numberOfConsensusEvents()); assertEquals(timestamp, deserialized.consensusTimestamp()); - assertEquals(runningEventHash, deserialized.runningEventHash()); - assertEquals(runningEventHashMnemonic, deserialized.runningEventHashMnemonic()); assertEquals(minimumGenerationNonAncient, deserialized.minimumGenerationNonAncient()); assertEquals("why//are//there//newlines//here//please//stop//", deserialized.softwareVersion()); assertEquals(wallClockTime, deserialized.wallClockTime()); @@ -348,7 +330,6 @@ private void testMalformedFile( final Hash hash = randomHash(random); final long numberOfConsensusEvents = random.nextLong(); final Instant timestamp = RandomUtils.randomInstant(random); - final Hash runningEventHash = randomHash(random); final Hash legacyRunningEventHash = randomHash(random); final long minimumGenerationNonAncient = random.nextLong(); final SoftwareVersion softwareVersion = new BasicSoftwareVersion(random.nextLong()); @@ -368,8 +349,6 @@ private void testMalformedFile( hash.toMnemonic(), numberOfConsensusEvents, timestamp, - runningEventHash, - runningEventHash.toMnemonic(), legacyRunningEventHash, legacyRunningEventHash.toMnemonic(), minimumGenerationNonAncient, @@ -418,16 +397,6 @@ private void testMalformedFile( } assertEquals(numberOfConsensusEvents, deserialized.numberOfConsensusEvents()); assertEquals(timestamp, deserialized.consensusTimestamp()); - if (invalidFields.contains(RUNNING_EVENT_HASH)) { - assertNull(deserialized.runningEventHash()); - } else { - assertEquals(runningEventHash, deserialized.runningEventHash()); - } - if (invalidFields.contains(RUNNING_EVENT_HASH_MNEMONIC)) { - assertNull(deserialized.runningEventHashMnemonic()); - } else { - assertEquals(runningEventHash.toMnemonic(), deserialized.runningEventHashMnemonic()); - } if (invalidFields.contains(LEGACY_RUNNING_EVENT_HASH)) { assertNull(deserialized.legacyRunningEventHash()); } else { @@ -569,8 +538,8 @@ void invalidHashTest() throws IOException { final Random random = getRandomPrintSeed(); testMalformedFile( random, - (s, m) -> s.replace(m.runningEventHash().toString(), "NOT_A_REAL_HASH"), - Set.of(SavedStateMetadataField.RUNNING_EVENT_HASH)); + (s, m) -> s.replace(m.legacyRunningEventHash().toString(), "NOT_A_REAL_HASH"), + Set.of(LEGACY_RUNNING_EVENT_HASH)); } @Test @@ -613,14 +582,6 @@ void missingDataTestTest() throws IOException { Set.of(NUMBER_OF_CONSENSUS_EVENTS)); testMalformedFile( random, (s, m) -> s.replace(CONSENSUS_TIMESTAMP.name(), "notARealKey"), Set.of(CONSENSUS_TIMESTAMP)); - testMalformedFile( - random, - (s, m) -> s.replace("\n" + RUNNING_EVENT_HASH.name() + ":", "\nnotARealKey:"), - Set.of(RUNNING_EVENT_HASH)); - testMalformedFile( - random, - (s, m) -> s.replace("\n" + RUNNING_EVENT_HASH_MNEMONIC.name(), "\nnotARealKey"), - Set.of(RUNNING_EVENT_HASH_MNEMONIC)); testMalformedFile( random, (s, m) -> s.replace(LEGACY_RUNNING_EVENT_HASH.name() + ":", "notARealKey:"), diff --git a/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/SignedStateFileReadWriteTest.java b/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/SignedStateFileReadWriteTest.java index 4e6432a544cd..d38097c8dc36 100644 --- a/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/SignedStateFileReadWriteTest.java +++ b/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/SignedStateFileReadWriteTest.java @@ -17,13 +17,13 @@ package com.swirlds.platform; import static com.swirlds.common.io.utility.FileUtils.throwIfFileExists; -import static com.swirlds.platform.state.signed.SignedStateFileReader.readStateFile; -import static com.swirlds.platform.state.signed.SignedStateFileUtils.CURRENT_ADDRESS_BOOK_FILE_NAME; -import static com.swirlds.platform.state.signed.SignedStateFileUtils.HASH_INFO_FILE_NAME; -import static com.swirlds.platform.state.signed.SignedStateFileUtils.SIGNED_STATE_FILE_NAME; -import static com.swirlds.platform.state.signed.SignedStateFileWriter.writeHashInfoFile; -import static com.swirlds.platform.state.signed.SignedStateFileWriter.writeSignedStateToDisk; -import static com.swirlds.platform.state.signed.SignedStateFileWriter.writeStateFile; +import static com.swirlds.platform.state.snapshot.SignedStateFileReader.readStateFile; +import static com.swirlds.platform.state.snapshot.SignedStateFileUtils.CURRENT_ADDRESS_BOOK_FILE_NAME; +import static com.swirlds.platform.state.snapshot.SignedStateFileUtils.HASH_INFO_FILE_NAME; +import static com.swirlds.platform.state.snapshot.SignedStateFileUtils.SIGNED_STATE_FILE_NAME; +import static com.swirlds.platform.state.snapshot.SignedStateFileWriter.writeHashInfoFile; +import static com.swirlds.platform.state.snapshot.SignedStateFileWriter.writeSignedStateToDisk; +import static com.swirlds.platform.state.snapshot.SignedStateFileWriter.writeStateFile; import static java.nio.file.Files.exists; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; @@ -45,10 +45,10 @@ import com.swirlds.platform.config.StateConfig; import com.swirlds.platform.state.RandomSignedStateGenerator; import com.swirlds.platform.state.State; -import com.swirlds.platform.state.signed.DeserializedSignedState; import com.swirlds.platform.state.signed.SignedState; -import com.swirlds.platform.state.signed.SignedStateFileUtils; -import com.swirlds.platform.state.signed.StateToDiskReason; +import com.swirlds.platform.state.snapshot.DeserializedSignedState; +import com.swirlds.platform.state.snapshot.SignedStateFileUtils; +import com.swirlds.platform.state.snapshot.StateToDiskReason; import java.io.BufferedReader; import java.io.FileReader; import java.io.IOException; diff --git a/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/SignedStateFileManagerTests.java b/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/StateFileManagerTests.java similarity index 87% rename from platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/SignedStateFileManagerTests.java rename to platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/StateFileManagerTests.java index c09422c203c3..dcb40151f814 100644 --- a/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/SignedStateFileManagerTests.java +++ b/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/StateFileManagerTests.java @@ -19,10 +19,10 @@ import static com.swirlds.common.test.fixtures.AssertionUtils.assertEventuallyEquals; import static com.swirlds.common.test.fixtures.RandomUtils.getRandomPrintSeed; import static com.swirlds.common.threading.manager.AdHocThreadManager.getStaticThreadManager; -import static com.swirlds.platform.state.signed.SignedStateFileReader.readStateFile; -import static com.swirlds.platform.state.signed.StateToDiskReason.FATAL_ERROR; -import static com.swirlds.platform.state.signed.StateToDiskReason.ISS; -import static com.swirlds.platform.state.signed.StateToDiskReason.PERIODIC_SNAPSHOT; +import static com.swirlds.platform.state.snapshot.SignedStateFileReader.readStateFile; +import static com.swirlds.platform.state.snapshot.StateToDiskReason.FATAL_ERROR; +import static com.swirlds.platform.state.snapshot.StateToDiskReason.ISS; +import static com.swirlds.platform.state.snapshot.StateToDiskReason.PERIODIC_SNAPSHOT; import static java.nio.file.Files.exists; import static java.util.concurrent.TimeUnit.MILLISECONDS; import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; @@ -32,9 +32,7 @@ import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; -import com.swirlds.base.test.fixtures.time.FakeTime; import com.swirlds.common.config.StateCommonConfig; import com.swirlds.common.config.StateCommonConfig_; import com.swirlds.common.constructable.ConstructableRegistry; @@ -42,31 +40,28 @@ import com.swirlds.common.context.PlatformContext; import com.swirlds.common.io.utility.LegacyTemporaryFileBuilder; import com.swirlds.common.merkle.crypto.MerkleCryptoFactory; -import com.swirlds.common.metrics.RunningAverageMetric; import com.swirlds.common.platform.NodeId; import com.swirlds.common.test.fixtures.platform.TestPlatformContextBuilder; import com.swirlds.common.threading.framework.config.ThreadConfiguration; import com.swirlds.common.utility.CompareTo; import com.swirlds.config.extensions.test.fixtures.TestConfigBuilder; -import com.swirlds.metrics.api.Counter; import com.swirlds.platform.components.DefaultSavedStateController; import com.swirlds.platform.components.SavedStateController; -import com.swirlds.platform.config.StateConfig; import com.swirlds.platform.config.StateConfig_; import com.swirlds.platform.internal.ConsensusRound; import com.swirlds.platform.state.RandomSignedStateGenerator; -import com.swirlds.platform.state.signed.DeserializedSignedState; import com.swirlds.platform.state.signed.ReservedSignedState; -import com.swirlds.platform.state.signed.SavedStateInfo; -import com.swirlds.platform.state.signed.SavedStateMetadata; import com.swirlds.platform.state.signed.SignedState; -import com.swirlds.platform.state.signed.SignedStateFileManager; -import com.swirlds.platform.state.signed.SignedStateFilePath; -import com.swirlds.platform.state.signed.SignedStateFileReader; -import com.swirlds.platform.state.signed.SignedStateFileUtils; -import com.swirlds.platform.state.signed.SignedStateMetrics; -import com.swirlds.platform.state.signed.StateDumpRequest; -import com.swirlds.platform.state.signed.StateSavingResult; +import com.swirlds.platform.state.snapshot.DefaultStateSnapshotManager; +import com.swirlds.platform.state.snapshot.DeserializedSignedState; +import com.swirlds.platform.state.snapshot.SavedStateInfo; +import com.swirlds.platform.state.snapshot.SavedStateMetadata; +import com.swirlds.platform.state.snapshot.SignedStateFilePath; +import com.swirlds.platform.state.snapshot.SignedStateFileReader; +import com.swirlds.platform.state.snapshot.SignedStateFileUtils; +import com.swirlds.platform.state.snapshot.StateDumpRequest; +import com.swirlds.platform.state.snapshot.StateSavingResult; +import com.swirlds.platform.state.snapshot.StateSnapshotManager; import com.swirlds.platform.test.fixtures.state.DummySwirldState; import com.swirlds.platform.wiring.components.StateAndRound; import java.io.IOException; @@ -86,8 +81,7 @@ import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.ValueSource; -@DisplayName("SignedStateFileManager Tests") -class SignedStateFileManagerTests { +class StateFileManagerTests { private static final NodeId SELF_ID = new NodeId(1234); private static final String MAIN_CLASS_NAME = "com.swirlds.foobar"; @@ -121,14 +115,6 @@ void beforeEach() throws IOException { new SignedStateFilePath(context.getConfiguration().getConfigData(StateCommonConfig.class)); } - private SignedStateMetrics buildMockMetrics() { - final SignedStateMetrics metrics = mock(SignedStateMetrics.class); - when(metrics.getWriteStateToDiskTimeMetric()).thenReturn(mock(RunningAverageMetric.class)); - when(metrics.getStateToDiskTimeMetric()).thenReturn(mock(RunningAverageMetric.class)); - when(metrics.getTotalUnsignedDiskStatesMetric()).thenReturn(mock(Counter.class)); - return metrics; - } - /** * Make sure the signed state was properly saved. */ @@ -191,8 +177,8 @@ void standardOperationTest(final boolean successExpected) throws IOException { Files.createFile(savedDir); } - final SignedStateFileManager manager = new SignedStateFileManager( - context, buildMockMetrics(), new FakeTime(), MAIN_CLASS_NAME, SELF_ID, SWIRLD_NAME); + final StateSnapshotManager manager = + new DefaultStateSnapshotManager(context, MAIN_CLASS_NAME, SELF_ID, SWIRLD_NAME); final StateSavingResult stateSavingResult = manager.saveStateTask(signedState.reserve("test")); @@ -210,8 +196,8 @@ void saveFatalSignedState() throws InterruptedException, IOException { final SignedState signedState = new RandomSignedStateGenerator().build(); ((DummySwirldState) signedState.getSwirldState()).enableBlockingSerialization(); - final SignedStateFileManager manager = new SignedStateFileManager( - context, buildMockMetrics(), new FakeTime(), MAIN_CLASS_NAME, SELF_ID, SWIRLD_NAME); + final StateSnapshotManager manager = + new DefaultStateSnapshotManager(context, MAIN_CLASS_NAME, SELF_ID, SWIRLD_NAME); signedState.markAsStateToSave(FATAL_ERROR); final Thread thread = new ThreadConfiguration(getStaticThreadManager()) @@ -236,8 +222,8 @@ void saveFatalSignedState() throws InterruptedException, IOException { void saveISSignedState() throws IOException { final SignedState signedState = new RandomSignedStateGenerator().build(); - final SignedStateFileManager manager = new SignedStateFileManager( - context, buildMockMetrics(), new FakeTime(), MAIN_CLASS_NAME, SELF_ID, SWIRLD_NAME); + final StateSnapshotManager manager = + new DefaultStateSnapshotManager(context, MAIN_CLASS_NAME, SELF_ID, SWIRLD_NAME); signedState.markAsStateToSave(ISS); manager.dumpStateTask(StateDumpRequest.create(signedState.reserve("test"))); @@ -273,10 +259,9 @@ void sequenceOfStatesTest(final boolean startAtGenesis) throws IOException { final int averageTimeBetweenStates = 10; final double standardDeviationTimeBetweenStates = 0.5; - final SignedStateFileManager manager = new SignedStateFileManager( - context, buildMockMetrics(), new FakeTime(), MAIN_CLASS_NAME, SELF_ID, SWIRLD_NAME); - final SavedStateController controller = - new DefaultSavedStateController(context.getConfiguration().getConfigData(StateConfig.class)); + final StateSnapshotManager manager = + new DefaultStateSnapshotManager(context, MAIN_CLASS_NAME, SELF_ID, SWIRLD_NAME); + final SavedStateController controller = new DefaultSavedStateController(context); Instant timestamp; final long firstRound; @@ -392,8 +377,8 @@ void stateDeletionTest() throws IOException { final int count = 10; - final SignedStateFileManager manager = new SignedStateFileManager( - context, buildMockMetrics(), new FakeTime(), MAIN_CLASS_NAME, SELF_ID, SWIRLD_NAME); + final StateSnapshotManager manager = + new DefaultStateSnapshotManager(context, MAIN_CLASS_NAME, SELF_ID, SWIRLD_NAME); final Path statesDirectory = signedStateFilePath.getSignedStatesDirectoryForSwirld(MAIN_CLASS_NAME, SELF_ID, SWIRLD_NAME); diff --git a/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/SyncManagerTest.java b/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/SyncManagerTest.java index e3b87d03fcb1..69e0508ff8fd 100644 --- a/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/SyncManagerTest.java +++ b/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/SyncManagerTest.java @@ -32,10 +32,10 @@ import com.swirlds.config.extensions.test.fixtures.TestConfigBuilder; import com.swirlds.platform.eventhandling.EventConfig; import com.swirlds.platform.eventhandling.EventConfig_; -import com.swirlds.platform.eventhandling.TransactionPool; import com.swirlds.platform.gossip.FallenBehindManagerImpl; import com.swirlds.platform.gossip.sync.SyncManagerImpl; import com.swirlds.platform.network.RandomGraph; +import com.swirlds.platform.pool.TransactionPoolNexus; import com.swirlds.platform.system.address.AddressBook; import com.swirlds.platform.system.status.StatusActionSubmitter; import java.util.List; @@ -56,7 +56,7 @@ private static class SyncManagerTestData { public DummyHashgraph hashgraph; public AddressBook addressBook; public NodeId selfId; - public TransactionPool transactionPool; + public TransactionPoolNexus transactionPoolNexus; public RandomGraph connectionGraph; public SyncManagerImpl syncManager; public Configuration configuration; @@ -67,7 +67,7 @@ public SyncManagerTestData() { final PlatformContext platformContext = TestPlatformContextBuilder.create().build(); - transactionPool = spy(new TransactionPool(platformContext)); + transactionPoolNexus = spy(new TransactionPoolNexus(platformContext)); this.addressBook = hashgraph.getAddressBook(); this.selfId = addressBook.getNodeId(0); diff --git a/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/crypto/CryptoArgsProvider.java b/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/crypto/CryptoArgsProvider.java index aa2fe11141d1..670adafc3064 100644 --- a/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/crypto/CryptoArgsProvider.java +++ b/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/crypto/CryptoArgsProvider.java @@ -17,10 +17,11 @@ package com.swirlds.platform.crypto; import com.swirlds.common.platform.NodeId; +import com.swirlds.common.test.fixtures.Randotron; import com.swirlds.common.test.fixtures.io.ResourceLoader; import com.swirlds.platform.system.address.AddressBook; -import com.swirlds.platform.test.fixtures.addressbook.RandomAddressBookGenerator; -import com.swirlds.platform.test.fixtures.addressbook.RandomAddressBookGenerator.WeightDistributionStrategy; +import com.swirlds.platform.test.fixtures.addressbook.RandomAddressBookBuilder; +import com.swirlds.platform.test.fixtures.addressbook.RandomAddressBookBuilder.WeightDistributionStrategy; import edu.umd.cs.findbugs.annotations.NonNull; import java.net.URISyntaxException; import java.security.KeyStoreException; @@ -58,9 +59,9 @@ static Stream basicTestArgs() throws Exception { } public static AddressBook createAddressBook(final int size) { - final AddressBook addresses = new RandomAddressBookGenerator() - .setSize(size) - .setWeightDistributionStrategy(WeightDistributionStrategy.BALANCED) + final AddressBook addresses = RandomAddressBookBuilder.create(Randotron.create()) + .withSize(size) + .withWeightDistributionStrategy(WeightDistributionStrategy.BALANCED) .build(); for (int i = 0; i < addresses.getSize(); i++) { diff --git a/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/crypto/KeysAndCertsTest.java b/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/crypto/KeysAndCertsTest.java index 66728fa20301..4f81dcfafe87 100644 --- a/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/crypto/KeysAndCertsTest.java +++ b/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/crypto/KeysAndCertsTest.java @@ -19,6 +19,7 @@ import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertTrue; +import com.hedera.pbj.runtime.io.buffer.Bytes; import com.swirlds.common.crypto.KeyType; import com.swirlds.common.crypto.Signature; import com.swirlds.common.platform.NodeId; @@ -33,18 +34,19 @@ import org.junit.jupiter.params.provider.MethodSource; class KeysAndCertsTest { - private static final byte[] DATA = {1, 2, 3}; + private static final byte[] DATA_ARRAY = {1, 2, 3}; + private static final Bytes DATA_BYTES = Bytes.wrap(DATA_ARRAY); private static final PublicKey WRONG_KEY = PreGeneratedPublicKeys.getPublicKey(KeyType.RSA, 0).getPublicKey(); private void testSignVerify(final PlatformSigner signer, final PublicKey publicKey) { - final Signature signature = signer.sign(DATA); + final Signature signature = signer.sign(DATA_ARRAY); assertTrue( - CryptoStatic.verifySignature(DATA, signature.getSignatureBytes(), publicKey), + CryptoStatic.verifySignature(DATA_BYTES, signature.getBytes(), publicKey), "verify should be true when using the correct public key"); assertFalse( - CryptoStatic.verifySignature(DATA, signature.getSignatureBytes(), WRONG_KEY), + CryptoStatic.verifySignature(DATA_BYTES, signature.getBytes(), WRONG_KEY), "verify should be false when using the incorrect public key"); } diff --git a/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/event/DetGenerateUtils.java b/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/event/DetGenerateUtils.java index d8194897ccb1..7bf5a8ffdc82 100644 --- a/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/event/DetGenerateUtils.java +++ b/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/event/DetGenerateUtils.java @@ -26,7 +26,6 @@ import com.swirlds.common.platform.NodeId; import com.swirlds.platform.system.BasicSoftwareVersion; import com.swirlds.platform.system.events.BaseEventHashedData; -import com.swirlds.platform.system.events.BaseEventUnhashedData; import com.swirlds.platform.system.events.ConsensusData; import com.swirlds.platform.system.events.EventConstants; import com.swirlds.platform.system.events.EventDescriptor; @@ -75,10 +74,6 @@ public static BaseEventHashedData generateBaseEventHashedData(final Random rando .toArray(new ConsensusTransactionImpl[0])); // transactions } - public static BaseEventUnhashedData generateBaseEventUnhashedData(final Random random) { - return new BaseEventUnhashedData(generateRandomByteArray(random, DEFAULT_SIGNATURE_SIZE)); - } - public static ConsensusData generateConsensusEventData(final Random random) { final ConsensusData data = new ConsensusData(); diff --git a/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/event/DetailedConsensusEventTest.java b/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/event/DetailedConsensusEventTest.java index d541e1830760..c18b693e9bc0 100644 --- a/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/event/DetailedConsensusEventTest.java +++ b/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/event/DetailedConsensusEventTest.java @@ -16,22 +16,24 @@ package com.swirlds.platform.event; +import static com.swirlds.platform.event.DetGenerateUtils.generateRandomByteArray; import static org.junit.jupiter.api.Assertions.assertEquals; +import com.hedera.pbj.runtime.io.buffer.Bytes; import com.swirlds.common.constructable.ConstructableRegistry; import com.swirlds.common.constructable.ConstructableRegistryException; import com.swirlds.common.crypto.CryptographyHolder; import com.swirlds.common.crypto.Hash; +import com.swirlds.common.crypto.SignatureType; +import com.swirlds.common.test.fixtures.Randotron; import com.swirlds.common.test.fixtures.io.InputOutputStream; import com.swirlds.platform.internal.EventImpl; import com.swirlds.platform.system.BasicSoftwareVersion; import com.swirlds.platform.system.StaticSoftwareVersion; import com.swirlds.platform.system.events.BaseEventHashedData; -import com.swirlds.platform.system.events.BaseEventUnhashedData; import com.swirlds.platform.system.events.ConsensusData; import com.swirlds.platform.system.events.DetailedConsensusEvent; import java.io.IOException; -import java.util.Random; import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; @@ -64,10 +66,7 @@ public void serializeAndDeserializeConsensusEvent() throws IOException { @Test public void EventImplGetHashTest() { DetailedConsensusEvent consensusEvent = generateConsensusEvent(); - EventImpl event = new EventImpl( - consensusEvent.getBaseEventHashedData(), - consensusEvent.getBaseEventUnhashedData(), - consensusEvent.getConsensusData()); + EventImpl event = new EventImpl(consensusEvent); CryptographyHolder.get().digestSync(consensusEvent); Hash expectedHash = consensusEvent.getHash(); CryptographyHolder.get().digestSync(event); @@ -75,10 +74,12 @@ public void EventImplGetHashTest() { } private DetailedConsensusEvent generateConsensusEvent() { - Random random = new Random(68651684861L); + Randotron random = Randotron.create(68651684861L); BaseEventHashedData hashedData = DetGenerateUtils.generateBaseEventHashedData(random); - BaseEventUnhashedData unhashedData = DetGenerateUtils.generateBaseEventUnhashedData(random); ConsensusData consensusData = DetGenerateUtils.generateConsensusEventData(random); - return new DetailedConsensusEvent(hashedData, unhashedData, consensusData); + return new DetailedConsensusEvent( + new GossipEvent( + hashedData, Bytes.wrap(generateRandomByteArray(random, SignatureType.RSA.signatureLength()))), + consensusData); } } diff --git a/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/event/EventDeduplicatorTests.java b/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/event/EventDeduplicatorTests.java index dd5c9d09c881..716e3c5a0aba 100644 --- a/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/event/EventDeduplicatorTests.java +++ b/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/event/EventDeduplicatorTests.java @@ -195,12 +195,12 @@ void standardOperation(final boolean useBirthRoundForAncientThreshold) { emittedEvents); } else { // submit a duplicate event with a different signature 25% of the time - final GossipEvent duplicateEvent = submittedEvents.get(random.nextInt(submittedEvents.size())); - - // randomize the signature - // this modifies the event in place (since we don't have a copy constructor), but that doesn't matter - ByteBuffer.wrap(duplicateEvent.getUnhashedData().getSignature()) - .put(randomSignatureBytes(random).toByteArray()); + final GossipEvent duplicateEvent = new GossipEvent( + submittedEvents + .get(random.nextInt(submittedEvents.size())) + .getHashedData(), + randomSignatureBytes(random) // randomize the signature + ); if (ancientMode == AncientMode.BIRTH_ROUND_THRESHOLD) { if (duplicateEvent.getDescriptor().getBirthRound() < minimumRoundNonAncient) { diff --git a/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/event/runninghash/RunningEventHasherTests.java b/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/event/runninghash/RunningEventHasherTests.java deleted file mode 100644 index f6a472044cad..000000000000 --- a/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/event/runninghash/RunningEventHasherTests.java +++ /dev/null @@ -1,460 +0,0 @@ -/* - * Copyright (C) 2024 Hedera Hashgraph, LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.swirlds.platform.event.runninghash; - -import static com.swirlds.common.test.fixtures.RandomUtils.getRandomPrintSeed; -import static com.swirlds.common.test.fixtures.RandomUtils.randomHash; -import static com.swirlds.common.test.fixtures.RandomUtils.randomInstant; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertNotEquals; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; - -import com.swirlds.base.test.fixtures.time.FakeTime; -import com.swirlds.common.crypto.Hash; -import com.swirlds.common.stream.RunningEventHashOverride; -import com.swirlds.platform.consensus.ConsensusSnapshot; -import com.swirlds.platform.consensus.EventWindow; -import com.swirlds.platform.event.GossipEvent; -import com.swirlds.platform.gossip.shadowgraph.Generations; -import com.swirlds.platform.internal.ConsensusRound; -import com.swirlds.platform.internal.EventImpl; -import com.swirlds.platform.system.address.AddressBook; -import com.swirlds.platform.test.fixtures.event.TestingEventBuilder; -import edu.umd.cs.findbugs.annotations.NonNull; -import java.time.Duration; -import java.time.Instant; -import java.util.ArrayList; -import java.util.List; -import java.util.Random; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.ValueSource; - -class RunningEventHasherTests { - - /** - * Build a consensus round and fill it with the fields needed by the hasher. - * - * @param roundNumber the round number - * @param events the events in the round - * @return the consensus round - */ - @NonNull - private static ConsensusRound buildRound(final long roundNumber, @NonNull final List events) { - final ConsensusSnapshot snapshot = mock(ConsensusSnapshot.class); - when(snapshot.round()).thenReturn(roundNumber); - - return new ConsensusRound( - mock(AddressBook.class), - events, - mock(EventImpl.class), - mock(Generations.class), - mock(EventWindow.class), - snapshot); - } - - @Test - void sequenceOfRegularRoundsTest() throws InterruptedException { - final Random random = getRandomPrintSeed(); - final FakeTime time = new FakeTime(randomInstant(random), Duration.ZERO); - final int eventCount = random.nextInt(1, 10); - - final List rounds = new ArrayList<>(); - - final long firstRound = random.nextLong(1, 100); - for (int roundOffset = 0; roundOffset < 10; roundOffset++) { - final List events = new ArrayList<>(); - for (int i = 0; i < eventCount; i++) { - final GossipEvent event = new TestingEventBuilder(random).build(); - final EventImpl eventImpl = new EventImpl(event.getHashedData(), event.getUnhashedData()); - eventImpl.setConsensusTimestamp(time.now()); - events.add(eventImpl); - - time.tick(Duration.ofMillis(random.nextInt(1, 100))); - } - final ConsensusRound round = buildRound(firstRound + roundOffset, events); - rounds.add(round); - } - - final Hash initialHash = randomHash(random); - - final RunningEventHasher hasherA = new DefaultRunningEventHasher(); - final RunningEventHasher hasherB = new DefaultRunningEventHasher(); - - hasherA.overrideRunningEventHash(new RunningEventHashOverride(null, initialHash, false)); - hasherB.overrideRunningEventHash(new RunningEventHashOverride(null, initialHash, false)); - - for (final ConsensusRound round : rounds) { - hasherA.computeRunningEventHash(round); - final Hash hashA = round.getRunningEventHash(); - - // Recreate the round to reset the running hash. - final ConsensusRound roundB = buildRound(round.getRoundNum(), round.getConsensusEvents()); - hasherB.computeRunningEventHash(roundB); - final Hash hashB = roundB.getRunningEventHash(); - - assertEquals(hashA, hashB); - } - } - - @ParameterizedTest - @ValueSource(booleans = {true, false}) - void differentInitialHashTest(final boolean emptyRound) throws InterruptedException { - final Random random = getRandomPrintSeed(); - final FakeTime time = new FakeTime(randomInstant(random), Duration.ZERO); - final int eventCount = emptyRound ? 0 : random.nextInt(1, 10); - - final long roundNumber = random.nextLong(1, 100); - - final List events = new ArrayList<>(); - for (int i = 0; i < eventCount; i++) { - final GossipEvent event = new TestingEventBuilder(random).build(); - final EventImpl eventImpl = new EventImpl(event.getHashedData(), event.getUnhashedData()); - eventImpl.setConsensusTimestamp(time.now()); - events.add(eventImpl); - - time.tick(Duration.ofMillis(random.nextInt(1, 100))); - } - final ConsensusRound round = buildRound(roundNumber, events); - - final Hash initialHashA = randomHash(random); - final Hash initialHashB = randomHash(random); - - final RunningEventHasher hasherA = new DefaultRunningEventHasher(); - final RunningEventHasher hasherB = new DefaultRunningEventHasher(); - - hasherA.overrideRunningEventHash(new RunningEventHashOverride(null, initialHashA, false)); - hasherB.overrideRunningEventHash(new RunningEventHashOverride(null, initialHashB, false)); - - hasherA.computeRunningEventHash(round); - final Hash hashA = round.getRunningEventHash(); - - // Recreate the round to reset the running hash. - final ConsensusRound roundB = buildRound(round.getRoundNum(), round.getConsensusEvents()); - hasherB.computeRunningEventHash(roundB); - final Hash hashB = roundB.getRunningEventHash(); - - assertNotEquals(hashA, hashB); - } - - @ParameterizedTest - @ValueSource(booleans = {true, false}) - void differentRoundNumberTest(final boolean emptyRound) throws InterruptedException { - final Random random = getRandomPrintSeed(); - final FakeTime time = new FakeTime(randomInstant(random), Duration.ZERO); - final int eventCount = emptyRound ? 0 : random.nextInt(1, 10); - - final long roundNumberA = random.nextLong(2, 100); - final long roundNumberB = roundNumberA + random.nextInt(1, 100); - - final List events = new ArrayList<>(); - for (int i = 0; i < random.nextInt(1, 10); i++) { - final GossipEvent event = new TestingEventBuilder(random).build(); - final EventImpl eventImpl = new EventImpl(event.getHashedData(), event.getUnhashedData()); - eventImpl.setConsensusTimestamp(time.now()); - events.add(eventImpl); - - time.tick(Duration.ofMillis(random.nextInt(1, 100))); - } - - final ConsensusRound roundA = buildRound(roundNumberA, events); - final ConsensusRound roundB = buildRound(roundNumberB, events); - - final Hash initialHash = randomHash(random); - - final RunningEventHasher hasherA = new DefaultRunningEventHasher(); - final RunningEventHasher hasherB = new DefaultRunningEventHasher(); - - hasherA.overrideRunningEventHash(new RunningEventHashOverride(null, initialHash, false)); - hasherB.overrideRunningEventHash(new RunningEventHashOverride(null, initialHash, false)); - - hasherA.computeRunningEventHash(roundA); - final Hash hashA = roundA.getRunningEventHash(); - - hasherB.computeRunningEventHash(roundB); - final Hash hashB = roundB.getRunningEventHash(); - - assertNotEquals(hashA, hashB); - } - - @Test - void differentEventOrderTest() throws InterruptedException { - final Random random = getRandomPrintSeed(); - final FakeTime time = new FakeTime(randomInstant(random), Duration.ZERO); - final int eventCount = random.nextInt(2, 10); - - final long roundNumber = random.nextLong(1, 100); - - final List events = new ArrayList<>(); - for (int i = 0; i < eventCount; i++) { - final GossipEvent event = new TestingEventBuilder(random).build(); - final EventImpl eventImpl = new EventImpl(event.getHashedData(), event.getUnhashedData()); - eventImpl.setConsensusTimestamp(time.now()); - events.add(eventImpl); - - time.tick(Duration.ofMillis(random.nextInt(1, 100))); - } - // Swap the order of two events. Event list is guaranteed to have at least 2 events. - final List eventsB = new ArrayList<>(events); - eventsB.set(0, events.get(1)); - eventsB.set(1, events.get(0)); - - final ConsensusRound roundA = buildRound(roundNumber, events); - final ConsensusRound roundB = buildRound(roundNumber, eventsB); - - final Hash initialHash = randomHash(random); - - final RunningEventHasher hasherA = new DefaultRunningEventHasher(); - final RunningEventHasher hasherB = new DefaultRunningEventHasher(); - - hasherA.overrideRunningEventHash(new RunningEventHashOverride(null, initialHash, false)); - hasherB.overrideRunningEventHash(new RunningEventHashOverride(null, initialHash, false)); - - hasherA.computeRunningEventHash(roundA); - final Hash hashA = roundA.getRunningEventHash(); - - hasherB.computeRunningEventHash(roundB); - final Hash hashB = roundB.getRunningEventHash(); - - assertNotEquals(hashA, hashB); - } - - @Test - void differentEventSeconds() throws InterruptedException { - final Random random = getRandomPrintSeed(); - final FakeTime time = new FakeTime(randomInstant(random), Duration.ZERO); - final int eventCount = random.nextInt(1, 10); - - final long roundNumber = random.nextLong(1, 100); - - final List events = new ArrayList<>(); - for (int i = 0; i < eventCount; i++) { - final GossipEvent event = new TestingEventBuilder(random).build(); - final EventImpl eventImpl = new EventImpl(event.getHashedData(), event.getUnhashedData()); - eventImpl.setConsensusTimestamp(time.now()); - events.add(eventImpl); - - time.tick(Duration.ofMillis(random.nextInt(1, 100))); - } - - final List eventsB = new ArrayList<>(events); - final EventImpl originalEvent = events.get(0); - final EventImpl modifiedEvent = - new EventImpl(originalEvent.getBaseEvent().getHashedData(), originalEvent.getUnhashedData()); - final Instant modifiedTimestamp = - originalEvent.getConsensusData().getConsensusTimestamp().plusSeconds(1); - modifiedEvent.setConsensusTimestamp(modifiedTimestamp); - eventsB.set(0, modifiedEvent); - - final ConsensusRound roundA = buildRound(roundNumber, events); - final ConsensusRound roundB = buildRound(roundNumber, eventsB); - - final Hash initialHash = randomHash(random); - - final RunningEventHasher hasherA = new DefaultRunningEventHasher(); - final RunningEventHasher hasherB = new DefaultRunningEventHasher(); - - hasherA.overrideRunningEventHash(new RunningEventHashOverride(null, initialHash, false)); - hasherB.overrideRunningEventHash(new RunningEventHashOverride(null, initialHash, false)); - - hasherA.computeRunningEventHash(roundA); - final Hash hashA = roundA.getRunningEventHash(); - - hasherB.computeRunningEventHash(roundB); - final Hash hashB = roundB.getRunningEventHash(); - - assertNotEquals(hashA, hashB); - } - - @Test - void differentEventNanoseconds() throws InterruptedException { - final Random random = getRandomPrintSeed(); - final FakeTime time = new FakeTime(randomInstant(random), Duration.ZERO); - final int eventCount = random.nextInt(1, 10); - - final long roundNumber = random.nextLong(1, 100); - - final List events = new ArrayList<>(); - for (int i = 0; i < eventCount; i++) { - final GossipEvent event = new TestingEventBuilder(random).build(); - final EventImpl eventImpl = new EventImpl(event.getHashedData(), event.getUnhashedData()); - eventImpl.setConsensusTimestamp(time.now()); - events.add(eventImpl); - - time.tick(Duration.ofMillis(random.nextInt(1, 100))); - } - - final List eventsB = new ArrayList<>(events); - final EventImpl originalEvent = events.get(0); - final EventImpl modifiedEvent = - new EventImpl(originalEvent.getBaseEvent().getHashedData(), originalEvent.getUnhashedData()); - final Instant modifiedTimestamp = - originalEvent.getConsensusData().getConsensusTimestamp().plusNanos(1); - modifiedEvent.setConsensusTimestamp(modifiedTimestamp); - eventsB.set(0, modifiedEvent); - - final ConsensusRound roundA = buildRound(roundNumber, events); - final ConsensusRound roundB = buildRound(roundNumber, eventsB); - - final Hash initialHash = randomHash(random); - - final RunningEventHasher hasherA = new DefaultRunningEventHasher(); - final RunningEventHasher hasherB = new DefaultRunningEventHasher(); - - hasherA.overrideRunningEventHash(new RunningEventHashOverride(null, initialHash, false)); - hasherB.overrideRunningEventHash(new RunningEventHashOverride(null, initialHash, false)); - - hasherA.computeRunningEventHash(roundA); - final Hash hashA = roundA.getRunningEventHash(); - - hasherB.computeRunningEventHash(roundB); - final Hash hashB = roundB.getRunningEventHash(); - - assertNotEquals(hashA, hashB); - } - - @Test - void differentEventHashTest() throws InterruptedException { - final Random random = getRandomPrintSeed(); - final FakeTime time = new FakeTime(randomInstant(random), Duration.ZERO); - final int eventCount = random.nextInt(1, 10); - - final long roundNumber = random.nextLong(1, 100); - - final List events = new ArrayList<>(); - for (int i = 0; i < eventCount; i++) { - final GossipEvent event = new TestingEventBuilder(random).build(); - final EventImpl eventImpl = new EventImpl(event.getHashedData(), event.getUnhashedData()); - eventImpl.setConsensusTimestamp(time.now()); - events.add(eventImpl); - - time.tick(Duration.ofMillis(random.nextInt(1, 100))); - } - - final List eventsB = new ArrayList<>(events); - final EventImpl originalEvent = events.get(0); - final GossipEvent newGossipEvent = new TestingEventBuilder(random).build(); - final EventImpl modifiedEvent = new EventImpl(newGossipEvent.getHashedData(), newGossipEvent.getUnhashedData()); - modifiedEvent.setConsensusTimestamp(originalEvent.getConsensusData().getConsensusTimestamp()); - eventsB.set(0, modifiedEvent); - - final ConsensusRound roundA = buildRound(roundNumber, events); - final ConsensusRound roundB = buildRound(roundNumber, eventsB); - - final Hash initialHash = randomHash(random); - - final RunningEventHasher hasherA = new DefaultRunningEventHasher(); - final RunningEventHasher hasherB = new DefaultRunningEventHasher(); - - hasherA.overrideRunningEventHash(new RunningEventHashOverride(null, initialHash, false)); - hasherB.overrideRunningEventHash(new RunningEventHashOverride(null, initialHash, false)); - - hasherA.computeRunningEventHash(roundA); - final Hash hashA = roundA.getRunningEventHash(); - - hasherB.computeRunningEventHash(roundB); - final Hash hashB = roundB.getRunningEventHash(); - - assertNotEquals(hashA, hashB); - } - - @Test - void missingEventTest() throws InterruptedException { - final Random random = getRandomPrintSeed(); - final FakeTime time = new FakeTime(randomInstant(random), Duration.ZERO); - final int eventCount = random.nextInt(1, 10); - - final long roundNumber = random.nextLong(1, 100); - - final List events = new ArrayList<>(); - for (int i = 0; i < eventCount; i++) { - final GossipEvent event = new TestingEventBuilder(random).build(); - final EventImpl eventImpl = new EventImpl(event.getHashedData(), event.getUnhashedData()); - eventImpl.setConsensusTimestamp(time.now()); - events.add(eventImpl); - - time.tick(Duration.ofMillis(random.nextInt(1, 100))); - } - - final List eventsB = new ArrayList<>(events); - eventsB.removeLast(); - - final ConsensusRound roundA = buildRound(roundNumber, events); - final ConsensusRound roundB = buildRound(roundNumber, eventsB); - - final Hash initialHash = randomHash(random); - - final RunningEventHasher hasherA = new DefaultRunningEventHasher(); - final RunningEventHasher hasherB = new DefaultRunningEventHasher(); - - hasherA.overrideRunningEventHash(new RunningEventHashOverride(null, initialHash, false)); - hasherB.overrideRunningEventHash(new RunningEventHashOverride(null, initialHash, false)); - - hasherA.computeRunningEventHash(roundA); - final Hash hashA = roundA.getRunningEventHash(); - - hasherB.computeRunningEventHash(roundB); - final Hash hashB = roundB.getRunningEventHash(); - - assertNotEquals(hashA, hashB); - } - - @Test - void extraEventTest() throws InterruptedException { - final Random random = getRandomPrintSeed(); - final FakeTime time = new FakeTime(randomInstant(random), Duration.ZERO); - final int eventCount = random.nextInt(1, 10); - - final long roundNumber = random.nextLong(1, 100); - - final List events = new ArrayList<>(); - for (int i = 0; i < eventCount; i++) { - final GossipEvent event = new TestingEventBuilder(random).build(); - final EventImpl eventImpl = new EventImpl(event.getHashedData(), event.getUnhashedData()); - eventImpl.setConsensusTimestamp(time.now()); - events.add(eventImpl); - - time.tick(Duration.ofMillis(random.nextInt(1, 100))); - } - - final List eventsB = new ArrayList<>(events); - final GossipEvent newGossipEvent = new TestingEventBuilder(random).build(); - final EventImpl extraEvent = new EventImpl(newGossipEvent.getHashedData(), newGossipEvent.getUnhashedData()); - extraEvent.setConsensusTimestamp(time.now()); - eventsB.add(extraEvent); - - final ConsensusRound roundA = buildRound(roundNumber, events); - final ConsensusRound roundB = buildRound(roundNumber, eventsB); - - final Hash initialHash = randomHash(random); - - final RunningEventHasher hasherA = new DefaultRunningEventHasher(); - final RunningEventHasher hasherB = new DefaultRunningEventHasher(); - - hasherA.overrideRunningEventHash(new RunningEventHashOverride(null, initialHash, false)); - hasherB.overrideRunningEventHash(new RunningEventHashOverride(null, initialHash, false)); - - hasherA.computeRunningEventHash(roundA); - final Hash hashA = roundA.getRunningEventHash(); - - hasherB.computeRunningEventHash(roundB); - final Hash hashB = roundB.getRunningEventHash(); - - assertNotEquals(hashA, hashB); - } -} diff --git a/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/event/stale/StaleEventDetectorTests.java b/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/event/stale/StaleEventDetectorTests.java new file mode 100644 index 000000000000..0e761213ab1b --- /dev/null +++ b/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/event/stale/StaleEventDetectorTests.java @@ -0,0 +1,322 @@ +/* + * Copyright (C) 2024 Hedera Hashgraph, LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.swirlds.platform.event.stale; + +import static com.swirlds.platform.event.AncientMode.BIRTH_ROUND_THRESHOLD; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertSame; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.mockito.Mockito.mock; + +import com.swirlds.common.context.PlatformContext; +import com.swirlds.common.platform.NodeId; +import com.swirlds.common.test.fixtures.Randotron; +import com.swirlds.common.test.fixtures.platform.TestPlatformContextBuilder; +import com.swirlds.common.wiring.transformers.RoutableData; +import com.swirlds.config.api.Configuration; +import com.swirlds.config.extensions.test.fixtures.TestConfigBuilder; +import com.swirlds.platform.consensus.ConsensusSnapshot; +import com.swirlds.platform.consensus.EventWindow; +import com.swirlds.platform.event.GossipEvent; +import com.swirlds.platform.eventhandling.EventConfig_; +import com.swirlds.platform.gossip.shadowgraph.Generations; +import com.swirlds.platform.internal.ConsensusRound; +import com.swirlds.platform.internal.EventImpl; +import com.swirlds.platform.system.address.AddressBook; +import com.swirlds.platform.test.fixtures.event.TestingEventBuilder; +import edu.umd.cs.findbugs.annotations.NonNull; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import org.junit.jupiter.api.Test; + +class StaleEventDetectorTests { + + /** + * Extract self events from a stream containing both self events and stale self events. Corresponds to data tagged + * with {@link StaleEventDetectorOutput#SELF_EVENT}. + */ + private List getSelfEvents(@NonNull final List> data) { + final List output = new ArrayList<>(); + for (final RoutableData datum : data) { + if (datum.address() == StaleEventDetectorOutput.SELF_EVENT) { + output.add((GossipEvent) datum.data()); + } + } + return output; + } + + /** + * Validate that the correct stale event was returned as part of the output. + * + * @param data the output data + * @param selfEvent the self event that should have been returned + */ + private void assertSelfEventReturned( + @NonNull final List> data, @NonNull final GossipEvent selfEvent) { + + final List selfEvents = getSelfEvents(data); + assertEquals(1, selfEvents.size()); + assertSame(selfEvent, selfEvents.getFirst()); + } + + /** + * Validate that no self events were returned as part of the output. (Not to be confused with "stale self events" + * events.) Essentially, we don't want to see data tagged with {@link StaleEventDetectorOutput#SELF_EVENT} unless we + * are adding a self event and want to see it pass through. + * + * @param data the output data + */ + private void assertNoSelfEventReturned(@NonNull final List> data) { + final List selfEvents = getSelfEvents(data); + assertEquals(0, selfEvents.size()); + } + + /** + * Extract stale self events from a stream containing both self events and stale self events. Corresponds to data + * tagged with {@link StaleEventDetectorOutput#STALE_SELF_EVENT}. + */ + private List getStaleSelfEvents(@NonNull final List> data) { + final List output = new ArrayList<>(); + for (final RoutableData datum : data) { + if (datum.address() == StaleEventDetectorOutput.STALE_SELF_EVENT) { + output.add((GossipEvent) datum.data()); + } + } + return output; + } + + @Test + void throwIfInitialEventWindowNotSetTest() { + final Randotron randotron = Randotron.create(); + final NodeId selfId = new NodeId(randotron.nextPositiveLong()); + + final Configuration configuration = new TestConfigBuilder() + .withValue(EventConfig_.USE_BIRTH_ROUND_ANCIENT_THRESHOLD, true) + .getOrCreateConfig(); + final PlatformContext platformContext = TestPlatformContextBuilder.create() + .withConfiguration(configuration) + .build(); + final StaleEventDetector detector = new DefaultStaleEventDetector(platformContext, selfId); + + final GossipEvent event = new TestingEventBuilder(randotron).build(); + + assertThrows(IllegalStateException.class, () -> detector.addSelfEvent(event)); + } + + @Test + void eventIsStaleBeforeAddedTest() { + final Randotron randotron = Randotron.create(); + final NodeId selfId = new NodeId(randotron.nextPositiveLong()); + + final Configuration configuration = new TestConfigBuilder() + .withValue(EventConfig_.USE_BIRTH_ROUND_ANCIENT_THRESHOLD, true) + .getOrCreateConfig(); + final PlatformContext platformContext = TestPlatformContextBuilder.create() + .withConfiguration(configuration) + .build(); + final StaleEventDetector detector = new DefaultStaleEventDetector(platformContext, selfId); + + final long ancientThreshold = randotron.nextPositiveLong() + 100; + final long eventBirthRound = ancientThreshold - randotron.nextLong(100); + + final GossipEvent event = new TestingEventBuilder(randotron) + .setCreatorId(selfId) + .setBirthRound(eventBirthRound) + .build(); + + detector.setInitialEventWindow(new EventWindow( + randotron.nextPositiveInt(), ancientThreshold, randotron.nextPositiveLong(), BIRTH_ROUND_THRESHOLD)); + + final List> output = detector.addSelfEvent(event); + + final List gossipEvents = getSelfEvents(output); + final List staleEvents = getStaleSelfEvents(output); + + assertEquals(1, staleEvents.size()); + assertSame(event, staleEvents.getFirst()); + + assertSelfEventReturned(output, event); + } + + /** + * Construct a consensus round. + * + * @param randotron a source of randomness + * @param events events that will reach consensus in this round + * @param ancientThreshold the ancient threshold for this round + * @return a consensus round + */ + @NonNull + private ConsensusRound createConsensusRound( + @NonNull final Randotron randotron, @NonNull final List events, final long ancientThreshold) { + final List eventImpls = new ArrayList<>(); + for (final GossipEvent consensusEvent : events) { + eventImpls.add(new EventImpl(consensusEvent)); + } + + final EventWindow eventWindow = new EventWindow( + randotron.nextPositiveLong(), ancientThreshold, randotron.nextPositiveLong(), BIRTH_ROUND_THRESHOLD); + + return new ConsensusRound( + mock(AddressBook.class), + eventImpls, + mock(EventImpl.class), + mock(Generations.class), + eventWindow, + mock(ConsensusSnapshot.class)); + } + + @Test + void randomEventsTest() { + final Randotron randotron = Randotron.create(); + final NodeId selfId = new NodeId(randotron.nextPositiveLong()); + + final Configuration configuration = new TestConfigBuilder() + .withValue(EventConfig_.USE_BIRTH_ROUND_ANCIENT_THRESHOLD, true) + .getOrCreateConfig(); + final PlatformContext platformContext = TestPlatformContextBuilder.create() + .withConfiguration(configuration) + .build(); + final StaleEventDetector detector = new DefaultStaleEventDetector(platformContext, selfId); + + final Set detectedStaleEvents = new HashSet<>(); + final Set expectedStaleEvents = new HashSet<>(); + final List consensusEvents = new ArrayList<>(); + + long currentAncientThreshold = randotron.nextLong(100, 1_000); + detector.setInitialEventWindow(new EventWindow( + randotron.nextPositiveLong(), + currentAncientThreshold, + randotron.nextPositiveLong(), + BIRTH_ROUND_THRESHOLD)); + + for (int i = 0; i < 10_000; i++) { + final boolean selfEvent = randotron.nextBoolean(0.25); + final NodeId eventCreator = selfEvent ? selfId : new NodeId(randotron.nextPositiveLong()); + + final TestingEventBuilder eventBuilder = new TestingEventBuilder(randotron).setCreatorId(eventCreator); + + final boolean eventIsAncientBeforeAdded = randotron.nextBoolean(0.01); + if (eventIsAncientBeforeAdded) { + eventBuilder.setBirthRound(currentAncientThreshold - randotron.nextLong(1, 100)); + } else { + eventBuilder.setBirthRound(currentAncientThreshold + randotron.nextLong(3)); + } + final GossipEvent event = eventBuilder.build(); + + final boolean willReachConsensus = !eventIsAncientBeforeAdded && randotron.nextBoolean(0.8); + + if (willReachConsensus) { + consensusEvents.add(event); + } + + if (selfEvent && (eventIsAncientBeforeAdded || !willReachConsensus)) { + expectedStaleEvents.add(event); + } + + if (selfEvent) { + final List> output = detector.addSelfEvent(event); + detectedStaleEvents.addAll(getStaleSelfEvents(output)); + assertSelfEventReturned(output, event); + } + + // Once in a while, permit a round to "reach consensus" + if (randotron.nextBoolean(0.01)) { + currentAncientThreshold += randotron.nextLong(3); + + final ConsensusRound consensusRound = + createConsensusRound(randotron, consensusEvents, currentAncientThreshold); + + final List> output = detector.addConsensusRound(consensusRound); + detectedStaleEvents.addAll(getStaleSelfEvents(output)); + assertNoSelfEventReturned(output); + consensusEvents.clear(); + } + } + + // Create a final round with all remaining consensus events. Move ancient threshold far enough forward + // to flush out all events we expect to eventually become stale. + currentAncientThreshold += randotron.nextLong(1_000, 10_000); + final ConsensusRound consensusRound = createConsensusRound(randotron, consensusEvents, currentAncientThreshold); + final List> output = detector.addConsensusRound(consensusRound); + detectedStaleEvents.addAll(getStaleSelfEvents(output)); + assertNoSelfEventReturned(output); + + assertEquals(expectedStaleEvents.size(), detectedStaleEvents.size()); + } + + @Test + void clearTest() { + final Randotron randotron = Randotron.create(); + final NodeId selfId = new NodeId(randotron.nextPositiveLong()); + + final Configuration configuration = new TestConfigBuilder() + .withValue(EventConfig_.USE_BIRTH_ROUND_ANCIENT_THRESHOLD, true) + .getOrCreateConfig(); + final PlatformContext platformContext = TestPlatformContextBuilder.create() + .withConfiguration(configuration) + .build(); + final StaleEventDetector detector = new DefaultStaleEventDetector(platformContext, selfId); + + final long ancientThreshold1 = randotron.nextPositiveInt() + 100; + final long eventBirthRound1 = ancientThreshold1 + randotron.nextPositiveInt(10); + + final GossipEvent event1 = new TestingEventBuilder(randotron) + .setCreatorId(selfId) + .setBirthRound(eventBirthRound1) + .build(); + + detector.setInitialEventWindow(new EventWindow( + randotron.nextPositiveInt(), ancientThreshold1, randotron.nextPositiveLong(), BIRTH_ROUND_THRESHOLD)); + + final List> output1 = detector.addSelfEvent(event1); + assertSelfEventReturned(output1, event1); + assertEquals(0, getStaleSelfEvents(output1).size()); + + detector.clear(); + + // Adding an event again before setting the event window should throw. + assertThrows(IllegalStateException.class, () -> detector.addSelfEvent(event1)); + + // Setting the ancient threshold after the original event should not cause it to come back as stale. + final long ancientThreshold2 = eventBirthRound1 + randotron.nextPositiveInt(); + detector.setInitialEventWindow(new EventWindow( + randotron.nextPositiveInt(), ancientThreshold2, randotron.nextPositiveLong(), BIRTH_ROUND_THRESHOLD)); + + // Verify that we get otherwise normal behavior after the clear. + + final long eventBirthRound2 = ancientThreshold2 + randotron.nextPositiveInt(10); + final GossipEvent event2 = new TestingEventBuilder(randotron) + .setCreatorId(selfId) + .setBirthRound(eventBirthRound2) + .build(); + + final List> output2 = detector.addSelfEvent(event2); + assertSelfEventReturned(output2, event2); + assertEquals(0, getStaleSelfEvents(output2).size()); + + final long ancientThreshold3 = eventBirthRound2 + randotron.nextPositiveInt(10); + final ConsensusRound consensusRound = createConsensusRound(randotron, List.of(), ancientThreshold3); + final List> output3 = detector.addConsensusRound(consensusRound); + assertNoSelfEventReturned(output3); + final List staleEvents = getStaleSelfEvents(output3); + assertEquals(1, staleEvents.size()); + assertSame(event2, staleEvents.getFirst()); + } +} diff --git a/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/event/stale/TransactionResubmitterTests.java b/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/event/stale/TransactionResubmitterTests.java new file mode 100644 index 000000000000..c9550b0a9db3 --- /dev/null +++ b/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/event/stale/TransactionResubmitterTests.java @@ -0,0 +1,97 @@ +/* + * Copyright (C) 2024 Hedera Hashgraph, LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.swirlds.platform.event.stale; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertSame; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import com.swirlds.common.test.fixtures.Randotron; +import com.swirlds.platform.event.GossipEvent; +import com.swirlds.platform.system.transaction.ConsensusTransactionImpl; +import com.swirlds.platform.test.fixtures.event.TestingEventBuilder; +import java.util.ArrayList; +import java.util.List; +import org.junit.jupiter.api.Test; + +class TransactionResubmitterTests { + + @Test + void basicBehaviorTest() { + final Randotron randotron = Randotron.create(); + + final TransactionResubmitter resubmitter = new DefaultTransactionResubmitter(); + + final int transactionCount = randotron.nextInt(1, 100); + final ConsensusTransactionImpl[] transactions = new ConsensusTransactionImpl[transactionCount]; + final List systemTransactions = new ArrayList<>(); + for (int i = 0; i < transactionCount; i++) { + final boolean systemTransaction = randotron.nextBoolean(); + final ConsensusTransactionImpl transaction = mock(ConsensusTransactionImpl.class); + when(transaction.isSystem()).thenReturn(systemTransaction); + if (systemTransaction) { + systemTransactions.add(transaction); + } + transactions[i] = transaction; + } + + final GossipEvent event = + new TestingEventBuilder(randotron).setTransactions(transactions).build(); + + final List transactionsToResubmit = resubmitter.resubmitStaleTransactions(event); + assertEquals(systemTransactions.size(), transactionsToResubmit.size()); + for (int i = 0; i < systemTransactions.size(); i++) { + assertSame(systemTransactions.get(i), transactionsToResubmit.get(i)); + } + } + + @Test + void noSystemTransactionsTest() { + final Randotron randotron = Randotron.create(); + + final TransactionResubmitter resubmitter = new DefaultTransactionResubmitter(); + + final int transactionCount = randotron.nextInt(1, 100); + final ConsensusTransactionImpl[] transactions = new ConsensusTransactionImpl[transactionCount]; + for (int i = 0; i < transactionCount; i++) { + final ConsensusTransactionImpl transaction = mock(ConsensusTransactionImpl.class); + when(transaction.isSystem()).thenReturn(false); + transactions[i] = transaction; + } + + final GossipEvent event = + new TestingEventBuilder(randotron).setTransactions(transactions).build(); + + final List transactionsToResubmit = resubmitter.resubmitStaleTransactions(event); + assertEquals(0, transactionsToResubmit.size()); + } + + @Test + void noTransactionsTest() { + final Randotron randotron = Randotron.create(); + + final TransactionResubmitter resubmitter = new DefaultTransactionResubmitter(); + + final GossipEvent event = new TestingEventBuilder(randotron) + .setTransactions(new ConsensusTransactionImpl[0]) + .build(); + + final List transactionsToResubmit = resubmitter.resubmitStaleTransactions(event); + assertEquals(0, transactionsToResubmit.size()); + } +} diff --git a/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/event/stream/ConsensusEventStreamTest.java b/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/event/stream/ConsensusEventStreamTest.java index 85dcf72814cf..9fd626e44b50 100644 --- a/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/event/stream/ConsensusEventStreamTest.java +++ b/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/event/stream/ConsensusEventStreamTest.java @@ -99,7 +99,7 @@ void setStartWriteAtCompleteWindowTest(final boolean startWriteAtCompleteWindow) wiring.bind(CONSENSUS_EVENT_STREAM); wiring.getInputWire(ConsensusEventStream::legacyHashOverride) - .inject(new RunningEventHashOverride(runningHash, randomHash(random), startWriteAtCompleteWindow)); + .inject(new RunningEventHashOverride(runningHash, startWriteAtCompleteWindow)); verify(multiStreamMock).setRunningHash(runningHash); } diff --git a/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/event/validation/EventSignatureValidatorTests.java b/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/event/validation/EventSignatureValidatorTests.java index 46461cd7a65b..19fdeafd02ce 100644 --- a/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/event/validation/EventSignatureValidatorTests.java +++ b/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/event/validation/EventSignatureValidatorTests.java @@ -16,26 +16,22 @@ package com.swirlds.platform.event.validation; -import static com.swirlds.common.test.fixtures.RandomUtils.getRandomPrintSeed; -import static com.swirlds.common.test.fixtures.RandomUtils.randomHash; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotEquals; import static org.junit.jupiter.api.Assertions.assertNull; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; import com.swirlds.base.test.fixtures.time.FakeTime; import com.swirlds.common.context.PlatformContext; -import com.swirlds.common.crypto.Hash; import com.swirlds.common.platform.NodeId; +import com.swirlds.common.test.fixtures.Randotron; import com.swirlds.common.test.fixtures.platform.TestPlatformContextBuilder; import com.swirlds.config.extensions.test.fixtures.TestConfigBuilder; import com.swirlds.platform.consensus.ConsensusConstants; import com.swirlds.platform.consensus.EventWindow; import com.swirlds.platform.crypto.SignatureVerifier; -import com.swirlds.platform.event.AncientMode; import com.swirlds.platform.event.GossipEvent; import com.swirlds.platform.eventhandling.EventConfig; import com.swirlds.platform.eventhandling.EventConfig_; @@ -44,13 +40,11 @@ import com.swirlds.platform.system.SoftwareVersion; import com.swirlds.platform.system.address.Address; import com.swirlds.platform.system.address.AddressBook; -import com.swirlds.platform.system.events.BaseEventHashedData; -import com.swirlds.platform.system.events.BaseEventUnhashedData; import com.swirlds.platform.system.events.EventConstants; import com.swirlds.platform.test.fixtures.crypto.PreGeneratedX509Certs; +import com.swirlds.platform.test.fixtures.event.TestingEventBuilder; import edu.umd.cs.findbugs.annotations.NonNull; import java.util.List; -import java.util.Random; import java.util.concurrent.atomic.AtomicLong; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; @@ -59,7 +53,7 @@ import org.junit.jupiter.params.provider.ValueSource; class EventSignatureValidatorTests { - private Random random; + private Randotron random; private PlatformContext platformContext; private FakeTime time; private AtomicLong exitedIntakePipelineCount; @@ -103,31 +97,9 @@ private static Address generateMockAddress(final @NonNull NodeId nodeId) { nodeId, "", "", 10, null, 77, null, 88, PreGeneratedX509Certs.getSigCert(nodeId.id()), null, ""); } - /** - * Generate a mock event, with enough elements mocked to support the signature validation. - * - * @param version the software version to use for the event - * @param hash the hash to use for the event - * @param creatorId the creator id to use for the event - * @return a mock event - */ - private static GossipEvent generateMockEvent( - @NonNull final SoftwareVersion version, @NonNull final Hash hash, @NonNull final NodeId creatorId) { - final BaseEventHashedData hashedData = mock(BaseEventHashedData.class); - when(hashedData.getSoftwareVersion()).thenReturn(version); - when(hashedData.getHash()).thenReturn(hash); - when(hashedData.getCreatorId()).thenReturn(creatorId); - - final GossipEvent event = mock(GossipEvent.class); - when(event.getHashedData()).thenReturn(hashedData); - when(event.getUnhashedData()).thenReturn(mock(BaseEventUnhashedData.class)); - - return event; - } - @BeforeEach void setup() { - random = getRandomPrintSeed(); + random = Randotron.create(); time = new FakeTime(); platformContext = TestPlatformContextBuilder.create().withTime(time).build(); @@ -169,8 +141,10 @@ void setup() { @Test @DisplayName("Events with higher version than the app should always fail validation") void irreconcilableVersions() { - final GossipEvent event = - generateMockEvent(new BasicSoftwareVersion(3), randomHash(random), currentNodeAddress.getNodeId()); + final GossipEvent event = new TestingEventBuilder(random) + .setCreatorId(currentNodeAddress.getNodeId()) + .setSoftwareVersion(new BasicSoftwareVersion(3)) + .build(); assertNull(validatorWithTrueVerifier.validateSignature(event)); assertEquals(1, exitedIntakePipelineCount.get()); @@ -182,8 +156,10 @@ void versionMismatchWithNullPreviousAddressBook() { final EventSignatureValidator signatureValidator = new DefaultEventSignatureValidator( platformContext, trueVerifier, defaultVersion, null, currentAddressBook, intakeEventCounter); - final GossipEvent event = - generateMockEvent(new BasicSoftwareVersion(1), randomHash(random), previousNodeAddress.getNodeId()); + final GossipEvent event = new TestingEventBuilder(random) + .setCreatorId(previousNodeAddress.getNodeId()) + .setSoftwareVersion(new BasicSoftwareVersion(3)) + .build(); assertNull(signatureValidator.validateSignature(event)); assertEquals(1, exitedIntakePipelineCount.get()); @@ -193,8 +169,10 @@ void versionMismatchWithNullPreviousAddressBook() { @DisplayName("Node is missing from the applicable address book") void applicableAddressBookMissingNode() { // this creator isn't in the current address book, so verification will fail - final GossipEvent event = - generateMockEvent(defaultVersion, randomHash(random), previousNodeAddress.getNodeId()); + final GossipEvent event = new TestingEventBuilder(random) + .setCreatorId(previousNodeAddress.getNodeId()) + .setSoftwareVersion(defaultVersion) + .build(); assertNull(validatorWithTrueVerifier.validateSignature(event)); assertEquals(1, exitedIntakePipelineCount.get()); @@ -208,7 +186,10 @@ void missingPublicKey() { currentAddressBook.add(nodeAddress); - final GossipEvent event = generateMockEvent(defaultVersion, randomHash(random), nodeId); + final GossipEvent event = new TestingEventBuilder(random) + .setCreatorId(nodeId) + .setSoftwareVersion(defaultVersion) + .build(); assertNull(validatorWithTrueVerifier.validateSignature(event)); assertEquals(1, exitedIntakePipelineCount.get()); @@ -218,15 +199,19 @@ void missingPublicKey() { @DisplayName("Event passes validation if the signature verifies") void validSignature() { // both the event and the app have the same version, so the currentAddressBook will be selected - final GossipEvent event1 = - generateMockEvent(defaultVersion, randomHash(random), currentNodeAddress.getNodeId()); + final GossipEvent event1 = new TestingEventBuilder(random) + .setCreatorId(currentNodeAddress.getNodeId()) + .setSoftwareVersion(defaultVersion) + .build(); assertNotEquals(null, validatorWithTrueVerifier.validateSignature(event1)); assertEquals(0, exitedIntakePipelineCount.get()); // event2 is from a previous version, so the previous address book will be selected - final GossipEvent event2 = - generateMockEvent(new BasicSoftwareVersion(1), randomHash(random), previousNodeAddress.getNodeId()); + final GossipEvent event2 = new TestingEventBuilder(random) + .setCreatorId(previousNodeAddress.getNodeId()) + .setSoftwareVersion(new BasicSoftwareVersion(1)) + .build(); assertNotEquals(null, validatorWithTrueVerifier.validateSignature(event2)); assertEquals(0, exitedIntakePipelineCount.get()); @@ -235,7 +220,10 @@ void validSignature() { @Test @DisplayName("Event fails validation if the signature does not verify") void verificationFails() { - final GossipEvent event = generateMockEvent(defaultVersion, randomHash(random), currentNodeAddress.getNodeId()); + final GossipEvent event = new TestingEventBuilder(random) + .setCreatorId(currentNodeAddress.getNodeId()) + .setSoftwareVersion(defaultVersion) + .build(); assertNotEquals(null, validatorWithTrueVerifier.validateSignature(event)); assertEquals(0, exitedIntakePipelineCount.get()); @@ -263,16 +251,11 @@ void ancientEvent(final boolean useBirthRoundForAncientThreshold) { currentAddressBook, intakeEventCounter); - final GossipEvent event = generateMockEvent(defaultVersion, randomHash(random), currentNodeAddress.getNodeId()); - final BaseEventHashedData hData = event.getHashedData(); - when(hData.getBirthRound()).thenReturn(EventConstants.MINIMUM_ROUND_CREATED); - when(hData.getGeneration()).thenReturn(EventConstants.FIRST_GENERATION); - when(event.getAncientIndicator(any())).thenAnswer(invocation -> { - final AncientMode mode = invocation.getArgument(0); - return mode == AncientMode.GENERATION_THRESHOLD - ? EventConstants.FIRST_GENERATION - : EventConstants.MINIMUM_ROUND_CREATED; - }); + final GossipEvent event = new TestingEventBuilder(random) + .setCreatorId(currentNodeAddress.getNodeId()) + .setBirthRound(EventConstants.MINIMUM_ROUND_CREATED) + .setSoftwareVersion(defaultVersion) + .build(); assertNotEquals(null, validator.validateSignature(event)); assertEquals(0, exitedIntakePipelineCount.get()); diff --git a/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/event/validation/InternalEventValidatorTests.java b/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/event/validation/InternalEventValidatorTests.java index 04810021f190..545cd6565809 100644 --- a/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/event/validation/InternalEventValidatorTests.java +++ b/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/event/validation/InternalEventValidatorTests.java @@ -28,6 +28,7 @@ import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; +import com.hedera.pbj.runtime.io.buffer.Bytes; import com.swirlds.base.test.fixtures.time.FakeTime; import com.swirlds.base.time.Time; import com.swirlds.common.context.PlatformContext; @@ -39,7 +40,6 @@ import com.swirlds.platform.eventhandling.EventConfig_; import com.swirlds.platform.gossip.IntakeEventCounter; import com.swirlds.platform.system.events.BaseEventHashedData; -import com.swirlds.platform.system.events.BaseEventUnhashedData; import com.swirlds.platform.system.events.EventDescriptor; import com.swirlds.platform.system.transaction.ConsensusTransactionImpl; import edu.umd.cs.findbugs.annotations.NonNull; @@ -115,11 +115,9 @@ private static GossipEvent generateEvent( when(hashedData.getOtherParents()) .thenReturn(otherParent == null ? Collections.EMPTY_LIST : Collections.singletonList(otherParent)); - final BaseEventUnhashedData unhashedData = mock(BaseEventUnhashedData.class); - final GossipEvent event = mock(GossipEvent.class); when(event.getHashedData()).thenReturn(hashedData); - when(event.getUnhashedData()).thenReturn(unhashedData); + when(event.getSignature()).thenReturn(Bytes.EMPTY); when(event.getGeneration()).thenReturn(self.getGeneration()); when(event.getDescriptor()).thenReturn(self); @@ -147,10 +145,10 @@ void nullHashedData() { } @Test - @DisplayName("An event with null unhashed data is invalid") - void nullUnhashedData() { + @DisplayName("An event with null signature is invalid") + void nullSignatureData() { final GossipEvent event = generateGoodEvent(random, 1111); - when(event.getUnhashedData()).thenReturn(null); + when(event.getSignature()).thenReturn(null); assertNull(multinodeValidator.validateEvent(event)); assertNull(singleNodeValidator.validateEvent(event)); diff --git a/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/eventhandling/ConsensusRoundHandlerTests.java b/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/eventhandling/ConsensusRoundHandlerTests.java index 72026f3ae374..407c59e82d77 100644 --- a/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/eventhandling/ConsensusRoundHandlerTests.java +++ b/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/eventhandling/ConsensusRoundHandlerTests.java @@ -16,13 +16,8 @@ package com.swirlds.platform.eventhandling; -import static com.swirlds.common.test.fixtures.AssertionUtils.assertEventuallyTrue; import static com.swirlds.common.test.fixtures.RandomUtils.getRandomPrintSeed; -import static com.swirlds.common.test.fixtures.RandomUtils.randomHash; -import static com.swirlds.common.test.fixtures.RandomUtils.randomInstant; -import static java.util.concurrent.TimeUnit.MILLISECONDS; import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertNotEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertNull; @@ -38,26 +33,20 @@ import com.swirlds.common.context.PlatformContext; import com.swirlds.common.crypto.Hash; import com.swirlds.common.test.fixtures.platform.TestPlatformContextBuilder; -import com.swirlds.platform.consensus.ConsensusSnapshot; -import com.swirlds.platform.consensus.EventWindow; -import com.swirlds.platform.gossip.shadowgraph.Generations; import com.swirlds.platform.internal.ConsensusRound; import com.swirlds.platform.internal.EventImpl; import com.swirlds.platform.state.PlatformState; import com.swirlds.platform.state.State; import com.swirlds.platform.state.SwirldStateManager; import com.swirlds.platform.system.SoftwareVersion; -import com.swirlds.platform.system.address.AddressBook; import com.swirlds.platform.system.status.StatusActionSubmitter; import com.swirlds.platform.system.status.actions.FreezePeriodEnteredAction; import com.swirlds.platform.test.fixtures.event.EventImplTestUtils; import com.swirlds.platform.test.fixtures.event.TestingEventBuilder; import com.swirlds.platform.wiring.components.StateAndRound; import edu.umd.cs.findbugs.annotations.NonNull; -import java.time.Duration; import java.util.List; import java.util.Random; -import java.util.concurrent.atomic.AtomicBoolean; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; @@ -212,56 +201,4 @@ void freezeHandling() throws InterruptedException { .setLegacyRunningEventHash( events.getLast().getRunningHash().getFutureHash().getAndRethrow()); } - - @Test - void runningEventHashInserted() throws InterruptedException { - final PlatformContext platformContext = - TestPlatformContextBuilder.create().build(); - - final State state = new State(); - final PlatformState platformState = new PlatformState(); - state.setPlatformState(platformState); - - final SwirldStateManager swirldStateManager = mock(SwirldStateManager.class); - when(swirldStateManager.getConsensusState()).thenReturn(state); - when(swirldStateManager.getStateForSigning()).thenReturn(state); - - final ConsensusRoundHandler consensusRoundHandler = new ConsensusRoundHandler( - platformContext, swirldStateManager, mock(StatusActionSubmitter.class), mock(SoftwareVersion.class)); - - final EventImpl keystoneEvent = buildEvent(); - final List events = List.of(buildEvent(), buildEvent(), buildEvent()); - - final ConsensusSnapshot consensusSnapshot = mock(ConsensusSnapshot.class); - when(consensusSnapshot.consensusTimestamp()).thenReturn(randomInstant(random)); - - final ConsensusRound consensusRound = new ConsensusRound( - mock(AddressBook.class), - events, - keystoneEvent, - mock(Generations.class), - mock(EventWindow.class), - consensusSnapshot); - - // Call handle on a background thread. We expect the thread to block until the running event hash has been set. - final AtomicBoolean completed = new AtomicBoolean(false); - final Thread thread = new Thread(() -> { - consensusRoundHandler.handleConsensusRound(consensusRound); - completed.set(true); - }); - thread.start(); - - // The thread should become blocked. Sleep for a little while to make sure it doesn't try to do things - // before the running event hash is set. - MILLISECONDS.sleep(200); - assertFalse(completed.get()); - - final Hash runningHash = randomHash(random); - consensusRound.setRunningEventHash(runningHash); - - assertEventuallyTrue(completed::get, Duration.ofSeconds(1), "handling should have completed"); - assertEquals(runningHash, platformState.getRunningEventHash()); - - thread.join(); - } } diff --git a/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/eventhandling/TransactionPrehandlerTests.java b/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/eventhandling/TransactionPrehandlerTests.java index 15471118f5bf..82fbcac9eec8 100644 --- a/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/eventhandling/TransactionPrehandlerTests.java +++ b/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/eventhandling/TransactionPrehandlerTests.java @@ -19,8 +19,8 @@ import static com.swirlds.common.test.fixtures.AssertionUtils.assertEventuallyTrue; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import com.swirlds.common.context.PlatformContext; @@ -48,7 +48,16 @@ void normalOperation() { final AtomicBoolean returnValidState = new AtomicBoolean(false); final AtomicBoolean stateRetrievalAttempted = new AtomicBoolean(false); + final AtomicBoolean stateClosed = new AtomicBoolean(false); final ReservedSignedState state = mock(ReservedSignedState.class); + doAnswer(invocation -> { + assertFalse(stateClosed::get); + stateClosed.set(true); + return null; + }) + .when(state) + .close(); + final SignedStateNexus latestImmutableStateNexus = mock(SignedStateNexus.class); // return null until returnValidState is set to true. keep track of when the first state retrieval is attempted, // so we can assert that prehandle hasn't happened before the state is available @@ -78,7 +87,6 @@ void normalOperation() { returnValidState.set(true); assertEventuallyTrue(prehandleCompleted::get, Duration.ofSeconds(1), "prehandle didn't complete"); - - verify(state).close(); + assertEventuallyTrue(stateClosed::get, Duration.ofSeconds(1), "state wasn't closed"); } } diff --git a/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/pool/TransactionPoolTests.java b/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/pool/TransactionPoolTests.java new file mode 100644 index 000000000000..929ed5b16b64 --- /dev/null +++ b/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/pool/TransactionPoolTests.java @@ -0,0 +1,73 @@ +/* + * Copyright (C) 2024 Hedera Hashgraph, LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.swirlds.platform.pool; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertSame; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyBoolean; +import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import com.swirlds.platform.system.transaction.ConsensusTransactionImpl; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.atomic.AtomicBoolean; +import org.junit.jupiter.api.Test; + +class TransactionPoolTests { + + @Test + void addTransactionTest() { + final List transactionList = new ArrayList<>(); + final TransactionPoolNexus transactionPoolNexus = mock(TransactionPoolNexus.class); + when(transactionPoolNexus.submitTransaction(any(), anyBoolean())).thenAnswer(invocation -> { + final ConsensusTransactionImpl transaction = invocation.getArgument(0); + final boolean isPriority = invocation.getArgument(1); + assertTrue(isPriority); + transactionList.add(transaction); + return true; + }); + + final TransactionPool transactionPool = new DefaultTransactionPool(transactionPoolNexus); + final ConsensusTransactionImpl transaction = mock(ConsensusTransactionImpl.class); + + transactionPool.submitSystemTransaction(transaction); + assertEquals(1, transactionList.size()); + assertSame(transaction, transactionList.getFirst()); + } + + @Test + void clearTest() { + final TransactionPoolNexus transactionPoolNexus = mock(TransactionPoolNexus.class); + final AtomicBoolean clearCalled = new AtomicBoolean(false); + + doAnswer(invocation -> { + clearCalled.set(true); + return null; + }) + .when(transactionPoolNexus) + .clear(); + + final TransactionPool transactionPool = new DefaultTransactionPool(transactionPoolNexus); + transactionPool.clear(); + + assertTrue(clearCalled.get()); + } +} diff --git a/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/proof/StateProofTests.java b/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/proof/StateProofTests.java index f280a11e9ab7..7d4f9994e923 100644 --- a/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/proof/StateProofTests.java +++ b/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/proof/StateProofTests.java @@ -49,7 +49,7 @@ import com.swirlds.common.utility.Threshold; import com.swirlds.platform.system.address.Address; import com.swirlds.platform.system.address.AddressBook; -import com.swirlds.platform.test.fixtures.addressbook.RandomAddressBookGenerator; +import com.swirlds.platform.test.fixtures.addressbook.RandomAddressBookBuilder; import edu.umd.cs.findbugs.annotations.NonNull; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; @@ -99,7 +99,7 @@ private Map generateThresholdOfSignatures( for (final NodeId nodeId : nodes) { final Address address = addressBook.getAddress(nodeId); weight += address.getWeight(); - signatures.put(nodeId, signatureBuilder.fakeSign(hash.getValue(), address.getSigPublicKey())); + signatures.put(nodeId, signatureBuilder.fakeSign(hash.copyToByteArray(), address.getSigPublicKey())); if (threshold.isSatisfiedBy(weight, addressBook.getTotalWeight())) { break; @@ -145,7 +145,7 @@ void basicBehaviorTest() throws IOException { MerkleCryptoFactory.getInstance().digestTreeSync(root); final AddressBook addressBook = - new RandomAddressBookGenerator(random).setSize(10).build(); + RandomAddressBookBuilder.create(random).withSize(10).build(); final MerkleLeaf nodeD = root.getNodeAtRoute(MerkleRouteFactory.buildRoute(2, 0)).asLeaf(); @@ -182,7 +182,7 @@ void leafTreeTest() throws IOException { MerkleCryptoFactory.getInstance().digestTreeSync(root); final AddressBook addressBook = - new RandomAddressBookGenerator(random).setSize(10).build(); + RandomAddressBookBuilder.create(random).withSize(10).build(); final Map signatures = generateThresholdOfSignatures(random, addressBook, signatureBuilder, root.getHash(), SUPER_MAJORITY); @@ -216,8 +216,8 @@ private void testWithNPayloads( MerkleCryptoFactory.getInstance().digestTreeSync(root); - final AddressBook addressBook = new RandomAddressBookGenerator(random) - .setSize(random.nextInt(1, 10)) + final AddressBook addressBook = RandomAddressBookBuilder.create(random) + .withSize(random.nextInt(1, 10)) .build(); final Map signatures = @@ -310,8 +310,8 @@ private void testWithNInvalidPayloads( // Now, rehash the tree using the incorrect leaf hashes. MerkleCryptoFactory.getInstance().digestTreeSync(root); - final AddressBook addressBook = new RandomAddressBookGenerator(random) - .setSize(random.nextInt(1, 10)) + final AddressBook addressBook = RandomAddressBookBuilder.create(random) + .withSize(random.nextInt(1, 10)) .build(); final Map signatures = @@ -461,7 +461,7 @@ void zeroWeightSignatureTest() { // For this test, there will be 9 total weight, // with the node at index 9 having 0 stake, and all others having a weight of 1. final AddressBook addressBook = - new RandomAddressBookGenerator(random).setSize(10).build(); + RandomAddressBookBuilder.create(random).withSize(10).build(); for (int index = 0; index < 10; index++) { final NodeId nodeId = addressBook.getNodeId(index); if (index == 9) { @@ -479,7 +479,8 @@ void zeroWeightSignatureTest() { for (int index = 0; index < 6; index++) { final NodeId nodeId = addressBook.getNodeId(index); final Address address = addressBook.getAddress(nodeId); - final Signature signature = signatureBuilder.fakeSign(root.getHash().getValue(), address.getSigPublicKey()); + final Signature signature = + signatureBuilder.fakeSign(root.getHash().copyToByteArray(), address.getSigPublicKey()); signatures.put(nodeId, signature); } @@ -491,7 +492,8 @@ void zeroWeightSignatureTest() { // Adding the zero weight signature should not change the result. final NodeId nodeId = addressBook.getNodeId(9); final Address address = addressBook.getAddress(nodeId); - final Signature signature = signatureBuilder.fakeSign(root.getHash().getValue(), address.getSigPublicKey()); + final Signature signature = + signatureBuilder.fakeSign(root.getHash().copyToByteArray(), address.getSigPublicKey()); signatures.put(nodeId, signature); final StateProof stateProofB = new StateProof(cryptography, root, signatures, List.of(nodeD)); @@ -501,7 +503,8 @@ void zeroWeightSignatureTest() { final NodeId nodeId2 = addressBook.getNodeId(8); final Address address2 = addressBook.getAddress(nodeId2); - final Signature signature2 = signatureBuilder.fakeSign(root.getHash().getValue(), address2.getSigPublicKey()); + final Signature signature2 = + signatureBuilder.fakeSign(root.getHash().copyToByteArray(), address2.getSigPublicKey()); signatures.put(nodeId2, signature2); final StateProof stateProofC = new StateProof(cryptography, root, signatures, List.of(nodeD)); @@ -521,7 +524,7 @@ void signatureNotInAddressBookTest() { // For this test, there will be 9 total weight, with each node having a weight of 1. final AddressBook addressBook = - new RandomAddressBookGenerator(random).setSize(9).build(); + RandomAddressBookBuilder.create(random).withSize(9).build(); for (int index = 0; index < 9; index++) { final NodeId nodeId = addressBook.getNodeId(index); addressBook.add(addressBook.getAddress(nodeId).copySetWeight(1)); @@ -535,7 +538,8 @@ void signatureNotInAddressBookTest() { for (int index = 0; index < 6; index++) { final NodeId nodeId = addressBook.getNodeId(index); final Address address = addressBook.getAddress(nodeId); - final Signature signature = signatureBuilder.fakeSign(root.getHash().getValue(), address.getSigPublicKey()); + final Signature signature = + signatureBuilder.fakeSign(root.getHash().copyToByteArray(), address.getSigPublicKey()); signatures.put(nodeId, signature); } @@ -557,7 +561,8 @@ void signatureNotInAddressBookTest() { final NodeId nodeId2 = addressBook.getNodeId(7); final Address address2 = addressBook.getAddress(nodeId2); - final Signature signature2 = signatureBuilder.fakeSign(root.getHash().getValue(), address2.getSigPublicKey()); + final Signature signature2 = + signatureBuilder.fakeSign(root.getHash().copyToByteArray(), address2.getSigPublicKey()); signatures.put(nodeId2, signature2); final StateProof stateProofC = new StateProof(cryptography, root, signatures, List.of(nodeD)); @@ -588,7 +593,7 @@ void duplicateSignaturesTest() throws IOException { // For this test, there will be 9 total weight, with each node having a weight of 1. final AddressBook addressBook = - new RandomAddressBookGenerator(random).setSize(9).build(); + RandomAddressBookBuilder.create(random).withSize(9).build(); for (int index = 0; index < 9; index++) { final NodeId nodeId = addressBook.getNodeId(index); addressBook.add(addressBook.getAddress(nodeId).copySetWeight(1)); @@ -602,7 +607,8 @@ void duplicateSignaturesTest() throws IOException { for (int index = 0; index < 6; index++) { final NodeId nodeId = addressBook.getNodeId(index); final Address address = addressBook.getAddress(nodeId); - final Signature signature = signatureBuilder.fakeSign(root.getHash().getValue(), address.getSigPublicKey()); + final Signature signature = + signatureBuilder.fakeSign(root.getHash().copyToByteArray(), address.getSigPublicKey()); signatures.put(nodeId, signature); } @@ -633,7 +639,8 @@ void duplicateSignaturesTest() throws IOException { final NodeId nodeId2 = addressBook.getNodeId(7); final Address address2 = addressBook.getAddress(nodeId2); - final Signature signature2 = signatureBuilder.fakeSign(root.getHash().getValue(), address2.getSigPublicKey()); + final Signature signature2 = + signatureBuilder.fakeSign(root.getHash().copyToByteArray(), address2.getSigPublicKey()); signatures.put(nodeId2, signature2); final StateProof stateProofC = new StateProof(cryptography, root, signatures, List.of(nodeD)); @@ -653,7 +660,7 @@ void realSignaturesWrongIdsTest() throws IOException { // For this test, there will be 9 total weight, with each node having a weight of 1. final AddressBook addressBook = - new RandomAddressBookGenerator(random).setSize(9).build(); + RandomAddressBookBuilder.create(random).withSize(9).build(); for (int index = 0; index < 9; index++) { final NodeId nodeId = addressBook.getNodeId(index); addressBook.add(addressBook.getAddress(nodeId).copySetWeight(1)); @@ -667,7 +674,8 @@ void realSignaturesWrongIdsTest() throws IOException { for (int index = 0; index < 7; index++) { final NodeId nodeId = addressBook.getNodeId(index); final Address address = addressBook.getAddress(nodeId); - final Signature signature = signatureBuilder.fakeSign(root.getHash().getValue(), address.getSigPublicKey()); + final Signature signature = + signatureBuilder.fakeSign(root.getHash().copyToByteArray(), address.getSigPublicKey()); signatures.put(nodeId, signature); } @@ -711,7 +719,7 @@ void thresholdTest() { // For this test, there will be 12 total weight, with each node having a weight of 1. // 12 is chosen because it is divisible by both 2 and 3. final AddressBook addressBook = - new RandomAddressBookGenerator(random).setSize(12).build(); + RandomAddressBookBuilder.create(random).withSize(12).build(); for (int index = 0; index < 12; index++) { final NodeId nodeId = addressBook.getNodeId(index); addressBook.add(addressBook.getAddress(nodeId).copySetWeight(1)); @@ -725,7 +733,8 @@ void thresholdTest() { final NodeId nextId = addressBook.getNodeId(index); final Address address = addressBook.getAddress(nextId); - final Signature signature = signatureBuilder.fakeSign(root.getHash().getValue(), address.getSigPublicKey()); + final Signature signature = + signatureBuilder.fakeSign(root.getHash().copyToByteArray(), address.getSigPublicKey()); signatures.put(nextId, signature); int weight = index + 1; diff --git a/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/reconnect/DefaultSignedStateValidatorTests.java b/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/reconnect/DefaultSignedStateValidatorTests.java index 05d1df249fb6..6cbe37cf99c2 100644 --- a/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/reconnect/DefaultSignedStateValidatorTests.java +++ b/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/reconnect/DefaultSignedStateValidatorTests.java @@ -27,6 +27,7 @@ import com.swirlds.common.crypto.SignatureType; import com.swirlds.common.platform.NodeId; import com.swirlds.common.test.fixtures.RandomUtils; +import com.swirlds.common.test.fixtures.Randotron; import com.swirlds.common.test.fixtures.platform.TestPlatformContextBuilder; import com.swirlds.platform.crypto.SignatureVerifier; import com.swirlds.platform.state.RandomSignedStateGenerator; @@ -34,7 +35,8 @@ import com.swirlds.platform.state.signed.SignedStateInvalidException; import com.swirlds.platform.state.signed.SignedStateValidationData; import com.swirlds.platform.system.address.AddressBook; -import com.swirlds.platform.test.fixtures.addressbook.RandomAddressBookGenerator; +import com.swirlds.platform.test.fixtures.addressbook.RandomAddressBuilder; +import edu.umd.cs.findbugs.annotations.NonNull; import java.util.ArrayList; import java.util.HashMap; import java.util.LinkedList; @@ -230,15 +232,24 @@ private static List initNodes() { .collect(Collectors.toList())); } + @NonNull + private static AddressBook createAddressBook(@NonNull final Random random, @NonNull final List nodes) { + final AddressBook addressBook = new AddressBook(); + for (final Node node : nodes.stream().sorted().toList()) { + addressBook.add(RandomAddressBuilder.create(random) + .withNodeId(node.id) + .withWeight(node.weight) + .build()); + } + return addressBook; + } + @ParameterizedTest @MethodSource({"staticNodeParams", "randomizedNodeParams"}) @DisplayName("Signed State Validation") void testSignedStateValidationRandom(final String desc, final List nodes, final List signingNodes) { - final Map nodeWeights = nodes.stream().collect(Collectors.toMap(Node::id, Node::weight)); - addressBook = new RandomAddressBookGenerator() - .setNodeIds(nodeWeights.keySet()) - .setCustomWeightGenerator(nodeWeights::get) - .build(); + final Randotron randotron = Randotron.create(); + addressBook = createAddressBook(randotron, nodes); final PlatformContext platformContext = TestPlatformContextBuilder.create().build(); @@ -308,10 +319,10 @@ private SignedState stateSignedByNodes(final List signingNodes) { final SignatureVerifier signatureVerifier = (data, signature, key) -> { // a signature with a 0 byte is always invalid // this is set in the nodeSigs() method - if (signature[0] == 0) { + if (signature.getByte(0) == 0) { return false; } - final Hash hash = new Hash(data, stateHash.getDigestType()); + final Hash hash = new Hash(data.toByteArray(), stateHash.getDigestType()); return hash.equals(stateHash); }; @@ -342,11 +353,16 @@ private Map nodeSigs(final List nodes) { * A record representing a simple node that holds its id, amount of weight, and if it signs states with a valid * signature. */ - private record Node(NodeId id, long weight, boolean validSignature) { + private record Node(NodeId id, long weight, boolean validSignature) implements Comparable { @Override public String toString() { return String.format("NodeId: %s,\tWeight: %s,\tValidSig: %s", id, weight, validSignature); } + + @Override + public int compareTo(@NonNull final Node that) { + return id.compareTo(that.id); + } } } diff --git a/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/reconnect/ReconnectProtocolTests.java b/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/reconnect/ReconnectProtocolTests.java index 612f69dda956..462efa95cd13 100644 --- a/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/reconnect/ReconnectProtocolTests.java +++ b/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/reconnect/ReconnectProtocolTests.java @@ -174,7 +174,7 @@ void shouldInitiateTest(final InitiateParams params) { reconnectController, mock(SignedStateValidator.class), fallenBehindManager, - activeStatusGetter, + activeStatusGetter::getCurrentStatus, configuration); assertEquals( @@ -217,7 +217,7 @@ void testShouldAccept(final AcceptParams params) { reconnectController, mock(SignedStateValidator.class), fallenBehindManager, - activeStatusGetter, + activeStatusGetter::getCurrentStatus, configuration); assertEquals( @@ -251,7 +251,7 @@ void testPermitReleased() throws InterruptedException { reconnectController, mock(SignedStateValidator.class), fallenBehindManager, - activeStatusGetter, + activeStatusGetter::getCurrentStatus, configuration); // the ReconnectController must be running in order to provide permits @@ -302,7 +302,7 @@ void testTeacherThrottleReleased() { reconnectController, mock(SignedStateValidator.class), fallenBehindManager, - activeStatusGetter, + activeStatusGetter::getCurrentStatus, configuration, Time.getCurrent()); final SignedState signedState = spy(new RandomSignedStateGenerator().build()); @@ -323,7 +323,7 @@ void testTeacherThrottleReleased() { reconnectController, mock(SignedStateValidator.class), fallenBehindManager, - activeStatusGetter, + activeStatusGetter::getCurrentStatus, configuration, Time.getCurrent()); @@ -368,7 +368,7 @@ void abortedLearner() { reconnectController, mock(SignedStateValidator.class), fallenBehindManager, - activeStatusGetter, + activeStatusGetter::getCurrentStatus, configuration); final Protocol protocol = reconnectProtocolFactory.build(new NodeId(0)); assertTrue(protocol.shouldInitiate()); @@ -412,7 +412,7 @@ void abortedTeacher() { reconnectController, mock(SignedStateValidator.class), fallenBehindManager, - activeStatusGetter, + activeStatusGetter::getCurrentStatus, configuration); final Protocol protocol = reconnectProtocolFactory.build(new NodeId(0)); @@ -450,7 +450,7 @@ void teacherHasNoSignedState() { mock(ReconnectController.class), mock(SignedStateValidator.class), fallenBehindManager, - activeStatusGetter, + activeStatusGetter::getCurrentStatus, configuration); final Protocol protocol = reconnectProtocolFactory.build(new NodeId(0)); assertFalse(protocol.shouldAccept()); @@ -483,7 +483,7 @@ void teacherNotActive() { mock(ReconnectController.class), mock(SignedStateValidator.class), fallenBehindManager, - inactiveStatusGetter, + inactiveStatusGetter::getCurrentStatus, configuration); final Protocol protocol = reconnectProtocolFactory.build(new NodeId(0)); assertFalse(protocol.shouldAccept()); @@ -509,7 +509,7 @@ void teacherHoldsLearnerPermit() { reconnectController, mock(SignedStateValidator.class), mock(FallenBehindManager.class), - activeStatusGetter, + activeStatusGetter::getCurrentStatus, configuration); final Protocol protocol = reconnectProtocolFactory.build(new NodeId(0)); assertTrue(protocol.shouldAccept()); @@ -555,7 +555,7 @@ void teacherCantAcquireLearnerPermit() { reconnectController, mock(SignedStateValidator.class), mock(FallenBehindManager.class), - activeStatusGetter, + activeStatusGetter::getCurrentStatus, configuration); final Protocol protocol = reconnectProtocolFactory.build(new NodeId(0)); assertFalse(protocol.shouldAccept()); diff --git a/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/reconnect/ReconnectTest.java b/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/reconnect/ReconnectTest.java index b7b5210935a9..5758af4b78f4 100644 --- a/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/reconnect/ReconnectTest.java +++ b/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/reconnect/ReconnectTest.java @@ -44,7 +44,7 @@ import com.swirlds.platform.state.signed.SignedStateValidator; import com.swirlds.platform.system.address.Address; import com.swirlds.platform.system.address.AddressBook; -import com.swirlds.platform.test.fixtures.addressbook.RandomAddressBookGenerator; +import com.swirlds.platform.test.fixtures.addressbook.RandomAddressBookBuilder; import java.io.IOException; import java.security.PublicKey; import java.time.Duration; @@ -109,11 +109,10 @@ private void executeReconnect(final ReconnectMetrics reconnectMetrics) throws In IntStream.range(0, numNodes).mapToObj(NodeId::new).toList(); final Random random = RandomUtils.getRandomPrintSeed(); - final AddressBook addressBook = new RandomAddressBookGenerator(random) - .setSize(numNodes) - .setAverageWeight(weightPerNode) - .setWeightDistributionStrategy(RandomAddressBookGenerator.WeightDistributionStrategy.BALANCED) - .setHashStrategy(RandomAddressBookGenerator.HashStrategy.REAL_HASH) + final AddressBook addressBook = RandomAddressBookBuilder.create(random) + .withSize(numNodes) + .withAverageWeight(weightPerNode) + .withWeightDistributionStrategy(RandomAddressBookBuilder.WeightDistributionStrategy.BALANCED) .build(); try (final PairedStreams pairedStreams = new PairedStreams()) { diff --git a/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/reconnect/emergency/EmergencyReconnectTests.java b/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/reconnect/emergency/EmergencyReconnectTests.java index d92a2fe96baf..e2a71ed80c02 100644 --- a/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/reconnect/emergency/EmergencyReconnectTests.java +++ b/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/reconnect/emergency/EmergencyReconnectTests.java @@ -63,7 +63,7 @@ import com.swirlds.platform.state.signed.SignedState; import com.swirlds.platform.system.address.AddressBook; import com.swirlds.platform.system.status.StatusActionSubmitter; -import com.swirlds.platform.test.fixtures.addressbook.RandomAddressBookGenerator; +import com.swirlds.platform.test.fixtures.addressbook.RandomAddressBookBuilder; import java.io.IOException; import java.time.Duration; import java.time.temporal.ChronoUnit; @@ -326,11 +326,10 @@ private void mockTeacherHasCompatibleState(final SignedState teacherState) { } private AddressBook newAddressBook(final Random random, final int numNodes) { - return new RandomAddressBookGenerator(random) - .setSize(numNodes) - .setAverageWeight(100L) - .setWeightDistributionStrategy(RandomAddressBookGenerator.WeightDistributionStrategy.BALANCED) - .setHashStrategy(RandomAddressBookGenerator.HashStrategy.REAL_HASH) + return RandomAddressBookBuilder.create(random) + .withSize(numNodes) + .withAverageWeight(100L) + .withWeightDistributionStrategy(RandomAddressBookBuilder.WeightDistributionStrategy.BALANCED) .build(); } diff --git a/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/reconnect/emergency/EmergencySignedStateValidatorTests.java b/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/reconnect/emergency/EmergencySignedStateValidatorTests.java index 68d5ffd3989a..970c617f6c94 100644 --- a/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/reconnect/emergency/EmergencySignedStateValidatorTests.java +++ b/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/reconnect/emergency/EmergencySignedStateValidatorTests.java @@ -24,6 +24,7 @@ import com.swirlds.common.crypto.Hash; import com.swirlds.common.platform.NodeId; import com.swirlds.common.test.fixtures.RandomUtils; +import com.swirlds.common.test.fixtures.Randotron; import com.swirlds.config.extensions.test.fixtures.TestConfigBuilder; import com.swirlds.platform.config.StateConfig; import com.swirlds.platform.recovery.emergencyfile.EmergencyRecoveryFile; @@ -31,7 +32,7 @@ import com.swirlds.platform.state.signed.SignedState; import com.swirlds.platform.state.signed.SignedStateInvalidException; import com.swirlds.platform.system.address.AddressBook; -import com.swirlds.platform.test.fixtures.addressbook.RandomAddressBookGenerator; +import com.swirlds.platform.test.fixtures.addressbook.RandomAddressBookBuilder; import java.util.List; import java.util.Map; import java.util.Random; @@ -52,10 +53,10 @@ public class EmergencySignedStateValidatorTests { @BeforeEach void setup() { - addressBook = new RandomAddressBookGenerator() - .setSize(NUM_NODES) - .setAverageWeight(WEIGHT_PER_NODE) - .setWeightDistributionStrategy(RandomAddressBookGenerator.WeightDistributionStrategy.BALANCED) + addressBook = RandomAddressBookBuilder.create(Randotron.create()) + .withSize(NUM_NODES) + .withAverageWeight(WEIGHT_PER_NODE) + .withWeightDistributionStrategy(RandomAddressBookBuilder.WeightDistributionStrategy.BALANCED) .build(); } diff --git a/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/recovery/EventStreamMultiFileIteratorTest.java b/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/recovery/EventStreamMultiFileIteratorTest.java index c2f0c5aaf902..1f2b64cfce25 100644 --- a/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/recovery/EventStreamMultiFileIteratorTest.java +++ b/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/recovery/EventStreamMultiFileIteratorTest.java @@ -103,8 +103,7 @@ void readAllEventsTest() throws IOException, NoSuchAlgorithmException { final DetailedConsensusEvent event = deserializedEvents.get(eventIndex); // Convert to event impl to allow comparison - final EventImpl e = new EventImpl( - event.getBaseEventHashedData(), event.getBaseEventUnhashedData(), event.getConsensusData()); + final EventImpl e = new EventImpl(event); assertEventsAreEqual(e, events.get(eventIndex)); } @@ -144,8 +143,7 @@ void readEventsStartingAtRoundTest() throws NoSuchAlgorithmException, IOExceptio final DetailedConsensusEvent event = deserializedEvents.get(eventIndex); // Convert to event impl to allow comparison - final EventImpl e = new EventImpl( - event.getBaseEventHashedData(), event.getBaseEventUnhashedData(), event.getConsensusData()); + final EventImpl e = new EventImpl(event); assertEventsAreEqual(e, events.get(eventIndex + startingIndex)); } @@ -216,8 +214,7 @@ void missingEventStreamFileTest() throws IOException, NoSuchAlgorithmException { final DetailedConsensusEvent event = deserializedEvents.get(eventIndex); // Convert to event impl to allow comparison - final EventImpl e = new EventImpl( - event.getBaseEventHashedData(), event.getBaseEventUnhashedData(), event.getConsensusData()); + final EventImpl e = new EventImpl(event); assertEquals(e, events.get(eventIndex), "event should match input event"); } @@ -272,8 +269,7 @@ void truncatedLastFileTest() throws NoSuchAlgorithmException, IOException { final DetailedConsensusEvent event = deserializedEvents.get(eventIndex); // Convert to event impl to allow comparison - final EventImpl e = new EventImpl( - event.getBaseEventHashedData(), event.getBaseEventUnhashedData(), event.getConsensusData()); + final EventImpl e = new EventImpl(event); assertEventsAreEqual(e, events.get(eventIndex)); } @@ -312,8 +308,7 @@ void truncatedMiddleFileTest() throws NoSuchAlgorithmException, IOException { final DetailedConsensusEvent event = deserializedEvents.get(eventIndex); // Convert to event impl to allow comparison - final EventImpl e = new EventImpl( - event.getBaseEventHashedData(), event.getBaseEventUnhashedData(), event.getConsensusData()); + final EventImpl e = new EventImpl(event); assertEquals(e, events.get(eventIndex), "event should match input event"); } @@ -417,8 +412,7 @@ private void testEventStreamBound( final DetailedConsensusEvent event = deserializedEvents.get(eventIndex); // Convert to event impl to allow comparison - final EventImpl e = new EventImpl( - event.getBaseEventHashedData(), event.getBaseEventUnhashedData(), event.getConsensusData()); + final EventImpl e = new EventImpl(event); assertEventsAreEqual(e, events.get(startingIndex + eventIndex)); } } diff --git a/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/recovery/EventStreamSingleFileIteratorTest.java b/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/recovery/EventStreamSingleFileIteratorTest.java index c6b2b79b923b..69c81944df17 100644 --- a/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/recovery/EventStreamSingleFileIteratorTest.java +++ b/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/recovery/EventStreamSingleFileIteratorTest.java @@ -94,8 +94,7 @@ void simpleStreamTest() throws IOException, NoSuchAlgorithmException, Constructa assertSame(event, peekObject, "invalid peek behavior"); // Convert to event impl to allow comparison - final EventImpl e = new EventImpl( - event.getBaseEventHashedData(), event.getBaseEventUnhashedData(), event.getConsensusData()); + final EventImpl e = new EventImpl(event); assertEventsAreEqual(e, events.get(eventIndex)); eventIndex++; } @@ -139,8 +138,7 @@ void allowedTruncatedFileTest() throws IOException, NoSuchAlgorithmException, Co assertSame(event, peekObject, "invalid peek behavior"); // Convert to event impl to allow comparison - final EventImpl e = new EventImpl( - event.getBaseEventHashedData(), event.getBaseEventUnhashedData(), event.getConsensusData()); + final EventImpl e = new EventImpl(event); assertEquals(e, events.get(eventIndex), "event should match input event"); eventIndex++; } @@ -186,8 +184,7 @@ void disallowedTruncatedFileTest() throws IOException, NoSuchAlgorithmException, assertSame(event, peekObject, "invalid peek behavior"); // Convert to event impl to allow comparison - final EventImpl e = new EventImpl( - event.getBaseEventHashedData(), event.getBaseEventUnhashedData(), event.getConsensusData()); + final EventImpl e = new EventImpl(event); assertEventsAreEqual(e, events.get(eventIndex)); eventIndex++; } @@ -239,8 +236,7 @@ void disallowedTruncatedOnBoundaryTest() assertSame(event, peekObject, "invalid peek behavior"); // Convert to event impl to allow comparison - final EventImpl e = new EventImpl( - event.getBaseEventHashedData(), event.getBaseEventUnhashedData(), event.getConsensusData()); + final EventImpl e = new EventImpl(event); assertEventsAreEqual(e, events.get(eventIndex)); eventIndex++; } diff --git a/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/recovery/ObjectStreamIteratorTest.java b/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/recovery/ObjectStreamIteratorTest.java index 83da12cfbc30..fd57e7cf1072 100644 --- a/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/recovery/ObjectStreamIteratorTest.java +++ b/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/recovery/ObjectStreamIteratorTest.java @@ -104,8 +104,7 @@ public void simpleStreamTest() throws IOException, NoSuchAlgorithmException, Con } else if (object instanceof DetailedConsensusEvent event) { // Convert to event impl to allow comparison - final EventImpl e = new EventImpl( - event.getBaseEventHashedData(), event.getBaseEventUnhashedData(), event.getConsensusData()); + final EventImpl e = new EventImpl(event); assertEventsAreEqual(e, events.get(eventIndex)); eventIndex++; } else { @@ -176,8 +175,7 @@ public void allowedTruncatedFileTest() } else if (object instanceof DetailedConsensusEvent event) { // Convert to event impl to allow comparison - final EventImpl e = new EventImpl( - event.getBaseEventHashedData(), event.getBaseEventUnhashedData(), event.getConsensusData()); + final EventImpl e = new EventImpl(event); assertEventsAreEqual(e, events.get(eventIndex)); eventIndex++; } else { @@ -235,8 +233,7 @@ public void disallowedTruncatedFileTest() } else if (object instanceof DetailedConsensusEvent event) { // Convert to event impl to allow comparison - final EventImpl e = new EventImpl( - event.getBaseEventHashedData(), event.getBaseEventUnhashedData(), event.getConsensusData()); + final EventImpl e = new EventImpl(event); assertEventsAreEqual(e, events.get(eventIndex)); eventIndex++; } else { diff --git a/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/recovery/RecoveryTestUtils.java b/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/recovery/RecoveryTestUtils.java index 8f207eceb4e5..853b385cd11d 100644 --- a/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/recovery/RecoveryTestUtils.java +++ b/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/recovery/RecoveryTestUtils.java @@ -34,13 +34,13 @@ import com.swirlds.common.test.fixtures.platform.TestPlatformContextBuilder; import com.swirlds.config.api.Configuration; import com.swirlds.config.extensions.test.fixtures.TestConfigBuilder; +import com.swirlds.platform.event.GossipEvent; import com.swirlds.platform.event.stream.DefaultConsensusEventStream; import com.swirlds.platform.eventhandling.EventConfig_; import com.swirlds.platform.internal.EventImpl; import com.swirlds.platform.recovery.internal.ObjectStreamIterator; import com.swirlds.platform.system.BasicSoftwareVersion; import com.swirlds.platform.system.events.BaseEventHashedData; -import com.swirlds.platform.system.events.BaseEventUnhashedData; import com.swirlds.platform.system.events.ConsensusData; import com.swirlds.platform.system.events.EventConstants; import com.swirlds.platform.system.events.EventDescriptor; @@ -108,8 +108,7 @@ public static EventImpl generateRandomEvent( now, transactions); - final BaseEventUnhashedData baseEventUnhashedData = - new BaseEventUnhashedData(randomSignature(random).getSignatureBytes()); + final byte[] signature = randomSignature(random).getSignatureBytes(); final ConsensusData consensusData = new ConsensusData(); consensusData.setConsensusTimestamp(now); @@ -117,7 +116,8 @@ public static EventImpl generateRandomEvent( consensusData.setConsensusOrder(random.nextLong()); consensusData.setLastInRoundReceived(lastInRound); - final EventImpl event = new EventImpl(baseEventHashedData, baseEventUnhashedData, consensusData); + final EventImpl event = + new EventImpl(new GossipEvent(baseEventHashedData, signature), consensusData, null, null); event.setRoundCreated(random.nextLong()); event.setStale(random.nextBoolean()); return event; diff --git a/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/roster/RosterDiffGeneratorTests.java b/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/roster/RosterDiffGeneratorTests.java index c0a24213722e..779ddd23a062 100644 --- a/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/roster/RosterDiffGeneratorTests.java +++ b/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/roster/RosterDiffGeneratorTests.java @@ -31,7 +31,8 @@ import com.swirlds.common.test.fixtures.platform.TestPlatformContextBuilder; import com.swirlds.platform.system.address.Address; import com.swirlds.platform.system.address.AddressBook; -import com.swirlds.platform.test.fixtures.addressbook.RandomAddressBookGenerator; +import com.swirlds.platform.test.fixtures.addressbook.RandomAddressBookBuilder; +import com.swirlds.platform.test.fixtures.addressbook.RandomAddressBuilder; import java.util.ArrayList; import java.util.HashSet; import java.util.List; @@ -52,7 +53,7 @@ void noChangesTest() { TestPlatformContextBuilder.create().build(); final RosterDiffGenerator generator = new RosterDiffGenerator(platformContext); - final AddressBook roster = new RandomAddressBookGenerator(random).build(); + final AddressBook roster = RandomAddressBookBuilder.create(random).build(); roster.setHash(platformContext.getCryptography().digestSync(roster)); // First round added should yield a null diff @@ -84,7 +85,7 @@ void randomChangesTest() { final RosterDiffGenerator generator = new RosterDiffGenerator(platformContext); AddressBook previousRoster = - new RandomAddressBookGenerator(random).setSize(8).build(); + RandomAddressBookBuilder.create(random).withSize(8).build(); previousRoster.setHash(platformContext.getCryptography().digestSync(previousRoster)); assertNull(generator.generateDiff(new UpdatedRoster(0, previousRoster))); @@ -149,9 +150,9 @@ void randomChangesTest() { final NodeId nodeToAdd = newRoster.getNextNodeId(); addedNodes.add(nodeToAdd); - final Address address = RandomAddressBookGenerator.addressWithRandomData( - random, nodeToAdd, random.nextLong(1, 100_000_000)); - + final Address address = RandomAddressBuilder.create(random) + .withNodeId(nodeToAdd) + .build(); newRoster.add(address); } diff --git a/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/roster/legacy/AddressBookRosterTests.java b/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/roster/legacy/AddressBookRosterTests.java index 6dce97b11d03..eeb678092fbf 100644 --- a/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/roster/legacy/AddressBookRosterTests.java +++ b/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/roster/legacy/AddressBookRosterTests.java @@ -24,11 +24,12 @@ import com.swirlds.common.io.streams.SerializableDataInputStream; import com.swirlds.common.io.streams.SerializableDataOutputStream; import com.swirlds.common.platform.NodeId; +import com.swirlds.common.test.fixtures.Randotron; import com.swirlds.platform.roster.Roster; import com.swirlds.platform.roster.RosterEntry; import com.swirlds.platform.system.address.Address; import com.swirlds.platform.system.address.AddressBook; -import com.swirlds.platform.test.fixtures.addressbook.RandomAddressBookGenerator; +import com.swirlds.platform.test.fixtures.addressbook.RandomAddressBookBuilder; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; @@ -41,8 +42,9 @@ public class AddressBookRosterTests { @Test @DisplayName("Serialize and deserialize AddressBook derived Roster") void serializeDeserializeTest() throws IOException, ConstructableRegistryException { + final Randotron randotron = Randotron.create(); final AddressBook addressBook = - new RandomAddressBookGenerator().setSize(100).build(); + RandomAddressBookBuilder.create(randotron).withSize(100).build(); ConstructableRegistry.getInstance().registerConstructables("com.swirlds"); final AddressBookRoster roster = new AddressBookRoster(addressBook); @@ -62,8 +64,9 @@ void serializeDeserializeTest() throws IOException, ConstructableRegistryExcepti @Test @DisplayName("Roster derived from AddressBook") void addressBookRosterTest() { + final Randotron randotron = Randotron.create(); final AddressBook addressBook = - new RandomAddressBookGenerator().setSize(100).build(); + RandomAddressBookBuilder.create(randotron).withSize(100).build(); final Roster roster = new AddressBookRoster(addressBook); final Iterator entries = roster.iterator(); for (int i = 0; i < addressBook.getSize(); i++) { @@ -82,8 +85,9 @@ void addressBookRosterTest() { @Test @DisplayName("Serialize and deserialize AddressBook derived Roster") void serializeDeserializeEntryTest() throws IOException, ConstructableRegistryException { + final Randotron randotron = Randotron.create(); final AddressBook addressBook = - new RandomAddressBookGenerator().setSize(100).build(); + RandomAddressBookBuilder.create(randotron).withSize(100).build(); ConstructableRegistry.getInstance().registerConstructables("com.swirlds"); final Roster roster = new AddressBookRoster(addressBook); diff --git a/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/state/RandomSignedStateGenerator.java b/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/state/RandomSignedStateGenerator.java index f41776bc18fc..3f3a8a137d66 100644 --- a/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/state/RandomSignedStateGenerator.java +++ b/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/state/RandomSignedStateGenerator.java @@ -37,7 +37,7 @@ import com.swirlds.platform.system.BasicSoftwareVersion; import com.swirlds.platform.system.SoftwareVersion; import com.swirlds.platform.system.address.AddressBook; -import com.swirlds.platform.test.fixtures.addressbook.RandomAddressBookGenerator; +import com.swirlds.platform.test.fixtures.addressbook.RandomAddressBookBuilder; import com.swirlds.platform.test.fixtures.state.DummySwirldState; import edu.umd.cs.findbugs.annotations.NonNull; import java.time.Instant; @@ -61,7 +61,6 @@ public class RandomSignedStateGenerator { private State state; private Long round; private Hash legacyRunningEventHash; - private Hash runningEventHash; private AddressBook addressBook; private Instant consensusTimestamp; private Boolean freezeState = false; @@ -105,9 +104,8 @@ public RandomSignedStateGenerator(final Random random) { public SignedState build() { final AddressBook addressBookInstance; if (addressBook == null) { - addressBookInstance = new RandomAddressBookGenerator(random) - .setWeightDistributionStrategy(RandomAddressBookGenerator.WeightDistributionStrategy.BALANCED) - .setHashStrategy(RandomAddressBookGenerator.HashStrategy.REAL_HASH) + addressBookInstance = RandomAddressBookBuilder.create(random) + .withWeightDistributionStrategy(RandomAddressBookBuilder.WeightDistributionStrategy.BALANCED) .build(); } else { addressBookInstance = addressBook; @@ -140,13 +138,6 @@ public SignedState build() { legacyRunningEventHashInstance = legacyRunningEventHash; } - final Hash runningEventHashInstance; - if (runningEventHash == null) { - runningEventHashInstance = randomHash(random); - } else { - runningEventHashInstance = runningEventHash; - } - final Instant consensusTimestampInstance; if (consensusTimestamp == null) { consensusTimestampInstance = RandomUtils.randomInstant(random); @@ -193,7 +184,6 @@ public SignedState build() { platformState.setRound(roundInstance); platformState.setLegacyRunningEventHash(legacyRunningEventHashInstance); - platformState.setRunningEventHash(runningEventHashInstance); platformState.setConsensusTimestamp(consensusTimestampInstance); platformState.setCreationSoftwareVersion(softwareVersionInstance); platformState.setRoundsNonAncient(roundsNonAncientInstance); @@ -316,17 +306,6 @@ public RandomSignedStateGenerator setLegacyRunningEventHash(final Hash legacyRun return this; } - /** - * Set the running hash of all events that have been applied to this state since the last freeze. - * - * @return this object - */ - @NonNull - public RandomSignedStateGenerator setRunningEventHash(final Hash runningEventHash) { - this.runningEventHash = runningEventHash; - return this; - } - /** * Set the address book. * diff --git a/hedera-node/hedera-app-spi/src/test/java/com/hedera/node/app/spi/state/ReadableQueueStateBaseTest.java b/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/state/ReadableQueueStateBaseTest.java similarity index 90% rename from hedera-node/hedera-app-spi/src/test/java/com/hedera/node/app/spi/state/ReadableQueueStateBaseTest.java rename to platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/state/ReadableQueueStateBaseTest.java index 19cc6c11659c..8cf9a0bfd5a0 100644 --- a/hedera-node/hedera-app-spi/src/test/java/com/hedera/node/app/spi/state/ReadableQueueStateBaseTest.java +++ b/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/state/ReadableQueueStateBaseTest.java @@ -14,12 +14,13 @@ * limitations under the License. */ -package com.hedera.node.app.spi.state; +package com.swirlds.platform.state; import static org.assertj.core.api.Assertions.assertThat; -import com.hedera.node.app.spi.fixtures.state.ListReadableQueueState; -import com.hedera.node.app.spi.fixtures.state.ListWritableQueueState; +import com.swirlds.platform.test.fixtures.state.ListReadableQueueState; +import com.swirlds.platform.test.fixtures.state.ListWritableQueueState; +import com.swirlds.platform.test.fixtures.state.StateTestBase; import java.util.ArrayList; import org.junit.jupiter.api.Test; diff --git a/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/state/SignedStateMapTests.java b/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/state/SignedStateMapTests.java deleted file mode 100644 index 507dcb9e3f2a..000000000000 --- a/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/state/SignedStateMapTests.java +++ /dev/null @@ -1,273 +0,0 @@ -/* - * Copyright (C) 2022-2024 Hedera Hashgraph, LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.swirlds.platform.state; - -import static com.swirlds.platform.state.signed.SignedStateMap.NO_STATE_ROUND; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertNull; -import static org.junit.jupiter.api.Assertions.assertSame; -import static org.junit.jupiter.api.Assertions.assertThrows; -import static org.junit.jupiter.api.Assertions.assertTrue; -import static org.mockito.Mockito.doReturn; -import static org.mockito.Mockito.spy; - -import com.swirlds.platform.state.signed.ReservedSignedState; -import com.swirlds.platform.state.signed.SignedState; -import com.swirlds.platform.state.signed.SignedStateMap; -import java.util.concurrent.atomic.AtomicBoolean; -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Test; - -@DisplayName("SignedStateMap Tests") -class SignedStateMapTests { - - @Test - @DisplayName("get() Test") - void getTest() { - - final SignedStateMap map = new SignedStateMap(); - assertEquals(0, map.getSize(), "unexpected size"); - assertEquals(NO_STATE_ROUND, map.getLatestRound()); - assertNull(map.getLatestAndReserve("test").getNullable()); - - final SignedState signedState = spy(SignedStateReferenceTests.buildSignedState()); - final long round = 1234; - doReturn(round).when(signedState).getRound(); - - map.put(signedState, "test"); - assertEquals(1, map.getSize(), "unexpected size"); - try (final ReservedSignedState wrapper = map.getLatestAndReserve("test")) { - assertSame(signedState, wrapper.get()); - } - assertEquals(signedState.getRound(), map.getLatestRound()); - - assertEquals(1, signedState.getReservationCount(), "invalid reference count"); - - ReservedSignedState wrapper; - - // Get a reference to a round that is not in the map - wrapper = map.getAndReserve(0, "test"); - assertNull(wrapper.getNullable()); - wrapper.close(); - - wrapper = map.getAndReserve(0, "test"); - assertNull(wrapper.getNullable()); - wrapper.close(); - - wrapper = map.getAndReserve(round, "test"); - assertSame(signedState, wrapper.get(), "wrapper returned incorrect object"); - assertEquals(2, signedState.getReservationCount(), "invalid reference count"); - wrapper.close(); - assertEquals(1, signedState.getReservationCount(), "invalid reference count"); - - assertEquals(1, map.getSize(), "unexpected size"); - } - - @Test - @DisplayName("remove() Test") - void removeTest() { - final SignedStateMap map = new SignedStateMap(); - assertEquals(0, map.getSize(), "unexpected size"); - assertEquals(NO_STATE_ROUND, map.getLatestRound()); - assertNull(map.getLatestAndReserve("test").getNullable()); - - final SignedState signedState = spy(SignedStateReferenceTests.buildSignedState()); - final long round = 1234; - doReturn(round).when(signedState).getRound(); - - map.put(signedState, "test"); - assertEquals(1, map.getSize(), "unexpected size"); - try (final ReservedSignedState wrapper = map.getLatestAndReserve("test")) { - assertSame(signedState, wrapper.get()); - } - - assertEquals(1, signedState.getReservationCount(), "invalid reference count"); - - // remove an element in the map - map.remove(round); - assertEquals(0, map.getSize(), "unexpected size"); - assertEquals(-1, signedState.getReservationCount(), "invalid reference count"); - assertEquals(NO_STATE_ROUND, map.getLatestRound()); - assertNull(map.getLatestAndReserve("test").getNullable()); - - // remove an element not in the map, should not throw - map.remove(0); - } - - @Test - @DisplayName("replace() Test") - void replaceTest() { - final SignedStateMap map = new SignedStateMap(); - assertEquals(0, map.getSize(), "unexpected size"); - - final SignedState signedState1 = spy(SignedStateReferenceTests.buildSignedState()); - final long round = 1234; - doReturn(round).when(signedState1).getRound(); - - final SignedState signedState2 = spy(SignedStateReferenceTests.buildSignedState()); - doReturn(round).when(signedState2).getRound(); - - map.put(signedState1, "test"); - assertEquals(1, map.getSize(), "unexpected size"); - assertEquals(1, signedState1.getReservationCount(), "invalid reference count"); - assertEquals(0, signedState2.getReservationCount(), "invalid reference count"); - try (final ReservedSignedState wrapper = map.getLatestAndReserve("test")) { - assertSame(signedState1, wrapper.get()); - } - assertEquals(round, map.getLatestRound()); - - map.put(signedState2, "test"); - assertEquals(1, map.getSize(), "unexpected size"); - assertEquals(-1, signedState1.getReservationCount(), "invalid reference count"); - assertEquals(1, signedState2.getReservationCount(), "invalid reference count"); - try (final ReservedSignedState wrapper = map.getLatestAndReserve("test")) { - assertSame(signedState2, wrapper.get()); - } - assertEquals(round, map.getLatestRound()); - } - - @Test - @DisplayName("No Null Values Test") - void noNullValuesTest() { - final SignedStateMap map = new SignedStateMap(); - assertEquals(0, map.getSize(), "unexpected size"); - - assertThrows(NullPointerException.class, () -> map.put(null, ""), "map should reject a null signed state"); - assertEquals(0, map.getSize(), "unexpected size"); - assertEquals(NO_STATE_ROUND, map.getLatestRound()); - assertNull(map.getLatestAndReserve("test").getNullable()); - } - - @Test - @DisplayName("clear() Test") - void clearTest() { - final SignedStateMap map = new SignedStateMap(); - assertEquals(0, map.getSize(), "unexpected size"); - - final SignedState signedState1 = spy(SignedStateReferenceTests.buildSignedState()); - final long round1 = 1234; - doReturn(round1).when(signedState1).getRound(); - - final SignedState signedState2 = spy(SignedStateReferenceTests.buildSignedState()); - final long round2 = 1235; - doReturn(round2).when(signedState2).getRound(); - - final SignedState signedState3 = spy(SignedStateReferenceTests.buildSignedState()); - final long round3 = 1236; - doReturn(round3).when(signedState3).getRound(); - - map.put(signedState1, "test"); - try (final ReservedSignedState wrapper = map.getLatestAndReserve("test")) { - assertSame(signedState1, wrapper.get()); - } - assertEquals(signedState1.getRound(), map.getLatestRound()); - map.put(signedState2, "test"); - try (final ReservedSignedState wrapper = map.getLatestAndReserve("test")) { - assertSame(signedState2, wrapper.get()); - } - assertEquals(signedState2.getRound(), map.getLatestRound()); - map.put(signedState3, "test"); - try (final ReservedSignedState wrapper = map.getLatestAndReserve("test")) { - assertSame(signedState3, wrapper.get()); - } - assertEquals(signedState3.getRound(), map.getLatestRound()); - - assertEquals(3, map.getSize(), "unexpected size"); - assertEquals(1, signedState1.getReservationCount(), "invalid reference count"); - assertEquals(1, signedState2.getReservationCount(), "invalid reference count"); - assertEquals(1, signedState3.getReservationCount(), "invalid reference count"); - - map.clear(); - assertEquals(0, map.getSize(), "unexpected size"); - assertEquals(-1, signedState1.getReservationCount(), "invalid reference count"); - assertEquals(-1, signedState2.getReservationCount(), "invalid reference count"); - assertEquals(-1, signedState3.getReservationCount(), "invalid reference count"); - assertEquals(NO_STATE_ROUND, map.getLatestRound()); - assertNull(map.getLatestAndReserve("test").getNullable()); - - assertNull(map.getAndReserve(round1, "test").getNullable(), "state should not be in map"); - assertNull(map.getAndReserve(round2, "test").getNullable(), "state should not be in map"); - assertNull(map.getAndReserve(round3, "test").getNullable(), "state should not be in map"); - assertEquals(0, map.getSize(), "unexpected size"); - assertEquals(-1, signedState1.getReservationCount(), "invalid reference count"); - assertEquals(-1, signedState2.getReservationCount(), "invalid reference count"); - assertEquals(-1, signedState3.getReservationCount(), "invalid reference count"); - } - - @Test - @DisplayName("Iteration Test") - void iterationTest() { - final SignedStateMap map = new SignedStateMap(); - assertEquals(0, map.getSize(), "unexpected size"); - - final SignedState signedState1 = spy(SignedStateReferenceTests.buildSignedState()); - final long round1 = 1234; - doReturn(round1).when(signedState1).getRound(); - - final SignedState signedState2 = spy(SignedStateReferenceTests.buildSignedState()); - final long round2 = 1235; - doReturn(round2).when(signedState2).getRound(); - - final SignedState signedState3 = spy(SignedStateReferenceTests.buildSignedState()); - final long round3 = 1236; - doReturn(round3).when(signedState2).getRound(); - - map.put(signedState1, "test"); - map.put(signedState2, "test"); - map.put(signedState3, "test"); - assertEquals(3, map.getSize(), "unexpected size"); - assertEquals(1, signedState1.getReservationCount(), "invalid reference count"); - assertEquals(1, signedState2.getReservationCount(), "invalid reference count"); - assertEquals(1, signedState3.getReservationCount(), "invalid reference count"); - - final AtomicBoolean state1Found = new AtomicBoolean(); - final AtomicBoolean state2Found = new AtomicBoolean(); - final AtomicBoolean state3Found = new AtomicBoolean(); - map.atomicIteration(iterator -> iterator.forEachRemaining(state -> { - if (state == signedState1) { - assertFalse(state1Found.get(), "should only encounter state once"); - state1Found.set(true); - } - if (state == signedState2) { - assertFalse(state2Found.get(), "should only encounter state once"); - state2Found.set(true); - } - if (state == signedState3) { - assertFalse(state3Found.get(), "should only encounter state once"); - state3Found.set(true); - } - })); - assertTrue(state1Found.get(), "state not found"); - assertTrue(state2Found.get(), "state not found"); - assertTrue(state3Found.get(), "state not found"); - assertEquals(3, map.getSize(), "unexpected size"); - assertEquals(1, signedState1.getReservationCount(), "invalid reference count"); - assertEquals(1, signedState2.getReservationCount(), "invalid reference count"); - assertEquals(1, signedState3.getReservationCount(), "invalid reference count"); - - map.atomicIteration(iterator -> iterator.forEachRemaining(state -> { - if (state == signedState2) { - iterator.remove(); - } - })); - assertEquals(2, map.getSize(), "unexpected size"); - assertEquals(1, signedState1.getReservationCount(), "invalid reference count"); - assertEquals(-1, signedState2.getReservationCount(), "invalid reference count"); - assertEquals(1, signedState3.getReservationCount(), "invalid reference count"); - } -} diff --git a/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/state/StateSignatureCollectorTester.java b/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/state/StateSignatureCollectorTester.java index b59b8269c733..a2302469dcc7 100644 --- a/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/state/StateSignatureCollectorTester.java +++ b/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/state/StateSignatureCollectorTester.java @@ -17,19 +17,18 @@ package com.swirlds.platform.state; import com.hedera.hapi.platform.event.StateSignaturePayload; -import com.swirlds.common.metrics.noop.NoOpMetrics; +import com.swirlds.common.context.PlatformContext; import com.swirlds.common.platform.NodeId; import com.swirlds.platform.components.state.output.StateHasEnoughSignaturesConsumer; import com.swirlds.platform.components.state.output.StateLacksSignaturesConsumer; import com.swirlds.platform.components.transaction.system.ScopedSystemTransaction; -import com.swirlds.platform.config.StateConfig; import com.swirlds.platform.consensus.EventWindow; import com.swirlds.platform.event.AncientMode; import com.swirlds.platform.state.nexus.DefaultLatestCompleteStateNexus; import com.swirlds.platform.state.nexus.LatestCompleteStateNexus; +import com.swirlds.platform.state.signed.DefaultStateSignatureCollector; import com.swirlds.platform.state.signed.ReservedSignedState; import com.swirlds.platform.state.signed.SignedStateMetrics; -import com.swirlds.platform.state.signed.StateSignatureCollector; import edu.umd.cs.findbugs.annotations.NonNull; import edu.umd.cs.findbugs.annotations.Nullable; import java.util.List; @@ -39,32 +38,31 @@ * A StateSignatureCollector that is used for unit testing. In the future, these unit tests should become small * integration tests that test multiple components, this class should be removed once we have achieved that. */ -public class StateSignatureCollectorTester extends StateSignatureCollector { +public class StateSignatureCollectorTester extends DefaultStateSignatureCollector { private final LatestCompleteStateNexus latestSignedState; private final StateHasEnoughSignaturesConsumer stateHasEnoughSignaturesConsumer; private final StateLacksSignaturesConsumer stateLacksSignaturesConsumer; private StateSignatureCollectorTester( - @NonNull final StateConfig stateConfig, + @NonNull final PlatformContext platformContext, @NonNull final SignedStateMetrics signedStateMetrics, @NonNull final LatestCompleteStateNexus latestSignedState, @NonNull final StateHasEnoughSignaturesConsumer stateHasEnoughSignaturesConsumer, @NonNull final StateLacksSignaturesConsumer stateLacksSignaturesConsumer) { - super(stateConfig, signedStateMetrics); + super(platformContext, signedStateMetrics); this.latestSignedState = latestSignedState; this.stateHasEnoughSignaturesConsumer = stateHasEnoughSignaturesConsumer; this.stateLacksSignaturesConsumer = stateLacksSignaturesConsumer; } public static StateSignatureCollectorTester create( - @NonNull final StateConfig stateConfig, + @NonNull final PlatformContext platformContext, @NonNull final SignedStateMetrics signedStateMetrics, @NonNull final StateHasEnoughSignaturesConsumer stateHasEnoughSignaturesConsumer, @NonNull final StateLacksSignaturesConsumer stateLacksSignaturesConsumer) { - final LatestCompleteStateNexus latestSignedState = - new DefaultLatestCompleteStateNexus(stateConfig, new NoOpMetrics()); + final LatestCompleteStateNexus latestSignedState = new DefaultLatestCompleteStateNexus(platformContext); return new StateSignatureCollectorTester( - stateConfig, + platformContext, signedStateMetrics, latestSignedState, stateHasEnoughSignaturesConsumer, diff --git a/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/state/StateSigningTests.java b/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/state/StateSigningTests.java index 38774e201b86..5d6bcd4aa7f3 100644 --- a/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/state/StateSigningTests.java +++ b/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/state/StateSigningTests.java @@ -40,7 +40,7 @@ import com.swirlds.platform.state.signed.SignedStateInvalidException; import com.swirlds.platform.system.address.Address; import com.swirlds.platform.system.address.AddressBook; -import com.swirlds.platform.test.fixtures.addressbook.RandomAddressBookGenerator; +import com.swirlds.platform.test.fixtures.addressbook.RandomAddressBookBuilder; import edu.umd.cs.findbugs.annotations.NonNull; import java.security.PublicKey; import java.security.cert.X509Certificate; @@ -67,12 +67,12 @@ void addValidSignaturesTest(final boolean evenWeighting) { final int nodeCount = random.nextInt(10, 20); - final AddressBook addressBook = new RandomAddressBookGenerator(random) - .setWeightDistributionStrategy( + final AddressBook addressBook = RandomAddressBookBuilder.create(random) + .withWeightDistributionStrategy( evenWeighting - ? RandomAddressBookGenerator.WeightDistributionStrategy.BALANCED - : RandomAddressBookGenerator.WeightDistributionStrategy.GAUSSIAN) - .setSize(nodeCount) + ? RandomAddressBookBuilder.WeightDistributionStrategy.BALANCED + : RandomAddressBookBuilder.WeightDistributionStrategy.GAUSSIAN) + .withSize(nodeCount) .build(); final SignedState signedState = new RandomSignedStateGenerator(random) @@ -172,12 +172,12 @@ void addInvalidSignaturesTest(final boolean evenWeighting) { final int nodeCount = random.nextInt(10, 20); - final AddressBook addressBook = new RandomAddressBookGenerator(random) - .setWeightDistributionStrategy( + final AddressBook addressBook = RandomAddressBookBuilder.create(random) + .withWeightDistributionStrategy( evenWeighting - ? RandomAddressBookGenerator.WeightDistributionStrategy.BALANCED - : RandomAddressBookGenerator.WeightDistributionStrategy.GAUSSIAN) - .setSize(nodeCount) + ? RandomAddressBookBuilder.WeightDistributionStrategy.BALANCED + : RandomAddressBookBuilder.WeightDistributionStrategy.GAUSSIAN) + .withSize(nodeCount) .build(); final SignedState signedState = new RandomSignedStateGenerator(random) @@ -269,12 +269,12 @@ void signatureBecomesInvalidTest(final boolean evenWeighting) { final int nodeCount = random.nextInt(10, 20); - final AddressBook addressBook = new RandomAddressBookGenerator(random) - .setWeightDistributionStrategy( + final AddressBook addressBook = RandomAddressBookBuilder.create(random) + .withWeightDistributionStrategy( evenWeighting - ? RandomAddressBookGenerator.WeightDistributionStrategy.BALANCED - : RandomAddressBookGenerator.WeightDistributionStrategy.GAUSSIAN) - .setSize(nodeCount) + ? RandomAddressBookBuilder.WeightDistributionStrategy.BALANCED + : RandomAddressBookBuilder.WeightDistributionStrategy.GAUSSIAN) + .withSize(nodeCount) .build(); final SignedState signedState = new RandomSignedStateGenerator(random) @@ -350,12 +350,12 @@ void allSignaturesBecomeInvalidTest(final boolean evenWeighting) { final int nodeCount = random.nextInt(10, 20); - final AddressBook addressBook = new RandomAddressBookGenerator(random) - .setWeightDistributionStrategy( + final AddressBook addressBook = RandomAddressBookBuilder.create(random) + .withWeightDistributionStrategy( evenWeighting - ? RandomAddressBookGenerator.WeightDistributionStrategy.BALANCED - : RandomAddressBookGenerator.WeightDistributionStrategy.GAUSSIAN) - .setSize(nodeCount) + ? RandomAddressBookBuilder.WeightDistributionStrategy.BALANCED + : RandomAddressBookBuilder.WeightDistributionStrategy.GAUSSIAN) + .withSize(nodeCount) .build(); final SignedState signedState = new RandomSignedStateGenerator(random) @@ -401,12 +401,12 @@ void signaturesInvalidWithDifferentAddressBookTest(final boolean evenWeighting) final int nodeCount = random.nextInt(10, 20); - final AddressBook addressBook = new RandomAddressBookGenerator(random) - .setWeightDistributionStrategy( + final AddressBook addressBook = RandomAddressBookBuilder.create(random) + .withWeightDistributionStrategy( evenWeighting - ? RandomAddressBookGenerator.WeightDistributionStrategy.BALANCED - : RandomAddressBookGenerator.WeightDistributionStrategy.GAUSSIAN) - .setSize(nodeCount) + ? RandomAddressBookBuilder.WeightDistributionStrategy.BALANCED + : RandomAddressBookBuilder.WeightDistributionStrategy.GAUSSIAN) + .withSize(nodeCount) .build(); final SignedState signedState = new RandomSignedStateGenerator(random) @@ -462,12 +462,12 @@ void signaturesInvalidDueToZeroWeightTest(final boolean evenWeighting) { final int nodeCount = random.nextInt(10, 20); - final AddressBook addressBook = new RandomAddressBookGenerator(random) - .setWeightDistributionStrategy( + final AddressBook addressBook = RandomAddressBookBuilder.create(random) + .withWeightDistributionStrategy( evenWeighting - ? RandomAddressBookGenerator.WeightDistributionStrategy.BALANCED - : RandomAddressBookGenerator.WeightDistributionStrategy.GAUSSIAN) - .setSize(nodeCount) + ? RandomAddressBookBuilder.WeightDistributionStrategy.BALANCED + : RandomAddressBookBuilder.WeightDistributionStrategy.GAUSSIAN) + .withSize(nodeCount) .build(); // set node to zero weight @@ -526,12 +526,12 @@ void recoveryStateIsCompleteTest(final boolean evenWeighting) { final int nodeCount = random.nextInt(10, 20); - final AddressBook addressBook = new RandomAddressBookGenerator(random) - .setWeightDistributionStrategy( + final AddressBook addressBook = RandomAddressBookBuilder.create(random) + .withWeightDistributionStrategy( evenWeighting - ? RandomAddressBookGenerator.WeightDistributionStrategy.BALANCED - : RandomAddressBookGenerator.WeightDistributionStrategy.GAUSSIAN) - .setSize(nodeCount) + ? RandomAddressBookBuilder.WeightDistributionStrategy.BALANCED + : RandomAddressBookBuilder.WeightDistributionStrategy.GAUSSIAN) + .withSize(nodeCount) .build(); final SignedState signedState = new RandomSignedStateGenerator(random) diff --git a/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/state/SwirldStateManagerTests.java b/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/state/SwirldStateManagerTests.java index edf1b87a1362..a7a96e0ff29b 100644 --- a/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/state/SwirldStateManagerTests.java +++ b/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/state/SwirldStateManagerTests.java @@ -22,14 +22,14 @@ import com.swirlds.common.context.PlatformContext; import com.swirlds.common.platform.NodeId; +import com.swirlds.common.test.fixtures.Randotron; import com.swirlds.common.test.fixtures.platform.TestPlatformContextBuilder; import com.swirlds.platform.SwirldsPlatform; -import com.swirlds.platform.metrics.SwirldStateMetrics; import com.swirlds.platform.state.signed.SignedState; import com.swirlds.platform.system.BasicSoftwareVersion; import com.swirlds.platform.system.address.AddressBook; import com.swirlds.platform.system.status.StatusActionSubmitter; -import com.swirlds.platform.test.fixtures.addressbook.RandomAddressBookGenerator; +import com.swirlds.platform.test.fixtures.addressbook.RandomAddressBookBuilder; import com.swirlds.platform.test.fixtures.state.DummySwirldState; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; @@ -43,7 +43,8 @@ class SwirldStateManagerTests { @BeforeEach void setup() { final SwirldsPlatform platform = mock(SwirldsPlatform.class); - final AddressBook addressBook = new RandomAddressBookGenerator().build(); + final AddressBook addressBook = + RandomAddressBookBuilder.create(Randotron.create()).build(); when(platform.getAddressBook()).thenReturn(addressBook); initialState = newState(); final PlatformContext platformContext = @@ -53,10 +54,9 @@ void setup() { platformContext, addressBook, new NodeId(0L), - mock(SwirldStateMetrics.class), mock(StatusActionSubmitter.class), - initialState, new BasicSoftwareVersion(1)); + swirldStateManager.setInitialState(initialState); } @Test diff --git a/hedera-node/hedera-app-spi/src/test/java/com/hedera/node/app/spi/state/WritableQueueStateBaseTest.java b/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/state/WritableQueueStateBaseTest.java similarity index 99% rename from hedera-node/hedera-app-spi/src/test/java/com/hedera/node/app/spi/state/WritableQueueStateBaseTest.java rename to platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/state/WritableQueueStateBaseTest.java index 44bd1a1083a2..1d2748ecd1d7 100644 --- a/hedera-node/hedera-app-spi/src/test/java/com/hedera/node/app/spi/state/WritableQueueStateBaseTest.java +++ b/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/state/WritableQueueStateBaseTest.java @@ -14,12 +14,12 @@ * limitations under the License. */ -package com.hedera.node.app.spi.state; +package com.swirlds.platform.state; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; -import com.hedera.node.app.spi.fixtures.state.ListWritableQueueState; +import com.swirlds.platform.test.fixtures.state.ListWritableQueueState; import java.util.ConcurrentModificationException; import java.util.LinkedList; import org.junit.jupiter.api.Nested; diff --git a/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/util/HashLoggerTest.java b/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/state/hashlogger/HashLoggerTest.java similarity index 85% rename from platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/util/HashLoggerTest.java rename to platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/state/hashlogger/HashLoggerTest.java index 164da0870ea4..ffda5b6586fa 100644 --- a/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/util/HashLoggerTest.java +++ b/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/state/hashlogger/HashLoggerTest.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.swirlds.platform.util; +package com.swirlds.platform.state.hashlogger; import static com.swirlds.logging.legacy.LogMarker.STATE_HASH; import static org.assertj.core.api.Assertions.assertThat; @@ -26,12 +26,15 @@ import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; +import com.swirlds.common.context.PlatformContext; import com.swirlds.common.merkle.MerkleNode; import com.swirlds.common.merkle.crypto.MerkleCryptoFactory; import com.swirlds.common.test.fixtures.junit.tags.TestQualifierTags; import com.swirlds.common.test.fixtures.merkle.util.MerkleTestUtils; +import com.swirlds.common.test.fixtures.platform.TestPlatformContextBuilder; +import com.swirlds.config.api.Configuration; import com.swirlds.config.extensions.test.fixtures.TestConfigBuilder; -import com.swirlds.platform.config.StateConfig; +import com.swirlds.platform.config.StateConfig_; import com.swirlds.platform.state.PlatformState; import com.swirlds.platform.state.State; import com.swirlds.platform.state.signed.ReservedSignedState; @@ -63,9 +66,11 @@ private String getRoundEqualsRegex(final long round) { @BeforeEach public void setUp() { mockLogger = mock(Logger.class); - final StateConfig stateConfig = - new TestConfigBuilder().getOrCreateConfig().getConfigData(StateConfig.class); - hashLogger = new HashLogger(stateConfig, mockLogger); + + final PlatformContext platformContext = + TestPlatformContextBuilder.create().build(); + + hashLogger = new DefaultHashLogger(platformContext, mockLogger); logged = new ArrayList<>(); doAnswer(invocation -> { @@ -119,23 +124,25 @@ public void loggingWithGapsAddsExtraWarning() { @Test public void noLoggingWhenDisabled() { - final StateConfig stateConfig = new TestConfigBuilder() - .withValue("state.enableHashStreamLogging", "false") - .getOrCreateConfig() - .getConfigData(StateConfig.class); - - hashLogger = new HashLogger(stateConfig, mockLogger); + final Configuration configuration = new TestConfigBuilder() + .withValue(StateConfig_.ENABLE_HASH_STREAM_LOGGING, false) + .getOrCreateConfig(); + final PlatformContext platformContext = TestPlatformContextBuilder.create() + .withConfiguration(configuration) + .build(); + + hashLogger = new DefaultHashLogger(platformContext, mockLogger); hashLogger.logHashes(createSignedState(1)); assertThat(logged).isEmpty(); } @Test public void loggerWithDefaultConstructorWorks() { - final StateConfig stateConfig = - new TestConfigBuilder().getOrCreateConfig().getConfigData(StateConfig.class); + final PlatformContext platformContext = + TestPlatformContextBuilder.create().build(); assertDoesNotThrow(() -> { - hashLogger = new HashLogger(stateConfig); + hashLogger = new DefaultHashLogger(platformContext); hashLogger.logHashes(createSignedState(1)); }); } diff --git a/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/state/manager/AbstractStateSignatureCollectorTest.java b/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/state/manager/AbstractStateSignatureCollectorTest.java index 87dcb96943c6..3661d1dbd64b 100644 --- a/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/state/manager/AbstractStateSignatureCollectorTest.java +++ b/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/state/manager/AbstractStateSignatureCollectorTest.java @@ -27,7 +27,6 @@ import com.swirlds.common.platform.NodeId; import com.swirlds.config.api.Configuration; import com.swirlds.config.extensions.test.fixtures.TestConfigBuilder; -import com.swirlds.platform.config.StateConfig; import com.swirlds.platform.config.StateConfig_; import com.swirlds.platform.state.StateSignatureCollectorTester; import com.swirlds.platform.state.signed.SignedState; @@ -66,14 +65,13 @@ public class AbstractStateSignatureCollectorTest { */ protected final AtomicBoolean error = new AtomicBoolean(false); - protected StateConfig buildStateConfig() { - final Configuration configuration = new TestConfigBuilder() + @NonNull + protected Configuration buildStateConfig() { + return new TestConfigBuilder() .withValue(StateConfig_.ROUNDS_TO_KEEP_FOR_SIGNING, roundsToKeepForSigning) .withValue(StateConfig_.MAX_AGE_OF_FUTURE_STATE_SIGNATURES, futureStateSignatureRounds) .withValue(StateConfig_.ROUNDS_TO_KEEP_AFTER_SIGNING, roundsToKeepAfterSigning) .getOrCreateConfig(); - - return configuration.getConfigData(StateConfig.class); } @AfterEach diff --git a/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/state/manager/AddIncompleteStateTest.java b/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/state/manager/AddIncompleteStateTest.java index 5b3019d9b13d..0db5eec10876 100644 --- a/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/state/manager/AddIncompleteStateTest.java +++ b/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/state/manager/AddIncompleteStateTest.java @@ -21,9 +21,11 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNull; +import com.swirlds.common.context.PlatformContext; import com.swirlds.common.crypto.Hash; import com.swirlds.common.crypto.Signature; import com.swirlds.common.platform.NodeId; +import com.swirlds.common.test.fixtures.platform.TestPlatformContextBuilder; import com.swirlds.platform.components.state.output.StateHasEnoughSignaturesConsumer; import com.swirlds.platform.components.state.output.StateLacksSignaturesConsumer; import com.swirlds.platform.state.RandomSignedStateGenerator; @@ -31,7 +33,7 @@ import com.swirlds.platform.state.signed.SignedState; import com.swirlds.platform.system.address.Address; import com.swirlds.platform.system.address.AddressBook; -import com.swirlds.platform.test.fixtures.addressbook.RandomAddressBookGenerator; +import com.swirlds.platform.test.fixtures.addressbook.RandomAddressBookBuilder; import java.util.HashMap; import java.util.Map; import org.junit.jupiter.api.DisplayName; @@ -47,9 +49,9 @@ class AddIncompleteStateTest extends AbstractStateSignatureCollectorTest { private final int roundAgeToSign = 3; - private final AddressBook addressBook = new RandomAddressBookGenerator(random) - .setSize(4) - .setWeightDistributionStrategy(RandomAddressBookGenerator.WeightDistributionStrategy.BALANCED) + private final AddressBook addressBook = RandomAddressBookBuilder.create(random) + .withSize(4) + .withWeightDistributionStrategy(RandomAddressBookBuilder.WeightDistributionStrategy.BALANCED) .build(); /** @@ -79,7 +81,10 @@ private StateHasEnoughSignaturesConsumer stateHasEnoughSignaturesConsumer() { @DisplayName("Add Incomplete State Test") void addIncompleteStateTest() { - final StateSignatureCollectorTester manager = new StateSignatureCollectorBuilder(buildStateConfig()) + final PlatformContext platformContext = TestPlatformContextBuilder.create() + .withConfiguration(buildStateConfig()) + .build(); + final StateSignatureCollectorTester manager = new StateSignatureCollectorBuilder(platformContext) .stateLacksSignaturesConsumer(stateLacksSignaturesConsumer()) .stateHasEnoughSignaturesConsumer(stateHasEnoughSignaturesConsumer()) .build(); diff --git a/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/state/manager/EarlySignaturesTest.java b/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/state/manager/EarlySignaturesTest.java index f7e9e68f0756..5b95f4bba1c4 100644 --- a/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/state/manager/EarlySignaturesTest.java +++ b/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/state/manager/EarlySignaturesTest.java @@ -21,6 +21,8 @@ import static org.junit.jupiter.api.Assertions.assertSame; import com.hedera.hapi.platform.event.StateSignaturePayload; +import com.swirlds.common.context.PlatformContext; +import com.swirlds.common.test.fixtures.platform.TestPlatformContextBuilder; import com.swirlds.platform.components.state.output.StateHasEnoughSignaturesConsumer; import com.swirlds.platform.components.state.output.StateLacksSignaturesConsumer; import com.swirlds.platform.config.StateConfig; @@ -29,7 +31,7 @@ import com.swirlds.platform.state.signed.ReservedSignedState; import com.swirlds.platform.state.signed.SignedState; import com.swirlds.platform.system.address.AddressBook; -import com.swirlds.platform.test.fixtures.addressbook.RandomAddressBookGenerator; +import com.swirlds.platform.test.fixtures.addressbook.RandomAddressBookBuilder; import java.util.ArrayList; import java.util.HashMap; import java.util.List; @@ -46,9 +48,9 @@ public class EarlySignaturesTest extends AbstractStateSignatureCollectorTest { private final int roundAgeToSign = 3; - private final AddressBook addressBook = new RandomAddressBookGenerator(random) - .setSize(4) - .setWeightDistributionStrategy(RandomAddressBookGenerator.WeightDistributionStrategy.BALANCED) + private final AddressBook addressBook = RandomAddressBookBuilder.create(random) + .withSize(4) + .withWeightDistributionStrategy(RandomAddressBookBuilder.WeightDistributionStrategy.BALANCED) .build(); /** @@ -77,9 +79,14 @@ private StateHasEnoughSignaturesConsumer stateHasEnoughSignaturesConsumer() { @DisplayName("Early Signatures Test") void earlySignaturesTest() throws InterruptedException { final int count = 100; - final StateConfig stateConfig = buildStateConfig(); - final int futureSignatures = stateConfig.maxAgeOfFutureStateSignatures(); - final StateSignatureCollectorTester manager = new StateSignatureCollectorBuilder(stateConfig) + final PlatformContext platformContext = TestPlatformContextBuilder.create() + .withConfiguration(buildStateConfig()) + .build(); + final int futureSignatures = platformContext + .getConfiguration() + .getConfigData(StateConfig.class) + .maxAgeOfFutureStateSignatures(); + final StateSignatureCollectorTester manager = new StateSignatureCollectorBuilder(platformContext) .stateLacksSignaturesConsumer(stateLacksSignaturesConsumer()) .stateHasEnoughSignaturesConsumer(stateHasEnoughSignaturesConsumer()) .build(); diff --git a/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/state/manager/OldCompleteStateEventuallyReleasedTest.java b/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/state/manager/OldCompleteStateEventuallyReleasedTest.java index 09d267c30dcb..e0ffd3170bd1 100644 --- a/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/state/manager/OldCompleteStateEventuallyReleasedTest.java +++ b/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/state/manager/OldCompleteStateEventuallyReleasedTest.java @@ -22,9 +22,11 @@ import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertSame; +import com.swirlds.common.context.PlatformContext; import com.swirlds.common.crypto.Hash; import com.swirlds.common.crypto.Signature; import com.swirlds.common.platform.NodeId; +import com.swirlds.common.test.fixtures.platform.TestPlatformContextBuilder; import com.swirlds.platform.components.state.output.StateHasEnoughSignaturesConsumer; import com.swirlds.platform.components.state.output.StateLacksSignaturesConsumer; import com.swirlds.platform.state.RandomSignedStateGenerator; @@ -33,7 +35,7 @@ import com.swirlds.platform.state.signed.SignedState; import com.swirlds.platform.system.address.Address; import com.swirlds.platform.system.address.AddressBook; -import com.swirlds.platform.test.fixtures.addressbook.RandomAddressBookGenerator; +import com.swirlds.platform.test.fixtures.addressbook.RandomAddressBookBuilder; import java.util.HashMap; import java.util.Map; import org.junit.jupiter.api.DisplayName; @@ -48,7 +50,7 @@ class OldCompleteStateEventuallyReleasedTest extends AbstractStateSignatureColle // DO NOT ADD ADDITIONAL UNIT TESTS TO THIS CLASS! private final AddressBook addressBook = - new RandomAddressBookGenerator(random).setSize(4).build(); + RandomAddressBookBuilder.create(random).withSize(4).build(); /** * Called on each state as it gets too old without collecting enough signatures. @@ -76,7 +78,10 @@ private StateHasEnoughSignaturesConsumer stateHasEnoughSignaturesConsumer() { @DisplayName("Old Complete State Eventually Released") void oldCompleteStateEventuallyReleased() throws InterruptedException { - final StateSignatureCollectorTester manager = new StateSignatureCollectorBuilder(buildStateConfig()) + final PlatformContext platformContext = TestPlatformContextBuilder.create() + .withConfiguration(buildStateConfig()) + .build(); + final StateSignatureCollectorTester manager = new StateSignatureCollectorBuilder(platformContext) .stateLacksSignaturesConsumer(stateLacksSignaturesConsumer()) .stateHasEnoughSignaturesConsumer(stateHasEnoughSignaturesConsumer()) .build(); diff --git a/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/state/manager/PostconsensusSignaturesTest.java b/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/state/manager/PostconsensusSignaturesTest.java index 4a83ba9f7450..bd1f02349008 100644 --- a/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/state/manager/PostconsensusSignaturesTest.java +++ b/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/state/manager/PostconsensusSignaturesTest.java @@ -22,16 +22,17 @@ import static org.junit.jupiter.api.Assertions.assertSame; import com.hedera.hapi.platform.event.StateSignaturePayload; +import com.swirlds.common.context.PlatformContext; +import com.swirlds.common.test.fixtures.platform.TestPlatformContextBuilder; import com.swirlds.platform.components.state.output.StateHasEnoughSignaturesConsumer; import com.swirlds.platform.components.state.output.StateLacksSignaturesConsumer; -import com.swirlds.platform.config.StateConfig; import com.swirlds.platform.state.RandomSignedStateGenerator; import com.swirlds.platform.state.StateSignatureCollectorTester; +import com.swirlds.platform.state.signed.DefaultStateSignatureCollector; import com.swirlds.platform.state.signed.ReservedSignedState; import com.swirlds.platform.state.signed.SignedState; -import com.swirlds.platform.state.signed.StateSignatureCollector; import com.swirlds.platform.system.address.AddressBook; -import com.swirlds.platform.test.fixtures.addressbook.RandomAddressBookGenerator; +import com.swirlds.platform.test.fixtures.addressbook.RandomAddressBookBuilder; import java.util.ArrayList; import java.util.HashMap; import java.util.List; @@ -39,13 +40,13 @@ import org.junit.jupiter.api.Test; /** - * Tests for {@link StateSignatureCollector#handlePostconsensusSignatures(List)} + * Tests for {@link DefaultStateSignatureCollector#handlePostconsensusSignatures(List)} */ class PostconsensusSignaturesTest extends AbstractStateSignatureCollectorTest { - private final AddressBook addressBook = new RandomAddressBookGenerator(random) - .setSize(4) - .setWeightDistributionStrategy(RandomAddressBookGenerator.WeightDistributionStrategy.BALANCED) + private final AddressBook addressBook = RandomAddressBookBuilder.create(random) + .withSize(4) + .withWeightDistributionStrategy(RandomAddressBookBuilder.WeightDistributionStrategy.BALANCED) .build(); /** @@ -72,9 +73,11 @@ private StateHasEnoughSignaturesConsumer stateHasEnoughSignaturesConsumer() { @DisplayName("Postconsensus signatures") void postconsensusSignatureTests() throws InterruptedException { final int count = 100; - final StateConfig stateConfig = buildStateConfig(); + final PlatformContext platformContext = TestPlatformContextBuilder.create() + .withConfiguration(buildStateConfig()) + .build(); - final StateSignatureCollectorTester manager = new StateSignatureCollectorBuilder(stateConfig) + final StateSignatureCollectorTester manager = new StateSignatureCollectorBuilder(platformContext) .stateLacksSignaturesConsumer(stateLacksSignaturesConsumer()) .stateHasEnoughSignaturesConsumer(stateHasEnoughSignaturesConsumer()) .build(); diff --git a/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/state/manager/RegisterStatesWithoutSignaturesTest.java b/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/state/manager/RegisterStatesWithoutSignaturesTest.java index 7267d4250986..d1ab99a54abf 100644 --- a/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/state/manager/RegisterStatesWithoutSignaturesTest.java +++ b/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/state/manager/RegisterStatesWithoutSignaturesTest.java @@ -21,6 +21,8 @@ import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertSame; +import com.swirlds.common.context.PlatformContext; +import com.swirlds.common.test.fixtures.platform.TestPlatformContextBuilder; import com.swirlds.platform.components.state.output.StateHasEnoughSignaturesConsumer; import com.swirlds.platform.components.state.output.StateLacksSignaturesConsumer; import com.swirlds.platform.state.RandomSignedStateGenerator; @@ -28,7 +30,7 @@ import com.swirlds.platform.state.signed.ReservedSignedState; import com.swirlds.platform.state.signed.SignedState; import com.swirlds.platform.system.address.AddressBook; -import com.swirlds.platform.test.fixtures.addressbook.RandomAddressBookGenerator; +import com.swirlds.platform.test.fixtures.addressbook.RandomAddressBookBuilder; import java.util.HashMap; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; @@ -42,7 +44,7 @@ public class RegisterStatesWithoutSignaturesTest extends AbstractStateSignatureC // DO NOT ADD ADDITIONAL UNIT TESTS TO THIS CLASS! private final AddressBook addressBook = - new RandomAddressBookGenerator(random).setSize(4).build(); + RandomAddressBookBuilder.create(random).withSize(4).build(); /** * Called on each state as it gets too old without collecting enough signatures. @@ -77,7 +79,10 @@ private StateHasEnoughSignaturesConsumer stateHasEnoughSignaturesConsumer() { @Test @DisplayName("Register States Without Signatures") void registerStatesWithoutSignatures() throws InterruptedException { - final StateSignatureCollectorTester manager = new StateSignatureCollectorBuilder(buildStateConfig()) + final PlatformContext platformContext = TestPlatformContextBuilder.create() + .withConfiguration(buildStateConfig()) + .build(); + final StateSignatureCollectorTester manager = new StateSignatureCollectorBuilder(platformContext) .stateLacksSignaturesConsumer(stateLacksSignaturesConsumer()) .stateHasEnoughSignaturesConsumer(stateHasEnoughSignaturesConsumer()) .build(); diff --git a/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/state/manager/SequentialSignaturesRestartTest.java b/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/state/manager/SequentialSignaturesRestartTest.java index 8e313a4703ae..0a37a48fbea2 100644 --- a/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/state/manager/SequentialSignaturesRestartTest.java +++ b/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/state/manager/SequentialSignaturesRestartTest.java @@ -23,9 +23,11 @@ import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertSame; +import com.swirlds.common.context.PlatformContext; import com.swirlds.common.crypto.Hash; import com.swirlds.common.crypto.Signature; import com.swirlds.common.platform.NodeId; +import com.swirlds.common.test.fixtures.platform.TestPlatformContextBuilder; import com.swirlds.platform.components.state.output.StateHasEnoughSignaturesConsumer; import com.swirlds.platform.components.state.output.StateLacksSignaturesConsumer; import com.swirlds.platform.state.RandomSignedStateGenerator; @@ -34,7 +36,7 @@ import com.swirlds.platform.state.signed.SignedState; import com.swirlds.platform.system.address.Address; import com.swirlds.platform.system.address.AddressBook; -import com.swirlds.platform.test.fixtures.addressbook.RandomAddressBookGenerator; +import com.swirlds.platform.test.fixtures.addressbook.RandomAddressBookBuilder; import java.util.HashMap; import java.util.Map; import org.junit.jupiter.api.DisplayName; @@ -50,9 +52,9 @@ public class SequentialSignaturesRestartTest extends AbstractStateSignatureColle private final int roundAgeToSign = 3; - private final AddressBook addressBook = new RandomAddressBookGenerator(random) - .setSize(4) - .setWeightDistributionStrategy(RandomAddressBookGenerator.WeightDistributionStrategy.BALANCED) + private final AddressBook addressBook = RandomAddressBookBuilder.create(random) + .withSize(4) + .withWeightDistributionStrategy(RandomAddressBookBuilder.WeightDistributionStrategy.BALANCED) .build(); private final long firstRound = 50; @@ -84,7 +86,10 @@ private StateHasEnoughSignaturesConsumer stateHasEnoughSignaturesConsumer() { @DisplayName("Sequential Signatures After Restart Test") void sequentialSignaturesAfterRestartTest() throws InterruptedException { - final StateSignatureCollectorTester manager = new StateSignatureCollectorBuilder(buildStateConfig()) + final PlatformContext platformContext = TestPlatformContextBuilder.create() + .withConfiguration(buildStateConfig()) + .build(); + final StateSignatureCollectorTester manager = new StateSignatureCollectorBuilder(platformContext) .stateLacksSignaturesConsumer(stateLacksSignaturesConsumer()) .stateHasEnoughSignaturesConsumer(stateHasEnoughSignaturesConsumer()) .build(); diff --git a/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/state/manager/SequentialSignaturesTest.java b/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/state/manager/SequentialSignaturesTest.java index a88c3e460b40..219f0b01f391 100644 --- a/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/state/manager/SequentialSignaturesTest.java +++ b/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/state/manager/SequentialSignaturesTest.java @@ -21,6 +21,8 @@ import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertSame; +import com.swirlds.common.context.PlatformContext; +import com.swirlds.common.test.fixtures.platform.TestPlatformContextBuilder; import com.swirlds.platform.components.state.output.StateHasEnoughSignaturesConsumer; import com.swirlds.platform.components.state.output.StateLacksSignaturesConsumer; import com.swirlds.platform.state.RandomSignedStateGenerator; @@ -28,7 +30,7 @@ import com.swirlds.platform.state.signed.ReservedSignedState; import com.swirlds.platform.state.signed.SignedState; import com.swirlds.platform.system.address.AddressBook; -import com.swirlds.platform.test.fixtures.addressbook.RandomAddressBookGenerator; +import com.swirlds.platform.test.fixtures.addressbook.RandomAddressBookBuilder; import java.util.HashMap; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; @@ -43,9 +45,9 @@ public class SequentialSignaturesTest extends AbstractStateSignatureCollectorTes private final int roundAgeToSign = 3; - private final AddressBook addressBook = new RandomAddressBookGenerator(random) - .setSize(4) - .setWeightDistributionStrategy(RandomAddressBookGenerator.WeightDistributionStrategy.BALANCED) + private final AddressBook addressBook = RandomAddressBookBuilder.create(random) + .withSize(4) + .withWeightDistributionStrategy(RandomAddressBookBuilder.WeightDistributionStrategy.BALANCED) .build(); /** @@ -75,7 +77,10 @@ private StateHasEnoughSignaturesConsumer stateHasEnoughSignaturesConsumer() { @DisplayName("Sequential Signatures Test") void sequentialSignaturesTest() throws InterruptedException { this.roundsToKeepAfterSigning = 4; - final StateSignatureCollectorTester manager = new StateSignatureCollectorBuilder(buildStateConfig()) + final PlatformContext platformContext = TestPlatformContextBuilder.create() + .withConfiguration(buildStateConfig()) + .build(); + final StateSignatureCollectorTester manager = new StateSignatureCollectorBuilder(platformContext) .stateLacksSignaturesConsumer(stateLacksSignaturesConsumer()) .stateHasEnoughSignaturesConsumer(stateHasEnoughSignaturesConsumer()) .build(); diff --git a/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/state/manager/SignatureVerificationTestUtils.java b/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/state/manager/SignatureVerificationTestUtils.java index 6fba3785609b..dde770450fc0 100644 --- a/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/state/manager/SignatureVerificationTestUtils.java +++ b/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/state/manager/SignatureVerificationTestUtils.java @@ -22,7 +22,6 @@ import com.swirlds.common.crypto.SignatureType; import edu.umd.cs.findbugs.annotations.NonNull; import java.security.PublicKey; -import java.util.Arrays; /** * Utility methods for testing signature verification. @@ -34,7 +33,7 @@ public class SignatureVerificationTestUtils { * invalid signature for any other key/hash. */ public static Signature buildFakeSignature(@NonNull final PublicKey key, @NonNull final Hash hash) { - return new Signature(SignatureType.RSA, concat(key.getEncoded(), hash.getValue())); + return new Signature(SignatureType.RSA, concat(key, hash.getBytes()).toByteArray()); } /** @@ -42,20 +41,19 @@ public static Signature buildFakeSignature(@NonNull final PublicKey key, @NonNul * invalid signature for any other key/hash. */ public static Bytes buildFakeSignatureBytes(@NonNull final PublicKey key, @NonNull final Hash hash) { - return Bytes.wrap(concat(key.getEncoded(), hash.getValue())); + return concat(key, hash.getBytes()); } /** * A {@link com.swirlds.platform.crypto.SignatureVerifier} to be used when using signatures built by {@link #buildFakeSignature(PublicKey, Hash)} */ public static boolean verifySignature( - @NonNull final byte[] data, @NonNull final byte[] signature, @NonNull final PublicKey publicKey) { - return Arrays.equals(concat(publicKey.getEncoded(), data), signature); + @NonNull final Bytes data, @NonNull final Bytes signature, @NonNull final PublicKey publicKey) { + return concat(publicKey, data).equals(signature); } - private static byte[] concat(@NonNull final byte[] first, @NonNull final byte[] second) { - final byte[] result = Arrays.copyOf(first, first.length + second.length); - System.arraycopy(second, 0, result, first.length, second.length); - return result; + private static Bytes concat(@NonNull final PublicKey key, @NonNull final Bytes bytes) { + final Bytes keyEncoded = Bytes.wrap(key.getEncoded()); + return keyEncoded.append(bytes); } } diff --git a/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/state/manager/StateSignatureCollectorBuilder.java b/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/state/manager/StateSignatureCollectorBuilder.java index d478fdb2d595..83c7ee9008e3 100644 --- a/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/state/manager/StateSignatureCollectorBuilder.java +++ b/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/state/manager/StateSignatureCollectorBuilder.java @@ -16,27 +16,27 @@ package com.swirlds.platform.state.manager; -import com.swirlds.common.metrics.noop.NoOpMetrics; +import com.swirlds.common.context.PlatformContext; import com.swirlds.platform.components.state.output.StateHasEnoughSignaturesConsumer; import com.swirlds.platform.components.state.output.StateLacksSignaturesConsumer; -import com.swirlds.platform.config.StateConfig; import com.swirlds.platform.state.StateSignatureCollectorTester; import com.swirlds.platform.state.signed.SignedStateMetrics; +import edu.umd.cs.findbugs.annotations.NonNull; /** * Utility class for building instances of {@link StateSignatureCollectorTester}. */ public class StateSignatureCollectorBuilder { - private final StateConfig stateConfig; + private final PlatformContext platformContext; private final SignedStateMetrics metrics; private StateHasEnoughSignaturesConsumer stateHasEnoughSignaturesConsumer = x -> {}; private StateLacksSignaturesConsumer stateLacksSignaturesConsumer = x -> {}; - public StateSignatureCollectorBuilder(final StateConfig stateConfig) { - this.stateConfig = stateConfig; + public StateSignatureCollectorBuilder(@NonNull final PlatformContext platformContext) { + this.platformContext = platformContext; - this.metrics = new SignedStateMetrics(new NoOpMetrics()); + this.metrics = new SignedStateMetrics(platformContext.getMetrics()); } public StateSignatureCollectorBuilder stateHasEnoughSignaturesConsumer( @@ -52,6 +52,6 @@ public StateSignatureCollectorBuilder stateLacksSignaturesConsumer(final StateLa public StateSignatureCollectorTester build() { return StateSignatureCollectorTester.create( - stateConfig, metrics, stateHasEnoughSignaturesConsumer, stateLacksSignaturesConsumer); + platformContext, metrics, stateHasEnoughSignaturesConsumer, stateLacksSignaturesConsumer); } } diff --git a/hedera-node/hedera-app/src/test/java/com/hedera/node/app/state/merkle/ClassLoaderHelper.java b/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/state/merkle/ClassLoaderHelper.java similarity index 98% rename from hedera-node/hedera-app/src/test/java/com/hedera/node/app/state/merkle/ClassLoaderHelper.java rename to platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/state/merkle/ClassLoaderHelper.java index 95b75ed8f54b..190ba0200427 100644 --- a/hedera-node/hedera-app/src/test/java/com/hedera/node/app/state/merkle/ClassLoaderHelper.java +++ b/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/state/merkle/ClassLoaderHelper.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.hedera.node.app.state.merkle; +package com.swirlds.platform.state.merkle; import com.swirlds.common.constructable.ClassConstructorPair; import com.swirlds.common.constructable.ConstructableRegistry; diff --git a/hedera-node/hedera-app/src/test/java/com/hedera/node/app/state/merkle/CryptoConfigUtils.java b/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/state/merkle/CryptoConfigUtils.java similarity index 100% rename from hedera-node/hedera-app/src/test/java/com/hedera/node/app/state/merkle/CryptoConfigUtils.java rename to platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/state/merkle/CryptoConfigUtils.java diff --git a/hedera-node/hedera-app/src/test/java/com/hedera/node/app/state/merkle/StateUtilsTest.java b/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/state/merkle/StateUtilsTest.java similarity index 65% rename from hedera-node/hedera-app/src/test/java/com/hedera/node/app/state/merkle/StateUtilsTest.java rename to platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/state/merkle/StateUtilsTest.java index 27aa7fda9b86..73a55859b1dd 100644 --- a/hedera-node/hedera-app/src/test/java/com/hedera/node/app/state/merkle/StateUtilsTest.java +++ b/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/state/merkle/StateUtilsTest.java @@ -14,24 +14,19 @@ * limitations under the License. */ -package com.hedera.node.app.state.merkle; +package com.swirlds.platform.state.merkle; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.AssertionsForClassTypes.assertThatThrownBy; -import com.hedera.node.app.spi.fixtures.state.TestSchema; -import com.hedera.node.app.spi.state.StateDefinition; -import java.util.*; -import java.util.stream.Stream; +import com.swirlds.platform.test.fixtures.state.merkle.MerkleTestBase; +import java.util.HashSet; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.MethodSource; class StateUtilsTest extends MerkleTestBase { - /** A *static* pseudo-random number generator used to generate the legal identifiers */ - private static final Random RAND = new Random(8892381L); @Test @DisplayName("Validating a null service name throws an NPE") @@ -49,7 +44,7 @@ void emptyServiceNameThrows() { } @ParameterizedTest - @MethodSource("illegalIdentifiers") + @MethodSource("com.swirlds.platform.test.fixtures.state.merkle.TestArgumentUtils#illegalIdentifiers") @DisplayName("Service Names with illegal characters throw an exception") void invalidServiceNameThrows(final String serviceName) { assertThatThrownBy(() -> StateUtils.validateServiceName(serviceName)) @@ -57,7 +52,7 @@ void invalidServiceNameThrows(final String serviceName) { } @ParameterizedTest - @MethodSource("legalIdentifiers") + @MethodSource("com.swirlds.platform.test.fixtures.state.merkle.TestArgumentUtils#legalIdentifiers") @DisplayName("Service names with legal characters are valid") void validServiceNameWorks(final String serviceName) { assertThat(StateUtils.validateServiceName(serviceName)).isEqualTo(serviceName); @@ -79,14 +74,14 @@ void emptyStateKeyThrows() { } @ParameterizedTest - @MethodSource("illegalIdentifiers") + @MethodSource("com.swirlds.platform.test.fixtures.state.merkle.TestArgumentUtils#illegalIdentifiers") @DisplayName("State keys with illegal characters throw an exception") void invalidStateKeyThrows(final String stateKey) { assertThatThrownBy(() -> StateUtils.validateStateKey(stateKey)).isInstanceOf(IllegalArgumentException.class); } @ParameterizedTest - @MethodSource("legalIdentifiers") + @MethodSource("com.swirlds.platform.test.fixtures.state.merkle.TestArgumentUtils#legalIdentifiers") @DisplayName("State keys with legal characters are valid") void validStateKeyWorks(final String stateKey) { assertThat(StateUtils.validateServiceName(stateKey)).isEqualTo(stateKey); @@ -106,7 +101,7 @@ void emptyIdentifierThrows() { } @ParameterizedTest - @MethodSource("illegalIdentifiers") + @MethodSource("com.swirlds.platform.test.fixtures.state.merkle.TestArgumentUtils#illegalIdentifiers") @DisplayName("Identifiers with illegal characters throw an exception") void invalidIdentifierThrows(final String identifier) { assertThatThrownBy(() -> StateUtils.validateIdentifier(identifier)) @@ -114,7 +109,7 @@ void invalidIdentifierThrows(final String identifier) { } @ParameterizedTest - @MethodSource("legalIdentifiers") + @MethodSource("com.swirlds.platform.test.fixtures.state.merkle.TestArgumentUtils#legalIdentifiers") @DisplayName("Identifiers with legal characters are valid") void validIdentifierWorks(final String identifier) { assertThat(StateUtils.validateIdentifier(identifier)).isEqualTo(identifier); @@ -171,18 +166,18 @@ void computeClassId() { @DisplayName("`computeClassId` with metadata is always {serviceName}:{stateKey}:v{version}:{extra}") void computeClassId_withMetadata() { setupFruitMerkleMap(); - final var ver = fruitMetadata.schema().getVersion(); - final var classId = StateUtils.hashString(fruitMetadata.serviceName() + final var classId = StateUtils.hashString(FIRST_SERVICE + ":" - + fruitMetadata.stateDefinition().stateKey() + + FRUIT_STATE_KEY + ":v" - + ver.major() + + TEST_VERSION.major() + "." - + ver.minor() + + TEST_VERSION.minor() + "." - + ver.patch() + + TEST_VERSION.patch() + ":C"); - assertThat(StateUtils.computeClassId(fruitMetadata, "C")).isEqualTo(classId); + assertThat(StateUtils.computeClassId(FIRST_SERVICE, FRUIT_STATE_KEY, TEST_VERSION, "C")) + .isEqualTo(classId); } @Test @@ -197,9 +192,7 @@ void uniqueHashing() { // When I call computeValueClassId with those and collect the resulting hash for (final var serviceName : fakeServiceNames) { for (final var stateKey : fakeStateKeys) { - final var md = new StateMetadata<>( - serviceName, new TestSchema(1), StateDefinition.inMemory(stateKey, STRING_CODEC, STRING_CODEC)); - final var hash = StateUtils.computeClassId(md, "extra string"); + final var hash = StateUtils.computeClassId(serviceName, stateKey, TEST_VERSION, "extra string"); hashes.add(hash); } } @@ -207,68 +200,4 @@ void uniqueHashing() { // Then each hash is highly probabilistically unique (and for our test, definitely unique) assertThat(hashes).hasSize(numWords * numWords); } - - public static Stream illegalIdentifiers() { - // The only valid characters are A-Za-z0-9_-. Any other character is a problem. - // So I will construct three different types of invalid strings. Those that contain only - // an invalid character, those that start with an invalid character and are followed by - // valid characters, and those that start with valid characters and are trailed by a single - // invalid character. I will select invalid characters from the ASCII set, and a subset of - // values that are above the ASCII range. I will make sure to include multibyte characters. - - // Add every ASCII char that is illegal - final List illegalChars = new ArrayList<>(); - for (char i = 0; i < 255; i++) { - if ((i >= 'A' && i <= 'Z') || (i >= 'a' && i <= 'z') || (i >= '0' && i <= '9') || (i == '-' || i == '_')) { - // This is a valid character, so skip it -- don't add it to illegalChars! - continue; - } - illegalChars.add("" + i); - } - // And the illegal 😈 multibyte character - illegalChars.add("\uD83D\uDE08"); - - // Construct the arguments - final List argumentsList = new LinkedList<>(); - - // Add each illegal character on its own as a test case - for (final var illegalChar : illegalChars) { - argumentsList.add(Arguments.of(illegalChar)); - } - - // Add each illegal character as the suffix to an otherwise legal string - for (final var illegalChar : illegalChars) { - argumentsList.add(Arguments.of("Some Legal Characters " + illegalChar)); - } - - // Add each illegal character as the prefix to an otherwise legal string - for (final var illegalChar : illegalChars) { - argumentsList.add(Arguments.of(illegalChar + " Some Legal Characters")); - } - - // return it - return argumentsList.stream(); - } - - public static Stream legalIdentifiers() { - List args = new LinkedList<>(); - - // An arbitrary collection of strings that contain 0-9A-Za-z and space. - final String upper = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"; - final String lower = upper.toLowerCase(Locale.ROOT); - String digits = "0123456789"; - String validChars = upper + lower + digits + "-" + "_"; - - // Test scenarios where there is a single valid char - for (int i = 0; i < validChars.length(); i++) { - args.add(Arguments.of("" + validChars.charAt(i))); - } - - // Test scenarios where we have a set of valid chars - for (int i = 0; i < 100; i++) { - args.add(Arguments.of(randomString(RAND, validChars, 12))); - } - - return args.stream(); - } } diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/system/events/EventSerializationOptions.java b/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/state/merkle/disk/OnDiskKeySerializerTest.java similarity index 76% rename from platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/system/events/EventSerializationOptions.java rename to platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/state/merkle/disk/OnDiskKeySerializerTest.java index 196b07542d08..ca6706b9c2fb 100644 --- a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/system/events/EventSerializationOptions.java +++ b/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/state/merkle/disk/OnDiskKeySerializerTest.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2016-2024 Hedera Hashgraph, LLC + * Copyright (C) 2024 Hedera Hashgraph, LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,9 +14,8 @@ * limitations under the License. */ -package com.swirlds.platform.system.events; +package com.swirlds.platform.state.merkle.disk; -public enum EventSerializationOptions { - FULL, - OMIT_TRANSACTIONS -} +import static org.junit.jupiter.api.Assertions.*; + +class OnDiskKeySerializerTest {} diff --git a/hedera-node/hedera-app/src/test/java/com/hedera/node/app/state/merkle/disk/OnDiskReadableStateTest.java b/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/state/merkle/disk/OnDiskReadableStateTest.java similarity index 62% rename from hedera-node/hedera-app/src/test/java/com/hedera/node/app/state/merkle/disk/OnDiskReadableStateTest.java rename to platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/state/merkle/disk/OnDiskReadableStateTest.java index 30f0c58c61d0..cdc33328d981 100644 --- a/hedera-node/hedera-app/src/test/java/com/hedera/node/app/state/merkle/disk/OnDiskReadableStateTest.java +++ b/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/state/merkle/disk/OnDiskReadableStateTest.java @@ -14,16 +14,13 @@ * limitations under the License. */ -package com.hedera.node.app.state.merkle.disk; +package com.swirlds.platform.state.merkle.disk; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.mockito.Mockito.verify; -import com.hedera.node.app.spi.fixtures.state.TestSchema; -import com.hedera.node.app.spi.state.StateDefinition; -import com.hedera.node.app.state.merkle.MerkleTestBase; -import com.hedera.node.app.state.merkle.StateMetadata; +import com.swirlds.platform.test.fixtures.state.merkle.MerkleTestBase; import com.swirlds.virtualmap.VirtualMap; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; @@ -35,31 +32,37 @@ @ExtendWith(MockitoExtension.class) class OnDiskReadableStateTest extends MerkleTestBase { - private StateMetadata md; - private VirtualMap, OnDiskValue> virtualMap; - @BeforeEach void setUp() { - md = new StateMetadata<>( - FIRST_SERVICE, - new TestSchema(1), - StateDefinition.onDisk(FRUIT_STATE_KEY, STRING_CODEC, STRING_CODEC, 100)); - virtualMap = createVirtualMap("TEST LABEL", md); + setupFruitVirtualMap(); } @Test @DisplayName("The size of the state is the size of the virtual map") void sizeWorks() { - final var state = new OnDiskReadableKVState<>(md, virtualMap); + final var state = + new OnDiskReadableKVState<>(FRUIT_STATE_KEY, onDiskKeyClassId(), STRING_CODEC, fruitVirtualMap); assertThat(state.size()).isZero(); - add(virtualMap, md, A_KEY, APPLE); - add(virtualMap, md, B_KEY, BANANA); - add(virtualMap, md, C_KEY, CHERRY); - assertThat(state.size()).isEqualTo(virtualMap.size()); + add(A_KEY, APPLE); + add(B_KEY, BANANA); + add(C_KEY, CHERRY); + assertThat(state.size()).isEqualTo(fruitVirtualMap.size()); assertThat(state.size()).isEqualTo(3); } + private void add(String key, String value) { + add(fruitVirtualMap, onDiskKeyClassId(), STRING_CODEC, onDiskValueClassId(), STRING_CODEC, key, value); + } + + private static long onDiskValueClassId() { + return onDiskValueClassId(FRUIT_STATE_KEY); + } + + private static long onDiskKeyClassId() { + return onDiskKeyClassId(FRUIT_STATE_KEY); + } + @Nested @DisplayName("Constructor Tests") final class ConstructorTest { @@ -68,7 +71,8 @@ final class ConstructorTest { @DisplayName("You must specify the metadata") void nullStateKeyThrows() { //noinspection DataFlowIssue - assertThatThrownBy(() -> new OnDiskReadableKVState<>(null, virtualMap)) + assertThatThrownBy( + () -> new OnDiskReadableKVState<>(null, onDiskKeyClassId(), STRING_CODEC, fruitVirtualMap)) .isInstanceOf(NullPointerException.class); } @@ -76,13 +80,16 @@ void nullStateKeyThrows() { @DisplayName("You must specify the virtual map") void nullVirtualMapThrows() { //noinspection DataFlowIssue - assertThatThrownBy(() -> new OnDiskReadableKVState<>(md, null)).isInstanceOf(NullPointerException.class); + assertThatThrownBy( + () -> new OnDiskReadableKVState<>(FRUIT_STATE_KEY, onDiskKeyClassId(), STRING_CODEC, null)) + .isInstanceOf(NullPointerException.class); } @Test @DisplayName("The stateKey matches that supplied") void stateKey() { - final var state = new OnDiskReadableKVState<>(md, virtualMap); + final var state = + new OnDiskReadableKVState<>(FRUIT_STATE_KEY, onDiskKeyClassId(), STRING_CODEC, fruitVirtualMap); assertThat(state.getStateKey()).isEqualTo(FRUIT_STATE_KEY); } } @@ -94,10 +101,10 @@ final class QueryTest { @BeforeEach void setUp() { - state = new OnDiskReadableKVState<>(md, virtualMap); - add(virtualMap, md, A_KEY, APPLE); - add(virtualMap, md, B_KEY, BANANA); - add(virtualMap, md, C_KEY, CHERRY); + state = new OnDiskReadableKVState<>(FRUIT_STATE_KEY, onDiskKeyClassId(), STRING_CODEC, fruitVirtualMap); + add(A_KEY, APPLE); + add(B_KEY, BANANA); + add(C_KEY, CHERRY); } @Test @@ -116,8 +123,9 @@ void get() { @Test @DisplayName("The method warm() calls the appropriate method on the virtual map") void warm(@Mock VirtualMap, OnDiskValue> virtualMapMock) { - final var state = new OnDiskReadableKVState<>(md, virtualMapMock); + final var state = + new OnDiskReadableKVState<>(FRUIT_STATE_KEY, onDiskKeyClassId(), STRING_CODEC, virtualMapMock); state.warm(A_KEY); - verify(virtualMapMock).warm(new OnDiskKey<>(md, A_KEY)); + verify(virtualMapMock).warm(new OnDiskKey<>(onDiskKeyClassId(), STRING_CODEC, A_KEY)); } } diff --git a/hedera-node/hedera-app/src/test/java/com/hedera/node/app/state/merkle/disk/OnDiskWritableStateTest.java b/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/state/merkle/disk/OnDiskWritableStateTest.java similarity index 76% rename from hedera-node/hedera-app/src/test/java/com/hedera/node/app/state/merkle/disk/OnDiskWritableStateTest.java rename to platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/state/merkle/disk/OnDiskWritableStateTest.java index cb1a70872e6a..3706fb09df0a 100644 --- a/hedera-node/hedera-app/src/test/java/com/hedera/node/app/state/merkle/disk/OnDiskWritableStateTest.java +++ b/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/state/merkle/disk/OnDiskWritableStateTest.java @@ -14,12 +14,12 @@ * limitations under the License. */ -package com.hedera.node.app.state.merkle.disk; +package com.swirlds.platform.state.merkle.disk; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.AssertionsForClassTypes.assertThatThrownBy; -import com.hedera.node.app.state.merkle.MerkleTestBase; +import com.swirlds.platform.test.fixtures.state.merkle.MerkleTestBase; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Nested; @@ -38,12 +38,18 @@ void setUp() { @Test @DisplayName("The size of the state is the size of the virtual map") void sizeWorks() { - final var state = new OnDiskWritableKVState<>(fruitVirtualMetadata, fruitVirtualMap); + final var state = new OnDiskWritableKVState<>( + FRUIT_STATE_KEY, + onDiskKeyClassId(), + STRING_CODEC, + onDiskValueClassId(), + STRING_CODEC, + fruitVirtualMap); assertThat(state.size()).isZero(); - add(fruitVirtualMap, fruitVirtualMetadata, A_KEY, APPLE); - add(fruitVirtualMap, fruitVirtualMetadata, B_KEY, BANANA); - add(fruitVirtualMap, fruitVirtualMetadata, C_KEY, CHERRY); + add(A_KEY, APPLE); + add(B_KEY, BANANA); + add(C_KEY, CHERRY); assertThat(state.size()).isEqualTo(fruitVirtualMap.size()); assertThat(state.size()).isEqualTo(3); @@ -53,7 +59,13 @@ void sizeWorks() { @DisplayName("You must specify the metadata") void nullMetadataThrows() { //noinspection DataFlowIssue - assertThatThrownBy(() -> new OnDiskWritableKVState<>(null, fruitVirtualMap)) + assertThatThrownBy(() -> new OnDiskWritableKVState<>( + null, + onDiskKeyClassId(), + STRING_CODEC, + onDiskValueClassId(), + STRING_CODEC, + fruitVirtualMap)) .isInstanceOf(NullPointerException.class); } @@ -61,18 +73,42 @@ void nullMetadataThrows() { @DisplayName("You must specify the virtual map") void nullMerkleMapThrows() { //noinspection DataFlowIssue - assertThatThrownBy(() -> new OnDiskWritableKVState<>(fruitVirtualMetadata, null)) + assertThatThrownBy(() -> new OnDiskWritableKVState<>( + FRUIT_STATE_KEY, + onDiskKeyClassId(), + STRING_CODEC, + onDiskValueClassId(), + STRING_CODEC, + null)) .isInstanceOf(NullPointerException.class); } @Test @DisplayName("The stateKey matches that supplied by the metadata") void stateKey() { - final var state = new OnDiskWritableKVState<>(fruitVirtualMetadata, fruitVirtualMap); + final var state = new OnDiskWritableKVState<>( + FRUIT_STATE_KEY, + onDiskKeyClassId(), + STRING_CODEC, + onDiskValueClassId(), + STRING_CODEC, + fruitVirtualMap); assertThat(state.getStateKey()).isEqualTo(FRUIT_STATE_KEY); } } + private void add(String key, String value) { + add(fruitVirtualMap, onDiskKeyClassId(), STRING_CODEC, onDiskValueClassId(), STRING_CODEC, key, value); + } + + private static long onDiskValueClassId() { + return onDiskValueClassId(FRUIT_STATE_KEY); + } + + private static long onDiskKeyClassId() { + return onDiskKeyClassId(FRUIT_STATE_KEY); + } + @Nested @DisplayName("Query Tests") final class QueryTest { @@ -81,10 +117,16 @@ final class QueryTest { @BeforeEach void setUp() { setupFruitVirtualMap(); - state = new OnDiskWritableKVState<>(fruitVirtualMetadata, fruitVirtualMap); - add(fruitVirtualMap, fruitVirtualMetadata, A_KEY, APPLE); - add(fruitVirtualMap, fruitVirtualMetadata, B_KEY, BANANA); - add(fruitVirtualMap, fruitVirtualMetadata, C_KEY, CHERRY); + state = new OnDiskWritableKVState<>( + FRUIT_STATE_KEY, + onDiskKeyClassId(), + STRING_CODEC, + onDiskValueClassId(), + STRING_CODEC, + fruitVirtualMap); + add(A_KEY, APPLE); + add(B_KEY, BANANA); + add(C_KEY, CHERRY); } @Test @@ -114,17 +156,23 @@ final class MutationTest { @BeforeEach void setUp() { setupFruitVirtualMap(); - state = new OnDiskWritableKVState<>(fruitVirtualMetadata, fruitVirtualMap); - add(fruitVirtualMap, fruitVirtualMetadata, A_KEY, APPLE); - add(fruitVirtualMap, fruitVirtualMetadata, B_KEY, BANANA); + state = new OnDiskWritableKVState<>( + FRUIT_STATE_KEY, + onDiskKeyClassId(), + STRING_CODEC, + onDiskValueClassId(), + STRING_CODEC, + fruitVirtualMap); + add(A_KEY, APPLE); + add(B_KEY, BANANA); } boolean merkleMapContainsKey(String key) { - return fruitVirtualMap.containsKey(new OnDiskKey<>(fruitVirtualMetadata, key)); + return fruitVirtualMap.containsKey(new OnDiskKey<>(onDiskKeyClassId(), STRING_CODEC, key)); } String readValueFromMerkleMap(String key) { - final var val = fruitVirtualMap.get(new OnDiskKey<>(fruitVirtualMetadata, key)); + final var val = fruitVirtualMap.get(new OnDiskKey<>(onDiskKeyClassId(), STRING_CODEC, key)); return val == null ? null : val.getValue(); } @@ -234,7 +282,13 @@ void smorgasbord() { // modifications and reads. And then let's throw them all away and make // sure the virtual map hasn't changed. fruitVirtualMap = fruitVirtualMap.copy(); - state = new OnDiskWritableKVState<>(fruitVirtualMetadata, fruitVirtualMap); + state = new OnDiskWritableKVState<>( + FRUIT_STATE_KEY, + onDiskKeyClassId(), + STRING_CODEC, + onDiskValueClassId(), + STRING_CODEC, + fruitVirtualMap); assertThat(state.getForModify(A_KEY)).isEqualTo(APPLE); state.remove(B_KEY); assertThat(state.get(C_KEY)).isEqualTo(CHERRY); diff --git a/hedera-node/hedera-app/src/test/java/com/hedera/node/app/state/merkle/memory/InMemoryReadableStateTest.java b/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/state/merkle/memory/InMemoryReadableStateTest.java similarity index 80% rename from hedera-node/hedera-app/src/test/java/com/hedera/node/app/state/merkle/memory/InMemoryReadableStateTest.java rename to platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/state/merkle/memory/InMemoryReadableStateTest.java index 720a4c0e6ac2..ea4de7558a8d 100644 --- a/hedera-node/hedera-app/src/test/java/com/hedera/node/app/state/merkle/memory/InMemoryReadableStateTest.java +++ b/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/state/merkle/memory/InMemoryReadableStateTest.java @@ -14,12 +14,12 @@ * limitations under the License. */ -package com.hedera.node.app.state.merkle.memory; +package com.swirlds.platform.state.merkle.memory; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.AssertionsForClassTypes.assertThatThrownBy; -import com.hedera.node.app.state.merkle.MerkleTestBase; +import com.swirlds.platform.test.fixtures.state.merkle.MerkleTestBase; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Nested; @@ -47,31 +47,35 @@ void nullMetadataThrows() { @DisplayName("You must specify the merkle map") void nullMerkleMapThrows() { //noinspection DataFlowIssue - assertThatThrownBy(() -> new InMemoryReadableKVState<>(fruitMetadata, null)) + assertThatThrownBy(() -> new InMemoryReadableKVState<>(FRUIT_STATE_KEY, null)) .isInstanceOf(NullPointerException.class); } @Test @DisplayName("The stateKey matches that supplied by the metadata") void stateKey() { - final var state = new InMemoryReadableKVState<>(fruitMetadata, fruitMerkleMap); + final var state = new InMemoryReadableKVState<>(FRUIT_STATE_KEY, fruitMerkleMap); assertThat(state.getStateKey()).isEqualTo(FRUIT_STATE_KEY); } @Test @DisplayName("The size of the state is the size of the merkle map") void sizeWorks() { - final var state = new InMemoryReadableKVState<>(fruitMetadata, fruitMerkleMap); + final var state = new InMemoryReadableKVState<>(FRUIT_STATE_KEY, fruitMerkleMap); assertThat(state.size()).isZero(); - add(fruitMerkleMap, fruitMetadata, A_KEY, APPLE); - add(fruitMerkleMap, fruitMetadata, B_KEY, BANANA); - add(fruitMerkleMap, fruitMetadata, C_KEY, CHERRY); + add(A_KEY, APPLE); + add(B_KEY, BANANA); + add(C_KEY, CHERRY); assertThat(state.size()).isEqualTo(fruitMerkleMap.size()); assertThat(state.size()).isEqualTo(3); } } + private void add(String key, String value) { + add(fruitMerkleMap, inMemoryValueClassId(FRUIT_STATE_KEY), STRING_CODEC, STRING_CODEC, key, value); + } + @Nested @DisplayName("Query Tests") final class QueryTest { @@ -80,10 +84,10 @@ final class QueryTest { @BeforeEach void setUp() { setupFruitMerkleMap(); - state = new InMemoryReadableKVState<>(fruitMetadata, fruitMerkleMap); - add(fruitMerkleMap, fruitMetadata, A_KEY, APPLE); - add(fruitMerkleMap, fruitMetadata, B_KEY, BANANA); - add(fruitMerkleMap, fruitMetadata, C_KEY, CHERRY); + state = new InMemoryReadableKVState<>(FRUIT_STATE_KEY, fruitMerkleMap); + add(A_KEY, APPLE); + add(B_KEY, BANANA); + add(C_KEY, CHERRY); } @Test diff --git a/hedera-node/hedera-app/src/test/java/com/hedera/node/app/state/merkle/memory/InMemoryWritableStateTest.java b/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/state/merkle/memory/InMemoryWritableStateTest.java similarity index 88% rename from hedera-node/hedera-app/src/test/java/com/hedera/node/app/state/merkle/memory/InMemoryWritableStateTest.java rename to platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/state/merkle/memory/InMemoryWritableStateTest.java index 443a343fd9e9..0d3557121350 100644 --- a/hedera-node/hedera-app/src/test/java/com/hedera/node/app/state/merkle/memory/InMemoryWritableStateTest.java +++ b/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/state/merkle/memory/InMemoryWritableStateTest.java @@ -14,12 +14,12 @@ * limitations under the License. */ -package com.hedera.node.app.state.merkle.memory; +package com.swirlds.platform.state.merkle.memory; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.AssertionsForClassTypes.assertThatThrownBy; -import com.hedera.node.app.state.merkle.MerkleTestBase; +import com.swirlds.platform.test.fixtures.state.merkle.MerkleTestBase; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Nested; @@ -40,7 +40,8 @@ void setUp() { @DisplayName("You must specify the metadata") void nullMetadataThrows() { //noinspection DataFlowIssue - assertThatThrownBy(() -> new InMemoryWritableKVState<>(null, fruitMerkleMap)) + assertThatThrownBy(() -> new InMemoryWritableKVState<>( + null, inMemoryValueClassId(FRUIT_STATE_KEY), STRING_CODEC, STRING_CODEC, fruitMerkleMap)) .isInstanceOf(NullPointerException.class); } @@ -48,30 +49,40 @@ void nullMetadataThrows() { @DisplayName("You must specify the merkle map") void nullMerkleMapThrows() { //noinspection DataFlowIssue - assertThatThrownBy(() -> new InMemoryWritableKVState<>(fruitMetadata, null)) + assertThatThrownBy(() -> new InMemoryWritableKVState<>( + FRUIT_STATE_KEY, inMemoryValueClassId(FRUIT_STATE_KEY), STRING_CODEC, STRING_CODEC, null)) .isInstanceOf(NullPointerException.class); } @Test @DisplayName("The stateKey matches that supplied by the metadata") void stateKey() { - final var state = new InMemoryWritableKVState<>(fruitMetadata, fruitMerkleMap); + final var state = createState(); assertThat(state.getStateKey()).isEqualTo(FRUIT_STATE_KEY); } @Test @DisplayName("The size of the state is the size of the merkle map") void sizeWorks() { - final var state = new InMemoryWritableKVState<>(fruitMetadata, fruitMerkleMap); + final var state = createState(); assertThat(state.size()).isZero(); - add(fruitMerkleMap, fruitMetadata, A_KEY, APPLE); - add(fruitMerkleMap, fruitMetadata, B_KEY, BANANA); - add(fruitMerkleMap, fruitMetadata, C_KEY, CHERRY); + add(A_KEY, APPLE); + add(B_KEY, BANANA); + add(C_KEY, CHERRY); assertThat(state.sizeOfDataSource()).isEqualTo(fruitMerkleMap.size()); } } + private InMemoryWritableKVState createState() { + return new InMemoryWritableKVState<>( + FRUIT_STATE_KEY, inMemoryValueClassId(FRUIT_STATE_KEY), STRING_CODEC, STRING_CODEC, fruitMerkleMap); + } + + private void add(String key, String value) { + add(fruitMerkleMap, inMemoryValueClassId(FRUIT_STATE_KEY), STRING_CODEC, STRING_CODEC, key, value); + } + @Nested @DisplayName("Query Tests") final class QueryTest { @@ -80,10 +91,10 @@ final class QueryTest { @BeforeEach void setUp() { setupFruitMerkleMap(); - state = new InMemoryWritableKVState<>(fruitMetadata, fruitMerkleMap); - add(fruitMerkleMap, fruitMetadata, A_KEY, APPLE); - add(fruitMerkleMap, fruitMetadata, B_KEY, BANANA); - add(fruitMerkleMap, fruitMetadata, C_KEY, CHERRY); + state = createState(); + add(A_KEY, APPLE); + add(B_KEY, BANANA); + add(C_KEY, CHERRY); } @Test @@ -113,9 +124,9 @@ final class MutationTest { @BeforeEach void setUp() { setupFruitMerkleMap(); - state = new InMemoryWritableKVState<>(fruitMetadata, fruitMerkleMap); - add(fruitMerkleMap, fruitMetadata, A_KEY, APPLE); - add(fruitMerkleMap, fruitMetadata, B_KEY, BANANA); + state = createState(); + add(A_KEY, APPLE); + add(B_KEY, BANANA); } boolean merkleMapContainsKey(String key) { @@ -235,7 +246,7 @@ void smorgasbord() { // modifications and reads. And then let's throw them all away and make // sure the merkle map hasn't changed. fruitMerkleMap = fruitMerkleMap.copy(); - state = new InMemoryWritableKVState<>(fruitMetadata, fruitMerkleMap); + state = createState(); assertThat(state.getForModify(A_KEY)).isEqualTo(APPLE); state.remove(B_KEY); assertThat(state.get(C_KEY)).isEqualTo(CHERRY); diff --git a/hedera-node/hedera-app/src/test/java/com/hedera/node/app/state/merkle/queue/QueueNodeTest.java b/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/state/merkle/queue/QueueNodeTest.java similarity index 67% rename from hedera-node/hedera-app/src/test/java/com/hedera/node/app/state/merkle/queue/QueueNodeTest.java rename to platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/state/merkle/queue/QueueNodeTest.java index 1847182f76db..de2c0e87af01 100644 --- a/hedera-node/hedera-app/src/test/java/com/hedera/node/app/state/merkle/queue/QueueNodeTest.java +++ b/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/state/merkle/queue/QueueNodeTest.java @@ -14,29 +14,29 @@ * limitations under the License. */ -package com.hedera.node.app.state.merkle.queue; +package com.swirlds.platform.state.merkle.queue; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotEquals; -import com.hedera.node.app.spi.fixtures.state.TestSchema; -import com.hedera.node.app.spi.state.StateDefinition; -import com.hedera.node.app.state.merkle.MerkleTestBase; -import com.hedera.node.app.state.merkle.StateMetadata; +import com.swirlds.platform.test.fixtures.state.merkle.MerkleTestBase; import org.junit.jupiter.api.Test; class QueueNodeTest extends MerkleTestBase { @Test void usesQueueNodeIdFromMetadataIfAvailable() { - final var metadata = new StateMetadata<>( - FIRST_SERVICE, new TestSchema(1), StateDefinition.queue(FRUIT_STATE_KEY, STRING_CODEC)); - final var node = new QueueNode<>(metadata); + final var node = new QueueNode<>( + FIRST_SERVICE, + FRUIT_STATE_KEY, + queueNodeClassId(FRUIT_STATE_KEY), + singletonClassId(FRUIT_STATE_KEY), + STRING_CODEC); assertNotEquals(0x990FF87AD2691DCL, node.getClassId()); } @Test void usesDefaultClassIdWithoutMetadata() { - final var node = new QueueNode(); + final var node = new QueueNode<>(); assertEquals(0x990FF87AD2691DCL, node.getClassId()); } } diff --git a/hedera-node/hedera-app/src/test/java/com/hedera/node/app/state/merkle/singleton/StringLeafTest.java b/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/state/merkle/singleton/StringLeafTest.java similarity index 91% rename from hedera-node/hedera-app/src/test/java/com/hedera/node/app/state/merkle/singleton/StringLeafTest.java rename to platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/state/merkle/singleton/StringLeafTest.java index a1e4b1a6f6b8..c2988802b817 100644 --- a/hedera-node/hedera-app/src/test/java/com/hedera/node/app/state/merkle/singleton/StringLeafTest.java +++ b/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/state/merkle/singleton/StringLeafTest.java @@ -14,12 +14,12 @@ * limitations under the License. */ -package com.hedera.node.app.state.merkle.singleton; +package com.swirlds.platform.state.merkle.singleton; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; -import com.hedera.node.app.state.merkle.MerkleTestBase; +import com.swirlds.platform.test.fixtures.state.merkle.MerkleTestBase; import org.junit.jupiter.api.Test; class StringLeafTest extends MerkleTestBase { diff --git a/hedera-node/hedera-app/src/test/java/com/hedera/node/app/state/merkle/singleton/ValueLeafTest.java b/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/state/merkle/singleton/ValueLeafTest.java similarity index 81% rename from hedera-node/hedera-app/src/test/java/com/hedera/node/app/state/merkle/singleton/ValueLeafTest.java rename to platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/state/merkle/singleton/ValueLeafTest.java index 70d2266d76ce..1da76704188a 100644 --- a/hedera-node/hedera-app/src/test/java/com/hedera/node/app/state/merkle/singleton/ValueLeafTest.java +++ b/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/state/merkle/singleton/ValueLeafTest.java @@ -14,18 +14,18 @@ * limitations under the License. */ -package com.hedera.node.app.state.merkle.singleton; +package com.swirlds.platform.state.merkle.singleton; import static org.assertj.core.api.Assertions.assertThat; -import com.hedera.node.app.state.merkle.MerkleTestBase; +import com.swirlds.platform.test.fixtures.state.merkle.MerkleTestBase; import org.junit.jupiter.api.Test; class ValueLeafTest extends MerkleTestBase { @Test void setValue() { setupSingletonCountry(); - final var leaf = new ValueLeaf<>(countryMetadata, DENMARK); + final var leaf = new ValueLeaf<>(singletonClassId(COUNTRY_STATE_KEY), STRING_CODEC, DENMARK); assertThat(leaf.getValue()).isEqualTo(DENMARK); leaf.setValue(FRANCE); assertThat(leaf.getValue()).isEqualTo(FRANCE); diff --git a/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/state/nexus/SignedStateNexusTest.java b/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/state/nexus/SignedStateNexusTest.java index 51710c440300..c9d9b87181c4 100644 --- a/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/state/nexus/SignedStateNexusTest.java +++ b/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/state/nexus/SignedStateNexusTest.java @@ -24,9 +24,8 @@ import static org.junit.jupiter.api.Assertions.assertSame; import static org.junit.jupiter.api.Assertions.assertTrue; -import com.swirlds.common.metrics.noop.NoOpMetrics; -import com.swirlds.config.api.ConfigurationBuilder; -import com.swirlds.platform.config.StateConfig; +import com.swirlds.common.context.PlatformContext; +import com.swirlds.common.test.fixtures.platform.TestPlatformContextBuilder; import com.swirlds.platform.consensus.ConsensusConstants; import com.swirlds.platform.state.RandomSignedStateGenerator; import com.swirlds.platform.state.signed.ReservedSignedState; @@ -42,14 +41,9 @@ class SignedStateNexusTest { private static Stream allInstances() { - return Stream.concat( - raceConditionInstances(), - Stream.of(new DefaultLatestCompleteStateNexus( - ConfigurationBuilder.create() - .withConfigDataType(StateConfig.class) - .build() - .getConfigData(StateConfig.class), - new NoOpMetrics()))); + final PlatformContext platformContext = + TestPlatformContextBuilder.create().build(); + return Stream.concat(raceConditionInstances(), Stream.of(new DefaultLatestCompleteStateNexus(platformContext))); } private static Stream raceConditionInstances() { diff --git a/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/state/signed/DefaultSignedStateHasherTests.java b/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/state/signed/DefaultStateHasherTests.java similarity index 61% rename from platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/state/signed/DefaultSignedStateHasherTests.java rename to platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/state/signed/DefaultStateHasherTests.java index 76add34717cb..e7b84d2526b6 100644 --- a/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/state/signed/DefaultSignedStateHasherTests.java +++ b/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/state/signed/DefaultStateHasherTests.java @@ -16,35 +16,31 @@ package com.swirlds.platform.state.signed; -import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertNotEquals; import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; -import com.swirlds.common.metrics.RunningAverageMetric; +import com.swirlds.common.context.PlatformContext; +import com.swirlds.common.test.fixtures.platform.TestPlatformContextBuilder; import com.swirlds.platform.internal.ConsensusRound; +import com.swirlds.platform.state.hasher.DefaultStateHasher; +import com.swirlds.platform.state.hasher.StateHasher; import com.swirlds.platform.wiring.components.StateAndRound; -import java.util.concurrent.atomic.AtomicBoolean; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; /** - * Unit tests for {@link DefaultSignedStateHasher} + * Unit tests for {@link DefaultStateHasher} */ -public class DefaultSignedStateHasherTests { +public class DefaultStateHasherTests { @Test @DisplayName("Normal operation") void normalOperation() { - // mock metrics - final RunningAverageMetric hashingTimeMetric = mock(RunningAverageMetric.class); - final SignedStateMetrics signedStateMetrics = mock(SignedStateMetrics.class); - when(signedStateMetrics.getSignedStateHashingTimeMetric()).thenReturn(hashingTimeMetric); + final PlatformContext platformContext = + TestPlatformContextBuilder.create().build(); // create the hasher - final AtomicBoolean fatalErrorConsumer = new AtomicBoolean(); - final SignedStateHasher hasher = - new DefaultSignedStateHasher(signedStateMetrics, (a, b, c) -> fatalErrorConsumer.set(true)); + final StateHasher hasher = new DefaultStateHasher(platformContext); // mock a state final SignedState signedState = mock(SignedState.class); @@ -56,9 +52,5 @@ void normalOperation() { // do the test final StateAndRound result = hasher.hashState(stateAndRound); assertNotEquals(null, result, "The hasher should return a new StateAndRound"); - - // hashing time metric should get updated - verify(signedStateMetrics).getSignedStateHashingTimeMetric(); - assertFalse(fatalErrorConsumer.get(), "There should be no fatal errors"); } } diff --git a/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/state/signed/StartupStateUtilsTests.java b/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/state/signed/StartupStateUtilsTests.java index 55cf071dcdf9..249671ec583a 100644 --- a/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/state/signed/StartupStateUtilsTests.java +++ b/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/state/signed/StartupStateUtilsTests.java @@ -17,19 +17,13 @@ package com.swirlds.platform.state.signed; import static com.swirlds.common.test.fixtures.RandomUtils.getRandomPrintSeed; -import static com.swirlds.common.test.fixtures.RandomUtils.randomHash; -import static com.swirlds.platform.state.signed.SignedStateFileWriter.writeSignedStateToDisk; -import static com.swirlds.platform.state.signed.StartupStateUtils.doRecoveryCleanup; +import static com.swirlds.platform.state.snapshot.SignedStateFileWriter.writeSignedStateToDisk; import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertThrows; -import static org.junit.jupiter.api.Assertions.assertTrue; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.doAnswer; -import static org.mockito.Mockito.mock; import static org.mockito.Mockito.spy; -import static org.mockito.Mockito.when; import com.swirlds.common.config.StateCommonConfig; import com.swirlds.common.config.StateCommonConfig_; @@ -37,24 +31,19 @@ import com.swirlds.common.constructable.ConstructableRegistryException; import com.swirlds.common.context.PlatformContext; import com.swirlds.common.crypto.Hash; +import com.swirlds.common.io.filesystem.FileSystemManager; import com.swirlds.common.io.utility.FileUtils; -import com.swirlds.common.io.utility.LegacyTemporaryFileBuilder; import com.swirlds.common.io.utility.RecycleBin; import com.swirlds.common.platform.NodeId; -import com.swirlds.common.scratchpad.Scratchpad; import com.swirlds.common.test.fixtures.TestRecycleBin; import com.swirlds.common.test.fixtures.platform.TestPlatformContextBuilder; import com.swirlds.config.api.Configuration; import com.swirlds.config.extensions.test.fixtures.TestConfigBuilder; import com.swirlds.platform.config.StateConfig_; -import com.swirlds.platform.event.preconsensus.PcesConfig; import com.swirlds.platform.internal.SignedStateLoadingException; -import com.swirlds.platform.recovery.EmergencyRecoveryManager; -import com.swirlds.platform.recovery.RecoveryScratchpad; -import com.swirlds.platform.recovery.emergencyfile.EmergencyRecoveryFile; -import com.swirlds.platform.recovery.emergencyfile.Recovery; -import com.swirlds.platform.recovery.emergencyfile.State; import com.swirlds.platform.state.RandomSignedStateGenerator; +import com.swirlds.platform.state.snapshot.SignedStateFilePath; +import com.swirlds.platform.state.snapshot.StateToDiskReason; import com.swirlds.platform.system.BasicSoftwareVersion; import edu.umd.cs.findbugs.annotations.NonNull; import edu.umd.cs.findbugs.annotations.Nullable; @@ -63,7 +52,6 @@ import java.nio.file.Files; import java.nio.file.Path; import java.util.Random; -import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicInteger; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeAll; @@ -75,7 +63,7 @@ import org.junit.jupiter.params.provider.ValueSource; @DisplayName("StartupStateUtilities Tests") -class StartupStateUtilsTests { +public class StartupStateUtilsTests { /** * Temporary directory provided by JUnit @@ -92,7 +80,6 @@ class StartupStateUtilsTests { @BeforeEach void beforeEach() throws IOException { FileUtils.deleteDirectory(testDirectory); - LegacyTemporaryFileBuilder.overrideTemporaryFileLocation(testDirectory); signedStateFilePath = new SignedStateFilePath(new TestConfigBuilder() .withValue("state.savedStateDirectory", testDirectory.toString()) .getOrCreateConfig() @@ -110,29 +97,48 @@ static void beforeAll() throws ConstructableRegistryException { } @NonNull - private PlatformContext buildContext(final boolean deleteInvalidStateFiles, final RecycleBin recycleBin) { + private PlatformContext buildContext(final boolean deleteInvalidStateFiles, @NonNull final RecycleBin recycleBin) { final Configuration configuration = new TestConfigBuilder() .withValue(StateCommonConfig_.SAVED_STATE_DIRECTORY, testDirectory.toString()) .withValue(StateConfig_.DELETE_INVALID_STATE_FILES, deleteInvalidStateFiles) .getOrCreateConfig(); - return TestPlatformContextBuilder.create() - .withConfiguration(configuration) - .withTestFileSystemManager(testDirectory, recycleBin) - .build(); - } + // FUTURE WORK do this more elegantly when we have a better test implementation + final FileSystemManager fileSystemManager = new FileSystemManager() { + @NonNull + @Override + public Path resolve(@NonNull final Path relativePath) { + throw new UnsupportedOperationException(); + } - @NonNull - private PlatformContext buildContext(final boolean deleteInvalidStateFiles) { - final Configuration configuration = new TestConfigBuilder() - .withValue(StateCommonConfig_.SAVED_STATE_DIRECTORY, testDirectory.toString()) - .withValue(StateConfig_.DELETE_INVALID_STATE_FILES, deleteInvalidStateFiles) - .getOrCreateConfig(); + @NonNull + @Override + public Path resolveNewTemp(@Nullable final String tag) { + throw new UnsupportedOperationException(); + } + + @Override + public void recycle(@NonNull final Path path) throws IOException { + recycleBin.recycle(path); + } + + @Override + public void start() { + throw new UnsupportedOperationException(); + } - return TestPlatformContextBuilder.create() + @Override + public void stop() { + throw new UnsupportedOperationException(); + } + }; + + final PlatformContext platformContext = TestPlatformContextBuilder.create() .withConfiguration(configuration) - .withTestFileSystemManager(testDirectory) + .withFileSystemManager((x, y) -> fileSystemManager) .build(); + + return platformContext; } /** @@ -174,15 +180,10 @@ private SignedState writeState( @Test @DisplayName("Genesis Test") void genesisTest() throws SignedStateLoadingException { - final PlatformContext platformContext = buildContext(false); + final PlatformContext platformContext = buildContext(false, TestRecycleBin.getInstance()); final SignedState loadedState = StartupStateUtils.loadStateFile( - platformContext, - selfId, - mainClassName, - swirldName, - new BasicSoftwareVersion(1), - mock(EmergencyRecoveryManager.class)) + platformContext, selfId, mainClassName, swirldName, new BasicSoftwareVersion(1)) .getNullable(); assertNull(loadedState); @@ -192,7 +193,7 @@ void genesisTest() throws SignedStateLoadingException { @DisplayName("Normal Restart Test") void normalRestartTest() throws IOException, SignedStateLoadingException { final Random random = getRandomPrintSeed(); - final PlatformContext platformContext = buildContext(false); + final PlatformContext platformContext = buildContext(false, TestRecycleBin.getInstance()); int stateCount = 5; @@ -204,12 +205,7 @@ void normalRestartTest() throws IOException, SignedStateLoadingException { } final SignedState loadedState = StartupStateUtils.loadStateFile( - platformContext, - selfId, - mainClassName, - swirldName, - new BasicSoftwareVersion(1), - mock(EmergencyRecoveryManager.class)) + platformContext, selfId, mainClassName, swirldName, new BasicSoftwareVersion(1)) .get(); loadedState.getState().throwIfImmutable(); @@ -223,7 +219,7 @@ void normalRestartTest() throws IOException, SignedStateLoadingException { @DisplayName("Corrupted State No Recycling Test") void corruptedStateNoRecyclingTest() throws IOException { final Random random = getRandomPrintSeed(); - final PlatformContext platformContext = buildContext(false); + final PlatformContext platformContext = buildContext(false, TestRecycleBin.getInstance()); int stateCount = 5; @@ -235,12 +231,7 @@ void corruptedStateNoRecyclingTest() throws IOException { } assertThrows(SignedStateLoadingException.class, () -> StartupStateUtils.loadStateFile( - platformContext, - selfId, - mainClassName, - swirldName, - new BasicSoftwareVersion(1), - mock(EmergencyRecoveryManager.class)) + platformContext, selfId, mainClassName, swirldName, new BasicSoftwareVersion(1)) .get()); } @@ -250,393 +241,6 @@ void corruptedStateNoRecyclingTest() throws IOException { void corruptedStateRecyclingPermittedTest(final int invalidStateCount) throws IOException, SignedStateLoadingException { final Random random = getRandomPrintSeed(); - final AtomicInteger recycleCount = new AtomicInteger(0); - final RecycleBin recycleBin = spy(TestRecycleBin.getInstance()); - // increment recycle count every time recycleBin.recycle() is called - doAnswer(invocation -> { - invocation.callRealMethod(); - recycleCount.incrementAndGet(); - return null; - }) - .when(recycleBin) - .recycle(any()); - - final PlatformContext platformContext = buildContext(true, recycleBin); - - int stateCount = 5; - - int latestRound = random.nextInt(1_000, 10_000); - SignedState latestUncorruptedState = null; - for (int i = 0; i < stateCount; i++) { - latestRound += random.nextInt(100, 200); - final boolean corrupted = (stateCount - i) <= invalidStateCount; - final SignedState state = writeState(random, platformContext, latestRound, null, corrupted); - if (!corrupted) { - latestUncorruptedState = state; - } - } - - final SignedState loadedState = StartupStateUtils.loadStateFile( - platformContext, - selfId, - mainClassName, - swirldName, - new BasicSoftwareVersion(1), - mock(EmergencyRecoveryManager.class)) - .getNullable(); - - if (latestUncorruptedState != null) { - loadedState.getState().throwIfImmutable(); - loadedState.getState().throwIfDestroyed(); - - assertEquals(latestUncorruptedState.getRound(), loadedState.getRound()); - assertEquals( - latestUncorruptedState.getState().getHash(), - loadedState.getState().getHash()); - } else { - assertNull(loadedState); - } - - final Path savedStateDirectory = signedStateFilePath - .getSignedStateDirectory(mainClassName, selfId, swirldName, latestRound) - .getParent(); - - assertEquals(5 - invalidStateCount, Files.list(savedStateDirectory).count()); - assertEquals(invalidStateCount, recycleCount.get()); - } - - @Test - @DisplayName("Latest State Exact Epoch Hash Test") - void latestStateHasExactEpochHashTest() throws IOException, SignedStateLoadingException { - final Random random = getRandomPrintSeed(); - final PlatformContext platformContext = buildContext(false); - - int stateCount = 5; - - int latestRound = random.nextInt(1_000, 10_000); - SignedState latestState = null; - for (int i = 0; i < stateCount; i++) { - latestRound += random.nextInt(100, 200); - latestState = writeState(random, platformContext, latestRound, null, false); - } - - final Hash epoch = latestState.getState().getHash(); - final long epochRound = latestState.getRound(); - - final AtomicBoolean emergencyStateLoaded = new AtomicBoolean(false); - - final EmergencyRecoveryManager emergencyRecoveryManager = mock(EmergencyRecoveryManager.class); - when(emergencyRecoveryManager.isEmergencyStateRequired()).thenReturn(true); - doAnswer(invocation -> { - emergencyStateLoaded.set(true); - return null; - }) - .when(emergencyRecoveryManager) - .emergencyStateLoaded(); - - final State state = new State(epochRound, epoch, null); - final Recovery recovery = new Recovery(state, null, null, null); - final EmergencyRecoveryFile emergencyRecoveryFile = new EmergencyRecoveryFile(recovery); - when(emergencyRecoveryManager.getEmergencyRecoveryFile()).thenReturn(emergencyRecoveryFile); - - final SignedState loadedState = StartupStateUtils.loadStateFile( - platformContext, - selfId, - mainClassName, - swirldName, - new BasicSoftwareVersion(1), - emergencyRecoveryManager) - .get(); - - loadedState.getState().throwIfImmutable(); - loadedState.getState().throwIfDestroyed(); - - assertEquals(latestState.getRound(), loadedState.getRound()); - assertEquals(latestState.getState().getHash(), loadedState.getState().getHash()); - - assertTrue(emergencyStateLoaded.get()); - } - - @Test - @DisplayName("Previous State Has Epoch Hash Test") - void previousStateHasExactEpochHashTest() throws IOException, SignedStateLoadingException { - final Random random = getRandomPrintSeed(); - final PlatformContext platformContext = buildContext(false); - - int stateCount = 5; - - int latestRound = random.nextInt(1_000, 10_000); - SignedState targetState = null; - for (int i = 0; i < stateCount; i++) { - latestRound += random.nextInt(100, 200); - final SignedState state = writeState(random, platformContext, latestRound, null, false); - if (i == 2) { - targetState = state; - } - } - - final Hash epoch = targetState.getState().getHash(); - final long epochRound = targetState.getRound(); - - final AtomicBoolean emergencyStateLoaded = new AtomicBoolean(false); - - final EmergencyRecoveryManager emergencyRecoveryManager = mock(EmergencyRecoveryManager.class); - when(emergencyRecoveryManager.isEmergencyStateRequired()).thenReturn(true); - doAnswer(invocation -> { - emergencyStateLoaded.set(true); - return null; - }) - .when(emergencyRecoveryManager) - .emergencyStateLoaded(); - - final State state = new State(epochRound, epoch, null); - final Recovery recovery = new Recovery(state, null, null, null); - final EmergencyRecoveryFile emergencyRecoveryFile = new EmergencyRecoveryFile(recovery); - when(emergencyRecoveryManager.getEmergencyRecoveryFile()).thenReturn(emergencyRecoveryFile); - - final SignedState loadedState = StartupStateUtils.loadStateFile( - platformContext, - selfId, - mainClassName, - swirldName, - new BasicSoftwareVersion(1), - emergencyRecoveryManager) - .get(); - - loadedState.getState().throwIfImmutable(); - loadedState.getState().throwIfDestroyed(); - - assertEquals(targetState.getRound(), loadedState.getRound()); - assertEquals(targetState.getState().getHash(), loadedState.getState().getHash()); - - assertTrue(emergencyStateLoaded.get()); - } - - @ParameterizedTest - @ValueSource(ints = {0, 1, 2, 3, 4}) - @DisplayName("Previous State Has Epoch Hash") - void noStateHasEpochHashPreviousRoundExistsTest(final int startingStateIndex) - throws IOException, SignedStateLoadingException { - - final Random random = getRandomPrintSeed(); - final PlatformContext platformContext = buildContext(false); - - int stateCount = 5; - - int latestRound = random.nextInt(1_000, 10_000); - SignedState targetState = null; - for (int i = 0; i < stateCount; i++) { - latestRound += random.nextInt(100, 200); - final SignedState state = writeState(random, platformContext, latestRound, null, false); - if (i == startingStateIndex) { - targetState = state; - } - } - - final Hash epoch = randomHash(random); - final long epochRound = targetState.getRound() + 1; - - final AtomicBoolean emergencyStateLoaded = new AtomicBoolean(false); - - final EmergencyRecoveryManager emergencyRecoveryManager = mock(EmergencyRecoveryManager.class); - when(emergencyRecoveryManager.isEmergencyStateRequired()).thenReturn(true); - doAnswer(invocation -> { - emergencyStateLoaded.set(true); - return null; - }) - .when(emergencyRecoveryManager) - .emergencyStateLoaded(); - - final State state = new State(epochRound, epoch, null); - final Recovery recovery = new Recovery(state, null, null, null); - final EmergencyRecoveryFile emergencyRecoveryFile = new EmergencyRecoveryFile(recovery); - when(emergencyRecoveryManager.getEmergencyRecoveryFile()).thenReturn(emergencyRecoveryFile); - - final SignedState loadedState = StartupStateUtils.loadStateFile( - platformContext, - selfId, - mainClassName, - swirldName, - new BasicSoftwareVersion(1), - emergencyRecoveryManager) - .get(); - - loadedState.getState().throwIfImmutable(); - loadedState.getState().throwIfDestroyed(); - - assertEquals(targetState.getRound(), loadedState.getRound()); - assertEquals(targetState.getState().getHash(), loadedState.getState().getHash()); - - // As a sanity check, make sure the consensus timestamp is the same. This is generated randomly, so if this - // matches then it's a good signal that the correct state was loaded. - assertEquals( - targetState.getState().getPlatformState().getConsensusTimestamp(), - loadedState.getState().getPlatformState().getConsensusTimestamp()); - - assertFalse(emergencyStateLoaded.get()); - } - - @Test - @DisplayName("Recover From Genesis Test") - void recoverFromGenesisTest() throws IOException, SignedStateLoadingException { - final Random random = getRandomPrintSeed(); - final PlatformContext platformContext = buildContext(false); - - int stateCount = 5; - - int latestRound = random.nextInt(1_000, 10_000); - SignedState firstState = null; - for (int i = 0; i < stateCount; i++) { - latestRound += random.nextInt(100, 200); - final SignedState state = writeState(random, platformContext, latestRound, null, false); - if (i == 0) { - firstState = state; - } - } - - final Hash epoch = randomHash(random); - final long epochRound = firstState.getRound() - 1; - - final AtomicBoolean emergencyStateLoaded = new AtomicBoolean(false); - - final EmergencyRecoveryManager emergencyRecoveryManager = mock(EmergencyRecoveryManager.class); - when(emergencyRecoveryManager.isEmergencyStateRequired()).thenReturn(true); - doAnswer(invocation -> { - emergencyStateLoaded.set(true); - return null; - }) - .when(emergencyRecoveryManager) - .emergencyStateLoaded(); - - final State state = new State(epochRound, epoch, null); - final Recovery recovery = new Recovery(state, null, null, null); - final EmergencyRecoveryFile emergencyRecoveryFile = new EmergencyRecoveryFile(recovery); - when(emergencyRecoveryManager.getEmergencyRecoveryFile()).thenReturn(emergencyRecoveryFile); - - final SignedState loadedState = StartupStateUtils.loadStateFile( - platformContext, - selfId, - mainClassName, - swirldName, - new BasicSoftwareVersion(1), - emergencyRecoveryManager) - .getNullable(); - - assertNull(loadedState); - assertFalse(emergencyStateLoaded.get()); - } - - @Test - @DisplayName("State After Epoch State Is Present Test") - void stateAfterEpochStateIsPresentTest() throws IOException, SignedStateLoadingException { - final Random random = getRandomPrintSeed(); - final PlatformContext platformContext = buildContext(false); - - int stateCount = 5; - - final Hash epoch = randomHash(random); - - int latestRound = random.nextInt(1_000, 10_000); - SignedState targetState = null; - for (int i = 0; i < stateCount; i++) { - latestRound += random.nextInt(100, 200); - - final Hash epochHash = i == (stateCount - 1) ? epoch : null; - - final SignedState state = writeState(random, platformContext, latestRound, epochHash, false); - - if (i == (stateCount - 1)) { - targetState = state; - } - } - - final long epochRound = targetState.getRound() - 1; - - final AtomicBoolean emergencyStateLoaded = new AtomicBoolean(false); - - final EmergencyRecoveryManager emergencyRecoveryManager = mock(EmergencyRecoveryManager.class); - when(emergencyRecoveryManager.isEmergencyStateRequired()).thenReturn(true); - doAnswer(invocation -> { - emergencyStateLoaded.set(true); - return null; - }) - .when(emergencyRecoveryManager) - .emergencyStateLoaded(); - - final State state = new State(epochRound, epoch, null); - final Recovery recovery = new Recovery(state, null, null, null); - final EmergencyRecoveryFile emergencyRecoveryFile = new EmergencyRecoveryFile(recovery); - when(emergencyRecoveryManager.getEmergencyRecoveryFile()).thenReturn(emergencyRecoveryFile); - - final SignedState loadedState = StartupStateUtils.loadStateFile( - platformContext, - selfId, - mainClassName, - swirldName, - new BasicSoftwareVersion(1), - emergencyRecoveryManager) - .get(); - - loadedState.getState().throwIfImmutable(); - loadedState.getState().throwIfDestroyed(); - - assertEquals(targetState.getRound(), loadedState.getRound()); - assertEquals(targetState.getState().getHash(), loadedState.getState().getHash()); - - assertTrue(emergencyStateLoaded.get()); - } - - @Test - @DisplayName("Recovery Corrupted State No Recycling Test") - void recoveryCorruptedStateNoRecyclingTest() throws IOException { - final Random random = getRandomPrintSeed(); - final PlatformContext platformContext = buildContext(false); - - int stateCount = 5; - - int latestRound = random.nextInt(1_000, 10_000); - for (int i = 0; i < stateCount; i++) { - latestRound += random.nextInt(100, 200); - final boolean corrupted = i == stateCount - 1; - writeState(random, platformContext, latestRound, null, corrupted); - } - - final Hash epoch = randomHash(random); - final long epochRound = latestRound + 1; - - final AtomicBoolean emergencyStateLoaded = new AtomicBoolean(false); - - final EmergencyRecoveryManager emergencyRecoveryManager = mock(EmergencyRecoveryManager.class); - when(emergencyRecoveryManager.isEmergencyStateRequired()).thenReturn(true); - doAnswer(invocation -> { - emergencyStateLoaded.set(true); - return null; - }) - .when(emergencyRecoveryManager) - .emergencyStateLoaded(); - - final State state = new State(epochRound, epoch, null); - final Recovery recovery = new Recovery(state, null, null, null); - final EmergencyRecoveryFile emergencyRecoveryFile = new EmergencyRecoveryFile(recovery); - when(emergencyRecoveryManager.getEmergencyRecoveryFile()).thenReturn(emergencyRecoveryFile); - - assertThrows( - SignedStateLoadingException.class, - () -> StartupStateUtils.loadStateFile( - platformContext, - selfId, - mainClassName, - swirldName, - new BasicSoftwareVersion(1), - emergencyRecoveryManager)); - } - - @ParameterizedTest - @ValueSource(ints = {1, 2, 3, 4, 5}) - @DisplayName("Recovery Corrupted State Recycling Permitted Test") - void recoveryCorruptedStateRecyclingPermittedTest(final int invalidStateCount) - throws IOException, SignedStateLoadingException { - final Random random = getRandomPrintSeed(); final AtomicInteger recycleCount = new AtomicInteger(0); final RecycleBin recycleBin = spy(TestRecycleBin.getInstance()); @@ -664,32 +268,8 @@ void recoveryCorruptedStateRecyclingPermittedTest(final int invalidStateCount) } } - final Hash epoch = randomHash(random); - final long epochRound = latestRound + 1; - - final AtomicBoolean emergencyStateLoaded = new AtomicBoolean(false); - - final EmergencyRecoveryManager emergencyRecoveryManager = mock(EmergencyRecoveryManager.class); - when(emergencyRecoveryManager.isEmergencyStateRequired()).thenReturn(true); - doAnswer(invocation -> { - emergencyStateLoaded.set(true); - return null; - }) - .when(emergencyRecoveryManager) - .emergencyStateLoaded(); - - final State state = new State(epochRound, epoch, null); - final Recovery recovery = new Recovery(state, null, null, null); - final EmergencyRecoveryFile emergencyRecoveryFile = new EmergencyRecoveryFile(recovery); - when(emergencyRecoveryManager.getEmergencyRecoveryFile()).thenReturn(emergencyRecoveryFile); - final SignedState loadedState = StartupStateUtils.loadStateFile( - platformContext, - selfId, - mainClassName, - swirldName, - new BasicSoftwareVersion(1), - emergencyRecoveryManager) + platformContext, selfId, mainClassName, swirldName, new BasicSoftwareVersion(1)) .getNullable(); if (latestUncorruptedState != null) { @@ -697,16 +277,9 @@ void recoveryCorruptedStateRecyclingPermittedTest(final int invalidStateCount) loadedState.getState().throwIfDestroyed(); assertEquals(latestUncorruptedState.getRound(), loadedState.getRound()); - assertEquals( latestUncorruptedState.getState().getHash(), loadedState.getState().getHash()); - - // As a sanity check, make sure the consensus timestamp is the same. This is generated randomly, so if this - // matches then it's a good signal that the correct state was loaded. - assertEquals( - latestUncorruptedState.getState().getPlatformState().getConsensusTimestamp(), - loadedState.getState().getPlatformState().getConsensusTimestamp()); } else { assertNull(loadedState); } @@ -718,186 +291,4 @@ void recoveryCorruptedStateRecyclingPermittedTest(final int invalidStateCount) assertEquals(5 - invalidStateCount, Files.list(savedStateDirectory).count()); assertEquals(invalidStateCount, recycleCount.get()); } - - @Test - @DisplayName("doRecoveryCleanup() Initial Epoch Test") - void doRecoveryCleanupInitialEpochTest() throws IOException { - - final AtomicInteger recycleCount = new AtomicInteger(0); - final RecycleBin recycleBin = spy(TestRecycleBin.getInstance()); - // increment recycle count every time recycleBin.recycle() is called - doAnswer(invocation -> { - invocation.callRealMethod(); - recycleCount.incrementAndGet(); - return null; - }) - .when(recycleBin) - .recycle(any()); - final PlatformContext platformContext = buildContext(false, recycleBin); - - final Random random = getRandomPrintSeed(); - - int stateCount = 5; - int latestRound = random.nextInt(1_000, 10_000); - for (int i = 0; i < stateCount; i++) { - latestRound += random.nextInt(100, 200); - writeState(random, platformContext, latestRound, null, false); - } - - // Write a file into the PCES directory. This file will be deleted if the PCES is cleared. - final StateCommonConfig stateConfig = platformContext.getConfiguration().getConfigData(StateCommonConfig.class); - final PcesConfig preconsensusEventStreamConfig = - platformContext.getConfiguration().getConfigData(PcesConfig.class); - final Path savedStateDirectory = stateConfig.savedStateDirectory(); - final Path pcesDirectory = savedStateDirectory - .resolve(preconsensusEventStreamConfig.databaseDirectory()) - .resolve("0"); - Files.createDirectories(pcesDirectory); - - final Path markerFile = pcesDirectory.resolve("markerFile"); - final BufferedWriter writer = Files.newBufferedWriter(markerFile); - writer.write("this is a marker file"); - writer.close(); - - doRecoveryCleanup(platformContext, selfId, swirldName, mainClassName, null, latestRound); - - final Path signedStateDirectory = signedStateFilePath - .getSignedStateDirectory(mainClassName, selfId, swirldName, latestRound) - .getParent(); - - assertEquals(0, recycleCount.get()); - assertEquals(stateCount, Files.list(signedStateDirectory).count()); - - assertTrue(Files.exists(markerFile)); - } - - @Test - @DisplayName("doRecoveryCleanup() Already Cleaned Up Test") - void doRecoveryCleanupAlreadyCleanedUpTest() throws IOException { - final Random random = getRandomPrintSeed(); - - final Hash epoch = randomHash(random); - - final AtomicInteger recycleCount = new AtomicInteger(0); - final RecycleBin recycleBin = spy(TestRecycleBin.getInstance()); - // increment recycle count every time recycleBin.recycle() is called - doAnswer(invocation -> { - invocation.callRealMethod(); - recycleCount.incrementAndGet(); - return null; - }) - .when(recycleBin) - .recycle(any()); - final PlatformContext platformContext = buildContext(false, recycleBin); - - final Scratchpad scratchpad = - Scratchpad.create(platformContext, selfId, RecoveryScratchpad.class, RecoveryScratchpad.SCRATCHPAD_ID); - scratchpad.set(RecoveryScratchpad.EPOCH_HASH, epoch); - - int stateCount = 5; - int latestRound = random.nextInt(1_000, 10_000); - for (int i = 0; i < stateCount; i++) { - latestRound += random.nextInt(100, 200); - writeState(random, platformContext, latestRound, null, false); - } - - // Write a file into the PCES directory. This file will be deleted if the PCES is cleared. - final StateCommonConfig stateConfig = platformContext.getConfiguration().getConfigData(StateCommonConfig.class); - final PcesConfig preconsensusEventStreamConfig = - platformContext.getConfiguration().getConfigData(PcesConfig.class); - final Path savedStateDirectory = stateConfig.savedStateDirectory(); - final Path pcesDirectory = savedStateDirectory - .resolve(preconsensusEventStreamConfig.databaseDirectory()) - .resolve("0"); - Files.createDirectories(pcesDirectory); - - final Path markerFile = pcesDirectory.resolve("markerFile"); - final BufferedWriter writer = Files.newBufferedWriter(markerFile); - writer.write("this is a marker file"); - writer.close(); - - doRecoveryCleanup(platformContext, selfId, swirldName, mainClassName, epoch, latestRound); - - final Path signedStateDirectory = signedStateFilePath - .getSignedStateDirectory(mainClassName, selfId, swirldName, latestRound) - .getParent(); - - assertEquals(0, recycleCount.get()); - assertEquals(stateCount, Files.list(signedStateDirectory).count()); - - assertTrue(Files.exists(markerFile)); - } - - @ParameterizedTest - @ValueSource(ints = {1, 2, 3, 4, 5}) - @DisplayName("doRecoveryCleanup() Work Required Test") - void doRecoveryCleanupWorkRequiredTest(final int statesToDelete) throws IOException { - final Random random = getRandomPrintSeed(); - - final AtomicInteger recycleCount = new AtomicInteger(0); - final RecycleBin recycleBin = spy(TestRecycleBin.getInstance()); - // increment recycle count every time recycleBin.recycle() is called - doAnswer(invocation -> { - invocation.callRealMethod(); - recycleCount.incrementAndGet(); - return null; - }) - .when(recycleBin) - .recycle(any()); - - final PlatformContext platformContext = buildContext(false, recycleBin); - - int stateCount = 5; - int latestRound = random.nextInt(1_000, 10_000); - SignedState targetState = null; - for (int i = 0; i < stateCount; i++) { - latestRound += random.nextInt(100, 200); - final SignedState signedState = writeState(random, platformContext, latestRound, null, false); - if (i == (stateCount - statesToDelete - 1)) { - targetState = signedState; - } - } - - final Hash epoch = randomHash(random); - final long epochRound; - if (statesToDelete == stateCount) { - // lower round than what all states have - epochRound = 999; - } else { - epochRound = targetState.getRound(); - } - - // Write a file into the PCES directory. This file will should be deleted - final StateCommonConfig stateConfig = platformContext.getConfiguration().getConfigData(StateCommonConfig.class); - final PcesConfig preconsensusEventStreamConfig = - platformContext.getConfiguration().getConfigData(PcesConfig.class); - final Path savedStateDirectory = stateConfig.savedStateDirectory(); - final Path pcesDirectory = savedStateDirectory - .resolve(preconsensusEventStreamConfig.databaseDirectory()) - .resolve("0"); - Files.createDirectories(pcesDirectory); - - final Path markerFile = pcesDirectory.resolve("markerFile"); - final BufferedWriter writer = Files.newBufferedWriter(markerFile); - writer.write("this is a marker file"); - writer.close(); - - doRecoveryCleanup(platformContext, selfId, swirldName, mainClassName, epoch, epochRound); - - final Scratchpad scratchpad = - Scratchpad.create(platformContext, selfId, RecoveryScratchpad.class, RecoveryScratchpad.SCRATCHPAD_ID); - - assertEquals(epoch, scratchpad.get(RecoveryScratchpad.EPOCH_HASH)); - - final Path signedStateDirectory = signedStateFilePath - .getSignedStateDirectory(mainClassName, selfId, swirldName, latestRound) - .getParent(); - - assertEquals(statesToDelete, recycleCount.get()); - - assertEquals( - stateCount - statesToDelete, Files.list(signedStateDirectory).count()); - - assertTrue(Files.exists(markerFile)); - } } diff --git a/hedera-node/hedera-app-spi/src/test/java/com/hedera/node/app/spi/state/ReadableKVStateBaseTest.java b/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/state/spi/ReadableKVStateBaseTest.java similarity index 96% rename from hedera-node/hedera-app-spi/src/test/java/com/hedera/node/app/spi/state/ReadableKVStateBaseTest.java rename to platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/state/spi/ReadableKVStateBaseTest.java index 4278efd01226..66bb2f56f1fd 100644 --- a/hedera-node/hedera-app-spi/src/test/java/com/hedera/node/app/spi/state/ReadableKVStateBaseTest.java +++ b/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/state/spi/ReadableKVStateBaseTest.java @@ -14,12 +14,13 @@ * limitations under the License. */ -package com.hedera.node.app.spi.state; +package com.swirlds.platform.state.spi; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; -import com.hedera.node.app.spi.fixtures.state.MapReadableKVState; +import com.swirlds.platform.test.fixtures.state.MapReadableKVState; +import com.swirlds.platform.test.fixtures.state.StateTestBase; import java.util.HashMap; import java.util.Map; import org.junit.jupiter.api.BeforeEach; @@ -31,7 +32,7 @@ * tested in isolation of specific subclasses. Those that cannot be (such as {@link * ReadableKVStateBase#reset()}) will be covered by other tests in addition to this one. */ -class ReadableKVStateBaseTest extends StateTestBase { +public class ReadableKVStateBaseTest extends StateTestBase { private ReadableKVStateBase state; protected Map backingMap; diff --git a/hedera-node/hedera-app-spi/src/test/java/com/hedera/node/app/spi/state/ReadableSingletonStateBaseTest.java b/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/state/spi/ReadableSingletonStateBaseTest.java similarity index 98% rename from hedera-node/hedera-app-spi/src/test/java/com/hedera/node/app/spi/state/ReadableSingletonStateBaseTest.java rename to platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/state/spi/ReadableSingletonStateBaseTest.java index 15d6990a31c6..1580e1a7b58e 100644 --- a/hedera-node/hedera-app-spi/src/test/java/com/hedera/node/app/spi/state/ReadableSingletonStateBaseTest.java +++ b/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/state/spi/ReadableSingletonStateBaseTest.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.hedera.node.app.spi.state; +package com.swirlds.platform.state.spi; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; diff --git a/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/state/spi/SingletonStateTestBase.java b/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/state/spi/SingletonStateTestBase.java new file mode 100644 index 000000000000..39ba74e7793e --- /dev/null +++ b/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/state/spi/SingletonStateTestBase.java @@ -0,0 +1,102 @@ +/* + * Copyright (C) 2023-2024 Hedera Hashgraph, LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.swirlds.platform.state.spi; + +import static org.assertj.core.api.Assertions.assertThat; + +import com.swirlds.platform.test.fixtures.state.StateTestBase; +import java.util.concurrent.atomic.AtomicReference; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +abstract class SingletonStateTestBase extends StateTestBase { + protected AtomicReference backingStore = new AtomicReference<>(AUSTRALIA); + + protected abstract ReadableSingletonStateBase createState(); + + /** + * When we are asked to get an unknown item (something not in the backing store), the {@link + * ReadableSingletonStateBase} still needs to remember we read this value, because the fact that + * the value was missing might be an important piece of information when working in pre-handle. + */ + @Test + @DisplayName("`get` with no value") + void testNonExistingGet() { + backingStore.set(null); + final var state = createState(); + assertThat(state.isRead()).isFalse(); + assertThat(state.get()).isNull(); + assertThat(state.isRead()).isTrue(); + } + + /** + * When asked to get a known item, not only is it returned, but we also record this in the + * "readKeys". + */ + @Test + @DisplayName("`get` of a known item returns that item") + void testExistingGet() { + backingStore.set(BRAZIL); + final var state = createState(); + assertThat(state.isRead()).isFalse(); + assertThat(state.get()).isEqualTo(BRAZIL); + assertThat(state.isRead()).isTrue(); + } + + @Test + @DisplayName("`reset` clears the readKeys cache") + void testReset() { + // Given a state which has been read and has the "read" flag set + backingStore.set(CHAD); + final var state = createState(); + assertThat(state.get()).isEqualTo(CHAD); + assertThat(state.isRead()).isTrue(); + + // When we reset + state.reset(); + + // Then the "read" flag is false + assertThat(state.isRead()).isFalse(); + + // And when the value in the backing store is changed + backingStore.set(DENMARK); + // Then it doesn't affect the state + assertThat(state.isRead()).isFalse(); + + // Until we ask for the value, and then it shows the new value + assertThat(state.get()).isEqualTo(DENMARK); + assertThat(state.isRead()).isTrue(); + } + + /** + * This specific behavior should never show up in actual code, because all states associated + * with a transaction are destroyed after having been committed (or in a nested/wrapped + * scenario, we shouldn't be updating the backing data while a wrapped data is being used). + * However, in such a situation, the right behavior is to show the updated values in the backing + * store, unless the value has been overridden in this state (which cannot happen on readable + * states). + */ + @Test + @DisplayName("State sees changes committed to backend") + void dirtyRead() { + backingStore.set(CHAD); + final var state = createState(); + assertThat(state.get()).isEqualTo(CHAD); + backingStore.set(DENMARK); + assertThat(state.get()).isEqualTo(DENMARK); + } +} diff --git a/hedera-node/hedera-app-spi/src/test/java/com/hedera/node/app/spi/state/WritableKVStateBaseTest.java b/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/state/spi/WritableKVStateBaseTest.java similarity index 99% rename from hedera-node/hedera-app-spi/src/test/java/com/hedera/node/app/spi/state/WritableKVStateBaseTest.java rename to platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/state/spi/WritableKVStateBaseTest.java index 6e6890b573c7..97486f57cf3e 100644 --- a/hedera-node/hedera-app-spi/src/test/java/com/hedera/node/app/spi/state/WritableKVStateBaseTest.java +++ b/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/state/spi/WritableKVStateBaseTest.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.hedera.node.app.spi.state; +package com.swirlds.platform.state.spi; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.AssertionsForClassTypes.assertThatThrownBy; @@ -22,7 +22,7 @@ import static org.junit.jupiter.api.Assertions.fail; import static org.mockito.ArgumentMatchers.anyString; -import com.hedera.node.app.spi.fixtures.state.MapWritableKVState; +import com.swirlds.platform.test.fixtures.state.MapWritableKVState; import edu.umd.cs.findbugs.annotations.NonNull; import java.util.ArrayList; import java.util.HashMap; @@ -47,7 +47,7 @@ *

    In this test, we create a backing store with only {(A=APPLE),(B=BANANA)}. We then have a * series of tests that will replace the values for A, B, or remove them, or add new values. */ -class WritableKVStateBaseTest extends ReadableKVStateBaseTest { +public class WritableKVStateBaseTest extends ReadableKVStateBaseTest { private static final String NUM_ITERATIONS_ARG = "WritableKVStateBaseTest.DeterministicUpdates.numIterations"; protected WritableKVStateBase state; diff --git a/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/state/spi/WritableSingletonStateBaseTest.java b/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/state/spi/WritableSingletonStateBaseTest.java new file mode 100644 index 000000000000..770f2d3f465e --- /dev/null +++ b/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/state/spi/WritableSingletonStateBaseTest.java @@ -0,0 +1,186 @@ +/* + * Copyright (C) 2023-2024 Hedera Hashgraph, LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.swirlds.platform.state.spi; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; + +public class WritableSingletonStateBaseTest extends SingletonStateTestBase { + + @Override + protected WritableSingletonStateBase createState() { + return new WritableSingletonStateBase<>(COUNTRY_STATE_KEY, backingStore::get, backingStore::set); + } + + protected String getBackingStoreValue() { + return backingStore.get(); + } + + @Nested + @DisplayName("Constructor Tests") + class ConstructorTest { + @Test + @DisplayName("Constructor throws NPE if stateKey is null") + void nullStateKey() { + //noinspection DataFlowIssue + assertThatThrownBy(() -> new WritableSingletonStateBase<>(null, () -> AUSTRALIA, val -> {})) + .isInstanceOf(NullPointerException.class); + } + + /** Make sure the constructor is holding onto the state key properly */ + @Test + @DisplayName("The state key must match what was provided in the constructor") + void testStateKey() { + final var state = new WritableSingletonStateBase<>(COUNTRY_STATE_KEY, () -> AUSTRALIA, val -> {}); + assertThat(state.getStateKey()).isEqualTo(COUNTRY_STATE_KEY); + } + + @Test + @DisplayName("Constructor throws NPE if backingStoreAccessor is null") + void nullAccessor() { + //noinspection DataFlowIssue + assertThatThrownBy(() -> new WritableSingletonStateBase<>(COUNTRY_STATE_KEY, null, val -> {})) + .isInstanceOf(NullPointerException.class); + } + + @Test + @DisplayName("Constructor throws NPE if backingStoreMutator is null") + void nullMutator() { + //noinspection DataFlowIssue + assertThatThrownBy(() -> new WritableSingletonStateBase<>(COUNTRY_STATE_KEY, () -> AUSTRALIA, null)) + .isInstanceOf(NullPointerException.class); + } + } + + @Nested + @DisplayName("Modified Flag Tests") + class ModifiedTest { + @Test + @DisplayName("`modified` is false on a new state") + void initialValueForModified() { + final var state = createState(); + assertThat(state.isModified()).isFalse(); + } + + @Test + @DisplayName("`modified` is true after `put`") + void modifiedAfterPut() { + final var state = createState(); + state.put(BRAZIL); + assertThat(state.isModified()).isTrue(); + } + + @Test + @DisplayName("`modified` is not impacted by `get`") + void readDoesNotModify() { + final var state = createState(); + assertThat(state.get()).isEqualTo(AUSTRALIA); + assertThat(state.isModified()).isFalse(); + } + } + + @Nested + @DisplayName("Put Tests") + class PutTest { + @Test + @DisplayName("`get` reads `put` value") + void getAfterPut() { + final var state = createState(); + assertThat(state.get()).isEqualTo(AUSTRALIA); + state.put(BRAZIL); + assertThat(state.get()).isEqualTo(BRAZIL); + } + } + + @Nested + @DisplayName("Commit Tests") + class CommitTest { + @Test + @DisplayName("`commit` of clean state is no-op") + void commitClean() { + final var state = createState(); + state.commit(); + assertThat(getBackingStoreValue()).isEqualTo(AUSTRALIA); + } + + @Test + @DisplayName("`commit` of newly put value stores in backing store") + void commitDirty() { + final var state = createState(); + state.put(FRANCE); + state.commit(); + assertThat(getBackingStoreValue()).isEqualTo(FRANCE); + } + + @Test + @DisplayName("`commit` of newly put null stores in backing store") + void commitNull() { + final var state = createState(); + state.put(null); + state.commit(); + assertThat(getBackingStoreValue()).isNull(); + } + } + + @Nested + @DisplayName("Reset Tests") + class ResetTest { + @Test + @DisplayName("`modified` is cleared on reset") + void modifiedCleared() { + final var state = createState(); + state.put(BRAZIL); + assertThat(state.isModified()).isTrue(); + state.reset(); + assertThat(state.isModified()).isFalse(); + } + + @Test + @DisplayName("modified value is cleared on reset") + void valueCleared() { + final var state = createState(); + state.put(BRAZIL); + state.reset(); + assertThat(state.get()).isEqualTo(AUSTRALIA); + } + } + + /** + * This specific behavior should never show up in actual code, because all states associated + * with a transaction are destroyed after having been committed (or in a nested/wrapped + * scenario, we shouldn't be updating the backing data while a wrapped data is being used). + * However, in such a situation, the right behavior is to show the updated values in the backing + * store, unless the value has been overridden in this state. + */ + @Test + @DisplayName("State sees changes committed to backend if not modified on the state") + void dirtyRead() { + backingStore.set(CHAD); + final var state = createState(); + assertThat(state.get()).isEqualTo(CHAD); + backingStore.set(DENMARK); + assertThat(state.get()).isEqualTo(DENMARK); // Sees change + state.put(ESTONIA); + assertThat(state.get()).isEqualTo(ESTONIA); + backingStore.set(FRANCE); + assertThat(state.get()).isEqualTo(ESTONIA); // Does not see change + } +} diff --git a/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/sync/protocol/SyncProtocolFactoryTests.java b/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/sync/protocol/SyncProtocolFactoryTests.java index d11f7dfa4ce1..77fb3f4b78b1 100644 --- a/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/sync/protocol/SyncProtocolFactoryTests.java +++ b/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/sync/protocol/SyncProtocolFactoryTests.java @@ -104,7 +104,7 @@ void shouldInitiate() { () -> false, sleepAfterSync, syncMetrics, - statusNexus); + statusNexus::getCurrentStatus); assertEquals(2, permitProvider.getNumAvailable()); assertTrue(syncProtocolFactory.build(peerId).shouldInitiate()); @@ -125,7 +125,7 @@ void initiateCooldown() { () -> false, Duration.ofMillis(100), syncMetrics, - statusNexus); + statusNexus::getCurrentStatus); final Protocol protocol = syncProtocolFactory.build(peerId); // do an initial sync, so we can verify that the resulting cooldown period is respected assertTrue(protocol.shouldInitiate()); @@ -164,7 +164,7 @@ void incorrectStatusToInitiate() { () -> false, sleepAfterSync, syncMetrics, - statusNexus); + statusNexus::getCurrentStatus); final Protocol protocol = syncProtocolFactory.build(peerId); assertEquals(2, permitProvider.getNumAvailable()); @@ -184,7 +184,7 @@ void noPermitAvailableToInitiate() { () -> false, sleepAfterSync, syncMetrics, - statusNexus); + statusNexus::getCurrentStatus); final Protocol protocol = syncProtocolFactory.build(peerId); assertEquals(2, permitProvider.getNumAvailable()); @@ -209,7 +209,7 @@ void peerAgnosticChecksFailAtInitiate() { () -> true, sleepAfterSync, syncMetrics, - statusNexus); + statusNexus::getCurrentStatus); final Protocol protocol = syncProtocolFactory.build(peerId); assertEquals(2, permitProvider.getNumAvailable()); @@ -232,7 +232,7 @@ void fallenBehindAtInitiate() { () -> false, sleepAfterSync, syncMetrics, - statusNexus); + statusNexus::getCurrentStatus); final Protocol protocol = syncProtocolFactory.build(peerId); assertEquals(2, permitProvider.getNumAvailable()); @@ -254,7 +254,7 @@ void initiateForFallenBehind() { () -> false, sleepAfterSync, syncMetrics, - statusNexus); + statusNexus::getCurrentStatus); final Protocol protocol = syncProtocolFactory.build(peerId); assertEquals(2, permitProvider.getNumAvailable()); @@ -275,7 +275,7 @@ void initiateForCriticalQuorum() { () -> false, sleepAfterSync, syncMetrics, - statusNexus); + statusNexus::getCurrentStatus); final Protocol protocol = syncProtocolFactory.build(new NodeId(6)); assertEquals(2, permitProvider.getNumAvailable()); @@ -300,7 +300,7 @@ void shouldAccept() { () -> false, sleepAfterSync, syncMetrics, - statusNexus); + statusNexus::getCurrentStatus); final Protocol protocol = syncProtocolFactory.build(peerId); assertTrue(protocol.shouldAccept()); @@ -321,7 +321,7 @@ void acceptCooldown() { () -> false, Duration.ofMillis(100), syncMetrics, - statusNexus); + statusNexus::getCurrentStatus); final Protocol protocol = syncProtocolFactory.build(peerId); // do an initial sync, so we can verify that the resulting cooldown period is respected @@ -360,7 +360,7 @@ void incorrectStatusToAccept() { () -> false, sleepAfterSync, syncMetrics, - statusNexus); + statusNexus::getCurrentStatus); final Protocol protocol = syncProtocolFactory.build(peerId); assertEquals(2, permitProvider.getNumAvailable()); @@ -388,7 +388,7 @@ void noPermitAvailableToAccept() { () -> false, sleepAfterSync, syncMetrics, - statusNexus); + statusNexus::getCurrentStatus); final Protocol protocol = syncProtocolFactory.build(peerId); assertFalse(protocol.shouldAccept()); @@ -407,7 +407,7 @@ void peerAgnosticChecksFailAtAccept() { () -> true, sleepAfterSync, syncMetrics, - statusNexus); + statusNexus::getCurrentStatus); final Protocol protocol = syncProtocolFactory.build(peerId); assertEquals(2, permitProvider.getNumAvailable()); @@ -430,7 +430,7 @@ void fallenBehindAtAccept() { () -> false, sleepAfterSync, syncMetrics, - statusNexus); + statusNexus::getCurrentStatus); final Protocol protocol = syncProtocolFactory.build(peerId); assertEquals(2, permitProvider.getNumAvailable()); @@ -450,7 +450,7 @@ void permitClosesAfterFailedAccept() { () -> false, sleepAfterSync, syncMetrics, - statusNexus); + statusNexus::getCurrentStatus); final Protocol protocol = syncProtocolFactory.build(peerId); assertEquals(2, permitProvider.getNumAvailable()); @@ -472,7 +472,7 @@ void permitClosesAfterFailedInitiate() { () -> false, sleepAfterSync, syncMetrics, - statusNexus); + statusNexus::getCurrentStatus); final Protocol protocol = syncProtocolFactory.build(peerId); assertEquals(2, permitProvider.getNumAvailable()); @@ -494,7 +494,7 @@ void successfulInitiatedProtocol() { () -> false, sleepAfterSync, syncMetrics, - statusNexus); + statusNexus::getCurrentStatus); final Protocol protocol = syncProtocolFactory.build(peerId); assertEquals(2, permitProvider.getNumAvailable()); @@ -516,7 +516,7 @@ void successfulAcceptedProtocol() { () -> false, sleepAfterSync, syncMetrics, - statusNexus); + statusNexus::getCurrentStatus); final Protocol protocol = syncProtocolFactory.build(peerId); assertEquals(2, permitProvider.getNumAvailable()); @@ -539,7 +539,7 @@ void rethrowParallelExecutionException() () -> false, sleepAfterSync, syncMetrics, - statusNexus); + statusNexus::getCurrentStatus); final Protocol protocol = syncProtocolFactory.build(peerId); // mock synchronize to throw a ParallelExecutionException @@ -568,7 +568,7 @@ void rethrowRootCauseIOException() () -> false, sleepAfterSync, syncMetrics, - statusNexus); + statusNexus::getCurrentStatus); final Protocol protocol = syncProtocolFactory.build(peerId); // mock synchronize to throw a ParallelExecutionException with root cause being an IOException @@ -596,7 +596,7 @@ void rethrowSyncException() throws ParallelExecutionException, IOException, Sync () -> false, sleepAfterSync, syncMetrics, - statusNexus); + statusNexus::getCurrentStatus); final Protocol protocol = syncProtocolFactory.build(peerId); // mock synchronize to throw a SyncException @@ -623,7 +623,7 @@ void acceptOnSimultaneousInitiate() { () -> false, sleepAfterSync, syncMetrics, - statusNexus); + statusNexus::getCurrentStatus); final Protocol protocol = syncProtocolFactory.build(peerId); assertTrue(protocol.acceptOnSimultaneousInitiate()); diff --git a/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/system/address/AddressBookTests.java b/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/system/address/AddressBookTests.java index 9ea94db9b620..eea280b7f684 100644 --- a/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/system/address/AddressBookTests.java +++ b/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/system/address/AddressBookTests.java @@ -31,7 +31,10 @@ import com.swirlds.common.io.streams.SerializableDataInputStream; import com.swirlds.common.io.streams.SerializableDataOutputStream; import com.swirlds.common.platform.NodeId; -import com.swirlds.platform.test.fixtures.addressbook.RandomAddressBookGenerator; +import com.swirlds.common.test.fixtures.Randotron; +import com.swirlds.platform.test.fixtures.addressbook.RandomAddressBookBuilder; +import com.swirlds.platform.test.fixtures.addressbook.RandomAddressBuilder; +import edu.umd.cs.findbugs.annotations.NonNull; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; @@ -123,7 +126,8 @@ private void validateAddressBookContents( @Test @DisplayName("Address Book Update Weight Test") void validateAddressBookUpdateWeightTest() { - final RandomAddressBookGenerator generator = new RandomAddressBookGenerator(getRandomPrintSeed()).setSize(10); + final RandomAddressBookBuilder generator = + RandomAddressBookBuilder.create(getRandomPrintSeed()).withSize(10); final AddressBook addressBook = generator.build(); final Address address = addressBook.getAddress(addressBook.getNodeId(0)); final long totalWeight = addressBook.getTotalWeight(); @@ -146,16 +150,29 @@ void validateAddressBookUpdateWeightTest() { "should not be able to set weight for non-existent node"); } + /** + * Build an address to be added to an address book. + * + * @param random the random number generator to use + * @param addressBook the address book to add the address to + * @return a new address + */ + @NonNull + private static Address buildNextAddress(@NonNull final Random random, @NonNull final AddressBook addressBook) { + return RandomAddressBuilder.create(random) + .withNodeId(new NodeId(addressBook.getNextNodeId().id() + random.nextInt(0, 3))) + .build(); + } + @Test @DisplayName("Add/Remove Test") void addRemoveTest() { final Random random = getRandomPrintSeed(); - final RandomAddressBookGenerator generator = new RandomAddressBookGenerator(random) - .setMinimumWeight(0) - .setAverageWeight(100) - .setWeightStandardDeviation(50) - .setSize(100); + final RandomAddressBookBuilder generator = RandomAddressBookBuilder.create(random) + .withAverageWeight(100) + .withWeightStandardDeviation(50) + .withSize(100); final AddressBook addressBook = generator.build(); final Map expectedAddresses = new HashMap<>(); @@ -169,7 +186,7 @@ void addRemoveTest() { assertNotNull(expectedAddresses.remove(nodeIdToRemove), "item to be removed should be present"); addressBook.remove(nodeIdToRemove); } else { - final Address newAddress = generator.buildNextAddress(); + final Address newAddress = buildNextAddress(random, addressBook); expectedAddresses.put(newAddress.getNodeId(), newAddress); addressBook.add(newAddress); } @@ -184,7 +201,8 @@ void addRemoveTest() { void updateTest() { final Random random = getRandomPrintSeed(); - final RandomAddressBookGenerator generator = new RandomAddressBookGenerator(random).setSize(100); + final RandomAddressBookBuilder generator = + RandomAddressBookBuilder.create(random).withSize(100); final AddressBook addressBook = generator.build(); final Map expectedAddresses = new HashMap<>(); @@ -195,7 +213,7 @@ void updateTest() { final int indexToUpdate = random.nextInt(addressBook.getSize()); final NodeId nodeIdToUpdate = addressBook.getNodeId(indexToUpdate); - final Address updatedAddress = generator.buildNextAddress().copySetNodeId(nodeIdToUpdate); + final Address updatedAddress = buildNextAddress(random, addressBook).copySetNodeId(nodeIdToUpdate); expectedAddresses.put(nodeIdToUpdate, updatedAddress); addressBook.add(updatedAddress); @@ -208,7 +226,9 @@ void updateTest() { @Test @DisplayName("Get/Set Round Test") void getSetRoundTest() { - final AddressBook addressBook = new RandomAddressBookGenerator().build(); + final Randotron randotron = Randotron.create(); + final AddressBook addressBook = + RandomAddressBookBuilder.create(randotron).build(); addressBook.setRound(1234); assertEquals(1234, addressBook.getRound(), "unexpected round"); @@ -217,19 +237,19 @@ void getSetRoundTest() { @Test @DisplayName("Equality Test") void equalityTest() { - final Random random = getRandomPrintSeed(); - final long seed = random.nextLong(); + final Randotron randotron = Randotron.create(); final AddressBook addressBook1 = - new RandomAddressBookGenerator(seed).setSize(100).build(); - final AddressBook addressBook2 = - new RandomAddressBookGenerator(seed).setSize(100).build(); + RandomAddressBookBuilder.create(randotron).withSize(100).build(); + final AddressBook addressBook2 = RandomAddressBookBuilder.create(randotron.copyAndReset()) + .withSize(100) + .build(); assertEquals(addressBook1, addressBook2, "address books should be the same"); assertEquals(addressBook1.hashCode(), addressBook2.hashCode(), "address books should have the same hash code"); final Address updatedAddress = addressBook1 - .getAddress(addressBook1.getNodeId(random.nextInt(100))) + .getAddress(addressBook1.getNodeId(randotron.nextInt(100))) .copySetNickname("foobar"); addressBook1.add(updatedAddress); @@ -239,7 +259,8 @@ void equalityTest() { @Test @DisplayName("toString() Test") void atoStringSanityTest() { - final AddressBook addressBook = new RandomAddressBookGenerator(getRandomPrintSeed()).build(); + final AddressBook addressBook = + RandomAddressBookBuilder.create(getRandomPrintSeed()).build(); // Basic sanity check, make sure this doesn't throw an exception System.out.println(addressBook); @@ -250,8 +271,8 @@ void atoStringSanityTest() { void serializationTest() throws IOException, ConstructableRegistryException { ConstructableRegistry.getInstance().registerConstructables("com.swirlds"); - final AddressBook original = new RandomAddressBookGenerator(getRandomPrintSeed()) - .setSize(100) + final AddressBook original = RandomAddressBookBuilder.create(getRandomPrintSeed()) + .withSize(100) .build(); // FQDN Support: addresses must support long text based host names. @@ -282,12 +303,11 @@ void serializationTest() throws IOException, ConstructableRegistryException { @Test @DisplayName("clear() test") void clearTest() { - final AddressBook addressBook = new RandomAddressBookGenerator(getRandomPrintSeed()) - .setSize(100) - .setMinimumWeight(0) - .setMaximumWeight(10) - .setAverageWeight(5) - .setWeightStandardDeviation(5) + final AddressBook addressBook = RandomAddressBookBuilder.create(getRandomPrintSeed()) + .withSize(100) + .withMaximumWeight(10) + .withAverageWeight(5) + .withWeightStandardDeviation(5) .build(); validateAddressBookConsistency(addressBook); @@ -304,11 +324,12 @@ void clearTest() { @Test @DisplayName("Reinsertion Test") void reinsertionTest() { - final RandomAddressBookGenerator generator = new RandomAddressBookGenerator(getRandomPrintSeed()); + final Randotron randotron = Randotron.create(); + final AddressBook addressBook = new AddressBook(); for (int i = 0; i < 100; i++) { - addressBook.add(generator.buildNextAddress()); + addressBook.add(buildNextAddress(randotron, addressBook)); } validateAddressBookConsistency(addressBook); @@ -324,13 +345,16 @@ void reinsertionTest() { @Test @DisplayName("Out Of Order add() Test") void outOfOrderAddTest() { - final RandomAddressBookGenerator generator = new RandomAddressBookGenerator(getRandomPrintSeed()).setSize(100); + final Randotron randotron = Randotron.create(); + + final RandomAddressBookBuilder generator = + RandomAddressBookBuilder.create(randotron).withSize(100); final AddressBook addressBook = generator.build(); // The address book has gaps. Make sure we can't insert anything into those gaps. for (int i = 0; i < addressBook.getNextNodeId().id(); i++) { - final Address address = generator.buildNextAddress().copySetNodeId(new NodeId(i)); + final Address address = buildNextAddress(randotron, addressBook).copySetNodeId(new NodeId(i)); if (addressBook.contains(new NodeId(i))) { // It's ok to update an existing address @@ -350,25 +374,26 @@ void outOfOrderAddTest() { @Test @DisplayName("Max Size Test") void maxSizeTest() { - final RandomAddressBookGenerator generator = new RandomAddressBookGenerator(getRandomPrintSeed()); + final Randotron randotron = Randotron.create(); + final AddressBook addressBook = new AddressBook(); for (int i = 0; i < AddressBook.MAX_ADDRESSES; i++) { - addressBook.add(generator.buildNextAddress()); + addressBook.add(buildNextAddress(randotron, addressBook)); } validateAddressBookConsistency(addressBook); assertThrows( IllegalStateException.class, - () -> addressBook.add(generator.buildNextAddress()), + () -> addressBook.add(buildNextAddress(randotron, addressBook)), "shouldn't be able to exceed max address book size"); } @Test @DisplayName("setNextNodeId() Test") void setNextNodeIdTest() { - final RandomAddressBookGenerator generator = new RandomAddressBookGenerator(getRandomPrintSeed()); + final RandomAddressBookBuilder generator = RandomAddressBookBuilder.create(getRandomPrintSeed()); final AddressBook addressBook = generator.build(); final NodeId nextId = addressBook.getNextNodeId(); @@ -386,7 +411,7 @@ void setNextNodeIdTest() { @Test @DisplayName("Roundtrip address book serialization and deserialization compatible with config.txt") void roundTripSerializeAndDeserializeCompatibleWithConfigTxt() throws ParseException { - final RandomAddressBookGenerator generator = new RandomAddressBookGenerator(getRandomPrintSeed()); + final RandomAddressBookBuilder generator = RandomAddressBookBuilder.create(getRandomPrintSeed()); final AddressBook addressBook = generator.build(); // FQDN Support: modify address in address book to have a text based host name. addressBook.add(addressBook @@ -460,8 +485,9 @@ private void validateParseException(final String addressBook, final int part) { @Test @DisplayName("Reconnect Address Book Comparison Test") public void reconnectAddressBookComparisonTest() { + final Randotron randotron = Randotron.create(); final AddressBook addressBook = - new RandomAddressBookGenerator().setSize(10).build(); + RandomAddressBookBuilder.create(randotron).withSize(10).build(); assertDoesNotThrow(() -> AddressBookUtils.verifyReconnectAddressBooks(addressBook, addressBook.copy())); // test exception on size mismatch diff --git a/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/system/address/AddressBookValidatorTests.java b/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/system/address/AddressBookValidatorTests.java index cd8fd938dc3d..60e67886471e 100644 --- a/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/system/address/AddressBookValidatorTests.java +++ b/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/system/address/AddressBookValidatorTests.java @@ -28,7 +28,14 @@ import static org.junit.jupiter.api.Assertions.assertTrue; import com.swirlds.common.platform.NodeId; -import com.swirlds.platform.test.fixtures.addressbook.RandomAddressBookGenerator; +import com.swirlds.common.test.fixtures.Randotron; +import com.swirlds.platform.test.fixtures.addressbook.RandomAddressBookBuilder; +import com.swirlds.platform.test.fixtures.addressbook.RandomAddressBuilder; +import edu.umd.cs.findbugs.annotations.NonNull; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Objects; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; @@ -38,14 +45,15 @@ class AddressBookValidatorTests { @Test @DisplayName("hasNonZeroWeight Test") void hasNonZeroWeightTest() { + final Randotron randotron = Randotron.create(); final AddressBook emptyAddressBook = - new RandomAddressBookGenerator().setSize(0).build(); - final AddressBook zeroWeightAddressBook = new RandomAddressBookGenerator() - .setSize(10) - .setCustomWeightGenerator(n -> 0L) + RandomAddressBookBuilder.create(randotron).withSize(0).build(); + final AddressBook zeroWeightAddressBook = RandomAddressBookBuilder.create(randotron) + .withSize(10) + .withMaximumWeight(0) .build(); final AddressBook validAddressBook = - new RandomAddressBookGenerator().setSize(10).build(); + RandomAddressBookBuilder.create(randotron).withSize(10).build(); assertFalse(hasNonZeroWeight(emptyAddressBook), "should fail validation"); assertFalse(isGenesisAddressBookValid(emptyAddressBook), "should fail validation"); @@ -59,10 +67,11 @@ void hasNonZeroWeightTest() { @Test @DisplayName("isNonEmpty Test") void isNonEmptyTest() { + final Randotron randotron = Randotron.create(); final AddressBook emptyAddressBook = - new RandomAddressBookGenerator().setSize(0).build(); + RandomAddressBookBuilder.create(randotron).withSize(0).build(); final AddressBook validAddressBook = - new RandomAddressBookGenerator().setSize(10).build(); + RandomAddressBookBuilder.create(randotron).withSize(10).build(); assertFalse(isNonEmpty(emptyAddressBook), "should fail validation"); assertFalse(isGenesisAddressBookValid(emptyAddressBook), "should fail validation"); @@ -74,11 +83,21 @@ void isNonEmptyTest() { @Test @DisplayName("validNextId Test") void validNextIdTest() { - final RandomAddressBookGenerator generator = new RandomAddressBookGenerator().setSize(10); + final Randotron randotron = Randotron.create(); + final RandomAddressBookBuilder generator = + RandomAddressBookBuilder.create(randotron).withSize(10); final AddressBook addressBook1 = generator.build(); - final AddressBook addressBook2 = generator.addToAddressBook(addressBook1.copy()); - final AddressBook addressBook3 = generator.addToAddressBook(addressBook2.copy()); + + final AddressBook addressBook2 = addressBook1.copy(); + addressBook2.add(RandomAddressBuilder.create(randotron) + .withNodeId(new NodeId(addressBook2.getNextNodeId().id() + randotron.nextInt(1, 3))) + .build()); + + final AddressBook addressBook3 = addressBook2.copy(); + addressBook3.add(RandomAddressBuilder.create(randotron) + .withNodeId(new NodeId(addressBook3.getNextNodeId().id() + randotron.nextInt(1, 3))) + .build()); assertTrue(validNextId(addressBook1, addressBook2), "should pass validation"); assertTrue(isNextAddressBookValid(addressBook1, addressBook2), "should pass validation"); @@ -93,14 +112,36 @@ void validNextIdTest() { assertFalse(isNextAddressBookValid(addressBook2, addressBook1), "should fail validation"); } + /** + * Remove a number of addresses from an address book. + * + * @param randotron the random number generator to use + * @param addressBook the address book to remove from + * @param count the number of addresses to remove, removes all addresses if count exceeds address book size + * @return the input address book + */ + public static AddressBook removeFromAddressBook( + @NonNull final Randotron randotron, @NonNull final AddressBook addressBook, final int count) { + Objects.requireNonNull(addressBook, "AddressBook must not be null"); + final List nodeIds = new ArrayList<>(addressBook.getSize()); + addressBook.forEach((final Address address) -> nodeIds.add(address.getNodeId())); + Collections.shuffle(nodeIds, randotron); + for (int i = 0; i < count && i < nodeIds.size(); i++) { + addressBook.remove(nodeIds.get(i)); + } + return addressBook; + } + @Test @DisplayName("noAddressReinsertion Test") void noAddressReinsertionTest() { - final RandomAddressBookGenerator generator = new RandomAddressBookGenerator().setSize(10); + final Randotron randotron = Randotron.create(); + final RandomAddressBookBuilder generator = + RandomAddressBookBuilder.create(randotron).withSize(10); final AddressBook addressBook1 = generator.build(); final AddressBook addressBook2 = generator.build(); - final AddressBook reducedAddressBook1 = generator.removeFromAddressBook(addressBook1.copy(), 5); + final AddressBook reducedAddressBook1 = removeFromAddressBook(randotron, addressBook1.copy(), 5); assertTrue(noAddressReinsertion(addressBook1, addressBook2), "should pass validation"); assertTrue(isNextAddressBookValid(addressBook1, addressBook2), "should pass validation"); @@ -114,7 +155,9 @@ void noAddressReinsertionTest() { @Test @DisplayName("validation of nwew nextNodeId and address book") void validateNextNodeIdAndAddressBook() { - final RandomAddressBookGenerator generator = new RandomAddressBookGenerator().setSize(10); + final Randotron randotron = Randotron.create(); + final RandomAddressBookBuilder generator = + RandomAddressBookBuilder.create(randotron).withSize(10); final AddressBook oldAddressBook = generator.build(); final NodeId oldNextNodeId = oldAddressBook.getNextNodeId(); diff --git a/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/system/address/RandomAddressBookBuilderTests.java b/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/system/address/RandomAddressBookBuilderTests.java new file mode 100644 index 000000000000..9896cbddfe01 --- /dev/null +++ b/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/system/address/RandomAddressBookBuilderTests.java @@ -0,0 +1,135 @@ +/* + * Copyright (C) 2024 Hedera Hashgraph, LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.swirlds.platform.system.address; + +import static org.assertj.core.api.Fail.fail; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import com.hedera.pbj.runtime.io.buffer.Bytes; +import com.swirlds.common.context.PlatformContext; +import com.swirlds.common.platform.NodeId; +import com.swirlds.common.test.fixtures.Randotron; +import com.swirlds.common.test.fixtures.platform.TestPlatformContextBuilder; +import com.swirlds.platform.crypto.CryptoStatic; +import com.swirlds.platform.crypto.KeysAndCerts; +import com.swirlds.platform.crypto.PlatformSigner; +import com.swirlds.platform.test.fixtures.addressbook.RandomAddressBookBuilder; +import edu.umd.cs.findbugs.annotations.NonNull; +import java.security.PublicKey; +import org.junit.jupiter.api.Test; + +class RandomAddressBookBuilderTests { + + /** + * Assert that the given keys are unique. + * + * @param keyA the first key + * @param keyB the second key + */ + private void assertKeysAreUnique(@NonNull final PublicKey keyA, @NonNull final PublicKey keyB) { + final byte[] keyABytes = keyA.getEncoded(); + final byte[] keyBBytes = keyB.getEncoded(); + + for (int i = 0; i < keyABytes.length; i++) { + if (keyABytes[i] != keyBBytes[i]) { + return; + } + } + fail("Keys are not unique"); + } + + /** + * Normally this would be broken up into several tests, but because it's not cheap to generate keys, better + * to do it all in one test with the same set of keys. + */ + @Test + void validDeterministicKeysTest() { + final Randotron randotron = Randotron.create(); + + // Only generate small address book (it's expensive to generate signatures) + final int size = 3; + + final RandomAddressBookBuilder builderA = + RandomAddressBookBuilder.create(randotron).withSize(size).withRealKeysEnabled(true); + final AddressBook addressBookA = builderA.build(); + + final RandomAddressBookBuilder builderB = RandomAddressBookBuilder.create(randotron.copyAndReset()) + .withSize(size) + .withRealKeysEnabled(true); + final AddressBook addressBookB = builderB.build(); + + // The address book should be the same (keys should be deterministic) + final PlatformContext platformContext = + TestPlatformContextBuilder.create().build(); + platformContext.getCryptography().digestSync(addressBookA); + platformContext.getCryptography().digestSync(addressBookB); + assertEquals(addressBookA.getHash(), addressBookB.getHash()); + + // Verify that each address has unique keys + for (int i = 0; i < size; i++) { + for (int j = i + 1; j < size; j++) { + if (i == j) { + continue; + } + + final NodeId idI = addressBookA.getNodeId(i); + final Address addressI = addressBookA.getAddress(idI); + final PublicKey signaturePublicKeyI = addressI.getSigPublicKey(); + final PublicKey agreementPublicKeyI = addressI.getAgreePublicKey(); + + final NodeId idJ = addressBookA.getNodeId(j); + final Address addressJ = addressBookA.getAddress(idJ); + final PublicKey signaturePublicKeyJ = addressJ.getSigPublicKey(); + final PublicKey agreementPublicKeyJ = addressJ.getAgreePublicKey(); + + assertKeysAreUnique(signaturePublicKeyI, signaturePublicKeyJ); + assertKeysAreUnique(agreementPublicKeyI, agreementPublicKeyJ); + } + } + + // Verify that the private key can produce valid signatures that can be verified by the public key + for (int i = 0; i < size; i++) { + final NodeId id = addressBookA.getNodeId(i); + final Address address = addressBookA.getAddress(id); + final PublicKey signaturePublicKey = address.getSigPublicKey(); + final KeysAndCerts privateKeys = builderA.getPrivateKeys(id); + + final byte[] dataArray = randotron.nextByteArray(64); + final Bytes dataBytes = Bytes.wrap(dataArray); + final com.swirlds.common.crypto.Signature signature = new PlatformSigner(privateKeys).sign(dataArray); + + assertTrue(CryptoStatic.verifySignature(dataBytes, signature.getBytes(), signaturePublicKey)); + + // Sanity check: validating using the wrong public key should fail + final NodeId wrongId = addressBookA.getNodeId((i + 1) % size); + final Address wrongAddress = addressBookA.getAddress(wrongId); + final PublicKey wrongPublicKey = wrongAddress.getSigPublicKey(); + assertFalse(CryptoStatic.verifySignature(dataBytes, signature.getBytes(), wrongPublicKey)); + + // Sanity check: validating against the wrong data should fail + final Bytes wrongData = randotron.nextHashBytes(); + assertFalse(CryptoStatic.verifySignature(wrongData, signature.getBytes(), signaturePublicKey)); + + // Sanity check: validating with a modified signature should fail + final byte[] modifiedSignature = signature.getBytes().toByteArray(); + modifiedSignature[0] = (byte) ~modifiedSignature[0]; + assertFalse(CryptoStatic.verifySignature(dataBytes, Bytes.wrap(modifiedSignature), signaturePublicKey)); + } + } +} diff --git a/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/turtle/gossip/SimulatedGossipTests.java b/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/turtle/gossip/SimulatedGossipTests.java new file mode 100644 index 000000000000..78521f4a4aaf --- /dev/null +++ b/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/turtle/gossip/SimulatedGossipTests.java @@ -0,0 +1,181 @@ +/* + * Copyright (C) 2024 Hedera Hashgraph, LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.swirlds.platform.turtle.gossip; + +import static com.swirlds.common.wiring.schedulers.builders.TaskSchedulerConfiguration.DIRECT_THREADSAFE_CONFIGURATION; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.mockito.Mockito.mock; + +import com.swirlds.base.test.fixtures.time.FakeTime; +import com.swirlds.common.constructable.ConstructableRegistry; +import com.swirlds.common.constructable.ConstructableRegistryException; +import com.swirlds.common.context.PlatformContext; +import com.swirlds.common.platform.NodeId; +import com.swirlds.common.test.fixtures.Randotron; +import com.swirlds.common.test.fixtures.platform.TestPlatformContextBuilder; +import com.swirlds.common.wiring.model.TraceableWiringModel; +import com.swirlds.common.wiring.model.WiringModel; +import com.swirlds.common.wiring.model.WiringModelBuilder; +import com.swirlds.common.wiring.schedulers.TaskScheduler; +import com.swirlds.common.wiring.wires.input.BindableInputWire; +import com.swirlds.common.wiring.wires.output.StandardOutputWire; +import com.swirlds.platform.event.GossipEvent; +import com.swirlds.platform.system.BasicSoftwareVersion; +import com.swirlds.platform.system.StaticSoftwareVersion; +import com.swirlds.platform.system.address.AddressBook; +import com.swirlds.platform.system.events.EventDescriptor; +import com.swirlds.platform.test.fixtures.addressbook.RandomAddressBookBuilder; +import com.swirlds.platform.test.fixtures.event.TestingEventBuilder; +import com.swirlds.platform.test.fixtures.turtle.gossip.SimulatedNetwork; +import edu.umd.cs.findbugs.annotations.NonNull; +import java.time.Duration; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.function.Consumer; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; + +class SimulatedGossipTests { + + /** + * Given a list of events that may contain duplicates, create a set of unique event descriptors. + * + * @param events the list of events + * @return the set of unique event descriptors + */ + private static Set getUniqueDescriptors(@NonNull final List events) { + final HashSet uniqueDescriptors = new HashSet<>(); + for (final GossipEvent event : events) { + uniqueDescriptors.add(event.getHashedData().getDescriptor()); + } + return uniqueDescriptors; + } + + @BeforeAll + static void beforeAll() throws ConstructableRegistryException { + StaticSoftwareVersion.setSoftwareVersion(new BasicSoftwareVersion(1)); + ConstructableRegistry.getInstance().registerConstructables(""); + } + + @SuppressWarnings("unchecked") + @ParameterizedTest + @ValueSource(ints = {1, 2, 4, 8, 16, 32}) + void randomDataTest(final int networkSize) { + final Randotron randotron = Randotron.create(); + + final FakeTime time = new FakeTime(); + final PlatformContext context = + TestPlatformContextBuilder.create().withTime(time).build(); + + final AddressBook addressBook = + RandomAddressBookBuilder.create(randotron).withSize(networkSize).build(); + + // We can safely choose large numbers because time is simulated + final Duration averageDelay = Duration.ofMillis(randotron.nextInt(1, 1_000_000)); + final Duration standardDeviationDelay = Duration.ofMillis((long) (averageDelay.toMillis() * 0.1)); + + final SimulatedNetwork network = + new SimulatedNetwork(randotron, addressBook, averageDelay, standardDeviationDelay); + + // Each node will add received events to the appropriate list + final Map> receivedEvents = new HashMap<>(); + + // Passing an event to one of these consumers causes the node to gossip the event to the network + final Map> eventSubmitters = new HashMap<>(); + + // Wire things up + for (final NodeId nodeId : addressBook.getNodeIdSet()) { + final WiringModel model = WiringModelBuilder.create(context) + .withDeterministicModeEnabled(true) + .build(); + + final TaskScheduler eventInputShim = model.schedulerBuilder("eventInputShim") + .configure(DIRECT_THREADSAFE_CONFIGURATION) + .build() + .cast(); + + final List receivedEventsForNode = new ArrayList<>(); + receivedEvents.put(nodeId, receivedEventsForNode); + final StandardOutputWire eventOutputWire = + new StandardOutputWire<>((TraceableWiringModel) model, "eventOutputWire"); + eventOutputWire.solderTo("handleOutputEvent", "event", receivedEventsForNode::add); + + final BindableInputWire eventInputWire = eventInputShim.buildInputWire("eventInputWire"); + eventSubmitters.put(nodeId, eventInputWire::inject); + + network.getGossipInstance(nodeId) + .bind( + model, + eventInputWire, + mock(BindableInputWire.class), + eventOutputWire, + mock(BindableInputWire.class), + mock(BindableInputWire.class), + mock(BindableInputWire.class)); + } + + // For each event, choose a random subset of nodes that will submit the event. Our end goal is to see + // each event distributed at least once to each node in the network. + final int eventCount = networkSize * 100; + final List eventsToGossip = new ArrayList<>(); + for (int i = 0; i < eventCount; i++) { + final NodeId creator = addressBook.getNodeId(randotron.nextInt(networkSize)); + final GossipEvent event = + new TestingEventBuilder(randotron).setCreatorId(creator).build(); + context.getCryptography().digestSync(event.getHashedData()); + + eventsToGossip.add(event); + } + + // Gossip all of the events. Each event is guaranteed to be gossiped by at least one node. + for (int eventIndex = 0; eventIndex < eventCount; eventIndex++) { + final GossipEvent event = eventsToGossip.get(eventIndex); + + for (final NodeId nodeId : addressBook.getNodeIdSet()) { + if (event.getHashedData().getCreatorId().equals(nodeId) || randotron.nextBoolean(0.1)) { + eventSubmitters.get(nodeId).accept(event); + + // When a node sends out an event, add it to the list of events that node knows about. + receivedEvents.get(nodeId).add(event); + } + } + + if (randotron.nextBoolean(0.1)) { + time.tick(averageDelay.dividedBy(10)); + network.tick(time.now()); + } + } + + // Move time forward enough to ensure that all events have been gossiped around the network. + // Ticking twice ensures everything is properly flushed out of the system. + network.tick(time.now()); + time.tick(averageDelay.multipliedBy(100000)); + network.tick(time.now()); + + // Verify that all nodes received all events. + final Set expectedDescriptors = getUniqueDescriptors(eventsToGossip); + for (final NodeId nodeId : addressBook.getNodeIdSet()) { + final Set uniqueDescriptors = getUniqueDescriptors(receivedEvents.get(nodeId)); + assertEquals(expectedDescriptors, uniqueDescriptors); + } + } +} diff --git a/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/turtle/runner/Turtle.java b/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/turtle/runner/Turtle.java new file mode 100644 index 000000000000..fd00324d1209 --- /dev/null +++ b/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/turtle/runner/Turtle.java @@ -0,0 +1,212 @@ +/* + * Copyright (C) 2024 Hedera Hashgraph, LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.swirlds.platform.turtle.runner; + +import com.swirlds.base.test.fixtures.time.FakeTime; +import com.swirlds.common.constructable.ConstructableRegistry; +import com.swirlds.common.constructable.ConstructableRegistryException; +import com.swirlds.common.platform.NodeId; +import com.swirlds.common.test.fixtures.Randotron; +import com.swirlds.platform.system.address.AddressBook; +import com.swirlds.platform.test.fixtures.addressbook.RandomAddressBookBuilder; +import com.swirlds.platform.test.fixtures.turtle.gossip.SimulatedNetwork; +import edu.umd.cs.findbugs.annotations.NonNull; +import java.time.Duration; +import java.time.Instant; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.Future; + +/** + * Runs a TURTLE network. All nodes run in this JVM, and if configured properly the execution is expected to be + * deterministic. + *

    + *             _________________
    + *           /   Testing        \
    + *          |    Utility         |
    + *          |    Running         |    _ -
    + *          |    Totally in a    |=<( o 0 )
    + *          |    Local           |   \===/
    + *           \   Environment    /
    + *            ------------------
    + *             / /       | | \ \
    + *            """        """ """
    + *            _________________
    + *           /        gnitseT   \
    + *          |         ytilitU    |
    + *   - _    |         gninnuR    |
    + * ( o O )<=|    a ni yllatoT    |
    + *  \===/   |           lacoL    |
    + *           \    tnemnorivnE   /
    + *            ------------------
    + *             / / | |       \ \
    + *            """  """        """
    + *             _________________
    + *           /   Testing        \
    + *          |    Utility         |
    + *          |    Running         |    _ -
    + *          |    Totally in a    |=<( o 0 )
    + *          |    Local           |   \===/
    + *           \   Environment    /
    + *            ------------------
    + *             / /       | | \ \
    + *            """        """ """
    + * 
    + */ +public class Turtle { + + private final FakeTime time; + private final Duration simulationGranularity; + + private final ExecutorService threadPool; + + private final SimulatedNetwork network; + private final ArrayList nodes = new ArrayList<>(); + + private final boolean timeReportingEnabled; + private long tickCount; + private Instant previousRealTime; + private Instant previousSimulatedTime; + + /** + * Constructor. + * + * @param builder the turtle builder + */ + Turtle(@NonNull final TurtleBuilder builder) { + final Randotron randotron = builder.getRandotron(); + simulationGranularity = builder.getSimulationGranularity(); + timeReportingEnabled = builder.isTimeReportingEnabled(); + + try { + ConstructableRegistry.getInstance().registerConstructables(""); + } catch (final ConstructableRegistryException e) { + throw new RuntimeException(e); + } + + threadPool = Executors.newFixedThreadPool( + Math.min(builder.getNodeCount(), Runtime.getRuntime().availableProcessors())); + time = new FakeTime(randotron.nextInstant(), Duration.ZERO); + + final RandomAddressBookBuilder addressBookBuilder = RandomAddressBookBuilder.create(randotron) + .withSize(builder.getNodeCount()) + .withRealKeysEnabled(true); + final AddressBook addressBook = addressBookBuilder.build(); + + network = new SimulatedNetwork(randotron, addressBook, Duration.ofMillis(200), Duration.ofMillis(10)); + + for (final NodeId nodeId : addressBook.getNodeIdSet().stream().sorted().toList()) { + nodes.add(new TurtleNode( + randotron, time, nodeId, addressBook, addressBookBuilder.getPrivateKeys(nodeId), network)); + } + } + + /** + * Start the network. Does not simulate any time after each node is started. + */ + public void start() { + for (final TurtleNode node : nodes) { + node.start(); + } + } + + /** + * Simulate the network for a period of time. + * + * @param duration the duration to simulate + */ + public void simulateTime(@NonNull final Duration duration) { + final Instant simulatedStart = time.now(); + final Instant simulatedEnd = simulatedStart.plus(duration); + + while (time.now().isBefore(simulatedEnd)) { + reportThePassageOfTime(); + + time.tick(simulationGranularity); + network.tick(time.now()); + tickAllNodes(); + } + } + + /** + * Generate a report of the passage of time. + */ + private void reportThePassageOfTime() { + if (!timeReportingEnabled) { + return; + } + if (tickCount == 0) { + previousRealTime = Instant.now(); + previousSimulatedTime = time.now(); + } + + if (tickCount > 0 && tickCount % 1000 == 0) { + final Instant now = Instant.now(); + + final Duration realElapsedTime = Duration.between(previousRealTime, now); + final Duration simulatedElapsedTime = Duration.between(previousSimulatedTime, time.now()); + + final double temporalVelocity; + if (realElapsedTime.isZero()) { + temporalVelocity = -1; + } else { + temporalVelocity = simulatedElapsedTime.toNanos() / (double) realElapsedTime.toNanos(); + } + + previousSimulatedTime = time.now(); + previousRealTime = now; + + System.out.printf( + "tick %d, temporal velocity = %.2f, simulated time = %s%n", + tickCount, temporalVelocity, time.now()); + } + + tickCount++; + } + + /** + * Call tick() on all nodes in the network. Nodes do not interact with each other during their tick() phase, so it + * is safe to run them in parallel. + */ + private void tickAllNodes() { + final List> futures = new ArrayList<>(); + + // Iteration order over nodes does not need to be deterministic -- nodes are not permitted to communicate with + // each other during the tick phase, and they run on separate threads to boot. + for (final TurtleNode node : nodes) { + final Future future = threadPool.submit(() -> { + node.tick(); + return null; + }); + futures.add(future); + } + + for (final Future future : futures) { + try { + future.get(); + } catch (final InterruptedException e) { + Thread.currentThread().interrupt(); + throw new RuntimeException("Interrupted while ticking nodes", e); + } catch (final ExecutionException e) { + throw new RuntimeException(e); + } + } + } +} diff --git a/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/turtle/runner/TurtleBuilder.java b/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/turtle/runner/TurtleBuilder.java new file mode 100644 index 000000000000..50bb7cd6e01c --- /dev/null +++ b/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/turtle/runner/TurtleBuilder.java @@ -0,0 +1,149 @@ +/* + * Copyright (C) 2024 Hedera Hashgraph, LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.swirlds.platform.turtle.runner; + +import com.swirlds.common.test.fixtures.Randotron; +import edu.umd.cs.findbugs.annotations.NonNull; +import java.time.Duration; +import java.util.Objects; + +/** + * Configures and builds a Turtle. + *
    + *    _________________                        _________________
    + *  /   Testing        \                     /        gnitseT   \
    + * |    Utility         |                   |         ytilitU    |
    + * |    Running         |    _ -     - _    |         gninnuR    |
    + * |    Totally in a    |=<( o 0 ) ( o O )<=|    a ni yllatoT    |
    + * |    Local           |   \===/   \===/   |           lacoL    |
    + *  \   Environment    /                     \    tnemnorivnE    /
    + *   ------------------                        ------------------
    + *   / /       | | \ \                          / / | |       \ \
    + *  """        """ """                         """  """        """
    + * 
    + */ +public class TurtleBuilder { + + private final Randotron randotron; + private Duration simulationGranularity = Duration.ofMillis(10); + private int nodeCount = 4; + private boolean timeReportingEnabled; + + /** + * Create a new TurtleBuilder. + * + * @param randotron a source of randomness + * @return a new TurtleBuilder + */ + public static TurtleBuilder create(@NonNull final Randotron randotron) { + return new TurtleBuilder(randotron); + } + + /** + * Constructor. + * + * @param randotron a source of randomness + */ + private TurtleBuilder(@NonNull final Randotron randotron) { + this.randotron = Objects.requireNonNull(randotron); + } + + @NonNull + public Turtle build() { + return new Turtle(this); + } + + /** + * Set the simulation granularity. + * + * @param simulationGranularity the simulation granularity + * @return this builder + */ + @NonNull + public TurtleBuilder withSimulationGranularity(@NonNull final Duration simulationGranularity) { + this.simulationGranularity = Objects.requireNonNull(simulationGranularity); + if (simulationGranularity.isZero() || simulationGranularity.isNegative()) { + throw new IllegalArgumentException("simulation granularity must be a positive non-zero value"); + } + return this; + } + + /** + * Get the simulation granularity. + * + * @return the simulation granularity + */ + @NonNull + Duration getSimulationGranularity() { + return simulationGranularity; + } + + /** + * Set the number of nodes to run. + * + * @param nodeCount the number of nodes to run + * @return this builder + */ + @NonNull + public TurtleBuilder withNodeCount(final int nodeCount) { + if (nodeCount < 1) { + throw new IllegalArgumentException("node count must be at least 1"); + } + this.nodeCount = nodeCount; + return this; + } + + /** + * Get the number of nodes to run. + * + * @return the number of nodes to run + */ + int getNodeCount() { + return nodeCount; + } + + /** + * Get the randotron. + * + * @return the randotron + */ + @NonNull + Randotron getRandotron() { + return randotron; + } + + /** + * Enable or disable time reporting to the console. + * + * @param timeReportingEnabled true to enable time reporting, false to disable + * @return this builder + */ + @NonNull + public TurtleBuilder withTimeReportingEnabled(final boolean timeReportingEnabled) { + this.timeReportingEnabled = timeReportingEnabled; + return this; + } + + /** + * Get whether time reporting is enabled. + * + * @return true if time reporting is enabled, false otherwise + */ + boolean isTimeReportingEnabled() { + return timeReportingEnabled; + } +} diff --git a/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/turtle/runner/TurtleNode.java b/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/turtle/runner/TurtleNode.java new file mode 100644 index 000000000000..34fedb038a10 --- /dev/null +++ b/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/turtle/runner/TurtleNode.java @@ -0,0 +1,127 @@ +/* + * Copyright (C) 2024 Hedera Hashgraph, LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.swirlds.platform.turtle.runner; + +import com.swirlds.base.time.Time; +import com.swirlds.common.context.PlatformContext; +import com.swirlds.common.platform.NodeId; +import com.swirlds.common.test.fixtures.Randotron; +import com.swirlds.common.test.fixtures.platform.TestPlatformContextBuilder; +import com.swirlds.common.wiring.model.DeterministicWiringModel; +import com.swirlds.common.wiring.model.WiringModelBuilder; +import com.swirlds.config.api.Configuration; +import com.swirlds.config.extensions.test.fixtures.TestConfigBuilder; +import com.swirlds.platform.builder.PlatformBuilder; +import com.swirlds.platform.builder.PlatformComponentBuilder; +import com.swirlds.platform.config.BasicConfig_; +import com.swirlds.platform.crypto.KeysAndCerts; +import com.swirlds.platform.eventhandling.EventConfig_; +import com.swirlds.platform.system.BasicSoftwareVersion; +import com.swirlds.platform.system.Platform; +import com.swirlds.platform.system.address.AddressBook; +import com.swirlds.platform.test.fixtures.turtle.gossip.SimulatedGossip; +import com.swirlds.platform.test.fixtures.turtle.gossip.SimulatedNetwork; +import com.swirlds.platform.util.RandomBuilder; +import com.swirlds.platform.wiring.PlatformSchedulersConfig_; +import edu.umd.cs.findbugs.annotations.NonNull; + +/** + * Encapsulates a single node running in a TURTLE network. + *
    + *    _________________
    + *  /   Testing        \
    + * |    Utility         |
    + * |    Running         |    _ -
    + * |    Totally in a    |=<( o 0 )
    + * |    Local           |   \===/
    + *  \   Environment    /
    + *   ------------------
    + *   / /       | | \ \
    + *  """        """ """
    + * 
    + */ +public class TurtleNode { + + private final DeterministicWiringModel model; + private final Platform platform; + + /** + * Create a new TurtleNode. Simulates a single consensus node in a TURTLE network. + * + * @param randotron a source of randomness + * @param time the current time + * @param nodeId the ID of this node + * @param addressBook the address book for the network + * @param privateKeys the private keys for this node + * @param network the simulated network + */ + TurtleNode( + @NonNull final Randotron randotron, + @NonNull final Time time, + @NonNull final NodeId nodeId, + @NonNull final AddressBook addressBook, + @NonNull final KeysAndCerts privateKeys, + @NonNull final SimulatedNetwork network) { + + final Configuration configuration = new TestConfigBuilder() + .withValue(EventConfig_.USE_OLD_STYLE_INTAKE_QUEUE, false) + .withValue(PlatformSchedulersConfig_.CONSENSUS_EVENT_STREAM, "NO_OP") + .withValue(BasicConfig_.JVM_PAUSE_DETECTOR_SLEEP_MS, "0") + .getOrCreateConfig(); + + final PlatformContext platformContext = TestPlatformContextBuilder.create() + .withTime(time) + .withConfiguration(configuration) + .build(); + + model = WiringModelBuilder.create(platformContext) + .withDeterministicModeEnabled(true) + .build(); + + final PlatformBuilder platformBuilder = PlatformBuilder.create( + "foo", "bar", new BasicSoftwareVersion(1), TurtleTestingToolState::new, nodeId) + .withModel(model) + .withRandomBuilder(new RandomBuilder(randotron.nextLong())) + .withPlatformContext(platformContext) + .withBootstrapAddressBook(addressBook) + .withKeysAndCerts(privateKeys); + + final PlatformComponentBuilder platformComponentBuilder = platformBuilder.buildComponentBuilder(); + + final SimulatedGossip gossip = network.getGossipInstance(nodeId); + gossip.provideIntakeEventCounter( + platformComponentBuilder.getBuildingBlocks().intakeEventCounter()); + + platformComponentBuilder.withMetricsDocumentationEnabled(false).withGossip(network.getGossipInstance(nodeId)); + + platform = platformComponentBuilder.build(); + } + + /** + * Start this node. + */ + public void start() { + platform.start(); + } + + /** + * Simulate the next time step for this node. + */ + public void tick() { + model.tick(); + } +} diff --git a/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/turtle/runner/TurtleTestingToolState.java b/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/turtle/runner/TurtleTestingToolState.java new file mode 100644 index 000000000000..986f742be93f --- /dev/null +++ b/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/turtle/runner/TurtleTestingToolState.java @@ -0,0 +1,114 @@ +/* + * Copyright (C) 2024 Hedera Hashgraph, LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.swirlds.platform.turtle.runner; + +import com.swirlds.common.io.streams.SerializableDataInputStream; +import com.swirlds.common.io.streams.SerializableDataOutputStream; +import com.swirlds.common.merkle.MerkleLeaf; +import com.swirlds.common.merkle.impl.PartialMerkleLeaf; +import com.swirlds.common.utility.NonCryptographicHashing; +import com.swirlds.platform.state.PlatformState; +import com.swirlds.platform.system.Round; +import com.swirlds.platform.system.SwirldState; +import edu.umd.cs.findbugs.annotations.NonNull; +import java.io.IOException; + +/** + * A simple testing application intended for use with TURTLE. + *
    + *   _______    Ö¥  Ö–       Ö¥  Ö–    _______
    + * 〈 Tᴜʀᴛʟᴇ á³ï¹™âš¬â—¡Â°ï¹š   ﹙°◡⚬﹚ḠᴇʟᴛʀᴜT 〉
    + *   ﹉âˆï¹‰âˆï¹‰                   ﹉âˆï¹‰âˆï¹‰
    + * 
    + */ +public class TurtleTestingToolState extends PartialMerkleLeaf implements SwirldState, MerkleLeaf { + + private static final long CLASS_ID = 0xa49b3822a4136ac6L; + + private static final class ClassVersion { + + public static final int ORIGINAL = 1; + } + + private long state; + + /** + * Zero arg constructor needed for constructable registry. + */ + public TurtleTestingToolState() {} + + /** + * Copy constructor. + * + * @param that the object to copy + */ + private TurtleTestingToolState(@NonNull final TurtleTestingToolState that) { + this.state = that.state; + } + + /** + * {@inheritDoc} + */ + @Override + public long getClassId() { + return CLASS_ID; + } + + /** + * {@inheritDoc} + */ + @Override + public int getVersion() { + return ClassVersion.ORIGINAL; + } + + /** + * {@inheritDoc} + */ + @Override + public void handleConsensusRound(@NonNull final Round round, @NonNull final PlatformState platformState) { + state = NonCryptographicHashing.hash64( + state, + round.getRoundNum(), + round.getConsensusTimestamp().getNano(), + round.getConsensusTimestamp().getEpochSecond()); + } + + /** + * {@inheritDoc} + */ + @Override + public TurtleTestingToolState copy() { + return new TurtleTestingToolState(this); + } + + /** + * {@inheritDoc} + */ + @Override + public void serialize(@NonNull final SerializableDataOutputStream out) throws IOException { + out.writeLong(state); + } + + /** + * {@inheritDoc} + */ + @Override + public void deserialize(@NonNull final SerializableDataInputStream in, final int version) throws IOException { + state = in.readLong(); + } +} diff --git a/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/turtle/runner/TurtleTests.java b/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/turtle/runner/TurtleTests.java new file mode 100644 index 000000000000..0f3e03b5dd2e --- /dev/null +++ b/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/turtle/runner/TurtleTests.java @@ -0,0 +1,54 @@ +/* + * Copyright (C) 2024 Hedera Hashgraph, LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.swirlds.platform.turtle.runner; + +import com.swirlds.common.test.fixtures.Randotron; +import java.time.Duration; +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; + +class TurtleTests { + + /** + * Simulate a turtle network for 5 minutes. + *

    + * This test needs to remain disabled until the following things are resolved: + *

      + *
    • We need validation. No point in running a test if you can't validate if it is a pass/fail.
    • + *
    • We need to work out the proper place for these tests in the CI lifecycle. TURTLE tests are long for a + * unit test, so we need to be careful where we run them.
    • + *
    • We need to ensure that all resources used by TURTLE are properly freed up after the test is run. + * Currently turtle leaves a bunch of junk on the file system.
    • + *
    • We need safeguards/detection against test deadlock. These tests are sufficiently complex as to make + * deadlocks a real possibility, and so it would be good to make the framework handle deadlocks.
    • + *
    + */ + @Disabled + @Test + void turtleTest() { + final Randotron randotron = Randotron.create(); + + final Turtle turtle = TurtleBuilder.create(randotron) + .withNodeCount(4) + .withSimulationGranularity(Duration.ofMillis(10)) + .withTimeReportingEnabled(true) + .build(); + + turtle.start(); + turtle.simulateTime(Duration.ofMinutes(5)); + } +} diff --git a/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/uptime/UptimeTests.java b/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/uptime/UptimeTests.java index dae21e65132f..5a1cee907655 100644 --- a/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/uptime/UptimeTests.java +++ b/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/uptime/UptimeTests.java @@ -40,7 +40,7 @@ import com.swirlds.platform.system.address.AddressBook; import com.swirlds.platform.system.events.ConsensusEvent; import com.swirlds.platform.system.status.StatusActionSubmitter; -import com.swirlds.platform.test.fixtures.addressbook.RandomAddressBookGenerator; +import com.swirlds.platform.test.fixtures.addressbook.RandomAddressBookBuilder; import edu.umd.cs.findbugs.annotations.NonNull; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; @@ -125,7 +125,7 @@ void roundScanTest() { final FakeTime time = new FakeTime(); final AddressBook addressBook = - new RandomAddressBookGenerator(random).setSize(10).build(); + RandomAddressBookBuilder.create(random).withSize(10).build(); final NodeId selfId = addressBook.getNodeId(0); final UptimeTracker uptimeTracker = @@ -263,7 +263,7 @@ void roundScanChangingAddressBookTest() { final FakeTime time = new FakeTime(); final AddressBook addressBook = - new RandomAddressBookGenerator(random).setSize(10).build(); + RandomAddressBookBuilder.create(random).withSize(10).build(); final NodeId selfId = addressBook.getNodeId(0); final UptimeTracker uptimeTracker = @@ -614,7 +614,7 @@ void degradedTest() { final FakeTime time = new FakeTime(); final AddressBook addressBook = - new RandomAddressBookGenerator(random).setSize(3).build(); + RandomAddressBookBuilder.create(random).withSize(3).build(); final NodeId selfId = addressBook.getNodeId(0); final UptimeTracker uptimeTracker = diff --git a/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/util/AddressBookNetworkUtilsTests.java b/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/util/AddressBookNetworkUtilsTests.java index a82349f22013..853d8a842c77 100644 --- a/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/util/AddressBookNetworkUtilsTests.java +++ b/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/util/AddressBookNetworkUtilsTests.java @@ -20,14 +20,14 @@ import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; +import com.swirlds.common.test.fixtures.Randotron; import com.swirlds.platform.network.Network; import com.swirlds.platform.state.address.AddressBookNetworkUtils; import com.swirlds.platform.system.address.Address; import com.swirlds.platform.system.address.AddressBook; -import com.swirlds.platform.test.fixtures.addressbook.RandomAddressBookGenerator; +import com.swirlds.platform.test.fixtures.addressbook.RandomAddressBookBuilder; import java.net.Inet4Address; import java.net.InetAddress; -import java.net.SocketException; import java.net.UnknownHostException; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; @@ -41,9 +41,10 @@ class AddressBookNetworkUtilsTests { @Test @DisplayName("Determine If Local Node") - void determineLocalNodeAddress() throws UnknownHostException, SocketException { + void determineLocalNodeAddress() throws UnknownHostException { + final Randotron randotron = Randotron.create(); final AddressBook addressBook = - new RandomAddressBookGenerator().setSize(2).build(); + RandomAddressBookBuilder.create(randotron).withSize(2).build(); final Address address = addressBook.getAddress(addressBook.getNodeId(0)); final Address loopBackAddress = address.copySetHostnameInternal( @@ -66,9 +67,10 @@ void determineLocalNodeAddress() throws UnknownHostException, SocketException { @Test @DisplayName("Error On Invalid Local Address") @DisabledOnOs({OS.WINDOWS, OS.MAC}) - void ErrorOnInvalidLocalAddress() throws UnknownHostException, SocketException { + void ErrorOnInvalidLocalAddress() { + final Randotron randotron = Randotron.create(); final AddressBook addressBook = - new RandomAddressBookGenerator().setSize(2).build(); + RandomAddressBookBuilder.create(randotron).withSize(2).build(); final Address address = addressBook.getAddress(addressBook.getNodeId(0)); final Address badLocalAddress = address.copySetHostnameInternal("500.8.8"); diff --git a/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/wiring/PlatformWiringTests.java b/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/wiring/PlatformWiringTests.java index e23b967e2b8d..c355547d0b18 100644 --- a/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/wiring/PlatformWiringTests.java +++ b/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/wiring/PlatformWiringTests.java @@ -21,7 +21,10 @@ import com.swirlds.common.context.PlatformContext; import com.swirlds.common.test.fixtures.platform.TestPlatformContextBuilder; +import com.swirlds.common.wiring.model.WiringModel; +import com.swirlds.common.wiring.model.WiringModelBuilder; import com.swirlds.platform.StateSigner; +import com.swirlds.platform.builder.ApplicationCallbacks; import com.swirlds.platform.builder.PlatformBuildingBlocks; import com.swirlds.platform.builder.PlatformComponentBuilder; import com.swirlds.platform.components.AppNotifier; @@ -32,34 +35,34 @@ import com.swirlds.platform.event.creation.EventCreationManager; import com.swirlds.platform.event.deduplication.EventDeduplicator; import com.swirlds.platform.event.hashing.EventHasher; -import com.swirlds.platform.event.linking.InOrderLinker; import com.swirlds.platform.event.orphan.OrphanBuffer; import com.swirlds.platform.event.preconsensus.PcesReplayer; import com.swirlds.platform.event.preconsensus.PcesSequencer; import com.swirlds.platform.event.preconsensus.PcesWriter; import com.swirlds.platform.event.preconsensus.durability.RoundDurabilityBuffer; -import com.swirlds.platform.event.runninghash.RunningEventHasher; import com.swirlds.platform.event.signing.SelfEventSigner; +import com.swirlds.platform.event.stale.DefaultStaleEventDetector; +import com.swirlds.platform.event.stale.TransactionResubmitter; import com.swirlds.platform.event.stream.ConsensusEventStream; import com.swirlds.platform.event.validation.EventSignatureValidator; import com.swirlds.platform.event.validation.InternalEventValidator; import com.swirlds.platform.eventhandling.ConsensusRoundHandler; import com.swirlds.platform.eventhandling.TransactionPrehandler; -import com.swirlds.platform.gossip.shadowgraph.Shadowgraph; +import com.swirlds.platform.pool.TransactionPool; import com.swirlds.platform.publisher.PlatformPublisher; +import com.swirlds.platform.state.hasher.StateHasher; +import com.swirlds.platform.state.hashlogger.HashLogger; import com.swirlds.platform.state.iss.IssDetector; import com.swirlds.platform.state.iss.IssHandler; import com.swirlds.platform.state.nexus.LatestCompleteStateNexus; import com.swirlds.platform.state.nexus.SignedStateNexus; -import com.swirlds.platform.state.signed.SignedStateFileManager; -import com.swirlds.platform.state.signed.SignedStateHasher; import com.swirlds.platform.state.signed.SignedStateSentinel; import com.swirlds.platform.state.signed.StateGarbageCollector; import com.swirlds.platform.state.signed.StateSignatureCollector; +import com.swirlds.platform.state.snapshot.StateSnapshotManager; import com.swirlds.platform.system.events.BirthRoundMigrationShim; import com.swirlds.platform.system.status.PlatformStatusNexus; import com.swirlds.platform.system.status.StatusStateMachine; -import com.swirlds.platform.util.HashLogger; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; @@ -73,7 +76,11 @@ void testBindings() { final PlatformContext platformContext = TestPlatformContextBuilder.create().build(); - final PlatformWiring wiring = new PlatformWiring(platformContext, true, true); + final ApplicationCallbacks applicationCallbacks = new ApplicationCallbacks(x -> {}, x -> {}, x -> {}); + + final WiringModel model = WiringModelBuilder.create(platformContext).build(); + + final PlatformWiring wiring = new PlatformWiring(platformContext, model, applicationCallbacks); final PlatformComponentBuilder componentBuilder = new PlatformComponentBuilder(mock(PlatformBuildingBlocks.class)); @@ -86,9 +93,7 @@ void testBindings() { .withStateGarbageCollector(mock(StateGarbageCollector.class)) .withSelfEventSigner(mock(SelfEventSigner.class)) .withOrphanBuffer(mock(OrphanBuffer.class)) - .withRunningEventHasher(mock(RunningEventHasher.class)) .withEventCreationManager(mock(EventCreationManager.class)) - .withInOrderLinker(mock(InOrderLinker.class)) .withConsensusEngine(mock(ConsensusEngine.class)) .withConsensusEventStream(mock(ConsensusEventStream.class)) .withPcesSequencer(mock(PcesSequencer.class)) @@ -96,26 +101,41 @@ void testBindings() { .withStatusStateMachine(mock(StatusStateMachine.class)) .withTransactionPrehandler(mock(TransactionPrehandler.class)) .withPcesWriter(mock(PcesWriter.class)) - .withSignedStateSentinel(mock(SignedStateSentinel.class)); + .withSignedStateSentinel(mock(SignedStateSentinel.class)) + .withIssDetector(mock(IssDetector.class)) + .withStateHasher(mock(StateHasher.class)) + .withStaleEventDetector(mock(DefaultStaleEventDetector.class)) + .withTransactionResubmitter(mock(TransactionResubmitter.class)) + .withTransactionPool(mock(TransactionPool.class)) + .withStateSnapshotManager(mock(StateSnapshotManager.class)) + .withHashLogger(mock(HashLogger.class)); + + // Gossip is a special case, it's not like other components. + // Currently we just have a facade between gossip and the wiring framework. + // In the future when gossip is refactored to operate within the wiring + // framework like other components, such things will not be needed. + componentBuilder.withGossip( + (wiringModel, eventInput, eventWindowInput, eventOutput, startInput, stopInput, clearInput) -> { + eventInput.bindConsumer(event -> {}); + eventWindowInput.bindConsumer(eventWindow -> {}); + startInput.bindConsumer(noInput -> {}); + stopInput.bindConsumer(noInput -> {}); + clearInput.bindConsumer(noInput -> {}); + }); wiring.bind( componentBuilder, - mock(SignedStateFileManager.class), mock(StateSigner.class), mock(PcesReplayer.class), - mock(Shadowgraph.class), mock(StateSignatureCollector.class), mock(EventWindowManager.class), mock(ConsensusRoundHandler.class), - mock(IssDetector.class), mock(IssHandler.class), - mock(HashLogger.class), mock(BirthRoundMigrationShim.class), mock(LatestCompleteStateNotifier.class), mock(SignedStateNexus.class), mock(LatestCompleteStateNexus.class), mock(SavedStateController.class), - mock(SignedStateHasher.class), mock(AppNotifier.class), mock(PlatformPublisher.class), mock(PlatformStatusNexus.class)); diff --git a/hedera-node/hedera-app/src/test/java/com/hedera/node/app/state/merkle/AddresBookUtils.java b/platform-sdk/swirlds-platform-core/src/testFixtures/java/com/swirlds/platform/test/fixtures/addressbook/AddresBookUtils.java similarity index 97% rename from hedera-node/hedera-app/src/test/java/com/hedera/node/app/state/merkle/AddresBookUtils.java rename to platform-sdk/swirlds-platform-core/src/testFixtures/java/com/swirlds/platform/test/fixtures/addressbook/AddresBookUtils.java index 099796b21311..e0674891bad3 100644 --- a/hedera-node/hedera-app/src/test/java/com/hedera/node/app/state/merkle/AddresBookUtils.java +++ b/platform-sdk/swirlds-platform-core/src/testFixtures/java/com/swirlds/platform/test/fixtures/addressbook/AddresBookUtils.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.hedera.node.app.state.merkle; +package com.swirlds.platform.test.fixtures.addressbook; import static org.mockito.Mockito.mock; diff --git a/platform-sdk/swirlds-platform-core/src/testFixtures/java/com/swirlds/platform/test/fixtures/addressbook/RandomAddressBookBuilder.java b/platform-sdk/swirlds-platform-core/src/testFixtures/java/com/swirlds/platform/test/fixtures/addressbook/RandomAddressBookBuilder.java new file mode 100644 index 000000000000..5a60aa81b971 --- /dev/null +++ b/platform-sdk/swirlds-platform-core/src/testFixtures/java/com/swirlds/platform/test/fixtures/addressbook/RandomAddressBookBuilder.java @@ -0,0 +1,312 @@ +/* + * Copyright (C) 2024 Hedera Hashgraph, LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.swirlds.platform.test.fixtures.addressbook; + +import static com.swirlds.common.utility.CommonUtils.nameToAlias; +import static com.swirlds.platform.crypto.KeyCertPurpose.AGREEMENT; +import static com.swirlds.platform.crypto.KeyCertPurpose.SIGNING; + +import com.swirlds.common.platform.NodeId; +import com.swirlds.platform.crypto.KeysAndCerts; +import com.swirlds.platform.crypto.PublicStores; +import com.swirlds.platform.crypto.SerializableX509Certificate; +import com.swirlds.platform.system.address.AddressBook; +import edu.umd.cs.findbugs.annotations.NonNull; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; +import java.util.Random; + +/** + * A utility for generating a random address book. + */ +public class RandomAddressBookBuilder { + + /** + * All randomness comes from this. + */ + private final Random random; + + /** + * The number of addresses to put into the address book. + */ + private int size = 4; + + /** + * Describes different ways that the random address book has its weight distributed if the custom strategy lambda is + * unset. + */ + public enum WeightDistributionStrategy { + /** + * All nodes have equal weight. + */ + BALANCED, + /** + * Nodes are given weight with a gaussian distribution. + */ + GAUSSIAN + } + + /** + * The weight distribution strategy. + */ + private WeightDistributionStrategy weightDistributionStrategy = WeightDistributionStrategy.GAUSSIAN; + + /** + * The average weight. Used directly if using {@link WeightDistributionStrategy#BALANCED}, used as mean if using + * {@link WeightDistributionStrategy#GAUSSIAN}. + */ + private long averageWeight = 1000; + + /** + * The standard deviation of the weight, ignored if distribution strategy is not + * {@link WeightDistributionStrategy#GAUSSIAN}. + */ + private long weightStandardDeviation = 100; + + /** + * The minimum weight to give to any particular address. + */ + private long minimumWeight = 0; + + /** + * The maximum weight to give to any particular address. + */ + private Long maximumWeight; + + /** + * the next available node id for new addresses. + */ + private NodeId nextNodeId = NodeId.FIRST_NODE_ID; + + /** + * If true then generate real cryptographic keys. + */ + private boolean realKeys; + + /** + * If we are using real keys, this map will hold the private keys for each address. + */ + private final Map privateKeys = new HashMap<>(); + + /** + * Create a new random address book generator. + * + * @param random a source of randomness + * @return a new random address book generator + */ + @NonNull + public static RandomAddressBookBuilder create(@NonNull final Random random) { + return new RandomAddressBookBuilder(random); + } + + /** + * Constructor. + * + * @param random a source of randomness + */ + private RandomAddressBookBuilder(@NonNull final Random random) { + this.random = Objects.requireNonNull(random); + } + + /** + * Build a random address book given the provided configuration. + */ + @NonNull + public AddressBook build() { + final AddressBook addressBook = new AddressBook(); + addressBook.setNextNodeId(nextNodeId); + addressBook.setRound(Math.abs(random.nextLong())); + + if (maximumWeight == null && size > 0) { + // We don't want the total weight to overflow a long + maximumWeight = Long.MAX_VALUE / size; + } + + for (int index = 0; index < size; index++) { + final NodeId nodeId = getNextNodeId(); + final RandomAddressBuilder addressBuilder = + RandomAddressBuilder.create(random).withNodeId(nodeId).withWeight(getNextWeight()); + + generateKeys(nodeId, addressBuilder); + addressBook.add(addressBuilder.build()); + } + + return addressBook; + } + + /** + * Set the size of the address book. + * + * @return this object + */ + @NonNull + public RandomAddressBookBuilder withSize(final int size) { + this.size = size; + return this; + } + + /** + * Set the average weight for an address. If the weight distribution strategy is + * {@link WeightDistributionStrategy#BALANCED}, all addresses will have this weight. If the weight distribution + * strategy is {@link WeightDistributionStrategy#GAUSSIAN}, this will be the mean weight. + * + * @return this object + */ + @NonNull + public RandomAddressBookBuilder withAverageWeight(final long averageWeight) { + this.averageWeight = averageWeight; + return this; + } + + /** + * Set the standard deviation for the weight for an address. Ignored unless the weight distribution strategy is + * {@link WeightDistributionStrategy#GAUSSIAN}. + * + * @return this object + */ + @NonNull + public RandomAddressBookBuilder withWeightStandardDeviation(final long weightStandardDeviation) { + this.weightStandardDeviation = weightStandardDeviation; + return this; + } + + /** + * Set the minimum weight for an address. Overrides the weight generation strategy. + * + * @return this object + */ + @NonNull + public RandomAddressBookBuilder withMinimumWeight(final long minimumWeight) { + this.minimumWeight = minimumWeight; + return this; + } + + /** + * Set the maximum weight for an address. Overrides the weight generation strategy. + * + * @return this object + */ + @NonNull + public RandomAddressBookBuilder withMaximumWeight(final long maximumWeight) { + this.maximumWeight = maximumWeight; + return this; + } + + /** + * Set the strategy used for deciding distribution of weight. + * + * @return this object + */ + @NonNull + public RandomAddressBookBuilder withWeightDistributionStrategy( + final WeightDistributionStrategy weightDistributionStrategy) { + + this.weightDistributionStrategy = weightDistributionStrategy; + return this; + } + + /** + * Specify if real cryptographic keys should be generated (default false). Warning: generating real keys is very + * time consuming. + * + * @param realKeysEnabled if true then generate real cryptographic keys + * @return this object + */ + @NonNull + public RandomAddressBookBuilder withRealKeysEnabled(final boolean realKeysEnabled) { + this.realKeys = realKeysEnabled; + return this; + } + + /** + * Get the private keys for a node. Should only be called after the address book has been built and only if + * {@link #withRealKeysEnabled(boolean)} was set to true. + * + * @param nodeId the node id + * @return the private keys + * @throws IllegalStateException if real keys are not being generated or the address book has not been built + */ + @NonNull + public KeysAndCerts getPrivateKeys(final NodeId nodeId) { + if (!realKeys) { + throw new IllegalStateException("Real keys are not being generated"); + } + if (!privateKeys.containsKey(nodeId)) { + throw new IllegalStateException("Unknown node ID " + nodeId); + } + return privateKeys.get(nodeId); + } + + /** + * Generate the next node ID. + */ + @NonNull + private NodeId getNextNodeId() { + final NodeId nextId = nextNodeId; + // randomly advance between 1 and 3 steps + final int randomAdvance = random.nextInt(3); + nextNodeId = nextNodeId.getOffset(randomAdvance + 1L); + return nextId; + } + + /** + * Generate the next weight for the next address. + */ + private long getNextWeight() { + final long unboundedWeight; + switch (weightDistributionStrategy) { + case BALANCED -> unboundedWeight = averageWeight; + case GAUSSIAN -> unboundedWeight = + Math.max(0, (long) (averageWeight + random.nextGaussian() * weightStandardDeviation)); + default -> throw new IllegalStateException("Unexpected value: " + weightDistributionStrategy); + } + + return Math.min(maximumWeight, Math.max(minimumWeight, unboundedWeight)); + } + + /** + * Generate the cryptographic keys for a node. + */ + private void generateKeys(@NonNull final NodeId nodeId, @NonNull final RandomAddressBuilder addressBuilder) { + if (realKeys) { + try { + final PublicStores publicStores = new PublicStores(); + final String name = nodeId.toString(); + + final byte[] masterKey = new byte[64]; + random.nextBytes(masterKey); + + final KeysAndCerts keysAndCerts = + KeysAndCerts.generate(name, new byte[] {}, masterKey, new byte[] {}, publicStores); + privateKeys.put(nodeId, keysAndCerts); + + final String alias = nameToAlias(name); + + final SerializableX509Certificate sigCert = + new SerializableX509Certificate(publicStores.getCertificate(SIGNING, alias)); + final SerializableX509Certificate agrCert = + new SerializableX509Certificate(publicStores.getCertificate(AGREEMENT, alias)); + + addressBuilder.withSigCert(sigCert).withAgreeCert(agrCert); + + } catch (final Exception e) { + throw new RuntimeException(); + } + } + } +} diff --git a/platform-sdk/swirlds-platform-core/src/testFixtures/java/com/swirlds/platform/test/fixtures/addressbook/RandomAddressBookGenerator.java b/platform-sdk/swirlds-platform-core/src/testFixtures/java/com/swirlds/platform/test/fixtures/addressbook/RandomAddressBookGenerator.java deleted file mode 100644 index 11d16436862f..000000000000 --- a/platform-sdk/swirlds-platform-core/src/testFixtures/java/com/swirlds/platform/test/fixtures/addressbook/RandomAddressBookGenerator.java +++ /dev/null @@ -1,427 +0,0 @@ -/* - * Copyright (C) 2024 Hedera Hashgraph, LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.swirlds.platform.test.fixtures.addressbook; - -import static com.swirlds.common.test.fixtures.RandomUtils.randomHash; - -import com.swirlds.common.crypto.CryptographyHolder; -import com.swirlds.common.platform.NodeId; -import com.swirlds.common.test.fixtures.NameUtils; -import com.swirlds.common.test.fixtures.RandomUtils; -import com.swirlds.platform.crypto.SerializableX509Certificate; -import com.swirlds.platform.system.address.Address; -import com.swirlds.platform.system.address.AddressBook; -import com.swirlds.platform.test.fixtures.crypto.PreGeneratedX509Certs; -import edu.umd.cs.findbugs.annotations.NonNull; -import edu.umd.cs.findbugs.annotations.Nullable; -import java.net.InetAddress; -import java.net.UnknownHostException; -import java.util.ArrayList; -import java.util.Collections; -import java.util.HashSet; -import java.util.List; -import java.util.Objects; -import java.util.Random; -import java.util.Set; -import java.util.function.Function; - -/** - * A utility for generating a random address book. - */ -public class RandomAddressBookGenerator { - - /** - * All randomness comes from this. - */ - private final Random random; - - private final Set nodeIds = new HashSet<>(); - - /** - * The number of addresses to put into the address book. - */ - private int size = 4; - - /** - * Describes different ways that the random address book is hashed. - */ - public enum HashStrategy { - NO_HASH, - FAKE_HASH, - REAL_HASH - } - - /** - * The strategy that should be used when hashing the address book. - */ - private HashStrategy hashStrategy = HashStrategy.NO_HASH; - - /** - * Describes different ways that the random address book has its weight distributed if the custom strategy lambda is - * unset. - */ - public enum WeightDistributionStrategy { - /** - * All nodes have equal weight. - */ - BALANCED, - /** - * Nodes are given weight with a gaussian distribution. - */ - GAUSSIAN - } - - /** - * The weight distribution strategy. - */ - private WeightDistributionStrategy weightDistributionStrategy = WeightDistributionStrategy.GAUSSIAN; - - /** - * The average weight. Used directly if using {@link WeightDistributionStrategy#BALANCED}, used as mean if using - * {@link WeightDistributionStrategy#GAUSSIAN}. - */ - private long averageWeight = 1000; - - /** - * The standard deviation of the weight, ignored if distribution strategy is not - * {@link WeightDistributionStrategy#GAUSSIAN}. - */ - private long weightStandardDeviation = 100; - - /** - * The minimum weight to give to any particular address. - */ - private long minimumWeight = 1; - - /** - * The maximum weight to give to any particular address. - */ - private long maximumWeight = 100_000; - - /** - * Used to determine weight for each node. Overrides all other behaviors if set. - */ - private Function customWeightGenerator; - - /** the next available node id for new addresses. */ - private NodeId nextNodeId = NodeId.FIRST_NODE_ID; - - /** - * Create a new address book generator. - */ - public RandomAddressBookGenerator() { - this(new Random()); - } - - /** - * Create a new address book generator with a source of randomness. - * - * @param random a source of randomness - */ - public RandomAddressBookGenerator(final Random random) { - this.random = random; - } - - /** - * Create a new address book generator with a seed. - * - * @param seed the seed for the random number generator - */ - public RandomAddressBookGenerator(final long seed) { - this(new Random(seed)); - } - - /** - * Generate an address that has random data in the "unimportant" fields. - * - * @param random a source of randomness - * @param id the node ID - * @param weight the weight - */ - @NonNull - public static Address addressWithRandomData( - @NonNull final Random random, @NonNull final NodeId id, final long weight) { - Objects.requireNonNull(random, "Random must not be null"); - Objects.requireNonNull(id, "NodeId must not be null"); - - final SerializableX509Certificate sigCert = PreGeneratedX509Certs.getSigCert(id.id()); - final SerializableX509Certificate agreeCert = PreGeneratedX509Certs.getAgreeCert(id.id()); - - final String nickname = NameUtils.getName(id.id()); - final String selfName = RandomUtils.randomString(random, 10); - - final int maxPort = 65535; - final int minPort = 2000; - final String addressInternalHostname; - try { - addressInternalHostname = - InetAddress.getByName(RandomUtils.randomIp(random)).getHostAddress(); - } catch (final UnknownHostException e) { - throw new RuntimeException(e); - } - final int portInternalIpv4 = minPort + random.nextInt(maxPort - minPort); - final String addressExternalHostname; - try { - addressExternalHostname = - InetAddress.getByName(RandomUtils.randomIp(random)).getHostAddress(); - } catch (final UnknownHostException e) { - throw new RuntimeException(e); - } - final int portExternalIpv4 = minPort + random.nextInt(maxPort - minPort); - - final String memo = RandomUtils.randomString(random, 10); - - return new Address( - id, - nickname, - selfName, - weight, - addressInternalHostname, - portInternalIpv4, - addressExternalHostname, - portExternalIpv4, - sigCert, - agreeCert, - memo); - } - - /** - * Generate the next node ID. - */ - private NodeId getNextNodeId() { - final NodeId nextId = this.nextNodeId; - // randomly advance between 1 and 3 steps - final int randomAdvance = random.nextInt(3); - this.nextNodeId = this.nextNodeId.getOffset(randomAdvance + 1L); - return nextId; - } - - /** - * Generate the next weight for the next address. - */ - private long getNextWeight(@NonNull final NodeId nodeId) { - Objects.requireNonNull(nodeId, "NodeId must not be null"); - - if (customWeightGenerator != null) { - return customWeightGenerator.apply(nodeId); - } - - final long unboundedWeight; - switch (weightDistributionStrategy) { - case BALANCED -> unboundedWeight = averageWeight; - case GAUSSIAN -> unboundedWeight = (long) (averageWeight + random.nextGaussian() * weightStandardDeviation); - default -> throw new IllegalStateException("Unexpected value: " + weightDistributionStrategy); - } - - return Math.min(maximumWeight, Math.max(minimumWeight, unboundedWeight)); - } - - /** - * Build a random address book given the provided configuration. - */ - public AddressBook build() { - final AddressBook addressBook = new AddressBook(); - addressBook.setNextNodeId(this.nextNodeId); - addressBook.setRound(Math.abs(random.nextLong())); - - addToAddressBook(addressBook); - return addressBook; - } - - /** - * Add new addresses to an address book. The number of addresses is equal to the value specified by - * {@link #setSize(int)}. The next candidate ID is set to be the address book's - * {@link AddressBook#getNextNodeId()}. - * - * @param addressBook the address book to add new addresses to - * @return the input address book after it has been expanded - */ - public AddressBook addToAddressBook(final AddressBook addressBook) { - setNextPossibleNodeId(addressBook.getNextNodeId()); - - if (!nodeIds.isEmpty()) { - nodeIds.stream().sorted().forEach(nodeId -> addressBook.add(buildNextAddress(nodeId))); - } else { - for (int index = 0; index < size; index++) { - addressBook.add(buildNextAddress()); - } - } - - if (hashStrategy == HashStrategy.FAKE_HASH) { - addressBook.setHash(randomHash(random)); - } else if (hashStrategy == HashStrategy.REAL_HASH) { - CryptographyHolder.get().digestSync(addressBook); - } - - return addressBook; - } - - /** - * Remove a number of addresses from an address book. - * - * @param addressBook the address book to remove from - * @param count the number of addresses to remove, removes all addresses if count exceeds address book size - * @return the input address book - */ - @NonNull - public AddressBook removeFromAddressBook(@NonNull final AddressBook addressBook, final int count) { - Objects.requireNonNull(addressBook, "AddressBook must not be null"); - final List nodeIds = new ArrayList<>(addressBook.getSize()); - addressBook.forEach((final Address address) -> nodeIds.add(address.getNodeId())); - Collections.shuffle(nodeIds, random); - for (int i = 0; i < count && i < nodeIds.size(); i++) { - addressBook.remove(nodeIds.get(i)); - } - return addressBook; - } - - /** - * Build a random address using provided configuration. Address IS NOT automatically added to the address book. - * - * @return a random address - */ - public Address buildNextAddress() { - return buildNextAddress(null); - } - - /** - * Build a random address using provided configuration. Address IS NOT automatically added to the address book. - * - * @return a random address - */ - @NonNull - public Address buildNextAddress(@Nullable final NodeId suppliedNodeId) { - final NodeId nodeId = suppliedNodeId == null ? getNextNodeId() : suppliedNodeId; - return addressWithRandomData(random, nodeId, getNextWeight(nodeId)); - } - - /** - * Build a random address with a specific node ID and take. - */ - public Address buildNextAddress(final NodeId nodeId, final long weight) { - this.nextNodeId = nodeId; - return addressWithRandomData(random, getNextNodeId(), weight); - } - - /** - * Set the size of the address book. - * - * @return this object - */ - public RandomAddressBookGenerator setSize(final int size) { - this.size = size; - return this; - } - - /** - * Set the node IDs of the address book. - * - * @return this object - */ - public RandomAddressBookGenerator setNodeIds(@NonNull final Set nodeIds) { - Objects.requireNonNull(nodeIds, "NodeIds must not be null"); - this.nodeIds.clear(); - this.nodeIds.addAll(nodeIds); - return this; - } - - /** - * Set the desired hashing strategy for the address book. - * - * @return this object - */ - public RandomAddressBookGenerator setHashStrategy(final HashStrategy hashStrategy) { - this.hashStrategy = hashStrategy; - return this; - } - - /** - * Set the average weight for an address. - * - * @return this object - */ - public RandomAddressBookGenerator setAverageWeight(final long averageWeight) { - this.averageWeight = averageWeight; - return this; - } - - /** - * Set the standard deviation for the weight for an address. - * - * @return this object - */ - public RandomAddressBookGenerator setWeightStandardDeviation(final long weightStandardDeviation) { - this.weightStandardDeviation = weightStandardDeviation; - return this; - } - - /** - * Set the minimum weight for an address. - * - * @return this object - */ - public RandomAddressBookGenerator setMinimumWeight(final long minimumWeight) { - this.minimumWeight = minimumWeight; - return this; - } - - /** - * Set the maximum weight for an address. - * - * @return this object - */ - public RandomAddressBookGenerator setMaximumWeight(final long maximumWeight) { - this.maximumWeight = maximumWeight; - return this; - } - - /** - * Provide a method that is used to determine the weight of each node. Overrides all other weight generation - * settings if set. - * - * @return this object - */ - public RandomAddressBookGenerator setCustomWeightGenerator(final Function customWeightGenerator) { - this.customWeightGenerator = customWeightGenerator; - return this; - } - - /** - * Set the strategy used for deciding distribution of weight. - * - * @return this object - */ - public RandomAddressBookGenerator setWeightDistributionStrategy( - final WeightDistributionStrategy weightDistributionStrategy) { - - this.weightDistributionStrategy = weightDistributionStrategy; - return this; - } - - /** - * Set the next node ID that may be used to generate a random address. This node ID may be skipped if gaps are - * permitted. - * - * @param nodeId the next node ID that is considered when generating a random address - * @return this object - */ - public RandomAddressBookGenerator setNextPossibleNodeId(@NonNull final NodeId nodeId) { - Objects.requireNonNull(nodeId, "NodeId must not be null"); - this.nextNodeId = nodeId; - return this; - } -} diff --git a/platform-sdk/swirlds-platform-core/src/testFixtures/java/com/swirlds/platform/test/fixtures/addressbook/RandomAddressBuilder.java b/platform-sdk/swirlds-platform-core/src/testFixtures/java/com/swirlds/platform/test/fixtures/addressbook/RandomAddressBuilder.java new file mode 100644 index 000000000000..8b3d0ccdc18b --- /dev/null +++ b/platform-sdk/swirlds-platform-core/src/testFixtures/java/com/swirlds/platform/test/fixtures/addressbook/RandomAddressBuilder.java @@ -0,0 +1,209 @@ +/* + * Copyright (C) 2024 Hedera Hashgraph, LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.swirlds.platform.test.fixtures.addressbook; + +import static com.swirlds.common.test.fixtures.RandomUtils.randomIp; +import static com.swirlds.common.test.fixtures.RandomUtils.randomString; + +import com.swirlds.common.platform.NodeId; +import com.swirlds.platform.crypto.SerializableX509Certificate; +import com.swirlds.platform.system.address.Address; +import com.swirlds.platform.test.fixtures.crypto.PreGeneratedX509Certs; +import edu.umd.cs.findbugs.annotations.NonNull; +import java.util.Objects; +import java.util.Random; + +/** + * A builder for creating random {@link Address} instances. + */ +public class RandomAddressBuilder { + + private final Random random; + private NodeId nodeId; + private Long weight; + private Integer port; + private String hostname; + private SerializableX509Certificate sigCert; + private SerializableX509Certificate agreeCert; + + private long minimumWeight = 0; + private long maximumWeight = Long.MAX_VALUE / 1024; + + /** + * Creates a new {@link RandomAddressBuilder} instance. + * + * @param random the random number generator to use + * @return a new {@link RandomAddressBuilder} instance + */ + @NonNull + public static RandomAddressBuilder create(@NonNull final Random random) { + return new RandomAddressBuilder(random); + } + + /** + * Constructor. + * + * @param random the random number generator to use + */ + private RandomAddressBuilder(@NonNull final Random random) { + this.random = Objects.requireNonNull(random); + } + + /** + * Builds a new {@link Address} instance. + * + * @return a new {@link Address} instance + */ + @NonNull + public Address build() { + + // Future work: use randotron utility methods once randotron changes merge + + if (nodeId == null) { + nodeId = new NodeId(random.nextLong(0, Long.MAX_VALUE)); + } + + if (weight == null) { + weight = random.nextLong(minimumWeight, maximumWeight); + } + + if (port == null) { + port = random.nextInt(0, 65535); + } + + if (hostname == null) { + hostname = randomIp(random); + } + + if (sigCert == null) { + sigCert = PreGeneratedX509Certs.getSigCert(nodeId.id()); + } + + if (agreeCert == null) { + agreeCert = PreGeneratedX509Certs.getAgreeCert(nodeId.id()); + } + + return new Address( + nodeId, + randomString(random, 8), + randomString(random, 8), + weight, + hostname, + port, + hostname, + port, + sigCert, + agreeCert, + randomString(random, 8)); + } + + /** + * Sets the {@link NodeId} for the address. + * + * @param nodeId the node ID + * @return this builder + */ + @NonNull + public RandomAddressBuilder withNodeId(@NonNull final NodeId nodeId) { + this.nodeId = Objects.requireNonNull(nodeId); + return this; + } + + /** + * Sets the weight for the address. + * + * @param weight the weight + * @return this builder + */ + @NonNull + public RandomAddressBuilder withWeight(final long weight) { + this.weight = weight; + return this; + } + + /** + * Sets the port for the address. + * + * @param port the port + * @return this builder + */ + @NonNull + public RandomAddressBuilder withPort(final int port) { + this.port = port; + return this; + } + + /** + * Sets the hostname for the address. + * + * @param hostname the hostname + * @return this builder + */ + @NonNull + public RandomAddressBuilder withHostname(@NonNull final String hostname) { + this.hostname = Objects.requireNonNull(hostname); + return this; + } + + /** + * Sets the sigCert for the address. + * + * @param sigCert the sigCert + * @return this builder + */ + @NonNull + public RandomAddressBuilder withSigCert(@NonNull final SerializableX509Certificate sigCert) { + this.sigCert = Objects.requireNonNull(sigCert); + return this; + } + + /** + * Sets the agreeCert for the address. + * + * @param agreeCert the agreeCert + * @return this builder + */ + @NonNull + public RandomAddressBuilder withAgreeCert(@NonNull final SerializableX509Certificate agreeCert) { + this.agreeCert = Objects.requireNonNull(agreeCert); + return this; + } + + /** + * Sets the minimum weight. Ignored if the weight is specifically set. Default 0. + * + * @param minimumWeight the minimum weight + * @return this builder + */ + @NonNull + public RandomAddressBuilder withMinimumWeight(final long minimumWeight) { + this.minimumWeight = minimumWeight; + return this; + } + + /** + * Sets the maximum weight. Ignored if the weight is specifically set. Default Long.MAX_VALUE / 1024. + * + * @param maximumWeight the maximum weight + * @return this builder + */ + @NonNull + public RandomAddressBuilder withMaximumWeight(final long maximumWeight) { + this.maximumWeight = maximumWeight; + return this; + } +} diff --git a/platform-sdk/swirlds-platform-core/src/testFixtures/java/com/swirlds/platform/test/fixtures/crypto/PreGeneratedX509Certs.java b/platform-sdk/swirlds-platform-core/src/testFixtures/java/com/swirlds/platform/test/fixtures/crypto/PreGeneratedX509Certs.java index 50673a01a8d0..9bc5e8a20580 100644 --- a/platform-sdk/swirlds-platform-core/src/testFixtures/java/com/swirlds/platform/test/fixtures/crypto/PreGeneratedX509Certs.java +++ b/platform-sdk/swirlds-platform-core/src/testFixtures/java/com/swirlds/platform/test/fixtures/crypto/PreGeneratedX509Certs.java @@ -27,7 +27,7 @@ import com.swirlds.platform.crypto.SerializableX509Certificate; import com.swirlds.platform.system.address.Address; import com.swirlds.platform.system.address.AddressBook; -import com.swirlds.platform.test.fixtures.addressbook.RandomAddressBookGenerator; +import com.swirlds.platform.test.fixtures.addressbook.RandomAddressBookBuilder; import edu.umd.cs.findbugs.annotations.NonNull; import edu.umd.cs.findbugs.annotations.Nullable; import java.io.File; @@ -92,7 +92,7 @@ public static void generateCerts(final int numCerts, @NonNull final Random rando // create address book without any certs. final AddressBook addressBook = - new RandomAddressBookGenerator(random).setSize(numCerts).build(); + RandomAddressBookBuilder.create(random).withSize(numCerts).build(); // generate certs for the address book. generateKeysAndCerts(addressBook); diff --git a/platform-sdk/swirlds-platform-core/src/testFixtures/java/com/swirlds/platform/test/fixtures/event/IndexedEvent.java b/platform-sdk/swirlds-platform-core/src/testFixtures/java/com/swirlds/platform/test/fixtures/event/IndexedEvent.java index df438cf997e5..40f5aca9fdee 100644 --- a/platform-sdk/swirlds-platform-core/src/testFixtures/java/com/swirlds/platform/test/fixtures/event/IndexedEvent.java +++ b/platform-sdk/swirlds-platform-core/src/testFixtures/java/com/swirlds/platform/test/fixtures/event/IndexedEvent.java @@ -16,9 +16,8 @@ package com.swirlds.platform.test.fixtures.event; +import com.swirlds.platform.event.GossipEvent; import com.swirlds.platform.internal.EventImpl; -import com.swirlds.platform.system.events.BaseEventHashedData; -import com.swirlds.platform.system.events.BaseEventUnhashedData; /** * An event with the same behavior as a standard event but with the addition of some debugging @@ -34,12 +33,12 @@ public class IndexedEvent extends EventImpl { public IndexedEvent() {} - public IndexedEvent( - final BaseEventHashedData baseEventHashedData, - final BaseEventUnhashedData baseEventUnhashedData, - final EventImpl selfParent, - final EventImpl otherParent) { - super(baseEventHashedData, baseEventUnhashedData, selfParent, otherParent); + public IndexedEvent(final GossipEvent gossipEvent) { + super(gossipEvent); + } + + public IndexedEvent(final GossipEvent gossipEvent, final EventImpl selfParent, final EventImpl otherParent) { + super(gossipEvent, selfParent, otherParent); } /** diff --git a/platform-sdk/swirlds-platform-core/src/testFixtures/java/com/swirlds/platform/test/fixtures/event/RandomEventUtils.java b/platform-sdk/swirlds-platform-core/src/testFixtures/java/com/swirlds/platform/test/fixtures/event/RandomEventUtils.java index 1d8db2c49285..1d8412e14783 100644 --- a/platform-sdk/swirlds-platform-core/src/testFixtures/java/com/swirlds/platform/test/fixtures/event/RandomEventUtils.java +++ b/platform-sdk/swirlds-platform-core/src/testFixtures/java/com/swirlds/platform/test/fixtures/event/RandomEventUtils.java @@ -20,10 +20,10 @@ import com.swirlds.common.crypto.SignatureType; import com.swirlds.common.platform.NodeId; import com.swirlds.common.test.fixtures.RandomUtils; +import com.swirlds.platform.event.GossipEvent; import com.swirlds.platform.internal.EventImpl; import com.swirlds.platform.system.BasicSoftwareVersion; import com.swirlds.platform.system.events.BaseEventHashedData; -import com.swirlds.platform.system.events.BaseEventUnhashedData; import com.swirlds.platform.system.events.EventDescriptor; import com.swirlds.platform.system.transaction.ConsensusTransactionImpl; import edu.umd.cs.findbugs.annotations.NonNull; @@ -55,9 +55,7 @@ public static IndexedEvent randomEventWithTimestamp( final byte[] sig = new byte[SignatureType.RSA.signatureLength()]; random.nextBytes(sig); - final BaseEventUnhashedData unhashedData = new BaseEventUnhashedData(sig); - - return new IndexedEvent(hashedData, unhashedData, selfParent, otherParent); + return new IndexedEvent(new GossipEvent(hashedData, sig), selfParent, otherParent); } /** diff --git a/platform-sdk/swirlds-platform-core/src/testFixtures/java/com/swirlds/platform/test/fixtures/event/TestingEventBuilder.java b/platform-sdk/swirlds-platform-core/src/testFixtures/java/com/swirlds/platform/test/fixtures/event/TestingEventBuilder.java index 953ac342a6c5..da79500d75cc 100644 --- a/platform-sdk/swirlds-platform-core/src/testFixtures/java/com/swirlds/platform/test/fixtures/event/TestingEventBuilder.java +++ b/platform-sdk/swirlds-platform-core/src/testFixtures/java/com/swirlds/platform/test/fixtures/event/TestingEventBuilder.java @@ -26,7 +26,6 @@ import com.swirlds.platform.system.BasicSoftwareVersion; import com.swirlds.platform.system.SoftwareVersion; import com.swirlds.platform.system.events.BaseEventHashedData; -import com.swirlds.platform.system.events.BaseEventUnhashedData; import com.swirlds.platform.system.events.EventDescriptor; import com.swirlds.platform.system.transaction.ConsensusTransactionImpl; import com.swirlds.platform.system.transaction.StateSignatureTransaction; @@ -502,8 +501,7 @@ private EventDescriptor createDescriptorFromParent( final byte[] signature = new byte[SignatureType.RSA.signatureLength()]; random.nextBytes(signature); - final BaseEventUnhashedData unhashedData = new BaseEventUnhashedData(signature); - final GossipEvent gossipEvent = new GossipEvent(hashedData, unhashedData); + final GossipEvent gossipEvent = new GossipEvent(hashedData, signature); return gossipEvent; } diff --git a/platform-sdk/swirlds-platform-core/src/testFixtures/java/com/swirlds/platform/test/fixtures/event/generator/StandardGraphGenerator.java b/platform-sdk/swirlds-platform-core/src/testFixtures/java/com/swirlds/platform/test/fixtures/event/generator/StandardGraphGenerator.java index e585846a16e8..7016c2ca5309 100644 --- a/platform-sdk/swirlds-platform-core/src/testFixtures/java/com/swirlds/platform/test/fixtures/event/generator/StandardGraphGenerator.java +++ b/platform-sdk/swirlds-platform-core/src/testFixtures/java/com/swirlds/platform/test/fixtures/event/generator/StandardGraphGenerator.java @@ -28,7 +28,7 @@ import com.swirlds.platform.event.linking.InOrderLinker; import com.swirlds.platform.metrics.NoOpConsensusMetrics; import com.swirlds.platform.system.address.AddressBook; -import com.swirlds.platform.test.fixtures.addressbook.RandomAddressBookGenerator; +import com.swirlds.platform.test.fixtures.addressbook.RandomAddressBookBuilder; import com.swirlds.platform.test.fixtures.event.DynamicValue; import com.swirlds.platform.test.fixtures.event.DynamicValueGenerator; import com.swirlds.platform.test.fixtures.event.IndexedEvent; @@ -208,9 +208,8 @@ private void initializeInternalConsensus() { private void buildAddressBookInitializeEventSources(@NonNull final List> eventSources) { final int eventSourceCount = eventSources.size(); - final AddressBook addressBook = new RandomAddressBookGenerator(getRandom()) - .setSize(eventSourceCount) - .setHashStrategy(RandomAddressBookGenerator.HashStrategy.FAKE_HASH) + final AddressBook addressBook = RandomAddressBookBuilder.create(getRandom()) + .withSize(eventSourceCount) .build(); setAddressBookInitializeEventSources(eventSources, addressBook); } diff --git a/hedera-node/hedera-app-spi/src/testFixtures/java/com/hedera/node/app/spi/fixtures/state/ListReadableQueueState.java b/platform-sdk/swirlds-platform-core/src/testFixtures/java/com/swirlds/platform/test/fixtures/state/ListReadableQueueState.java similarity index 96% rename from hedera-node/hedera-app-spi/src/testFixtures/java/com/hedera/node/app/spi/fixtures/state/ListReadableQueueState.java rename to platform-sdk/swirlds-platform-core/src/testFixtures/java/com/swirlds/platform/test/fixtures/state/ListReadableQueueState.java index 4390db007d4c..f10b3622ee60 100644 --- a/hedera-node/hedera-app-spi/src/testFixtures/java/com/hedera/node/app/spi/fixtures/state/ListReadableQueueState.java +++ b/platform-sdk/swirlds-platform-core/src/testFixtures/java/com/swirlds/platform/test/fixtures/state/ListReadableQueueState.java @@ -14,9 +14,9 @@ * limitations under the License. */ -package com.hedera.node.app.spi.fixtures.state; +package com.swirlds.platform.test.fixtures.state; -import com.hedera.node.app.spi.state.ReadableQueueStateBase; +import com.swirlds.platform.state.spi.ReadableQueueStateBase; import edu.umd.cs.findbugs.annotations.NonNull; import edu.umd.cs.findbugs.annotations.Nullable; import java.util.Iterator; diff --git a/hedera-node/hedera-app-spi/src/testFixtures/java/com/hedera/node/app/spi/fixtures/state/ListWritableQueueState.java b/platform-sdk/swirlds-platform-core/src/testFixtures/java/com/swirlds/platform/test/fixtures/state/ListWritableQueueState.java similarity index 97% rename from hedera-node/hedera-app-spi/src/testFixtures/java/com/hedera/node/app/spi/fixtures/state/ListWritableQueueState.java rename to platform-sdk/swirlds-platform-core/src/testFixtures/java/com/swirlds/platform/test/fixtures/state/ListWritableQueueState.java index 078d5d3c363c..93956584d1a0 100644 --- a/hedera-node/hedera-app-spi/src/testFixtures/java/com/hedera/node/app/spi/fixtures/state/ListWritableQueueState.java +++ b/platform-sdk/swirlds-platform-core/src/testFixtures/java/com/swirlds/platform/test/fixtures/state/ListWritableQueueState.java @@ -14,9 +14,9 @@ * limitations under the License. */ -package com.hedera.node.app.spi.fixtures.state; +package com.swirlds.platform.test.fixtures.state; -import com.hedera.node.app.spi.state.WritableQueueStateBase; +import com.swirlds.platform.state.spi.WritableQueueStateBase; import edu.umd.cs.findbugs.annotations.NonNull; import java.util.Iterator; import java.util.LinkedList; diff --git a/hedera-node/hedera-app-spi/src/testFixtures/java/com/hedera/node/app/spi/fixtures/state/MapReadableKVState.java b/platform-sdk/swirlds-platform-core/src/testFixtures/java/com/swirlds/platform/test/fixtures/state/MapReadableKVState.java similarity index 93% rename from hedera-node/hedera-app-spi/src/testFixtures/java/com/hedera/node/app/spi/fixtures/state/MapReadableKVState.java rename to platform-sdk/swirlds-platform-core/src/testFixtures/java/com/swirlds/platform/test/fixtures/state/MapReadableKVState.java index 5aeaa94244d2..e90ef2c06987 100644 --- a/hedera-node/hedera-app-spi/src/testFixtures/java/com/hedera/node/app/spi/fixtures/state/MapReadableKVState.java +++ b/platform-sdk/swirlds-platform-core/src/testFixtures/java/com/swirlds/platform/test/fixtures/state/MapReadableKVState.java @@ -14,9 +14,9 @@ * limitations under the License. */ -package com.hedera.node.app.spi.fixtures.state; +package com.swirlds.platform.test.fixtures.state; -import com.hedera.node.app.spi.state.ReadableKVStateBase; +import com.swirlds.platform.state.spi.ReadableKVStateBase; import edu.umd.cs.findbugs.annotations.NonNull; import edu.umd.cs.findbugs.annotations.Nullable; import java.util.HashMap; @@ -25,7 +25,7 @@ import java.util.Objects; /** - * A simple implementation of {@link com.hedera.node.app.spi.state.ReadableKVState} backed by a + * A simple implementation of {@link com.swirlds.platform.state.spi.ReadableKVState} backed by a * {@link Map}. Test code has the option of creating an instance disregarding the backing map, or by * supplying the backing map to use. This latter option is useful if you want to use Mockito to spy * on it, or if you want to pre-populate it, or use Mockito to make the map throw an exception in @@ -88,7 +88,7 @@ public static Builder builder(@NonNull final String stateKey) { /** * A convenient builder for creating instances of {@link - * com.hedera.node.app.spi.fixtures.state.MapReadableKVState}. + * MapReadableKVState}. */ public static final class Builder { private final Map backingStore = new HashMap<>(); diff --git a/hedera-node/hedera-app-spi/src/testFixtures/java/com/hedera/node/app/spi/fixtures/state/MapReadableStates.java b/platform-sdk/swirlds-platform-core/src/testFixtures/java/com/swirlds/platform/test/fixtures/state/MapReadableStates.java similarity index 94% rename from hedera-node/hedera-app-spi/src/testFixtures/java/com/hedera/node/app/spi/fixtures/state/MapReadableStates.java rename to platform-sdk/swirlds-platform-core/src/testFixtures/java/com/swirlds/platform/test/fixtures/state/MapReadableStates.java index 48a29b988830..4374df7eabd8 100644 --- a/hedera-node/hedera-app-spi/src/testFixtures/java/com/hedera/node/app/spi/fixtures/state/MapReadableStates.java +++ b/platform-sdk/swirlds-platform-core/src/testFixtures/java/com/swirlds/platform/test/fixtures/state/MapReadableStates.java @@ -14,12 +14,12 @@ * limitations under the License. */ -package com.hedera.node.app.spi.fixtures.state; +package com.swirlds.platform.test.fixtures.state; -import com.hedera.node.app.spi.state.ReadableKVState; -import com.hedera.node.app.spi.state.ReadableQueueState; -import com.hedera.node.app.spi.state.ReadableSingletonState; -import com.hedera.node.app.spi.state.ReadableStates; +import com.swirlds.state.spi.ReadableKVState; +import com.swirlds.state.spi.ReadableQueueState; +import com.swirlds.state.spi.ReadableSingletonState; +import com.swirlds.state.spi.ReadableStates; import edu.umd.cs.findbugs.annotations.NonNull; import java.util.*; diff --git a/hedera-node/hedera-app-spi/src/testFixtures/java/com/hedera/node/app/spi/fixtures/state/MapWritableKVState.java b/platform-sdk/swirlds-platform-core/src/testFixtures/java/com/swirlds/platform/test/fixtures/state/MapWritableKVState.java similarity index 94% rename from hedera-node/hedera-app-spi/src/testFixtures/java/com/hedera/node/app/spi/fixtures/state/MapWritableKVState.java rename to platform-sdk/swirlds-platform-core/src/testFixtures/java/com/swirlds/platform/test/fixtures/state/MapWritableKVState.java index 8eac446aaf33..50d3cc924d82 100644 --- a/hedera-node/hedera-app-spi/src/testFixtures/java/com/hedera/node/app/spi/fixtures/state/MapWritableKVState.java +++ b/platform-sdk/swirlds-platform-core/src/testFixtures/java/com/swirlds/platform/test/fixtures/state/MapWritableKVState.java @@ -14,9 +14,9 @@ * limitations under the License. */ -package com.hedera.node.app.spi.fixtures.state; +package com.swirlds.platform.test.fixtures.state; -import com.hedera.node.app.spi.state.WritableKVStateBase; +import com.swirlds.platform.state.spi.WritableKVStateBase; import edu.umd.cs.findbugs.annotations.NonNull; import edu.umd.cs.findbugs.annotations.Nullable; import java.util.HashMap; @@ -25,7 +25,7 @@ import java.util.Objects; /** - * A simple implementation of {@link com.hedera.node.app.spi.state.WritableKVState} backed by a + * A simple implementation of {@link com.swirlds.state.spi.WritableKVState} backed by a * {@link Map}. Test code has the option of creating an instance disregarding the backing map, or by * supplying the backing map to use. This latter option is useful if you want to use Mockito to spy * on it, or if you want to pre-populate it, or use Mockito to make the map throw an exception in @@ -116,7 +116,7 @@ public static Builder builder(@NonNull final String stateKey) { /** * A convenient builder for creating instances of {@link - * com.hedera.node.app.spi.fixtures.state.MapWritableKVState}. + * MapWritableKVState}. */ public static final class Builder { private final Map backingStore = new HashMap<>(); diff --git a/hedera-node/hedera-app-spi/src/test/java/com/hedera/node/app/spi/state/StateTestBase.java b/platform-sdk/swirlds-platform-core/src/testFixtures/java/com/swirlds/platform/test/fixtures/state/StateTestBase.java similarity index 55% rename from hedera-node/hedera-app-spi/src/test/java/com/hedera/node/app/spi/state/StateTestBase.java rename to platform-sdk/swirlds-platform-core/src/testFixtures/java/com/swirlds/platform/test/fixtures/state/StateTestBase.java index 44b4b564ad2a..a8275f122d00 100644 --- a/hedera-node/hedera-app-spi/src/test/java/com/hedera/node/app/spi/state/StateTestBase.java +++ b/platform-sdk/swirlds-platform-core/src/testFixtures/java/com/swirlds/platform/test/fixtures/state/StateTestBase.java @@ -14,18 +14,80 @@ * limitations under the License. */ -package com.hedera.node.app.spi.state; - -import com.hedera.node.app.spi.fixtures.state.ListReadableQueueState; -import com.hedera.node.app.spi.fixtures.state.ListWritableQueueState; -import com.hedera.node.app.spi.fixtures.state.MapReadableKVState; -import com.hedera.node.app.spi.fixtures.state.MapReadableStates; -import com.hedera.node.app.spi.fixtures.state.MapWritableKVState; -import com.hedera.node.app.spi.fixtures.state.MapWritableStates; +package com.swirlds.platform.test.fixtures.state; + +import com.swirlds.platform.state.spi.ReadableSingletonStateBase; +import com.swirlds.platform.state.spi.WritableSingletonStateBase; +import com.swirlds.state.spi.ReadableSingletonState; +import com.swirlds.state.spi.WritableSingletonState; import edu.umd.cs.findbugs.annotations.NonNull; import java.util.concurrent.atomic.AtomicReference; -public class StateTestBase extends com.hedera.node.app.spi.fixtures.state.StateTestBase { +public class StateTestBase extends TestBase { + protected static final String UNKNOWN_STATE_KEY = "BOGUS_STATE_KEY"; + protected static final String UNKNOWN_KEY = "BOGUS_KEY"; + + protected static final String FRUIT_STATE_KEY = "FRUIT"; + protected static final String ANIMAL_STATE_KEY = "ANIMAL"; + protected static final String SPACE_STATE_KEY = "SPACE"; + protected static final String STEAM_STATE_KEY = "STEAM"; + public static final String COUNTRY_STATE_KEY = "COUNTRY"; + + protected static final String A_KEY = "A"; + protected static final String B_KEY = "B"; + protected static final String C_KEY = "C"; + protected static final String D_KEY = "D"; + protected static final String E_KEY = "E"; + protected static final String F_KEY = "F"; + protected static final String G_KEY = "G"; + + protected static final String APPLE = "Apple"; + protected static final String ACAI = "Acai"; + protected static final String BANANA = "Banana"; + protected static final String BLACKBERRY = "BlackBerry"; + protected static final String BLUEBERRY = "BlueBerry"; + protected static final String CHERRY = "Cherry"; + protected static final String CRANBERRY = "Cranberry"; + protected static final String DATE = "Date"; + protected static final String DRAGONFRUIT = "DragonFruit"; + protected static final String EGGPLANT = "Eggplant"; + protected static final String ELDERBERRY = "ElderBerry"; + protected static final String FIG = "Fig"; + protected static final String FEIJOA = "Feijoa"; + protected static final String GRAPE = "Grape"; + + protected static final String AARDVARK = "Aardvark"; + protected static final String BEAR = "Bear"; + protected static final String CUTTLEFISH = "Cuttlefish"; + protected static final String DOG = "Dog"; + protected static final String EMU = "Emu"; + protected static final String FOX = "Fox"; + protected static final String GOOSE = "Goose"; + + protected static final String ASTRONAUT = "Astronaut"; + protected static final String BLASTOFF = "Blastoff"; + protected static final String COMET = "Comet"; + protected static final String DRACO = "Draco"; + protected static final String EXOPLANET = "Exoplanet"; + protected static final String FORCE = "Force"; + protected static final String GRAVITY = "Gravity"; + + protected static final String ART = "Art"; + protected static final String BIOLOGY = "Biology"; + protected static final String CHEMISTRY = "Chemistry"; + protected static final String DISCIPLINE = "Discipline"; + protected static final String ECOLOGY = "Ecology"; + protected static final String FIELDS = "Fields"; + protected static final String GEOMETRY = "Geometry"; + + protected static final String AUSTRALIA = "Australia"; + protected static final String BRAZIL = "Brazil"; + protected static final String CHAD = "Chad"; + protected static final String DENMARK = "Denmark"; + protected static final String ESTONIA = "Estonia"; + protected static final String FRANCE = "France"; + protected static final String GHANA = "Ghana"; + @NonNull protected MapReadableKVState readableFruitState() { return MapReadableKVState.builder(FRUIT_STATE_KEY) @@ -125,26 +187,4 @@ protected WritableSingletonState writableCountryState() { final AtomicReference backingValue = new AtomicReference<>(AUSTRALIA); return new WritableSingletonStateBase<>(COUNTRY_STATE_KEY, backingValue::get, backingValue::set); } - - @NonNull - protected MapReadableStates allReadableStates() { - return MapReadableStates.builder() - .state(readableFruitState()) - .state(readableCountryState()) - .state(readableAnimalState()) - .state(readableSTEAMState()) - .state(readableSpaceState()) - .build(); - } - - @NonNull - protected MapWritableStates allWritableStates() { - return MapWritableStates.builder() - .state(writableAnimalState()) - .state(writableCountryState()) - .state(writableAnimalState()) - .state(writableSTEAMState()) - .state(writableSpaceState()) - .build(); - } } diff --git a/hedera-node/hedera-app-spi/src/testFixtures/java/com/hedera/node/app/spi/fixtures/TestBase.java b/platform-sdk/swirlds-platform-core/src/testFixtures/java/com/swirlds/platform/test/fixtures/state/TestBase.java similarity index 99% rename from hedera-node/hedera-app-spi/src/testFixtures/java/com/hedera/node/app/spi/fixtures/TestBase.java rename to platform-sdk/swirlds-platform-core/src/testFixtures/java/com/swirlds/platform/test/fixtures/state/TestBase.java index 6cf2b98794df..dff5bd998d03 100644 --- a/hedera-node/hedera-app-spi/src/testFixtures/java/com/hedera/node/app/spi/fixtures/TestBase.java +++ b/platform-sdk/swirlds-platform-core/src/testFixtures/java/com/swirlds/platform/test/fixtures/state/TestBase.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.hedera.node.app.spi.fixtures; +package com.swirlds.platform.test.fixtures.state; import com.hedera.pbj.runtime.io.buffer.Bytes; import edu.umd.cs.findbugs.annotations.NonNull; diff --git a/platform-sdk/swirlds-platform-core/src/testFixtures/java/com/swirlds/platform/test/fixtures/state/merkle/MerkleTestBase.java b/platform-sdk/swirlds-platform-core/src/testFixtures/java/com/swirlds/platform/test/fixtures/state/merkle/MerkleTestBase.java new file mode 100644 index 000000000000..06645b62a8e9 --- /dev/null +++ b/platform-sdk/swirlds-platform-core/src/testFixtures/java/com/swirlds/platform/test/fixtures/state/merkle/MerkleTestBase.java @@ -0,0 +1,357 @@ +/* + * Copyright (C) 2023-2024 Hedera Hashgraph, LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.swirlds.platform.test.fixtures.state.merkle; + +import static com.swirlds.platform.state.merkle.StateUtils.computeClassId; + +import com.hedera.hapi.node.base.SemanticVersion; +import com.hedera.pbj.runtime.Codec; +import com.swirlds.common.constructable.ConstructableRegistry; +import com.swirlds.common.constructable.ConstructableRegistryException; +import com.swirlds.common.crypto.DigestType; +import com.swirlds.common.io.streams.MerkleDataInputStream; +import com.swirlds.common.io.streams.MerkleDataOutputStream; +import com.swirlds.common.merkle.MerkleNode; +import com.swirlds.common.merkle.crypto.MerkleCryptoFactory; +import com.swirlds.common.merkle.crypto.MerkleCryptography; +import com.swirlds.merkle.map.MerkleMap; +import com.swirlds.merkledb.MerkleDb; +import com.swirlds.merkledb.MerkleDbDataSourceBuilder; +import com.swirlds.merkledb.MerkleDbTableConfig; +import com.swirlds.platform.state.merkle.StateUtils; +import com.swirlds.platform.state.merkle.disk.OnDiskKey; +import com.swirlds.platform.state.merkle.disk.OnDiskKeySerializer; +import com.swirlds.platform.state.merkle.disk.OnDiskValue; +import com.swirlds.platform.state.merkle.disk.OnDiskValueSerializer; +import com.swirlds.platform.state.merkle.memory.InMemoryKey; +import com.swirlds.platform.state.merkle.memory.InMemoryValue; +import com.swirlds.platform.state.merkle.queue.QueueNode; +import com.swirlds.platform.state.merkle.singleton.SingletonNode; +import com.swirlds.platform.test.fixtures.state.StateTestBase; +import com.swirlds.virtualmap.VirtualMap; +import edu.umd.cs.findbugs.annotations.NonNull; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.nio.file.Path; +import java.util.stream.Stream; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.io.TempDir; +import org.junit.jupiter.params.provider.Arguments; + +/** + * This base class provides helpful methods and defaults for simplifying the other merkle related + * tests in this and sub packages. It is highly recommended to extend from this class. + * + *

    Services

    + * + *

    This class introduces two real services, and one bad service. The real services are called + * (quite unhelpfully) {@link #FIRST_SERVICE} and {@link #SECOND_SERVICE}. There is also an {@link + * #UNKNOWN_SERVICE} which is useful for tests where we are trying to look up a service that should + * not exist. + * + *

    Each service has a number of associated states, based on those defined in {@link + * StateTestBase}. The {@link #FIRST_SERVICE} has "fruit" and "animal" states, while the {@link + * #SECOND_SERVICE} has space, steam, and country themed states. Most of these are simple String + * types for the key and value, but the space themed state uses Long as the key type. + * + *

    This class defines all the {@link Codec}, and {@link MerkleMap}s + * required to represent each of these. It does not create a {@link VirtualMap} automatically, but + * does provide APIs to make it easy to create them (the {@link VirtualMap} has a lot of setup + * complexity, and also requires a storage directory, so rather than creating these for every test + * even if they don't need it, I just use it for virtual map specific tests). + */ +public class MerkleTestBase extends StateTestBase { + public static final String FIRST_SERVICE = "First-Service"; + public static final String SECOND_SERVICE = "Second-Service"; + public static final String UNKNOWN_SERVICE = "Bogus-Service"; + + /** A TEST ONLY {@link Codec} to be used with String data types */ + public static final Codec STRING_CODEC = TestStringCodec.SINGLETON; + /** A TEST ONLY {@link Codec} to be used with Long data types */ + public static final Codec LONG_CODEC = TestLongCodec.SINGLETON; + + public static final SemanticVersion TEST_VERSION = + SemanticVersion.newBuilder().major(1).build(); + + private static final String ON_DISK_KEY_CLASS_ID_SUFFIX = "OnDiskKey"; + private static final String ON_DISK_VALUE_CLASS_ID_SUFFIX = "OnDiskValue"; + private static final String ON_DISK_KEY_SERIALIZER_CLASS_ID_SUFFIX = "OnDiskKeySerializer"; + private static final String ON_DISK_VALUE_SERIALIZER_CLASS_ID_SUFFIX = "OnDiskValueSerializer"; + private static final String IN_MEMORY_VALUE_CLASS_ID_SUFFIX = "InMemoryValue"; + private static final String SINGLETON_CLASS_ID_SUFFIX = "SingletonLeaf"; + private static final String QUEUE_NODE_CLASS_ID_SUFFIX = "QueueNode"; + + /** Used by some tests that need to hash */ + protected static final MerkleCryptography CRYPTO = MerkleCryptoFactory.getInstance(); + + // These longs are used with the "space" k/v state + public static final long A_LONG_KEY = 0L; + public static final long B_LONG_KEY = 1L; + public static final long C_LONG_KEY = 2L; + public static final long D_LONG_KEY = 3L; + public static final long E_LONG_KEY = 4L; + public static final long F_LONG_KEY = 5L; + public static final long G_LONG_KEY = 6L; + + /** + * This {@link ConstructableRegistry} is required for serialization tests. It is expensive to + * configure it, so it is null unless {@link #setupConstructableRegistry()} has been called by + * the test code. + */ + protected ConstructableRegistry registry; + + @TempDir + private Path virtualDbPath; + + // The "FRUIT" Map is part of FIRST_SERVICE + protected String fruitLabel; + protected MerkleMap, InMemoryValue> fruitMerkleMap; + + // An alternative "FRUIT" Map that is also part of FIRST_SERVICE, but based on VirtualMap + protected String fruitVirtualLabel; + protected VirtualMap, OnDiskValue> fruitVirtualMap; + + // The "ANIMAL" map is part of FIRST_SERVICE + protected String animalLabel; + protected MerkleMap, InMemoryValue> animalMerkleMap; + + // The "SPACE" map is part of SECOND_SERVICE and uses the long-based keys + protected String spaceLabel; + protected MerkleMap, InMemoryValue> spaceMerkleMap; + + // The "STEAM" queue is part of FIRST_SERVICE + protected String steamLabel; + protected QueueNode steamQueue; + + // The "COUNTRY" singleton is part of FIRST_SERVICE + protected String countryLabel; + protected SingletonNode countrySingleton; + + /** Sets up the "Fruit" merkle map, label, and metadata. */ + protected void setupFruitMerkleMap() { + fruitLabel = StateUtils.computeLabel(FIRST_SERVICE, FRUIT_STATE_KEY); + fruitMerkleMap = createMerkleMap(fruitLabel); + } + + /** Sets up the "Fruit" virtual map, label, and metadata. */ + protected void setupFruitVirtualMap() { + fruitVirtualLabel = StateUtils.computeLabel(FIRST_SERVICE, FRUIT_STATE_KEY); + fruitVirtualMap = createVirtualMap( + fruitVirtualLabel, + onDiskKeySerializerClassId(FRUIT_STATE_KEY), + onDiskKeyClassId(FRUIT_STATE_KEY), + STRING_CODEC, + onDiskValueSerializerClassId(FRUIT_STATE_KEY), + onDiskValueClassId(FRUIT_STATE_KEY), + STRING_CODEC); + } + + protected static long onDiskKeyClassId(String stateKey) { + return onDiskKeyClassId(FIRST_SERVICE, stateKey); + } + + protected static long onDiskKeyClassId(String serviceName, String stateKey) { + return computeClassId(serviceName, stateKey, TEST_VERSION, ON_DISK_KEY_CLASS_ID_SUFFIX); + } + + protected static long onDiskKeySerializerClassId(String stateKey) { + return onDiskKeySerializerClassId(FIRST_SERVICE, stateKey); + } + + protected static long onDiskKeySerializerClassId(String serviceName, String stateKey) { + return computeClassId(serviceName, stateKey, TEST_VERSION, ON_DISK_KEY_SERIALIZER_CLASS_ID_SUFFIX); + } + + protected static long onDiskValueClassId(String stateKey) { + return onDiskValueClassId(FIRST_SERVICE, stateKey); + } + + protected static long onDiskValueClassId(String serviceName, String stateKey) { + return computeClassId(serviceName, stateKey, TEST_VERSION, ON_DISK_VALUE_CLASS_ID_SUFFIX); + } + + protected static long onDiskValueSerializerClassId(String stateKey) { + return onDiskValueSerializerClassId(FIRST_SERVICE, stateKey); + } + + protected static long onDiskValueSerializerClassId(String serviceName, String stateKey) { + return computeClassId(serviceName, stateKey, TEST_VERSION, ON_DISK_VALUE_SERIALIZER_CLASS_ID_SUFFIX); + } + + protected static long queueNodeClassId(String stateKey) { + return computeClassId(FIRST_SERVICE, stateKey, TEST_VERSION, QUEUE_NODE_CLASS_ID_SUFFIX); + } + + protected static long inMemoryValueClassId(String stateKey) { + return computeClassId(FIRST_SERVICE, stateKey, TEST_VERSION, IN_MEMORY_VALUE_CLASS_ID_SUFFIX); + } + + protected static long singletonClassId(String stateKey) { + return computeClassId(FIRST_SERVICE, stateKey, TEST_VERSION, SINGLETON_CLASS_ID_SUFFIX); + } + + /** Sets up the "Animal" merkle map, label, and metadata. */ + protected void setupAnimalMerkleMap() { + animalLabel = StateUtils.computeLabel(FIRST_SERVICE, ANIMAL_STATE_KEY); + animalMerkleMap = createMerkleMap(animalLabel); + } + + /** Sets up the "Space" merkle map, label, and metadata. */ + protected void setupSpaceMerkleMap() { + spaceLabel = StateUtils.computeLabel(SECOND_SERVICE, SPACE_STATE_KEY); + spaceMerkleMap = createMerkleMap(spaceLabel); + } + + protected void setupSingletonCountry() { + countryLabel = StateUtils.computeLabel(FIRST_SERVICE, COUNTRY_STATE_KEY); + countrySingleton = new SingletonNode<>( + FIRST_SERVICE, + COUNTRY_STATE_KEY, + computeClassId(FIRST_SERVICE, COUNTRY_STATE_KEY, TEST_VERSION, SINGLETON_CLASS_ID_SUFFIX), + STRING_CODEC, + AUSTRALIA); + } + + protected void setupSteamQueue() { + steamLabel = StateUtils.computeLabel(FIRST_SERVICE, STEAM_STATE_KEY); + steamQueue = new QueueNode<>( + FIRST_SERVICE, + STEAM_STATE_KEY, + computeClassId(FIRST_SERVICE, STEAM_STATE_KEY, TEST_VERSION, QUEUE_NODE_CLASS_ID_SUFFIX), + computeClassId(FIRST_SERVICE, STEAM_STATE_KEY, TEST_VERSION, SINGLETON_CLASS_ID_SUFFIX), + STRING_CODEC); + } + + /** Sets up the {@link #registry}, ready to be used for serialization tests */ + protected void setupConstructableRegistry() { + // Unfortunately, we need to configure the ConstructableRegistry for serialization tests and + // even for basic usage of the MerkleMap (it uses it internally to make copies of internal + // nodes). + try { + registry = ConstructableRegistry.getInstance(); + + // It may have been configured during some other test, so we reset it + registry.reset(); + registry.registerConstructables("com.swirlds.merklemap"); + registry.registerConstructables("com.swirlds.merkledb"); + registry.registerConstructables("com.swirlds.fcqueue"); + registry.registerConstructables("com.swirlds.virtualmap"); + registry.registerConstructables("com.swirlds.common.merkle"); + registry.registerConstructables("com.swirlds.common"); + registry.registerConstructables("com.swirlds.merkle"); + registry.registerConstructables("com.swirlds.merkle.tree"); + } catch (ConstructableRegistryException ex) { + throw new AssertionError(ex); + } + } + + /** Creates a new arbitrary merkle map with the given label. */ + protected , V> MerkleMap, InMemoryValue> createMerkleMap( + String label) { + final var map = new MerkleMap, InMemoryValue>(); + map.setLabel(label); + return map; + } + + /** Creates a new arbitrary virtual map with the given label, storageDir, and metadata */ + @SuppressWarnings("unchecked") + protected VirtualMap, OnDiskValue> createVirtualMap( + String label, + long keySerializerClassId, + long keyClassId, + Codec keyCodec, + long valueSerializerClassId, + long valueClassId, + Codec valueCodec) { + final var merkleDbTableConfig = new MerkleDbTableConfig<>( + (short) 1, + DigestType.SHA_384, + (short) 1, + new OnDiskKeySerializer<>(keySerializerClassId, keyClassId, keyCodec), + (short) 1, + new OnDiskValueSerializer<>(valueSerializerClassId, valueClassId, valueCodec)); + merkleDbTableConfig.hashesRamToDiskThreshold(0); + merkleDbTableConfig.maxNumberOfKeys(100); + merkleDbTableConfig.preferDiskIndices(true); + final var builder = new MerkleDbDataSourceBuilder<>(virtualDbPath, merkleDbTableConfig); + return new VirtualMap<>(label, builder); + } + + /** A convenience method for creating {@link SemanticVersion}. */ + protected SemanticVersion version(int major, int minor, int patch) { + return new SemanticVersion(major, minor, patch, null, null); + } + + /** A convenience method for adding a k/v pair to a merkle map */ + protected void add( + MerkleMap, InMemoryValue> map, + long inMemoryValueClassId, + Codec keyCodec, + Codec valueCodec, + String key, + String value) { + final var k = new InMemoryKey<>(key); + map.put(k, new InMemoryValue<>(inMemoryValueClassId, keyCodec, valueCodec, k, value)); + } + + /** A convenience method for adding a k/v pair to a virtual map */ + protected void add( + VirtualMap, OnDiskValue> map, + long onDiskKeyClassId, + Codec keyCodec, + long onDiskValueClassId, + Codec valueCodec, + String key, + String value) { + final var k = new OnDiskKey<>(onDiskKeyClassId, keyCodec, key); + map.put(k, new OnDiskValue<>(onDiskValueClassId, valueCodec, value)); + } + + /** A convenience method used to serialize a merkle tree */ + protected byte[] writeTree(@NonNull final MerkleNode tree, @NonNull final Path tempDir) throws IOException { + final var byteOutputStream = new ByteArrayOutputStream(); + try (final var out = new MerkleDataOutputStream(byteOutputStream)) { + out.writeMerkleTree(tempDir, tree); + } + return byteOutputStream.toByteArray(); + } + + /** A convenience method used to deserialize a merkle tree */ + protected T parseTree(@NonNull final byte[] state, @NonNull final Path tempDir) + throws IOException { + // Restore to a fresh MerkleDb instance + MerkleDb.resetDefaultInstancePath(); + final var byteInputStream = new ByteArrayInputStream(state); + try (final var in = new MerkleDataInputStream(byteInputStream)) { + return in.readMerkleTree(tempDir, 100); + } + } + + public static Stream illegalServiceNames() { + return TestArgumentUtils.illegalIdentifiers(); + } + + public static Stream legalServiceNames() { + return TestArgumentUtils.legalIdentifiers(); + } + + @AfterEach + void cleanUp() { + MerkleDb.resetDefaultInstancePath(); + } +} diff --git a/platform-sdk/swirlds-platform-core/src/testFixtures/java/com/swirlds/platform/test/fixtures/state/merkle/TestArgumentUtils.java b/platform-sdk/swirlds-platform-core/src/testFixtures/java/com/swirlds/platform/test/fixtures/state/merkle/TestArgumentUtils.java new file mode 100644 index 000000000000..8699d952aa0a --- /dev/null +++ b/platform-sdk/swirlds-platform-core/src/testFixtures/java/com/swirlds/platform/test/fixtures/state/merkle/TestArgumentUtils.java @@ -0,0 +1,95 @@ +/* + * Copyright (C) 2024 Hedera Hashgraph, LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.swirlds.platform.test.fixtures.state.merkle; + +import com.swirlds.platform.test.fixtures.state.TestBase; +import java.util.ArrayList; +import java.util.LinkedList; +import java.util.List; +import java.util.Locale; +import java.util.Random; +import java.util.stream.Stream; +import org.junit.jupiter.params.provider.Arguments; + +public class TestArgumentUtils { + /** A *static* pseudo-random number generator used to generate the legal identifiers */ + private static final Random RAND = new Random(8892381L); + + public static Stream illegalIdentifiers() { + // The only valid characters are A-Za-z0-9_-. Any other character is a problem. + // So I will construct three different types of invalid strings. Those that contain only + // an invalid character, those that start with an invalid character and are followed by + // valid characters, and those that start with valid characters and are trailed by a single + // invalid character. I will select invalid characters from the ASCII set, and a subset of + // values that are above the ASCII range. I will make sure to include multibyte characters. + + // Add every ASCII char that is illegal + final List illegalChars = new ArrayList<>(); + for (char i = 0; i < 255; i++) { + if ((i >= 'A' && i <= 'Z') || (i >= 'a' && i <= 'z') || (i >= '0' && i <= '9') || (i == '-' || i == '_')) { + // This is a valid character, so skip it -- don't add it to illegalChars! + continue; + } + illegalChars.add("" + i); + } + // And the illegal 😈 multibyte character + illegalChars.add("\uD83D\uDE08"); + + // Construct the arguments + final List argumentsList = new LinkedList<>(); + + // Add each illegal character on its own as a test case + for (final var illegalChar : illegalChars) { + argumentsList.add(Arguments.of(illegalChar)); + } + + // Add each illegal character as the suffix to an otherwise legal string + for (final var illegalChar : illegalChars) { + argumentsList.add(Arguments.of("Some Legal Characters " + illegalChar)); + } + + // Add each illegal character as the prefix to an otherwise legal string + for (final var illegalChar : illegalChars) { + argumentsList.add(Arguments.of(illegalChar + " Some Legal Characters")); + } + + // return it + return argumentsList.stream(); + } + + public static Stream legalIdentifiers() { + List args = new LinkedList<>(); + + // An arbitrary collection of strings that contain 0-9A-Za-z and space. + final String upper = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"; + final String lower = upper.toLowerCase(Locale.ROOT); + String digits = "0123456789"; + String validChars = upper + lower + digits + "-" + "_"; + + // Test scenarios where there is a single valid char + for (int i = 0; i < validChars.length(); i++) { + args.add(Arguments.of("" + validChars.charAt(i))); + } + + // Test scenarios where we have a set of valid chars + for (int i = 0; i < 100; i++) { + args.add(Arguments.of(TestBase.randomString(RAND, validChars, 12))); + } + + return args.stream(); + } +} diff --git a/hedera-node/hedera-app/src/test/java/com/hedera/node/app/state/merkle/TestLongCodec.java b/platform-sdk/swirlds-platform-core/src/testFixtures/java/com/swirlds/platform/test/fixtures/state/merkle/TestLongCodec.java similarity index 95% rename from hedera-node/hedera-app/src/test/java/com/hedera/node/app/state/merkle/TestLongCodec.java rename to platform-sdk/swirlds-platform-core/src/testFixtures/java/com/swirlds/platform/test/fixtures/state/merkle/TestLongCodec.java index 643074c10316..e0cbd90d3e9e 100644 --- a/hedera-node/hedera-app/src/test/java/com/hedera/node/app/state/merkle/TestLongCodec.java +++ b/platform-sdk/swirlds-platform-core/src/testFixtures/java/com/swirlds/platform/test/fixtures/state/merkle/TestLongCodec.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.hedera.node.app.state.merkle; +package com.swirlds.platform.test.fixtures.state.merkle; import com.hedera.pbj.runtime.Codec; import com.hedera.pbj.runtime.ParseException; @@ -33,7 +33,7 @@ * @deprecated Use ProtoLong and ProtoLong.PROTOBUF instead of Long and this codec. */ @SuppressWarnings("Singleton") -class TestLongCodec implements Codec { +public class TestLongCodec implements Codec { public static final TestLongCodec SINGLETON = new TestLongCodec(); private TestLongCodec() {} diff --git a/hedera-node/hedera-app/src/test/java/com/hedera/node/app/state/merkle/TestStringCodec.java b/platform-sdk/swirlds-platform-core/src/testFixtures/java/com/swirlds/platform/test/fixtures/state/merkle/TestStringCodec.java similarity index 96% rename from hedera-node/hedera-app/src/test/java/com/hedera/node/app/state/merkle/TestStringCodec.java rename to platform-sdk/swirlds-platform-core/src/testFixtures/java/com/swirlds/platform/test/fixtures/state/merkle/TestStringCodec.java index f0b72e621638..94a6cf46ae2d 100644 --- a/hedera-node/hedera-app/src/test/java/com/hedera/node/app/state/merkle/TestStringCodec.java +++ b/platform-sdk/swirlds-platform-core/src/testFixtures/java/com/swirlds/platform/test/fixtures/state/merkle/TestStringCodec.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.hedera.node.app.state.merkle; +package com.swirlds.platform.test.fixtures.state.merkle; import com.hedera.pbj.runtime.Codec; import com.hedera.pbj.runtime.ParseException; @@ -33,7 +33,7 @@ * @deprecated Use ProtoString and ProtoString.PROTOBUF instead of String and this codec. */ @SuppressWarnings("Singleton") -class TestStringCodec implements Codec { +public class TestStringCodec implements Codec { public static final TestStringCodec SINGLETON = new TestStringCodec(); private TestStringCodec() {} diff --git a/platform-sdk/swirlds-platform-core/src/testFixtures/java/com/swirlds/platform/test/fixtures/turtle/gossip/EventInTransit.java b/platform-sdk/swirlds-platform-core/src/testFixtures/java/com/swirlds/platform/test/fixtures/turtle/gossip/EventInTransit.java new file mode 100644 index 000000000000..88789788dcbe --- /dev/null +++ b/platform-sdk/swirlds-platform-core/src/testFixtures/java/com/swirlds/platform/test/fixtures/turtle/gossip/EventInTransit.java @@ -0,0 +1,37 @@ +/* + * Copyright (C) 2024 Hedera Hashgraph, LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.swirlds.platform.test.fixtures.turtle.gossip; + +import com.swirlds.common.platform.NodeId; +import com.swirlds.platform.event.GossipEvent; +import edu.umd.cs.findbugs.annotations.NonNull; +import java.time.Instant; + +/** + * An event that is in transit between nodes in the network. + * + * @param event the event being transmitted + * @param sender the node that sent the event + * @param arrivalTime the time the event is scheduled to arrive at its destination + */ +public record EventInTransit(@NonNull GossipEvent event, @NonNull NodeId sender, @NonNull Instant arrivalTime) + implements Comparable { + @Override + public int compareTo(@NonNull final EventInTransit that) { + return arrivalTime.compareTo(that.arrivalTime); + } +} diff --git a/platform-sdk/swirlds-platform-core/src/testFixtures/java/com/swirlds/platform/test/fixtures/turtle/gossip/SimulatedGossip.java b/platform-sdk/swirlds-platform-core/src/testFixtures/java/com/swirlds/platform/test/fixtures/turtle/gossip/SimulatedGossip.java new file mode 100644 index 000000000000..d0760e0d60d8 --- /dev/null +++ b/platform-sdk/swirlds-platform-core/src/testFixtures/java/com/swirlds/platform/test/fixtures/turtle/gossip/SimulatedGossip.java @@ -0,0 +1,95 @@ +/* + * Copyright (C) 2024 Hedera Hashgraph, LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.swirlds.platform.test.fixtures.turtle.gossip; + +import com.swirlds.common.platform.NodeId; +import com.swirlds.common.wiring.model.WiringModel; +import com.swirlds.common.wiring.wires.input.BindableInputWire; +import com.swirlds.common.wiring.wires.output.StandardOutputWire; +import com.swirlds.platform.consensus.EventWindow; +import com.swirlds.platform.event.GossipEvent; +import com.swirlds.platform.gossip.IntakeEventCounter; +import com.swirlds.platform.wiring.NoInput; +import com.swirlds.platform.wiring.components.Gossip; +import edu.umd.cs.findbugs.annotations.NonNull; +import java.util.Objects; + +/** + * Simulates the {@link Gossip} subsystem for a group of nodes running on a {@link SimulatedNetwork}. + */ +public class SimulatedGossip implements Gossip { + + private final SimulatedNetwork network; + private final NodeId selfId; + private IntakeEventCounter intakeEventCounter; + + private StandardOutputWire eventOutput; + + /** + * Constructor. + * + * @param network the network on which this gossip system will run + * @param selfId the ID of the node running this gossip system + */ + public SimulatedGossip(@NonNull final SimulatedNetwork network, @NonNull final NodeId selfId) { + this.network = Objects.requireNonNull(network); + this.selfId = Objects.requireNonNull(selfId); + } + + /** + * Add an intake event counter that gets incremented for all events that enter the intake pipeline. + * + * @param intakeEventCounter the intake event counter + */ + public void provideIntakeEventCounter(@NonNull final IntakeEventCounter intakeEventCounter) { + this.intakeEventCounter = Objects.requireNonNull(intakeEventCounter); + } + + /** + * {@inheritDoc} + */ + @Override + public void bind( + @NonNull final WiringModel model, + @NonNull final BindableInputWire eventInput, + @NonNull final BindableInputWire eventWindowInput, + @NonNull final StandardOutputWire eventOutput, + @NonNull final BindableInputWire startInput, + @NonNull final BindableInputWire stopInput, + @NonNull final BindableInputWire clearInput) { + + this.eventOutput = Objects.requireNonNull(eventOutput); + eventInput.bindConsumer(event -> network.submitEvent(selfId, event)); + + eventWindowInput.bindConsumer(ignored -> {}); + startInput.bindConsumer(ignored -> {}); + stopInput.bindConsumer(ignored -> {}); + clearInput.bindConsumer(ignored -> {}); + } + + /** + * This method is called every time this node receives an event from the network. + * + * @param event the event that was received + */ + void receiveEvent(@NonNull final GossipEvent event) { + if (intakeEventCounter != null) { + intakeEventCounter.eventEnteredIntakePipeline(event.getSenderId()); + } + eventOutput.forward(event); + } +} diff --git a/platform-sdk/swirlds-platform-core/src/testFixtures/java/com/swirlds/platform/test/fixtures/turtle/gossip/SimulatedNetwork.java b/platform-sdk/swirlds-platform-core/src/testFixtures/java/com/swirlds/platform/test/fixtures/turtle/gossip/SimulatedNetwork.java new file mode 100644 index 000000000000..f7155856431a --- /dev/null +++ b/platform-sdk/swirlds-platform-core/src/testFixtures/java/com/swirlds/platform/test/fixtures/turtle/gossip/SimulatedNetwork.java @@ -0,0 +1,221 @@ +/* + * Copyright (C) 2024 Hedera Hashgraph, LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.swirlds.platform.test.fixtures.turtle.gossip; + +import com.swirlds.common.io.streams.SerializableDataInputStream; +import com.swirlds.common.io.streams.SerializableDataOutputStream; +import com.swirlds.common.platform.NodeId; +import com.swirlds.platform.event.GossipEvent; +import com.swirlds.platform.system.address.AddressBook; +import edu.umd.cs.findbugs.annotations.NonNull; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.time.Duration; +import java.time.Instant; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.PriorityQueue; +import java.util.Random; + +/** + * Connects {@link SimulatedGossip} peers in a simulated network. + *

    + * This gossip simulation is intentionally simplistic. It does not attempt to mimic any real gossip algorithm in any + * meaningful way and makes no attempt to reduce the rate of duplicate events. + */ +public class SimulatedNetwork { + + /** + * The random number generator to use for simulating network delays. + */ + private final Random random; + + /** + * Events that have been submitted within the most recent tick. It is safe for multiple nodes to add to their list + * of submitted events in parallel. + */ + private final Map> newlySubmittedEvents = new HashMap<>(); + + /** + * A sorted list of node IDs for when deterministic iteration order is required. + */ + final List sortedNodeIds = new ArrayList<>(); + + /** + * Events that are currently in transit between nodes in the network. + */ + private final Map> eventsInTransit = new HashMap<>(); + + /** + * The gossip "component" for each node in the network. + */ + private final Map gossipInstances = new HashMap<>(); + + /** + * The average delay for events to travel between nodes, in nanoseconds. + */ + private final long averageDelayNanos; + + /** + * The standard deviation of the delay for events to travel between nodes, in nanoseconds. + */ + private final long standardDeviationDelayNanos; + + /** + * Constructor. + * + * @param random the random number generator to use for simulating network delays + * @param addressBook the address book of the network + * @param averageDelay the average delay for events to travel between nodes + * @param standardDeviationDelay the standard deviation of the delay for events to travel between nodes + */ + public SimulatedNetwork( + @NonNull final Random random, + @NonNull final AddressBook addressBook, + @NonNull final Duration averageDelay, + @NonNull final Duration standardDeviationDelay) { + + this.random = Objects.requireNonNull(random); + + for (final NodeId nodeId : addressBook.getNodeIdSet().stream().sorted().toList()) { + newlySubmittedEvents.put(nodeId, new ArrayList<>()); + sortedNodeIds.add(nodeId); + eventsInTransit.put(nodeId, new PriorityQueue<>()); + gossipInstances.put(nodeId, new SimulatedGossip(this, nodeId)); + } + + this.averageDelayNanos = averageDelay.toNanos(); + this.standardDeviationDelayNanos = standardDeviationDelay.toNanos(); + } + + /** + * Get the gossip instance for a given node. + * + * @param nodeId the id of the node + * @return the gossip instance for the node + */ + @NonNull + public SimulatedGossip getGossipInstance(@NonNull final NodeId nodeId) { + return gossipInstances.get(nodeId); + } + + /** + * Submit an event to be gossiped around the network. Safe to be called by multiple nodes in parallel. + * + * @param submitterId the id of the node submitting the event + * @param event the event to gossip + */ + public void submitEvent(@NonNull final NodeId submitterId, @NonNull final GossipEvent event) { + newlySubmittedEvents.get(submitterId).add(event); + } + + /** + * Move time forward to the given instant. + * + * @param now the new time + */ + public void tick(@NonNull final Instant now) { + deliverEvents(now); + transmitEvents(now); + } + + /** + * For each node, deliver all events that are eligible for immediate delivery. + */ + private void deliverEvents(@NonNull final Instant now) { + // Iteration order does not need to be deterministic. The nodes are not running on any thread + // when this method is called, and so the order in which nodes are provided events makes no difference. + for (final Map.Entry> entry : eventsInTransit.entrySet()) { + final NodeId nodeId = entry.getKey(); + final PriorityQueue events = entry.getValue(); + + final Iterator iterator = events.iterator(); + while (iterator.hasNext()) { + final EventInTransit event = iterator.next(); + if (event.arrivalTime().isAfter(now)) { + // no more events to deliver + break; + } + + iterator.remove(); + gossipInstances.get(nodeId).receiveEvent(event.event()); + } + } + } + + /** + * For each node, take the events that were submitted within the last tick and "transmit them over the network". + * + * @param now the current time + */ + private void transmitEvents(@NonNull final Instant now) { + // Transmission order of the loops in this method must be deterministic, else nodes may receive events + // in nondeterministic orders with nondeterministic timing. + + for (final NodeId sender : sortedNodeIds) { + final List events = newlySubmittedEvents.get(sender); + for (final GossipEvent event : events) { + for (final NodeId receiver : sortedNodeIds) { + if (sender.equals(receiver)) { + // Don't gossip to ourselves + continue; + } + + final PriorityQueue receiverEvents = eventsInTransit.get(receiver); + + final Instant deliveryTime = now.plusNanos( + (long) (averageDelayNanos + random.nextGaussian() * standardDeviationDelayNanos)); + + final GossipEvent eventToDeliver = deepCopyEvent(event); + eventToDeliver.setSenderId(sender); + eventToDeliver.setTimeReceived(deliveryTime); + final EventInTransit eventInTransit = new EventInTransit(eventToDeliver, sender, deliveryTime); + receiverEvents.add(eventInTransit); + } + } + events.clear(); + } + } + + /** + * Create a deep copy of an event. Until events become entirely immutable, this is necessary to prevent nodes from + * modifying each other's events. + * + * @param event the event to copy + * @return a deep copy of the event + */ + @NonNull + private GossipEvent deepCopyEvent(@NonNull final GossipEvent event) { + try { + final ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); + final SerializableDataOutputStream outputStream = new SerializableDataOutputStream(byteArrayOutputStream); + outputStream.writeSerializable(event, false); + final SerializableDataInputStream inputStream = + new SerializableDataInputStream(new ByteArrayInputStream(byteArrayOutputStream.toByteArray())); + final GossipEvent copy = inputStream.readSerializable(false, GossipEvent::new); + copy.getHashedData().setHash(event.getHashedData().getHash()); + return copy; + } catch (final IOException e) { + throw new RuntimeException(e); + } + } +} diff --git a/platform-sdk/swirlds-platform-core/src/testFixtures/java/module-info.java b/platform-sdk/swirlds-platform-core/src/testFixtures/java/module-info.java index f12cf007c25d..900845cfd642 100644 --- a/platform-sdk/swirlds-platform-core/src/testFixtures/java/module-info.java +++ b/platform-sdk/swirlds-platform-core/src/testFixtures/java/module-info.java @@ -1,18 +1,26 @@ open module com.swirlds.platform.core.test.fixtures { + requires transitive com.hedera.node.hapi; + requires transitive com.hedera.pbj.runtime; requires transitive com.swirlds.common.test.fixtures; requires transitive com.swirlds.common; + requires transitive com.swirlds.merkle; requires transitive com.swirlds.platform.core; - requires com.hedera.node.hapi; - requires com.hedera.pbj.runtime; + requires transitive com.swirlds.state.api; + requires transitive com.swirlds.virtualmap; + requires transitive org.junit.jupiter.params; requires com.swirlds.logging; + requires com.swirlds.merkledb; requires org.apache.logging.log4j; requires org.junit.jupiter.api; + requires org.mockito; requires static com.github.spotbugs.annotations; + exports com.swirlds.platform.test.fixtures; exports com.swirlds.platform.test.fixtures.stream; exports com.swirlds.platform.test.fixtures.event; exports com.swirlds.platform.test.fixtures.event.source; exports com.swirlds.platform.test.fixtures.event.generator; exports com.swirlds.platform.test.fixtures.state; exports com.swirlds.platform.test.fixtures.addressbook; + exports com.swirlds.platform.test.fixtures.state.merkle; } diff --git a/hedera-node/hedera-app-spi/src/testFixtures/resources/com/hedera/node/app/spi/fixtures/wordlist.txt b/platform-sdk/swirlds-platform-core/src/testFixtures/resources/com/swirlds/platform/test/fixtures/state/wordlist.txt similarity index 100% rename from hedera-node/hedera-app-spi/src/testFixtures/resources/com/hedera/node/app/spi/fixtures/wordlist.txt rename to platform-sdk/swirlds-platform-core/src/testFixtures/resources/com/swirlds/platform/test/fixtures/state/wordlist.txt diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/misc/GuidedTourLocalSuite.java b/platform-sdk/swirlds-state-api/build.gradle.kts similarity index 80% rename from hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/misc/GuidedTourLocalSuite.java rename to platform-sdk/swirlds-state-api/build.gradle.kts index dd88b47a263b..17e1b3a17f4a 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/misc/GuidedTourLocalSuite.java +++ b/platform-sdk/swirlds-state-api/build.gradle.kts @@ -1,5 +1,5 @@ /* - * Copyright (C) 2020-2024 Hedera Hashgraph, LLC + * Copyright (C) 2024 Hedera Hashgraph, LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,6 +14,7 @@ * limitations under the License. */ -package com.hedera.services.bdd.suites.misc; - -public class GuidedTourLocalSuite {} +plugins { + id("com.hedera.gradle.platform") + id("com.hedera.gradle.platform-publish") +} diff --git a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/state/HederaState.java b/platform-sdk/swirlds-state-api/src/main/java/com/swirlds/state/HederaState.java similarity index 87% rename from hedera-node/hedera-app/src/main/java/com/hedera/node/app/state/HederaState.java rename to platform-sdk/swirlds-state-api/src/main/java/com/swirlds/state/HederaState.java index 34228b20a40c..d5c499c54dba 100644 --- a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/state/HederaState.java +++ b/platform-sdk/swirlds-state-api/src/main/java/com/swirlds/state/HederaState.java @@ -14,12 +14,12 @@ * limitations under the License. */ -package com.hedera.node.app.state; +package com.swirlds.state; -import com.hedera.node.app.spi.state.ReadableKVState; -import com.hedera.node.app.spi.state.ReadableStates; -import com.hedera.node.app.spi.state.WritableKVState; -import com.hedera.node.app.spi.state.WritableStates; +import com.swirlds.state.spi.ReadableKVState; +import com.swirlds.state.spi.ReadableStates; +import com.swirlds.state.spi.WritableKVState; +import com.swirlds.state.spi.WritableStates; import edu.umd.cs.findbugs.annotations.NonNull; /** diff --git a/hedera-node/hedera-app-spi/src/main/java/com/hedera/node/app/spi/state/ReadableKVState.java b/platform-sdk/swirlds-state-api/src/main/java/com/swirlds/state/spi/ReadableKVState.java similarity index 98% rename from hedera-node/hedera-app-spi/src/main/java/com/hedera/node/app/spi/state/ReadableKVState.java rename to platform-sdk/swirlds-state-api/src/main/java/com/swirlds/state/spi/ReadableKVState.java index 4e1cf33d95f4..06b155ec25cc 100644 --- a/hedera-node/hedera-app-spi/src/main/java/com/hedera/node/app/spi/state/ReadableKVState.java +++ b/platform-sdk/swirlds-state-api/src/main/java/com/swirlds/state/spi/ReadableKVState.java @@ -14,8 +14,9 @@ * limitations under the License. */ -package com.hedera.node.app.spi.state; +package com.swirlds.state.spi; +import com.hedera.pbj.runtime.Schema; import edu.umd.cs.findbugs.annotations.NonNull; import edu.umd.cs.findbugs.annotations.Nullable; import java.util.Iterator; diff --git a/hedera-node/hedera-app-spi/src/main/java/com/hedera/node/app/spi/state/ReadableQueueState.java b/platform-sdk/swirlds-state-api/src/main/java/com/swirlds/state/spi/ReadableQueueState.java similarity index 96% rename from hedera-node/hedera-app-spi/src/main/java/com/hedera/node/app/spi/state/ReadableQueueState.java rename to platform-sdk/swirlds-state-api/src/main/java/com/swirlds/state/spi/ReadableQueueState.java index 66886164d2d8..fb9354591da1 100644 --- a/hedera-node/hedera-app-spi/src/main/java/com/hedera/node/app/spi/state/ReadableQueueState.java +++ b/platform-sdk/swirlds-state-api/src/main/java/com/swirlds/state/spi/ReadableQueueState.java @@ -14,8 +14,9 @@ * limitations under the License. */ -package com.hedera.node.app.spi.state; +package com.swirlds.state.spi; +import com.hedera.pbj.runtime.Schema; import edu.umd.cs.findbugs.annotations.NonNull; import edu.umd.cs.findbugs.annotations.Nullable; import java.util.Iterator; diff --git a/hedera-node/hedera-app-spi/src/main/java/com/hedera/node/app/spi/state/ReadableSingletonState.java b/platform-sdk/swirlds-state-api/src/main/java/com/swirlds/state/spi/ReadableSingletonState.java similarity index 96% rename from hedera-node/hedera-app-spi/src/main/java/com/hedera/node/app/spi/state/ReadableSingletonState.java rename to platform-sdk/swirlds-state-api/src/main/java/com/swirlds/state/spi/ReadableSingletonState.java index 0028fafebbcf..f4101352b52b 100644 --- a/hedera-node/hedera-app-spi/src/main/java/com/hedera/node/app/spi/state/ReadableSingletonState.java +++ b/platform-sdk/swirlds-state-api/src/main/java/com/swirlds/state/spi/ReadableSingletonState.java @@ -14,8 +14,9 @@ * limitations under the License. */ -package com.hedera.node.app.spi.state; +package com.swirlds.state.spi; +import com.hedera.pbj.runtime.Schema; import edu.umd.cs.findbugs.annotations.NonNull; import edu.umd.cs.findbugs.annotations.Nullable; diff --git a/hedera-node/hedera-app-spi/src/main/java/com/hedera/node/app/spi/state/ReadableStates.java b/platform-sdk/swirlds-state-api/src/main/java/com/swirlds/state/spi/ReadableStates.java similarity index 97% rename from hedera-node/hedera-app-spi/src/main/java/com/hedera/node/app/spi/state/ReadableStates.java rename to platform-sdk/swirlds-state-api/src/main/java/com/swirlds/state/spi/ReadableStates.java index 1d7712ba6079..58aad672a766 100644 --- a/hedera-node/hedera-app-spi/src/main/java/com/hedera/node/app/spi/state/ReadableStates.java +++ b/platform-sdk/swirlds-state-api/src/main/java/com/swirlds/state/spi/ReadableStates.java @@ -14,8 +14,9 @@ * limitations under the License. */ -package com.hedera.node.app.spi.state; +package com.swirlds.state.spi; +import com.hedera.pbj.runtime.Schema; import edu.umd.cs.findbugs.annotations.NonNull; import java.util.Set; diff --git a/hedera-node/hedera-app-spi/src/main/java/com/hedera/node/app/spi/state/WritableKVState.java b/platform-sdk/swirlds-state-api/src/main/java/com/swirlds/state/spi/WritableKVState.java similarity index 98% rename from hedera-node/hedera-app-spi/src/main/java/com/hedera/node/app/spi/state/WritableKVState.java rename to platform-sdk/swirlds-state-api/src/main/java/com/swirlds/state/spi/WritableKVState.java index badd908c31b7..e1a2eba224ec 100644 --- a/hedera-node/hedera-app-spi/src/main/java/com/hedera/node/app/spi/state/WritableKVState.java +++ b/platform-sdk/swirlds-state-api/src/main/java/com/swirlds/state/spi/WritableKVState.java @@ -14,9 +14,9 @@ * limitations under the License. */ -package com.hedera.node.app.spi.state; +package com.swirlds.state.spi; -import com.hedera.node.app.spi.metrics.StoreMetrics; +import com.swirlds.state.spi.metrics.StoreMetrics; import edu.umd.cs.findbugs.annotations.NonNull; import edu.umd.cs.findbugs.annotations.Nullable; import java.util.Iterator; diff --git a/hedera-node/hedera-app-spi/src/main/java/com/hedera/node/app/spi/state/WritableQueueState.java b/platform-sdk/swirlds-state-api/src/main/java/com/swirlds/state/spi/WritableQueueState.java similarity index 97% rename from hedera-node/hedera-app-spi/src/main/java/com/hedera/node/app/spi/state/WritableQueueState.java rename to platform-sdk/swirlds-state-api/src/main/java/com/swirlds/state/spi/WritableQueueState.java index b94d01a719af..24542505b9db 100644 --- a/hedera-node/hedera-app-spi/src/main/java/com/hedera/node/app/spi/state/WritableQueueState.java +++ b/platform-sdk/swirlds-state-api/src/main/java/com/swirlds/state/spi/WritableQueueState.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.hedera.node.app.spi.state; +package com.swirlds.state.spi; import edu.umd.cs.findbugs.annotations.NonNull; import edu.umd.cs.findbugs.annotations.Nullable; diff --git a/hedera-node/hedera-app-spi/src/main/java/com/hedera/node/app/spi/state/WritableSingletonState.java b/platform-sdk/swirlds-state-api/src/main/java/com/swirlds/state/spi/WritableSingletonState.java similarity index 96% rename from hedera-node/hedera-app-spi/src/main/java/com/hedera/node/app/spi/state/WritableSingletonState.java rename to platform-sdk/swirlds-state-api/src/main/java/com/swirlds/state/spi/WritableSingletonState.java index 797c594c0d7c..ec7fefaa9c63 100644 --- a/hedera-node/hedera-app-spi/src/main/java/com/hedera/node/app/spi/state/WritableSingletonState.java +++ b/platform-sdk/swirlds-state-api/src/main/java/com/swirlds/state/spi/WritableSingletonState.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.hedera.node.app.spi.state; +package com.swirlds.state.spi; import edu.umd.cs.findbugs.annotations.Nullable; diff --git a/hedera-node/hedera-app-spi/src/main/java/com/hedera/node/app/spi/state/WritableStates.java b/platform-sdk/swirlds-state-api/src/main/java/com/swirlds/state/spi/WritableStates.java similarity index 96% rename from hedera-node/hedera-app-spi/src/main/java/com/hedera/node/app/spi/state/WritableStates.java rename to platform-sdk/swirlds-state-api/src/main/java/com/swirlds/state/spi/WritableStates.java index 7ec38cd4a6ec..17338fbcb30a 100644 --- a/hedera-node/hedera-app-spi/src/main/java/com/hedera/node/app/spi/state/WritableStates.java +++ b/platform-sdk/swirlds-state-api/src/main/java/com/swirlds/state/spi/WritableStates.java @@ -14,8 +14,9 @@ * limitations under the License. */ -package com.hedera.node.app.spi.state; +package com.swirlds.state.spi; +import com.hedera.pbj.runtime.Schema; import edu.umd.cs.findbugs.annotations.NonNull; /** Essentially, a map of {@link WritableKVState}s. Each state may be retrieved by key. */ diff --git a/hedera-node/hedera-app-spi/src/main/java/com/hedera/node/app/spi/metrics/StoreMetrics.java b/platform-sdk/swirlds-state-api/src/main/java/com/swirlds/state/spi/metrics/StoreMetrics.java similarity index 95% rename from hedera-node/hedera-app-spi/src/main/java/com/hedera/node/app/spi/metrics/StoreMetrics.java rename to platform-sdk/swirlds-state-api/src/main/java/com/swirlds/state/spi/metrics/StoreMetrics.java index 6f5bb8c50275..0a6925f9da88 100644 --- a/hedera-node/hedera-app-spi/src/main/java/com/hedera/node/app/spi/metrics/StoreMetrics.java +++ b/platform-sdk/swirlds-state-api/src/main/java/com/swirlds/state/spi/metrics/StoreMetrics.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.hedera.node.app.spi.metrics; +package com.swirlds.state.spi.metrics; /** * Helper class that maintains utilization metrics for a store. diff --git a/platform-sdk/swirlds-state-api/src/main/java/module-info.java b/platform-sdk/swirlds-state-api/src/main/java/module-info.java new file mode 100644 index 000000000000..60d4171e8d6b --- /dev/null +++ b/platform-sdk/swirlds-state-api/src/main/java/module-info.java @@ -0,0 +1,8 @@ +module com.swirlds.state.api { + exports com.swirlds.state; + exports com.swirlds.state.spi; + exports com.swirlds.state.spi.metrics; + + requires com.hedera.pbj.runtime; + requires static com.github.spotbugs.annotations; +} diff --git a/platform-sdk/swirlds-unit-tests/core/swirlds-platform-test/build.gradle.kts b/platform-sdk/swirlds-unit-tests/core/swirlds-platform-test/build.gradle.kts index 2bcd15c2d09f..1bf099c6e52a 100644 --- a/platform-sdk/swirlds-unit-tests/core/swirlds-platform-test/build.gradle.kts +++ b/platform-sdk/swirlds-unit-tests/core/swirlds-platform-test/build.gradle.kts @@ -15,8 +15,8 @@ */ plugins { - id("com.hedera.hashgraph.sdk.conventions") - id("com.hedera.hashgraph.benchmark-conventions") + id("com.hedera.gradle.platform") + id("com.hedera.gradle.benchmark") } testModuleInfo { diff --git a/platform-sdk/swirlds-unit-tests/core/swirlds-platform-test/src/main/java/com/swirlds/platform/test/PlatformStateUtils.java b/platform-sdk/swirlds-unit-tests/core/swirlds-platform-test/src/main/java/com/swirlds/platform/test/PlatformStateUtils.java index 1f35b88d07a7..6c4a06e6e57d 100644 --- a/platform-sdk/swirlds-unit-tests/core/swirlds-platform-test/src/main/java/com/swirlds/platform/test/PlatformStateUtils.java +++ b/platform-sdk/swirlds-unit-tests/core/swirlds-platform-test/src/main/java/com/swirlds/platform/test/PlatformStateUtils.java @@ -23,8 +23,8 @@ import com.swirlds.platform.state.MinimumJudgeInfo; import com.swirlds.platform.state.PlatformState; import com.swirlds.platform.system.address.AddressBook; -import com.swirlds.platform.test.fixtures.addressbook.RandomAddressBookGenerator; -import com.swirlds.platform.test.fixtures.addressbook.RandomAddressBookGenerator.WeightDistributionStrategy; +import com.swirlds.platform.test.fixtures.addressbook.RandomAddressBookBuilder; +import com.swirlds.platform.test.fixtures.addressbook.RandomAddressBookBuilder.WeightDistributionStrategy; import java.util.LinkedList; import java.util.List; import java.util.Random; @@ -46,14 +46,13 @@ public static PlatformState randomPlatformState() { public static PlatformState randomPlatformState(final Random random) { final PlatformState platformState = new PlatformState(); - final AddressBook addressBook = new RandomAddressBookGenerator() - .setSize(4) - .setWeightDistributionStrategy(WeightDistributionStrategy.BALANCED) + final AddressBook addressBook = RandomAddressBookBuilder.create(random) + .withSize(4) + .withWeightDistributionStrategy(WeightDistributionStrategy.BALANCED) .build(); platformState.setAddressBook(addressBook); platformState.setLegacyRunningEventHash(randomHash(random)); - platformState.setRunningEventHash(randomHash(random)); platformState.setRound(random.nextLong()); platformState.setConsensusTimestamp(randomInstant(random)); diff --git a/platform-sdk/swirlds-unit-tests/core/swirlds-platform-test/src/main/java/com/swirlds/platform/test/consensus/ConsensusUtils.java b/platform-sdk/swirlds-unit-tests/core/swirlds-platform-test/src/main/java/com/swirlds/platform/test/consensus/ConsensusUtils.java index 45f8bf94b155..b44af341c883 100644 --- a/platform-sdk/swirlds-unit-tests/core/swirlds-platform-test/src/main/java/com/swirlds/platform/test/consensus/ConsensusUtils.java +++ b/platform-sdk/swirlds-unit-tests/core/swirlds-platform-test/src/main/java/com/swirlds/platform/test/consensus/ConsensusUtils.java @@ -44,7 +44,7 @@ public static void loadEventsIntoGenerator( == address.getNodeId().id()) .toList(); eventsByCreator.forEach(e -> { - final IndexedEvent indexedEvent = new IndexedEvent(e.getHashedData(), e.getUnhashedData(), null, null); + final IndexedEvent indexedEvent = new IndexedEvent(e); source.setLatestEvent(random, indexedEvent); }); final Instant creatorMax = eventsByCreator.stream() diff --git a/platform-sdk/swirlds-unit-tests/core/swirlds-platform-test/src/test/java/com/swirlds/platform/test/cli/EventStreamReportingToolTest.java b/platform-sdk/swirlds-unit-tests/core/swirlds-platform-test/src/test/java/com/swirlds/platform/test/cli/EventStreamReportingToolTest.java new file mode 100644 index 000000000000..5108ece4f780 --- /dev/null +++ b/platform-sdk/swirlds-unit-tests/core/swirlds-platform-test/src/test/java/com/swirlds/platform/test/cli/EventStreamReportingToolTest.java @@ -0,0 +1,162 @@ +/* + * Copyright (C) 2022-2024 Hedera Hashgraph, LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.swirlds.platform.test.cli; + +import static com.swirlds.platform.test.consensus.ConsensusTestArgs.DEFAULT_PLATFORM_CONTEXT; + +import com.swirlds.common.constructable.ConstructableRegistry; +import com.swirlds.common.constructable.ConstructableRegistryException; +import com.swirlds.common.test.fixtures.RandomUtils; +import com.swirlds.platform.event.report.EventStreamReport; +import com.swirlds.platform.event.report.EventStreamScanner; +import com.swirlds.platform.internal.ConsensusRound; +import com.swirlds.platform.internal.EventImpl; +import com.swirlds.platform.recovery.internal.EventStreamRoundLowerBound; +import com.swirlds.platform.recovery.internal.EventStreamTimestampLowerBound; +import com.swirlds.platform.system.BasicSoftwareVersion; +import com.swirlds.platform.system.StaticSoftwareVersion; +import com.swirlds.platform.test.consensus.GenerateConsensus; +import com.swirlds.platform.test.fixtures.stream.StreamUtils; +import com.swirlds.platform.test.simulated.RandomSigner; +import java.io.IOException; +import java.nio.file.Path; +import java.time.Duration; +import java.time.Instant; +import java.util.Deque; +import java.util.List; +import java.util.Optional; +import java.util.Random; +import java.util.concurrent.atomic.AtomicReference; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; + +class EventStreamReportingToolTest { + + @TempDir + Path tmpDir; + + @BeforeAll + static void beforeAll() { + StaticSoftwareVersion.setSoftwareVersion(new BasicSoftwareVersion(1)); + } + + @AfterAll + static void afterAll() { + StaticSoftwareVersion.reset(); + } + + /** + * Generates events, feeds them to consensus, then writes these consensus events to stream files. One the files a + * written, it generates a report and checks the values. + */ + @Test + void createReportTest() throws IOException, ConstructableRegistryException { + final Random random = RandomUtils.getRandomPrintSeed(); + final int numNodes = 10; + final int numEvents = 100_000; + final Duration eventStreamWindowSize = Duration.ofSeconds(1); + + // setup + ConstructableRegistry.getInstance().registerConstructables("com.swirlds"); + + // generate consensus events + final Deque rounds = GenerateConsensus.generateConsensusRounds( + DEFAULT_PLATFORM_CONTEXT, numNodes, numEvents, random.nextLong()); + if (rounds.isEmpty()) { + Assertions.fail("events are excepted to reach consensus"); + } + // get consensus info + final long roundToReportFrom = rounds.size() / 2; + final int numConsensusEvents = rounds.stream() + .filter(r -> r.getRoundNum() >= roundToReportFrom) + .mapToInt(ConsensusRound::getNumEvents) + .sum(); + final List lastRound = + Optional.ofNullable(rounds.peekLast()).orElseThrow().getConsensusEvents(); + final Instant lastEventTime = lastRound.get(lastRound.size() - 1).getConsensusTimestamp(); + + // write event stream + StreamUtils.writeRoundsToStream(tmpDir, new RandomSigner(random), eventStreamWindowSize, rounds); + + // get report + final EventStreamReport report = new EventStreamScanner( + tmpDir, new EventStreamRoundLowerBound(roundToReportFrom), Duration.ofSeconds(1), false) + .createReport(); + + // assert report has same info as expected + Assertions.assertEquals(numConsensusEvents, report.summary().eventCount()); + Assertions.assertEquals(lastEventTime, report.summary().end()); + Assertions.assertEquals( + lastEventTime, report.summary().lastEvent().getConsensusData().getConsensusTimestamp()); + } + + /** + * Generates events, feeds them to consensus, then writes these consensus events to stream files. One the files a + * written, it generates a report and checks the values. + */ + @Test + void createTimeBoundReportTest() throws IOException, ConstructableRegistryException { + final Random random = RandomUtils.getRandomPrintSeed(); + final int numNodes = 10; + final int numEvents = 100_000; + final Duration eventStreamWindowSize = Duration.ofSeconds(1); + + // setup + ConstructableRegistry.getInstance().registerConstructables("com.swirlds"); + + // generate consensus events + final Deque rounds = GenerateConsensus.generateConsensusRounds( + DEFAULT_PLATFORM_CONTEXT, numNodes, numEvents, random.nextLong()); + if (rounds.isEmpty()) { + Assertions.fail("events are excepted to reach consensus"); + } + // get consensus info + final long roundToReportFrom = rounds.size() / 2; + final AtomicReference timestampRef = new AtomicReference<>(Instant.MIN); + final int numConsensusEvents = rounds.stream() + .filter(r -> { + if (r.getRoundNum() >= roundToReportFrom) { + timestampRef.compareAndSet( + Instant.MIN, r.getConsensusEvents().get(0).getConsensusTimestamp()); + return true; + } + return false; + }) + .mapToInt(ConsensusRound::getNumEvents) + .sum(); + final List lastRound = + Optional.ofNullable(rounds.peekLast()).orElseThrow().getConsensusEvents(); + final Instant lastEventTime = lastRound.get(lastRound.size() - 1).getConsensusTimestamp(); + + // write event stream + StreamUtils.writeRoundsToStream(tmpDir, new RandomSigner(random), eventStreamWindowSize, rounds); + + // get report + final EventStreamReport report = new EventStreamScanner( + tmpDir, new EventStreamTimestampLowerBound(timestampRef.get()), Duration.ofSeconds(1), false) + .createReport(); + + // assert report has same info as expected + Assertions.assertEquals(numConsensusEvents, report.summary().eventCount()); + Assertions.assertEquals(lastEventTime, report.summary().end()); + Assertions.assertEquals( + lastEventTime, report.summary().lastEvent().getConsensusData().getConsensusTimestamp()); + } +} diff --git a/platform-sdk/swirlds-unit-tests/core/swirlds-platform-test/src/test/java/com/swirlds/platform/test/event/preconsensus/PcesFileManagerTests.java b/platform-sdk/swirlds-unit-tests/core/swirlds-platform-test/src/test/java/com/swirlds/platform/test/event/preconsensus/PcesFileManagerTests.java index 8283cb841083..396cb8636c2f 100644 --- a/platform-sdk/swirlds-unit-tests/core/swirlds-platform-test/src/test/java/com/swirlds/platform/test/event/preconsensus/PcesFileManagerTests.java +++ b/platform-sdk/swirlds-unit-tests/core/swirlds-platform-test/src/test/java/com/swirlds/platform/test/event/preconsensus/PcesFileManagerTests.java @@ -27,13 +27,11 @@ import com.swirlds.base.test.fixtures.time.FakeTime; import com.swirlds.base.time.Time; -import com.swirlds.common.context.DefaultPlatformContext; import com.swirlds.common.context.PlatformContext; -import com.swirlds.common.crypto.CryptographyHolder; import com.swirlds.common.io.config.FileSystemManagerConfig_; import com.swirlds.common.io.utility.FileUtils; -import com.swirlds.common.metrics.noop.NoOpMetrics; import com.swirlds.common.platform.NodeId; +import com.swirlds.common.test.fixtures.platform.TestPlatformContextBuilder; import com.swirlds.common.utility.CompareTo; import com.swirlds.config.api.Configuration; import com.swirlds.config.extensions.test.fixtures.TestConfigBuilder; @@ -101,7 +99,10 @@ private PlatformContext buildContext(@NonNull final AncientMode ancientMode, @No .withValue(PcesConfig_.COMPACT_LAST_FILE_ON_STARTUP, false) .getOrCreateConfig(); - return new DefaultPlatformContext(configuration, new NoOpMetrics(), CryptographyHolder.get(), time); + return TestPlatformContextBuilder.create() + .withConfiguration(configuration) + .withTime(time) + .build(); } @ParameterizedTest diff --git a/platform-sdk/swirlds-unit-tests/core/swirlds-platform-test/src/test/java/com/swirlds/platform/test/event/preconsensus/PcesFileReaderTests.java b/platform-sdk/swirlds-unit-tests/core/swirlds-platform-test/src/test/java/com/swirlds/platform/test/event/preconsensus/PcesFileReaderTests.java index 3dcf98416ef3..93cfe05e07d1 100644 --- a/platform-sdk/swirlds-unit-tests/core/swirlds-platform-test/src/test/java/com/swirlds/platform/test/event/preconsensus/PcesFileReaderTests.java +++ b/platform-sdk/swirlds-unit-tests/core/swirlds-platform-test/src/test/java/com/swirlds/platform/test/event/preconsensus/PcesFileReaderTests.java @@ -28,9 +28,7 @@ import static org.junit.jupiter.api.Assertions.assertTrue; import com.swirlds.base.time.Time; -import com.swirlds.common.context.DefaultPlatformContext; import com.swirlds.common.context.PlatformContext; -import com.swirlds.common.crypto.CryptographyHolder; import com.swirlds.common.io.streams.SerializableDataOutputStream; import com.swirlds.common.io.utility.FileUtils; import com.swirlds.common.io.utility.RecycleBin; @@ -38,6 +36,7 @@ import com.swirlds.common.metrics.noop.NoOpMetrics; import com.swirlds.common.test.fixtures.TestFileSystemManager; import com.swirlds.common.test.fixtures.TestRecycleBin; +import com.swirlds.common.test.fixtures.platform.TestPlatformContextBuilder; import com.swirlds.config.api.Configuration; import com.swirlds.config.extensions.test.fixtures.TestConfigBuilder; import com.swirlds.metrics.api.Metrics; @@ -119,8 +118,11 @@ private PlatformContext buildContext(final boolean permitGaps, @NonNull final An final TestFileSystemManager fileSystemManager = new TestFileSystemManager(testDirectory); fileSystemManager.setBin(TestRecycleBin.getInstance()); - return new DefaultPlatformContext( - configuration, metrics, CryptographyHolder.get(), Time.getCurrent(), fileSystemManager); + return TestPlatformContextBuilder.create() + .withConfiguration(configuration) + .withMetrics(metrics) + .withFileSystemManager((m, t) -> fileSystemManager) + .build(); } private PlatformContext buildContext( @@ -139,8 +141,12 @@ private PlatformContext buildContext( final TestFileSystemManager fileSystemManager = new TestFileSystemManager(testDirectory); fileSystemManager.setBin(recycleBinProvider.apply(metrics, Time.getCurrent())); - return new DefaultPlatformContext( - configuration, metrics, CryptographyHolder.get(), Time.getCurrent(), fileSystemManager); + + return TestPlatformContextBuilder.create() + .withConfiguration(configuration) + .withMetrics(metrics) + .withFileSystemManager((m, t) -> fileSystemManager) + .build(); } protected static Stream buildArguments() { diff --git a/platform-sdk/swirlds-unit-tests/core/swirlds-platform-test/src/test/java/com/swirlds/platform/test/event/preconsensus/PcesWriterTests.java b/platform-sdk/swirlds-unit-tests/core/swirlds-platform-test/src/test/java/com/swirlds/platform/test/event/preconsensus/PcesWriterTests.java index 0c652f987ec5..721bbfb7aa72 100644 --- a/platform-sdk/swirlds-unit-tests/core/swirlds-platform-test/src/test/java/com/swirlds/platform/test/event/preconsensus/PcesWriterTests.java +++ b/platform-sdk/swirlds-unit-tests/core/swirlds-platform-test/src/test/java/com/swirlds/platform/test/event/preconsensus/PcesWriterTests.java @@ -32,17 +32,15 @@ import com.swirlds.base.test.fixtures.time.FakeTime; import com.swirlds.common.constructable.ConstructableRegistry; import com.swirlds.common.constructable.ConstructableRegistryException; -import com.swirlds.common.context.DefaultPlatformContext; import com.swirlds.common.context.PlatformContext; -import com.swirlds.common.crypto.CryptographyHolder; import com.swirlds.common.io.IOIterator; import com.swirlds.common.io.config.FileSystemManagerConfig_; import com.swirlds.common.io.utility.FileUtils; -import com.swirlds.common.metrics.noop.NoOpMetrics; import com.swirlds.common.platform.NodeId; import com.swirlds.common.test.fixtures.RandomUtils; import com.swirlds.common.test.fixtures.TransactionGenerator; import com.swirlds.common.test.fixtures.io.FileManipulation; +import com.swirlds.common.test.fixtures.platform.TestPlatformContextBuilder; import com.swirlds.config.api.Configuration; import com.swirlds.config.extensions.test.fixtures.TestConfigBuilder; import com.swirlds.platform.config.TransactionConfig_; @@ -272,8 +270,10 @@ private PlatformContext buildContext(@NonNull final AncientMode ancientMode) { .withValue(EventConfig_.USE_BIRTH_ROUND_ANCIENT_THRESHOLD, ancientMode == BIRTH_ROUND_THRESHOLD) .getOrCreateConfig(); - return new DefaultPlatformContext( - configuration, new NoOpMetrics(), CryptographyHolder.get(), new FakeTime(Duration.ofMillis(1))); + return TestPlatformContextBuilder.create() + .withConfiguration(configuration) + .withTime(new FakeTime(Duration.ofMillis(1))) + .build(); } /** diff --git a/platform-sdk/swirlds-unit-tests/core/swirlds-platform-test/src/test/java/com/swirlds/platform/test/event/tipset/EventCreationManagerTests.java b/platform-sdk/swirlds-unit-tests/core/swirlds-platform-test/src/test/java/com/swirlds/platform/test/event/tipset/EventCreationManagerTests.java index 685a1e38e697..678833f165db 100644 --- a/platform-sdk/swirlds-unit-tests/core/swirlds-platform-test/src/test/java/com/swirlds/platform/test/event/tipset/EventCreationManagerTests.java +++ b/platform-sdk/swirlds-unit-tests/core/swirlds-platform-test/src/test/java/com/swirlds/platform/test/event/tipset/EventCreationManagerTests.java @@ -31,7 +31,7 @@ import com.swirlds.platform.event.creation.DefaultEventCreationManager; import com.swirlds.platform.event.creation.EventCreationManager; import com.swirlds.platform.event.creation.EventCreator; -import com.swirlds.platform.eventhandling.TransactionPool; +import com.swirlds.platform.pool.TransactionPoolNexus; import com.swirlds.platform.system.events.BaseEventHashedData; import com.swirlds.platform.system.status.PlatformStatus; import java.time.Duration; @@ -69,7 +69,7 @@ void setUp() { intakeQueueSize = new AtomicLong(0); manager = new DefaultEventCreationManager( - platformContext, mock(TransactionPool.class), intakeQueueSize::get, creator); + platformContext, mock(TransactionPoolNexus.class), intakeQueueSize::get, creator); manager.updatePlatformStatus(PlatformStatus.ACTIVE); } diff --git a/platform-sdk/swirlds-unit-tests/core/swirlds-platform-test/src/test/java/com/swirlds/platform/test/event/tipset/EventCreationRulesTests.java b/platform-sdk/swirlds-unit-tests/core/swirlds-platform-test/src/test/java/com/swirlds/platform/test/event/tipset/EventCreationRulesTests.java index d7fc9bd80f7a..26ed2f365532 100644 --- a/platform-sdk/swirlds-unit-tests/core/swirlds-platform-test/src/test/java/com/swirlds/platform/test/event/tipset/EventCreationRulesTests.java +++ b/platform-sdk/swirlds-unit-tests/core/swirlds-platform-test/src/test/java/com/swirlds/platform/test/event/tipset/EventCreationRulesTests.java @@ -40,7 +40,7 @@ import com.swirlds.platform.event.creation.rules.EventCreationRule; import com.swirlds.platform.event.creation.rules.MaximumRateRule; import com.swirlds.platform.event.creation.rules.PlatformStatusRule; -import com.swirlds.platform.eventhandling.TransactionPool; +import com.swirlds.platform.pool.TransactionPoolNexus; import com.swirlds.platform.system.status.PlatformStatus; import java.time.Duration; import java.util.Random; @@ -135,8 +135,8 @@ void blockedByFreeze() { final Supplier platformStatusSupplier = () -> FREEZING; final AtomicInteger numSignatureTransactions = new AtomicInteger(0); - final TransactionPool transactionPool = mock(TransactionPool.class); - when(transactionPool.hasBufferedSignatureTransactions()) + final TransactionPoolNexus transactionPoolNexus = mock(TransactionPoolNexus.class); + when(transactionPoolNexus.hasBufferedSignatureTransactions()) .thenAnswer(invocation -> numSignatureTransactions.get() > 0); final AtomicInteger eventCreationCount = new AtomicInteger(0); @@ -146,7 +146,7 @@ void blockedByFreeze() { return null; }); - final EventCreationRule rule = new PlatformStatusRule(platformStatusSupplier, transactionPool); + final EventCreationRule rule = new PlatformStatusRule(platformStatusSupplier, transactionPoolNexus); assertFalse(rule.isEventCreationPermitted()); numSignatureTransactions.set(1); @@ -156,7 +156,7 @@ void blockedByFreeze() { @Test @DisplayName("Blocked by Status Test") void blockedByStatus() { - final TransactionPool transactionPool = mock(TransactionPool.class); + final TransactionPoolNexus transactionPoolNexus = mock(TransactionPoolNexus.class); final AtomicReference status = new AtomicReference<>(); @@ -167,7 +167,7 @@ void blockedByStatus() { return null; }); - final EventCreationRule rule = new PlatformStatusRule(status::get, transactionPool); + final EventCreationRule rule = new PlatformStatusRule(status::get, transactionPoolNexus); for (final PlatformStatus platformStatus : PlatformStatus.values()) { if (platformStatus == FREEZING) { diff --git a/platform-sdk/swirlds-unit-tests/core/swirlds-platform-test/src/test/java/com/swirlds/platform/test/event/tipset/TipsetEventCreatorTests.java b/platform-sdk/swirlds-unit-tests/core/swirlds-platform-test/src/test/java/com/swirlds/platform/test/event/tipset/TipsetEventCreatorTests.java index ed094f9a3497..26643e0551f9 100644 --- a/platform-sdk/swirlds-unit-tests/core/swirlds-platform-test/src/test/java/com/swirlds/platform/test/event/tipset/TipsetEventCreatorTests.java +++ b/platform-sdk/swirlds-unit-tests/core/swirlds-platform-test/src/test/java/com/swirlds/platform/test/event/tipset/TipsetEventCreatorTests.java @@ -53,13 +53,12 @@ import com.swirlds.platform.system.address.Address; import com.swirlds.platform.system.address.AddressBook; import com.swirlds.platform.system.events.BaseEventHashedData; -import com.swirlds.platform.system.events.BaseEventUnhashedData; import com.swirlds.platform.system.events.ConsensusData; import com.swirlds.platform.system.events.EventConstants; import com.swirlds.platform.system.events.EventDescriptor; import com.swirlds.platform.system.transaction.ConsensusTransactionImpl; import com.swirlds.platform.system.transaction.SwirldTransaction; -import com.swirlds.platform.test.fixtures.addressbook.RandomAddressBookGenerator; +import com.swirlds.platform.test.fixtures.addressbook.RandomAddressBookBuilder; import com.swirlds.platform.test.fixtures.event.TestingEventBuilder; import edu.umd.cs.findbugs.annotations.NonNull; import edu.umd.cs.findbugs.annotations.Nullable; @@ -246,8 +245,8 @@ private EventImpl linkEvent( final EventImpl selfParent = events.get(event.getSelfParentHash()); final EventImpl otherParent = events.get(event.getOtherParentHash()); - final EventImpl eventImpl = new EventImpl( - new GossipEvent(event, new BaseEventUnhashedData()), new ConsensusData(), selfParent, otherParent); + final EventImpl eventImpl = + new EventImpl(new GossipEvent(event, new byte[0]), new ConsensusData(), selfParent, otherParent); events.put(event.getHash(), eventImpl); return eventImpl; @@ -297,7 +296,7 @@ void roundRobinTest(final boolean advancingClock, final boolean useBirthRoundFor final int networkSize = 10; final AddressBook addressBook = - new RandomAddressBookGenerator(random).setSize(networkSize).build(); + RandomAddressBookBuilder.create(random).withSize(networkSize).build(); final FakeTime time = new FakeTime(); @@ -351,7 +350,7 @@ void randomOrderTest(final boolean advancingClock, final boolean useBirthRoundFo final int networkSize = 10; final AddressBook addressBook = - new RandomAddressBookGenerator(random).setSize(networkSize).build(); + RandomAddressBookBuilder.create(random).withSize(networkSize).build(); final FakeTime time = new FakeTime(); @@ -419,7 +418,7 @@ void clearTest(final boolean advancingClock) { final int networkSize = 10; final AddressBook addressBook = - new RandomAddressBookGenerator(random).setSize(networkSize).build(); + RandomAddressBookBuilder.create(random).withSize(networkSize).build(); final FakeTime time = new FakeTime(); @@ -494,7 +493,7 @@ void createManyEventsInARowTest(final boolean advancingClock, final boolean useB final int networkSize = 10; final AddressBook addressBook = - new RandomAddressBookGenerator(random).setSize(networkSize).build(); + RandomAddressBookBuilder.create(random).withSize(networkSize).build(); final FakeTime time = new FakeTime(); @@ -562,7 +561,7 @@ void zeroWeightNodeTest(final boolean advancingClock, final boolean useBirthRoun final int networkSize = 10; final AddressBook addressBook = - new RandomAddressBookGenerator(random).setSize(networkSize).build(); + RandomAddressBookBuilder.create(random).withSize(networkSize).build(); final NodeId zeroWeightNode = addressBook.getNodeId(0); @@ -659,7 +658,7 @@ void zeroWeightSlowNodeTest(final boolean advancingClock, final boolean useBirth final int networkSize = 10; final AddressBook addressBook = - new RandomAddressBookGenerator(random).setSize(networkSize).build(); + RandomAddressBookBuilder.create(random).withSize(networkSize).build(); final NodeId zeroWeightNode = addressBook.getNodeId(0); @@ -767,7 +766,7 @@ void sizeOneNetworkTest(final boolean advancingClock, final boolean useBirthRoun final int networkSize = 1; final AddressBook addressBook = - new RandomAddressBookGenerator(random).setSize(networkSize).build(); + RandomAddressBookBuilder.create(random).withSize(networkSize).build(); final FakeTime time = new FakeTime(); @@ -845,9 +844,10 @@ void frozenEventCreationBug() { final int networkSize = 4; - final AddressBook addressBook = new RandomAddressBookGenerator(random) - .setCustomWeightGenerator(x -> 1L) - .setSize(networkSize) + final AddressBook addressBook = RandomAddressBookBuilder.create(random) + .withMinimumWeight(1) + .withMaximumWeight(1) + .withSize(networkSize) .build(); final FakeTime time = new FakeTime(); @@ -922,9 +922,10 @@ void notRegisteringEventsFromNodesNotInAddressBook() { final int networkSize = 4; - final AddressBook addressBook = new RandomAddressBookGenerator(random) - .setCustomWeightGenerator(x -> 1L) - .setSize(networkSize) + final AddressBook addressBook = RandomAddressBookBuilder.create(random) + .withMinimumWeight(1) + .withMaximumWeight(1) + .withSize(networkSize) .build(); final FakeTime time = new FakeTime(); @@ -991,9 +992,10 @@ void noStaleEventsAtCreationTimeTest(final boolean useBirthRoundForAncient) { final int networkSize = 4; - final AddressBook addressBook = new RandomAddressBookGenerator(random) - .setCustomWeightGenerator(x -> 1L) - .setSize(networkSize) + final AddressBook addressBook = RandomAddressBookBuilder.create(random) + .withMinimumWeight(1) + .withMaximumWeight(1) + .withSize(networkSize) .build(); final FakeTime time = new FakeTime(); @@ -1029,7 +1031,7 @@ void checkSettingEventBirthRound(final boolean advancingClock, final boolean use final int networkSize = 10; final AddressBook addressBook = - new RandomAddressBookGenerator(random).setSize(networkSize).build(); + RandomAddressBookBuilder.create(random).withSize(networkSize).build(); final FakeTime time = new FakeTime(); diff --git a/platform-sdk/swirlds-unit-tests/core/swirlds-platform-test/src/test/java/com/swirlds/platform/test/event/tipset/TipsetTests.java b/platform-sdk/swirlds-unit-tests/core/swirlds-platform-test/src/test/java/com/swirlds/platform/test/event/tipset/TipsetTests.java index 4e499cd84e53..e012f2fbc7a2 100644 --- a/platform-sdk/swirlds-unit-tests/core/swirlds-platform-test/src/test/java/com/swirlds/platform/test/event/tipset/TipsetTests.java +++ b/platform-sdk/swirlds-unit-tests/core/swirlds-platform-test/src/test/java/com/swirlds/platform/test/event/tipset/TipsetTests.java @@ -24,8 +24,8 @@ import com.swirlds.platform.event.creation.tipset.TipsetAdvancementWeight; import com.swirlds.platform.system.address.Address; import com.swirlds.platform.system.address.AddressBook; -import com.swirlds.platform.test.fixtures.addressbook.RandomAddressBookGenerator; -import com.swirlds.platform.test.fixtures.addressbook.RandomAddressBookGenerator.WeightDistributionStrategy; +import com.swirlds.platform.test.fixtures.addressbook.RandomAddressBookBuilder; +import com.swirlds.platform.test.fixtures.addressbook.RandomAddressBookBuilder.WeightDistributionStrategy; import java.util.ArrayList; import java.util.HashMap; import java.util.List; @@ -51,7 +51,7 @@ void advancementTest() { final int nodeCount = 100; final AddressBook addressBook = - new RandomAddressBookGenerator(random).setSize(nodeCount).build(); + RandomAddressBookBuilder.create(random).withSize(nodeCount).build(); final Tipset tipset = new Tipset(addressBook); assertEquals(nodeCount, tipset.size()); @@ -78,7 +78,7 @@ void mergeTest() { final int nodeCount = 100; final AddressBook addressBook = - new RandomAddressBookGenerator(random).setSize(nodeCount).build(); + RandomAddressBookBuilder.create(random).withSize(nodeCount).build(); for (int count = 0; count < 10; count++) { final List tipsets = new ArrayList<>(); @@ -107,10 +107,10 @@ void getAdvancementCountTest() { final int nodeCount = 100; - final AddressBook addressBook = new RandomAddressBookGenerator(random) - .setSize(nodeCount) - .setAverageWeight(1) - .setWeightDistributionStrategy(WeightDistributionStrategy.BALANCED) + final AddressBook addressBook = RandomAddressBookBuilder.create(random) + .withSize(nodeCount) + .withAverageWeight(1) + .withWeightDistributionStrategy(WeightDistributionStrategy.BALANCED) .build(); final NodeId selfId = addressBook.getNodeId(random.nextInt(nodeCount)); @@ -164,7 +164,7 @@ void weightedGetAdvancementCountTest() { final int nodeCount = 100; final AddressBook addressBook = - new RandomAddressBookGenerator(random).setSize(nodeCount).build(); + RandomAddressBookBuilder.create(random).withSize(nodeCount).build(); final Map weights = new HashMap<>(); for (final Address address : addressBook) { diff --git a/platform-sdk/swirlds-unit-tests/core/swirlds-platform-test/src/test/java/com/swirlds/platform/test/event/tipset/TipsetTrackerTests.java b/platform-sdk/swirlds-unit-tests/core/swirlds-platform-test/src/test/java/com/swirlds/platform/test/event/tipset/TipsetTrackerTests.java index 3a2da226322d..f74fab50afec 100644 --- a/platform-sdk/swirlds-unit-tests/core/swirlds-platform-test/src/test/java/com/swirlds/platform/test/event/tipset/TipsetTrackerTests.java +++ b/platform-sdk/swirlds-unit-tests/core/swirlds-platform-test/src/test/java/com/swirlds/platform/test/event/tipset/TipsetTrackerTests.java @@ -34,7 +34,7 @@ import com.swirlds.platform.system.address.AddressBook; import com.swirlds.platform.system.events.EventConstants; import com.swirlds.platform.system.events.EventDescriptor; -import com.swirlds.platform.test.fixtures.addressbook.RandomAddressBookGenerator; +import com.swirlds.platform.test.fixtures.addressbook.RandomAddressBookBuilder; import edu.umd.cs.findbugs.annotations.NonNull; import java.util.ArrayList; import java.util.HashMap; @@ -69,7 +69,7 @@ void basicBehaviorTest(final AncientMode ancientMode) { final int nodeCount = random.nextInt(10, 20); final AddressBook addressBook = - new RandomAddressBookGenerator(random).setSize(nodeCount).build(); + RandomAddressBookBuilder.create(random).withSize(nodeCount).build(); final Map latestEvents = new HashMap<>(); final Map expectedTipsets = new HashMap<>(); diff --git a/platform-sdk/swirlds-unit-tests/core/swirlds-platform-test/src/test/java/com/swirlds/platform/test/event/tipset/TipsetWeightCalculatorTests.java b/platform-sdk/swirlds-unit-tests/core/swirlds-platform-test/src/test/java/com/swirlds/platform/test/event/tipset/TipsetWeightCalculatorTests.java index 5c3ff8b87b8d..b3ae83aabe1f 100644 --- a/platform-sdk/swirlds-unit-tests/core/swirlds-platform-test/src/test/java/com/swirlds/platform/test/event/tipset/TipsetWeightCalculatorTests.java +++ b/platform-sdk/swirlds-unit-tests/core/swirlds-platform-test/src/test/java/com/swirlds/platform/test/event/tipset/TipsetWeightCalculatorTests.java @@ -44,8 +44,8 @@ import com.swirlds.platform.system.address.AddressBook; import com.swirlds.platform.system.events.EventConstants; import com.swirlds.platform.system.events.EventDescriptor; -import com.swirlds.platform.test.fixtures.addressbook.RandomAddressBookGenerator; -import com.swirlds.platform.test.fixtures.addressbook.RandomAddressBookGenerator.WeightDistributionStrategy; +import com.swirlds.platform.test.fixtures.addressbook.RandomAddressBookBuilder; +import com.swirlds.platform.test.fixtures.addressbook.RandomAddressBookBuilder.WeightDistributionStrategy; import edu.umd.cs.findbugs.annotations.NonNull; import java.util.ArrayList; import java.util.HashMap; @@ -83,7 +83,7 @@ void basicBehaviorTest() { final Map latestEvents = new HashMap<>(); final AddressBook addressBook = - new RandomAddressBookGenerator(random).setSize(nodeCount).build(); + RandomAddressBookBuilder.create(random).withSize(nodeCount).build(); final Map weightMap = new HashMap<>(); long totalWeight = 0; @@ -215,10 +215,10 @@ void selfishNodeTest() { final Random random = getRandomPrintSeed(); final int nodeCount = 4; - final AddressBook addressBook = new RandomAddressBookGenerator(random) - .setSize(nodeCount) - .setAverageWeight(1) - .setWeightDistributionStrategy(WeightDistributionStrategy.BALANCED) + final AddressBook addressBook = RandomAddressBookBuilder.create(random) + .withSize(nodeCount) + .withAverageWeight(1) + .withWeightDistributionStrategy(WeightDistributionStrategy.BALANCED) .build(); // In this test, we simulate from the perspective of node A. All nodes have 1 weight. @@ -428,10 +428,10 @@ void zeroWeightNodeTest() { final Random random = getRandomPrintSeed(); final int nodeCount = 4; - final AddressBook addressBook = new RandomAddressBookGenerator(random) - .setSize(nodeCount) - .setAverageWeight(1) - .setWeightDistributionStrategy(WeightDistributionStrategy.BALANCED) + final AddressBook addressBook = RandomAddressBookBuilder.create(random) + .withSize(nodeCount) + .withAverageWeight(1) + .withWeightDistributionStrategy(WeightDistributionStrategy.BALANCED) .build(); // In this test, we simulate from the perspective of node A. @@ -506,10 +506,10 @@ void ancientParentTest() { final Random random = getRandomPrintSeed(); final int nodeCount = 4; - final AddressBook addressBook = new RandomAddressBookGenerator(random) - .setSize(nodeCount) - .setAverageWeight(1) - .setWeightDistributionStrategy(WeightDistributionStrategy.BALANCED) + final AddressBook addressBook = RandomAddressBookBuilder.create(random) + .withSize(nodeCount) + .withAverageWeight(1) + .withWeightDistributionStrategy(WeightDistributionStrategy.BALANCED) .build(); final NodeId nodeA = addressBook.getNodeId(0); diff --git a/platform-sdk/swirlds-unit-tests/core/swirlds-platform-test/src/test/java/com/swirlds/platform/test/gui/HashgraphGuiTest.java b/platform-sdk/swirlds-unit-tests/core/swirlds-platform-test/src/test/java/com/swirlds/platform/test/gui/HashgraphGuiTest.java index a7c5b02759d9..eb3e8d905a5d 100644 --- a/platform-sdk/swirlds-unit-tests/core/swirlds-platform-test/src/test/java/com/swirlds/platform/test/gui/HashgraphGuiTest.java +++ b/platform-sdk/swirlds-unit-tests/core/swirlds-platform-test/src/test/java/com/swirlds/platform/test/gui/HashgraphGuiTest.java @@ -17,9 +17,10 @@ package com.swirlds.platform.test.gui; import com.swirlds.common.context.PlatformContext; +import com.swirlds.common.test.fixtures.Randotron; import com.swirlds.common.test.fixtures.platform.TestPlatformContextBuilder; import com.swirlds.platform.system.address.AddressBook; -import com.swirlds.platform.test.fixtures.addressbook.RandomAddressBookGenerator; +import com.swirlds.platform.test.fixtures.addressbook.RandomAddressBookBuilder; import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; @@ -27,7 +28,7 @@ class HashgraphGuiTest { @Test @Disabled("this test is useful for debugging consensus") void runGuiWithControls() { - final long seed = 1; + final Randotron randotron = Randotron.create(1); final int numNodes = 4; final int initialEvents = 0; @@ -35,9 +36,9 @@ void runGuiWithControls() { TestPlatformContextBuilder.create().build(); final AddressBook addressBook = - new RandomAddressBookGenerator(seed).setSize(numNodes).build(); + RandomAddressBookBuilder.create(randotron).withSize(numNodes).build(); - final TestGuiSource guiSource = new TestGuiSource(platformContext, seed, addressBook); + final TestGuiSource guiSource = new TestGuiSource(platformContext, randotron.nextInt(), addressBook); guiSource.generateEvents(initialEvents); guiSource.runGui(); } diff --git a/platform-sdk/swirlds-unit-tests/core/swirlds-platform-test/src/test/java/com/swirlds/platform/test/network/OutboundConnectionCreatorTest.java b/platform-sdk/swirlds-unit-tests/core/swirlds-platform-test/src/test/java/com/swirlds/platform/test/network/OutboundConnectionCreatorTest.java index cc0d74645547..2122362c73b0 100644 --- a/platform-sdk/swirlds-unit-tests/core/swirlds-platform-test/src/test/java/com/swirlds/platform/test/network/OutboundConnectionCreatorTest.java +++ b/platform-sdk/swirlds-unit-tests/core/swirlds-platform-test/src/test/java/com/swirlds/platform/test/network/OutboundConnectionCreatorTest.java @@ -39,8 +39,8 @@ import com.swirlds.platform.network.connectivity.OutboundConnectionCreator; import com.swirlds.platform.network.connectivity.SocketFactory; import com.swirlds.platform.system.address.AddressBook; -import com.swirlds.platform.test.fixtures.addressbook.RandomAddressBookGenerator; -import com.swirlds.platform.test.fixtures.addressbook.RandomAddressBookGenerator.WeightDistributionStrategy; +import com.swirlds.platform.test.fixtures.addressbook.RandomAddressBookBuilder; +import com.swirlds.platform.test.fixtures.addressbook.RandomAddressBookBuilder.WeightDistributionStrategy; import edu.umd.cs.findbugs.annotations.NonNull; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; @@ -61,10 +61,9 @@ void createConnectionTest() throws IOException { final int numNodes = 10; final Random r = new Random(); - final AddressBook addressBook = new RandomAddressBookGenerator(r) - .setSize(numNodes) - .setWeightDistributionStrategy(WeightDistributionStrategy.BALANCED) - .setHashStrategy(RandomAddressBookGenerator.HashStrategy.FAKE_HASH) + final AddressBook addressBook = RandomAddressBookBuilder.create(r) + .withSize(numNodes) + .withWeightDistributionStrategy(WeightDistributionStrategy.BALANCED) .build(); final int thisNodeIndex = r.nextInt(numNodes); final int otherNodeIndex = r.nextInt(numNodes); @@ -141,10 +140,9 @@ void mismatchedVersionIgnoredTest() throws IOException { final int numNodes = 10; final Random r = new Random(); - final AddressBook addressBook = new RandomAddressBookGenerator(r) - .setSize(numNodes) - .setWeightDistributionStrategy(WeightDistributionStrategy.BALANCED) - .setHashStrategy(RandomAddressBookGenerator.HashStrategy.FAKE_HASH) + final AddressBook addressBook = RandomAddressBookBuilder.create(r) + .withSize(numNodes) + .withWeightDistributionStrategy(WeightDistributionStrategy.BALANCED) .build(); final int thisNodeIndex = r.nextInt(numNodes); final int otherNodeIndex = r.nextInt(numNodes); diff --git a/platform-sdk/swirlds-unit-tests/core/swirlds-platform-test/src/test/java/com/swirlds/platform/test/network/StaticConnectionManagersTest.java b/platform-sdk/swirlds-unit-tests/core/swirlds-platform-test/src/test/java/com/swirlds/platform/test/network/StaticConnectionManagersTest.java index 748e6137e7ae..d650316c881a 100644 --- a/platform-sdk/swirlds-unit-tests/core/swirlds-platform-test/src/test/java/com/swirlds/platform/test/network/StaticConnectionManagersTest.java +++ b/platform-sdk/swirlds-unit-tests/core/swirlds-platform-test/src/test/java/com/swirlds/platform/test/network/StaticConnectionManagersTest.java @@ -30,7 +30,7 @@ import com.swirlds.platform.network.topology.StaticConnectionManagers; import com.swirlds.platform.network.topology.StaticTopology; import com.swirlds.platform.system.address.AddressBook; -import com.swirlds.platform.test.fixtures.addressbook.RandomAddressBookGenerator; +import com.swirlds.platform.test.fixtures.addressbook.RandomAddressBookBuilder; import java.util.List; import java.util.Random; import org.junit.jupiter.api.extension.ExtendWith; @@ -55,7 +55,7 @@ private static List topologicalVariations() { void testShouldConnectToMe(final int numNodes, final int numNeighbors) throws Exception { final Random r = RandomUtils.getRandomPrintSeed(); final AddressBook addressBook = - new RandomAddressBookGenerator(r).setSize(numNodes).build(); + RandomAddressBookBuilder.create(r).withSize(numNodes).build(); final NodeId selfId = addressBook.getNodeId(r.nextInt(numNodes)); final StaticTopology topology = new StaticTopology(r, addressBook, selfId, numNeighbors); final StaticConnectionManagers managers = new StaticConnectionManagers(topology, connectionCreator); @@ -88,7 +88,7 @@ void testShouldConnectToMe(final int numNodes, final int numNeighbors) throws Ex void testShouldConnectTo(final int numNodes, final int numNeighbors) throws Exception { final Random r = RandomUtils.getRandomPrintSeed(); final AddressBook addressBook = - new RandomAddressBookGenerator(r).setSize(numNodes).build(); + RandomAddressBookBuilder.create(r).withSize(numNodes).build(); final NodeId selfId = addressBook.getNodeId(r.nextInt(numNodes)); final StaticTopology topology = new StaticTopology(r, addressBook, selfId, numNeighbors); final StaticConnectionManagers managers = new StaticConnectionManagers(topology, connectionCreator); diff --git a/platform-sdk/swirlds-unit-tests/core/swirlds-platform-test/src/test/java/com/swirlds/platform/test/network/TopologyTest.java b/platform-sdk/swirlds-unit-tests/core/swirlds-platform-test/src/test/java/com/swirlds/platform/test/network/TopologyTest.java index 3730bcf0458c..dc237282d7ec 100644 --- a/platform-sdk/swirlds-unit-tests/core/swirlds-platform-test/src/test/java/com/swirlds/platform/test/network/TopologyTest.java +++ b/platform-sdk/swirlds-unit-tests/core/swirlds-platform-test/src/test/java/com/swirlds/platform/test/network/TopologyTest.java @@ -22,11 +22,12 @@ import static org.junit.jupiter.api.Assertions.assertTrue; import com.swirlds.common.platform.NodeId; +import com.swirlds.common.test.fixtures.Randotron; import com.swirlds.platform.network.RandomGraph; import com.swirlds.platform.network.topology.NetworkTopology; import com.swirlds.platform.network.topology.StaticTopology; import com.swirlds.platform.system.address.AddressBook; -import com.swirlds.platform.test.fixtures.addressbook.RandomAddressBookGenerator; +import com.swirlds.platform.test.fixtures.addressbook.RandomAddressBookBuilder; import java.util.ArrayList; import java.util.Arrays; import java.util.HashSet; @@ -118,8 +119,9 @@ void testRandomGraphs(final int numNodes, final int numNeighbors, final long see @ParameterizedTest @MethodSource("fullyConnected") void testFullyConnectedTopology(final int numNodes, final int numNeighbors, final long ignoredSeed) { + final Randotron randotron = Randotron.create(); final AddressBook addressBook = - new RandomAddressBookGenerator().setSize(numNodes).build(); + RandomAddressBookBuilder.create(randotron).withSize(numNodes).build(); for (int thisNode = 0; thisNode < numNodes; thisNode++) { final NodeId outOfBoundsId = addressBook.getNextNodeId(); final NodeId thisNodeId = addressBook.getNodeId(thisNode); diff --git a/platform-sdk/swirlds-unit-tests/core/swirlds-platform-test/src/test/java/com/swirlds/platform/test/reconnect/FallenBehindManagerTest.java b/platform-sdk/swirlds-unit-tests/core/swirlds-platform-test/src/test/java/com/swirlds/platform/test/reconnect/FallenBehindManagerTest.java index 6ceafeb7671b..23ca662ac914 100644 --- a/platform-sdk/swirlds-unit-tests/core/swirlds-platform-test/src/test/java/com/swirlds/platform/test/reconnect/FallenBehindManagerTest.java +++ b/platform-sdk/swirlds-unit-tests/core/swirlds-platform-test/src/test/java/com/swirlds/platform/test/reconnect/FallenBehindManagerTest.java @@ -23,21 +23,23 @@ import com.swirlds.common.merkle.synchronization.config.ReconnectConfig; import com.swirlds.common.merkle.synchronization.config.ReconnectConfig_; import com.swirlds.common.platform.NodeId; +import com.swirlds.common.test.fixtures.Randotron; import com.swirlds.config.extensions.test.fixtures.TestConfigBuilder; import com.swirlds.platform.gossip.FallenBehindManager; import com.swirlds.platform.gossip.FallenBehindManagerImpl; import com.swirlds.platform.network.RandomGraph; import com.swirlds.platform.system.address.AddressBook; import com.swirlds.platform.system.status.StatusActionSubmitter; -import com.swirlds.platform.test.fixtures.addressbook.RandomAddressBookGenerator; +import com.swirlds.platform.test.fixtures.addressbook.RandomAddressBookBuilder; import java.util.Random; import java.util.concurrent.atomic.AtomicInteger; import org.junit.jupiter.api.Test; class FallenBehindManagerTest { private final int numNodes = 11; - private final AddressBook addressBook = - new RandomAddressBookGenerator().setSize(numNodes).build(); + private final AddressBook addressBook = RandomAddressBookBuilder.create(Randotron.create()) + .withSize(numNodes) + .build(); private final double fallenBehindThreshold = 0.5; private final NodeId selfId = addressBook.getNodeId(0); private final Random random = getRandomPrintSeed(); diff --git a/platform-sdk/swirlds-unit-tests/core/swirlds-platform-test/src/test/java/com/swirlds/platform/test/state/IssDetectorTests.java b/platform-sdk/swirlds-unit-tests/core/swirlds-platform-test/src/test/java/com/swirlds/platform/test/state/IssDetectorTests.java index 09047916311e..3bbf3c0bfb0d 100644 --- a/platform-sdk/swirlds-unit-tests/core/swirlds-platform-test/src/test/java/com/swirlds/platform/test/state/IssDetectorTests.java +++ b/platform-sdk/swirlds-unit-tests/core/swirlds-platform-test/src/test/java/com/swirlds/platform/test/state/IssDetectorTests.java @@ -38,6 +38,7 @@ import com.swirlds.platform.internal.ConsensusRound; import com.swirlds.platform.internal.EventImpl; import com.swirlds.platform.state.State; +import com.swirlds.platform.state.iss.DefaultIssDetector; import com.swirlds.platform.state.iss.IssDetector; import com.swirlds.platform.state.iss.internal.HashValidityStatus; import com.swirlds.platform.state.signed.ReservedSignedState; @@ -50,7 +51,7 @@ import com.swirlds.platform.system.transaction.ConsensusTransactionImpl; import com.swirlds.platform.system.transaction.StateSignatureTransaction; import com.swirlds.platform.test.PlatformTest; -import com.swirlds.platform.test.fixtures.addressbook.RandomAddressBookGenerator; +import com.swirlds.platform.test.fixtures.addressbook.RandomAddressBookBuilder; import com.swirlds.platform.test.fixtures.event.EventImplTestUtils; import com.swirlds.platform.test.fixtures.event.TestingEventBuilder; import com.swirlds.platform.wiring.components.StateAndRound; @@ -66,6 +67,7 @@ @DisplayName("IssDetector Tests") class IssDetectorTests extends PlatformTest { + /** * Generates a list of events, with each event containing a signature transaction from a node for the given round. * @@ -167,16 +169,16 @@ private static ConsensusRound createRoundWithSignatureEvents( @DisplayName("No ISSes Test") void noIss() { final Randotron random = Randotron.create(); - final AddressBook addressBook = new RandomAddressBookGenerator(random) - .setSize(100) - .setAverageWeight(100) - .setWeightStandardDeviation(50) + final AddressBook addressBook = RandomAddressBookBuilder.create(random) + .withSize(100) + .withAverageWeight(100) + .withWeightStandardDeviation(50) .build(); final PlatformContext platformContext = createDefaultPlatformContext(); - final IssDetector issDetector = - new IssDetector(platformContext, addressBook, new BasicSoftwareVersion(1), false, DO_NOT_IGNORE_ROUNDS); + final IssDetector issDetector = new DefaultIssDetector( + platformContext, addressBook, new BasicSoftwareVersion(1), false, DO_NOT_IGNORE_ROUNDS); final IssDetectorTestHelper issDetectorTestHelper = new IssDetectorTestHelper(issDetector); // signature events are generated for each round when that round is handled, and then are included randomly @@ -229,10 +231,10 @@ void noIss() { void mixedOrderTest() { final Randotron random = Randotron.create(); - final AddressBook addressBook = new RandomAddressBookGenerator(random) - .setSize(Math.max(10, random.nextInt(1000))) - .setAverageWeight(100) - .setWeightStandardDeviation(50) + final AddressBook addressBook = RandomAddressBookBuilder.create(random) + .withSize(Math.max(10, random.nextInt(1000))) + .withAverageWeight(100) + .withWeightStandardDeviation(50) .build(); final PlatformContext platformContext = createDefaultPlatformContext(); @@ -295,8 +297,8 @@ void mixedOrderTest() { } } - final IssDetector issDetector = - new IssDetector(platformContext, addressBook, new BasicSoftwareVersion(1), false, DO_NOT_IGNORE_ROUNDS); + final IssDetector issDetector = new DefaultIssDetector( + platformContext, addressBook, new BasicSoftwareVersion(1), false, DO_NOT_IGNORE_ROUNDS); final IssDetectorTestHelper issDetectorTestHelper = new IssDetectorTestHelper(issDetector); long currentRound = 0; @@ -374,15 +376,15 @@ void decideForCatastrophicIss() { final Randotron random = Randotron.create(); final PlatformContext platformContext = createDefaultPlatformContext(); - final AddressBook addressBook = new RandomAddressBookGenerator(random) - .setSize(100) - .setAverageWeight(100) - .setWeightStandardDeviation(50) + final AddressBook addressBook = RandomAddressBookBuilder.create(random) + .withSize(100) + .withAverageWeight(100) + .withWeightStandardDeviation(50) .build(); final NodeId selfId = addressBook.getNodeId(0); - final IssDetector issDetector = - new IssDetector(platformContext, addressBook, new BasicSoftwareVersion(1), false, DO_NOT_IGNORE_ROUNDS); + final IssDetector issDetector = new DefaultIssDetector( + platformContext, addressBook, new BasicSoftwareVersion(1), false, DO_NOT_IGNORE_ROUNDS); final IssDetectorTestHelper issDetectorTestHelper = new IssDetectorTestHelper(issDetector); long currentRound = 0; @@ -496,15 +498,15 @@ void catastrophicShiftBeforeCompleteTest() { .getConfiguration() .getConfigData(ConsensusConfig.class) .roundsNonAncient(); - final AddressBook addressBook = new RandomAddressBookGenerator(random) - .setSize(100) - .setAverageWeight(100) - .setWeightStandardDeviation(50) + final AddressBook addressBook = RandomAddressBookBuilder.create(random) + .withSize(100) + .withAverageWeight(100) + .withWeightStandardDeviation(50) .build(); final NodeId selfId = addressBook.getNodeId(0); - final IssDetector issDetector = - new IssDetector(platformContext, addressBook, new BasicSoftwareVersion(1), false, DO_NOT_IGNORE_ROUNDS); + final IssDetector issDetector = new DefaultIssDetector( + platformContext, addressBook, new BasicSoftwareVersion(1), false, DO_NOT_IGNORE_ROUNDS); final IssDetectorTestHelper issDetectorTestHelper = new IssDetectorTestHelper(issDetector); long currentRound = 0; @@ -581,15 +583,15 @@ void bigShiftTest() { .getConfiguration() .getConfigData(ConsensusConfig.class) .roundsNonAncient(); - final AddressBook addressBook = new RandomAddressBookGenerator(random) - .setSize(100) - .setAverageWeight(100) - .setWeightStandardDeviation(50) + final AddressBook addressBook = RandomAddressBookBuilder.create(random) + .withSize(100) + .withAverageWeight(100) + .withWeightStandardDeviation(50) .build(); final NodeId selfId = addressBook.getNodeId(0); - final IssDetector issDetector = - new IssDetector(platformContext, addressBook, new BasicSoftwareVersion(1), false, DO_NOT_IGNORE_ROUNDS); + final IssDetector issDetector = new DefaultIssDetector( + platformContext, addressBook, new BasicSoftwareVersion(1), false, DO_NOT_IGNORE_ROUNDS); final IssDetectorTestHelper issDetectorTestHelper = new IssDetectorTestHelper(issDetector); long currentRound = 0; @@ -653,10 +655,10 @@ void bigShiftTest() { void ignoredRoundTest() { final Randotron random = Randotron.create(); - final AddressBook addressBook = new RandomAddressBookGenerator(random) - .setSize(100) - .setAverageWeight(100) - .setWeightStandardDeviation(50) + final AddressBook addressBook = RandomAddressBookBuilder.create(random) + .withSize(100) + .withAverageWeight(100) + .withWeightStandardDeviation(50) .build(); final PlatformContext platformContext = createDefaultPlatformContext(); @@ -666,7 +668,7 @@ void ignoredRoundTest() { .roundsNonAncient(); final IssDetector issDetector = - new IssDetector(platformContext, addressBook, new BasicSoftwareVersion(1), false, 1); + new DefaultIssDetector(platformContext, addressBook, new BasicSoftwareVersion(1), false, 1); final IssDetectorTestHelper issDetectorTestHelper = new IssDetectorTestHelper(issDetector); long currentRound = 0; diff --git a/platform-sdk/swirlds-unit-tests/core/swirlds-platform-test/src/test/java/com/swirlds/platform/test/state/IssHandlerTests.java b/platform-sdk/swirlds-unit-tests/core/swirlds-platform-test/src/test/java/com/swirlds/platform/test/state/IssHandlerTests.java index 56f634c92388..fa123b58994f 100644 --- a/platform-sdk/swirlds-unit-tests/core/swirlds-platform-test/src/test/java/com/swirlds/platform/test/state/IssHandlerTests.java +++ b/platform-sdk/swirlds-unit-tests/core/swirlds-platform-test/src/test/java/com/swirlds/platform/test/state/IssHandlerTests.java @@ -20,12 +20,14 @@ import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertNull; +import com.swirlds.common.context.PlatformContext; import com.swirlds.common.merkle.utility.SerializableLong; import com.swirlds.common.scratchpad.Scratchpad; +import com.swirlds.common.test.fixtures.platform.TestPlatformContextBuilder; import com.swirlds.config.api.Configuration; import com.swirlds.config.extensions.test.fixtures.TestConfigBuilder; import com.swirlds.platform.components.common.output.FatalErrorConsumer; -import com.swirlds.platform.config.StateConfig; +import com.swirlds.platform.config.StateConfig_; import com.swirlds.platform.state.iss.IssHandler; import com.swirlds.platform.state.iss.IssScratchpad; import com.swirlds.platform.system.state.notifications.IssNotification; @@ -41,9 +43,12 @@ class IssHandlerTests { @Test @DisplayName("Other ISS Always Freeze") void otherIssAlwaysFreeze() { - final Configuration configuration = - new TestConfigBuilder().withValue("state.haltOnAnyIss", true).getOrCreateConfig(); - final StateConfig stateConfig = configuration.getConfigData(StateConfig.class); + final Configuration configuration = new TestConfigBuilder() + .withValue(StateConfig_.HALT_ON_ANY_ISS, true) + .getOrCreateConfig(); + final PlatformContext platformContext = TestPlatformContextBuilder.create() + .withConfiguration(configuration) + .build(); final AtomicInteger freezeCount = new AtomicInteger(); final AtomicInteger shutdownCount = new AtomicInteger(); @@ -54,7 +59,7 @@ void otherIssAlwaysFreeze() { final Scratchpad simpleScratchpad = new SimpleScratchpad<>(); final IssHandler handler = - new IssHandler(stateConfig, haltRequestedConsumer, fatalErrorConsumer, simpleScratchpad); + new IssHandler(platformContext, haltRequestedConsumer, fatalErrorConsumer, simpleScratchpad); handler.issObserved(new IssNotification(1234L, IssType.OTHER_ISS)); @@ -74,9 +79,12 @@ void otherIssAlwaysFreeze() { @Test @DisplayName("Other ISS No Action") void otherIssNoAction() { - final Configuration configuration = - new TestConfigBuilder().withValue("state.haltOnAnyIss", false).getOrCreateConfig(); - final StateConfig stateConfig = configuration.getConfigData(StateConfig.class); + final Configuration configuration = new TestConfigBuilder() + .withValue(StateConfig_.HALT_ON_ANY_ISS, false) + .getOrCreateConfig(); + final PlatformContext platformContext = TestPlatformContextBuilder.create() + .withConfiguration(configuration) + .build(); final AtomicInteger freezeCount = new AtomicInteger(); final AtomicInteger shutdownCount = new AtomicInteger(); @@ -87,7 +95,7 @@ void otherIssNoAction() { final Scratchpad simpleScratchpad = new SimpleScratchpad<>(); final IssHandler handler = - new IssHandler(stateConfig, haltRequestedConsumer, fatalErrorConsumer, simpleScratchpad); + new IssHandler(platformContext, haltRequestedConsumer, fatalErrorConsumer, simpleScratchpad); handler.issObserved(new IssNotification(1234L, IssType.OTHER_ISS)); @@ -101,10 +109,12 @@ void otherIssNoAction() { @DisplayName("Self ISS Automated Recovery") void selfIssAutomatedRecovery() { final Configuration configuration = new TestConfigBuilder() - .withValue("state.haltOnAnyIss", false) - .withValue("state.automatedSelfIssRecovery", true) + .withValue(StateConfig_.HALT_ON_ANY_ISS, false) + .withValue(StateConfig_.AUTOMATED_SELF_ISS_RECOVERY, true) .getOrCreateConfig(); - final StateConfig stateConfig = configuration.getConfigData(StateConfig.class); + final PlatformContext platformContext = TestPlatformContextBuilder.create() + .withConfiguration(configuration) + .build(); final AtomicInteger freezeCount = new AtomicInteger(); final AtomicInteger shutdownCount = new AtomicInteger(); @@ -115,7 +125,7 @@ void selfIssAutomatedRecovery() { final Scratchpad simpleScratchpad = new SimpleScratchpad<>(); final IssHandler handler = - new IssHandler(stateConfig, haltRequestedConsumer, fatalErrorConsumer, simpleScratchpad); + new IssHandler(platformContext, haltRequestedConsumer, fatalErrorConsumer, simpleScratchpad); handler.issObserved(new IssNotification(1234L, IssType.SELF_ISS)); @@ -131,10 +141,12 @@ void selfIssAutomatedRecovery() { @DisplayName("Self ISS No Action") void selfIssNoAction() { final Configuration configuration = new TestConfigBuilder() - .withValue("state.haltOnAnyIss", false) - .withValue("state.automatedSelfIssRecovery", false) + .withValue(StateConfig_.HALT_ON_ANY_ISS, false) + .withValue(StateConfig_.AUTOMATED_SELF_ISS_RECOVERY, false) .getOrCreateConfig(); - final StateConfig stateConfig = configuration.getConfigData(StateConfig.class); + final PlatformContext platformContext = TestPlatformContextBuilder.create() + .withConfiguration(configuration) + .build(); final AtomicInteger freezeCount = new AtomicInteger(); final AtomicInteger shutdownCount = new AtomicInteger(); @@ -145,7 +157,7 @@ void selfIssNoAction() { final Scratchpad simpleScratchpad = new SimpleScratchpad<>(); final IssHandler handler = - new IssHandler(stateConfig, haltRequestedConsumer, fatalErrorConsumer, simpleScratchpad); + new IssHandler(platformContext, haltRequestedConsumer, fatalErrorConsumer, simpleScratchpad); handler.issObserved(new IssNotification(1234L, IssType.SELF_ISS)); @@ -161,10 +173,12 @@ void selfIssNoAction() { @DisplayName("Self ISS Always Freeze") void selfIssAlwaysFreeze() { final Configuration configuration = new TestConfigBuilder() - .withValue("state.haltOnAnyIss", true) - .withValue("state.automatedSelfIssRecovery", false) + .withValue(StateConfig_.HALT_ON_ANY_ISS, true) + .withValue(StateConfig_.AUTOMATED_SELF_ISS_RECOVERY, false) .getOrCreateConfig(); - final StateConfig stateConfig = configuration.getConfigData(StateConfig.class); + final PlatformContext platformContext = TestPlatformContextBuilder.create() + .withConfiguration(configuration) + .build(); final AtomicInteger freezeCount = new AtomicInteger(); final AtomicInteger shutdownCount = new AtomicInteger(); @@ -175,7 +189,7 @@ void selfIssAlwaysFreeze() { final Scratchpad simpleScratchpad = new SimpleScratchpad<>(); final IssHandler handler = - new IssHandler(stateConfig, haltRequestedConsumer, fatalErrorConsumer, simpleScratchpad); + new IssHandler(platformContext, haltRequestedConsumer, fatalErrorConsumer, simpleScratchpad); handler.issObserved(new IssNotification(1234L, IssType.SELF_ISS)); @@ -197,10 +211,12 @@ void selfIssAlwaysFreeze() { @DisplayName("Catastrophic ISS No Action") void catastrophicIssNoAction() { final Configuration configuration = new TestConfigBuilder() - .withValue("state.haltOnAnyIss", false) - .withValue("state.haltOnCatastrophicIss", false) + .withValue(StateConfig_.HALT_ON_ANY_ISS, false) + .withValue(StateConfig_.HALT_ON_CATASTROPHIC_ISS, false) .getOrCreateConfig(); - final StateConfig stateConfig = configuration.getConfigData(StateConfig.class); + final PlatformContext platformContext = TestPlatformContextBuilder.create() + .withConfiguration(configuration) + .build(); final AtomicInteger freezeCount = new AtomicInteger(); final AtomicInteger shutdownCount = new AtomicInteger(); @@ -211,7 +227,7 @@ void catastrophicIssNoAction() { final Scratchpad simpleScratchpad = new SimpleScratchpad<>(); final IssHandler handler = - new IssHandler(stateConfig, haltRequestedConsumer, fatalErrorConsumer, simpleScratchpad); + new IssHandler(platformContext, haltRequestedConsumer, fatalErrorConsumer, simpleScratchpad); handler.issObserved(new IssNotification(1234L, IssType.CATASTROPHIC_ISS)); @@ -227,10 +243,12 @@ void catastrophicIssNoAction() { @DisplayName("Catastrophic ISS Always Freeze") void catastrophicIssAlwaysFreeze() { final Configuration configuration = new TestConfigBuilder() - .withValue("state.haltOnAnyIss", true) - .withValue("state.haltOnCatastrophicIss", false) + .withValue(StateConfig_.HALT_ON_ANY_ISS, true) + .withValue(StateConfig_.HALT_ON_CATASTROPHIC_ISS, false) .getOrCreateConfig(); - final StateConfig stateConfig = configuration.getConfigData(StateConfig.class); + final PlatformContext platformContext = TestPlatformContextBuilder.create() + .withConfiguration(configuration) + .build(); final AtomicInteger freezeCount = new AtomicInteger(); final AtomicInteger shutdownCount = new AtomicInteger(); @@ -241,7 +259,7 @@ void catastrophicIssAlwaysFreeze() { final Scratchpad simpleScratchpad = new SimpleScratchpad<>(); final IssHandler handler = - new IssHandler(stateConfig, haltRequestedConsumer, fatalErrorConsumer, simpleScratchpad); + new IssHandler(platformContext, haltRequestedConsumer, fatalErrorConsumer, simpleScratchpad); handler.issObserved(new IssNotification(1234L, IssType.CATASTROPHIC_ISS)); @@ -263,10 +281,12 @@ void catastrophicIssAlwaysFreeze() { @DisplayName("Catastrophic ISS Freeze On Catastrophic") void catastrophicIssFreezeOnCatastrophic() { final Configuration configuration = new TestConfigBuilder() - .withValue("state.haltOnAnyIss", false) - .withValue("state.haltOnCatastrophicIss", true) + .withValue(StateConfig_.HALT_ON_ANY_ISS, false) + .withValue(StateConfig_.HALT_ON_CATASTROPHIC_ISS, true) .getOrCreateConfig(); - final StateConfig stateConfig = configuration.getConfigData(StateConfig.class); + final PlatformContext platformContext = TestPlatformContextBuilder.create() + .withConfiguration(configuration) + .build(); final AtomicInteger freezeCount = new AtomicInteger(); final AtomicInteger shutdownCount = new AtomicInteger(); @@ -277,7 +297,7 @@ void catastrophicIssFreezeOnCatastrophic() { final Scratchpad simpleScratchpad = new SimpleScratchpad<>(); final IssHandler handler = - new IssHandler(stateConfig, haltRequestedConsumer, fatalErrorConsumer, simpleScratchpad); + new IssHandler(platformContext, haltRequestedConsumer, fatalErrorConsumer, simpleScratchpad); handler.issObserved(new IssNotification(1234L, IssType.CATASTROPHIC_ISS)); diff --git a/platform-sdk/swirlds-unit-tests/core/swirlds-platform-test/src/test/java/com/swirlds/platform/test/state/IssMetricsTests.java b/platform-sdk/swirlds-unit-tests/core/swirlds-platform-test/src/test/java/com/swirlds/platform/test/state/IssMetricsTests.java index 754f47b01fa7..51c1ee6d9058 100644 --- a/platform-sdk/swirlds-unit-tests/core/swirlds-platform-test/src/test/java/com/swirlds/platform/test/state/IssMetricsTests.java +++ b/platform-sdk/swirlds-unit-tests/core/swirlds-platform-test/src/test/java/com/swirlds/platform/test/state/IssMetricsTests.java @@ -24,10 +24,11 @@ import com.swirlds.common.crypto.Hash; import com.swirlds.common.metrics.noop.NoOpMetrics; import com.swirlds.common.platform.NodeId; +import com.swirlds.common.test.fixtures.Randotron; import com.swirlds.platform.metrics.IssMetrics; import com.swirlds.platform.system.address.Address; import com.swirlds.platform.system.address.AddressBook; -import com.swirlds.platform.test.fixtures.addressbook.RandomAddressBookGenerator; +import com.swirlds.platform.test.fixtures.addressbook.RandomAddressBookBuilder; import java.util.Random; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; @@ -38,9 +39,10 @@ class IssMetricsTests { @Test @DisplayName("Update Non-Existent Node") void updateNonExistentNode() { + final Randotron randotron = Randotron.create(); final AddressBook addressBook = - new RandomAddressBookGenerator().setSize(100).build(); + RandomAddressBookBuilder.create(randotron).withSize(100).build(); final IssMetrics issMetrics = new IssMetrics(new NoOpMetrics(), addressBook); @@ -60,7 +62,7 @@ void updateTest() { final Hash hashB = randomHash(random); final AddressBook addressBook = - new RandomAddressBookGenerator(random).setSize(100).build(); + RandomAddressBookBuilder.create(random).withSize(100).build(); final IssMetrics issMetrics = new IssMetrics(new NoOpMetrics(), addressBook); diff --git a/platform-sdk/swirlds-unit-tests/core/swirlds-platform-test/src/test/java/com/swirlds/platform/test/state/RoundHashValidatorTests.java b/platform-sdk/swirlds-unit-tests/core/swirlds-platform-test/src/test/java/com/swirlds/platform/test/state/RoundHashValidatorTests.java index 5b0dd90970b8..8c6e64f4d5e7 100644 --- a/platform-sdk/swirlds-unit-tests/core/swirlds-platform-test/src/test/java/com/swirlds/platform/test/state/RoundHashValidatorTests.java +++ b/platform-sdk/swirlds-unit-tests/core/swirlds-platform-test/src/test/java/com/swirlds/platform/test/state/RoundHashValidatorTests.java @@ -30,7 +30,7 @@ import com.swirlds.platform.state.iss.internal.HashValidityStatus; import com.swirlds.platform.state.iss.internal.RoundHashValidator; import com.swirlds.platform.system.address.AddressBook; -import com.swirlds.platform.test.fixtures.addressbook.RandomAddressBookGenerator; +import com.swirlds.platform.test.fixtures.addressbook.RandomAddressBookBuilder; import java.util.ArrayList; import java.util.Collections; import java.util.LinkedList; @@ -260,10 +260,10 @@ private static NodeHashInfo chooseSelfNode( void selfSignatureLastTest(final HashValidityStatus expectedStatus) { final Random random = getRandomPrintSeed(); - final AddressBook addressBook = new RandomAddressBookGenerator(random) - .setSize(Math.max(10, random.nextInt(1000))) - .setAverageWeight(100) - .setWeightStandardDeviation(50) + final AddressBook addressBook = RandomAddressBookBuilder.create(random) + .withSize(Math.max(10, random.nextInt(1000))) + .withAverageWeight(100) + .withWeightStandardDeviation(50) .build(); final HashGenerationData hashGenerationData = generateNodeHashes(random, addressBook, expectedStatus, 0); @@ -306,10 +306,10 @@ void selfSignatureLastTest(final HashValidityStatus expectedStatus) { void selfSignatureFirstTest(final HashValidityStatus expectedStatus) { final Random random = getRandomPrintSeed(); - final AddressBook addressBook = new RandomAddressBookGenerator(random) - .setSize(Math.max(10, random.nextInt(1000))) - .setAverageWeight(100) - .setWeightStandardDeviation(50) + final AddressBook addressBook = RandomAddressBookBuilder.create(random) + .withSize(Math.max(10, random.nextInt(1000))) + .withAverageWeight(100) + .withWeightStandardDeviation(50) .build(); final HashGenerationData hashGenerationData = generateNodeHashes(random, addressBook, expectedStatus, 0); @@ -350,10 +350,10 @@ void selfSignatureFirstTest(final HashValidityStatus expectedStatus) { void selfSignatureInMiddleTest(final HashValidityStatus expectedStatus) { final Random random = getRandomPrintSeed(); - final AddressBook addressBook = new RandomAddressBookGenerator(random) - .setSize(Math.max(10, random.nextInt(1000))) - .setAverageWeight(100) - .setWeightStandardDeviation(50) + final AddressBook addressBook = RandomAddressBookBuilder.create(random) + .withSize(Math.max(10, random.nextInt(1000))) + .withAverageWeight(100) + .withWeightStandardDeviation(50) .build(); final HashGenerationData hashGenerationData = generateNodeHashes(random, addressBook, expectedStatus, 0); @@ -400,10 +400,10 @@ void selfSignatureInMiddleTest(final HashValidityStatus expectedStatus) { void timeoutSelfHashTest() { final Random random = getRandomPrintSeed(); - final AddressBook addressBook = new RandomAddressBookGenerator(random) - .setSize(Math.max(10, random.nextInt(1000))) - .setAverageWeight(100) - .setWeightStandardDeviation(50) + final AddressBook addressBook = RandomAddressBookBuilder.create(random) + .withSize(Math.max(10, random.nextInt(1000))) + .withAverageWeight(100) + .withWeightStandardDeviation(50) .build(); final HashGenerationData hashGenerationData = @@ -433,10 +433,10 @@ void timeoutSelfHashTest() { void timeoutSelfHashAndSignaturesTest() { final Random random = getRandomPrintSeed(); - final AddressBook addressBook = new RandomAddressBookGenerator(random) - .setSize(Math.max(10, random.nextInt(1000))) - .setAverageWeight(100) - .setWeightStandardDeviation(50) + final AddressBook addressBook = RandomAddressBookBuilder.create(random) + .withSize(Math.max(10, random.nextInt(1000))) + .withAverageWeight(100) + .withWeightStandardDeviation(50) .build(); final long totalWeight = addressBook.getTotalWeight(); @@ -474,10 +474,10 @@ void timeoutSelfHashAndSignaturesTest() { void timeoutSignaturesTest() { final Random random = getRandomPrintSeed(); - final AddressBook addressBook = new RandomAddressBookGenerator(random) - .setSize(Math.max(10, random.nextInt(1000))) - .setAverageWeight(100) - .setWeightStandardDeviation(50) + final AddressBook addressBook = RandomAddressBookBuilder.create(random) + .withSize(Math.max(10, random.nextInt(1000))) + .withAverageWeight(100) + .withWeightStandardDeviation(50) .build(); final long totalWeight = addressBook.getTotalWeight(); @@ -519,10 +519,10 @@ void timeoutSignaturesTest() { void timeoutWithSuperMajorityTest() { final Random random = getRandomPrintSeed(); - final AddressBook addressBook = new RandomAddressBookGenerator(random) - .setSize(Math.max(10, random.nextInt(1000))) - .setAverageWeight(100) - .setWeightStandardDeviation(50) + final AddressBook addressBook = RandomAddressBookBuilder.create(random) + .withSize(Math.max(10, random.nextInt(1000))) + .withAverageWeight(100) + .withWeightStandardDeviation(50) .build(); final long totalWeight = addressBook.getTotalWeight(); diff --git a/platform-sdk/swirlds-unit-tests/core/swirlds-platform-test/src/test/java/com/swirlds/platform/test/sync/ShadowGraphTest.java b/platform-sdk/swirlds-unit-tests/core/swirlds-platform-test/src/test/java/com/swirlds/platform/test/sync/ShadowGraphTest.java index ec39d2942069..096958398461 100644 --- a/platform-sdk/swirlds-unit-tests/core/swirlds-platform-test/src/test/java/com/swirlds/platform/test/sync/ShadowGraphTest.java +++ b/platform-sdk/swirlds-unit-tests/core/swirlds-platform-test/src/test/java/com/swirlds/platform/test/sync/ShadowGraphTest.java @@ -32,7 +32,6 @@ import com.swirlds.common.crypto.Hash; import com.swirlds.common.test.fixtures.RandomUtils; import com.swirlds.common.test.fixtures.platform.TestPlatformContextBuilder; -import com.swirlds.common.utility.CommonUtils; import com.swirlds.platform.consensus.EventWindow; import com.swirlds.platform.gossip.NoOpIntakeEventCounter; import com.swirlds.platform.gossip.shadowgraph.ReservedEventWindow; @@ -43,7 +42,7 @@ import com.swirlds.platform.system.address.AddressBook; import com.swirlds.platform.test.event.emitter.EventEmitterFactory; import com.swirlds.platform.test.event.emitter.StandardEventEmitter; -import com.swirlds.platform.test.fixtures.addressbook.RandomAddressBookGenerator; +import com.swirlds.platform.test.fixtures.addressbook.RandomAddressBookBuilder; import com.swirlds.platform.test.fixtures.event.IndexedEvent; import java.time.Instant; import java.time.temporal.ChronoUnit; @@ -96,7 +95,7 @@ public void setup() { } private void initShadowgraph(final Random random, final int numEvents, final int numNodes) { - addressBook = new RandomAddressBookGenerator(random).setSize(numNodes).build(); + addressBook = RandomAddressBookBuilder.create(random).withSize(numNodes).build(); final PlatformContext platformContext = TestPlatformContextBuilder.create().build(); @@ -104,6 +103,7 @@ private void initShadowgraph(final Random random, final int numEvents, final int final EventEmitterFactory factory = new EventEmitterFactory(platformContext, random, addressBook); emitter = factory.newStandardEmitter(); shadowgraph = new Shadowgraph(platformContext, mock(AddressBook.class), new NoOpIntakeEventCounter()); + shadowgraph.updateEventWindow(EventWindow.getGenesisEventWindow(GENERATION_THRESHOLD)); for (int i = 0; i < numEvents; i++) { final IndexedEvent event = emitter.emitEvent(); @@ -176,7 +176,7 @@ void testFindAncestorsExcludesExpiredEvents() { private void assertSetsContainSameHashes(final Set expected, final Set actual) { for (final Hash hash : expected) { if (!actual.contains(hash)) { - fail(String.format("Expected to find an ancestor with hash %s", CommonUtils.hex(hash.getValue(), 4))); + fail(String.format("Expected to find an ancestor with hash %s", hash.toHex(4))); } } } @@ -642,6 +642,7 @@ void testClear() { r1.close(); shadowgraph.clear(); + shadowgraph.updateEventWindow(EventWindow.getGenesisEventWindow(GENERATION_THRESHOLD)); assertEquals(0, shadowgraph.getTips().size(), "Shadow graph should not have any tips after being cleared."); for (final IndexedEvent generatedEvent : generatedEvents) { @@ -722,7 +723,7 @@ void findAncestorsPerformance() throws ShadowgraphInsertionException { final Random random = RandomUtils.getRandomPrintSeed(); final AddressBook addressBook = - new RandomAddressBookGenerator(random).setSize(numNodes).build(); + RandomAddressBookBuilder.create(random).withSize(numNodes).build(); final PlatformContext platformContext = TestPlatformContextBuilder.create().build(); diff --git a/platform-sdk/swirlds-unit-tests/core/swirlds-platform-test/src/test/java/com/swirlds/platform/test/sync/ShadowgraphByBirthRoundTests.java b/platform-sdk/swirlds-unit-tests/core/swirlds-platform-test/src/test/java/com/swirlds/platform/test/sync/ShadowgraphByBirthRoundTests.java index 5d75bbffaf55..c7cdd040fe8d 100644 --- a/platform-sdk/swirlds-unit-tests/core/swirlds-platform-test/src/test/java/com/swirlds/platform/test/sync/ShadowgraphByBirthRoundTests.java +++ b/platform-sdk/swirlds-unit-tests/core/swirlds-platform-test/src/test/java/com/swirlds/platform/test/sync/ShadowgraphByBirthRoundTests.java @@ -32,7 +32,6 @@ import com.swirlds.common.crypto.Hash; import com.swirlds.common.test.fixtures.RandomUtils; import com.swirlds.common.test.fixtures.platform.TestPlatformContextBuilder; -import com.swirlds.common.utility.CommonUtils; import com.swirlds.config.api.Configuration; import com.swirlds.config.extensions.test.fixtures.TestConfigBuilder; import com.swirlds.platform.consensus.EventWindow; @@ -46,7 +45,7 @@ import com.swirlds.platform.system.address.AddressBook; import com.swirlds.platform.test.event.emitter.EventEmitterFactory; import com.swirlds.platform.test.event.emitter.StandardEventEmitter; -import com.swirlds.platform.test.fixtures.addressbook.RandomAddressBookGenerator; +import com.swirlds.platform.test.fixtures.addressbook.RandomAddressBookBuilder; import com.swirlds.platform.test.fixtures.event.IndexedEvent; import java.util.ArrayList; import java.util.Collections; @@ -97,7 +96,7 @@ public void setup() { } private void initShadowGraph(final Random random, final int numEvents, final int numNodes) { - addressBook = new RandomAddressBookGenerator(random).setSize(numNodes).build(); + addressBook = RandomAddressBookBuilder.create(random).withSize(numNodes).build(); final Configuration configuration = new TestConfigBuilder() .withValue(EventConfig_.USE_BIRTH_ROUND_ANCIENT_THRESHOLD, true) @@ -111,6 +110,7 @@ private void initShadowGraph(final Random random, final int numEvents, final int emitter = factory.newStandardEmitter(); shadowGraph = new Shadowgraph(platformContext, mock(AddressBook.class), new NoOpIntakeEventCounter()); + shadowGraph.updateEventWindow(EventWindow.getGenesisEventWindow(BIRTH_ROUND_THRESHOLD)); for (int i = 0; i < numEvents; i++) { final IndexedEvent event = emitter.emitEvent(); @@ -190,7 +190,7 @@ void testFindAncestorsExcludesExpiredEvents() { private void assertSetsContainSameHashes(final Set expected, final Set actual) { for (final Hash hash : expected) { if (!actual.contains(hash)) { - fail(String.format("Expected to find an ancestor with hash %s", CommonUtils.hex(hash.getValue(), 4))); + fail(String.format("Expected to find an ancestor with hash %s", hash.toHex(4))); } } } @@ -662,6 +662,7 @@ void testClear() { r1.close(); shadowGraph.clear(); + shadowGraph.updateEventWindow(EventWindow.getGenesisEventWindow(BIRTH_ROUND_THRESHOLD)); assertEquals(0, shadowGraph.getTips().size(), "Shadow graph should not have any tips after being cleared."); for (final IndexedEvent generatedEvent : generatedEvents) { diff --git a/platform-sdk/swirlds-unit-tests/core/swirlds-platform-test/src/test/java/com/swirlds/platform/test/sync/SyncFilteringTest.java b/platform-sdk/swirlds-unit-tests/core/swirlds-platform-test/src/test/java/com/swirlds/platform/test/sync/SyncFilteringTest.java index 0d9d0ff3fd3e..330664c81d6c 100644 --- a/platform-sdk/swirlds-unit-tests/core/swirlds-platform-test/src/test/java/com/swirlds/platform/test/sync/SyncFilteringTest.java +++ b/platform-sdk/swirlds-unit-tests/core/swirlds-platform-test/src/test/java/com/swirlds/platform/test/sync/SyncFilteringTest.java @@ -30,7 +30,7 @@ import com.swirlds.platform.gossip.sync.config.SyncConfig; import com.swirlds.platform.internal.EventImpl; import com.swirlds.platform.system.address.AddressBook; -import com.swirlds.platform.test.fixtures.addressbook.RandomAddressBookGenerator; +import com.swirlds.platform.test.fixtures.addressbook.RandomAddressBookBuilder; import com.swirlds.platform.test.fixtures.event.generator.StandardGraphGenerator; import com.swirlds.platform.test.fixtures.event.source.EventSource; import com.swirlds.platform.test.fixtures.event.source.StandardEventSource; @@ -125,7 +125,7 @@ void filterLikelyDuplicatesTest() { final Random random = getRandomPrintSeed(); final AddressBook addressBook = - new RandomAddressBookGenerator(random).setSize(32).build(); + RandomAddressBookBuilder.create(random).withSize(32).build(); final NodeId selfId = addressBook.getNodeId(0); final Instant startingTime = Instant.ofEpochMilli(random.nextInt()); diff --git a/platform-sdk/swirlds-unit-tests/core/swirlds-platform-test/src/test/java/com/swirlds/platform/test/sync/SyncNode.java b/platform-sdk/swirlds-unit-tests/core/swirlds-platform-test/src/test/java/com/swirlds/platform/test/sync/SyncNode.java index 6559a6c78cbc..961e0e406785 100644 --- a/platform-sdk/swirlds-unit-tests/core/swirlds-platform-test/src/test/java/com/swirlds/platform/test/sync/SyncNode.java +++ b/platform-sdk/swirlds-unit-tests/core/swirlds-platform-test/src/test/java/com/swirlds/platform/test/sync/SyncNode.java @@ -144,6 +144,7 @@ public SyncNode( .build(); shadowGraph = new Shadowgraph(platformContext, mock(AddressBook.class), new NoOpIntakeEventCounter()); + shadowGraph.updateEventWindow(EventWindow.getGenesisEventWindow(ancientMode)); this.executor = executor; } diff --git a/platform-sdk/swirlds-unit-tests/core/swirlds-platform-test/src/test/java/com/swirlds/platform/test/sync/SyncTestExecutor.java b/platform-sdk/swirlds-unit-tests/core/swirlds-platform-test/src/test/java/com/swirlds/platform/test/sync/SyncTestExecutor.java index 97c33b9aadd4..074f5328b761 100644 --- a/platform-sdk/swirlds-unit-tests/core/swirlds-platform-test/src/test/java/com/swirlds/platform/test/sync/SyncTestExecutor.java +++ b/platform-sdk/swirlds-unit-tests/core/swirlds-platform-test/src/test/java/com/swirlds/platform/test/sync/SyncTestExecutor.java @@ -21,6 +21,7 @@ import com.swirlds.base.utility.Pair; import com.swirlds.common.context.PlatformContext; import com.swirlds.common.test.fixtures.RandomUtils; +import com.swirlds.common.test.fixtures.Randotron; import com.swirlds.common.test.fixtures.platform.TestPlatformContextBuilder; import com.swirlds.common.threading.pool.CachedPoolParallelExecutor; import com.swirlds.common.threading.pool.ParallelExecutor; @@ -34,7 +35,7 @@ import com.swirlds.platform.test.event.emitter.EventEmitter; import com.swirlds.platform.test.event.emitter.EventEmitterFactory; import com.swirlds.platform.test.event.emitter.ShuffledEventEmitter; -import com.swirlds.platform.test.fixtures.addressbook.RandomAddressBookGenerator; +import com.swirlds.platform.test.fixtures.addressbook.RandomAddressBookBuilder; import com.swirlds.platform.test.fixtures.event.IndexedEvent; import edu.umd.cs.findbugs.annotations.NonNull; import java.io.IOException; @@ -81,8 +82,8 @@ public class SyncTestExecutor { public SyncTestExecutor(final SyncTestParams params) { this.params = params; this.ancientMode = params.getAncientMode(); - this.addressBook = new RandomAddressBookGenerator() - .setSize(params.getNumNetworkNodes()) + this.addressBook = RandomAddressBookBuilder.create(Randotron.create()) + .withSize(params.getNumNetworkNodes()) .build(); factoryConfig = (f) -> {}; diff --git a/platform-sdk/swirlds-unit-tests/core/swirlds-platform-test/src/test/java/com/swirlds/platform/test/sync/SyncValidator.java b/platform-sdk/swirlds-unit-tests/core/swirlds-platform-test/src/test/java/com/swirlds/platform/test/sync/SyncValidator.java index 3fe060b5611c..f6dcb4f0de95 100644 --- a/platform-sdk/swirlds-unit-tests/core/swirlds-platform-test/src/test/java/com/swirlds/platform/test/sync/SyncValidator.java +++ b/platform-sdk/swirlds-unit-tests/core/swirlds-platform-test/src/test/java/com/swirlds/platform/test/sync/SyncValidator.java @@ -25,13 +25,12 @@ import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.jupiter.api.Assertions.fail; -import com.swirlds.common.utility.CommonUtils; +import com.hedera.pbj.runtime.io.buffer.Bytes; import com.swirlds.platform.event.AncientMode; import com.swirlds.platform.event.GossipEvent; import com.swirlds.platform.internal.EventImpl; import com.swirlds.platform.network.Connection; import com.swirlds.platform.system.events.BaseEventHashedData; -import com.swirlds.platform.system.events.BaseEventUnhashedData; import com.swirlds.platform.test.fixtures.event.IndexedEvent; import edu.umd.cs.findbugs.annotations.NonNull; import java.io.IOException; @@ -202,10 +201,10 @@ private static void compareEventLists( for (final GossipEvent actual : actualList) { final BaseEventHashedData actualHashedData = actual.getHashedData(); - final BaseEventUnhashedData actualUnhashedData = actual.getUnhashedData(); + final Bytes actualSignature = actual.getSignature(); if (expected.getHashedData().equals(actualHashedData) - && expected.getUnhashedData().equals(actualUnhashedData)) { + && expected.getBaseEvent().getSignature().equals(actualSignature)) { foundMatch = true; break; } @@ -223,7 +222,7 @@ private static void compareEventLists( if (!expectedAndNotFound.isEmpty()) { List missingHashes = expectedAndNotFound.stream() .map(EventImpl::getBaseHash) - .map(h -> CommonUtils.hex(h.getValue(), 4)) + .map(h -> h.toHex(4)) .collect(Collectors.toList()); fail(format( "Actual list is missing %s expected event(s) with hash(es) %s", diff --git a/platform-sdk/swirlds-unit-tests/core/swirlds-platform-test/src/test/java/com/swirlds/platform/test/threading/PauseAndDoTest.java b/platform-sdk/swirlds-unit-tests/core/swirlds-platform-test/src/test/java/com/swirlds/platform/test/threading/PauseAndDoTest.java deleted file mode 100644 index bcbdda5db7e2..000000000000 --- a/platform-sdk/swirlds-unit-tests/core/swirlds-platform-test/src/test/java/com/swirlds/platform/test/threading/PauseAndDoTest.java +++ /dev/null @@ -1,82 +0,0 @@ -/* - * Copyright (C) 2022-2024 Hedera Hashgraph, LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.swirlds.platform.test.threading; - -import com.swirlds.common.threading.framework.StoppableThread; -import com.swirlds.common.utility.Clearable; -import com.swirlds.platform.state.signed.LoadableFromSignedState; -import com.swirlds.platform.threading.PauseAndClear; -import com.swirlds.platform.threading.PauseAndLoad; -import org.junit.jupiter.api.Assertions; -import org.junit.jupiter.api.Test; -import org.mockito.Mockito; - -class PauseAndDoTest { - @Test - void pauseAndClearBasic() { - final StoppableThread thread = Mockito.mock(StoppableThread.class); - final Clearable clearable = Mockito.mock(Clearable.class); - - final PauseAndClear pauseAndClear = new PauseAndClear(thread, clearable); - pauseAndClear.clear(); - - Mockito.verify(thread).pause(); - Mockito.verify(clearable).clear(); - Mockito.verify(thread).resume(); - } - - @Test - void pauseAndClearException() { - final StoppableThread thread = Mockito.mock(StoppableThread.class); - final Clearable clearable = () -> { - throw new RuntimeException(); - }; - - final PauseAndClear pauseAndClear = new PauseAndClear(thread, clearable); - Assertions.assertThrows(RuntimeException.class, pauseAndClear::clear); - - Mockito.verify(thread).pause(); - Mockito.verify(thread).resume(); - } - - @Test - void pauseAndLoadBasic() { - final StoppableThread thread = Mockito.mock(StoppableThread.class); - final LoadableFromSignedState loadable = Mockito.mock(LoadableFromSignedState.class); - - final PauseAndLoad pauseAndLoad = new PauseAndLoad(thread, loadable); - pauseAndLoad.loadFromSignedState(null); - - Mockito.verify(thread).pause(); - Mockito.verify(loadable).loadFromSignedState(null); - Mockito.verify(thread).resume(); - } - - @Test - void pauseAndLoadException() { - final StoppableThread thread = Mockito.mock(StoppableThread.class); - final LoadableFromSignedState loadable = (ss) -> { - throw new RuntimeException(); - }; - - final PauseAndLoad pauseAndLoad = new PauseAndLoad(thread, loadable); - Assertions.assertThrows(RuntimeException.class, () -> pauseAndLoad.loadFromSignedState(null)); - - Mockito.verify(thread).pause(); - Mockito.verify(thread).resume(); - } -} diff --git a/platform-sdk/swirlds-virtualmap/build.gradle.kts b/platform-sdk/swirlds-virtualmap/build.gradle.kts index 83ffa4ff76a8..5f3e33ce0281 100644 --- a/platform-sdk/swirlds-virtualmap/build.gradle.kts +++ b/platform-sdk/swirlds-virtualmap/build.gradle.kts @@ -17,10 +17,10 @@ import me.champeau.jmh.JMHTask plugins { - id("com.hedera.hashgraph.sdk.conventions") - id("com.hedera.hashgraph.platform-maven-publish") - id("com.hedera.hashgraph.benchmark-conventions") - id("com.hedera.hashgraph.java-test-fixtures") + id("com.hedera.gradle.platform") + id("com.hedera.gradle.platform-publish") + id("com.hedera.gradle.benchmark") + id("com.hedera.gradle.java-test-fixtures") } mainModuleInfo { annotationProcessor("com.swirlds.config.processor") } diff --git a/platform-sdk/swirlds-virtualmap/src/main/java/com/swirlds/virtualmap/VirtualMap.java b/platform-sdk/swirlds-virtualmap/src/main/java/com/swirlds/virtualmap/VirtualMap.java index 4dc84df054e4..2947e2562fbe 100644 --- a/platform-sdk/swirlds-virtualmap/src/main/java/com/swirlds/virtualmap/VirtualMap.java +++ b/platform-sdk/swirlds-virtualmap/src/main/java/com/swirlds/virtualmap/VirtualMap.java @@ -26,6 +26,7 @@ import com.swirlds.common.merkle.MerkleInternal; import com.swirlds.common.merkle.MerkleNode; import com.swirlds.common.merkle.impl.PartialBinaryMerkleInternal; +import com.swirlds.common.merkle.utility.DebugIterationEndpoint; import com.swirlds.common.utility.Labeled; import com.swirlds.common.utility.RuntimeObjectRecord; import com.swirlds.common.utility.RuntimeObjectRegistry; @@ -112,6 +113,7 @@ * @param * The value. Must be a {@link VirtualValue}. */ +@DebugIterationEndpoint public final class VirtualMap extends PartialBinaryMerkleInternal implements ExternalSelfSerializable, Labeled, MerkleInternal { diff --git a/platform-sdk/swirlds-virtualmap/src/main/java/com/swirlds/virtualmap/internal/ConcurrentNodeStatusTracker.java b/platform-sdk/swirlds-virtualmap/src/main/java/com/swirlds/virtualmap/internal/ConcurrentNodeStatusTracker.java index 1b9152834585..21bc2111d034 100644 --- a/platform-sdk/swirlds-virtualmap/src/main/java/com/swirlds/virtualmap/internal/ConcurrentNodeStatusTracker.java +++ b/platform-sdk/swirlds-virtualmap/src/main/java/com/swirlds/virtualmap/internal/ConcurrentNodeStatusTracker.java @@ -179,7 +179,7 @@ public void set(final long value, final Status status) { * * @param value * path of node to check - * @return status of aa node + * @return status of a node */ public Status getStatus(long value) { if (value < 0 || value >= capacity) { @@ -200,6 +200,30 @@ public Status getStatus(long value) { return status; } + /** + * Get the status of a node as reported by the learner, or return UNKNOWN. + *

    + * Unlike the getStatus(long value) method above, this method returns the actual + * status of the requested node without traversing the tree to its parents. + * If the learner hasn't reported a status for this particular node, this method + * returns UNKNOWN. + * + * @param value path of node to check + * @return status of the node, or UNKNOWN if its status has never been reported yet + */ + public Status getReportedStatus(long value) { + if (value < 0 || value >= capacity) { + throw new IllegalArgumentException( + String.format("Value can only be between [0, %d), %d is illegal", capacity, value)); + } + + final int index = getIndexInBitSetFor(value); + final long bitSetIndex = getBitSetIndexFor(value); + return statusBitSets + .computeIfAbsent(bitSetIndex, k -> new BitSetGroup()) + .getStatus(index); + } + /** * Atomically sets the status of a node represented by its value (path). * Currently, for the immediate use case, we are blocking for each diff --git a/platform-sdk/swirlds-virtualmap/src/main/java/com/swirlds/virtualmap/internal/pipeline/VirtualPipeline.java b/platform-sdk/swirlds-virtualmap/src/main/java/com/swirlds/virtualmap/internal/pipeline/VirtualPipeline.java index ccf29b8e2a93..c69b7c6bb024 100644 --- a/platform-sdk/swirlds-virtualmap/src/main/java/com/swirlds/virtualmap/internal/pipeline/VirtualPipeline.java +++ b/platform-sdk/swirlds-virtualmap/src/main/java/com/swirlds/virtualmap/internal/pipeline/VirtualPipeline.java @@ -624,7 +624,7 @@ private synchronized void shutdown(final boolean immediately) { * @param runnable * The runnable. Cannot be null. */ - private void pausePipelineAndExecute(final String label, final Runnable runnable) { + void pausePipelineAndExecute(final String label, final Runnable runnable) { Objects.requireNonNull(runnable); final CountDownLatch waitForBackgroundThreadToStart = new CountDownLatch(1); final CountDownLatch waitForRunnableToFinish = new CountDownLatch(1); diff --git a/platform-sdk/swirlds-virtualmap/src/main/java/com/swirlds/virtualmap/internal/reconnect/PullVirtualTreeRequest.java b/platform-sdk/swirlds-virtualmap/src/main/java/com/swirlds/virtualmap/internal/reconnect/PullVirtualTreeRequest.java index 4cdfcb305869..b2dce72db113 100644 --- a/platform-sdk/swirlds-virtualmap/src/main/java/com/swirlds/virtualmap/internal/reconnect/PullVirtualTreeRequest.java +++ b/platform-sdk/swirlds-virtualmap/src/main/java/com/swirlds/virtualmap/internal/reconnect/PullVirtualTreeRequest.java @@ -78,7 +78,7 @@ public PullVirtualTreeRequest(final long path, final Hash hash) { public void serialize(final SerializableDataOutputStream out) throws IOException { out.writeLong(path); if (hash != null) { - out.write(hash.getValue()); + hash.getBytes().writeTo(out); } } @@ -89,10 +89,11 @@ public void serialize(final SerializableDataOutputStream out) throws IOException public void deserialize(final SerializableDataInputStream in, final int version) throws IOException { path = in.readLong(); if (path >= 0) { - hash = new Hash(DigestType.SHA_384); - if (VirtualReconnectUtils.completelyRead(in, hash.getValue()) != DigestType.SHA_384.digestLength()) { + final byte[] hashBytes = new byte[DigestType.SHA_384.digestLength()]; + if (VirtualReconnectUtils.completelyRead(in, hashBytes) != DigestType.SHA_384.digestLength()) { throw new IOException("Failed to read node hash from the learner"); } + hash = new Hash(hashBytes, DigestType.SHA_384); } } diff --git a/platform-sdk/swirlds-virtualmap/src/main/java/com/swirlds/virtualmap/internal/reconnect/TeacherPushVirtualTreeView.java b/platform-sdk/swirlds-virtualmap/src/main/java/com/swirlds/virtualmap/internal/reconnect/TeacherPushVirtualTreeView.java index d696e6e40d19..e56f4230f27b 100644 --- a/platform-sdk/swirlds-virtualmap/src/main/java/com/swirlds/virtualmap/internal/reconnect/TeacherPushVirtualTreeView.java +++ b/platform-sdk/swirlds-virtualmap/src/main/java/com/swirlds/virtualmap/internal/reconnect/TeacherPushVirtualTreeView.java @@ -71,11 +71,57 @@ public final class TeacherPushVirtualTreeView + * It's volatile to avoid extra locks when accessing the value. It's totally + * okay if the value is updated after it's read. The worst that could happen + * is that we'll start sending the next level of nodes before receiving + * a response for the last node at the current level, but this is unlikely to happen + * because the code processing the queue is single-threaded, + * and it doesn't hurt from the correctness perspective even if it happens. + */ + private volatile Long lastNodeAwaitingReporting = null; + /** - * A queue of the nodes (by path) that we are about to handle. Note that ConcurrentBitSetQueue - * cleans up after itself in "chunks", such that we don't end up consuming a ton of memory. + * A node status may become implicitly reported by a response about its ancestors, + * in which case we can retest the condition and go ahead and send the next level + * without waiting for this particular node to report its status since it's already known/inferred. + * So we use a timeout to recheck the condition periodically. */ - private final ConcurrentBitSetQueue handleQueue = new ConcurrentBitSetQueue(); + private static final long AWAIT_FOR_REPORT_TIMEOUT_MILLIS = 1; + + /** + * The maximum total time for the `while (!hasReported) { wait() }` loop. + * This is used to protect the teacher from a dying learner. + */ + private static final long MAX_TOTAL_AWAIT_FOR_REPORT_TIMEOUT_MILLIS = 1000; /** * A queue of the nodes (by path) that we expect responses for. @@ -188,7 +234,7 @@ public Long getRoot() { public void addToHandleQueue(final Long node) { processed.incrementAndGet(); checkValidNode(node, reconnectState); - handleQueue.add(node); + accumulatingHandleQueue.add(node); } /** @@ -196,7 +242,38 @@ public void addToHandleQueue(final Long node) { */ @Override public Long getNextNodeToHandle() { - return handleQueue.remove(); + if (processingHandleQueue.isEmpty()) { + // We've just sent an entire level of the tree, and before we resume sending the next level + // which has been accumulated in the current accumulatingHandleQueue, we'll wait + // until the learner has reported the status of the lastNodeAwaitingReporting. + // Note that in case we're just starting, there hasn't been any nodes sent yet, + // so we have to flip w/o waiting in that case (when it's null.) + if (lastNodeAwaitingReporting != null) { + try { + synchronized (lastNodeAwaitingReporting) { + final long waitStartMillis = System.currentTimeMillis(); + while (!hasLearnerReportedFor(lastNodeAwaitingReporting) + && System.currentTimeMillis() - waitStartMillis + < MAX_TOTAL_AWAIT_FOR_REPORT_TIMEOUT_MILLIS) { + lastNodeAwaitingReporting.wait(AWAIT_FOR_REPORT_TIMEOUT_MILLIS); + } + } + } catch (InterruptedException ignore) { + // We can ignore this. In the worst case, we'll just go ahead + // and send the next level w/o awaiting a report from the learner. + } + } + // Exchange the accumulating queue with the processing queue: + flipQueues(); + // Note that we know the other queue isn't empty because this method has been called after + // the caller checked areThereNodesToHandle() which tests both the queues. + } + final long node = processingHandleQueue.remove(); + // Avoid waiting on a node which status has already been reported/inferred: + if (!hasLearnerReportedFor(node)) { + lastNodeAwaitingReporting = node; + } + return node; } /** @@ -204,7 +281,7 @@ public Long getNextNodeToHandle() { */ @Override public boolean areThereNodesToHandle() { - return !handleQueue.isEmpty(); + return !processingHandleQueue.isEmpty() || !accumulatingHandleQueue.isEmpty(); } /** @@ -242,6 +319,11 @@ public void registerResponseForNode(final Long node, final boolean learnerHasNod ? ConcurrentNodeStatusTracker.Status.KNOWN : ConcurrentNodeStatusTracker.Status.NOT_KNOWN; nodeStatusTracker.set(node, status); + if (node == lastNodeAwaitingReporting) { + synchronized (node) { + node.notifyAll(); + } + } } /** @@ -252,6 +334,17 @@ public boolean hasLearnerConfirmedFor(final Long node) { return nodeStatusTracker.getStatus(node) == ConcurrentNodeStatusTracker.Status.KNOWN; } + /** + * Determines if the status of the given node has been reported either directly, + * or indirectly by reporting the status of an ancestor of the node. + * + * @param node a node + * @return true if the status has been reported by the learner + */ + private boolean hasLearnerReportedFor(final Long node) { + return nodeStatusTracker.getReportedStatus(node) != ConcurrentNodeStatusTracker.Status.UNKNOWN; + } + /** * {@inheritDoc} */ diff --git a/platform-sdk/swirlds-virtualmap/src/main/java/module-info.java b/platform-sdk/swirlds-virtualmap/src/main/java/module-info.java index 1d78ad0707e7..83f9a67d6b61 100644 --- a/platform-sdk/swirlds-virtualmap/src/main/java/module-info.java +++ b/platform-sdk/swirlds-virtualmap/src/main/java/module-info.java @@ -20,6 +20,7 @@ requires com.swirlds.base; requires com.swirlds.config.extensions; requires com.swirlds.logging; + requires com.hedera.pbj.runtime; requires java.management; // Test dependency requires org.apache.logging.log4j; requires static com.github.spotbugs.annotations; diff --git a/platform-sdk/swirlds-virtualmap/src/test/java/com/swirlds/virtualmap/internal/pipeline/DummyVirtualRoot.java b/platform-sdk/swirlds-virtualmap/src/test/java/com/swirlds/virtualmap/internal/pipeline/DummyVirtualRoot.java index 1b069305ac89..23f59be69c50 100644 --- a/platform-sdk/swirlds-virtualmap/src/test/java/com/swirlds/virtualmap/internal/pipeline/DummyVirtualRoot.java +++ b/platform-sdk/swirlds-virtualmap/src/test/java/com/swirlds/virtualmap/internal/pipeline/DummyVirtualRoot.java @@ -51,7 +51,7 @@ class DummyVirtualRoot extends PartialMerkleLeaf implements VirtualRoot, MerkleL private int copyIndex; - private long estimatedSize = 0; + private volatile long estimatedSize = 0; /** * If set, automatically cause a copy to be flushable based on copy index. Only applies to copies made diff --git a/platform-sdk/swirlds-virtualmap/src/test/java/com/swirlds/virtualmap/internal/pipeline/VirtualPipelineTests.java b/platform-sdk/swirlds-virtualmap/src/test/java/com/swirlds/virtualmap/internal/pipeline/VirtualPipelineTests.java index 501585b2760a..354bdd531509 100644 --- a/platform-sdk/swirlds-virtualmap/src/test/java/com/swirlds/virtualmap/internal/pipeline/VirtualPipelineTests.java +++ b/platform-sdk/swirlds-virtualmap/src/test/java/com/swirlds/virtualmap/internal/pipeline/VirtualPipelineTests.java @@ -661,9 +661,16 @@ public void sizeBasedFlushes(int copyCount) throws InterruptedException { // Every 11th copy should be flushed copy.setEstimatedSize(config.copyFlushThreshold() / 10 - 1); } - for (DummyVirtualRoot copy : copies) { - copy.release(); - } + // Release all copies to make them mergeable / flushable. Note that when the first copy is + // released, a thread race between this thread and the pipeline thread starts. It may + // happen that the pipeline will check if a copy should be flushed before it's released, + // which would result in less than expected number of flushed copies. To avoid that, + // pause the pipeline until all copies are released + afterCopy.getPipeline().pausePipelineAndExecute("releaseAll", () -> { + for (DummyVirtualRoot copy : copies) { + copy.release(); + } + }); afterCopy.release(); afterCopy.waitUntilFlushed(); int flushedCount = 0; diff --git a/platform-sdk/swirlds/build.gradle.kts b/platform-sdk/swirlds/build.gradle.kts index 2f6e49716417..f2726a41736f 100644 --- a/platform-sdk/swirlds/build.gradle.kts +++ b/platform-sdk/swirlds/build.gradle.kts @@ -14,7 +14,7 @@ * limitations under the License. */ -plugins { id("com.hedera.hashgraph.application") } +plugins { id("com.hedera.gradle.application") } mainModuleInfo { runtimeOnly("com.swirlds.platform.core") diff --git a/settings.gradle.kts b/settings.gradle.kts index 7475ae31a5cb..5283c9f0bcaa 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -14,13 +14,16 @@ * limitations under the License. */ -pluginManagement { includeBuild("build-logic") } +pluginManagement { includeBuild("gradle/plugins") } -plugins { id("com.hedera.hashgraph.settings") } +plugins { id("com.hedera.gradle.settings") } // "BOM" with versions of 3rd party dependencies include("hedera-dependency-versions") +// Project to aggregate code coverage data for the whole repository into one report +include(":reports", "gradle/reports") + // Hedera Node projects include(":app", "hedera-node/hedera-app") @@ -28,6 +31,8 @@ include(":app-hapi-fees", "hedera-node/hapi-fees") include(":app-hapi-utils", "hedera-node/hapi-utils") +include(":app-service-addressbook", "hedera-node/hedera-addressbook-service") + include(":app-service-consensus", "hedera-node/hedera-consensus-service") include(":app-service-consensus-impl", "hedera-node/hedera-consensus-service-impl") @@ -36,9 +41,9 @@ include(":app-service-contract", "hedera-node/hedera-smart-contract-service") include(":app-service-contract-impl", "hedera-node/hedera-smart-contract-service-impl") -include(":app-service-evm", "hedera-node/hedera-evm") +include(":hedera-evm", "hedera-node/hedera-evm") -include(":app-service-evm-impl", "hedera-node/hedera-evm-impl") +include(":hedera-evm-impl", "hedera-node/hedera-evm-impl") include(":app-service-file", "hedera-node/hedera-file-service") @@ -107,6 +112,8 @@ include(":swirlds-virtualmap", "platform-sdk/swirlds-virtualmap") include(":swirlds-platform-core", "platform-sdk/swirlds-platform-core") +include(":swirlds-state-api", "platform-sdk/swirlds-state-api") + include(":swirlds-cli", "platform-sdk/swirlds-cli") include(":swirlds-benchmarks", "platform-sdk/swirlds-benchmarks") @@ -157,6 +164,6 @@ dependencyResolutionManagement { version("grpc-proto", "1.45.1") version("hapi-proto", hapiProtoVersion) - plugin("pbj", "com.hedera.pbj.pbj-compiler").version("0.8.7") + plugin("pbj", "com.hedera.pbj.pbj-compiler").version("0.8.9") } }